diff --git a/.circleci/config.yml b/.circleci/config.yml index 0d48ec13fa1..5ea5e87218c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ aliases: - &environment docker: # specify the version you desire here - - image: circleci/node:8.9.0 + - image: circleci/node:12.16.1 # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images @@ -92,3 +92,6 @@ workflows: - master jobs: - e2etest + +experimental: + pipelines: true \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 56e4808f985..c64ab379c52 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,7 +15,8 @@ module.exports = { }, "extends": "standard", "plugins": [ - "prebid" + "prebid", + "import" ], "globals": { "$$PREBID_GLOBAL$$": false @@ -27,6 +28,7 @@ module.exports = { "comma-dangle": "off", "semi": "off", "space-before-function-paren": "off", + "import/extensions": ["error", "ignorePackages"], // Exceptions below this line are temporary, so that eslint can be added into the CI process. // Violations of these styles should be fixed, and the exceptions removed over time. @@ -35,8 +37,9 @@ module.exports = { "eqeqeq": "off", "no-return-assign": "off", "no-throw-literal": "off", - "no-undef": "off", + "no-undef": 2, "no-useless-escape": "off", + "no-console": "error" }, "overrides": Object.keys(allowedModules).map((key) => ({ "files": key + "/**/*.js", diff --git a/.nvmrc b/.nvmrc index fa97ecedc28..66df3b7ab2d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -8.9 +12.16.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6188d6c2182..016f4055216 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,9 @@ When you are adding code to Prebid.js, or modifying code that isn't covered by a - 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 you need to check `adloader.loadExternalScript` 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.loadExternalScript` call without affecting external resources +- If your test makes ajax requests, use the global xhr stub in `test/mocks/xhr`. Do not use your own `sinon.useFakeXMLHttpRequest()` or `sinon.createFakeServer()`. - When writing tests you may use ES2015 syntax if desired +- If your test relies on `Window` or `global` object, do not mutate that object directly. Instead, create a separate copy of that object and perform operations on that new copy. ### Test Examples Prebid.js already has many tests. Read them to see how Prebid.js is tested, and for inspiration: diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 4ad8b8ec372..6402fcbbbaa 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -15,10 +15,15 @@ For modules and core platform updates, the initial reviewer should request an ad - If the change is a new feature / change to core prebid.js - review the change with a Tech Lead on the project and make sure they agree with the nature of change. - If the change results in needing updates to docs (such as public API change, module interface etc), add a label for "needs docs" and inform the submitter they must submit a docs PR to update the appropriate area of Prebid.org **before the PR can merge**. Help them with finding where the docs are located on prebid.org if needed. - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/bidder.md file): - - Add support for GDPR consentManagement module > add `gdpr_supported: true` - - Add support for userId module > add `userId: pubCommon, digitrust, newProviderHere` - - Add support for video and/or native mediaTypes > add `media_types: video, native` - - Add support for COPPA > add `coppa_supported: true` + - If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true` + - If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true` + - If they support the US Privacy consentManagementUsp module, add `usp_supported: true` + - If they support one or more userId modules, add `userId: (list of supported vendors)` + - If they support video and/or native mediaTypes add `media_types: video, native`. Note that display is added by default. If you don't support display, add "no-display" as the first entry, e.g. `media_types: no-display, native` + - If they support COPPA, add `coppa_supported: true` + - If they support SChain, add `schain_supported: true` + - If their bidder doesn't work well with safeframed creatives, add `safeframes_ok: false`. This will alert publishers to not use safeframed creatives when creating the ad server entries for their bidder. + - If they're a member of Prebid.org, add `prebid_member: true` - If all above is good, add a `LGTM` comment and request 1 additional core member to review. - Once there is 2 `LGTM` on the PR, merge to master - Ask the submitter to add a PR for documentation if applicable. @@ -29,13 +34,14 @@ For modules and core platform updates, the initial reviewer should request an ad - 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 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 +- Video openrtb params should be read from the ad unit when available; however bidder config can override the ad unit. - 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. @@ -44,11 +50,16 @@ For modules and core platform updates, the initial reviewer should request an ad ## Ticket Coordinator -Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. That person should: +Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. Every Monday morning a reminder is +sent to the prebid-js slack channel with a link to the spreadsheet. If you're on rotation, please check that list each +Monday to see if you're on-duty. + +When on-duty: - Review issues and PRs at least once per weekday for new items. Encourage a 48 "SLA" on PRs/issues assigned. Aim for touchpoint once every 48/hours. -- For PRs: assign PRs to individuals on the PR review list. Try to be equitable -- not all PRs are created equally. Use the "Assigned" field and add the "Needs Review" label. +- For 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. Please add labels as appropriate (I.E. bug, question, backlog etc). - Issues that are questions or troubleshooting requests may be closed if the originator doesn't respond within a week to requests for confirmation or details. - Issues that are bug reports should be left open and assigned to someone in PR rotation to confirm or deny the bug status. -- It's polite to check with others before assigning them large tasks. -- If possible, check in on older items and see if they can be unstuck. +- It's polite to check with others before assigning them extra-large tasks. +- If possible, check in on older PRs and Issues and see if they can be unstuck. +- Perform the weekly Prebid.js release per instructions at https://github.com/prebid/Prebid.js/blob/master/RELEASE_SCHEDULE.md . This generally takes place on Tues or Weds. diff --git a/README.md b/README.md index be07a27ddc1..b3f33b27aa5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ This README is for developers who want to contribute to Prebid.js. Additional documentation can be found at [the Prebid homepage](http://prebid.org). Working examples can be found in [the developer docs](http://prebid.org/dev-docs/getting-started.html). +Prebid.js is open source software that is offered for free as a convenience. While it is designed to help companies address legal requirements associated with header bidding, we cannot and do not warrant that your use of Prebid.js will satisfy legal requirements. You are solely responsible for ensuring that your use of Prebid.js complies with all applicable laws. We strongly encourage you to obtain legal advice when using Prebid.js to ensure your implementation complies with all laws where you operate. + **Table of Contents** - [Usage](#Usage) @@ -53,7 +55,7 @@ module.exports = { // override the regular exclusion from above (for being inside node_modules). { test: /.js$/, - include: new RegExp(`\\${path.sep}prebid\.js`), + include: new RegExp(`\\${path.sep}prebid\\.js`), use: { loader: 'babel-loader', // presets and plugins for Prebid.js must be manually specified separate from your other babel rule. @@ -112,7 +114,7 @@ prebid.requestBids({ $ cd Prebid.js $ npm install -*Note:* You need to have `NodeJS` 8.9.x or greater installed. +*Note:* You need to have `NodeJS` 12.16.1 or greater installed. *Note:* In the 1.24.0 release of Prebid.js we have transitioned to using gulp 4.0 from using gulp 3.9.1. To comply with gulp's recommended setup for 4.0, you'll need to have `gulp-cli` installed globally prior to running the general `npm install`. This shouldn't impact any other projects you may work on that use an earlier version of gulp in its setup. diff --git a/allowedModules.js b/allowedModules.js index 0d4a36308c3..d8e8b69f593 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -1,11 +1,11 @@ const sharedWhiteList = [ - "core-js/library/fn/array/find", // no ie11 - "core-js/library/fn/array/includes", // no ie11 - "core-js/library/fn/set", // ie11 supports Set but not Set#values - "core-js/library/fn/string/includes", // no ie11 - "core-js/library/fn/number/is-integer", // no ie11, - "core-js/library/fn/array/from" // no ie11 + 'core-js-pure/features/array/find', // no ie11 + 'core-js-pure/features/array/includes', // no ie11 + 'core-js-pure/features/set', // ie11 supports Set but not Set#values + 'core-js-pure/features/string/includes', // no ie11 + 'core-js-pure/features/number/is-integer', // no ie11, + 'core-js-pure/features/array/from' // no ie11 ]; module.exports = { @@ -13,7 +13,8 @@ module.exports = { ...sharedWhiteList, 'criteo-direct-rsa-validate', 'jsencrypt', - 'crypto-js' + 'crypto-js', + 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/ ], 'src': [ ...sharedWhiteList, diff --git a/browsers.json b/browsers.json index 9042d7d0627..91e0548d78a 100644 --- a/browsers.json +++ b/browsers.json @@ -7,11 +7,11 @@ "device": null, "os": "Windows" }, - "bs_edge_16_windows_10": { + "bs_edge_18_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "edge", - "browser_version": "16.0", + "browser_version": "18.0", "device": null, "os": "Windows" }, @@ -23,43 +23,43 @@ "device": null, "os": "Windows" }, - "bs_chrome_74_windows_10": { + "bs_chrome_80_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "74.0", + "browser_version": "80.0", "device": null, "os": "Windows" }, - "bs_chrome_75_windows_10": { + "bs_chrome_79_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "75.0", + "browser_version": "79.0", "device": null, "os": "Windows" }, - "bs_firefox_66_windows_10": { + "bs_firefox_73_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "firefox", - "browser_version": "66.0", + "browser_version": "73.0", "device": null, "os": "Windows" }, - "bs_firefox_67_windows_10": { + "bs_firefox_72_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "firefox", - "browser_version": "67.0", + "browser_version": "72.0", "device": null, "os": "Windows" }, - "bs_safari_11_mac_high_sierra": { + "bs_safari_11_mac_catalina": { "base": "BrowserStack", - "os_version": "High Sierra", + "os_version": "Catalina", "browser": "safari", - "browser_version": "11.1", + "browser_version": "13.0", "device": null, "os": "OS X" }, diff --git a/gulpHelpers.js b/gulpHelpers.js index 84f01b4e966..bcaf3736f15 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -59,6 +59,7 @@ module.exports = { }); } + // we need to forcefuly include the parentModule if the subModule is present in modules list and parentModule is not present in modules list Object.keys(submodules).forEach(parentModule => { if ( !modules.includes(parentModule) && @@ -83,7 +84,9 @@ module.exports = { if (fs.lstatSync(modulePath).isDirectory()) { modulePath = path.join(modulePath, 'index.js') } - memo[modulePath] = moduleName; + if (fs.existsSync(modulePath)) { + memo[modulePath] = moduleName; + } return memo; }, {}); } catch (err) { diff --git a/gulpfile.js b/gulpfile.js index 7c4ca306886..64152baa7ba 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict'; var _ = require('lodash'); @@ -32,8 +33,8 @@ var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n'; var port = 9999; -const mockServerPort = 4444; -const host = argv.host ? argv.host : 'localhost'; +const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; +const FAKE_SERVER_PORT = 4444; const { spawn } = require('child_process'); // these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules @@ -67,11 +68,11 @@ function lint(done) { if (argv.nolint) { return done(); } - const isFixed = function(file) { + const isFixed = function (file) { return file.eslint != null && file.eslint.fixed; } - return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'], {base: './'}) - .pipe(gulpif(argv.nolintfix, eslint(), eslint({fix: true}))) + return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'], { base: './' }) + .pipe(gulpif(argv.nolintfix, eslint(), eslint({ fix: true }))) .pipe(eslint.format('stylish')) .pipe(eslint.failAfterError()) .pipe(gulpif(isFixed, gulp.dest('./'))); @@ -84,7 +85,7 @@ function viewCoverage(done) { connect.server({ port: coveragePort, - root: 'build/coverage/karma_html', + root: 'build/coverage/lcov-report', livereload: false }); opens('http://' + mylocalhost + ':' + coveragePort); @@ -160,7 +161,7 @@ function nodeBundle(modules) { .on('error', (err) => { reject(err); }) - .pipe(through.obj(function(file, enc, done) { + .pipe(through.obj(function (file, enc, done) { resolve(file.contents.toString(enc)); done(); })); @@ -199,7 +200,7 @@ function bundle(dev, moduleArr) { return gulp.src( entries ) - .pipe(gulpif(dev, sourcemaps.init({loadMaps: true}))) + .pipe(gulpif(dev, sourcemaps.init({ loadMaps: true }))) .pipe(concat(outputFileName)) .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', { global: prebid.globalVarName @@ -238,25 +239,25 @@ function test(done) { ]; } - //run mock-server - const mockServer = spawn('node', ['./test/mock-server/index.js', '--port='+mockServerPort]); - mockServer.stdout.on('data', (data) => { + // run fake-server + const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); + fakeServer.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); - mockServer.stderr.on('data', (data) => { + fakeServer.stderr.on('data', (data) => { console.log(`stderr: ${data}`); }); execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) .then(stdout => { - //kill mock server - mockServer.kill('SIGINT'); + // kill fake server + fakeServer.kill('SIGINT'); done(); process.exit(0); }) .catch(err => { - //kill mock server - mockServer.kill('SIGINT'); + // kill fake server + fakeServer.kill('SIGINT'); done(new Error(`Tests failed with error: ${err}`)); process.exit(1); }); @@ -273,7 +274,7 @@ function test(done) { } function newKarmaCallback(done) { - return function(exitCode) { + return function (exitCode) { if (exitCode) { done(new Error('Karma tests failed with exit code ' + exitCode)); if (argv.browserstack) { @@ -296,7 +297,7 @@ function testCoverage(done) { function coveralls() { // 2nd arg is a dependency: 'test' must be finished // first send results of istanbul's test coverage to coveralls.io. return gulp.src('gulpfile.js', { read: false }) // You have to give it a file, but you don't - // have to read it. + // have to read it. .pipe(shell('cat build/coverage/lcov.info | node_modules/coveralls/bin/coveralls.js')); } @@ -326,11 +327,27 @@ function setupE2e(done) { done(); } -gulp.task('updatepath', function(){ +function injectFakeServerEndpoint() { return gulp.src(['build/dist/*.js']) - .pipe(replace('ib.adnxs.com/ut/v3/prebid', host + ':' + mockServerPort + '/')) - .pipe(gulp.dest('build/dist')); -}); + .pipe(replace('https://ib.adnxs.com/ut/v3/prebid', `http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}`)) + .pipe(gulp.dest('build/dist')); +} + +function injectFakeServerEndpointDev() { + return gulp.src(['build/dev/*.js']) + .pipe(replace('https://ib.adnxs.com/ut/v3/prebid', `http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}`)) + .pipe(gulp.dest('build/dev')); +} + +function startFakeServer() { + const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); + fakeServer.stdout.on('data', (data) => { + console.log(`stdout: ${data}`); + }); + fakeServer.stderr.on('data', (data) => { + console.log(`stderr: ${data}`); + }); +} // support tasks gulp.task(lint); @@ -355,9 +372,12 @@ gulp.task('build', gulp.series(clean, 'build-bundle-prod')); gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); +gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watch))); +gulp.task('serve-fake', gulp.series(clean, gulp.parallel('build-bundle-dev', watch), injectFakeServerEndpointDev, test, startFakeServer)); + gulp.task('default', gulp.series(clean, makeWebpackPkg)); -gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), 'updatepath', test)); +gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), injectFakeServerEndpoint, test)); // other tasks gulp.task(bundleToStdout); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step diff --git a/integrationExamples/gpt/advanced_size_mapping.html b/integrationExamples/gpt/advanced_size_mapping.html new file mode 100644 index 00000000000..4f1ba085c77 --- /dev/null +++ b/integrationExamples/gpt/advanced_size_mapping.html @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + +

Prebid.js Test

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

Prebid.js Test

-
- -
-
-

Audience Network quick start

-
    -
  1. Create a new App at https://developers.facebook.com/apps
  2. -
  3. Add the Audience Network product to it
  4. -
  5. Create a new Placement to generate your placementId
  6. -
  7. To test, ensure the User-Agent request header represents a mobile device
  8. -
-
- - diff --git a/integrationExamples/gpt/digitrust_Full.html b/integrationExamples/gpt/audigentSegments_example.html similarity index 64% rename from integrationExamples/gpt/digitrust_Full.html rename to integrationExamples/gpt/audigentSegments_example.html index 7ec268a619a..7739b558327 100644 --- a/integrationExamples/gpt/digitrust_Full.html +++ b/integrationExamples/gpt/audigentSegments_example.html @@ -1,6 +1,5 @@ - Full DigiTrust Prebid Sample - - +
+ +
+TDID: +
+
+Audigent Segments: +
+
- + diff --git a/integrationExamples/gpt/cmp_files/purposes.json b/integrationExamples/gpt/cmp_files/purposes.json new file mode 100644 index 00000000000..04219e92ce4 --- /dev/null +++ b/integrationExamples/gpt/cmp_files/purposes.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "purposes": [ + { + "id": 25, + "name": "Custom Purpose 1", + "description": "Here's a description of the first purpose" + }, + { + "id": 26, + "name": "Custom Purpose 2", + "description": "Here's a description of the second purpose" + }, + { + "id": 27, + "name": "Custom Purpose 3", + "description": "Here's a description of the third purpose" + }, + { + "id": 28, + "name": "Custom Purpose 4", + "description": "Here's a description of the fourth purpose" + } + ] +} diff --git a/integrationExamples/gpt/digitrust_Simple.html b/integrationExamples/gpt/digitrust_Simple.html deleted file mode 100644 index c9a8c1d2ad6..00000000000 --- a/integrationExamples/gpt/digitrust_Simple.html +++ /dev/null @@ -1,220 +0,0 @@ - - - Simple DigiTrust Prebid - No Framework - - - - - - - - - - - - - - -

DigiTrust Prebid Sample - No Framework

- -

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

-
- -
- -
- - diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html old mode 100644 new mode 100755 index d68e65011be..47ba5b8f18a --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -88,4 +88,4 @@
Div-1
- + \ No newline at end of file diff --git a/integrationExamples/gpt/revcontent_example.html b/integrationExamples/gpt/revcontent_example.html new file mode 100644 index 00000000000..d7a44df3014 --- /dev/null +++ b/integrationExamples/gpt/revcontent_example.html @@ -0,0 +1,109 @@ + + + + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 6d2c2ce677a..4acbe0aae31 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -140,7 +140,18 @@ name: "unifiedid", expires: 30 }, - }, { + },{ + name: "intentIqId", + params: { + partner: 0, //Set your real IntentIQ partner ID here for production. + }, + storage: { + type: "cookie", + name: "intentIqId", + expires: 30 + }, + }, + { name: "id5Id", params: { partner: 173 //Set your real ID5 partner ID here for production, please ask for one at http://id5.io/prebid @@ -157,11 +168,6 @@ params: { // change to Parrable Partner Client ID(s) you received from the Parrable Partners you are using partner: '30182847-e426-4ff9-b2b5-9ca1324ea09b' - }, - storage: { - type: "cookie", - name: "_parrable_eid", // create a cookie with this name - expires: 365 // cookie can last for a year } }, { name: "pubCommonId", @@ -184,9 +190,34 @@ name: 'idl_env', expires: 30 } + }, + { + name: "sharedId", + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + type: "cookie", + name: "sharedid", + expires: 28 + } + }, + { + name: 'lotamePanoramaId' + }, + { + name: "liveIntentId", + params: { + publisherId: "9896876" + }, + storage: { + type: "cookie", + name: "_li_pbid", + expires: 28 + } }], syncDelay: 5000, - auctionDelay: 1000 + auctionDelay: 1000 } }); pbjs.addAdUnits(adUnits); diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index a6981706227..fce46bb380f 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -6,7 +6,6 @@ var urlParser = document.createElement('a'); urlParser.href = '%%PATTERN:url%%'; var publisherDomain = urlParser.protocol + '//' + urlParser.hostname; -var adServerDomain = windowLocation.protocol + '//tpc.googlesyndication.com'; function renderAd(ev) { var key = ev.message ? 'message' : 'data'; @@ -58,8 +57,7 @@ function requestAdFromPrebid() { var message = JSON.stringify({ message: 'Prebid Request', - adId: '%%PATTERN:hb_adid%%', - adServerDomain: adServerDomain + adId: '%%PATTERN:hb_adid%%' }); window.parent.postMessage(message, publisherDomain); } diff --git a/integrationExamples/longform/basic_w_bidderSettings.html b/integrationExamples/longform/basic_w_bidderSettings.html index f9389686b1f..fb87ea5d990 100644 --- a/integrationExamples/longform/basic_w_bidderSettings.html +++ b/integrationExamples/longform/basic_w_bidderSettings.html @@ -5,6 +5,10 @@ Prebid Freewheel Integration Demo + + + - + + - + + - + + - + @@ -20,10 +24,9 @@ integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> - + - + + - `; + trackedAd += tracker; + }); + } + + return trackedAd; +} + +function getScreenParams() { + return `${window.screen.width}x${window.screen.height}@${window.devicePixelRatio}`; +} + +function getBids(bids) { + const bidArr = bids.map(bid => { + const bidId = bid.bidId; + + let mediaType = ''; + const mediaTypes = Object.keys(bid.mediaTypes) + switch (mediaTypes[0]) { + case 'video': + mediaType = 'v'; + break; + + case 'native': + mediaType = 'n'; + break; + + case 'audio': + mediaType = 'a'; + break; + + default: + mediaType = 'b'; + break; + } + + let adUnitCode = `,c=${bid.adUnitCode}`; + if (bid.params.code) { + adUnitCode = `,c=${encodeURIComponent(bid.params.code)}`; + } + if (bid.params.adunitId) { + adUnitCode = `,u=${encodeURIComponent(bid.params.adunitId)}`; + } + + return `${bidId}:t=${mediaType},s=${serializeSizes(bid.sizes)}${adUnitCode}`; + }); + + return bidArr.join(';'); +}; + +function getEndpointsGroups(bidRequests) { + let endpoints = []; + const getEndpoint = bid => { + if (bid.params.test) { + return `https://mock-bapi.userreport.com/v2/${bid.params.publisherId}/bid`; + } + + if (bid.params.endpoint) { + return `${bid.params.endpoint}${bid.params.publisherId}/bid`; + } + + return `https://bapi.userreport.com/v2/${bid.params.publisherId}/bid`; + } + bidRequests.forEach(bid => { + const exist = endpoints.filter(item => item.endpoint.indexOf(bid.params.endpoint) > -1)[0]; + if (exist) { + exist.bids.push(bid); + } else { + endpoints.push({ + endpoint: getEndpoint(bid), + bids: [bid] + }); + } + }); + + return endpoints; +} + +function isBidRequestValid(bid) { + const isPublisherIdExist = !!bid.params.publisherId; + const isOneMediaType = Object.keys(bid.mediaTypes).length === 1; + + return isPublisherIdExist && isOneMediaType; +} + +function buildRequests(bidRequests, bidderRequest) { + const data = { + med: encodeURIComponent(window.location.href), + auid: bidderRequest.auctionId, + ref: document.referrer, + dnt: utils.getDNT() ? 1 : 0, + sr: getScreenParams() + }; + + const consentData = getRawConsentString(bidderRequest.gdprConsent); + data.iab_consent = consentData; + + const options = { + withCredentials: true + }; + + const isConsent = getIabConsentString(bidderRequest); + const noDsu = config.getConfig('apstream.noDsu'); + if (!isConsent || noDsu) { + data.dsu = ''; + } else { + data.dsu = dsuModule.readOrCreateDsu(); + } + + if (!isConsent || isConsent === 'disabled') { + options.withCredentials = false; + } + + const endpoints = getEndpointsGroups(bidRequests); + const serverRequests = endpoints.map(item => ({ + method: 'GET', + url: item.endpoint, + data: { + ...data, + bids: getBids(item.bids), + rnd: Math.random() + }, + options: options + })); + + return serverRequests; +} + +function interpretResponse(serverResponse) { + let bidResponses = serverResponse && serverResponse.body; + + if (!bidResponses || !bidResponses.length) { + return []; + } + + return bidResponses.map(x => ({ + requestId: x.bidId, + cpm: x.bidDetails.cpm, + width: x.bidDetails.width, + height: x.bidDetails.height, + creativeId: x.bidDetails.creativeId, + currency: x.bidDetails.currency || 'USD', + netRevenue: x.bidDetails.netRevenue, + dealId: x.bidDetails.dealId, + ad: injectPixels(x.bidDetails.ad, x.bidDetails.noticeUrls, x.bidDetails.impressionScripts), + ttl: x.bidDetails.ttl, + })); +} + +export const spec = { + code: CONSTANTS.BIDDER_CODE, + gvlid: CONSTANTS.GVLID, + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse +} + +registerBidder(spec); diff --git a/modules/apstreamBidAdapter.md b/modules/apstreamBidAdapter.md new file mode 100644 index 00000000000..e528307a003 --- /dev/null +++ b/modules/apstreamBidAdapter.md @@ -0,0 +1,97 @@ +# Overview + +``` +Module Name: AP Stream Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@audienceproject.com +gdpr_supported: true +tcf2_supported: true +``` + +# Description + +Module that connects to AP Stream source + +# Inherit from prebid.js +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID // mandatory + } + }] + } + ]; +``` + +# Explicit ad-unit code +``` + var website = null; + switch (location.hostname) { + case "site1.com": + website = "S1"; + break; + case "site2.com": + website = "S2"; + break; + } + + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID, // mandatory + code: website + '_Leaderboard' + } + }] + } + ]; +``` + +# Explicit ad-unit ID +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID, // mandatory + adunitId: 1234 + } + }] + } + ]; +``` + +# DSU + +To disable DSU use config option: + +``` + pbjs.setConfig({ + apstream: { + noDsu: true + } + }); +``` diff --git a/modules/astraoneBidAdapter.js b/modules/astraoneBidAdapter.js index efff699b0dc..7e98c1022d2 100644 --- a/modules/astraoneBidAdapter.js +++ b/modules/astraoneBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils' -import { registerBidder } from '../src/adapters/bidderFactory' -import { BANNER } from '../src/mediaTypes' +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER } from '../src/mediaTypes.js' const BIDDER_CODE = 'astraone'; const SSP_ENDPOINT = 'https://ssp.astraone.io/auction/prebid'; diff --git a/modules/atomxBidAdapter.js b/modules/atomxBidAdapter.js index 86ad02cfd9a..e9f15218c4c 100644 --- a/modules/atomxBidAdapter.js +++ b/modules/atomxBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'atomx'; diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js new file mode 100644 index 00000000000..ad488aa50d9 --- /dev/null +++ b/modules/atsAnalyticsAdapter.js @@ -0,0 +1,171 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import CONSTANTS from '../src/constants.json'; +import adaptermanager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; + +const analyticsType = 'endpoint'; + +let handlerRequest = []; +let handlerResponse = []; +let host = ''; + +function bidRequestedHandler(args) { + let requests; + requests = args.bids.map(function(bid) { + return { + has_envelope: bid.userId ? !!bid.userId.idl_env : false, + bidder: bid.bidder, + bid_id: bid.bidId, + auction_id: args.auctionId, + user_browser: checkUserBrowser(), + user_platform: navigator.platform, + auction_start: new Date(args.auctionStart).toJSON(), + domain: window.location.hostname, + pid: atsAnalyticsAdapter.context.pid, + }; + }); + return requests; +} + +function bidResponseHandler(args) { + return { + bid_id: args.requestId, + response_time_stamp: new Date(args.responseTimestamp).toJSON(), + currency: args.currency, + cpm: args.cpm, + net_revenue: args.netRevenue + }; +} + +export function checkUserBrowser() { + let firefox = browserIsFirefox(); + let chrome = browserIsChrome(); + let edge = browserIsEdge(); + let safari = browserIsSafari(); + if (firefox) { + return firefox; + } if (chrome) { + return chrome; + } if (edge) { + return edge; + } if (safari) { + return safari; + } else { + return 'Unknown' + } +} + +export function browserIsFirefox() { + if (typeof InstallTrigger !== 'undefined') { + return 'Firefox'; + } else { + return false; + } +} + +export function browserIsIE() { + return !!document.documentMode; +} + +export function browserIsEdge() { + if (!browserIsIE() && !!window.StyleMedia) { + return 'Edge'; + } else { + return false; + } +} + +export function browserIsChrome() { + if ((!!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)) || (/Android/i.test(navigator.userAgent) && !!window.chrome)) { + return 'Chrome'; + } else { + return false; + } +} + +export function browserIsSafari() { + if (window.safari !== undefined) { + return 'Safari' + } else { + return false; + } +} + +function callHandler(evtype, args) { + if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) { + handlerRequest = handlerRequest.concat(bidRequestedHandler(args)); + } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) { + handlerResponse.push(bidResponseHandler(args)); + } + if (evtype === CONSTANTS.EVENTS.AUCTION_END) { + if (handlerRequest.length) { + let events = []; + if (handlerResponse.length) { + events = handlerRequest.filter(request => handlerResponse.filter(function(response) { + if (request.bid_id === response.bid_id) { + Object.assign(request, response); + } + })); + } else { + events = handlerRequest; + } + atsAnalyticsAdapter.context.events = events; + } + } +} + +let atsAnalyticsAdapter = Object.assign(adapter( + { + host, + analyticsType + }), +{ + track({eventType, args}) { + if (typeof args !== 'undefined') { + callHandler(eventType, args); + } + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + // send data to ats analytic endpoint + try { + let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; + let strJSON = JSON.stringify(dataToSend); + ajax(atsAnalyticsAdapter.context.host, function () { + }, strJSON, {method: 'POST', contentType: 'application/json'}); + } catch (err) { + } + } + } +}); + +// save the base class function +atsAnalyticsAdapter.originEnableAnalytics = atsAnalyticsAdapter.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +atsAnalyticsAdapter.enableAnalytics = function (config) { + if (!config.options.pid) { + utils.logError('Publisher ID (pid) option is not defined. Analytics won\'t work'); + return; + } + + if (!config.options.host) { + utils.logError('Host option is not defined. Analytics won\'t work'); + return; + } + + host = config.options.host; + atsAnalyticsAdapter.context = { + events: [], + host: config.options.host, + pid: config.options.pid + }; + let initOptions = config.options; + atsAnalyticsAdapter.originEnableAnalytics(initOptions); // call the base class function +}; + +adaptermanager.registerAnalyticsAdapter({ + adapter: atsAnalyticsAdapter, + code: 'atsAnalytics' +}); + +export default atsAnalyticsAdapter; diff --git a/modules/atsAnalyticsAdapter.md b/modules/atsAnalyticsAdapter.md new file mode 100644 index 00000000000..560ad237aa0 --- /dev/null +++ b/modules/atsAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview + +``` +Module Name: Ats Analytics Adapter +Module Type: Analytics Adapter +Maintainer: marko.matic@liveramp.com +``` + +# Description + +Analytics adapter for Authenticated Traffic Solution(ATS), provided by LiveRamp. + +# Test Parameters + +``` +{ + provider: 'atsAnalytics', + options: { + pid: '999', // publisher ID + host: 'https://example.com' // host is provided to publisher + } +} +``` diff --git a/modules/audienceNetworkBidAdapter.md b/modules/audienceNetworkBidAdapter.md deleted file mode 100644 index 6147191f4b7..00000000000 --- a/modules/audienceNetworkBidAdapter.md +++ /dev/null @@ -1,49 +0,0 @@ -# Overview - -Module Name: Audience Network Bid Adapter - -Module Type: Bidder Adapter - -Maintainer: Lovell Fuller - -# Parameters - -| Name | Scope | Description | Example | -| :------------ | :------- | :---------------------------------------------- | :--------------------------------- | -| `placementId` | required | The Placement ID from Audience Network | "555555555555555\_555555555555555" | -| `format` | optional | Format, one of "native" or "video" | "native" | - -# Example ad units - -```javascript -const adUnits = [{ - code: "test-iab", - sizes: [[300, 250]], - bids: [{ - bidder: "audienceNetwork", - params: { - placementId: "555555555555555_555555555555555" - } - }] -}, { - code: "test-native", - sizes: [[300, 250]], - bids: [{ - bidder: "audienceNetwork", - params: { - format: "native", - placementId: "555555555555555_555555555555555" - } - }] -}, { - code: "test-video", - sizes: [[640, 360]], - bids: [{ - bidder: "audienceNetwork", - params: { - format: "video", - placementId: "555555555555555_555555555555555" - } - }] -}]; -``` diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js new file mode 100644 index 00000000000..b90471ee21a --- /dev/null +++ b/modules/audiencerunBidAdapter.js @@ -0,0 +1,142 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'audiencerun'; +const ENDPOINT_URL = 'https://d.audiencerun.com/prebid'; + +export const spec = { + version: '1.0.0', + 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) { + let isValid = true; + if (!utils.deepAccess(bid, 'params.zoneId')) { + utils.logError('AudienceRun zoneId parameter is required. Bid aborted.'); + isValid = false; + } + return isValid; + }, + + /** + * 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. + * @param {*} bidderRequest + * @return {ServerRequest} Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const bids = bidRequests.map(bid => { + const sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes', []); + return { + zoneId: utils.getValue(bid.params, 'zoneId'), + sizes: sizes.map(size => ({ + w: size[0], + h: size[1] + })), + bidfloor: bid.params.bidfloor || 0.0, + bidId: bid.bidId, + bidderRequestId: utils.getBidIdParameter('bidderRequestId', bid), + adUnitCode: utils.getBidIdParameter('adUnitCode', bid), + auctionId: utils.getBidIdParameter('auctionId', bid), + transactionId: utils.getBidIdParameter('transactionId', bid) + }; + }); + + const payload = { + libVersion: this.version, + referer: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null, + currencyCode: config.getConfig('currency.adServerCurrency'), + timeout: config.getConfig('bidderTimeout'), + bids + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = { + consent: bidderRequest.gdprConsent.consentString, + applies: bidderRequest.gdprConsent.gdprApplies + }; + } else { + payload.gdpr = { + consent: '' + } + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + options: { + withCredentials: true + } + }; + }, + + /** + * 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 bids = []; + utils._each(serverResponse.body.bid, function (bidObject) { + if (!bidObject.cpm || bidObject.cpm === null || !bidObject.adm) { + return; + } + + const bid = {}; + + bid.ad = bidObject.adm; + bid.mediaType = BANNER; + + // Common properties + bid.requestId = bidObject.bidId; + bid.adId = bidObject.zoneId; + bid.cpm = parseFloat(bidObject.cpm); + bid.creativeId = bidObject.crid; + bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; + + bid.height = bidObject.h; + bid.width = bidObject.w; + bid.netRevenue = bidObject.isNet ? bidObject.isNet : false; + bid.ttl = 300; + + bids.push(bid); + }); + return bids; + }, + + /** + * 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 (!serverResponses || !serverResponses.length) return []; + + const syncs = []; + serverResponses.forEach(response => { + response.body.bid.forEach(bidObject => { + syncs.push({ + type: 'iframe', + url: bidObject.syncUrl + }); + }); + }); + + return syncs; + } +}; + +registerBidder(spec); diff --git a/modules/audiencerunBidAdapter.md b/modules/audiencerunBidAdapter.md new file mode 100644 index 00000000000..3704922fdd5 --- /dev/null +++ b/modules/audiencerunBidAdapter.md @@ -0,0 +1,48 @@ +# Overview + +**Module Name**: AudienceRun Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@audiencerun.com + +# Description + +Module that connects to AudienceRun demand sources + +Use `audiencerun` as bidder. + +`zoneId` is required and must be 10 alphanumeric characters. + +## AdUnits configuration example +``` + var adUnits = [{ + code: 'ad-slot-300x600', + mediaTypes: { + banner: { + sizes: [ + [300, 600] + ], + } + }, + bids: [{ + bidder: 'audiencerun', + params: { + zoneId: 'xtov2mgij0' + } + }] + },{ + code: 'ad-slot-728x90', + mediaTypes: { + banner: { + sizes: [ + [728, 90] + ], + } + }, + bids: [{ + bidder: 'audiencerun', + params: { + zoneId: 'u4q6z6u97b' + } + }] + }]; +``` diff --git a/modules/audigentRtdProvider.js b/modules/audigentRtdProvider.js new file mode 100644 index 00000000000..0f32c84962f --- /dev/null +++ b/modules/audigentRtdProvider.js @@ -0,0 +1,141 @@ +/** + * This module adds audigent provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch segments from audigent server + * @module modules/audigentRtdProvider + * @requires module:modules/realTimeData + */ + +/** + * @typedef {Object} ModuleParams + * @property {string} siteKey + * @property {string} pubKey + * @property {string} url + * @property {?string} keyName + * @property {number} auctionDelay + */ + +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import * as utils from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; + +/** @type {ModuleParams} */ +let _moduleParams = {}; + +/** + * XMLHttpRequest to get data form audigent server + * @param {string} url server url with query params + */ + +export function setData(data) { + storage.setDataInLocalStorage('__adgntseg', JSON.stringify(data)); +} + +function getSegments(adUnits, onDone) { + try { + let jsonData = storage.getDataFromLocalStorage('__adgntseg'); + if (jsonData) { + let data = JSON.parse(jsonData); + if (data.audigent_segments) { + let dataToReturn = adUnits.reduce((rp, cau) => { + const adUnitCode = cau && cau.code; + if (!adUnitCode) { return rp } + rp[adUnitCode] = data; + return rp; + }, {}); + + onDone(dataToReturn); + return; + } + } + getSegmentsAsync(adUnits, onDone); + } catch (e) { + getSegmentsAsync(adUnits, onDone); + } +} + +function getSegmentsAsync(adUnits, onDone) { + const userIds = (getGlobal()).getUserIds(); + let tdid = null; + + if (userIds && userIds['tdid']) { + tdid = userIds['tdid']; + } else { + onDone({}); + } + + const url = `https://seg.ad.gt/api/v1/rtb_segments?tdid=${tdid}`; + + ajax(url, { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.audigent_segments) { + setData(data); + let dataToReturn = adUnits.reduce((rp, cau) => { + const adUnitCode = cau && cau.code; + if (!adUnitCode) { return rp } + rp[adUnitCode] = data; + return rp; + }, {}); + + onDone(dataToReturn); + } else { + onDone({}); + } + } catch (err) { + utils.logError('unable to parse audigent segment data'); + onDone({}) + } + } else if (req.status === 204) { + // unrecognized site key + onDone({}); + } + }, + error: function () { + onDone({}); + utils.logError('unable to get audigent segment data'); + } + } + ); +} + +/** @type {RtdSubmodule} */ +export const audigentSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: 'audigent', + /** + * get data and send back to realTimeData module + * @function + * @param {adUnit[]} adUnits + * @param {function} onDone + */ + getData: getSegments +}; + +export function init(config) { + const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { + try { + _moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter(pr => pr.name && pr.name.toLowerCase() === 'audigent')[0].params; + _moduleParams.auctionDelay = realTimeData.auctionDelay; + } catch (e) { + _moduleParams = {}; + } + confListener(); + }); +} + +submodule('realTimeData', audigentSubmodule); +init(config); diff --git a/modules/audigentRtdProvider.md b/modules/audigentRtdProvider.md new file mode 100644 index 00000000000..47bcbbbf951 --- /dev/null +++ b/modules/audigentRtdProvider.md @@ -0,0 +1,52 @@ +Audigent is a next-generation data management platform and a first-of-a-kind +"data agency" containing some of the most exclusive content-consuming audiences +across desktop, mobile and social platforms. + +This real-time data module provides first-party Audigent segments that can be +attached to bid request objects destined for different SSPs in order to optimize +targeting. Audigent maintains a large database of first-party Tradedesk Unified +ID to third party segment mappings that can now be queried at bid-time. + +Usage: + +Compile the audigent RTD module into your Prebid build: + +`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` + +Audigent segments will then be attached to each bid request objects in +`bid.realTimeData.audigent_segments` + +The format of the segments is a per-SSP mapping: + +``` +{ + 'appnexus': ['anseg1', 'anseg2'], + 'google': ['gseg1', 'gseg2'] +} +``` + +If a given SSP's API backend supports segment fields, they can then be +attached prior to the bid request being sent: + +``` +pbjs.requestBids({bidsBackHandler: addAudigentSegments}); + +function addAudigentSegments() { + for (i = 0; i < adUnits.length; i++) { + let adUnit = adUnits[i]; + for (j = 0; j < adUnit.bids.length; j++) { + adUnit.bids[j].userId.lipb.segments = adUnit.bids[j].realTimeData.audigent_segments['rubicon']; + } + } +} +``` + +To view an example of the segments returned by Audigent's backends: + +`gulp serve --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/audigentSegments_example.html` + + diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js new file mode 100644 index 00000000000..414cadcd405 --- /dev/null +++ b/modules/automatadBidAdapter.js @@ -0,0 +1,128 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js' +import * as utils from '../src/utils.js' +import {BANNER} from '../src/mediaTypes.js' +import {ajax} from '../src/ajax.js' + +const BIDDER = 'automatad' + +const ENDPOINT_URL = 'https://rtb2.automatad.com/ortb2' + +const DEFAULT_BID_TTL = 30 +const DEFAULT_CURRENCY = 'USD' +const DEFAULT_NET_REVENUE = true + +export const spec = { + code: BIDDER, + aliases: ['atd'], + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + // will receive request bid. check if have necessary params for bidding + return (bid && bid.hasOwnProperty('params') && bid.params.hasOwnProperty('siteId') && bid.params.hasOwnProperty('placementId') && bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty('banner')) + }, + + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return + } + + const siteId = validBidRequests[0].params.siteId + const placementId = validBidRequests[0].params.placementId + + const impressions = validBidRequests.map(bidRequest => { + return { + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + banner: { + format: bidRequest.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1], + })) + }, + } + }) + + // params from bid request + const openrtbRequest = { + id: validBidRequests[0].auctionId, + imp: impressions, + site: { + id: siteId, + placement: placementId, + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null, + }, + } + + const payloadString = JSON.stringify(openrtbRequest) + return { + method: 'POST', + url: ENDPOINT_URL + '/resp', + data: payloadString, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + }, + } + }, + + interpretResponse: function (serverResponse, request) { + const bidResponses = [] + const response = (serverResponse || {}).body + + if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid[0].bid.forEach(bid => { + bidResponses.push({ + requestId: bid.impid, + cpm: bid.price, + ad: bid.adm, + adDomain: bid.adomain[0], + currency: DEFAULT_CURRENCY, + ttl: DEFAULT_BID_TTL, + creativeId: bid.crid, + width: bid.w, + height: bid.h, + netRevenue: DEFAULT_NET_REVENUE, + nurl: bid.nurl, + }) + }) + } else { + utils.logInfo('automatad :: no valid responses to interpret') + } + + return bidResponses + }, + getUserSyncs: function(syncOptions, serverResponse) { + return [{ + type: 'iframe', + url: 'https://rtb2.automatad.com/ortb2/async_usersync' + }] + }, + onBidWon: function(bid) { + if (!bid.nurl) { return } + const winCpm = (bid.hasOwnProperty('originalCpm')) ? bid.originalCpm : bid.cpm + const winCurr = (bid.hasOwnProperty('originalCurrency') && bid.hasOwnProperty('originalCpm')) ? bid.originalCurrency : bid.currency + const winUrl = bid.nurl.replace( + /\$\{AUCTION_PRICE\}/, + winCpm + ).replace( + /\$\{AUCTION_IMP_ID\}/, + bid.requestId + ).replace( + /\$\{AUCTION_CURRENCY\}/, + winCurr + ).replace( + /\$\{AUCTION_ID\}/, + bid.auctionId + ) + spec.ajaxCall(winUrl, null) + return true + }, + ajaxCall: function(endpoint, data) { + ajax(endpoint, data) + }, + +} +registerBidder(spec) diff --git a/modules/automatadBidAdapter.md b/modules/automatadBidAdapter.md new file mode 100644 index 00000000000..56a4b53c067 --- /dev/null +++ b/modules/automatadBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Automatad Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@automatad.com +``` + +# Description + +Connects to automatad exchange for bids. + +automatad bid adapter supports Banner ads. + +# Test Parameters +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'automatad', + params: { + siteId: 'someValue', + placementId: 'someValue' + } + }] + } +]; +``` diff --git a/modules/avocetBidAdapter.js b/modules/avocetBidAdapter.js new file mode 100644 index 00000000000..1163ac830ba --- /dev/null +++ b/modules/avocetBidAdapter.js @@ -0,0 +1,141 @@ +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'avct'; +const DEFAULT_BASE_URL = 'https://ads.avct.cloud'; +const DEFAULT_PREBID_PATH = '/prebid'; + +function getPrebidURL() { + let host = config.getConfig('avct.baseUrl'); + if (host && typeof host === 'string') { + return `${host}${getPrebidPath()}`; + } + return `${DEFAULT_BASE_URL}${getPrebidPath()}`; +} + +function getPrebidPath() { + let prebidPath = config.getConfig('avct.prebidPath'); + if (prebidPath && typeof prebidPath === 'string') { + return prebidPath; + } + return DEFAULT_PREBID_PATH; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid with params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return ( + !!bid.params && + !!bid.params.placement && + typeof bid.params.placement === 'string' && + bid.params.placement.length === 24 + ); + }, + /** + * 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, bidderRequest) { + // Get currency from config + const currency = config.getConfig('currency.adServerCurrency'); + + // Publisher domain from config + const publisherDomain = config.getConfig('publisherDomain'); + + // First-party data from config + const fpd = config.getConfig('fpd'); + + // GDPR status and TCF consent string + let tcfConsentString; + let gdprApplies = false; + if (bidderRequest.gdprConsent) { + tcfConsentString = bidderRequest.gdprConsent.consentString; + gdprApplies = !!bidderRequest.gdprConsent.gdprApplies; + } + + // US privacy string + let usPrivacyString; + if (bidderRequest.uspConsent) { + usPrivacyString = bidderRequest.uspConsent; + } + + // Supply chain + let schain; + if (bidderRequest.schain) { + schain = bidderRequest.schain; + } + + // ID5 identifier + let id5id; + if (bidRequests[0].userId && bidRequests[0].userId.id5id) { + id5id = bidRequests[0].userId.id5id; + } + + // Build the avocet ext object + const ext = { + currency, + tcfConsentString, + gdprApplies, + usPrivacyString, + schain, + publisherDomain, + fpd, + id5id, + }; + + // Extract properties from bidderRequest + const { + auctionId, + auctionStart, + bidderCode, + bidderRequestId, + refererInfo, + timeout, + } = bidderRequest; + + // Construct payload + const payload = JSON.stringify({ + auctionId, + auctionStart, + bidderCode, + bidderRequestId, + refererInfo, + timeout, + bids: bidRequests, + ext, + }); + + return { + method: 'POST', + url: getPrebidURL(), + data: payload, + }; + }, + interpretResponse: function (serverResponse, bidRequest) { + if ( + !serverResponse || + !serverResponse.body || + typeof serverResponse.body !== 'object' + ) { + return []; + } + if (Array.isArray(serverResponse.body)) { + return serverResponse.body; + } + if (Array.isArray(serverResponse.body.responses)) { + return serverResponse.body.responses; + } + return []; + }, +}; +registerBidder(spec); diff --git a/modules/avocetBidAdapter.md b/modules/avocetBidAdapter.md new file mode 100644 index 00000000000..95cb29303f2 --- /dev/null +++ b/modules/avocetBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Avocet Bidder Adapter +Module Type: Bidder Adapter +Maintainer: developers@avocet.io +``` + +# Description + +Module that connects to the Avocet advertising platform. + +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :---------------------------------- | :------------------------- | +| `placement` | required | A Placement ID from Avocet. | "5ebd27607781b9af3ccc3332" | + + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "avct", + params: { + placement: "5ebd27607781b9af3ccc3332" + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 49b6b0c4edb..12e78c684ad 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -1,13 +1,12 @@ -import * as utils from '../src/utils'; -import { parse as parseUrl } from '../src/url'; -import { config } from '../src/config'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { Renderer } from '../src/Renderer'; -import { VIDEO, BANNER } from '../src/mediaTypes'; -import find from 'core-js/library/fn/array/find'; -import includes from 'core-js/library/fn/array/includes'; - -const ADAPTER_VERSION = '1.8'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const ADAPTER_VERSION = '1.11'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; @@ -105,9 +104,9 @@ export const spec = { } }, - getUserSyncs(syncOptions, serverResponses = [], gdprConsent = {}) { + getUserSyncs(syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '') { let syncs = []; - let { gdprApplies, consentString } = gdprConsent; + let { gdprApplies, consentString = '' } = gdprConsent; let bannerResponse = find(serverResponses, (res) => utils.isArray(res.body)); if (bannerResponse) { @@ -124,12 +123,12 @@ export const spec = { } else if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: `https://sync.bfmio.com/sync_iframe?ifg=1&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString || ''}&gce=1` + url: `https://sync.bfmio.com/sync_iframe?ifg=1&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString}&gce=1&us_privacy=${uspConsent}` }); } else if (syncOptions.pixelEnabled) { syncs.push({ type: 'image', - url: `https://sync.bfmio.com/syncb?pid=144&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString || ''}&gce=1` + url: `https://sync.bfmio.com/syncb?pid=144&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString}&gce=1&us_privacy=${uspConsent}` }); } @@ -247,7 +246,7 @@ function isBannerBidValid(bid) { function getTopWindowLocation(bidderRequest) { let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; - return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); + return utils.parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); } function getTopWindowReferrer() { @@ -273,6 +272,7 @@ function createVideoRequestData(bid, bidderRequest) { let video = getVideoTargetingParams(bid); let appId = getVideoBidParam(bid, 'appId'); let bidfloor = getVideoBidParam(bid, 'bidfloor'); + let tagid = getVideoBidParam(bid, 'tagid'); let topLocation = getTopWindowLocation(bidderRequest); let payload = { isPrebid: true, @@ -286,7 +286,8 @@ function createVideoRequestData(bid, bidderRequest) { mimes: DEFAULT_MIMES }, video), bidfloor: bidfloor, - secure: topLocation.protocol === 'https:' ? 1 : 0, + tagid: tagid, + secure: topLocation.protocol.indexOf('https') === 0 ? 1 : 0, displaymanager: ADAPTER_NAME, displaymanagerver: ADAPTER_VERSION }], @@ -311,6 +312,10 @@ function createVideoRequestData(bid, bidderRequest) { cur: ['USD'] }; + if (bidderRequest && bidderRequest.uspConsent) { + payload.regs.ext.us_privacy = bidderRequest.uspConsent; + } + if (bidderRequest && bidderRequest.gdprConsent) { let { gdprApplies, consentString } = bidderRequest.gdprConsent; payload.regs.ext.gdpr = gdprApplies ? 1 : 0; @@ -363,6 +368,10 @@ function createBannerRequestData(bids, bidderRequest) { adapterName: ADAPTER_NAME }; + if (bidderRequest && bidderRequest.uspConsent) { + payload.usPrivacy = bidderRequest.uspConsent; + } + if (bidderRequest && bidderRequest.gdprConsent) { let { gdprApplies, consentString } = bidderRequest.gdprConsent; payload.gdpr = gdprApplies ? 1 : 0; diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index 374ea200b60..c435a5a993e 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,5 +1,5 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; - +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { getAdUnitSizes, parseSizesInput } from '../src/utils.js'; const BIDDER_CODE = 'between'; export const spec = { @@ -13,7 +13,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - return !!(bid.params.w && bid.params.h && bid.params.s); + return Boolean(bid.params.s); }, /** * Make a server request from the list of BidRequests. @@ -21,17 +21,18 @@ export const spec = { * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests) { + buildRequests: function(validBidRequests, bidderRequest) { let requests = []; + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + validBidRequests.forEach(i => { let params = { + sizes: parseSizesInput(getAdUnitSizes(i)).join('%2C'), jst: 'hb', ord: Math.random() * 10000000000000000, tz: getTz(), fl: getFl(), rr: getRr(), - w: i.params.w, - h: i.params.h, s: i.params.s, bidid: i.bidId, transactionid: i.transactionId, @@ -54,6 +55,16 @@ export const spec = { params['pubside_macro[' + key + ']'] = encodeURIComponent(i.params.pubdata[key]); } } + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + params.gdprApplies = !!gdprConsent.gdprApplies; + } + if (typeof gdprConsent.consentString !== 'undefined') { + params.consentString = gdprConsent.consentString; + } + } + requests.push({method: 'GET', url: 'https://ads.betweendigital.com/adjson', data: params}) }) return requests; @@ -96,7 +107,7 @@ export const spec = { if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: 'https://acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html' + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' }); } if (syncOptions.pixelEnabled && serverResponses.length > 0) { @@ -108,7 +119,7 @@ export const spec = { // syncs.push({ // type: 'iframe', - // url: 'https://acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html' + // url: 'https://acdn.adnxs.com/dmp/async_usersync.html' // }); syncs.push({ type: 'iframe', diff --git a/modules/betweenBidAdapter.md b/modules/betweenBidAdapter.md index 426d0aa2ed7..4371a8709d8 100644 --- a/modules/betweenBidAdapter.md +++ b/modules/betweenBidAdapter.md @@ -8,89 +8,147 @@ Maintainer: info@betweendigital.com You can use this adapter to get a bid from betweendigital. -About us : http://betweendigital.com +About us : [betweenx.com](https://betweenx.com) +More detailed instructions you can be found on [this page](https://cdn.betweendigital.com/prebid_instructions/index.html) . # Test Parameters + +> The parameters are used as an example: +> s: 3649326 — Between section id; code: ad_slot — id of an iframe element showing prebid ads + ```javascript - var adUnits = [ - { - code: 'test-div', - bids: [ - { - bidder: "between", - params: { - w: 200, - h: 400, - s: 111 - } - } - ] - } - ]; +var adUnits = [ + { + code: "ad_slot", + mediaTypes: { + banner: { + sizes: [[240, 400]], + }, + }, + bids: [ + { + bidder: "between", + params: { + s: 3649326, + }, + }, + ], + }, +]; ``` -Where: +### Multisizes -* s - the section id -* code - the id of the iframe tag to which the ads will be rendered +If you specify several sizes in the AdUnits settings in the **mediaTypes.banner.sizes** field, our SSP server will hold an auction with each size and respond with a bid with the maximum CPM. -# Example page +For example, your ad-slot supports three sizes: 970x250, 728x90 and 468x60. Then the AdUnits code will look like this: -```html - - - - +```javascript +{ + bidder: 'between', + params: { + s: BETWEEN_SECTION_ID, + cur: 'USD' + } +} +``` + +### GDPR +Also, we support GDPR. To find out how to use GDPR in Prebid you can visit [this page](http://prebid.org/dev-docs/modules/consentManagement.html) - + + + + }); + -

Prebid.js BetweenBidAdapter Test

- +

Prebid.js BetweenBidAdapter Test

+ - -``` \ No newline at end of file + +``` diff --git a/modules/bidfluenceBidAdapter.js b/modules/bidfluenceBidAdapter.js index c6adc2897b0..f8a1f9ac92f 100644 --- a/modules/bidfluenceBidAdapter.js +++ b/modules/bidfluenceBidAdapter.js @@ -1,5 +1,8 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); const BIDDER_CODE = 'bidfluence'; function stdTimezoneOffset(t) { @@ -46,7 +49,7 @@ export const spec = { var payload = { v: '2.0', azr: true, - ck: utils.cookiesAreEnabled(), + ck: storage.cookiesAreEnabled(), re: refInfo ? refInfo.referer : '', st: refInfo ? refInfo.stack : [], tz: getBdfTz(new Date()), diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 553a52b25f4..6db35f184ca 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -1,134 +1,134 @@ -import * as utils from '../src/utils'; -// import {config} from 'src/config'; -import {registerBidder} from '../src/adapters/bidderFactory'; - -const BIDDER_CODE = 'bidglass'; - -export const spec = { - code: BIDDER_CODE, - aliases: ['bg'], // short code - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - return !!(bid.params.adUnitId && !isNaN(parseFloat(bid.params.adUnitId)) && isFinite(bid.params.adUnitId)); - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - /* - Sample array entry for validBidRequests[]: - [{ - "bidder": "bidglass", - "bidId": "51ef8751f9aead", - "params": { - "adUnitId": 11, - ... - }, - "adUnitCode": "div-gpt-ad-1460505748561-0", - "transactionId": "d7b773de-ceaa-484d-89ca-d9f51b8d61ec", - "sizes": [[320,50],[300,250],[300,600]], - "bidderRequestId": "418b37f85e772c", - "auctionId": "18fd8b8b0bd757", - "bidRequestsCount": 1 - }] - */ - - let imps = []; - let getReferer = function() { - return window === window.top ? window.location.href : window.parent === window.top ? document.referrer : null; - }; - let getOrigins = function() { - var ori = ['https://' + window.location.hostname]; - - if (window.location.ancestorOrigins) { - for (var i = 0; i < window.location.ancestorOrigins.length; i++) { - ori.push(window.location.ancestorOrigins[i]); - } - } else if (window !== window.top) { - // Derive the parent origin - var parts = document.referrer.split('/'); - - ori.push('https://' + parts[2]); - - if (window.parent !== window.top) { - // Additional unknown origins exist - ori.push('null'); - } - } - - return ori; - }; - - utils._each(validBidRequests, function(bid) { - bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); - bid.sizes = bid.sizes.filter(size => utils.isArray(size)); - - // Stuff to send: [bid id, sizes, adUnitId] - imps.push({ - bidId: bid.bidId, - sizes: bid.sizes, - adUnitId: utils.getBidIdParameter('adUnitId', bid.params) - }); - }); - - // Stuff to send: page URL - const bidReq = { - reqId: utils.getUniqueIdentifierStr(), - imps: imps, - ref: getReferer(), - ori: getOrigins() - }; - - let url = 'https://bid.glass/ad/hb.php?' + - `src=$$REPO_AND_VERSION$$`; - - return { - method: 'POST', - url: url, - data: JSON.stringify(bidReq), - options: { - contentType: 'text/plain', - withCredentials: false - } - } - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse) { - const bidResponses = []; - - utils._each(serverResponse.body.bidResponses, function(bid) { - bidResponses.push({ - requestId: bid.requestId, - cpm: parseFloat(bid.cpm), - width: parseInt(bid.width, 10), - height: parseInt(bid.height, 10), - creativeId: bid.creativeId, - dealId: bid.dealId || null, - currency: bid.currency || 'USD', - mediaType: bid.mediaType || 'banner', - netRevenue: true, - ttl: bid.ttl || 10, - ad: bid.ad - }); - }); - - return bidResponses; - } - -} - -registerBidder(spec); +import * as utils from '../src/utils.js'; +// import {config} from 'src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'bidglass'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['bg'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.adUnitId && !isNaN(parseFloat(bid.params.adUnitId)) && isFinite(bid.params.adUnitId)); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + /* + Sample array entry for validBidRequests[]: + [{ + "bidder": "bidglass", + "bidId": "51ef8751f9aead", + "params": { + "adUnitId": 11, + ... + }, + "adUnitCode": "div-gpt-ad-1460505748561-0", + "transactionId": "d7b773de-ceaa-484d-89ca-d9f51b8d61ec", + "sizes": [[320,50],[300,250],[300,600]], + "bidderRequestId": "418b37f85e772c", + "auctionId": "18fd8b8b0bd757", + "bidRequestsCount": 1 + }] + */ + + let imps = []; + let getReferer = function() { + return window === window.top ? window.location.href : window.parent === window.top ? document.referrer : null; + }; + let getOrigins = function() { + var ori = ['https://' + window.location.hostname]; + + if (window.location.ancestorOrigins) { + for (var i = 0; i < window.location.ancestorOrigins.length; i++) { + ori.push(window.location.ancestorOrigins[i]); + } + } else if (window !== window.top) { + // Derive the parent origin + var parts = document.referrer.split('/'); + + ori.push('https://' + parts[2]); + + if (window.parent !== window.top) { + // Additional unknown origins exist + ori.push('null'); + } + } + + return ori; + }; + + utils._each(validBidRequests, function(bid) { + bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); + bid.sizes = bid.sizes.filter(size => utils.isArray(size)); + + // Stuff to send: [bid id, sizes, adUnitId] + imps.push({ + bidId: bid.bidId, + sizes: bid.sizes, + adUnitId: utils.getBidIdParameter('adUnitId', bid.params) + }); + }); + + // Stuff to send: page URL + const bidReq = { + reqId: utils.getUniqueIdentifierStr(), + imps: imps, + ref: getReferer(), + ori: getOrigins() + }; + + let url = 'https://bid.glass/ad/hb.php?' + + `src=$$REPO_AND_VERSION$$`; + + return { + method: 'POST', + url: url, + data: JSON.stringify(bidReq), + options: { + contentType: 'text/plain', + withCredentials: false + } + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse) { + const bidResponses = []; + + utils._each(serverResponse.body.bidResponses, function(bid) { + bidResponses.push({ + requestId: bid.requestId, + cpm: parseFloat(bid.cpm), + width: parseInt(bid.width, 10), + height: parseInt(bid.height, 10), + creativeId: bid.creativeId, + dealId: bid.dealId || null, + currency: bid.currency || 'USD', + mediaType: bid.mediaType || 'banner', + netRevenue: true, + ttl: bid.ttl || 10, + ad: bid.ad + }); + }); + + return bidResponses; + } + +} + +registerBidder(spec); diff --git a/modules/bidlabBidAdapter.js b/modules/bidlabBidAdapter.js new file mode 100644 index 00000000000..8f501505a6d --- /dev/null +++ b/modules/bidlabBidAdapter.js @@ -0,0 +1,112 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'bidlab'; +const AD_URL = 'https://service.bidlab.ai/?c=o&m=multi'; +const URL_SYNC = 'https://service.bidlab.ai/?c=o&m=sync'; +const NO_SYNC = true; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + noSync: NO_SYNC, + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + const len = validBidRequests.length; + + for (let i = 0; i < len; i++) { + let bid = validBidRequests[i]; + let traff = bid.params.traffic || BANNER + + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [], + traffic: traff + }); + if (bid.schain) { + placements.schain = bid.schain; + } + } + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + if (NO_SYNC) { + return false + } else { + return [{ + type: 'image', + url: URL_SYNC + }]; + } + } + +}; + +registerBidder(spec); diff --git a/modules/bidlabBidAdapter.md b/modules/bidlabBidAdapter.md new file mode 100644 index 00000000000..3e5fe3128ed --- /dev/null +++ b/modules/bidlabBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: bidlab Bidder Adapter +Module Type: bidlab Bidder Adapter +``` + +# Description + +Module that connects to bidlab demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'bidlab', + params: { + placementId: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'bidlab', + params: { + placementId: 0, + traffic: 'video' + } + } + ] + } + ]; +``` diff --git a/modules/bidphysicsBidAdapter.js b/modules/bidphysicsBidAdapter.js index cdf30e670fc..b6b5690ede5 100644 --- a/modules/bidphysicsBidAdapter.js +++ b/modules/bidphysicsBidAdapter.js @@ -1,6 +1,6 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; -import {BANNER} from '../src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER} from '../src/mediaTypes.js'; const ENDPOINT_URL = 'https://exchange.bidphysics.com/auction'; diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js new file mode 100644 index 00000000000..4d40d931e1d --- /dev/null +++ b/modules/bluebillywigBidAdapter.js @@ -0,0 +1,390 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; +import { createEidsArray } from './userId/eids.js'; + +const DEV_MODE = window.location.search.match(/bbpbs_debug=true/); + +// Blue Billywig Constants +const BB_CONSTANTS = { + BIDDER_CODE: 'bluebillywig', + AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION', + SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION', + RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js', + DEFAULT_TIMEOUT: 5000, + DEFAULT_TTL: 300, + DEFAULT_WIDTH: 768, + DEFAULT_HEIGHT: 432, + DEFAULT_NET_REVENUE: true +}; + +// Aliasing +const getConfig = config.getConfig; + +// Helper Functions +export const BB_HELPERS = { + addSiteAppDevice: function(request, pageUrl) { + if (!request) return; + + if (typeof getConfig('app') === 'object') request.app = getConfig('app'); + else { + request.site = {}; + if (typeof getConfig('site') === 'object') request.site = getConfig('site'); + if (pageUrl) request.site.page = pageUrl; + } + + if (typeof getConfig('device') === 'object') request.device = getConfig('device'); + if (!request.device) request.device = {}; + if (!request.device.w) request.device.w = window.innerWidth; + if (!request.device.h) request.device.h = window.innerHeight; + }, + addSchain: function(request, validBidRequests) { + if (!request) return; + + const schain = utils.deepAccess(validBidRequests, '0.schain'); + if (schain) request.source.ext = { schain: schain }; + }, + addCurrency: function(request) { + if (!request) return; + + const adServerCur = getConfig('currency.adServerCurrency'); + if (adServerCur && typeof adServerCur === 'string') request.cur = [adServerCur]; + else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]]; + }, + addUserIds: function(request, validBidRequests) { + if (!request) return; + + const bidUserId = utils.deepAccess(validBidRequests, '0.userId'); + const eids = createEidsArray(bidUserId); + + if (eids.length) { + utils.deepSetValue(request, 'user.ext.eids', eids); + } + }, + addDigiTrust: function(request, bidRequests) { + const digiTrust = BB_HELPERS.getDigiTrustParams(bidRequests && bidRequests[0]); + if (digiTrust) utils.deepSetValue(request, 'user.ext.digitrust', digiTrust); + }, + substituteUrl: function (url, publication, renderer) { + return url.replace('$$URL_START', (DEV_MODE) ? 'https://dev.' : 'https://').replace('$$PUBLICATION', publication).replace('$$RENDERER', renderer); + }, + getAuctionUrl: function(publication) { + return BB_HELPERS.substituteUrl(BB_CONSTANTS.AUCTION_URL, publication); + }, + getSyncUrl: function(publication) { + return BB_HELPERS.substituteUrl(BB_CONSTANTS.SYNC_URL, publication); + }, + getRendererUrl: function(publication, renderer) { + return BB_HELPERS.substituteUrl(BB_CONSTANTS.RENDERER_URL, publication, renderer); + }, + getDigiTrustParams: function(bidRequest) { + const digiTrustId = BB_HELPERS.getDigiTrustId(bidRequest); + + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) return null; + return { + id: digiTrustId.id, + keyv: digiTrustId.keyv + } + }, + getDigiTrustId: function(bidRequest) { + const bidRequestDigiTrust = utils.deepAccess(bidRequest, 'userId.digitrustid.data'); + if (bidRequestDigiTrust) return bidRequestDigiTrust; + + const digiTrustUser = getConfig('digiTrustId'); + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + }, + transformRTBToPrebidProps: function(bid, serverResponse) { + bid.cpm = bid.price; delete bid.price; + bid.bidId = bid.impid; + bid.requestId = bid.impid; delete bid.impid; + bid.width = bid.w || BB_CONSTANTS.DEFAULT_WIDTH; + bid.height = bid.h || BB_CONSTANTS.DEFAULT_HEIGHT; + if (bid.adm) { + bid.ad = bid.adm; + bid.vastXml = bid.adm; + delete bid.adm; + } + if (bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5 + bid.vastUrl = bid.nurl; + delete bid.nurl; + } + bid.netRevenue = BB_CONSTANTS.DEFAULT_NET_REVENUE; + bid.creativeId = bid.crid; delete bid.crid; + bid.currency = serverResponse.cur; + bid.ttl = BB_CONSTANTS.DEFAULT_TTL; + }, +}; + +// Renderer Functions +const BB_RENDERER = { + bootstrapPlayer: function(bid) { + const config = { + code: bid.adUnitCode, + }; + + if (bid.vastXml) config.vastXml = bid.vastXml; + else if (bid.vastUrl) config.vastUrl = bid.vastUrl; + + if (!bid.vastXml && !bid.vastUrl) { + utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: No vastXml or vastUrl on bid, bailing...`); + return; + } + + const rendererId = BB_RENDERER.getRendererId(bid.publicationName, bid.rendererCode); + + const ele = document.getElementById(bid.adUnitCode); // NB convention + + let renderer; + + for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) { + if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) { + renderer = window.bluebillywig.renderers[rendererIndex]; + break; + } + } + + if (renderer) renderer.bootstrap(config, ele); + else utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`); + }, + newRenderer: function(rendererUrl, adUnitCode) { + const renderer = Renderer.install({ + url: rendererUrl, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(BB_RENDERER.outstreamRender); + } catch (err) { + utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Error tying to setRender on renderer`, err); + } + + return renderer; + }, + outstreamRender: function(bid) { + bid.renderer.push(function() { BB_RENDERER.bootstrapPlayer(bid) }); + }, + getRendererId: function(pub, renderer) { + return `${pub}-${renderer}`; // NB convention! + } +}; + +// Spec Functions +// These functions are used to construct the core spec for the adapter +export const spec = { + code: BB_CONSTANTS.BIDDER_CODE, + supportedMediaTypes: [VIDEO], + syncStore: { bidders: [], }, + isBidRequestValid(bid) { + const publicationNameRegex = /^\w+\.?\w+$/; + const rendererRegex = /^[\w+_]+$/; + + if (!bid.params) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no params set on bid. Rejecting bid: `, bid); + return false; + } + + if (!bid.params.hasOwnProperty('publicationName') || typeof bid.params.publicationName !== 'string') { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no publicationName specified in bid params, or it's not a string. Rejecting bid: `, bid); + return false; + } else if (!publicationNameRegex.test(bid.params.publicationName)) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: publicationName must be in format 'publication' or 'publication.environment'. Rejecting bid: `, bid); + return false; + } + + if ((!bid.params.hasOwnProperty('rendererCode') || typeof bid.params.rendererCode !== 'string')) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no rendererCode was specified in bid params. Rejecting bid: `, bid); + return false; + } else if (!rendererRegex.test(bid.params.rendererCode)) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: rendererCode must be alphanumeric, including underscores. Rejecting bid: `, bid); + return false; + } + + if (!bid.params.accountId) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no accountId specified in bid params. Rejecting bid: `, bid); + return false; + } + + if (bid.params.hasOwnProperty('connections')) { + if (!Array.isArray(bid.params.connections)) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid); + return false; + } else { + for (let connectionIndex = 0; connectionIndex < bid.params.connections.length; connectionIndex++) { + const connection = bid.params.connections[connectionIndex]; + if (!bid.params.hasOwnProperty(connection)) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connection specified in params.connections, but not configured in params. Rejecting bid: `, bid); + return false; + } + } + } + } else { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no connections specified in bid. Rejecting bid: `, bid); + return false; + } + + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid); + return false; + } + + if (bid.mediaTypes[VIDEO].context !== 'outstream') { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: video.context is invalid, must be "outstream". Rejecting bid: `, bid); + return false; + } + } else { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid); + return false; + } + + return true; + }, + buildRequests(validBidRequests, bidderRequest) { + const imps = []; + + for (let validBidRequestIndex = 0; validBidRequestIndex < validBidRequests.length; validBidRequestIndex++) { + const validBidRequest = validBidRequests[validBidRequestIndex]; + const _this = this; + + const ext = validBidRequest.params.connections.reduce(function(extBuilder, connection) { + extBuilder[connection] = validBidRequest.params[connection]; + + if (_this.syncStore.bidders.indexOf(connection) === -1) _this.syncStore.bidders.push(connection); + + return extBuilder; + }, {}); + + imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: utils.deepAccess(validBidRequest, 'mediaTypes.video') }); + } + + const request = { + id: bidderRequest.auctionId, + source: {tid: bidderRequest.auctionId}, + tmax: BB_CONSTANTS.DEFAULT_TIMEOUT, + imp: imps, + test: DEV_MODE ? 1 : 0, + ext: { + prebid: { + targeting: { includewinners: true, includebidderkeys: false } + } + } + }; + + // handle privacy settings for GDPR/CCPA/COPPA + if (bidderRequest.gdprConsent) { + let gdprApplies = 0; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + this.syncStore.uspConsent = bidderRequest.uspConsent; + } + + if (getConfig('coppa') == true) utils.deepSetValue(request, 'regs.coppa', 1); + + // Enrich the request with any external data we may have + BB_HELPERS.addSiteAppDevice(request, bidderRequest.refererInfo && bidderRequest.refererInfo.referer); + BB_HELPERS.addSchain(request, validBidRequests); + BB_HELPERS.addCurrency(request); + BB_HELPERS.addUserIds(request, validBidRequests); + BB_HELPERS.addDigiTrust(request, validBidRequests); + + return { + method: 'POST', + url: BB_HELPERS.getAuctionUrl(validBidRequests[0].params.publicationName), + data: JSON.stringify(request), + bidderRequest: bidderRequest + }; + }, + interpretResponse(serverResponse, request) { + serverResponse = serverResponse.body || {}; + + if (!serverResponse.hasOwnProperty('seatbid') || !Array.isArray(serverResponse.seatbid)) { + return []; + } + + const bids = []; + + for (let seatbidIndex = 0; seatbidIndex < serverResponse.seatbid.length; seatbidIndex++) { + const seatbid = serverResponse.seatbid[seatbidIndex]; + if (!seatbid.bid || !Array.isArray(seatbid.bid)) continue; + for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) { + const bid = seatbid.bid[bidIndex]; + BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse); + + let bidParams; + for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { + if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === bid.bidId) { + bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; + } + } + + if (bidParams) { + bid.publicationName = bidParams.publicationName; + bid.rendererCode = bidParams.rendererCode; + bid.accountId = bidParams.accountId; + } + + const rendererUrl = BB_HELPERS.getRendererUrl(bid.publicationName, bid.rendererCode); + + bid.renderer = BB_RENDERER.newRenderer(rendererUrl, bid.adUnitCode); + + bids.push(bid); + } + } + + return bids; + }, + getUserSyncs(syncOptions, serverResponses, gdpr) { + if (!serverResponses || !serverResponses.length) return []; + if (!syncOptions.iframeEnabled) return []; + + const queryString = []; + let accountId; + let publication; + + const serverResponse = serverResponses[0]; + if (!serverResponse.body || !serverResponse.body.seatbid) return []; + + for (let seatbidIndex = 0; seatbidIndex < serverResponse.body.seatbid.length; seatbidIndex++) { + const seatbid = serverResponse.body.seatbid[seatbidIndex]; + for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) { + const bid = seatbid.bid[bidIndex]; + accountId = bid.accountId || null; + publication = bid.publicationName || null; + + if (publication && accountId) break; + } + if (publication && accountId) break; + } + + if (!publication || !accountId) return []; + + if (gdpr.gdprApplies) queryString.push(`gdpr=${gdpr.gdprApplies ? 1 : 0}`); + if (gdpr.gdprApplies && gdpr.consentString) queryString.push(`gdpr_consent=${gdpr.consentString}`); + + if (this.syncStore.uspConsent) queryString.push(`usp_consent=${this.syncStore.uspConsent}`); + + queryString.push(`accountId=${accountId}`); + queryString.push(`bidders=${btoa(JSON.stringify(this.syncStore.bidders))}`); + queryString.push(`cb=${Date.now()}-${Math.random().toString().replace('.', '')}`); + + if (DEV_MODE) queryString.push('bbpbs_debug=true'); + + // NB syncUrl by default starts with ?pub=$$PUBLICATION + const syncUrl = `${BB_HELPERS.getSyncUrl(publication)}&${queryString.join('&')}`; + + return [{ + type: 'iframe', + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/bluebillywigBidAdapter.md b/modules/bluebillywigBidAdapter.md new file mode 100644 index 00000000000..7879697baf5 --- /dev/null +++ b/modules/bluebillywigBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Blue Billywig Adapter +Module Type: Bidder Adapter +Maintainer: dev+prebid@bluebillywig.com +``` + +# Description + +Prebid Blue Billywig Bidder Adapter + +# Test Parameters + +``` + const adUnits = [{ + code: 'ad-unit', + sizes: [[[768,432],[640,480],[640,360]]], + mediaTypes: { + video: { + playerSize: [768, 432], + context: 'outstream', + mimes: ['video/mp4'], + protocols: [ 2,3,5,6] + } + }, + bids: [{ + bidder: 'bluebillywig', + params: { + publicationName: "bbprebid", + rendererCode: "renderer", + accountId: 642, + connections: [ 'bluebillywig' ], + bluebillywig: {} + } + }] + }]; +``` diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js new file mode 100644 index 00000000000..04f4085ba24 --- /dev/null +++ b/modules/boldwinBidAdapter.js @@ -0,0 +1,110 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'boldwin'; +const AD_URL = 'https://ssp.videowalldirect.com/?c=o&m=multi'; +const SYNC_URL = 'https://cs.videowalldirect.com/?c=o&m=cookie' + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + const len = validBidRequests.length; + + for (let i = 0; i < len; i++) { + let bid = validBidRequests[i]; + let sizes + if (bid.mediaTypes) { + if (bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + sizes = bid.mediaTypes[BANNER].sizes + } else if (bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + sizes = bid.mediaTypes[VIDEO].playerSize + } + } + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + sizes: sizes || [], + wPlayer: sizes ? sizes[0] : 0, + hPlayer: sizes ? sizes[1] : 0, + traffic: bid.params.traffic || BANNER, + schain: bid.schain || {} + }); + } + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: () => { + return [{ + type: 'image', + url: SYNC_URL + }]; + } +}; + +registerBidder(spec); diff --git a/modules/boldwinBidAdapter.md b/modules/boldwinBidAdapter.md new file mode 100644 index 00000000000..4bf272c4de3 --- /dev/null +++ b/modules/boldwinBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: boldwin Bidder Adapter +Module Type: boldwin Bidder Adapter +``` + +# Description + +Module that connects to boldwin demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + placementId: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + placementId: 0, + traffic: 'video' + } + } + ] + } + ]; +``` diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js new file mode 100644 index 00000000000..0303e4f74bd --- /dev/null +++ b/modules/bridgewellBidAdapter.js @@ -0,0 +1,272 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; + +const BIDDER_CODE = 'bridgewell'; +const REQUEST_ENDPOINT = 'https://prebid.scupio.com/recweb/prebid.aspx?cb=' + Math.random(); +const BIDDER_VERSION = '0.0.2'; + +export const spec = { + code: BIDDER_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) { + let valid = false; + + if (bid && bid.params && bid.params.ChannelID) { + valid = true; + } + + return valid; + }, + + /** + * 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, bidderRequest) { + const adUnits = []; + utils._each(validBidRequests, function (bid) { + adUnits.push({ + ChannelID: bid.params.ChannelID, + adUnitCode: bid.adUnitCode, + mediaTypes: bid.mediaTypes || { + banner: { + sizes: bid.sizes + } + } + }); + }); + + let topUrl = ''; + if (bidderRequest && bidderRequest.refererInfo) { + topUrl = bidderRequest.refererInfo.referer; + } + + return { + method: 'POST', + url: REQUEST_ENDPOINT, + data: { + version: { + prebid: '$prebid.version$', + bridgewell: BIDDER_VERSION + }, + inIframe: utils.inIframe(), + url: topUrl, + referrer: getTopWindowReferrer(), + adUnits: adUnits + }, + validBidRequests: validBidRequests + }; + }, + + /** + * 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) { + const bidResponses = []; + + // map responses to requests + utils._each(bidRequest.validBidRequests, function (req) { + const bidResponse = {}; + + if (!serverResponse.body) { + return; + } + + let matchedResponse = find(serverResponse.body, function (res) { + let valid = false; + + if (res && !res.consumed) { + let mediaTypes = req.mediaTypes; + let adUnitCode = req.adUnitCode; + if (res.adUnitCode) { + return res.adUnitCode === adUnitCode; + } else if (res.width && res.height && mediaTypes) { + if (mediaTypes.native) { // dont care native sizes + valid = true; + } else if (mediaTypes.banner) { + if (mediaTypes.banner.sizes) { + let width = res.width; + let height = res.height; + let sizes = mediaTypes.banner.sizes; + // check response size validation + if (typeof sizes[0] === 'number') { // for foramt Array[Number] check + valid = width === sizes[0] && height === sizes[1]; + } else { // for format Array[Array[Number]] check + valid = !!find(sizes, function (size) { + return (width === size[0] && height === size[1]); + }); + } + } + } + } + } + + return valid; + }); + + if (matchedResponse) { + matchedResponse.consumed = true; + + // check required parameters + if (typeof matchedResponse.cpm !== 'number') { + return; + } else if (typeof matchedResponse.netRevenue !== 'boolean') { + return; + } else if (typeof matchedResponse.currency !== 'string') { + return; + } else if (typeof matchedResponse.mediaType !== 'string') { + return; + } + + bidResponse.requestId = req.bidId; + bidResponse.cpm = matchedResponse.cpm; + bidResponse.width = matchedResponse.width; + bidResponse.height = matchedResponse.height; + bidResponse.ttl = matchedResponse.ttl; + bidResponse.creativeId = matchedResponse.id; + bidResponse.netRevenue = matchedResponse.netRevenue; + bidResponse.currency = matchedResponse.currency; + bidResponse.mediaType = matchedResponse.mediaType; + + // check required parameters by matchedResponse.mediaType + switch (matchedResponse.mediaType) { + case BANNER: + // check banner required parameters + if (typeof matchedResponse.ad !== 'string') { + return; + } + + bidResponse.ad = matchedResponse.ad; + break; + case NATIVE: + // check native required parameters + if (!matchedResponse.native) { + return; + } + + let reqNativeLayout = req.mediaTypes.native; + let resNative = matchedResponse.native; + + // check title + let title = reqNativeLayout.title; + if (title && title.required) { + if (typeof resNative.title !== 'string') { + return; + } else if (title.len && title.len < resNative.title.length) { + return; + } + } + + // check body + let body = reqNativeLayout.body; + if (body && body.required) { + if (typeof resNative.body !== 'string') { + return; + } + } + + // check image + let image = reqNativeLayout.image; + if (image && image.required) { + if (resNative.image) { + if (typeof resNative.image.url !== 'string') { // check image url + return; + } else { + if (resNative.image.width !== image.sizes[0] || resNative.image.height !== image.sizes[1]) { // check image sizes + return; + } + } + } else { + return; + } + } + + // check sponsoredBy + let sponsoredBy = reqNativeLayout.sponsoredBy; + if (sponsoredBy && sponsoredBy.required) { + if (typeof resNative.sponsoredBy !== 'string') { + return; + } + } + + // check icon + let icon = reqNativeLayout.icon; + if (icon && icon.required) { + if (resNative.icon) { + if (typeof resNative.icon.url !== 'string') { // check icon url + return; + } else { + if (resNative.icon.width !== icon.sizes[0] || resNative.icon.height !== icon.sizes[0]) { // check image sizes + return; + } + } + } else { + return; + } + } + + // check clickUrl + if (typeof resNative.clickUrl !== 'string') { + return; + } + + // check clickTracker + let clickTrackers = resNative.clickTrackers; + if (clickTrackers) { + if (clickTrackers.length === 0) { + return; + } + } else { + return; + } + + // check impressionTrackers + let impressionTrackers = resNative.impressionTrackers; + if (impressionTrackers) { + if (impressionTrackers.length === 0) { + return; + } + } else { + return; + } + + bidResponse.native = matchedResponse.native; + + break; + + default: // response mediaType is not supported + return; + } + + bidResponses.push(bidResponse); + } + }); + + return bidResponses; + } +}; + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +registerBidder(spec); diff --git a/modules/bridgewellBidAdapter.md b/modules/bridgewellBidAdapter.md index 014be62ccef..6bcab4b8820 100644 --- a/modules/bridgewellBidAdapter.md +++ b/modules/bridgewellBidAdapter.md @@ -1,8 +1,8 @@ # Overview -Module Name: Bridgewell Bidder Adapter -Module Type: Bidder Adapter -Maintainer: kuchunchou@bridgewell.com +Module Name: Bridgewell Bidder Adapter +Module Type: Bidder Adapter +Maintainer: scupio@bridgewell.com # Description @@ -12,34 +12,15 @@ Module that connects to Bridgewell demand source to fetch bids. ``` var adUnits = [{ code: 'test-div', - sizes: [ - [300, 250] - ], - bids: [{ - bidder: 'bridgewell', - params: { - ChannelID: 'CgUxMjMzOBIBNiIFcGVubnkqCQisAhD6ARoBOQ' - } - }] - }, { - code: 'test-div', - sizes: [ - [728, 90] - ], - bids: [{ - bidder: 'bridgewell', - params: { - ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ', - cpmWeight: 1.5 + mediaTypes: { + banner: { + sizes: [300, 250] } - }] - }, { - code: 'test-div', - sizes: [728, 90], + }, bids: [{ bidder: 'bridgewell', params: { - ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ' + ChannelID: 'CgUxMjMzOBIBNiIFcGVubnkqCQisAhD6ARoBOQ' } }] }, { diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js new file mode 100644 index 00000000000..5a285be71c0 --- /dev/null +++ b/modules/brightMountainMediaBidAdapter.js @@ -0,0 +1,89 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'brightmountainmedia'; +const AD_URL = 'https://console.brightmountainmedia.com/hb/bid'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && bid.params.placement_id); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL' + request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + } + } + for (let i = 0; i < validBidRequests.length; i++) { + let bid = validBidRequests[i]; + let traff = bid.params.traffic || BANNER + let placement = { + placementId: bid.params.placement_id, + bidId: bid.bidId, + sizes: bid.mediaTypes[traff].sizes, + traffic: traff + }; + if (bid.schain) { + placement.schain = bid.schain; + } + placements.push(placement); + } + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + try { + serverResponse = serverResponse.body; + for (let i = 0; i < serverResponse.length; i++) { + let resItem = serverResponse[i]; + + response.push(resItem); + } + } catch (e) { + utils.logMessage(e); + }; + return response; + }, + + getUserSyncs: (syncOptions) => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: 'https://console.brightmountainmedia.com:4444/cookieSync' + }]; + } + }, + +}; + +registerBidder(spec); diff --git a/modules/brightMountainMediaBidAdapter.md b/modules/brightMountainMediaBidAdapter.md new file mode 100644 index 00000000000..a9900b5264d --- /dev/null +++ b/modules/brightMountainMediaBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Bright Mountain Media Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dev@brightmountainmedia.com +``` + +# Description + +Connects to Bright Mountain Media exchange for bids. + +Bright Mountain Media bid adapter currently supports Banner. + +# Test Parameters +``` + var adUnits = [ + code: 'placementid_0', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'brightmountainmedia', + params: { + placement_id: '5f21784949be82079d08c', + traffic: 'banner' + } + } + ] + ]; +``` \ No newline at end of file diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 862933d076b..2aad211b186 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -1,8 +1,7 @@ -import * as utils from '../src/utils'; -import * as url from '../src/url'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; -import { config } from '../src/config'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'brightcom'; const URL = 'https://brightcombid.marphezis.com/hb'; @@ -57,7 +56,7 @@ function buildRequests(bidReqs, bidderRequest) { id: utils.getUniqueIdentifierStr(), imp: brightcomImps, site: { - domain: url.parse(referrer).host, + domain: utils.parseUrl(referrer).host, page: referrer, publisher: { id: publisherId @@ -71,6 +70,11 @@ function buildRequests(bidReqs, bidderRequest) { tmax: config.getConfig('bidderTimeout') }; + if (bidderRequest && bidderRequest.gdprConsent) { + utils.deepSetValue(brightcomBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + utils.deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + return { method: 'POST', url: URL, diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js index 2eb24968c08..17a39e96aad 100644 --- a/modules/britepoolIdSystem.js +++ b/modules/britepoolIdSystem.js @@ -5,9 +5,9 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils' -import {ajax} from '../src/ajax'; -import {submodule} from '../src/hook'; +import * as utils from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; /** @type {Submodule} */ export const britepoolIdSubmodule = { @@ -89,7 +89,7 @@ export const britepoolIdSubmodule = { if (params.getter) { // Custom getter will not require other params if (typeof params.getter !== 'function') { - errors.push(`${MODULE_NAME} - britepoolId submodule requires getter to be a function`); + errors.push(`userIdTargeting - britepoolId submodule requires getter to be a function`); return { errors }; } } else { diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md index 89287aed7ca..a15f601aee3 100644 --- a/modules/britepoolIdSystem.md +++ b/modules/britepoolIdSystem.md @@ -7,7 +7,7 @@ BritePool User ID Module. For assistance setting up your module please contact u Individual params may be set for the BritePool User ID Submodule. At least one identifier must be set in the params. ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: 'britepoolId', storage: { diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 795c9c86f1e..9317786fb8d 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -13,16 +13,24 @@ * @property {string} pubKey * @property {string} url * @property {?string} keyName - * @property {number} auctionDelay + * @property {?number} auctionDelay + * @property {?number} timeout */ import {config} from '../src/config.js'; -import * as utils from '../src/utils'; -import {submodule} from '../src/hook'; -import {ajax} from '../src/ajax'; +import * as utils from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajaxBuilder} from '../src/ajax.js'; +import {loadExternalScript} from '../src/adloader.js'; +import { getStorageManager } from '../src/storageManager.js'; +import find from 'core-js-pure/features/array/find.js'; + +const storage = getStorageManager(); /** @type {string} */ const MODULE_NAME = 'realTimeData'; +/** @type {number} */ +const DEF_TIMEOUT = 1000; /** @type {ModuleParams} */ let _moduleParams = {}; /** @type {null|Object} */ @@ -32,17 +40,20 @@ let _dataReadyCallback = null; /** * add browsi script to page - * @param {string} bptUrl + * @param {Object} data */ -export function addBrowsiTag(bptUrl) { - let script = document.createElement('script'); +export function addBrowsiTag(data) { + let script = loadExternalScript(data.u, 'browsi'); script.async = true; script.setAttribute('data-sitekey', _moduleParams.siteKey); script.setAttribute('data-pubkey', _moduleParams.pubKey); script.setAttribute('prebidbpt', 'true'); script.setAttribute('id', 'browsi-tag'); - script.setAttribute('src', bptUrl); - document.head.appendChild(script); + script.setAttribute('src', data.u); + script.prebidData = utils.deepClone(data); + if (_moduleParams.keyName) { + script.prebidData.kn = _moduleParams.keyName; + } return script; } @@ -55,7 +66,7 @@ function collectData() { const doc = win.document; let browsiData = null; try { - browsiData = utils.getDataFromLocalStorage('__brtd'); + browsiData = storage.getDataFromLocalStorage('__brtd'); } catch (e) { utils.logError('unable to parse __brtd'); } @@ -65,7 +76,7 @@ function collectData() { sk: _moduleParams.siteKey, sw: (win.screen && win.screen.width) || -1, sh: (win.screen && win.screen.height) || -1, - url: encodeURIComponent(`${doc.location.protocol}//${doc.location.host}${doc.location.pathname}`), + url: `${doc.location.protocol}//${doc.location.host}${doc.location.pathname}`, }, ...(browsiData ? {us: browsiData} : {us: '{}'}), ...(document.referrer ? {r: document.referrer} : {}), @@ -110,18 +121,16 @@ function sendDataToModule(adUnits, onDone) { if (!_predictions || !Object.keys(_predictions).length) { return onDone({}); } - const slots = getAllSlots(); - if (!slots) { - return onDone({}); - } let dataToReturn = adUnits.reduce((rp, cau) => { const adUnitCode = cau && cau.code; if (!adUnitCode) { return rp } - const predictionData = _predictions[adUnitCode]; + const adSlot = getSlotByCode(adUnitCode); + const identifier = adSlot ? getMacroId(_predictionsData.pmd, adSlot) : adUnitCode; + const predictionData = _predictions[identifier]; if (!predictionData) { return rp } if (predictionData.p) { - if (!isIdMatchingAdUnit(adUnitCode, slots, predictionData.w)) { + if (!isIdMatchingAdUnit(adSlot, predictionData.w)) { return rp; } rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn); @@ -140,7 +149,7 @@ function sendDataToModule(adUnits, onDone) { * @return {Object[]} slot GoogleTag slots */ function getAllSlots() { - return utils.isGptPubadsDefined && window.googletag.pubads().getSlots(); + return utils.isGptPubadsDefined() && window.googletag.pubads().getSlots(); } /** * get prediction and return valid object for key value set @@ -156,25 +165,74 @@ function getKVObject(p, keyName) { } /** * check if placement id matches one of given ad units - * @param {number} id placement id - * @param {Object[]} allSlots google slots on page + * @param {Object} slot google slot * @param {string[]} whitelist ad units * @return {boolean} */ -export function isIdMatchingAdUnit(id, allSlots, whitelist) { - if (!whitelist || !whitelist.length) { +export function isIdMatchingAdUnit(slot, whitelist) { + if (!whitelist || !whitelist.length || !slot) { return true; } - const slot = allSlots.filter(s => s.getSlotElementId() === id); - const slotAdUnits = slot.map(s => s.getAdUnitPath()); - return slotAdUnits.some(a => whitelist.indexOf(a) !== -1); + const slotAdUnits = slot.getAdUnitPath(); + return whitelist.indexOf(slotAdUnits) !== -1; +} + +/** + * get GPT slot by placement id + * @param {string} code placement id + * @return {?Object} + */ +function getSlotByCode(code) { + const slots = getAllSlots(); + if (!slots || !slots.length) { + return null; + } + return find(slots, s => s.getSlotElementId() === code || s.getAdUnitPath() === code) || null; +} + +/** + * generate id according to macro script + * @param {Object} macro replacement macro + * @param {Object} slot google slot + * @return {?Object} + */ +export function getMacroId(macro, slot) { + if (macro) { + try { + const macroResult = evaluate(macro, slot.getSlotElementId(), slot.getAdUnitPath(), (match, p1) => { + return (p1 && slot.getTargeting(p1).join('_')) || 'NA'; + }); + return macroResult; + } catch (e) { + utils.logError(`failed to evaluate: ${macro}`); + } + } + return slot.getSlotElementId(); } +function evaluate(macro, divId, adUnit, replacer) { + let macroResult = macro.p + .replace(/['"]+/g, '') + .replace(//g, divId); + + if (adUnit) { + macroResult = macroResult.replace(//g, adUnit); + } + if (replacer) { + macroResult = macroResult.replace(//g, replacer); + } + if (macro.s) { + macroResult = macroResult.substring(macro.s.s, macro.s.e); + } + return macroResult; +} /** * XMLHttpRequest to get data form browsi server * @param {string} url server url with query params */ function getPredictionsFromServer(url) { + let ajax = ajaxBuilder(_moduleParams.auctionDelay || _moduleParams.timeout || DEF_TIMEOUT); + ajax(url, { success: function (response, req) { @@ -182,11 +240,11 @@ function getPredictionsFromServer(url) { try { const data = JSON.parse(response); if (data && data.p && data.kn) { - setData({p: data.p, kn: data.kn}); + setData({p: data.p, kn: data.kn, pmd: data.pmd}); } else { setData({}); } - addBrowsiTag(data.u); + addBrowsiTag(data); } catch (err) { utils.logError('unable to parse data'); setData({}) @@ -237,6 +295,7 @@ export function init(config) { _moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter( pr => pr.name && pr.name.toLowerCase() === 'browsi')[0].params; _moduleParams.auctionDelay = realTimeData.auctionDelay; + _moduleParams.timeout = realTimeData.timeout; } catch (e) { _moduleParams = {}; } diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js index 12a9e287f38..3f327e62121 100644 --- a/modules/bucksenseBidAdapter.js +++ b/modules/bucksenseBidAdapter.js @@ -1,10 +1,10 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; -import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; const WHO = 'BKSHBID-005'; const BIDDER_CODE = 'bucksense'; -const URL = 'https://prebid.bksn.se:445/prebidjs/'; +const URL = 'https://prebid.bksn.se/prebidjs/'; export const spec = { code: BIDDER_CODE, diff --git a/modules/buzzoolaBidAdapter.js b/modules/buzzoolaBidAdapter.js index da2a3b30c2e..f87607657c3 100644 --- a/modules/buzzoolaBidAdapter.js +++ b/modules/buzzoolaBidAdapter.js @@ -1,8 +1,8 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {BANNER, VIDEO} from '../src/mediaTypes'; -import {Renderer} from '../src/Renderer'; -import {OUTSTREAM} from '../src/video'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {OUTSTREAM} from '../src/video.js'; const BIDDER_CODE = 'buzzoola'; const ENDPOINT = 'https://exchange.buzzoola.com/ssp/prebidjs'; diff --git a/modules/byplayBidAdapter.js b/modules/byplayBidAdapter.js new file mode 100644 index 00000000000..6133cdfa647 --- /dev/null +++ b/modules/byplayBidAdapter.js @@ -0,0 +1,67 @@ +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'byplay'; +const ENDPOINT_URL = 'https://prebid.byplay.net/bidder'; +const VIDEO_PLAYER_URL = 'https://cdn.byplay.net/prebid-byplay-v2.js'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + isBidRequestValid: (bid) => { + return !!bid.params.sectionId; + }, + buildRequests: function(validBidRequests) { + return validBidRequests.map(req => { + const payload = { + requestId: req.bidId, + sectionId: req.params.sectionId, + ...(req.params.env ? { env: req.params.env } : {}) + }; + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + options: { + withCredentials: false + } + }; + }); + }, + interpretResponse: (serverResponse, bidderRequest) => { + const response = serverResponse.body; + const data = JSON.parse(bidderRequest.data); + const bidResponse = { + requestId: data.requestId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId || '0', + ttl: config.getConfig('_bidderTimeout'), + currency: 'JPY', + netRevenue: response.netRevenue, + mediaType: VIDEO, + vastXml: response.vastXml, + renderer: createRenderer() + }; + + return [bidResponse]; + } +}; + +function createRenderer() { + const renderer = Renderer.install({ url: VIDEO_PLAYER_URL }); + + renderer.setRender(bid => { + bid.renderer.push(() => { + window.adtagRender(bid); + }); + }); + + return renderer; +} + +registerBidder(spec); diff --git a/modules/byplayBidAdapter.md b/modules/byplayBidAdapter.md new file mode 100644 index 00000000000..67fb9c40d35 --- /dev/null +++ b/modules/byplayBidAdapter.md @@ -0,0 +1,37 @@ +# Overview + +``` +Module Name: ByPlay Bidder Adapter +Module Type: Bidder Adapter +Maintainer: byplayers@tsumikiinc.com +``` + +# Description + +Connects to ByPlay exchange for bids. + +ByPlay bid adapter supports Video. + +# Test Parameters +``` + const adUnits = [ + { + code: 'byplay-ad', + mediaTypes: { + video: { + playerSize: [400, 225], + context: 'outstream' + } + }, + bids: [ + { + bidder: 'byplay', + params: { + sectionId: '7986', + env: 'dev' + } + } + ] + } + ]; +``` diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js index 9364ef2256a..8e1f1487ba7 100644 --- a/modules/c1xBidAdapter.js +++ b/modules/c1xBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; -import { userSync } from '../src/userSync'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { userSync } from '../src/userSync.js'; const BIDDER_CODE = 'c1x'; const URL = 'https://ht.c1exchange.com/ht'; diff --git a/modules/categoryTranslation.js b/modules/categoryTranslation.js index 091b16c8211..9a9289fcd73 100644 --- a/modules/categoryTranslation.js +++ b/modules/categoryTranslation.js @@ -11,12 +11,14 @@ * If publisher has not defined translation file than prebid will use default prebid translation file provided here //cdn.jsdelivr.net/gh/prebid/category-mapping-file@1/freewheel-mapping.json */ -import { config } from '../src/config'; -import { setupBeforeHookFnOnce, hook } from '../src/hook'; -import { ajax } from '../src/ajax'; -import { timestamp, logError, setDataInLocalStorage, getDataFromLocalStorage } from '../src/utils'; -import { addBidResponse } from '../src/auction'; +import { config } from '../src/config.js'; +import { setupBeforeHookFnOnce, hook } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { timestamp, logError } from '../src/utils.js'; +import { addBidResponse } from '../src/auction.js'; +import { getCoreStorageManager } from '../src/storageManager.js'; +export const storage = getCoreStorageManager('categoryTranslation'); const DEFAULT_TRANSLATION_FILE_URL = 'https://cdn.jsdelivr.net/gh/prebid/category-mapping-file@1/freewheel-mapping.json'; const DEFAULT_IAB_TO_FW_MAPPING_KEY = 'iabToFwMappingkey'; const DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB = 'iabToFwMappingkeyPub'; @@ -43,15 +45,15 @@ export function getAdserverCategoryHook(fn, adUnitCode, bid) { let localStorageKey = (config.getConfig('brandCategoryTranslation.translationFile')) ? DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB : DEFAULT_IAB_TO_FW_MAPPING_KEY; if (bid.meta && !bid.meta.adServerCatId) { - let mapping = getDataFromLocalStorage(localStorageKey); + let mapping = storage.getDataFromLocalStorage(localStorageKey); if (mapping) { try { mapping = JSON.parse(mapping); } catch (error) { logError('Failed to parse translation mapping file'); } - if (bid.meta.iabSubCatId && mapping['mapping'] && mapping['mapping'][bid.meta.iabSubCatId]) { - bid.meta.adServerCatId = mapping['mapping'][bid.meta.iabSubCatId]['id']; + if (bid.meta.primaryCatId && mapping['mapping'] && mapping['mapping'][bid.meta.primaryCatId]) { + bid.meta.adServerCatId = mapping['mapping'][bid.meta.primaryCatId]['id']; } else { // This bid will be automatically ignored by adpod module as adServerCatId was not found bid.meta.adServerCatId = undefined; @@ -65,24 +67,29 @@ export function getAdserverCategoryHook(fn, adUnitCode, bid) { export function initTranslation(url, localStorageKey) { setupBeforeHookFnOnce(addBidResponse, getAdserverCategoryHook, 50); - let mappingData = getDataFromLocalStorage(localStorageKey); - if (!mappingData || timestamp() < mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { - ajax(url, - { - success: (response) => { - try { - response = JSON.parse(response); - response['lastUpdated'] = timestamp(); - setDataInLocalStorage(localStorageKey, JSON.stringify(response)); - } catch (error) { - logError('Failed to parse translation mapping file'); + let mappingData = storage.getDataFromLocalStorage(localStorageKey); + try { + mappingData = mappingData ? JSON.parse(mappingData) : undefined; + if (!mappingData || timestamp() > mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { + ajax(url, + { + success: (response) => { + try { + response = JSON.parse(response); + response['lastUpdated'] = timestamp(); + storage.setDataInLocalStorage(localStorageKey, JSON.stringify(response)); + } catch (error) { + logError('Failed to parse translation mapping file'); + } + }, + error: () => { + logError('Failed to load brand category translation file.') } }, - error: () => { - logError('Failed to load brand category translation file.') - } - }, - ); + ); + } + } catch (error) { + logError('Failed to parse translation mapping file'); } } diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js new file mode 100644 index 00000000000..ee15d6bb3ec --- /dev/null +++ b/modules/ccxBidAdapter.js @@ -0,0 +1,235 @@ +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { config } from '../src/config.js' +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); +const BIDDER_CODE = 'ccx' +const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid' +const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6] +const SUPPORTED_VIDEO_MIMES = ['video/mp4', 'video/x-flv'] +const SUPPORTED_VIDEO_PLAYBACK_METHODS = [1, 2, 3, 4] + +function _getDeviceObj () { + let device = {} + device.w = screen.width + device.y = screen.height + device.ua = navigator.userAgent + return device +} + +function _getSiteObj (bidderRequest) { + let site = {} + let url = config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + if (url.length > 0) { + url = url.split('?')[0] + } + site.page = url + + return site +} + +function _validateSizes (sizeObj, type) { + if (!utils.isArray(sizeObj) || typeof sizeObj[0] === 'undefined') { + return false + } + + if (type === 'video' && (!utils.isArray(sizeObj[0]) || sizeObj[0].length !== 2)) { + return false + } + + let result = true + + if (type === 'banner') { + utils._each(sizeObj, function (size) { + if (!utils.isArray(size) || (size.length !== 2)) { + result = false + } + }) + return result + } + + if (type === 'old') { + if (!utils.isArray(sizeObj[0]) && sizeObj.length !== 2) { + result = false + } else if (utils.isArray(sizeObj[0])) { + utils._each(sizeObj, function (size) { + if (!utils.isArray(size) || (size.length !== 2)) { + result = false + } + }) + } + return result; + } + + return true +} + +function _buildBid (bid) { + let placement = {} + placement.id = bid.bidId + placement.secure = 1 + + let sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes') || utils.deepAccess(bid, 'mediaTypes.video.playerSize') || utils.deepAccess(bid, 'sizes') + + if (utils.deepAccess(bid, 'mediaTypes.banner') || utils.deepAccess(bid, 'mediaType') === 'banner' || (!utils.deepAccess(bid, 'mediaTypes.video') && !utils.deepAccess(bid, 'mediaType'))) { + placement.banner = {'format': []} + if (utils.isArray(sizes[0])) { + utils._each(sizes, function (size) { + placement.banner.format.push({'w': size[0], 'h': size[1]}) + }) + } else { + placement.banner.format.push({'w': sizes[0], 'h': sizes[1]}) + } + } else if (utils.deepAccess(bid, 'mediaTypes.video') || utils.deepAccess(bid, 'mediaType') === 'video') { + placement.video = {} + + if (typeof sizes !== 'undefined') { + if (utils.isArray(sizes[0])) { + placement.video.w = sizes[0][0] + placement.video.h = sizes[0][1] + } else { + placement.video.w = sizes[0] + placement.video.h = sizes[1] + } + } + + placement.video.protocols = utils.deepAccess(bid, 'params.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS + placement.video.mimes = utils.deepAccess(bid, 'params.video.mimes') || SUPPORTED_VIDEO_MIMES + placement.video.playbackmethod = utils.deepAccess(bid, 'params.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS + placement.video.skip = utils.deepAccess(bid, 'params.video.skip') || 0 + if (placement.video.skip === 1 && utils.deepAccess(bid, 'params.video.skipafter')) { + placement.video.skipafter = utils.deepAccess(bid, 'params.video.skipafter') + } + } + + placement.ext = {'pid': bid.params.placementId} + + return placement +} + +function _buildResponse (bid, currency, ttl) { + let resp = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + netRevenue: false, + ttl: ttl, + currency: currency + } + + if (bid.ext.type === 'video') { + resp.vastXml = bid.adm + } else { + resp.ad = bid.adm + } + + if (utils.deepAccess(bid, 'dealid')) { + resp.dealId = bid.dealid + } + + return resp +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + if (!utils.deepAccess(bid, 'params.placementId')) { + utils.logWarn('placementId param is reqeuired.') + return false + } + if (utils.deepAccess(bid, 'mediaTypes.banner.sizes')) { + let isValid = _validateSizes(bid.mediaTypes.banner.sizes, 'banner') + if (!isValid) { + utils.logWarn('Bid sizes are invalid.') + } + return isValid + } else if (utils.deepAccess(bid, 'mediaTypes.video.playerSize')) { + let isValid = _validateSizes(bid.mediaTypes.video.playerSize, 'video') + if (!isValid) { + utils.logWarn('Bid sizes are invalid.') + } + return isValid + } else if (utils.deepAccess(bid, 'sizes')) { + let isValid = _validateSizes(bid.sizes, 'old') + if (!isValid) { + utils.logWarn('Bid sizes are invalid.') + } + return isValid + } else { + utils.logWarn('Bid sizes are required.') + return false + } + }, + buildRequests: function (validBidRequests, bidderRequest) { + // check if validBidRequests is not empty + if (validBidRequests.length > 0) { + let requestBody = {} + requestBody.imp = [] + requestBody.site = _getSiteObj(bidderRequest) + requestBody.device = _getDeviceObj() + requestBody.id = bidderRequest.bids[0].auctionId + requestBody.ext = {'ce': (storage.cookiesAreEnabled() ? 1 : 0)} + + // Attaching GDPR Consent Params + if (bidderRequest && bidderRequest.gdprConsent) { + requestBody.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString + } + }; + + requestBody.regs = { + ext: { + gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) + } + }; + } + + utils._each(validBidRequests, function (bid) { + requestBody.imp.push(_buildBid(bid)) + }) + // Return the server request + return { + 'method': 'POST', + 'url': BID_URL, + 'data': JSON.stringify(requestBody) + } + } + }, + interpretResponse: function (serverResponse, request) { + const bidResponses = [] + + // response is not empty (HTTP 204) + if (!utils.isEmpty(serverResponse.body)) { + utils._each(serverResponse.body.seatbid, function (seatbid) { + utils._each(seatbid.bid, function (bid) { + bidResponses.push(_buildResponse(bid, serverResponse.body.cur, serverResponse.body.ext.ttl)) + }) + }) + } + + return bidResponses + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = [] + + if (utils.deepAccess(serverResponses[0], 'body.ext.usersync') && !utils.isEmpty(serverResponses[0].body.ext.usersync)) { + utils._each(serverResponses[0].body.ext.usersync, function (match) { + if ((syncOptions.iframeEnabled && match.type === 'iframe') || (syncOptions.pixelEnabled && match.type === 'image')) { + syncs.push({ + type: match.type, + url: match.url + }) + } + }) + } + + return syncs + } +} +registerBidder(spec) diff --git a/modules/cedatoBidAdapter.js b/modules/cedatoBidAdapter.js index dc6b3c44f3d..ab381698f01 100644 --- a/modules/cedatoBidAdapter.js +++ b/modules/cedatoBidAdapter.js @@ -1,15 +1,15 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); const BIDDER_CODE = 'cedato'; const BID_URL = 'https://h.cedatoplayer.com/hb'; -const SYNC_URL = 'https://h.cedatoplayer.com/hb_usync?uid={UUID}'; -const COOKIE_NAME = 'hb-cedato-id'; -const UUID_LEN = 36; +const SYNC_URL = 'https://h.cedatoplayer.com/hb_usync'; const TTL = 10000; const CURRENCY = 'USD'; -const FIRST_PRICE = 1; const NET_REVENUE = true; export const spec = { @@ -22,17 +22,13 @@ export const spec = { bid.params && bid.params.player_id && utils.checkCookieSupport() && - utils.cookiesAreEnabled() + storage.cookiesAreEnabled() ); }, buildRequests: function(bidRequests, bidderRequest) { - const req = bidRequests[Math.floor(Math.random() * bidRequests.length)]; - const params = req.params; - const at = FIRST_PRICE; - const site = { id: params.player_id, domain: document.domain }; - const device = { ua: navigator.userAgent }; - const user = { id: getUserID() } + const site = { domain: document.domain }; + const device = { ua: navigator.userAgent, w: screen.width, h: screen.height }; const currency = CURRENCY; const tmax = bidderRequest.timeout; const auctionId = bidderRequest.auctionId; @@ -42,10 +38,11 @@ export const spec = { const imp = bidRequests.map(req => { const banner = getMediaType(req, 'banner'); const video = getMediaType(req, 'video'); - const bidfloor = params.bidfloor; + const params = req.params; const bidId = req.bidId; const adUnitCode = req.adUnitCode; const bidRequestsCount = req.bidRequestsCount; + const bidderWinsCount = req.bidderWinsCount; const transactionId = req.transactionId; return { @@ -53,18 +50,17 @@ export const spec = { banner, video, adUnitCode, - bidfloor, bidRequestsCount, - transactionId + bidderWinsCount, + transactionId, + params }; }); const payload = { version: '$prebid.version$', - at, site, device, - user, imp, currency, tmax, @@ -75,6 +71,8 @@ export const spec = { if (bidderRequest) { payload.referer_info = bidderRequest.refererInfo; + payload.us_privacy = bidderRequest.uspConsent; + if (bidderRequest.gdprConsent) { payload.gdpr_consent = { consent_string: bidderRequest.gdprConsent.consentString, @@ -83,12 +81,7 @@ export const spec = { } } - return { - method: 'POST', - url: params.bid_url || BID_URL, - data: JSON.stringify(payload), - bidderRequest - }; + return formatRequest(payload, bidderRequest); }, interpretResponse: function(resp, {bidderRequest}) { @@ -108,12 +101,12 @@ export const spec = { return bids; }, - getUserSyncs: function(syncOptions, resps, gdprConsent) { + getUserSyncs: function(syncOptions, resps, gdprConsent, uspConsent) { const syncs = []; if (syncOptions.iframeEnabled) { - syncs.push(getSync('iframe', gdprConsent)); + syncs.push(getSync('iframe', gdprConsent, uspConsent)); } else if (syncOptions.pixelEnabled) { - syncs.push(getSync('image', gdprConsent)); + syncs.push(getSync('image', gdprConsent, uspConsent)); } return syncs; } @@ -186,10 +179,36 @@ function newBid(serverBid, bidderRequest) { return bid; } -const getSync = (type, gdprConsent) => { - const uuid = getUserID(); +function formatRequest(payload, bidderRequest) { + const payloadByUrl = {}; + const requests = []; + + payload.imp.forEach(imp => { + const url = imp.params.bid_url || BID_URL; + if (!payloadByUrl[url]) { + payloadByUrl[url] = { + ...payload, + imp: [] + }; + } + payloadByUrl[url].imp.push(imp); + }); + + for (const url in payloadByUrl) { + requests.push({ + url, + method: 'POST', + data: JSON.stringify(payloadByUrl[url]), + bidderRequest + }); + } + + return requests; +} + +const getSync = (type, gdprConsent, uspConsent = '') => { const syncUrl = SYNC_URL; - let params = '&type=' + type; + let params = '?type=' + type + '&us_privacy=' + uspConsent; if (gdprConsent && typeof gdprConsent.consentString === 'string') { if (typeof gdprConsent.gdprApplies === 'boolean') { params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; @@ -199,26 +218,10 @@ const getSync = (type, gdprConsent) => { } return { type: type, - url: syncUrl.replace('{UUID}', uuid) + params, + url: syncUrl + params, }; } -const getUserID = () => { - const cookieName = COOKIE_NAME; - const uuidLen = UUID_LEN; - - const i = document.cookie.indexOf(cookieName); - - if (i === -1) { - const uuid = utils.generateUUID(); - document.cookie = `${cookieName}=${uuid}; path=/`; - return uuid; - } - - const j = i + cookieName.length + 1; - return document.cookie.substring(j, j + uuidLen); -}; - const getFormats = arr => arr.map((s) => { return { w: s[0], h: s[1] }; }); diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js new file mode 100644 index 00000000000..a8f37450d68 --- /dev/null +++ b/modules/cleanmedianetBidAdapter.js @@ -0,0 +1,315 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {Renderer} from '../src/Renderer.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +export const helper = { + getTopWindowDomain: function (url) { + const domainStart = url.indexOf('://') + '://'.length; + return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)); + }, + startsWith: function (str, search) { + return str.substr(0, search.length) === search; + }, + getMediaType: function (bid) { + if (bid.ext) { + if (bid.ext.media_type) { + return bid.ext.media_type.toLowerCase(); + } else if (bid.ext.vast_url) { + return VIDEO; + } else { + return BANNER; + } + } + return BANNER; + } +}; + +export const spec = { + code: 'cleanmedianet', + aliases: [], + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function (bid) { + return ( + !!bid.params.supplyPartnerId && + typeof bid.params.supplyPartnerId === 'string' && + (typeof bid.params.bidfloor === 'undefined' || + typeof bid.params.bidfloor === 'number') && + (typeof bid.params['adpos'] === 'undefined' || + typeof bid.params['adpos'] === 'number') && + (typeof bid.params['protocols'] === 'undefined' || + Array.isArray(bid.params['protocols'])) && + (typeof bid.params.instl === 'undefined' || + bid.params.instl === 0 || + bid.params.instl === 1) + ); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const { + adUnitCode, + auctionId, + mediaTypes, + params, + sizes, + transactionId + } = bidRequest; + const baseEndpoint = 'https://bidder.cleanmediaads.com'; + const rtbEndpoint = + `${baseEndpoint}/r/${ + params.supplyPartnerId + }/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + + (params.query ? '&' + params.query : ''); + let url = + config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; + + const rtbBidRequest = { + id: auctionId, + site: { + domain: helper.getTopWindowDomain(url), + page: url, + ref: bidderRequest.refererInfo.referer + }, + device: { + ua: navigator.userAgent, + dnt: utils.getDNT() ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + }, + imp: [], + ext: {}, + user: { + ext: {} + } + }; + + if ( + bidderRequest.gdprConsent && + bidderRequest.gdprConsent.consentString && + bidderRequest.gdprConsent.gdprApplies + ) { + rtbBidRequest.ext.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + rtbBidRequest.regs = { + ext: { + gdpr: bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + rtbBidRequest.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString + } + } + } + + const imp = { + id: transactionId, + instl: params.instl === 1 ? 1 : 0, + tagid: adUnitCode, + bidfloor: params.bidfloor || 0, + bidfloorcur: 'USD', + secure: 1 + }; + + const hasFavoredMediaType = + params.favoredMediaType && + this.supportedMediaTypes.includes(params.favoredMediaType); + + if (!mediaTypes || mediaTypes.banner) { + if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { + const bannerImp = Object.assign({}, imp, { + banner: { + w: sizes.length ? sizes[0][0] : 300, + h: sizes.length ? sizes[0][1] : 250, + pos: params.pos || 0, + topframe: utils.inIframe() ? 0 : 1 + } + }); + rtbBidRequest.imp.push(bannerImp); + } + } + + if (mediaTypes && mediaTypes.video) { + if (!hasFavoredMediaType || params.favoredMediaType === VIDEO) { + let videoImp = { + video: { + protocols: params.protocols || [1, 2, 3, 4, 5, 6], + pos: params.pos || 0, + ext: {context: mediaTypes.video.context} + } + }; + + let playerSize = mediaTypes.video.playerSize || sizes; + if (utils.isArray(playerSize[0])) { + videoImp.video.w = playerSize[0][0]; + videoImp.video.h = playerSize[0][1]; + } else if (utils.isNumber(playerSize[0])) { + videoImp.video.w = playerSize[0]; + videoImp.video.h = playerSize[1]; + } else { + videoImp.video.w = 300; + videoImp.video.h = 250; + } + + videoImp = Object.assign({}, imp, videoImp); + rtbBidRequest.imp.push(videoImp); + } + } + + if (rtbBidRequest.imp.length === 0) { + return; + } + + return { + method: 'POST', + url: rtbEndpoint, + data: rtbBidRequest, + bidRequest + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const response = serverResponse && serverResponse.body; + if (!response) { + utils.logError('empty response'); + return []; + } + + const bids = response.seatbid.reduce( + (acc, seatBid) => acc.concat(seatBid.bid), + [] + ); + let outBids = []; + + bids.forEach(bid => { + const outBid = { + requestId: bidRequest.bidRequest.bidId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: 360, + creativeId: bid.crid || bid.adid, + netRevenue: true, + currency: bid.cur || response.cur, + mediaType: helper.getMediaType(bid) + }; + + if ( + utils.deepAccess( + bidRequest.bidRequest, + 'mediaTypes.' + outBid.mediaType + ) + ) { + if (outBid.mediaType === BANNER) { + outBids.push(Object.assign({}, outBid, {ad: bid.adm})); + } else if (outBid.mediaType === VIDEO) { + const context = utils.deepAccess( + bidRequest.bidRequest, + 'mediaTypes.video.context' + ); + outBids.push( + Object.assign({}, outBid, { + vastUrl: bid.ext.vast_url, + vastXml: bid.adm, + renderer: + context === 'outstream' + ? newRenderer(bidRequest.bidRequest, bid) + : undefined + }) + ); + } + } + }); + return outBids; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + const syncs = []; + const gdprApplies = + gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' + ? gdprConsent.gdprApplies + : false; + const suffix = gdprApplies + ? 'gc=' + encodeURIComponent(gdprConsent.consentString) + : 'gc=missing'; + serverResponses.forEach(resp => { + if (resp.body) { + const bidResponse = resp.body; + if (bidResponse.ext && Array.isArray(bidResponse.ext['utrk'])) { + bidResponse.ext['utrk'].forEach(pixel => { + const url = + pixel.url + + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix); + return syncs.push({type: pixel.type, url}); + }); + } + if (Array.isArray(bidResponse.seatbid)) { + bidResponse.seatbid.forEach(seatBid => { + if (Array.isArray(seatBid.bid)) { + seatBid.bid.forEach(bid => { + if (bid.ext && Array.isArray(bid.ext['utrk'])) { + bid.ext['utrk'].forEach(pixel => { + const url = + pixel.url + + (pixel.url.indexOf('?') > 0 + ? '&' + suffix + : '?' + suffix); + return syncs.push({type: pixel.type, url}); + }); + } + }); + } + }); + } + } + }); + return syncs; + } +}; + +function newRenderer(bidRequest, bid, rendererOptions = {}) { + const renderer = Renderer.install({ + url: + (bidRequest.params && bidRequest.params.rendererUrl) || + (bid.ext && bid.ext.renderer_url) || + 'https://s.wlplayer.com/video/latest/renderer.js', + config: rendererOptions, + loaded: false + }); + try { + renderer.setRender(renderOutstream); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function renderOutstream(bid) { + bid.renderer.push(() => { + const unitId = bid.adUnitCode + '/' + bid.adId; + window['GamoshiPlayer'].renderAd({ + id: unitId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: document.getElementById(bid.adUnitCode), + width: bid.width, + height: bid.height, + events: { + ALL_ADS_COMPLETED: () => + window.setTimeout(() => { + window['GamoshiPlayer'].removeAd(unitId); + }, 300) + }, + vastUrl: bid.vastUrl, + vastXml: bid.vastXml + }); + }); +} + +registerBidder(spec); diff --git a/modules/clickforceBidAdapter.js b/modules/clickforceBidAdapter.js index 43ef76cc931..20408fe9177 100644 --- a/modules/clickforceBidAdapter.js +++ b/modules/clickforceBidAdapter.js @@ -1,8 +1,8 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {BANNER, NATIVE} from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; const BIDDER_CODE = 'clickforce'; -const ENDPOINT_URL = 'https://ad.doublemax.net/adserver/prebid.json?cb=' + new Date().getTime() + '&hb=1&ver=1.21'; +const ENDPOINT_URL = 'https://ad.holmesmind.com/adserver/prebid.json?cb=' + new Date().getTime() + '&hb=1&ver=1.21'; export const spec = { code: BIDDER_CODE, @@ -112,12 +112,12 @@ export const spec = { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: 'https://cdn.doublemax.net/js/capmapping.htm' + url: 'https://cdn.holmesmind.com/js/capmapping.htm' }] } else if (syncOptions.pixelEnabled) { return [{ type: 'image', - url: 'https://c.doublemax.net/cm' + url: 'https://c.holmesmind.com/cm' }] } } diff --git a/modules/clicktripzBidAdapter.js b/modules/clicktripzBidAdapter.js new file mode 100644 index 00000000000..2149cbe4527 --- /dev/null +++ b/modules/clicktripzBidAdapter.js @@ -0,0 +1,67 @@ +import {logError, _each} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'clicktripz'; +const ENDPOINT_URL = 'https://www.clicktripz.com/x/prebid/v1'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['ctz'], // short code + + isBidRequestValid: function (bid) { + if (bid && bid.params && bid.params.placementId && bid.params.siteId) { + return true; + } + + return false; + }, + + buildRequests: function (validBidRequests) { + let bidRequests = []; + + _each(validBidRequests, function (bid) { + bidRequests.push({ + bidId: bid.bidId, + placementId: bid.params.placementId, + siteId: bid.params.siteId, + sizes: bid.sizes.map(function (size) { + return size.join('x') + }) + }); + }); + return { + method: 'POST', + url: ENDPOINT_URL, + data: bidRequests + }; + }, + + interpretResponse: function (serverResponse) { + let bidResponses = []; + + if (serverResponse && serverResponse.body) { + _each(serverResponse.body, function (bid) { + if (bid.errors) { + logError(bid.errors); + return; + } + + const size = bid.size.split('x'); + bidResponses.push({ + requestId: bid.bidId, + cpm: bid.cpm, + width: size[0], + height: size[1], + creativeId: bid.creativeId, + currency: bid.currency, + netRevenue: bid.netRevenue, + ttl: bid.ttl, + adUrl: bid.adUrl + }); + }); + } + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/clicktripzBidAdapter.md b/modules/clicktripzBidAdapter.md new file mode 100644 index 00000000000..1de1e26f37a --- /dev/null +++ b/modules/clicktripzBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Clicktripz Bidder Adapter +Module Type: Bidder Adapter +Maintainer: integration-support@clicktripz.com +``` + +# Description +Our module makes it easy to integrate Clicktripz demand sources into your website. + +Supported Ad Fortmats: +* Banner + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [ + { + bidder: "clicktripz", + params: { + placementId: '4312c63f', + siteId: 'prebid', + } + } + ] + } + ]; diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index 26ea7bb71e1..240a3f1fcde 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import {config} from '../src/config'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'coinzilla'; const ENDPOINT_URL = 'https://request.czilladx.com/serve/request.php'; diff --git a/modules/collectcentBidAdapter.js b/modules/collectcentBidAdapter.js index f7f5f3ef0c4..add3e06430d 100644 --- a/modules/collectcentBidAdapter.js +++ b/modules/collectcentBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; -import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; const BIDDER_CODE = 'collectcent'; const URL_MULTI = 'https://publishers.motionspots.com/?c=o&m=multi'; diff --git a/modules/colombiaBidAdapter.js b/modules/colombiaBidAdapter.js new file mode 100644 index 00000000000..55109dbaab2 --- /dev/null +++ b/modules/colombiaBidAdapter.js @@ -0,0 +1,82 @@ +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +const BIDDER_CODE = 'colombia'; +const ENDPOINT_URL = 'https://ade.clmbtech.com/cde/prebid.htm'; +const HOST_NAME = document.location.protocol + '//' + window.location.host; + +export const spec = { + code: BIDDER_CODE, + aliases: ['clmb'], + supportedMediaTypes: [BANNER], + isBidRequestValid: function(bid) { + return !!(bid.params.placementId); + }, + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const params = bidRequest.params; + const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const width = sizes.split('x')[0]; + const height = sizes.split('x')[1]; + const placementId = params.placementId; + const cb = Math.floor(Math.random() * 99999999999); + const bidId = bidRequest.bidId; + const referrer = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : ''; + const payload = { + v: 'hb1', + p: placementId, + w: width, + h: height, + cb: cb, + r: referrer, + uid: bidId, + t: 'i', + d: HOST_NAME, + }; + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload, + } + }); + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const crid = response.creativeId || 0; + const width = response.width || 0; + const height = response.height || 0; + let cpm = response.cpm || 0; + if (width == 300 && height == 250) { + cpm = cpm * 0.2; + } + if (width == 320 && height == 50) { + cpm = cpm * 0.55; + } + if (cpm <= 0) { + return bidResponses; + } + if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'USD'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const 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: bidRequest.data.r, + ad: response.ad + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/colombiaBidAdapter.md b/modules/colombiaBidAdapter.md index 2131fcb4c5a..c754e49771d 100644 --- a/modules/colombiaBidAdapter.md +++ b/modules/colombiaBidAdapter.md @@ -10,13 +10,17 @@ Maintainer: colombiaonline@timesinteret.in Connect to COLOMBIA for bids. -THE COLOMBIA adapter requires setup and approval from the COLOMBIA team. Please reach out to your account team or colombiaonline@timesinteret.in for more information. +COLOMBIA adapter requires setup and approval from the COLOMBIA team. Please reach out to your account team or colombiaonline@timesinteret.in for more information. # Test Parameters ``` var adUnits = [{ code: 'test-ad-div', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250],[728,90],[320,50]] + } + }, bids: [{ bidder: 'colombia', params: { diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index adcd5df9fb6..baa60a76a0d 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; -import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; const BIDDER_CODE = 'colossusssp'; const G_URL = 'https://colossusssp.com/?c=o&m=multi'; @@ -23,6 +23,19 @@ function isBidResponseValid(bid) { } } +function getUserId(eids, id, source, uidExt) { + if (id) { + var uid = { id }; + if (uidExt) { + uid.ext = uidExt; + } + eids.push({ + source, + uids: [ uid ] + }); + } +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -60,13 +73,17 @@ export const spec = { 'secure': location.protocol === 'https:' ? 1 : 0, 'host': location.host, 'page': location.pathname, - 'placements': placements + 'placements': placements, }; if (bidderRequest) { if (bidderRequest.uspConsent) { request.ccpa = bidderRequest.uspConsent; } + if (bidderRequest.gdprConsent) { + request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL' + request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + } } for (let i = 0; i < validBidRequests.length; i++) { @@ -76,11 +93,20 @@ export const spec = { placementId: bid.params.placement_id, bidId: bid.bidId, sizes: bid.mediaTypes[traff].sizes, - traffic: traff + traffic: traff, + eids: [] }; if (bid.schain) { placement.schain = bid.schain; } + if (bid.userId) { + getUserId(placement.eids, bid.userId.britepoolid, 'britepool.com'); + getUserId(placement.eids, bid.userId.idl_env, 'identityLink'); + getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com') + getUserId(placement.eids, bid.userId.tdid, 'adserver.org', { + rtiPartner: 'TDID' + }); + } placements.push(placement); } return { diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js new file mode 100644 index 00000000000..04459ec1f09 --- /dev/null +++ b/modules/connectadBidAdapter.js @@ -0,0 +1,254 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js' +import {config} from '../src/config.js'; +import {createEidsArray} from './userId/eids.js'; + +const BIDDER_CODE = 'connectad'; +const BIDDER_CODE_ALIAS = 'connectadrealtime'; +const ENDPOINT_URL = 'https://i.connectad.io/api/v2'; +const SUPPORTED_MEDIA_TYPES = [BANNER]; + +export const spec = { + code: BIDDER_CODE, + aliases: [ BIDDER_CODE_ALIAS ], + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + isBidRequestValid: function(bid) { + return !!(bid.params.networkId && bid.params.siteId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + let ret = { + method: 'POST', + url: '', + data: '', + bidRequest: [] + }; + + if (validBidRequests.length < 1) { + return ret; + } + + const data = Object.assign({ + placements: [], + time: Date.now(), + user: {}, + url: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href, + referrer: window.document.referrer, + referrer_info: bidderRequest.refererInfo, + screensize: getScreenSize(), + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + language: navigator.language, + ua: navigator.userAgent, + pversion: '$prebid.version$' + }); + + // coppa compliance + if (config.getConfig('coppa') === true) { + utils.deepSetValue(data, 'user.coppa', 1); + } + + // adding schain object + if (validBidRequests[0].schain) { + utils.deepSetValue(data, 'source.ext.schain', validBidRequests[0].schain); + } + + // Attaching GDPR Consent Params + if (bidderRequest.gdprConsent) { + let gdprApplies; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + utils.deepSetValue(data, 'user.ext.gdpr', gdprApplies); + utils.deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + // CCPA + if (bidderRequest.uspConsent) { + utils.deepSetValue(data, 'user.ext.us_privacy', bidderRequest.uspConsent); + } + + // EIDS Support + if (validBidRequests[0].userId) { + data.user.ext.eids = createEidsArray(validBidRequests[0].userId); + } + + validBidRequests.map(bid => { + const placement = Object.assign({ + id: bid.transactionId, + divName: bid.bidId, + pisze: bid.mediaTypes.banner.sizes[0] || bid.sizes[0], + sizes: bid.mediaTypes.banner.sizes, + adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes), + floor: getBidFloor(bid) + }, bid.params); + + if (placement.networkId && placement.siteId) { + data.placements.push(placement); + } + }); + + ret.data = JSON.stringify(data); + ret.bidRequest = validBidRequests; + ret.url = ENDPOINT_URL; + + return ret; + }, + + interpretResponse: function(serverResponse, bidRequest, bidderRequest) { + let bid; + let bids; + let bidId; + let bidObj; + let bidResponses = []; + + bids = bidRequest.bidRequest; + + serverResponse = (serverResponse || {}).body; + for (let i = 0; i < bids.length; i++) { + bid = {}; + bidObj = bids[i]; + bidId = bidObj.bidId; + + if (serverResponse) { + const decision = serverResponse.decisions && serverResponse.decisions[bidId]; + const price = decision && decision.pricing && decision.pricing.clearPrice; + + if (decision && price) { + bid.requestId = bidId; + bid.cpm = price; + bid.width = decision.width; + bid.height = decision.height; + bid.dealid = decision.dealid || null; + bid.ad = retrieveAd(decision); + bid.currency = 'USD'; + bid.creativeId = decision.adId; + bid.ttl = 360; + bid.netRevenue = true; + bidResponses.push(bid); + } + } + } + + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + let syncEndpoint = 'https://cdn.connectad.io/connectmyusers.php?'; + + if (gdprConsent) { + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); + } + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'gdpr_consent', gdprConsent.consentString); + } + + if (uspConsent) { + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'us_privacy', uspConsent); + } + + if (config.getConfig('coppa') === true) { + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'coppa', 1); + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: syncEndpoint + }]; + } else { + utils.logWarn('Bidder ConnectAd: Please activate iFrame Sync'); + } + } +}; + +const sizeMap = [ + null, + '120x90', + '200x200', + '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', + '980x120', + '980x150', + '320x150', + '300x300', + '200x600', + '320x500', + '320x320' +]; + +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 getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: '*' + }); + } + + let floor = floorInfo.floor || 0; + + return floor; +} + +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; +} + +function retrieveAd(decision) { + return decision.contents && decision.contents[0] && decision.contents[0].body; +} + +function getScreenSize() { + return [window.screen.width, window.screen.height].join('x'); +} + +registerBidder(spec); diff --git a/modules/connectadBidAdapter.md b/modules/connectadBidAdapter.md new file mode 100644 index 00000000000..e63494e1add --- /dev/null +++ b/modules/connectadBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: ConnectAd PreBid Adapter +Module Type: Bidder Adapter +Maintainer: support@connectad.io +``` + +# Description + +ConnectAd bid adapter supports only Banner at present. Video and Mobile will follow Q2/2020 + +# Sample Ad Unit: For Publishers +``` +var adUnits = [ +{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'connectad', + params: { + siteId: 123456, + networkId: 123456, + bidfloor: 0.20 // Optional: Requested Bidfloor + } + }] +} + +# ## Configuration +ConnectAd recommends the UserSync configuration below otherwise we will not be able to performe user syncs. + +```javascript +pbjs.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: ['connectad'], + filter: 'include' + } + } + } +}); \ No newline at end of file diff --git a/modules/consentManagement.js b/modules/consentManagement.js index d5703c1a784..90fe24735db 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -4,21 +4,26 @@ * and make it available for any GDPR supported adapters to read/pass this information to * their system. */ -import * as utils from '../src/utils'; -import { config } from '../src/config'; -import { gdprDataHandler } from '../src/adapterManager'; -import includes from 'core-js/library/fn/array/includes'; -import strIncludes from 'core-js/library/fn/string/includes'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { gdprDataHandler } from '../src/adapterManager.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import strIncludes from 'core-js-pure/features/string/includes.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; const DEFAULT_ALLOW_AUCTION_WO_CONSENT = true; +export const allowAuction = { + value: DEFAULT_ALLOW_AUCTION_WO_CONSENT, + definedInConfig: false +} export let userCMP; export let consentTimeout; -export let allowAuction; +export let gdprScope; export let staticConsentData; +let cmpVersion = 0; let consentData; let addedConsentHook = false; @@ -47,11 +52,72 @@ function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { - function handleCmpResponseCallbacks() { + function findCMP() { + let f = window; + let cmpFrame; + let cmpFunction; + while (!cmpFrame) { + try { + if (typeof f.__tcfapi === 'function' || typeof f.__cmp === 'function') { + if (typeof f.__tcfapi === 'function') { + cmpVersion = 2; + cmpFunction = f.__tcfapi; + } else { + cmpVersion = 1; + cmpFunction = f.__cmp; + } + cmpFrame = f; + break; + } + } catch (e) { } + + // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env + try { + if (f.frames['__tcfapiLocator']) { + cmpVersion = 2; + cmpFrame = f; + break; + } + } catch (e) { } + + try { + if (f.frames['__cmpLocator']) { + cmpVersion = 1; + cmpFrame = f; + break; + } + } catch (e) { } + + if (f === window.top) break; + f = f.parent; + } + return { + cmpFrame, + cmpFunction + }; + } + + function v2CmpResponseCallback(tcfData, success) { + utils.logInfo('Received a response from CMP', tcfData); + if (success) { + if (tcfData.gdprApplies === false) { + cmpSuccess(tcfData, hookConfig); + } else if (tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { + cmpSuccess(tcfData, hookConfig); + } else if (tcfData.eventStatus === 'cmpuishown' && tcfData.tcString && tcfData.purposeOneTreatment === true) { + cmpSuccess(tcfData, hookConfig); + } + } else { + cmpError('CMP unable to register callback function. Please check CMP setup.', hookConfig); + } + } + + function handleV1CmpResponseCallbacks() { const cmpResponse = {}; function afterEach() { if (cmpResponse.getConsentData && cmpResponse.getVendorConsents) { + utils.logInfo('Received all requested responses from CMP', cmpResponse); cmpSuccess(cmpResponse, hookConfig); } } @@ -68,10 +134,13 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { } } - let callbackHandler = handleCmpResponseCallbacks(); + let v1CallbackHandler = handleV1CmpResponseCallbacks(); let cmpCallbacks = {}; - let cmpFunction; + let { cmpFrame, cmpFunction } = findCMP(); + if (!cmpFrame) { + return cmpError('CMP not found.', hookConfig); + } // to collect the consent information from the user, we perform two calls to the CMP in parallel: // first to collect the user's consent choices represented in an encoded string (via getConsentData) // second to collect the user's full unparsed consent information (via getVendorConsents) @@ -81,34 +150,28 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { // check to see if prebid is in a safeframe (with CMP support) // else assume prebid may be inside an iframe and use the IAB CMP locator code to see if CMP's located in a higher parent window. this works in cross domain iframes // if the CMP is not found, the iframe function will call the cmpError exit callback to abort the rest of the CMP workflow - try { - cmpFunction = window.__cmp || utils.getWindowTop().__cmp; - } catch (e) { } if (utils.isFn(cmpFunction)) { - cmpFunction('getConsentData', null, callbackHandler.consentDataCallback); - cmpFunction('getVendorConsents', null, callbackHandler.vendorConsentsCallback); - } else if (inASafeFrame() && typeof window.$sf.ext.cmp === 'function') { - callCmpWhileInSafeFrame('getConsentData', callbackHandler.consentDataCallback); - callCmpWhileInSafeFrame('getVendorConsents', callbackHandler.vendorConsentsCallback); - } else { - // find the CMP frame - let f = window; - let cmpFrame; - while (!cmpFrame) { - try { - if (f.frames['__cmpLocator']) cmpFrame = f; - } catch (e) { } - if (f === window.top) break; - f = f.parent; + utils.logInfo('Detected CMP API is directly accessible, calling it now...'); + if (cmpVersion === 1) { + cmpFunction('getConsentData', null, v1CallbackHandler.consentDataCallback); + cmpFunction('getVendorConsents', null, v1CallbackHandler.vendorConsentsCallback); + } else if (cmpVersion === 2) { + cmpFunction('addEventListener', cmpVersion, v2CmpResponseCallback); } - - if (!cmpFrame) { - return cmpError('CMP not found.', hookConfig); + } else if (cmpVersion === 1 && inASafeFrame() && typeof window.$sf.ext.cmp === 'function') { + // this safeframe workflow is only supported with TCF v1 spec; the v2 recommends to use the iframe postMessage route instead (even if you are in a safeframe). + utils.logInfo('Detected Prebid.js is encased in a SafeFrame and CMP is registered, calling it now...'); + callCmpWhileInSafeFrame('getConsentData', v1CallbackHandler.consentDataCallback); + callCmpWhileInSafeFrame('getVendorConsents', v1CallbackHandler.vendorConsentsCallback); + } else { + utils.logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); + if (cmpVersion === 1) { + callCmpWhileInIframe('getConsentData', cmpFrame, v1CallbackHandler.consentDataCallback); + callCmpWhileInIframe('getVendorConsents', cmpFrame, v1CallbackHandler.vendorConsentsCallback); + } else if (cmpVersion === 2) { + callCmpWhileInIframe('addEventListener', cmpFrame, v2CmpResponseCallback); } - - callCmpWhileInIframe('getConsentData', cmpFrame, callbackHandler.consentDataCallback); - callCmpWhileInIframe('getVendorConsents', cmpFrame, callbackHandler.vendorConsentsCallback); } function inASafeFrame() { @@ -138,17 +201,22 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { } function callCmpWhileInIframe(commandName, cmpFrame, moduleCallback) { + let apiName = (cmpVersion === 2) ? '__tcfapi' : '__cmp'; + /* Setup up a __cmp function to do the postMessage and stash the callback. This function behaves (from the caller's perspective identicially to the in-frame __cmp call */ - window.__cmp = function (cmd, arg, callback) { + window[apiName] = function (cmd, arg, callback) { let callId = Math.random() + ''; + let callName = `${apiName}Call`; let msg = { - __cmpCall: { + [callName]: { command: cmd, parameter: arg, callId: callId } }; + if (cmpVersion !== 1) msg[callName].version = cmpVersion; + cmpCallbacks[callId] = callback; cmpFrame.postMessage(msg, '*'); } @@ -157,28 +225,19 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { window.addEventListener('message', readPostMessageResponse, false); // call CMP - window.__cmp(commandName, null, cmpIframeCallback); + window[apiName](commandName, null, moduleCallback); function readPostMessageResponse(event) { - let json = (typeof event.data === 'string' && strIncludes(event.data, 'cmpReturn')) ? JSON.parse(event.data) : event.data; - if (json.__cmpReturn && json.__cmpReturn.callId) { - let i = json.__cmpReturn; + let cmpDataPkgName = `${apiName}Return`; + let json = (typeof event.data === 'string' && strIncludes(event.data, cmpDataPkgName)) ? JSON.parse(event.data) : event.data; + if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) { + let payload = json[cmpDataPkgName]; // TODO - clean up this logic (move listeners?); we have duplicate messages responses because 2 eventlisteners are active from the 2 cmp requests running in parallel - if (typeof cmpCallbacks[i.callId] !== 'undefined') { - cmpCallbacks[i.callId](i.returnValue, i.success); - delete cmpCallbacks[i.callId]; + if (typeof cmpCallbacks[payload.callId] !== 'undefined') { + cmpCallbacks[payload.callId](payload.returnValue, payload.success); } } } - - function removePostMessageListener() { - window.removeEventListener('message', readPostMessageResponse, false); - } - - function cmpIframeCallback(consentObject) { - removePostMessageListener(); - moduleCallback(consentObject); - } } } @@ -204,6 +263,7 @@ export function requestBidsHook(fn, reqBidsConfigObj) { // in case we already have consent (eg during bid refresh) if (consentData) { + utils.logInfo('User consent information already known. Pulling internally stored information...'); return exitModule(null, hookConfig); } @@ -232,22 +292,58 @@ export function requestBidsHook(fn, reqBidsConfigObj) { * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ function processCmpData(consentObject, hookConfig) { - let gdprApplies = consentObject && consentObject.getConsentData && consentObject.getConsentData.gdprApplies; - if ( - (typeof gdprApplies !== 'boolean') || - (gdprApplies === true && - !(utils.isStr(consentObject.getConsentData.consentData) && - utils.isPlainObject(consentObject.getVendorConsents) && - Object.keys(consentObject.getVendorConsents).length > 1 + function checkV1Data(consentObject) { + let gdprApplies = consentObject && consentObject.getConsentData && consentObject.getConsentData.gdprApplies; + return !!( + (typeof gdprApplies !== 'boolean') || + (gdprApplies === true && + !(utils.isStr(consentObject.getConsentData.consentData) && + utils.isPlainObject(consentObject.getVendorConsents) && + Object.keys(consentObject.getVendorConsents).length > 1 + ) ) - ) - ) { - cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject); - } else { - clearTimeout(hookConfig.timer); - storeConsentData(consentObject); + ); + } + + function checkV2Data() { + // if CMP does not respond with a gdprApplies boolean, use defaultGdprScope (gdprScope) + let gdprApplies = consentObject && typeof consentObject.gdprApplies === 'boolean' ? consentObject.gdprApplies : gdprScope; + let tcString = consentObject && consentObject.tcString; + return !!( + (typeof gdprApplies !== 'boolean') || + (gdprApplies === true && !utils.isStr(tcString)) + ); + } + + // do extra things for static config + if (userCMP === 'static') { + cmpVersion = (consentObject.getConsentData) ? 1 : (consentObject.getTCData) ? 2 : 0; + // remove extra layer in static v2 data object so it matches normal v2 CMP object for processing step + if (cmpVersion === 2) { + consentObject = consentObject.getTCData; + } + } + + // determine which set of checks to run based on cmpVersion + let checkFn = (cmpVersion === 1) ? checkV1Data : (cmpVersion === 2) ? checkV2Data : null; + + // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2. + if (allowAuction.definedInConfig && cmpVersion === 2) { + utils.logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`); + } else if (!allowAuction.definedInConfig && cmpVersion === 1) { + utils.logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); + } - exitModule(null, hookConfig); + if (utils.isFn(checkFn)) { + if (checkFn(consentObject)) { + cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject); + } else { + clearTimeout(hookConfig.timer); + storeConsentData(consentObject); + exitModule(null, hookConfig); + } + } else { + cmpFailed('Unable to derive CMP version to process data. Consent object does not conform to TCF v1 or v2 specs.', hookConfig, consentObject); } } @@ -268,28 +364,40 @@ function cmpFailed(errMsg, hookConfig, extraArgs) { clearTimeout(hookConfig.timer); // still set the consentData to undefined when there is a problem as per config options - if (allowAuction) { + if (allowAuction.value && cmpVersion === 1) { storeConsentData(undefined); } exitModule(errMsg, hookConfig, extraArgs); } /** - * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanger.js for later in the auction + * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanager.js for later in the auction * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) */ function storeConsentData(cmpConsentObject) { - consentData = { - consentString: (cmpConsentObject) ? cmpConsentObject.getConsentData.consentData : undefined, - vendorData: (cmpConsentObject) ? cmpConsentObject.getVendorConsents : undefined, - gdprApplies: (cmpConsentObject) ? cmpConsentObject.getConsentData.gdprApplies : undefined - }; + if (cmpVersion === 1) { + consentData = { + consentString: (cmpConsentObject) ? cmpConsentObject.getConsentData.consentData : undefined, + vendorData: (cmpConsentObject) ? cmpConsentObject.getVendorConsents : undefined, + gdprApplies: (cmpConsentObject) ? cmpConsentObject.getConsentData.gdprApplies : gdprScope + }; + } else { + consentData = { + consentString: (cmpConsentObject) ? cmpConsentObject.tcString : undefined, + vendorData: (cmpConsentObject) || undefined, + gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope + }; + if (cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) { + consentData.addtlConsent = cmpConsentObject.addtlConsent; + }; + } + consentData.apiVersion = cmpVersion; gdprDataHandler.setConsentData(consentData); } /** * This function handles the exit logic for the module. - * There are several paths in the module's logic to call this function and we only allow 1 of the 3 potential exits to happen before suppressing others. + * While there are several paths in the module's logic to call this function, we only allow 1 of the 3 potential exits to happen before suppressing others. * * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios. * One scenario could be auction was canceled due to timeout with CMP being reached. @@ -313,8 +421,8 @@ function exitModule(errMsg, hookConfig, extraArgs) { let nextFn = hookConfig.nextFn; if (errMsg) { - if (allowAuction) { - utils.logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs); + if (allowAuction.value && cmpVersion === 1) { + utils.logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs); nextFn.apply(context, args); } else { utils.logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs); @@ -336,12 +444,13 @@ function exitModule(errMsg, hookConfig, extraArgs) { export function resetConsentData() { consentData = undefined; userCMP = undefined; + cmpVersion = 0; gdprDataHandler.setConsentData(null); } /** * A configuration function that initializes some module variables, as well as add a hook into the requestBids function - * @param {object} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) + * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) */ export function setConsentConfig(config) { // if `config.gdpr` or `config.usp` exist, assume new config format. @@ -366,12 +475,13 @@ export function setConsentConfig(config) { } if (typeof config.allowAuctionWithoutConsent === 'boolean') { - allowAuction = config.allowAuctionWithoutConsent; - } else { - allowAuction = DEFAULT_ALLOW_AUCTION_WO_CONSENT; - utils.logInfo(`consentManagement config did not specify allowAuctionWithoutConsent. Using system default setting (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); + allowAuction.value = config.allowAuctionWithoutConsent; + allowAuction.definedInConfig = true; } + // if true, then gdprApplies should be set to true + gdprScope = config.defaultGdprScope === true; + utils.logInfo('consentManagement module has been activated...'); if (userCMP === 'static') { diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index af9f4f05dbf..e4d5c12eb46 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -4,9 +4,9 @@ * information and make it available for any USP (CCPA) supported adapters to * read/pass this information to their system. */ -import * as utils from '../src/utils'; -import { config } from '../src/config'; -import { uspDataHandler } from '../src/adapterManager'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { uspDataHandler } from '../src/adapterManager.js'; const DEFAULT_CONSENT_API = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 50; @@ -14,20 +14,32 @@ const USPAPI_VERSION = 1; export let consentAPI; export let consentTimeout; +export let staticConsentData; let consentData; let addedConsentHook = false; // consent APIs const uspCallMap = { - 'iab': lookupUspConsent + 'iab': lookupUspConsent, + 'static': lookupStaticConsentData }; +/** + * This function reads the consent string from the config to obtain the consent information of the user. + * @param {function(string)} cmpSuccess acts as a success callback when the value is read from config; pass along consentObject (string) from CMP + * @param {function(string)} cmpError acts as an error callback while interacting with the config string; pass along an error message (string) + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + */ +function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { + cmpSuccess(staticConsentData, hookConfig); +} + /** * This function handles interacting with an USP compliant consent manager to obtain the consent information of the user. * Given the async nature of the USP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function(string)} uspSuccess acts as a success callback when USPAPI returns a value; pass along consentObject (string) from UPSAPI + * @param {function(string)} uspSuccess acts as a success callback when USPAPI returns a value; pass along consentObject (string) from USPAPI * @param {function(string)} uspError acts as an error callback while interacting with USPAPI; pass along an error message (string) * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ @@ -63,27 +75,26 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { // - use the USPAPI locator code to see if USP's located in the current window or an ancestor window. This works in friendly or cross domain iframes // - if USPAPI is not found, the iframe function will call the uspError exit callback to abort the rest of the USPAPI workflow // - try to call the __uspapi() function directly, otherwise use the postMessage() api - // find the CMP frame/window - let f = window; - let uspapiFrame; - while (!uspapiFrame) { - try { - if (f.frames['__uspapiLocator']) uspapiFrame = f; - } catch (e) { } - if (f === window.top) break; - f = f.parent; - } - - if (!uspapiFrame) { - return uspError('USP CMP not found.', hookConfig); - } try { // try to call __uspapi directly - uspapiFrame.__uspapi('getUSPData', USPAPI_VERSION, callbackHandler.consentDataCallback); + window.__uspapi('getUSPData', USPAPI_VERSION, callbackHandler.consentDataCallback); } catch (e) { // must not have been accessible, try using postMessage() api + let f = window; + let uspapiFrame; + while (!uspapiFrame) { + try { + if (f.frames['__uspapiLocator']) uspapiFrame = f; + } catch (e) { } + if (f === window.top) break; + f = f.parent; + } + + if (!uspapiFrame) { + return uspError('USP CMP not found.', hookConfig); + } callUspApiWhileInIframe('getUSPData', uspapiFrame, callbackHandler.consentDataCallback); } @@ -147,11 +158,6 @@ export function requestBidsHook(fn, reqBidsConfigObj) { timer: null }; - // in case we already have consent (eg during bid refresh) - if (consentData) { - return exitModule(null, hookConfig); - } - if (!uspCallMap[consentAPI]) { utils.logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); @@ -179,7 +185,7 @@ export function requestBidsHook(fn, reqBidsConfigObj) { function processUspData(consentObject, hookConfig) { const valid = !!(consentObject && consentObject.usPrivacy); if (!valid) { - uspapiFailed(`UPSAPI returned unexpected value during lookup process.`, hookConfig, consentObject); + uspapiFailed(`USPAPI returned unexpected value during lookup process.`, hookConfig, consentObject); return; } @@ -284,6 +290,14 @@ export function setConsentConfig(config) { utils.logInfo('USPAPI consentManagement module has been activated...'); + if (consentAPI === 'static') { + if (utils.isPlainObject(config.consentData) && utils.isPlainObject(config.consentData.getUSPData)) { + if (config.consentData.getUSPData.uspString) staticConsentData = { usPrivacy: config.consentData.getUSPData.uspString }; + consentTimeout = 0; + } else { + utils.logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); + } + } if (!addedConsentHook) { $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); } diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index bd24870c5e5..8eb56f7d0c2 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'consumable'; @@ -62,6 +62,10 @@ export const spec = { }; } + if (bidderRequest && bidderRequest.uspConsent) { + data.ccpa = bidderRequest.uspConsent; + } + validBidRequests.map(bid => { const sizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes || []; const placement = Object.assign({ @@ -133,7 +137,7 @@ export const spec = { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: '//sync.serverbid.com/ss/' + siteId + '.html' + url: 'https://sync.serverbid.com/ss/' + siteId + '.html' }]; } diff --git a/modules/convergeBidAdapter.js b/modules/convergeBidAdapter.js new file mode 100644 index 00000000000..bea3b6cb1ab --- /dev/null +++ b/modules/convergeBidAdapter.js @@ -0,0 +1,313 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'converge'; +const ENDPOINT_URL = 'https://tech.convergd.com/hb'; +const TIME_TO_LIVE = 360; +const SYNC_URL = 'https://tech.convergd.com/push_sync'; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +let hasSynced = false; + +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, + supportedMediaTypes: [ BANNER, VIDEO ], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!bid.params.uid; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @param {bidderRequest} bidderRequest - bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const auids = []; + const bidsMap = {}; + const slotsMapByUid = {}; + const sizeMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let pageKeywords; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + reqId = bid.bidderRequestId; + const {params: {uid}, adUnitCode} = bid; + auids.push(uid); + const sizesId = utils.parseSizesInput(bid.sizes); + + if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { + const keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + pageKeywords = keywords; + } + + if (!slotsMapByUid[uid]) { + slotsMapByUid[uid] = {}; + } + const slotsMap = slotsMapByUid[uid]; + if (!slotsMap[adUnitCode]) { + slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + } else { + slotsMap[adUnitCode].bids.push(bid); + } + const slot = slotsMap[adUnitCode]; + + sizesId.forEach((sizeId) => { + sizeMap[sizeId] = true; + if (!bidsMap[uid]) { + bidsMap[uid] = {}; + } + + if (!bidsMap[uid][sizeId]) { + bidsMap[uid][sizeId] = [slot]; + } else { + bidsMap[uid][sizeId].push(slot); + } + slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); + }); + }); + + const payload = { + pt: priceType, + auids: auids.join(','), + sizes: utils.getKeys(sizeMap).join(','), + r: reqId, + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$' + }; + + if (pageKeywords) { + payload.keywords = JSON.stringify(pageKeywords); + } + + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + payload.u = bidderRequest.refererInfo.referer; + } + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap: bidsMap, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequest + * @param {Renderer} RendererConst + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { + 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, RendererConst); + }); + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + if (!hasSynced && syncOptions.pixelEnabled) { + let params = ''; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent) { + params += `&us_privacy=${uspConsent}`; + } + + hasSynced = true; + return { + type: 'image', + url: SYNC_URL + params + }; + } + } +}; + +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +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, RendererConst) { + 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) { + const sizeId = `${serverBid.w}x${serverBid.h}`; + if (awaitingBids[sizeId]) { + const slot = awaitingBids[sizeId][0]; + + const bid = slot.bids.shift(); + 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: 'EUR', + netRevenue: priceType !== 'gross', + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid + }; + if (serverBid.content_type === 'video' || (!serverBid.content_type && bid.mediaTypes && bid.mediaTypes.video)) { + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = VIDEO; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL + }, RendererConst); + } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; + } + + bidResponses.push(bidResponse); + + if (!slot.bids.length) { + slot.parents.forEach(({parent, key, uid}) => { + const index = parent[key].indexOf(slot); + if (index > -1) { + parent[key].splice(index, 1); + } + if (!parent[key].length) { + delete parent[key]; + if (!utils.getKeys(parent).length) { + delete bidsMap[uid]; + } + } + }); + } + } + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } + if (errorMessage) { + utils.logError(errorMessage); + } +} + +function outstreamRender (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +function createRenderer (bid, rendererParams, RendererConst) { + const rendererInst = RendererConst.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false + }); + + try { + rendererInst.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return rendererInst; +} + +export function resetUserSync() { + hasSynced = false; +} + +export function getSyncUrl() { + return SYNC_URL; +} + +registerBidder(spec); diff --git a/modules/convergeBidAdapter.md b/modules/convergeBidAdapter.md new file mode 100644 index 00000000000..ab916a8b3b6 --- /dev/null +++ b/modules/convergeBidAdapter.md @@ -0,0 +1,57 @@ +# Overview + +Module Name: Converge Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@converge-digital.com + +# Description + +Module that connects to Converge demand source to fetch bids. +Converge Bid Adapter supports Banner and Video (instream and outstream). + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "converge", + params: { + uid: '59', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "converge", + params: { + uid: 1, + priceType: 'gross', + keywords: { + brandsafety: ['disaster'], + topic: ['stress', 'fear'] + } + } + } + ] + },{ + code: 'test-div', + sizes: [[640, 360]], + mediaTypes: { video: {} }, + bids: [ + { + bidder: "converge", + params: { + uid: 60 + } + } + ] + } + ]; +``` diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index bb11b1f87cb..3b3d04dc498 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,12 +1,17 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const GVLID = 24; +export const storage = getStorageManager(GVLID); const BIDDER_CODE = 'conversant'; -const URL = 'https://web.hb.ad.cpe.dotomi.com/s2s/header/24'; +const URL = 'https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: ['cnvr'], // short code supportedMediaTypes: [BANNER, VIDEO], @@ -53,6 +58,7 @@ export const spec = { let requestId = ''; let pubcid = null; let pubcidName = '_pubcid'; + let bidurl = URL; const conversantImps = validBidRequests.map(function(bid) { const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); @@ -104,6 +110,9 @@ export const spec = { } else if (bid.crumbs && bid.crumbs.pubcid) { pubcid = bid.crumbs.pubcid; } + if (bid.params.white_label_url) { + bidurl = bid.params.white_label_url; + } return imp; }); @@ -159,7 +168,7 @@ export const spec = { return { method: 'POST', - url: URL, + url: bidurl, data: payload, }; }, @@ -314,36 +323,24 @@ function copyOptProperty(src, dst, dstName) { function collectEids(bidRequests) { const request = bidRequests[0]; // bidRequests have the same userId object const eids = []; - - addEid(eids, request, 'userId.tdid', 'adserver.org'); - addEid(eids, request, 'userId.idl_env', 'liveramp.com'); - addEid(eids, request, 'userId.criteoId', 'criteo.com'); - addEid(eids, request, 'userId.id5id', 'id5-sync.com'); - addEid(eids, request, 'userId.parrableid', 'parrable.com'); - addEid(eids, request, 'userId.digitrustid.data.id', 'digitru.st'); - addEid(eids, request, 'userId.lipb.lipbid', 'liveintent.com'); - - return eids; -} - -/** - * Extract and push a single extended id into eids array - * @param eids Array of extended IDs - * @param idObj Object containing IDs - * @param keyPath Nested properties expressed as a path - * @param source Source for the ID - */ -function addEid(eids, idObj, keyPath, source) { - const id = utils.deepAccess(idObj, keyPath); - if (id) { - eids.push({ - source: source, - uids: [{ - id: id, - atype: 1 - }] + if (utils.isArray(request.userIdAsEids) && request.userIdAsEids.length > 0) { + // later following white-list can be converted to block-list if needed + const requiredSourceValues = { + 'adserver.org': 1, + 'liveramp.com': 1, + 'criteo.com': 1, + 'id5-sync.com': 1, + 'parrable.com': 1, + 'digitru.st': 1, + 'liveintent.com': 1 + }; + request.userIdAsEids.forEach(function(eid) { + if (requiredSourceValues.hasOwnProperty(eid.source)) { + eids.push(eid); + } }); } + return eids; } /** @@ -355,13 +352,13 @@ function readStoredValue(key) { let storedValue; try { // check cookies first - storedValue = utils.getCookie(key); + storedValue = storage.getCookie(key); if (!storedValue) { // check expiration time before reading local storage - const storedValueExp = utils.getDataFromLocalStorage(`${key}_exp`); + const storedValueExp = storage.getDataFromLocalStorage(`${key}_exp`); if (storedValueExp === '' || (storedValueExp && (new Date(storedValueExp)).getTime() - Date.now() > 0)) { - storedValue = utils.getDataFromLocalStorage(key); + storedValue = storage.getDataFromLocalStorage(key); storedValue = storedValue ? decodeURIComponent(storedValue) : storedValue; } } diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md index 5aba5653043..fba793adad2 100644 --- a/modules/conversantBidAdapter.md +++ b/modules/conversantBidAdapter.md @@ -38,6 +38,7 @@ var adUnits = [ site_id: '108060', api: [2], protocols: [1, 2], + white_label_url: 'https://web.hb.ad.cpe.dotomi.com/s2s/header/24', mimes: ['video/mp4'] } }] diff --git a/modules/cosmosBidAdapter.js b/modules/cosmosBidAdapter.js index f3a8979165b..73ee5c223b3 100644 --- a/modules/cosmosBidAdapter.js +++ b/modules/cosmosBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; -import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; const BIDDER_CODE = 'cosmos'; const BID_ENDPOINT = 'https://bid.cosmoshq.com/openrtb2/bids'; diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js old mode 100644 new mode 100755 index 8d2ec31a2c6..b416c00c2d0 --- a/modules/cpmstarBidAdapter.js +++ b/modules/cpmstarBidAdapter.js @@ -1,7 +1,8 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import {VIDEO, BANNER} from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'cpmstar'; @@ -17,12 +18,12 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], pageID: Math.floor(Math.random() * 10e6), - getMediaType: function(bidRequest) { + getMediaType: function (bidRequest) { if (bidRequest == null) return BANNER; return !utils.deepAccess(bidRequest, 'mediaTypes.video') ? BANNER : VIDEO; }, - getPlayerSize: function(bidRequest) { + getPlayerSize: function (bidRequest) { var playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); if (playerSize == null) return [640, 440]; if (playerSize[0] != null) playerSize = playerSize[0]; @@ -46,9 +47,33 @@ export const spec = { var mediaType = spec.getMediaType(bidRequest); var playerSize = spec.getPlayerSize(bidRequest); var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : ''); + + var url = ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + + '&json=c_b&mv=1&poolid=' + utils.getBidIdParameter('placementId', bidRequest.params) + + '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + + '&requestid=' + bidRequest.bidId + + '&referer=' + encodeURIComponent(referer); + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString != null) { + url += '&gdpr_consent=' + bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies != null) { + url += '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + } + } + + if (bidderRequest.uspConsent != null) { + url += '&us_privacy=' + bidderRequest.uspConsent; + } + + if (config.getConfig('coppa')) { + url += '&tfcd=' + (config.getConfig('coppa') ? 1 : 0); + } + requests.push({ method: 'GET', - url: ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + '&json=c_b&mv=1&poolid=' + utils.getBidIdParameter('placementId', bidRequest.params) + '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + '&requestid=' + bidRequest.bidId + '&referer=' + referer, + url: url, bidRequest: bidRequest, }); } diff --git a/modules/cpmstarBidAdapter.md b/modules/cpmstarBidAdapter.md old mode 100644 new mode 100755 index 7dab435b0f0..c227f19bfaf --- a/modules/cpmstarBidAdapter.md +++ b/modules/cpmstarBidAdapter.md @@ -4,6 +4,9 @@ Module Name: Cpmstar Bidder Adapter Module Type: Bidder Adapter Maintainer: josh@cpmstar.com +gdpr_supported: true +usp_supported: true +coppa_supported: true ``` # Description diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js new file mode 100644 index 00000000000..3838f5dee59 --- /dev/null +++ b/modules/craftBidAdapter.js @@ -0,0 +1,238 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { auctionManager } from '../src/auctionManager.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const BIDDER_CODE = 'craft'; +const URL = 'https://gacraft.jp/prebid-v3'; +const TTL = 360; +const storage = getStorageManager(); + +export const spec = { + code: BIDDER_CODE, + aliases: ['craft'], + supportedMediaTypes: [BANNER], + + isBidRequestValid: function(bid) { + return !!bid.params.sitekey && !!bid.params.placementId && !isAmp(); + }, + + buildRequests: function(bidRequests, bidderRequest) { + const tags = bidRequests.map(bidToTag); + const schain = bidRequests[0].schain; + const payload = { + tags: [...tags], + ua: navigator.userAgent, + sdk: { + version: '$prebid.version$' + }, + schain: schain + }; + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: bidderRequest.refererInfo.referer, + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + }; + if (bidderRequest.refererInfo.stack) { + refererinfo.rd_stk = bidderRequest.refererInfo.stack.join(','); + } + payload.referrer_detection = refererinfo; + } + const request = formatRequest(payload, bidderRequest); + return request; + }, + + interpretResponse: function(serverResponse, {bidderRequest}) { + try { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse) { + return []; + } + if (serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse.error) { + errorMessage += `: ${serverResponse.error}`; + } + utils.logError(errorMessage); + return bids; + } + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } + } + }); + } + return bids; + } catch (e) { + return []; + } + }, + + transformBidParams: function(params, isOpenRtb) { + params = utils.convertTypes({ + 'sitekey': 'string', + 'placementId': 'string', + 'keywords': utils.transformBidderParamKeywords + }, params); + if (isOpenRtb) { + if (isPopulatedArray(params.keywords)) { + params.keywords.forEach(deleteValues); + } + Object.keys(params).forEach(paramKey => { + let convertedKey = utils.convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + } + return params; + }, + + onBidWon: function(bid) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', bid._prebidWon); + xhr.send(); + } +}; + +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function hasPurpose1Consent(bidderRequest) { + let result = true; + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { + result = !!(utils.deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); + } + } + return result; +} + +function formatRequest(payload, bidderRequest) { + let options = {}; + if (!hasPurpose1Consent(bidderRequest)) { + options = { + withCredentials: false + }; + } + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: URL, + data: payloadString, + bidderRequest, + options + }; +} + +function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm, + currency: 'JPY', + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + ad: rtbBid.rtb.banner.content, + ttl: TTL, + creativeId: rtbBid.creative_id, + netRevenue: false, // ??? + dealId: rtbBid.deal_id, + meta: null, + _adUnitCode: bidRequest.adUnitCode, + _bidKey: serverBid.bid_key, + _prebidWon: serverBid.won_url, + }; + return bid; +} + +function bidToTag(bid) { + const tag = {}; + for (var k in bid.params) { + tag[k] = bid.params[k]; + } + try { + if (storage.hasLocalStorage()) { + tag.uid = JSON.parse(storage.getDataFromLocalStorage(`${bid.params.sitekey}_uid`)); + } + } catch (e) { + } + tag.sizes = bid.sizes; + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (!utils.isEmpty(bid.params.keywords)) { + let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); + if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + tag.ad_types.push(BANNER); + } + + if (tag.ad_types.length === 0) { + delete tag.ad_types; + } + + return tag; +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else if (adType === NATIVE) { + return NATIVE; + } else { + return BANNER; + } +} + +function isAmp() { + try { + const ampContext = window.context || window.parent.context; + if (ampContext && ampContext.pageViewId) { + return ampContext; + } + return false; + } catch (e) { + return false; + } +} + +registerBidder(spec); diff --git a/modules/craftBidAdapter.md b/modules/craftBidAdapter.md new file mode 100644 index 00000000000..d1a8daeab73 --- /dev/null +++ b/modules/craftBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Craft Bid Adapter +Module Type: Bidder Adapter +Maintainer: system@gacraft.jp +``` + +# Description + +Connects to craft exchange for bids. + +Craft bid adapter supports Banner. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: '/21998384947/prebid-example', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'craft', + params: { + sitekey: 'craft-prebid-example', + placementId: '1234abcd' + } + }] + } +]; +``` diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 0007cc6a09c..3dabe911884 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,21 +1,23 @@ -import { loadExternalScript } from '../src/adloader'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { config } from '../src/config'; -import { BANNER, VIDEO } from '../src/mediaTypes'; -import { parse } from '../src/url'; -import * as utils from '../src/utils'; -import find from 'core-js/library/fn/array/find'; -import { verify } from 'criteo-direct-rsa-validate/build/verify'; - -export const ADAPTER_VERSION = 25; +import {loadExternalScript} from '../src/adloader.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; +import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const GVLID = 91; +export const ADAPTER_VERSION = 32; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; -const CRITEO_VENDOR_ID = 91; const PROFILE_ID_INLINE = 207; export const PROFILE_ID_PUBLISHERTAG = 185; +const storage = getStorageManager(GVLID); +const LOG_PREFIX = 'Criteo: '; // Unminified source code can be found in: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js -const PUBLISHER_TAG_URL = '//static.criteo.net/js/ld/publishertag.prebid.js'; +const PUBLISHER_TAG_URL = 'https://static.criteo.net/js/ld/publishertag.prebid.js'; const FAST_BID_PUBKEY_E = 65537; const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDeaWBMxHBUT55CYyboR/EZ4efghPi3CoNGfGWezpjko9P6p2EwGArtHEeS4slhu/SpSIFMjG6fdrpRoNuIAMhq1Z+Pr/+HOd1pThFKeGFr2/NhtAg+TXAzaU='; @@ -23,7 +25,8 @@ const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDe /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [ BANNER, VIDEO ], + gvlid: GVLID, + supportedMediaTypes: [ BANNER, VIDEO, NATIVE ], /** * @param {object} bid @@ -54,7 +57,11 @@ export const spec = { let url; let data; - Object.assign(bidderRequest, { ceh: config.getConfig('criteo.ceh') }); + Object.assign(bidderRequest, { + publisherExt: config.getConfig('fpd.context'), + userExt: config.getConfig('fpd.user'), + ceh: config.getConfig('criteo.ceh') + }); // If publisher tag not already loaded try to get it from fast bid if (!publisherTagAvailable()) { @@ -70,7 +77,12 @@ export const spec = { } if (publisherTagAvailable()) { + // eslint-disable-next-line no-undef const adapter = new Criteo.PubTag.Adapters.Prebid(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$'); + const enableSendAllBids = config.getConfig('enableSendAllBids'); + if (adapter.setEnableSendAllBids && typeof adapter.setEnableSendAllBids === 'function' && typeof enableSendAllBids === 'boolean') { + adapter.setEnableSendAllBids(enableSendAllBids); + } url = adapter.buildCdbUrl(); data = adapter.buildCdbRequest(); } else { @@ -93,6 +105,7 @@ export const spec = { const body = response.body || response; if (publisherTagAvailable()) { + // eslint-disable-next-line no-undef const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(request); if (adapter) { return adapter.interpretResponse(body, request); @@ -118,7 +131,14 @@ export const spec = { dealId: slot.dealCode, }; if (slot.native) { - bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); + if (bidRequest.params.nativeCallback) { + bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); + } else if (config.getConfig('enableSendAllBids') === true) { + return; + } else { + bid.native = createPrebidNativeAd(slot.native); + bid.mediaType = NATIVE; + } } else if (slot.video) { bid.vastUrl = slot.displayurl; bid.mediaType = VIDEO; @@ -136,9 +156,16 @@ export const spec = { * @param {TimedOutBid} timeoutData */ onTimeout: (timeoutData) => { - if (publisherTagAvailable()) { - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(timeoutData.auctionId); - adapter.handleBidTimeout(); + if (publisherTagAvailable() && Array.isArray(timeoutData)) { + var auctionsIds = []; + timeoutData.forEach((bid) => { + if (auctionsIds.indexOf(bid.auctionId) === -1) { + auctionsIds.push(bid.auctionId); + // eslint-disable-next-line no-undef + const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); + adapter.handleBidTimeout(); + } + }); } }, @@ -146,7 +173,8 @@ export const spec = { * @param {Bid} bid */ onBidWon: (bid) => { - if (publisherTagAvailable()) { + if (publisherTagAvailable() && bid) { + // eslint-disable-next-line no-undef const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); adapter.handleBidWon(bid); } @@ -157,6 +185,7 @@ export const spec = { */ onSetTargeting: (bid) => { if (publisherTagAvailable()) { + // eslint-disable-next-line no-undef const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); adapter.handleSetTargeting(bid); } @@ -167,20 +196,20 @@ export const spec = { * @return {boolean} */ function publisherTagAvailable() { + // eslint-disable-next-line no-undef return typeof Criteo !== 'undefined' && Criteo.PubTag && Criteo.PubTag.Adapters && Criteo.PubTag.Adapters.Prebid; } /** * @param {BidRequest[]} bidRequests * @param bidderRequest - * @return {CriteoContext} */ function buildContext(bidRequests, bidderRequest) { let referrer = ''; if (bidderRequest && bidderRequest.refererInfo) { referrer = bidderRequest.refererInfo.referer; } - const queryString = parse(referrer).search; + const queryString = utils.parseUrl(referrer).search; const context = { url: referrer, @@ -222,9 +251,20 @@ function buildCdbUrl(context) { return url; } +function checkNativeSendId(bidRequest) { + return !(bidRequest.nativeParams && + ((bidRequest.nativeParams.image && bidRequest.nativeParams.image.sendId !== true) || + (bidRequest.nativeParams.icon && bidRequest.nativeParams.icon.sendId !== true) || + (bidRequest.nativeParams.clickUrl && bidRequest.nativeParams.clickUrl.sendId !== true) || + (bidRequest.nativeParams.displayUrl && bidRequest.nativeParams.displayUrl.sendId !== true) || + (bidRequest.nativeParams.privacyLink && bidRequest.nativeParams.privacyLink.sendId !== true) || + (bidRequest.nativeParams.privacyIcon && bidRequest.nativeParams.privacyIcon.sendId !== true))); +} + /** * @param {CriteoContext} context * @param {BidRequest[]} bidRequests + * @param bidderRequest * @return {*} */ function buildCdbRequest(context, bidRequests, bidderRequest) { @@ -232,6 +272,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { const request = { publisher: { url: context.url, + ext: bidderRequest.publisherExt }, slots: bidRequests.map(bidRequest => { networkId = bidRequest.params.networkId || networkId; @@ -239,25 +280,36 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { impid: bidRequest.adUnitCode, transactionid: bidRequest.transactionId, auctionId: bidRequest.auctionId, - sizes: getBannerSizes(bidRequest), }; if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; } + if (bidRequest.fpd && bidRequest.fpd.context) { + slot.ext = bidRequest.fpd.context; + } + if (bidRequest.params.ext) { + slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); + } if (bidRequest.params.publisherSubId) { slot.publishersubid = bidRequest.params.publisherSubId; } - if (bidRequest.params.nativeCallback) { + if (bidRequest.params.nativeCallback || utils.deepAccess(bidRequest, `mediaTypes.${NATIVE}`)) { slot.native = true; + if (!checkNativeSendId(bidRequest)) { + utils.logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); + } + slot.sizes = parseSizes(retrieveBannerSizes(bidRequest), parseNativeSize); + } else { + slot.sizes = parseSizes(retrieveBannerSizes(bidRequest), parseSize); } if (hasVideoMediaType(bidRequest)) { const video = { - playersizes: getVideoSizes(bidRequest), + playersizes: parseSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), mimes: bidRequest.mediaTypes.video.mimes, protocols: bidRequest.mediaTypes.video.protocols, maxduration: bidRequest.mediaTypes.video.maxduration, api: bidRequest.mediaTypes.video.api - } + }; video.skip = bidRequest.params.video.skip; video.placement = bidRequest.params.video.placement; @@ -273,7 +325,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (networkId) { request.publisher.networkid = networkId; } - request.user = {}; + request.user = { + ext: bidderRequest.userExt + }; if (bidderRequest && bidderRequest.ceh) { request.user.ceh = bidderRequest.ceh; } @@ -282,10 +336,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { request.gdprConsent.gdprApplies = !!(bidderRequest.gdprConsent.gdprApplies); } - if (bidderRequest.gdprConsent.vendorData && bidderRequest.gdprConsent.vendorData.vendorConsents && - typeof bidderRequest.gdprConsent.vendorData.vendorConsents[ CRITEO_VENDOR_ID.toString(10) ] !== 'undefined') { - request.gdprConsent.consentGiven = !!(bidderRequest.gdprConsent.vendorData.vendorConsents[ CRITEO_VENDOR_ID.toString(10) ]); - } + request.gdprConsent.version = bidderRequest.gdprConsent.apiVersion; if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { request.gdprConsent.consentData = bidderRequest.gdprConsent.consentString; } @@ -296,24 +347,26 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { return request; } -function getVideoSizes(bidRequest) { - return parseSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize')); +function retrieveBannerSizes(bidRequest) { + return utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; } -function getBannerSizes(bidRequest) { - return parseSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes); +function parseSizes(sizes, parser) { + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parser(size)); + } + return [parser(sizes)]; // or a single one ? (ie. [728,90]) } function parseSize(size) { return size[0] + 'x' + size[1]; } -function parseSizes(sizes) { - if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map(size => parseSize(size)); +function parseNativeSize(size) { + if (size[0] === undefined && size[1] === undefined) { + return '2x2'; } - - return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) + return size[0] + 'x' + size[1]; } function hasVideoMediaType(bidRequest) { @@ -356,6 +409,27 @@ function hasValidVideoMediaType(bidRequest) { return false; } +/** + * Create prebid compatible native ad with native payload + * @param {*} payload + * @returns prebid native ad assets + */ +function createPrebidNativeAd(payload) { + return { + title: payload.products[0].title, + body: payload.products[0].description, + sponsoredBy: payload.advertiser.description, + icon: payload.advertiser.logo, + image: payload.products[0].image, + clickUrl: payload.products[0].click_url, + privacyLink: payload.privacy.optout_click_url, + privacyIcon: payload.privacy.optout_image_url, + cta: payload.products[0].call_to_action, + price: payload.products[0].price, + impressionTrackers: payload.impression_pixels.map(pix => pix.url) + }; +} + /** * @param {string} id * @param {*} payload @@ -386,7 +460,7 @@ export function tryGetCriteoFastBid() { try { const fastBidStorageKey = 'criteo_fast_bid'; const hashPrefix = '// Hash: '; - const fastBidFromStorage = localStorage.getItem(fastBidStorageKey); + const fastBidFromStorage = storage.getDataFromLocalStorage(fastBidStorageKey); if (fastBidFromStorage !== null) { // The value stored must contain the file's encrypted hash as first line @@ -395,7 +469,7 @@ export function tryGetCriteoFastBid() { if (firstLine.substr(0, hashPrefix.length) !== hashPrefix) { utils.logWarn('No hash found in FastBid'); - localStorage.removeItem(fastBidStorageKey); + storage.removeDataFromLocalStorage(fastBidStorageKey); } else { // Remove the hash part from the locally stored value const publisherTagHash = firstLine.substr(hashPrefix.length); @@ -403,13 +477,10 @@ export function tryGetCriteoFastBid() { if (verify(publisherTag, publisherTagHash, FAST_BID_PUBKEY_N, FAST_BID_PUBKEY_E)) { utils.logInfo('Using Criteo FastBid'); - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.text = publisherTag; - utils.insertElement(script); + eval(publisherTag); // eslint-disable-line no-eval } else { utils.logWarn('Invalid Criteo FastBid found'); - localStorage.removeItem(fastBidStorageKey); + storage.removeDataFromLocalStorage(fastBidStorageKey); } } } diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index fe89de5d341..c44f0c843ae 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -5,11 +5,13 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils' -import * as ajax from '../src/ajax' -import * as urlLib from '../src/url' -import { getRefererInfo } from '../src/refererDetection' -import { submodule } from '../src/hook'; +import * as utils from '../src/utils.js' +import * as ajax from '../src/ajax.js' +import { getRefererInfo } from '../src/refererDetection.js' +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; @@ -20,33 +22,33 @@ const pastDateString = new Date(0).toString(); const expirationString = new Date(utils.timestamp() + cookiesMaxAge).toString(); function areCookiesWriteable() { - utils.setCookie(cookieWriteableKey, '1'); - const canWrite = utils.getCookie(cookieWriteableKey) === '1'; - utils.setCookie(cookieWriteableKey, '', pastDateString); + storage.setCookie(cookieWriteableKey, '1'); + const canWrite = storage.getCookie(cookieWriteableKey) === '1'; + storage.setCookie(cookieWriteableKey, '', pastDateString); return canWrite; } function extractProtocolHost (url, returnOnlyHost = false) { - const parsedUrl = urlLib.parse(url) + const parsedUrl = utils.parseUrl(url) return returnOnlyHost ? `${parsedUrl.hostname}` : `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`; } function getFromAllStorages(key) { - return utils.getCookie(key) || utils.getDataFromLocalStorage(key); + return storage.getCookie(key) || storage.getDataFromLocalStorage(key); } function saveOnAllStorages(key, value) { if (key && value) { - utils.setCookie(key, value, expirationString); - utils.setDataInLocalStorage(key, value); + storage.setCookie(key, value, expirationString); + storage.setDataInLocalStorage(key, value); } } function deleteFromAllStorages(key) { - utils.setCookie(key, '', pastDateString); - utils.removeDataFromLocalStorage(key); + storage.setCookie(key, '', pastDateString); + storage.removeDataFromLocalStorage(key); } function getCriteoDataFromAllStorages() { @@ -56,18 +58,19 @@ function getCriteoDataFromAllStorages() { } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isPublishertagPresent) { +function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isPublishertagPresent, gdprString) { const url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + + `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` return url; } -function callCriteoUserSync(parsedCriteoData) { +function callCriteoUserSync(parsedCriteoData, gdprString) { const cw = areCookiesWriteable(); const topUrl = extractProtocolHost(getRefererInfo().referer); const domain = extractProtocolHost(document.location.href, true); @@ -78,7 +81,8 @@ function callCriteoUserSync(parsedCriteoData) { domain, parsedCriteoData.bundle, cw, - isPublishertagPresent + isPublishertagPresent, + gdprString ); ajax.ajaxBuilder()( @@ -119,11 +123,16 @@ export const criteoIdSubmodule = { /** * get the Criteo Id from local storages and initiate a new user sync * @function + * @param {SubmoduleParams} [configParams] + * @param {ConsentData} [consentData] * @returns {{id: {criteoId: string} | undefined}}} */ - getId() { + getId(configParams, consentData) { + const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; + const gdprConsentString = hasGdprData ? consentData.consentString : undefined; + let localData = getCriteoDataFromAllStorages(); - callCriteoUserSync(localData); + callCriteoUserSync(localData, gdprConsentString); return { id: localData.bidId ? { criteoId: localData.bidId } : undefined } } diff --git a/modules/criteortusIdSystem.js b/modules/criteortusIdSystem.js deleted file mode 100644 index 8486bfae9f3..00000000000 --- a/modules/criteortusIdSystem.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * This module adds Criteo Real Time User Sync to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/criteortusIdSystem - * @requires module:modules/userId - */ - -import * as utils from '../src/utils' -import { ajax } from '../src/ajax'; -import { submodule } from '../src/hook'; - -const key = '__pbjs_criteo_rtus'; - -/** @type {Submodule} */ -export const criteortusIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: 'criteortus', - /** - * decode the stored id value for passing to bid requests - * @function - * @returns {{criteortus:Object}} - */ - decode() { - let uid = utils.getCookie(key); - try { - uid = JSON.parse(uid); - return { 'criteortus': uid }; - } catch (error) { - utils.logError('Error in parsing criteo rtus data', error); - } - }, - /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleParams} [configParams] - * @returns {IdResponse|undefined} - */ - getId(configParams) { - if (!configParams || !utils.isPlainObject(configParams.clientIdentifier)) { - utils.logError('User ID - Criteo rtus requires client identifier to be defined'); - return; - } - - let uid = utils.getCookie(key); - if (uid) { - return {id: uid}; - } else { - let userIds = {}; - const resp = function(callback) { - let bidders = Object.keys(configParams.clientIdentifier); - - function afterAllResponses() { - // criteo rtus user id expires in 1 hour - const expiresStr = (new Date(Date.now() + (60 * 60 * 1000))).toUTCString(); - utils.setCookie(key, JSON.stringify(userIds), expiresStr); - callback(userIds); - } - - const onResponse = utils.delayExecution(afterAllResponses, bidders.length); - - bidders.forEach((bidder) => { - let url = `https://gum.criteo.com/sync?c=${configParams.clientIdentifier[bidder]}&r=3`; - const getSuccessHandler = (bidder) => { - return function onSuccess(response) { - if (response) { - try { - response = JSON.parse(response); - userIds[bidder] = response; - onResponse(); - } catch (error) { - utils.logError(error); - } - } - } - } - - const getFailureHandler = (bidder) => { - return function onFailure(error) { - utils.logError(`Criteo RTUS server call failed for ${bidder}`, error); - onResponse(); - } - } - - ajax( - url, - { - success: getSuccessHandler(bidder), - error: getFailureHandler(bidder) - }, - undefined, - Object.assign({ - method: 'GET', - withCredentials: true - }) - ); - }) - }; - return {callback: resp}; - } - } -}; - -submodule('userId', criteortusIdSubmodule); diff --git a/modules/currency.js b/modules/currency.js index 28e033fb1a2..0464d9b5cdb 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -1,8 +1,9 @@ -import { createBid } from '../src/bidfactory'; -import { STATUS } from '../src/constants'; -import { ajax } from '../src/ajax'; -import * as utils from '../src/utils'; -import { config } from '../src/config'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { createBid } from '../src/bidfactory.js'; +import { STATUS } from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$'; @@ -122,6 +123,8 @@ function initCurrency(url) { utils.logInfo('Installing addBidResponse decorator for currency module', arguments); + // Adding conversion function to prebid global for external module and on page use + getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); getHook('addBidResponse').before(addBidResponseHook, 100); // call for the file if we haven't already @@ -149,6 +152,7 @@ function resetCurrency() { utils.logInfo('Uninstalling addBidResponse decorator for currency module', arguments); getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + delete getGlobal().convertCurrency; adServerCurrency = 'USD'; conversionCache = {}; diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js index bcad7679c4f..1018417300a 100644 --- a/modules/dailyhuntBidAdapter.js +++ b/modules/dailyhuntBidAdapter.js @@ -1,55 +1,337 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import * as mediaTypes from '../src/mediaTypes'; -import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as mediaTypes from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import find from 'core-js-pure/features/array/find.js'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; const BIDDER_CODE = 'dailyhunt'; const BIDDER_ALIAS = 'dh'; -const SUPPORTED_MEDIA_TYPES = [mediaTypes.BANNER, mediaTypes.NATIVE]; +const SUPPORTED_MEDIA_TYPES = [mediaTypes.BANNER, mediaTypes.NATIVE, mediaTypes.VIDEO]; -const PROD_PREBID_ENDPOINT_URL = 'https://money.dailyhunt.in/openrtb2/auction'; +const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner='; +const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner='; -const PROD_ENDPOINT_URL = 'https://money.dailyhunt.in/openx/ads/index.php'; +const ORTB_NATIVE_TYPE_MAPPING = { + img: { + '3': 'image', + '1': 'icon' + }, + data: { + '1': 'sponsoredBy', + '2': 'body', + '3': 'rating', + '4': 'likes', + '5': 'downloads', + '6': 'price', + '7': 'salePrice', + '8': 'phone', + '9': 'address', + '10': 'body2', + '11': 'displayUrl', + '12': 'cta' + } +} + +const ORTB_NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + icon: { + id: 1, + type: 1, + name: 'img' + }, + image: { + id: 2, + type: 3, + name: 'img' + }, + sponsoredBy: { + id: 3, + name: 'data', + type: 1 + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + cta: { + id: 5, + type: 12, + name: 'data' + }, + body2: { + id: 4, + name: 'data', + type: 10 + }, +}; -function buildParams(bid) { - let params = { ...bid.params }; - params.pagetype = 'sources'; - params.placementId = 12345; - params.env = 'prod'; - if (params.testmode && params.testmode === true) { - params.customEvent = 'pb-testmode'; +// Encode URI. +const _encodeURIComponent = function (a) { + let b = window.encodeURIComponent(a); + b = b.replace(/'/g, '%27'); + return b; +} + +// Extract key from collections. +const extractKeyInfo = (collection, key) => { + for (let i = 0, result; i < collection.length; i++) { + result = utils.deepAccess(collection[i].params, key); + if (result) { + return result; + } } - let hasWeb5Size = false; - let hasWeb3Size = false; - bid && bid.sizes && bid.sizes.forEach((size, i) => { - if (!hasWeb3Size && size[0] == 300 && size[1] == 250) { - hasWeb3Size = true; + return undefined +} + +// Flattern Array. +const flatten = (arr) => { + return [].concat(...arr); +} + +const createOrtbRequest = (validBidRequests, bidderRequest) => { + let device = createOrtbDeviceObj(validBidRequests); + let user = createOrtbUserObj(validBidRequests) + let site = createOrtbSiteObj(validBidRequests, bidderRequest.refererInfo.referer) + return { + id: bidderRequest.auctionId, + imp: [], + site, + device, + user, + }; +} + +const createOrtbDeviceObj = (validBidRequests) => { + let device = { ...extractKeyInfo(validBidRequests, `device`) }; + device.ua = navigator.userAgent; + return device; +} + +const createOrtbUserObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `user`) }) + +const createOrtbSiteObj = (validBidRequests, page) => { + let site = { ...extractKeyInfo(validBidRequests, `site`), page }; + let publisher = createOrtbPublisherObj(validBidRequests); + if (publisher) { + site.publisher = publisher + } + return site +} + +const createOrtbPublisherObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `publisher`) }) + +const createOrtbImpObj = (bid) => { + let params = bid.params + let testMode = !!bid.params.test_mode + + // Validate Banner Request. + let bannerObj = utils.deepAccess(bid.mediaTypes, `banner`); + let nativeObj = utils.deepAccess(bid.mediaTypes, `native`); + let videoObj = utils.deepAccess(bid.mediaTypes, `video`); + + let imp = { + id: bid.bidId, + bidfloor: params.bidfloor ? params.bidfloor : 0, + ext: { + dailyhunt: { + placement_id: params.placement_id, + publisher_id: params.publisher_id, + partner: params.partner_name + } } - if (!hasWeb5Size && (size[0] == 300 || size[0] == 320) && size[1] == 50) { - hasWeb5Size = true; + }; + + // Test Mode Campaign. + if (testMode) { + imp.ext.test_mode = testMode; + } + + if (bannerObj) { + imp.banner = { + ...createOrtbImpBannerObj(bid, bannerObj) + } + } else if (nativeObj) { + imp.native = { + ...createOrtbImpNativeObj(bid, nativeObj) + } + } else if (videoObj) { + imp.video = { + ...createOrtbImpVideoObj(bid, videoObj) + } + } + return imp; +} + +const createOrtbImpBannerObj = (bid, bannerObj) => { + let format = []; + bannerObj.sizes.forEach(size => format.push({ w: size[0], h: size[1] })) + + return { + id: 'banner-' + bid.bidId, + format + } +} + +const createOrtbImpNativeObj = (bid, nativeObj) => { + const assets = utils._map(bid.nativeParams, (bidParams, key) => { + const props = ORTB_NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + let h = 0; + let w = 0; + + asset.id = props.id; + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len ? bidParams.len : 20, + type: props.type, + w, + h + }; + + return asset; + } + }).filter(Boolean); + let request = { + assets, + ver: '1,0' + } + return { request: JSON.stringify(request) }; +} + +const createOrtbImpVideoObj = (bid, videoObj) => { + let obj = {}; + let params = bid.params + if (!utils.isEmpty(bid.params.video)) { + obj = { + ...params.video, } - }) - params.zone = 'web'; - if (!hasWeb3Size && hasWeb5Size) { - params.subSlots = 'web-5'; } else { - params.subSlots = 'web-3'; + obj = { + mimes: ['video/mp4'], + }; } - if (bid.nativeParams) { - params.subSlots = 'web-3'; - params.ad_type = '2,3'; + obj.ext = { + ...videoObj, } - if (!params.partnerId) { - params.partnerId = 'unknown-pb-partner'; + return obj; +} + +const createServerRequest = (ortbRequest, validBidRequests, isTestMode = 'false') => ({ + method: 'POST', + url: isTestMode === 'true' ? PROD_PREBID_TEST_ENDPOINT_URL + validBidRequests[0].params.partner_name : PROD_PREBID_ENDPOINT_URL + validBidRequests[0].params.partner_name, + data: JSON.stringify(ortbRequest), + options: { + contentType: 'application/json', + withCredentials: true + }, + bids: validBidRequests +}) + +const createPrebidBannerBid = (bid, bidResponse) => ({ + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + width: bidResponse.w, + height: bidResponse.h, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: 'USD', + ad: bidResponse.adm, + mediaType: 'banner', + winUrl: bidResponse.nurl +}) + +const createPrebidNativeBid = (bid, bidResponse) => ({ + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + currency: 'USD', + ttl: 360, + netRevenue: bid.netRevenue === 'net', + native: parseNative(bidResponse), + mediaType: 'native', + winUrl: bidResponse.nurl, + width: bidResponse.w, + height: bidResponse.h, +}) + +const parseNative = (bid) => { + let adm = JSON.parse(bid.adm) + const { assets, link, imptrackers, jstracker } = adm.native; + const result = { + clickUrl: _encodeURIComponent(link.url), + clickTrackers: link.clicktrackers || [], + impressionTrackers: imptrackers || [], + javascriptTrackers: jstracker ? [ jstracker ] : [] + }; + assets.forEach(asset => { + if (!utils.isEmpty(asset.title)) { + result.title = asset.title.text + } else if (!utils.isEmpty(asset.img)) { + result[ORTB_NATIVE_TYPE_MAPPING.img[asset.img.type]] = { + url: asset.img.url, + height: asset.img.h, + width: asset.img.w + } + } else if (!utils.isEmpty(asset.data)) { + result[ORTB_NATIVE_TYPE_MAPPING.data[asset.data.type]] = asset.data.value + } + }); + + return result; +} + +const createPrebidVideoBid = (bid, bidResponse) => { + let videoBid = { + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + width: bidResponse.w, + height: bidResponse.h, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: 'USD', + mediaType: 'video', + winUrl: bidResponse.nurl, + }; + + let videoContext = bid.mediaTypes.video.context; + switch (videoContext) { + case OUTSTREAM: + videoBid.vastXml = bidResponse.adm; + break; + case INSTREAM: + videoBid.videoCacheKey = bidResponse.ext.bidder.cacheKey; + videoBid.vastUrl = bidResponse.ext.bidder.vastUrl; + break; } - params.pbRequestId = bid.bidId; - params.format = 'json'; - return params; + return videoBid; } -const _encodeURIComponent = function (a) { - let b = window.encodeURIComponent(a); - b = b.replace(/'/g, '%27'); - return b; +const getQueryVariable = (variable) => { + let query = window.location.search.substring(1); + let vars = query.split('&'); + for (var i = 0; i < vars.length; i++) { + let pair = vars[i].split('='); + if (decodeURIComponent(pair[0]) == variable) { + return decodeURIComponent(pair[1]); + } + } + return false; } export const spec = { @@ -59,134 +341,55 @@ export const spec = { supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - isBidRequestValid: bid => !!bid.params.partnerId, + isBidRequestValid: bid => !!bid.params.placement_id && !!bid.params.publisher_id && !!bid.params.partner_name, buildRequests: function (validBidRequests, bidderRequest) { let serverRequests = []; - const userAgent = navigator.userAgent; - const page = bidderRequest.refererInfo.referer; - - validBidRequests.forEach((bid, i) => { - let params = buildParams(bid); - let request = ''; - if (bid.nativeParams) { - request = { - method: 'GET', - url: PROD_ENDPOINT_URL, - data: utils.parseQueryStringParameters(params) - }; - } else { - let ortbReq = { - id: bidderRequest.auctionId, - imp: [{ - id: i.toString(), - banner: { - id: 'banner-' + bidderRequest.auctionId, - format: [ - { - 'h': 250, - 'w': 300 - }, - { - 'h': 50, - 'w': 320 - } - ] - }, - bidfloor: 0, - ext: { - dailyhunt: { - ...params - } - } - }], - site: { id: i.toString(), page }, - device: { userAgent }, - user: { - id: params.clientId || '', - } - }; - request = { - method: 'POST', - url: PROD_PREBID_ENDPOINT_URL, - data: JSON.stringify(ortbReq), - options: { - contentType: 'application/json', - withCredentials: true - }, - bids: validBidRequests - }; - } - serverRequests.push(request); + + // ORTB Request. + let ortbReq = createOrtbRequest(validBidRequests, bidderRequest); + + validBidRequests.forEach((bid) => { + let imp = createOrtbImpObj(bid) + ortbReq.imp.push(imp); }); + + serverRequests.push({ ...createServerRequest(ortbReq, validBidRequests, getQueryVariable('dh_test')) }); + return serverRequests; }, interpretResponse: function (serverResponse, request) { - let bidResponses = []; - if (!request.bids) { - let bid = serverResponse.body[0][0].ad; - if (bid.typeId != 2 && bid.typeId != 3) { - return bidResponses; - } - let impTrackers = []; - impTrackers.push(bid.beaconUrl); - impTrackers = (bid.beaconUrlAdditional && bid.beaconUrlAdditional.length !== 0) ? impTrackers.concat(bid.beaconUrlAdditional) : impTrackers; - let bidResponse = { - requestId: bid.pbRequestId, - cpm: bid.price, - creativeId: bid.bannerid, - currency: 'USD', - ttl: 360, - netRevenue: true, - }; - bidResponse.mediaType = 'native' - bidResponse.native = { - title: bid.content.itemTitle.data, - body: bid.content.itemSubtitle1.data, - body2: bid.content.itemSubtitle1.data, - cta: bid.content.itemSubtitle2.data, - clickUrl: _encodeURIComponent(bid.action), - impressionTrackers: impTrackers, - clickTrackers: bid.landingUrlAdditional && bid.landingUrlAdditional.length !== 0 ? bid.landingUrlAdditional : [], - image: { - url: bid.content.iconLink, - height: bid.height, - width: bid.width - }, - icon: { - url: bid.content.iconLink, - height: bid.height, - width: bid.width - } - } - bidResponses.push(bidResponse); - return bidResponses; - } else { - if (!serverResponse.body) { - return; + const { seatbid } = serverResponse.body; + let bids = request.bids; + let prebidResponse = []; + + let seatBids = seatbid[0].bid; + + seatBids.forEach(ortbResponseBid => { + let bidId = ortbResponseBid.impid; + let actualBid = find(bids, (bid) => bid.bidId === bidId); + let bidMediaType = ortbResponseBid.ext.prebid.type + switch (bidMediaType) { + case mediaTypes.BANNER: + prebidResponse.push(createPrebidBannerBid(actualBid, ortbResponseBid)); + break; + case mediaTypes.NATIVE: + prebidResponse.push(createPrebidNativeBid(actualBid, ortbResponseBid)); + break; + case mediaTypes.VIDEO: + prebidResponse.push(createPrebidVideoBid(actualBid, ortbResponseBid)); + break; } - const { seatbid } = serverResponse.body; - let bids = request.bids; - return bids.reduce((accumulator, bid, index) => { - const _cbid = seatbid && seatbid[index] && seatbid[index].bid; - const bidResponse = _cbid && _cbid[0]; - if (bidResponse) { - accumulator.push({ - requestId: bid.bidId, - cpm: bidResponse.price, - creativeId: bidResponse.crid, - width: bidResponse.w, - height: bidResponse.h, - ttl: 360, - netRevenue: bid.netRevenue === 'net', - currency: 'USD', - ad: bidResponse.adm - }); - } - return accumulator; - }, []); - } + }) + return prebidResponse; }, + + onBidWon: function(bid) { + ajax(bid.winUrl, null, null, { + method: 'GET' + }) + } } + registerBidder(spec); diff --git a/modules/dailyhuntBidAdapter.md b/modules/dailyhuntBidAdapter.md index d860d0817c2..acfd20a4de0 100644 --- a/modules/dailyhuntBidAdapter.md +++ b/modules/dailyhuntBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: Dailyhunt Connects to dailyhunt for bids. -Dailyhunt bid adapter supports Banner and Native. +Dailyhunt bid adapter supports Banner, Native and Video. # Test Parameters ``` @@ -27,7 +27,12 @@ Dailyhunt bid adapter supports Banner and Native. { bidder: 'dailyhunt', params: { - partnerId: 'pb-partnerId' + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + device: { + ip: "182.23.143.212" + } } } ] @@ -55,15 +60,42 @@ Dailyhunt bid adapter supports Banner and Native. { bidder: 'dailyhunt', params: { - partnerId: 'pb-partnerId' + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + device: { + ip: "182.23.143.212" + } + } + } + ] + }, + { + code: '/83414793/prebid_test_video', + mediaTypes: { + video: { + playerSize: [1280, 720], + context: 'instream' + } + }, + bids: [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + device: { + ip: "182.23.143.212" + }, + video: { + mimes: [ + 'video/mp4' + ] + } } } ] } ]; ``` - -# CheckList -``` -https://drive.google.com/open?id=1t4rmcyHl5OmRF3sYiqBi-VKEjzX6iN-A -``` diff --git a/modules/datablocksAnalyticsAdapter.js b/modules/datablocksAnalyticsAdapter.js index 76dd490180b..5e977155284 100644 --- a/modules/datablocksAnalyticsAdapter.js +++ b/modules/datablocksAnalyticsAdapter.js @@ -2,8 +2,8 @@ * Analytics Adapter for Datablocks */ -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; var datablocksAdapter = adapter({ global: 'datablocksAnalytics', diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index cbad3ac910c..b00a3eae659 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,7 +1,6 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; -import { parse as parseUrl } from '../src/url'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; const NATIVE_MAP = { 'body': 2, 'body2': 10, @@ -61,7 +60,7 @@ export const spec = { let imps = {}; let site = {}; let device = {}; - let refurl = parseUrl(bidderRequest.referrer); + let refurl = utils.parseUrl(bidderRequest.referrer); let requests = []; validBidRequests.forEach(bidRequest => { diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 39866086cfa..c4dc23cf912 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -1,9 +1,9 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; -import {BANNER} from '../src/mediaTypes'; -import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; const BIDDER_CODE = 'deepintent'; const BIDDER_ENDPOINT = 'https://prebid.deepintent.com/prebid'; -const USER_SYNC_URL = 'https://beacon.deepintent.com/usersync.html'; +const USER_SYNC_URL = 'https://cdn.deepintent.com/syncpixel.html'; const DI_M_V = '1.0.0'; export const spec = { code: BIDDER_CODE, @@ -37,9 +37,18 @@ export const spec = { imp: validBidRequests.map(bid => buildImpression(bid)), site: buildSite(bidderRequest), device: buildDevice(), - user: user && user.length == 1 ? user[0] : {} + user: user && user.length === 1 ? user[0] : {} }; + if (bidderRequest && bidderRequest.uspConsent) { + utils.deepSetValue(openRtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + utils.deepSetValue(openRtbBidRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + utils.deepSetValue(openRtbBidRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + return { method: 'POST', url: BIDDER_ENDPOINT, @@ -127,13 +136,15 @@ function buildBanner(bid) { if (utils.isArray(sizes) && sizes.length > 0) { return { h: sizes[0][1], - w: sizes[0][0] + w: sizes[0][0], + pos: bid && bid.params && bid.params.pos ? bid.params.pos : 0 } } } else { return { h: bid.params.height, - w: bid.params.width + w: bid.params.width, + pos: bid && bid.params && bid.params.pos ? bid.params.pos : 0 } } } diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md index 7f5afbd233a..79a6a1679e2 100644 --- a/modules/deepintentBidAdapter.md +++ b/modules/deepintentBidAdapter.md @@ -29,6 +29,7 @@ Module that connects to Deepintent's demand sources. tagId: '1300', // Required parameter w: 300, // Width and Height here will override sizes in mediatype h: 250, + pos: 1, custom: { // Custom parameters in form of key value pairs user_min_age: 18 } diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index c3f867308d1..1d999fdbacc 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -2,13 +2,12 @@ * This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid. */ -import { registerVideoSupport } from '../src/adServerManager'; -import { targeting } from '../src/targeting'; -import { formatQS, format as buildUrl, parse } from '../src/url'; -import { deepAccess, isEmpty, logError, parseSizesInput } from '../src/utils'; -import { config } from '../src/config'; -import { getHook, submodule } from '../src/hook'; -import { auctionManager } from '../src/auctionManager'; +import { registerVideoSupport } from '../src/adServerManager.js'; +import { targeting } from '../src/targeting.js'; +import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, buildUrl } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { getHook, submodule } from '../src/hook.js'; +import { auctionManager } from '../src/auctionManager.js'; /** * @typedef {Object} DfpVideoParams @@ -74,7 +73,7 @@ export function buildDfpVideoUrl(options) { if (options.url) { // when both `url` and `params` are given, parsed url will be overwriten // with any matching param components - urlComponents = parse(options.url, {noDecodeWholeURL: true}); + urlComponents = parseUrl(options.url, {noDecodeWholeURL: true}); if (isEmpty(options.params)) { return buildUrlFromAdserverUrlComponents(urlComponents, bid, options); @@ -83,7 +82,7 @@ export function buildDfpVideoUrl(options) { const derivedParams = { correlator: Date.now(), - sz: parseSizesInput(adUnit.sizes).join('|'), + sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'), url: encodeURIComponent(location.href), }; const encodedCustomParams = getCustParams(bid, options); diff --git a/modules/digiTrustIdSystem.js b/modules/digiTrustIdSystem.js deleted file mode 100644 index 89557e0917e..00000000000 --- a/modules/digiTrustIdSystem.js +++ /dev/null @@ -1,365 +0,0 @@ -/** - * This module adds DigiTrust ID support to the User ID module - * The {@link module:modules/userId} module is required - * If the full DigiTrust Id library is included the standard functions - * will be invoked to obtain the user's DigiTrust Id. - * When the full library is not included this will fall back to the - * DigiTrust Identity API and generate a mock DigiTrust object. - * @module modules/digiTrustIdSystem - * @requires module:modules/userId - */ - -import * as utils from '../src/utils' -import { ajax } from '../src/ajax'; -import { submodule } from '../src/hook'; - -var fallbackTimeout = 1550; // timeout value that allows userId system to execute first -var fallbackTimer = 0; // timer Id for fallback init so we don't double call - -/** - * Checks to see if the DigiTrust framework is initialized. - * @function - */ -function isInitialized() { - if (window.DigiTrust == null) { - return false; - } - return DigiTrust.isClient; // this is set to true after init -} - -/** - * Tests for presence of the DigiTrust object - * */ -function isPresent() { - return (window.DigiTrust != null); -} - -var noop = function () { -}; - -const MAX_RETRIES = 2; -const DT_ID_SVC = 'https://prebid.digitru.st/id/v1'; - -var isFunc = function (fn) { - return typeof (fn) === 'function'; -} - -function callApi(options) { - ajax( - DT_ID_SVC, - { - success: options.success, - error: options.fail - }, - null, - { - method: 'GET' - } - ); -} - -/** - * Encode the Id per DigiTrust lib - * @param {any} id - */ -function encId(id) { - try { - if (typeof (id) !== 'string') { - id = JSON.stringify(id); - } - return encodeURIComponent(btoa(id)); - } catch (ex) { - return id; - } -} - -/** - * Writes the Identity into the expected DigiTrust cookie - * @param {any} id - */ -function writeDigiId(id) { - var key = 'DigiTrust.v1.identity'; - var date = new Date(); - date.setTime(date.getTime() + 604800000); - var exp = 'expires=' + date.toUTCString(); - document.cookie = key + '=' + encId(id) + '; ' + exp + '; path=/;SameSite=none;'; -} - -/** - * Set up a DigiTrust facade object to mimic the API - * - */ -function initDigitrustFacade(config) { - var _savedId = null; // closure variable for storing Id to avoid additional requests - - clearTimeout(fallbackTimer); - fallbackTimer = 0; - - var facade = { - isClient: true, - isMock: true, - _internals: { - callCount: 0, - initCallback: null - }, - getUser: function (obj, callback) { - var isAsync = !!isFunc(callback); - var cb = isAsync ? callback : noop; - var errResp = { success: false }; - var inter = facade._internals; - inter.callCount++; - - // wrap the initializer callback, if present - var checkCallInitializeCb = function (idResponse) { - if (inter.callCount <= 1 && isFunc(inter.initCallback)) { - try { - inter.initCallback(idResponse); - } catch (ex) { - utils.logError('Exception in passed DigiTrust init callback'); - } - } - } - - if (!isMemberIdValid) { - if (!isAsync) { - return errResp - } else { - cb(errResp); - return; - } - } - - if (_savedId != null) { - checkCallInitializeCb(_savedId); - if (isAsync) { - cb(_savedId); - return; - } else { - return _savedId; - } - } - - var opts = { - success: function (respText, result) { - var idResult = { - success: true - } - try { - writeDigiId(respText); - idResult.identity = JSON.parse(respText); - _savedId = idResult; - } catch (ex) { - idResult.success = false; - } - checkCallInitializeCb(idResult); - cb(idResult); - }, - fail: function (statusErr, result) { - utils.logError('DigiTrustId API error: ' + statusErr); - } - } - - callApi(opts); - - if (!isAsync) { - return errResp; // even if it will be successful later, without a callback we report a "failure in this moment" - } - } - } - - if (config && isFunc(config.callback)) { - facade._internals.initCallback = config.callback; - } - - if (window && window.DigiTrust == null) { - window.DigiTrust = facade; - } -} - -/** - * Tests to see if a member ID is valid within facade - * @param {any} memberId - */ -var isMemberIdValid = function (memberId) { - if (memberId && memberId.length > 0) { - return true; - } else { - utils.logError('[DigiTrust Prebid Client Error] Missing member ID, add the member ID to the function call options'); - return false; - } -}; - -/** - * Encapsulation of needed info for the callback return. - * - * @param {any} opts - */ -var ResultWrapper = function (opts) { - var me = this; - this.idObj = null; - - var idSystemFn = null; - - /** - * Callback method that is passed back to the userId module. - * - * @param {function} callback - */ - this.userIdCallback = function (callback) { - idSystemFn = callback; - if (me.idObj != null && isFunc(callback)) { - callback(wrapIdResult()); - } - } - - /** - * Return a wrapped result formatted for userId system - */ - function wrapIdResult() { - if (me.idObj == null) { - return null; - } - - var cp = me.configParams; - var exp = (cp && cp.storage && cp.storage.expires) || 60; - - var rslt = { - data: null, - expires: exp - }; - if (me.idObj && me.idObj.success && me.idObj.identity) { - rslt.data = me.idObj.identity; - } else { - rslt.err = 'Failure getting id'; - } - - return rslt; - } - - this.retries = 0; - this.retryId = 0; - - this.executeIdRequest = function (configParams) { - DigiTrust.getUser({ member: 'prebid' }, function (idResult) { - me.idObj = idResult; - var cb = function () { - if (isFunc(idSystemFn)) { - idSystemFn(wrapIdResult()); - } - } - - cb(); - if (configParams && configParams.callback && isFunc(configParams.callback)) { - try { - configParams.callback(idResult); - } catch (ex) { - utils.logError('Failure in DigiTrust executeIdRequest', ex); - } - } - }); - } -} - -// An instance of the result wrapper object. -var resultHandler = new ResultWrapper(); - -/* - * Internal implementation to get the Id and trigger callback - */ -function getDigiTrustId(configParams) { - if (resultHandler.configParams == null) { - resultHandler.configParams = configParams; - } - - // First see if we should initialize DigiTrust framework - if (isPresent() && !isInitialized()) { - initializeDigiTrust(configParams); - resultHandler.retryId = setTimeout(function () { - getDigiTrustId(configParams); - }, 100 * (1 + resultHandler.retries++)); - return resultHandler.userIdCallback; - } else if (!isInitialized()) { // Second see if we should build a facade object - if (resultHandler.retries >= MAX_RETRIES) { - initDigitrustFacade(configParams); // initialize a facade object that relies on the AJAX call - resultHandler.executeIdRequest(configParams); - } else { - // use expanding envelope - if (resultHandler.retryId != 0) { - clearTimeout(resultHandler.retryId); - } - resultHandler.retryId = setTimeout(function () { - getDigiTrustId(configParams); - }, 100 * (1 + resultHandler.retries++)); - } - return resultHandler.userIdCallback; - } else { // Third get the ID - resultHandler.executeIdRequest(configParams); - return resultHandler.userIdCallback; - } -} - -function initializeDigiTrust(config) { - utils.logInfo('Digitrust Init'); - var dt = window.DigiTrust; - if (dt && !dt.isClient && config != null) { - dt.initialize(config.init, config.callback); - } else if (dt == null) { - // Assume we are already on a delay and DigiTrust is not on page - initDigitrustFacade(config); - } -} - -var testHook = {}; - -/** - * Exposes the test hook object by attaching to the digitrustIdModule. - * This method is called in the unit tests to surface internals. - */ -export function surfaceTestHook() { - digiTrustIdSubmodule['_testHook'] = testHook; - return testHook; -} - -testHook.initDigitrustFacade = initDigitrustFacade; // expose for unit tests - -/** @type {Submodule} */ -export const digiTrustIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: 'digitrust', - /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{pubcid:string}} - */ - decode: function (idData) { - try { - return { 'digitrustid': idData }; - } catch (e) { - utils.logError('DigiTrust ID submodule decode error'); - } - }, - getId: function (configParams) { - return {callback: getDigiTrustId(configParams)}; - }, - _testInit: surfaceTestHook -}; - -// check for fallback init of DigiTrust -function fallbackInit() { - if (resultHandler.retryId == 0 && !isInitialized()) { - // this triggers an init - var conf = { - member: 'fallback', - callback: noop - }; - getDigiTrustId(conf); - } -} - -fallbackTimer = setTimeout(fallbackInit, fallbackTimeout); - -submodule('userId', digiTrustIdSubmodule); diff --git a/modules/digiTrustIdSystem.md b/modules/digiTrustIdSystem.md deleted file mode 100644 index c0b274d3292..00000000000 --- a/modules/digiTrustIdSystem.md +++ /dev/null @@ -1,156 +0,0 @@ -## DigiTrust Universal Id Integration - -Setup ------ -The DigiTrust Id integration for Prebid may be used with or without the full -DigiTrust library. This is an optional module that must be used in conjunction -with the userId module. - -See the [Prebid Integration Guide for DigiTrust](https://github.com/digi-trust/dt-cdn/wiki/Prebid-Integration-for-DigiTrust-Id) -and the [DigiTrust wiki](https://github.com/digi-trust/dt-cdn/wiki) -for further instructions. - - -## Example Prebid Configuration for Digitrust Id -``` - pbjs.que.push(function() { - pbjs.setConfig({ - usersync: { - userIds: [{ - name: "digitrust", - params: { - init: { - member: 'example_member_id', - site: 'example_site_id' - }, - callback: function (digiTrustResult) { - // This callback method is optional and used for error handling - // in many if not most cases. - /* - if (digiTrustResult.success) { - // Success in Digitrust init; - // 'DigiTrust Id (encrypted): ' + digiTrustResult.identity.id; - } - else { - // Digitrust init failed - } - */ - } - }, - storage: { - type: "html5", - name: "pbjsdigitrust", - expires: 60 - } - }] - } - }); - pbjs.addAdUnits(adUnits); - pbjs.requestBids({ - bidsBackHandler: sendAdserverRequest - }); - }); - -``` - - -## Building Prebid with DigiTrust Support -Your Prebid build must include the modules for both **userId** and **digitrustIdLoader**. Follow the build instructions for Prebid as -explained in the top level README.md file of the Prebid source tree. - -ex: $ gulp build --modules=userId,digitrustIdLoader - -### Step by step Prebid build instructions for DigiTrust - -1. Download the Prebid source from [Prebid Git Repo](https://github.com/prebid/Prebid.js) -2. Set up your environment as outlined in the [Readme File](https://github.com/prebid/Prebid.js/blob/master/README.md#Build) -3. Execute the build command either with all modules or with the `userId` and `digitrustIdLoader` modules. - ``` - $ gulp build --modules=userId,digitrustIdLoader - ``` -4. (Optional) Concatenate the DigiTrust source code to the end of your `prebid.js` file for a single source distribution. -5. Upload the resulting source file to your CDN. - - -## Deploying Prebid with DigiTrust ID support -**Precondition:** You must be a DigiTrust member and have registered through the [DigiTrust Signup Process](http://www.digitru.st/signup/). -Your assigned publisher ID will be required in the configuration settings for all deployment scenarios. - -There are three supported approaches to deploying the Prebid-integrated DigiTrust package: - -* "Bare bones" deployment using only the integrated DigiTrust module code. -* Full DigiTrust with CDN referenced DigiTrust.js library. -* Full DigiTrust packaged with Prebid or site js. - -### Bare Bones Deployment - -This deployment results in the smallest Javascript package and is the simplest deployment. -It is appropriate for testing or deployments where simplicity is key. This approach -utilizes the REST API for ID generation. While there is less Javascript in use, -the user may experience more network requests than the scenarios that include the full -DigiTrust library. - -1. Build your Prebid package as above, skipping step 4. -2. Add the DigiTrust initializer section to your Prebid initialization object as below, - using your Member ID and Site ID. -3. Add a reference to your Prebid package and the initialization code on all pages you wish - to utilize Prebid with integrated DigiTrust ID. - - - - -### Full DigiTrust with CDN referenced DigiTrust library - -Both "Full DigiTrust" deployments will result in a larger initial Javascript payload. -The end user may experience fewer overall network requests as the encrypted and anonymous -DigiTrust ID can often be generated fully in client-side code. Utilizing the CDN reference -to the official DigiTrust distribution insures you will be running the latest version of the library. - -The Full DigiTrust deployment is designed to work with both new DigiTrust with Prebid deployments, and with -Prebid deployments by existing DigiTrust members. This allows you to migrate your code more slowly -without losing DigiTrust support in the process. - -1. Deploy your built copy of `prebid.js` to your CDN. -2. On each page reference both your `prebid.js` and a copy of the **DigiTrust** library. - This may either be a copy downloaded from the [DigiTrust CDN](https://cdn.digitru.st/prod/1/digitrust.min.js) to your CDN, - or directly referenced from the URL https://cdn.digitru.st/prod/1/digitrust.min.js. These may be added to the page in any order. -3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings. - -### Full DigiTrust packaged with Prebid - - -1. Deploy your built copy of `prebid.js` to your CDN. Be sure to perform *Step 4* of the build to concatenate or - integrate the full DigiTrust library code with your Prebid package. -2. On each page reference your `prebid.js` -3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings. - This code may also be appended to your Prebid package or placed in other initialization methods. - - - -## Parameter Descriptions for the `usersync` Configuration Section -The below parameters apply only to the DigiTrust ID integration. - -| Param under usersync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | ID value for the DigiTrust module - `"digitrust"` | `"digitrust"` | -| params | Required | Object | Details for DigiTrust initialization. | | -| params.init | Required | Object | Initialization parameters, including the DigiTrust Publisher ID and Site ID. | | -| params.init.member | Required | String | DigiTrust Publisher Id | "A897dTzB" | -| params.init.site | Required | String | DigiTrust Site Id | "MM2123" | -| params.callback | Optional | Function | Callback method to fire after initialization of the DigiTrust framework. The argument indicates failure and success and the identity object upon success. | | -| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | -| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | -| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"pbjsdigitrust"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. Default is 30 for UnifiedId and 1825 for PubCommonID | `365` | -| value | Optional | Object | Used only if the page has a separate mechanism for storing the Unified ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3"}` | - - - -## Further Reading - -+ [DigiTrust Home Page](http://digitru.st) - -+ [DigiTrust integration guide](https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide) - -+ [DigiTrust ID Encryption](https://github.com/digi-trust/dt-cdn/wiki/ID-encryption) - diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index 205017066a8..03dca0b607d 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import {config} from '../src/config'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'districtmDMX'; @@ -27,6 +27,9 @@ export const spec = { nBid.requestId = nBid.impid; nBid.width = nBid.w || width; nBid.height = nBid.h || height; + if (nBid.dealid) { + nBid.dealId = nBid.dealid; + } nBid.ad = nBid.adm; nBid.netRevenue = true; nBid.creativeId = nBid.crid; @@ -69,6 +72,7 @@ export const spec = { publisher: { id: String(bidRequest[0].params.memberid) || null } } } + try { let params = config.getConfig('dmx'); dmxRequest.user = params.user || {}; @@ -77,6 +81,18 @@ export const spec = { } catch (e) { } + + let eids = []; + if (bidRequest && bidRequest.userId) { + bindUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest, `userId.digitrustid.data.id`), 'digitru.st', 1); + bindUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid.org', 1); + bindUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 1); + dmxRequest.user = dmxRequest.user || {}; + dmxRequest.user.ext = dmxRequest.user.ext || {}; + dmxRequest.user.ext.eids = eids; + } if (!dmxRequest.test) { delete dmxRequest.test; } @@ -88,6 +104,13 @@ export const spec = { dmxRequest.user.ext = {}; dmxRequest.user.ext.consent = bidderRequest.gdprConsent.consentString; } + dmxRequest.regs = dmxRequest.regs || {}; + dmxRequest.regs.coppa = config.getConfig('coppa') === true ? 1 : 0; + if (bidderRequest && bidderRequest.uspConsent) { + dmxRequest.regs = dmxRequest.regs || {}; + dmxRequest.regs.ext = dmxRequest.regs.ext || {}; + dmxRequest.regs.ext.us_privacy = bidderRequest.uspConsent; + } try { schain = bidRequest[0].schain; dmxRequest.source = {}; @@ -125,11 +148,22 @@ export const spec = { test() { return window.location.href.indexOf('dmTest=true') !== -1 ? 1 : 0; }, - getUserSyncs(optionsType) { + getUserSyncs(optionsType, serverResponses, gdprConsent, uspConsent) { + let query = []; + let url = 'https://cdn.districtm.io/ids/index.html' + if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { + query.push(['gdpr', gdprConsent.consentString]) + } + if (uspConsent) { + query.push(['ccpa', uspConsent]) + } + if (query.length > 0) { + url += '?' + query.map(q => q.join('=')).join('&') + } if (optionsType.iframeEnabled) { return [{ type: 'iframe', - url: 'https://cdn.districtm.io/ids/index.html' + url: url }]; } } @@ -262,4 +296,18 @@ export function defaultSize(thebidObj) { returnObject.height = checkDeepArray(sizes)[1]; return returnObject; } + +export function bindUserId(eids, value, source, atype) { + if (utils.isStr(value) && Array.isArray(eids)) { + eids.push({ + source, + uids: [ + { + id: value, + atype + } + ] + }) + } +} registerBidder(spec); diff --git a/modules/districtmDmxBidAdapter.md b/modules/districtmDmxBidAdapter.md index 5bc1e5d5780..792cf2e7305 100644 --- a/modules/districtmDmxBidAdapter.md +++ b/modules/districtmDmxBidAdapter.md @@ -13,6 +13,8 @@ The `districtmDmxAdapter` module allows publishers to include DMX Exchange deman * Single Request * Multi-Size Support * GDPR Compliant +* CCPA Compliant +* COPPA Compliant * Bids returned in **NET** ## Media Types @@ -23,8 +25,8 @@ The `districtmDmxAdapter` module allows publishers to include DMX Exchange deman | Key | Scope | Type | Description | --- | --- | --- | --- -| dmxid | Mandatory | Integer | Unique identifier of the placement, dmxid can be obtained in the district m Boost platform. -| memberid | Mandatory | Integer | Unique identifier for your account, memberid can be obtained in the district m Boost platform. +| `dmxid` | Mandatory | Integer | Unique identifier of the placement, dmxid can be obtained in the district m Boost platform. +| `memberid` | Mandatory | Integer | Unique identifier for your account, memberid can be obtained in the district m Boost platform. # Ad Unit Configuration Example @@ -47,6 +49,9 @@ The `districtmDmxAdapter` module allows publishers to include DMX Exchange deman ``` +# Ad Unit Configuration when COPPA is needed + + # Quick Start Guide ###### 1. Including the `districtmDmxAdapter` in your build process. diff --git a/modules/djaxBidAdapter.js b/modules/djaxBidAdapter.js index 80975f0929a..ffaf61a3f15 100644 --- a/modules/djaxBidAdapter.js +++ b/modules/djaxBidAdapter.js @@ -1,9 +1,9 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { config } from '../src/config'; -import * as utils from '../src/utils'; -import {BANNER, VIDEO} from '../src/mediaTypes'; -import { ajax } from '../src/ajax'; -import {Renderer} from '../src/Renderer'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; +import {Renderer} from '../src/Renderer.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'djax'; diff --git a/modules/docereeBidAdapter.md b/modules/docereeBidAdapter.md new file mode 100644 index 00000000000..9e3d4bbe1b8 --- /dev/null +++ b/modules/docereeBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: Doceree Bidder Adapter +Module Type: Bidder Adapter + +# Description + +Connects to Doceree demand source to fetch bids. +Please use ```doceree``` as the bidder code. + +# Test Parameters +``` + var adUnits = [ + { + code: 'desktop-banner-ad-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "doceree", + params: { + accountID: '167283', + zoneID: '445501', + domain: 'adbserver.doceree.com', + extra: { + tuid: '1234-abcd' + } + } + } + ] + }, + ]; +``` diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js new file mode 100644 index 00000000000..d05549601e1 --- /dev/null +++ b/modules/dspxBidAdapter.js @@ -0,0 +1,154 @@ +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'dspx'; +const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; +const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; +const DEFAULT_VAST_FORMAT = 'vast2'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['dspx'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function(bid) { + return !!(bid.params.placement); + }, + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const params = bidRequest.params; + + const videoData = utils.deepAccess(bidRequest, 'mediaTypes.video') || {}; + const sizes = utils.parseSizesInput(videoData.playerSize || bidRequest.sizes)[0]; + const [width, height] = sizes.split('x'); + + const placementId = params.placement; + const rnd = Math.floor(Math.random() * 99999999999); + const referrer = bidderRequest.refererInfo.referer; + const bidId = bidRequest.bidId; + const isDev = params.devMode || false; + + let endpoint = isDev ? ENDPOINT_URL_DEV : ENDPOINT_URL; + let payload = {}; + + if (isVideoRequest(bidRequest)) { + let vastFormat = params.vastFormat || DEFAULT_VAST_FORMAT; + payload = { + _f: vastFormat, + alternative: 'prebid_js', + inventory_item_id: placementId, + srw: width, + srh: height, + idt: 100, + rnd: rnd, + ref: referrer, + bid_id: bidId, + }; + } else { + payload = { + _f: 'html', + alternative: 'prebid_js', + inventory_item_id: placementId, + srw: width, + srh: height, + idt: 100, + rnd: rnd, + ref: referrer, + bid_id: bidId, + }; + } + + if (params.pfilter !== undefined) { + payload.pfilter = params.pfilter; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + if (payload.pfilter !== undefined) { + if (!payload.pfilter.gdpr_consent) { + payload.pfilter.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.pfilter.gdpr = bidderRequest.gdprConsent.gdprApplies; + } + } else { + payload.pfilter = { + 'gdpr_consent': bidderRequest.gdprConsent.consentString, + 'gdpr': bidderRequest.gdprConsent.gdprApplies + }; + } + } + + if (params.bcat !== undefined) { + payload.bcat = params.bcat; + } + if (params.dvt !== undefined) { + payload.dvt = params.dvt; + } + if (isDev) { + payload.prebidDevMode = 1; + } + return { + method: 'GET', + url: endpoint, + data: objectToQueryString(payload), + } + }); + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const crid = response.crid || 0; + const cpm = response.cpm / 1000000 || 0; + if (cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'EUR'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const bidResponse = { + requestId: response.bid_id, + cpm: cpm, + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + type: response.type, + ttl: config.getConfig('_bidderTimeout') + }; + if (response.vastXml) { + bidResponse.vastXml = response.vastXml; + bidResponse.mediaType = 'video'; + } else { + bidResponse.ad = response.adTag; + } + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +function objectToQueryString(obj, prefix) { + let str = []; + let p; + for (p in obj) { + if (obj.hasOwnProperty(p)) { + let k = prefix ? prefix + '[' + p + ']' : p; + let v = obj[p]; + str.push((v !== null && typeof v === 'object') + ? objectToQueryString(v, k) + : encodeURIComponent(k) + '=' + encodeURIComponent(v)); + } + } + return str.join('&'); +} + +/** + * Check if it's a video bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a video bid + */ +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); +} + +registerBidder(spec); diff --git a/modules/dspxBidAdapter.md b/modules/dspxBidAdapter.md index 362f4fbcb69..8733aff698c 100644 --- a/modules/dspxBidAdapter.md +++ b/modules/dspxBidAdapter.md @@ -1,14 +1,14 @@ # Overview ``` -Module Name: Dspx Bidder Adapter +Module Name: DSPx Bidder Adapter Module Type: Bidder Adapter Maintainer: prebid@dspx.tv ``` # Description -Dspx adapter for Prebid.js 1.0 +DSPx adapter for Prebid. # Test Parameters ``` @@ -20,54 +20,46 @@ Dspx adapter for Prebid.js 1.0 sizes: [ [300, 250], [300, 600], - ], // a display size + ] } }, bids: [ { bidder: "dspx", params: { - placement: '101', - pfilter: { - floorprice: 1000000, // EUR * 1,000,000 - private_auction: 1, // Is private auction? 0 - no, 1 - yes - deals: [ - "666-9315-d58a7f9a-bdb9-4450-a3a2-046ba8ab2489;3;25000000;dspx-tv",// DEAL_ID;at;bidfloor;wseat1,wseat2;wadomain1,wadomain2" - "666-9315-d58a7f9a-bdb9-4450-a6a2-046ba8ab2489;3;25000000;dspx-tv",// DEAL_ID;at;bidfloor;wseat1,wseat2;wadomain1,wadomain2" - ], - geo: { // set client geo info manually (empty for auto detect) - lat: 52.52437, // Latitude from -90.0 to +90.0, where negative is south. - lon: 13.41053, // Longitude from -180.0 to +180.0, where negative is west - type: 1, // Source of location data: 1 - GPS/Location Services, 2 - IP Address, 3 - User provided (e.g. registration form) - country: 'DE', // Region of a country using FIPS 10-4 notation - region: 'DE-BE', // Region code using ISO-3166-2; 2-letter state code if USA. - regionfips104: 'GM', // Region of a country using FIPS 10-4 notation - city: 'BER', // City using United Nations Code for Trade and Transport Locations - zip: '10115' // Zip or postal code. - } + placement: '101', // [required] info available from your contact with DSPx team + /* + bcat: "IAB2,IAB4", // [optional] list of blocked advertiser categories (IAB), comma separated + */ + /* + pfilter: { // [optional] + // [optional] only required if a deal is negotiated + deals: [ // [optional] + "123-4567-d58a7f9a-..."// DEAL_ID from DSPx contact + ], + private_auction: 1 // [optional] 0 - no, 1 - yes + // usually managed on DSPx side + floorprice: 1000000 // input min_cpm_micros, CPM in EUR * 1000000 }, - bcat: "IAB2,IAB4", // List of Blocked Categories (IAB) - comma separated - dvt: "desktop|smartphone|tv|tablet" // DeVice Type (autodetect if not exists) + */ } } ] - },{ - code: 'test-div', + }, + { + code: 'video1', mediaTypes: { - banner: { - sizes: [[320, 50]], // a mobile size + video: { + playerSize: [640, 480], + context: 'instream' } }, - bids: [ - { - bidder: "dspx", - params: { - placement: 101 - } + bids: [{ + bidder: 'dspx', + params: { + placement: '106' } - ] + }] } ]; ``` - -Required param field is only `placement`. \ No newline at end of file diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js new file mode 100644 index 00000000000..9fc7035db32 --- /dev/null +++ b/modules/e_volutionBidAdapter.js @@ -0,0 +1,111 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'e_volution'; +const AD_URL = 'https://service.e-volution.ai/?c=o&m=multi'; +const URL_SYNC = 'https://service.e-volution.ai/?c=o&m=sync'; +const NO_SYNC = true; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + noSync: NO_SYNC, + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + const len = validBidRequests.length; + + for (let i = 0; i < len; i++) { + let bid = validBidRequests[i]; + let traff = bid.params.traffic || BANNER + + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [], + traffic: traff + }); + if (bid.schain) { + placements.schain = bid.schain; + } + } + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + if (NO_SYNC) { + return false + } else { + return [{ + type: 'image', + url: URL_SYNC + }]; + } + } + +}; + +registerBidder(spec); diff --git a/modules/e_volutionBidAdapter.md b/modules/e_volutionBidAdapter.md new file mode 100644 index 00000000000..ff5b2860e05 --- /dev/null +++ b/modules/e_volutionBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: e_volution Bidder Adapter +Module Type: Bidder Adapter +``` + +# Description + +Module that connects to e-volution-tech demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'e_volution', + params: { + placementId: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'e_volution', + params: { + placementId: 0, + traffic: 'video' + } + } + ] + } + ]; +``` diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js index d8af215f96a..c30c10d8a90 100644 --- a/modules/ebdrBidAdapter.js +++ b/modules/ebdrBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import { VIDEO, BANNER } from '../src/mediaTypes'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ebdr'; export const spec = { code: BIDDER_CODE, diff --git a/modules/edgequeryxBidAdapter.js b/modules/edgequeryxBidAdapter.js new file mode 100644 index 00000000000..ee50946ee18 --- /dev/null +++ b/modules/edgequeryxBidAdapter.js @@ -0,0 +1,98 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'edgequeryx'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['eqx'], // short 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: function (bid) { + return !!(bid.params && bid.params.accountId && bid.params.widgetId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest bidder request object + * @return {ServerRequest[]} Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + // use bidderRequest.bids[] to get bidder-dependent request info + // if your bidder supports multiple currencies, use config.getConfig(currency) + // to find which one the ad server needs + + // pull requested transaction ID from bidderRequest.bids[].transactionId + return validBidRequests.map(bid => { + // Common bid request attributes for banner, outstream and instream. + let payload = { + accountId: bid.params.accountId, + widgetId: bid.params.widgetId, + currencyCode: 'EUR', + tagId: bid.adUnitCode, + transactionId: bid.transactionId, + timeout: config.getConfig('bidderTimeout'), + bidId: bid.bidId, + prebidVersion: '$prebid.version$' + }; + + const bannerMediaType = utils.deepAccess(bid, 'mediaTypes.banner'); + payload.sizes = bannerMediaType.sizes.map(size => ({ + w: size[0], + h: size[1] + })); + + var payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: (bid.params.domain !== undefined ? bid.params.domain : 'https://deep.edgequery.io') + '/prebid/x', + data: payloadString, + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequestString + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequestString) { + const bidResponses = []; + let response = serverResponse.body; + try { + if (response) { + let bidResponse = { + requestId: response.requestId, + cpm: response.cpm, + currency: response.currency, + width: response.width, + height: response.height, + ad: response.ad, + ttl: response.ttl, + creativeId: response.creativeId, + netRevenue: response.netRevenue + }; + + bidResponses.push(bidResponse); + } + } catch (error) { + utils.logError('Error while parsing Edge Query X response', error); + } + return bidResponses; + } + +}; + +registerBidder(spec); diff --git a/modules/edgequeryxBidAdapter.md b/modules/edgequeryxBidAdapter.md new file mode 100644 index 00000000000..265120dfaba --- /dev/null +++ b/modules/edgequeryxBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +``` +Module Name: Edge Query X Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@edgequery.com +``` + +# Description + +Connect to Edge Query X for bids. + +The Edge Query X adapter requires setup and approval from the Edge Query team. +Please reach out to your Technical account manager for more information. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, + bids: [ + { + bidder: "edgequeryx", + params: { + accountId: "test", + widgetId: "test" + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/emoteevBidAdapter.js b/modules/emoteevBidAdapter.js index fb62e4381e3..e0f88725d8a 100644 --- a/modules/emoteevBidAdapter.js +++ b/modules/emoteevBidAdapter.js @@ -14,8 +14,8 @@ * @author Emoteev Engineering . */ -import {registerBidder} from '../src/adapters/bidderFactory'; -import {BANNER} from '../src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; import { triggerPixel, getUniqueIdentifierStr, @@ -24,10 +24,12 @@ import { isArray, isInteger, getParameterByName, - getCookie -} from '../src/utils'; -import {config} from '../src/config'; -import * as url from '../src/url'; + buildUrl +} from '../src/utils.js'; +import {config} from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); export const BIDDER_CODE = 'emoteev'; @@ -232,7 +234,7 @@ export const domain = (env) => { * @param {string} env Emoteev environment parameter * @returns {string} The full URL which events is sent to. */ -export const eventsUrl = env => url.format({ +export const eventsUrl = env => buildUrl({ protocol: (env === DEVELOPMENT) ? 'http' : 'https', hostname: domain(env), pathname: EVENTS_PATH @@ -244,7 +246,7 @@ export const eventsUrl = env => url.format({ * @param {string} env Emoteev environment parameter * @returns {string} The full URL which bidderRequest is sent to. */ -export const bidderUrl = env => url.format({ +export const bidderUrl = env => buildUrl({ protocol: (env === DEVELOPMENT) ? 'http' : 'https', hostname: domain(env), pathname: BIDDER_PATH @@ -256,7 +258,7 @@ export const bidderUrl = env => url.format({ * @param {string} env Emoteev environment parameter * @returns {string} The full URL called for iframe-based user sync */ -export const userSyncIframeUrl = env => url.format({ +export const userSyncIframeUrl = env => buildUrl({ protocol: (env === DEVELOPMENT) ? 'http' : 'https', hostname: domain(env), pathname: USER_SYNC_IFRAME_PATH @@ -268,7 +270,7 @@ export const userSyncIframeUrl = env => url.format({ * @param {string} env Emoteev environment parameter * @returns {string} The full URL called for image-based user sync */ -export const userSyncImageUrl = env => url.format({ +export const userSyncImageUrl = env => buildUrl({ protocol: (env === DEVELOPMENT) ? 'http' : 'https', hostname: domain(env), pathname: USER_SYNC_IMAGE_PATH @@ -506,12 +508,12 @@ export const spec = { bidderRequest), interpretResponse: interpretResponse, onBidWon: (bidObject) => - triggerPixel(url.format(onBidWon( + triggerPixel(buildUrl(onBidWon( resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), - getCookie('_pubcid'), + storage.getCookie('_pubcid'), bidObject))), onTimeout: (bidRequest) => - triggerPixel(url.format(onTimeout( + triggerPixel(buildUrl(onTimeout( resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), bidRequest))), getUserSyncs: (syncOptions) => diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js new file mode 100644 index 00000000000..fa58481548a --- /dev/null +++ b/modules/emx_digitalBidAdapter.js @@ -0,0 +1,303 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import find from 'core-js-pure/features/array/find.js'; + +const BIDDER_CODE = 'emx_digital'; +const ENDPOINT = 'hb.emxdgt.com'; +const RENDERER_URL = 'https://js.brealtime.com/outstream/1.30.0/bundle.js'; +const ADAPTER_VERSION = '1.5.1'; +const DEFAULT_CUR = 'USD'; + +export const emxAdapter = { + validateSizes: (sizes) => { + if (!utils.isArray(sizes) || typeof sizes[0] === 'undefined') { + utils.logWarn(BIDDER_CODE + ': Sizes should be an array'); + return false; + } + return sizes.every(size => utils.isArray(size) && size.length === 2); + }, + checkVideoContext: (bid) => { + return ((bid && bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context) && ((bid.mediaTypes.video.context === 'instream') || (bid.mediaTypes.video.context === 'outstream'))); + }, + buildBanner: (bid) => { + let sizes = []; + bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes ? sizes = bid.mediaTypes.banner.sizes : sizes = bid.sizes; + if (!emxAdapter.validateSizes(sizes)) { + utils.logWarn(BIDDER_CODE + ': could not detect mediaType banner sizes. Assigning to bid sizes instead'); + sizes = bid.sizes + } + return { + format: sizes.map((size) => { + return { + w: size[0], + h: size[1] + }; + }), + w: sizes[0][0], + h: sizes[0][1] + }; + }, + formatVideoResponse: (bidResponse, emxBid, bidRequest) => { + bidResponse.vastXml = emxBid.adm; + if (bidRequest.bidderRequest && bidRequest.bidderRequest.bids && bidRequest.bidderRequest.bids.length > 0) { + const matchingBid = find(bidRequest.bidderRequest.bids, bid => bidResponse.requestId && bid.bidId && bidResponse.requestId === bid.bidId && bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context === 'outstream'); + if (matchingBid) { + bidResponse.renderer = emxAdapter.createRenderer(bidResponse, { + id: emxBid.id, + url: RENDERER_URL + }); + } + } + return bidResponse; + }, + isMobile: () => { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); + }, + isConnectedTV: () => { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); + }, + getDevice: () => { + return { + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + h: screen.height, + w: screen.width, + devicetype: emxAdapter.isMobile() ? 1 : emxAdapter.isConnectedTV() ? 3 : 2, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + }; + }, + cleanProtocols: (video) => { + if (video.protocols && includes(video.protocols, 7)) { + // not supporting VAST protocol 7 (VAST 4.0); + utils.logWarn(BIDDER_CODE + ': VAST 4.0 is currently not supported. This protocol has been filtered out of the request.'); + video.protocols = video.protocols.filter(protocol => protocol !== 7); + } + return video; + }, + outstreamRender: (bid) => { + bid.renderer.push(function () { + let params = (bid && bid.params && bid.params[0] && bid.params[0].video) ? bid.params[0].video : {}; + window.emxVideoQueue = window.emxVideoQueue || []; + window.queueEmxVideo({ + id: bid.adUnitCode, + adsResponses: bid.vastXml, + options: params + }); + if (window.emxVideoReady && window.videojs) { + window.emxVideoReady(); + } + }); + }, + createRenderer: (bid, rendererParams) => { + const renderer = Renderer.install({ + id: rendererParams.id, + url: RENDERER_URL, + loaded: false + }); + try { + renderer.setRender(emxAdapter.outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; + }, + buildVideo: (bid) => { + let videoObj = Object.assign(bid.mediaTypes.video, bid.params.video); + + if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { + videoObj['w'] = bid.mediaTypes.video.playerSize[0][0]; + videoObj['h'] = bid.mediaTypes.video.playerSize[0][1]; + } else { + videoObj['w'] = bid.mediaTypes.video.playerSize[0]; + videoObj['h'] = bid.mediaTypes.video.playerSize[1]; + } + return emxAdapter.cleanProtocols(videoObj); + }, + parseResponse: (bidResponseAdm) => { + try { + return decodeURIComponent(bidResponseAdm.replace(/%(?![0-9][0-9a-fA-F]+)/g, '%25')); + } catch (err) { + utils.logError('emx_digitalBidAdapter', 'error', err); + } + }, + getReferrer: () => { + try { + return window.top.document.referrer; + } catch (err) { + return document.referrer; + } + }, + getSite: (refInfo) => { + let url = utils.parseUrl(refInfo.referer); + return { + domain: url.hostname, + page: refInfo.referer, + ref: emxAdapter.getReferrer() + } + }, + getGdpr: (bidRequests, emxData) => { + if (bidRequests.gdprConsent) { + emxData.regs = { + ext: { + gdpr: bidRequests.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + } + if (bidRequests.gdprConsent && bidRequests.gdprConsent.gdprApplies) { + emxData.user = { + ext: { + consent: bidRequests.gdprConsent.consentString + } + }; + } + + return emxData; + } +}; + +export const spec = { + code: BIDDER_CODE, + gvlid: 183, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function (bid) { + if (!bid || !bid.params) { + utils.logWarn(BIDDER_CODE + ': Missing bid or bid params.'); + return false; + } + + if (bid.bidder !== BIDDER_CODE) { + utils.logWarn(BIDDER_CODE + ': Must use "emx_digital" as bidder code.'); + return false; + } + + if (!bid.params.tagid || !utils.isStr(bid.params.tagid)) { + utils.logWarn(BIDDER_CODE + ': Missing tagid param or tagid present and not type String.'); + return false; + } + + if (bid.mediaTypes && bid.mediaTypes.banner) { + let sizes; + bid.mediaTypes.banner.sizes ? sizes = bid.mediaTypes.banner.sizes : sizes = bid.sizes; + if (!emxAdapter.validateSizes(sizes)) { + utils.logWarn(BIDDER_CODE + ': Missing sizes in bid'); + return false; + } + } else if (bid.mediaTypes && bid.mediaTypes.video) { + if (!emxAdapter.checkVideoContext(bid)) { + utils.logWarn(BIDDER_CODE + ': Missing video context: instream or outstream'); + return false; + } + + if (!bid.mediaTypes.video.playerSize) { + utils.logWarn(BIDDER_CODE + ': Missing video playerSize'); + return false; + } + } + + return true; + }, + buildRequests: function (validBidRequests, bidderRequest) { + const emxImps = []; + const timeout = bidderRequest.timeout || ''; + const timestamp = Date.now(); + const url = 'https://' + ENDPOINT + ('?t=' + timeout + '&ts=' + timestamp + '&src=pbjs'); + const secure = location.protocol.indexOf('https') > -1 ? 1 : 0; + const device = emxAdapter.getDevice(); + const site = emxAdapter.getSite(bidderRequest.refererInfo); + + utils._each(validBidRequests, function (bid) { + let tagid = utils.getBidIdParameter('tagid', bid.params); + let bidfloor = parseFloat(utils.getBidIdParameter('bidfloor', bid.params)) || 0; + let isVideo = !!bid.mediaTypes.video; + let data = { + id: bid.bidId, + tid: bid.transactionId, + tagid, + secure + }; + let typeSpecifics = isVideo ? { video: emxAdapter.buildVideo(bid) } : { banner: emxAdapter.buildBanner(bid) }; + let bidfloorObj = bidfloor > 0 ? { bidfloor, bidfloorcur: DEFAULT_CUR } : {}; + let emxBid = Object.assign(data, typeSpecifics, bidfloorObj); + + emxImps.push(emxBid); + }); + + let emxData = { + id: bidderRequest.auctionId, + imp: emxImps, + device, + site, + cur: DEFAULT_CUR, + version: ADAPTER_VERSION + }; + + emxData = emxAdapter.getGdpr(bidderRequest, Object.assign({}, emxData)); + if (bidderRequest && bidderRequest.uspConsent) { + emxData.us_privacy = bidderRequest.uspConsent + } + return { + method: 'POST', + url, + data: JSON.stringify(emxData), + options: { + withCredentials: true + }, + bidderRequest + }; + }, + interpretResponse: function (serverResponse, bidRequest) { + let emxBidResponses = []; + let response = serverResponse.body || {}; + if (response.seatbid && response.seatbid.length > 0 && response.seatbid[0].bid) { + response.seatbid.forEach(function (emxBid) { + emxBid = emxBid.bid[0]; + let isVideo = false; + let adm = emxAdapter.parseResponse(emxBid.adm) || ''; + let bidResponse = { + requestId: emxBid.id, + cpm: emxBid.price, + width: emxBid.w, + height: emxBid.h, + creativeId: emxBid.crid || emxBid.id, + dealId: emxBid.dealid || null, + currency: 'USD', + netRevenue: true, + ttl: emxBid.ttl, + ad: adm + }; + if (emxBid.adm && emxBid.adm.indexOf(' -1) { + isVideo = true; + bidResponse = emxAdapter.formatVideoResponse(bidResponse, Object.assign({}, emxBid), bidRequest); + } + bidResponse.mediaType = (isVideo ? VIDEO : BANNER); + emxBidResponses.push(bidResponse); + }); + } + return emxBidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + const syncs = []; + if (syncOptions.iframeEnabled) { + let url = 'https://biddr.brealtime.com/check.html'; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + url += `?gdpr_consent=${gdprConsent.consentString}`; + } + } + syncs.push({ + type: 'iframe', + url: url + }); + } + return syncs; + } +}; +registerBidder(spec); diff --git a/modules/envivoBidAdapter.js b/modules/envivoBidAdapter.js new file mode 100644 index 00000000000..b9c80ffd468 --- /dev/null +++ b/modules/envivoBidAdapter.js @@ -0,0 +1,129 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; +import {Renderer} from '../src/Renderer.js'; + +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BIDDER_CODE = 'envivo'; +const DOMAIN = 'https://ad.nvivo.tv/'; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +function isBidRequestValid(bid) { + return (typeof bid.params !== 'undefined' && parseInt(utils.getValue(bid.params, 'publisherId')) > 0); +} + +function buildRequests(validBidRequests) { + return { + method: 'POST', + url: DOMAIN + 'ads/www/admin/plugins/Prebid/getAd.php', + options: { + withCredentials: false, + crossOrigin: true + }, + data: validBidRequests, + }; +} + +function interpretResponse(serverResponse, request) { + const response = serverResponse.body; + const bidResponses = []; + var bidRequestResponses = []; + + utils._each(response, function(bidAd) { + bidAd.adResponse = { + content: bidAd.vastXml, + height: bidAd.height, + width: bidAd.width + }; + bidAd.ttl = config.getConfig('_bidderTimeout') + bidAd.renderer = bidAd.context === 'outstream' ? createRenderer(bidAd, { + id: bidAd.adUnitCode, + url: RENDERER_URL + }, bidAd.adUnitCode) : undefined; + bidResponses.push(bidAd); + }); + + bidRequestResponses.push({ + function: 'saveResponses', + request: request, + response: bidResponses + }); + sendResponseToServer(bidRequestResponses); + return bidResponses; +} + +function outstreamRender(bidAd) { + bidAd.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bidAd.width, bidAd.height], + width: bidAd.width, + height: bidAd.height, + targetId: bidAd.adUnitCode, + adResponse: bidAd.adResponse, + rendererOptions: { + showVolume: false, + allowFullscreen: false + } + }); + }); +} + +function createRenderer(bidAd, rendererParams, adUnitCode) { + const renderer = Renderer.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false, + config: {'player_height': bidAd.height, 'player_width': bidAd.width}, + adUnitCode + }); + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function onBidWon(bid) { + let wonBids = []; + wonBids.push(bid); + wonBids[0].function = 'onBidWon'; + sendResponseToServer(wonBids); +} + +function onTimeout(details) { + details.unshift({ 'function': 'onTimeout' }); + sendResponseToServer(details); +} + +function sendResponseToServer(data) { + ajax(DOMAIN + 'ads/www/admin/plugins/Prebid/tracking/track.php', null, JSON.stringify(data), { + withCredentials: false, + method: 'POST', + crossOrigin: true + }); +} + +function getUserSyncs(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: DOMAIN + 'ads/www/admin/plugins/Prebid/userSync.php' + }]; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon, + onTimeout +}; + +registerBidder(spec); diff --git a/modules/envivoBidAdapter.md b/modules/envivoBidAdapter.md new file mode 100644 index 00000000000..3ecc8a251f3 --- /dev/null +++ b/modules/envivoBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: envivo Bid Adapter +Module Type: Bidder Adapter +Maintainer : adtech@nvivo.tv +``` + +# Description + +Connects to Envivo Ad Server for bids. + +envivo bid adapter supports Banner and Video. + +# Test Parameters +``` + var adUnits = [ + //bannner object + { + code: 'banner-ad-slot', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'envivo', + params: { + publisherId: 14 + } + }] + + }, + //video object + { + code: 'video-ad-slot', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + }, + }, + bids: [{ + bidder: "envivo", + params: { + publisherId: 14 + } + }] + }]; +``` diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js index 21ecddfbc3a..08db2f2ca9d 100644 --- a/modules/eplanningAnalyticsAdapter.js +++ b/modules/eplanningAnalyticsAdapter.js @@ -1,7 +1,7 @@ -import {ajax} from '../src/ajax'; -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; -import * as utils from '../src/utils'; +import {ajax} from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; const CONSTANTS = require('../src/constants.json'); diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js new file mode 100644 index 00000000000..ac5ba659ad7 --- /dev/null +++ b/modules/eplanningBidAdapter.js @@ -0,0 +1,425 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +const BIDDER_CODE = 'eplanning'; +const rnd = Math.random(); +const DEFAULT_SV = 'ads.us.e-planning.net'; +const DEFAULT_ISV = 'i.e-planning.net'; +const PARAMS = ['ci', 'sv', 't', 'ml']; +const DOLLARS = 'USD'; +const NET_REVENUE = true; +const TTL = 120; +const NULL_SIZE = '1x1'; +const FILE = 'file'; +const STORAGE_RENDER_PREFIX = 'pbsr_'; +const STORAGE_VIEW_PREFIX = 'pbvi_'; + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return Boolean(bid.params.ci) || Boolean(bid.params.t); + }, + + buildRequests: function(bidRequests, bidderRequest) { + const method = 'GET'; + const dfpClientId = '1'; + const sec = 'ROS'; + let url; + let params; + const urlConfig = getUrlConfig(bidRequests); + const pcrs = getCharset(); + const spaces = getSpaces(bidRequests, urlConfig.ml); + const pageUrl = bidderRequest.refererInfo.referer; + const getDomain = (url) => { + let anchor = document.createElement('a'); + anchor.href = url; + return anchor.hostname; + } + if (urlConfig.t) { + url = 'https://' + urlConfig.isv + '/layers/t_pbjs_2.json'; + params = {}; + } else { + url = 'https://' + (urlConfig.sv || DEFAULT_SV) + '/hb/1/' + urlConfig.ci + '/' + dfpClientId + '/' + getDomain(pageUrl) + '/' + sec; + const referrerUrl = bidderRequest.refererInfo.referer.reachedTop ? window.top.document.referrer : bidderRequest.refererInfo.referer; + + if (storage.hasLocalStorage()) { + registerViewabilityAllBids(bidRequests); + } + + params = { + rnd: rnd, + e: spaces.str, + ur: pageUrl || FILE, + r: 'pbjs', + pbv: '$prebid.version$', + ncb: '1', + vs: spaces.vs + }; + if (pcrs) { + params.crs = pcrs; + } + + if (referrerUrl) { + params.fr = referrerUrl; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { + params.gdpr = bidderRequest.gdprConsent.gdprApplies ? '1' : '0'; + if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { + params.gdprcs = bidderRequest.gdprConsent.consentString; + } + } + } + + if (bidderRequest && bidderRequest.uspConsent) { + params.ccpa = bidderRequest.uspConsent; + } + } + + return { + method: method, + url: url, + data: params, + adUnitToBidId: spaces.map, + }; + }, + interpretResponse: function(serverResponse, request) { + const response = serverResponse.body; + let bidResponses = []; + + if (response && !utils.isEmpty(response.sp)) { + response.sp.forEach(space => { + if (!utils.isEmpty(space.a)) { + space.a.forEach(ad => { + const bidResponse = { + requestId: request.adUnitToBidId[space.k], + cpm: ad.pr, + width: ad.w, + height: ad.h, + ad: ad.adm, + ttl: TTL, + creativeId: ad.crid, + netRevenue: NET_REVENUE, + currency: DOLLARS, + }; + bidResponses.push(bidResponse); + }); + } + }); + } + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + const response = !utils.isEmpty(serverResponses) && serverResponses[0].body; + + if (response && !utils.isEmpty(response.cs)) { + const responseSyncs = response.cs; + responseSyncs.forEach(sync => { + if (typeof sync === 'string' && syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: sync, + }); + } else if (typeof sync === 'object' && sync.ifr && syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: sync.u, + }) + } + }); + } + + return syncs; + }, +} + +function getUrlConfig(bidRequests) { + if (isTestRequest(bidRequests)) { + return getTestConfig(bidRequests.filter(br => br.params.t)); + } + + let config = {}; + bidRequests.forEach(bid => { + PARAMS.forEach(param => { + if (bid.params[param] && !config[param]) { + config[param] = bid.params[param]; + } + }); + }); + + return config; +} +function isTestRequest(bidRequests) { + for (let i = 0; i < bidRequests.length; i++) { + if (bidRequests[i].params.t) { + return true; + } + } + return false; +} +function getTestConfig(bidRequests) { + let isv; + bidRequests.forEach(br => isv = isv || br.params.isv); + return { + t: true, + isv: (isv || DEFAULT_ISV) + }; +} + +function getSize(bid, first) { + return bid.sizes && bid.sizes.length ? utils.parseSizesInput(first ? bid.sizes[0] : bid.sizes).join(',') : NULL_SIZE; +} + +function getSpacesStruct(bids) { + let e = {}; + bids.forEach(bid => { + let size = getSize(bid, true); + e[size] = e[size] ? e[size] : []; + e[size].push(bid); + }); + + return e; +} + +function cleanName(name) { + return name.replace(/_|\.|-|\//g, '').replace(/\)\(|\(|\)|:/g, '_').replace(/^_+|_+$/g, ''); +} + +function getSpaces(bidRequests, ml) { + let spacesStruct = getSpacesStruct(bidRequests); + let es = {str: '', vs: '', map: {}}; + es.str = Object.keys(spacesStruct).map(size => spacesStruct[size].map((bid, i) => { + es.vs += getVs(bid); + let name = ml ? cleanName(bid.adUnitCode) : getSize(bid, true) + '_' + i; + es.map[name] = bid.bidId; + return name + ':' + getSize(bid); + }).join('+')).join('+'); + return es; +} + +function getVs(bid) { + let s; + let vs = ''; + if (storage.hasLocalStorage()) { + s = getViewabilityData(bid); + vs += s.render >= 4 ? s.ratio.toString(16) : 'F'; + } else { + vs += 'F'; + } + return vs; +} + +function getViewabilityData(bid) { + let r = storage.getDataFromLocalStorage(STORAGE_RENDER_PREFIX + bid.adUnitCode) || 0; + let v = storage.getDataFromLocalStorage(STORAGE_VIEW_PREFIX + bid.adUnitCode) || 0; + let ratio = r > 0 ? (v / r) : 0; + return { + render: r, + ratio: window.parseInt(ratio * 10, 10) + }; +} + +function getCharset() { + try { + return window.top.document.charset || window.top.document.characterSet; + } catch (e) { + return document.charset || document.characterSet; + } +} + +function waitForElementsPresent(elements) { + const observer = new MutationObserver(function (mutationList, observer) { + if (mutationList && Array.isArray(mutationList)) { + mutationList.forEach(mr => { + if (mr && mr.addedNodes && Array.isArray(mr.addedNodes)) { + mr.addedNodes.forEach(ad => { + let index = elements.indexOf(ad.id); + if (index >= 0) { + registerViewability(ad); + elements.splice(index, 1); + if (!elements.length) { + observer.disconnect(); + } + } + }); + } + }); + } + }); + document.addEventListener('DOMContentLoaded', function (event) { + var config = { + childList: true, + subtree: true, + characterData: true + }; + observer.observe(document.body, config); + }); +} + +function registerViewability(div) { + visibilityHandler({ + name: div.id, + div: div + }); +} + +function registerViewabilityAllBids(bids) { + let elementsNotPresent = []; + bids.forEach(bid => { + let div = document.getElementById(bid.adUnitCode); + if (div) { + registerViewability(div); + } else { + elementsNotPresent.push(bid.adUnitCode); + } + }); + if (elementsNotPresent.length) { + waitForElementsPresent(elementsNotPresent); + } +} + +function getViewabilityTracker() { + let TIME_PARTITIONS = 5; + let VIEWABILITY_TIME = 1000; + let VIEWABILITY_MIN_RATIO = 0.5; + let publicApi; + let context; + + function segmentIsOutsideTheVisibleRange(visibleRangeEnd, p1, p2) { + return p1 > visibleRangeEnd || p2 < 0; + } + + function segmentBeginsBeforeTheVisibleRange(p1) { + return p1 < 0; + } + + function segmentEndsAfterTheVisibleRange(visibleRangeEnd, p2) { + return p2 < visibleRangeEnd; + } + + function axialVisibilityRatio(visibleRangeEnd, p1, p2) { + let visibilityRatio = 0; + if (!segmentIsOutsideTheVisibleRange(visibleRangeEnd, p1, p2)) { + if (segmentBeginsBeforeTheVisibleRange(p1)) { + visibilityRatio = p2 / (p2 - p1); + } else { + visibilityRatio = segmentEndsAfterTheVisibleRange(visibleRangeEnd, p2) ? 1 : (visibleRangeEnd - p1) / (p2 - p1); + } + } + return visibilityRatio; + } + + function isNotHiddenByNonFriendlyIframe() { + try { return (window === window.top) || window.frameElement; } catch (e) {} + } + + function defineContext(e) { + try { + context = e && window.document.body.contains(e) ? window : (window.top.document.body.contains(e) ? top : undefined); + } catch (err) {} + return context; + } + + function getContext(e) { + return context; + } + + function verticalVisibilityRatio(position) { + return axialVisibilityRatio(getContext().innerHeight, position.top, position.bottom); + } + + function horizontalVisibilityRatio(position) { + return axialVisibilityRatio(getContext().innerWidth, position.left, position.right); + } + + function itIsNotHiddenByBannerAreaPosition(e) { + let position = e.getBoundingClientRect(); + return (verticalVisibilityRatio(position) * horizontalVisibilityRatio(position)) > VIEWABILITY_MIN_RATIO; + } + + function itIsNotHiddenByDisplayStyleCascade(e) { + return e.offsetHeight > 0 && e.offsetWidth > 0; + } + + function itIsNotHiddenByOpacityStyleCascade(e) { + let s = e.style; + let p = e.parentNode; + return !(s && parseFloat(s.opacity) === 0) && (!p || itIsNotHiddenByOpacityStyleCascade(p)); + } + + function itIsNotHiddenByVisibilityStyleCascade(e) { + return getContext().getComputedStyle(e).visibility !== 'hidden'; + } + + function itIsNotHiddenByTabFocus() { + try { return getContext().top.document.hasFocus(); } catch (e) {} + } + + function isDefined(e) { + return (e !== null) && (typeof e !== 'undefined'); + } + + function itIsNotHiddenByOrphanBranch() { + return isDefined(getContext()); + } + + function isContextInAnIframe() { + return isDefined(getContext().frameElement); + } + + function processIntervalVisibilityStatus(elapsedVisibleIntervals, element, callback) { + let visibleIntervals = isVisible(element) ? (elapsedVisibleIntervals + 1) : 0; + if (visibleIntervals === TIME_PARTITIONS) { + callback(); + } else { + setTimeout(processIntervalVisibilityStatus.bind(this, visibleIntervals, element, callback), VIEWABILITY_TIME / TIME_PARTITIONS); + } + } + + function isVisible(element) { + defineContext(element); + return isNotHiddenByNonFriendlyIframe() && + itIsNotHiddenByOrphanBranch() && + itIsNotHiddenByTabFocus() && + itIsNotHiddenByDisplayStyleCascade(element) && + itIsNotHiddenByVisibilityStyleCascade(element) && + itIsNotHiddenByOpacityStyleCascade(element) && + itIsNotHiddenByBannerAreaPosition(element) && + (!isContextInAnIframe() || isVisible(getContext().frameElement)); + } + + publicApi = { + isVisible: isVisible, + onView: processIntervalVisibilityStatus.bind(this, 0) + }; + + return publicApi; +}; + +function visibilityHandler(obj) { + if (obj.div) { + registerAuction(STORAGE_RENDER_PREFIX + obj.name); + getViewabilityTracker().onView(obj.div, registerAuction.bind(undefined, STORAGE_VIEW_PREFIX + obj.name)); + } +} + +function registerAuction(storageID) { + let value; + try { + value = storage.getDataFromLocalStorage(storageID); + value = value ? window.parseInt(value, 10) + 1 : 1; + storage.setDataInLocalStorage(storageID, value); + } catch (exc) { + return false; + } + + return true; +} +registerBidder(spec); diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index ad341a136b1..5e07561044a 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -1,7 +1,7 @@ 'use strict'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'etarget'; const countryMap = { diff --git a/modules/express.js b/modules/express.js index 1249822f587..f4a76daefdf 100644 --- a/modules/express.js +++ b/modules/express.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; +import * as utils from '../src/utils.js'; const MODULE_NAME = 'express'; @@ -61,6 +61,7 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { function defaultSlots(slots) { return Array.isArray(slots) ? slots.slice() + // eslint-disable-next-line no-undef : googletag.pubads().getSlots().slice(); } @@ -116,6 +117,7 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { // if not SRA mode, get only the gpt slot corresponding to sEementId var aGptSlots; if (!bEnabledSRA) { + // eslint-disable-next-line no-undef aGptSlots = googletag.pubads().getSlots().filter(function (oGptSlot) { return oGptSlot.getSlotElementId() === sElementId; }); diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 1e995ee8914..3992f2db5e0 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {BANNER, VIDEO} from '../src/mediaTypes'; -import {ajax} from '../src/ajax'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {ajax} from '../src/ajax.js'; /** * Version of the FeedAd bid adapter diff --git a/modules/fidelityBidAdapter.js b/modules/fidelityBidAdapter.js new file mode 100644 index 00000000000..baf5384fbfe --- /dev/null +++ b/modules/fidelityBidAdapter.js @@ -0,0 +1,147 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'fidelity'; +const BIDDER_SERVER = 'x.fidelity-media.com'; +const FIDELITY_VENDOR_ID = 408; +export const spec = { + code: BIDDER_CODE, + aliases: ['kubient'], + gvlid: 408, + isBidRequestValid: function isBidRequestValid(bid) { + return !!(bid && bid.params && bid.params.zoneid); + }, + buildRequests: function buildRequests(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + var server = bidRequest.params.server || BIDDER_SERVER; + + const payload = { + from: 'hb', + v: '1.0', + requestid: bidRequest.bidderRequestId, + impid: bidRequest.bidId, + zoneid: bidRequest.params.zoneid, + floor: parseFloat(bidRequest.params.floor) > 0 ? bidRequest.params.floor : 0, + charset: document.charSet || document.characterSet, + subid: 'hb', + flashver: getFlashVersion(), + tmax: bidderRequest.timeout, + defloc: bidderRequest.refererInfo.referer, + referrer: getTopWindowReferrer(), + schain: getSupplyChain(bidRequest.schain), + }; + setConsentParams(bidderRequest.gdprConsent, bidderRequest.uspConsent, payload); + + return { + method: 'GET', + url: 'https://' + server + '/delivery/hb.php', + data: payload + }; + }); + }, + interpretResponse: function interpretResponse(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.width, + height: bid.height, + ad: bid.adm, + netRevenue: bid.netRevenue, + currency: bid.cur, + ttl: bid.ttl, + }; + + bidResponses.push(bidResponse); + })); + } + return bidResponses; + }, + getUserSyncs: function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled) { + var url = 'https://' + BIDDER_SERVER + '/delivery/matches.php'; + var payload = { + type: 'iframe' + }; + setConsentParams(gdprConsent, uspConsent, payload); + + return [{ + type: 'iframe', + url: url + '?' + utils.parseQueryStringParameters(payload).replace(/\&$/, '') + }]; + } + } +} + +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 || ''; +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +function setConsentParams(gdprConsent, uspConsent, payload) { + if (gdprConsent) { + payload.gdpr = 0; + payload.consent_str = ''; + payload.consent_given = 0; + if (typeof gdprConsent.gdprApplies !== 'undefined') { + payload.gdpr = gdprConsent.gdprApplies ? 1 : 0; + } + if (typeof gdprConsent.consentString !== 'undefined') { + payload.consent_str = gdprConsent.consentString; + } + if (gdprConsent.apiVersion === 1 && gdprConsent.vendorData && gdprConsent.vendorData.vendorConsents && typeof gdprConsent.vendorData.vendorConsents[FIDELITY_VENDOR_ID.toString(10)] !== 'undefined') { + payload.consent_given = gdprConsent.vendorData.vendorConsents[FIDELITY_VENDOR_ID.toString(10)] ? 1 : 0; + } + if (gdprConsent.apiVersion === 2 && gdprConsent.vendorData && gdprConsent.vendorData.vendor && gdprConsent.vendorData.vendor.consents && typeof gdprConsent.vendorData.vendor.consents[FIDELITY_VENDOR_ID.toString(10)] !== 'undefined') { + payload.consent_given = gdprConsent.vendorData.vendor.consents[FIDELITY_VENDOR_ID.toString(10)] ? 1 : 0; + } + } + if (typeof uspConsent !== 'undefined') { + payload.us_privacy = uspConsent; + } +} + +function getSupplyChain(schain) { + var supplyChain = ''; + if (schain != null && schain.nodes) { + supplyChain = schain.ver + ',' + schain.complete; + for (let i = 0; i < schain.nodes.length; i++) { + supplyChain += '!'; + supplyChain += (schain.nodes[i].asi) ? encodeURIComponent(schain.nodes[i].asi) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].sid) ? encodeURIComponent(schain.nodes[i].sid) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].hp) ? encodeURIComponent(schain.nodes[i].hp) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].rid) ? encodeURIComponent(schain.nodes[i].rid) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].name) ? encodeURIComponent(schain.nodes[i].name) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].domain) ? encodeURIComponent(schain.nodes[i].domain) : ''; + } + } + return supplyChain; +} +registerBidder(spec); diff --git a/modules/fidelityBidAdapter.md b/modules/fidelityBidAdapter.md index a4f1e91cd3d..0af75689bd6 100644 --- a/modules/fidelityBidAdapter.md +++ b/modules/fidelityBidAdapter.md @@ -1,18 +1,22 @@ # Overview - +​ **Module Name**: Fidelity Media fmxSSP Bidder Adapter **Module Type**: Bidder Adapter **Maintainer**: on@fidelity-media.com - +​ # Description - +​ Connects to Fidelity Media fmxSSP demand source to fetch bids. - +​ # Test Parameters -``` +``` var adUnits = [{ code: 'banner-ad-div', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, bids: [{ bidder: 'fidelity', params: { @@ -23,4 +27,4 @@ Connects to Fidelity Media fmxSSP demand source to fetch bids. }] }]; -``` +``` \ No newline at end of file diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js index a1376c28427..8729035491f 100644 --- a/modules/fintezaAnalyticsAdapter.js +++ b/modules/fintezaAnalyticsAdapter.js @@ -1,9 +1,10 @@ -import { ajax } from '../src/ajax'; -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; -import * as utils from '../src/utils'; -import { parse as parseURL } from '../src/url'; +import { ajax } from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +const storage = getStorageManager(); const CONSTANTS = require('../src/constants.json'); const ANALYTICS_TYPE = 'endpoint'; @@ -27,7 +28,7 @@ function getPageInfo() { } if (document.referrer) { - pageInfo.referrerDomain = parseURL(document.referrer).hostname; + pageInfo.referrerDomain = utils.parseUrl(document.referrer).hostname; } return pageInfo; @@ -46,8 +47,8 @@ function getUniqId() { let uniq = cookies[ UNIQ_ID_KEY ]; if (!uniq) { try { - if (window.localStorage) { - uniq = window.localStorage.getItem(UNIQ_ID_KEY) || ''; + if (storage.hasLocalStorage()) { + uniq = storage.getDataFromLocalStorage(UNIQ_ID_KEY) || ''; isUniqFromLS = true; } } catch (b) {} @@ -62,7 +63,7 @@ function getUniqId() { expires.setFullYear(expires.getFullYear() + 10); try { - document.cookie = UNIQ_ID_KEY + '=' + uniq + '; path=/; expires=' + expires.toUTCString(); + storage.setCookie(UNIQ_ID_KEY, uniq, expires.toUTCString()); } catch (e) {} } @@ -90,7 +91,7 @@ function initFirstVisit() { now.setFullYear(now.getFullYear() + 20); try { - document.cookie = FIRST_VISIT_DATE + '=' + visitDate + '; path=/; expires=' + now.toUTCString(); + storage.setCookie(FIRST_VISIT_DATE, visitDate, now.toUTCString()); } catch (e) {} } @@ -110,7 +111,7 @@ function parseCookies(cookie) { let param, value; let i, j; - if (!cookie) { + if (!cookie || !storage.cookiesAreEnabled()) { return {}; } @@ -203,7 +204,7 @@ function initSession() { } try { - document.cookie = SESSION_ID + '=' + sessionId + '; path=/; expires=' + expires.toUTCString(); + storage.setCookie(SESSION_ID, sessionId, expires.toUTCString()); } catch (e) {} return { @@ -249,10 +250,10 @@ function saveTrackRequestTime() { const expires = new Date(now + SESSION_DURATION); try { - if (window.localStorage) { - window.localStorage.setItem(TRACK_TIME_KEY, now.toString()); + if (storage.hasLocalStorage()) { + storage.setDataInLocalStorage(TRACK_TIME_KEY, now.toString()); } else { - document.cookie = TRACK_TIME_KEY + '=' + now + '; path=/; expires=' + expires.toUTCString(); + storage.setCookie(TRACK_TIME_KEY, now.toString(), expires.toUTCString()); } } catch (a) {} } @@ -261,9 +262,9 @@ function getTrackRequestLastTime() { let cookie; try { - if (window.localStorage) { + if (storage.hasLocalStorage()) { return parseInt( - window.localStorage.getItem(TRACK_TIME_KEY) || 0, + storage.getDataFromLocalStorage(TRACK_TIME_KEY) || 0, 10, ); } diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js new file mode 100644 index 00000000000..420fe04ddcb --- /dev/null +++ b/modules/fluctBidAdapter.js @@ -0,0 +1,121 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'fluct'; +const END_POINT = 'https://hb.adingo.jp/prebid'; +const VERSION = '1.2'; +const NET_REVENUE = true; +const TTL = 300; + +export const spec = { + code: BIDDER_CODE, + aliases: ['adingo'], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return !!(bid.params.groupId && bid.params.tagId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + const serverRequests = []; + const referer = bidderRequest.refererInfo.referer; + + utils._each(validBidRequests, (request) => { + const data = Object(); + + data.referer = referer; + data.adUnitCode = request.adUnitCode; + data.bidId = request.bidId; + data.transactionId = request.transactionId; + + data.sizes = []; + utils._each(request.sizes, (size) => { + data.sizes.push({ + w: size[0], + h: size[1] + }); + }); + + data.params = request.params; + + serverRequests.push({ + method: 'POST', + url: END_POINT, + options: { + contentType: 'application/json', + withCredentials: true, + customHeaders: { + 'x-fluct-app': 'prebid/fluctBidAdapter', + 'x-fluct-version': VERSION, + 'x-openrtb-version': 2.5 + } + }, + data: data + }); + }); + + return serverRequests; + }, + + /* + * Unpack the respnse from the server into a list of bids. + * + * @param {serverResponse} serverResponse A successful response from the server. + * @return {bid[]} An array of bids which weer nested inside the server. + */ + interpretResponse: (serverResponse, serverRequest) => { + const bidResponses = []; + + const res = serverResponse.body; + if (!utils.isEmpty(res) && !utils.isEmpty(res.seatbid) && !utils.isEmpty(res.seatbid[0].bid)) { + const bid = res.seatbid[0].bid[0]; + const dealId = bid.dealid; + const beaconUrl = bid.burl; + const callImpBeacon = ``; + let data = { + bidderCode: BIDDER_CODE, + requestId: res.id, + currency: res.cur, + cpm: parseFloat(bid.price) || 0, + netRevenue: NET_REVENUE, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + ttl: TTL, + ad: bid.adm + callImpBeacon, + }; + if (!utils.isEmpty(dealId)) { + data.dealId = dealId; + } + bidResponses.push(data); + } + return bidResponses; + }, + + /* + * Register the user sync pixels which should be dropped after the auction. + * + * @params {syncOptions} syncOptions which user syncs are allowed? + * @params {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + * + */ + getUserSyncs: (syncOptions, serverResponses) => { + return []; + }, +}; + +registerBidder(spec); diff --git a/modules/fluctBidAdapter.md b/modules/fluctBidAdapter.md new file mode 100644 index 00000000000..a1dc4d6f225 --- /dev/null +++ b/modules/fluctBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: fluct Bid Adapter +Module Type: Bidder Adapter +Maintainer: developer@fluct.jp +``` + +# Description + +Connects to fluct exchange for bids. + +# Test parameters + +``` +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'fluct', + params: { + tagId: '25405:1000192893', + groupId: '1000105712', + dfpUnitCode: '/62532913/s_fluct.test_hb_prebid_11940', // Optional + } + } + ] + } +] +``` diff --git a/modules/freeWheelAdserverVideo.js b/modules/freeWheelAdserverVideo.js index 03217b1165d..cb4bd938373 100644 --- a/modules/freeWheelAdserverVideo.js +++ b/modules/freeWheelAdserverVideo.js @@ -2,8 +2,8 @@ * This module adds Freewheel support for Video to Prebid. */ -import { registerVideoSupport } from '../src/adServerManager'; -import { getHook, submodule } from '../src/hook'; +import { registerVideoSupport } from '../src/adServerManager.js'; +import { getHook, submodule } from '../src/hook.js'; export const adpodUtils = {}; export function notifyTranslationModule(fn) { diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js new file mode 100644 index 00000000000..dce678362cb --- /dev/null +++ b/modules/freewheel-sspBidAdapter.js @@ -0,0 +1,422 @@ +import * as utils from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'freewheel-ssp'; + +const PROTOCOL = getProtocol(); +const FREEWHEEL_ADSSETUP = PROTOCOL + '://ads.stickyadstv.com/www/delivery/swfIndex.php'; +const MUSTANG_URL = PROTOCOL + '://cdn.stickyadstv.com/mustang/mustang.min.js'; +const PRIMETIME_URL = PROTOCOL + '://cdn.stickyadstv.com/prime-time/'; +const USER_SYNC_URL = PROTOCOL + '://ads.stickyadstv.com/auto-user-sync'; + +function getProtocol() { + return 'https'; +} + +function isValidUrl(str) { + if (!str) { + return false; + } + + // regExp for url validation + var pattern = /^(https?|ftp|file):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/; + return pattern.test(str); +} + +function getBiggerSize(array) { + var result = [0, 0]; + for (var i = 0; i < array.length; i++) { + if (array[i][0] * array[i][1] > result[0] * result[1]) { + result = array[i]; + } + } + return result; +} + +function getBiggerSizeWithLimit(array, minSizeLimit, maxSizeLimit) { + var minSize = minSizeLimit || [0, 0]; + var maxSize = maxSizeLimit || [Number.MAX_VALUE, Number.MAX_VALUE]; + var candidates = []; + + for (var i = 0; i < array.length; i++) { + if (array[i][0] * array[i][1] >= minSize[0] * minSize[1] && array[i][0] * array[i][1] <= maxSize[0] * maxSize[1]) { + candidates.push(array[i]); + } + } + + return getBiggerSize(candidates); +} + +/* +* read the pricing extension with this format: 1.0000 +* @return {object} pricing data in format: {currency: "EUR", price:"1.000"} +*/ +function getPricing(xmlNode) { + var pricingExtNode; + var princingData = {}; + + var extensions = xmlNode.querySelectorAll('Extension'); + // Nodelist.forEach is not supported in IE and Edge + // Workaround given here https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10638731/ + Array.prototype.forEach.call(extensions, function(node) { + if (node.getAttribute('type') === 'StickyPricing') { + pricingExtNode = node; + } + }); + + if (pricingExtNode) { + var priceNode = pricingExtNode.querySelector('Price'); + princingData = { + currency: priceNode.getAttribute('currency'), + price: priceNode.textContent || priceNode.innerText + }; + } else { + utils.logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing pricing extension.'); + } + + return princingData; +} + +function hashcode(inputString) { + var hash = 0; + var char; + if (inputString.length == 0) return hash; + for (var i = 0; i < inputString.length; i++) { + char = inputString.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +} + +function getCreativeId(xmlNode) { + var creaId = ''; + var adNodes = xmlNode.querySelectorAll('Ad'); + // Nodelist.forEach is not supported in IE and Edge + // Workaround given here https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10638731/ + Array.prototype.forEach.call(adNodes, function(el) { + creaId += '[' + el.getAttribute('id') + ']'; + }); + + return creaId; +} + +function getDealId(xmlNode) { + var dealId = ''; + var impNodes = xmlNode.querySelectorAll('Impression'); // Nodelist.forEach is not supported in IE and Edge + // Workaround given here https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10638731/ + + Array.prototype.forEach.call(impNodes, function (el) { + var queries = el.textContent.substring(el.textContent.indexOf('?') + 1).split('&'); + Array.prototype.forEach.call(queries, function (item) { + var split = item.split('='); + if (split[0] == 'dealId') { + dealId = split[1]; + } + }); + }); + + return dealId; +} + +/** +* 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; +} + +function getComponentId(inputFormat) { + var component = 'mustang'; // default component id + + if (inputFormat && inputFormat !== 'inbanner') { + // format identifiers are equals to their component ids. + component = inputFormat; + } + + return component; +} + +function getAPIName(componentId) { + componentId = componentId || ''; + + // remove dash in componentId to get API name + return componentId.replace('-', ''); +} + +function formatAdHTML(bid, size) { + var integrationType = bid.params.format; + + var divHtml = '
'; + + var script = ''; + var libUrl = ''; + if (integrationType && integrationType !== 'inbanner') { + libUrl = PRIMETIME_URL + getComponentId(bid.params.format) + '.min.js'; + script = getOutstreamScript(bid, size); + } else { + libUrl = MUSTANG_URL; + script = getInBannerScript(bid, size); + } + + return divHtml + + ''; +} + +var getInBannerScript = function(bid, size) { + return 'var config = {' + + ' preloadedVast:vast,' + + ' autoPlay:true' + + ' };' + + ' var ad = new window.com.stickyadstv.vpaid.Ad(document.getElementById("freewheelssp_prebid_target"),config);' + + ' (new window.com.stickyadstv.tools.ASLoader(' + bid.params.zoneId + ', \'' + getComponentId(bid.params.format) + '\')).registerEvents(ad);' + + ' ad.initAd(' + size[0] + ',' + size[1] + ',"",0,"","");'; +}; + +var getOutstreamScript = function(bid) { + var config = bid.params; + + // default placement if no placement is set + if (!config.hasOwnProperty('domId') && !config.hasOwnProperty('auto') && !config.hasOwnProperty('p') && !config.hasOwnProperty('article')) { + if (config.format === 'intext-roll') { + config.iframeMode = 'dfp'; + } else { + config.domId = 'freewheelssp_prebid_target'; + } + } + + var script = 'var config = {' + + ' preloadedVast:vast,' + + ' ASLoader:new window.com.stickyadstv.tools.ASLoader(' + bid.params.zoneId + ', \'' + getComponentId(bid.params.format) + '\')'; + + for (var key in config) { + // dont' send format parameter + // neither zone nor vastUrlParams value as Vast is already loaded + if (config.hasOwnProperty(key) && key !== 'format' && key !== 'zone' && key !== 'zoneId' && key !== 'vastUrlParams') { + script += ',' + key + ':"' + config[key] + '"'; + } + } + script += '};' + + + 'window.com.stickyadstv.' + getAPIName(bid.params.format) + '.start(config);'; + + return script; +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + aliases: ['stickyadstv'], // former name for freewheel-ssp + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.zoneId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + // var currency = config.getConfig(currency); + + let buildRequest = (currentBidRequest, bidderRequest) => { + var zone = currentBidRequest.params.zoneId; + var timeInMillis = new Date().getTime(); + var keyCode = hashcode(zone + '' + timeInMillis); + var requestParams = { + reqType: 'AdsSetup', + protocolVersion: '2.0', + zoneId: zone, + componentId: 'prebid', + componentSubId: getComponentId(currentBidRequest.params.format), + timestamp: timeInMillis, + pKey: keyCode + }; + + // Add GDPR flag and consent string + if (bidderRequest && bidderRequest.gdprConsent) { + requestParams._fw_gdpr_consent = bidderRequest.gdprConsent.consentString; + + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + requestParams._fw_gdpr = bidderRequest.gdprConsent.gdprApplies; + } + } + + if (currentBidRequest.params.gdpr_consented_providers) { + requestParams._fw_gdpr_consented_providers = currentBidRequest.params.gdpr_consented_providers; + } + + // Add CCPA consent string + if (bidderRequest && bidderRequest.uspConsent) { + requestParams._fw_us_privacy = bidderRequest.uspConsent; + } + + var vastParams = currentBidRequest.params.vastUrlParams; + if (typeof vastParams === 'object') { + for (var key in vastParams) { + if (vastParams.hasOwnProperty(key)) { + requestParams[key] = vastParams[key]; + } + } + } + + var location = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : getTopMostWindow().location.href; + if (isValidUrl(location)) { + requestParams.loc = location; + } + + var playerSize = []; + if (currentBidRequest.mediaTypes.video && currentBidRequest.mediaTypes.video.playerSize) { + // If mediaTypes is video, get size from mediaTypes.video.playerSize per http://prebid.org/blog/pbjs-3 + if (utils.isArray(currentBidRequest.mediaTypes.video.playerSize[0])) { + playerSize = currentBidRequest.mediaTypes.video.playerSize[0]; + } else { + playerSize = currentBidRequest.mediaTypes.video.playerSize; + } + } else if (currentBidRequest.mediaTypes.banner.sizes) { + // If mediaTypes is banner, get size from mediaTypes.banner.sizes per http://prebid.org/blog/pbjs-3 + playerSize = getBiggerSizeWithLimit(currentBidRequest.mediaTypes.banner.sizes, currentBidRequest.mediaTypes.banner.minSizeLimit, currentBidRequest.mediaTypes.banner.maxSizeLimit); + } else { + // Backward compatible code, in case size still pass by sizes in bid request + playerSize = getBiggerSize(currentBidRequest.sizes); + } + + if (playerSize[0] > 0 || playerSize[1] > 0) { + requestParams.playerSize = playerSize[0] + 'x' + playerSize[1]; + } + + return { + method: 'GET', + url: FREEWHEEL_ADSSETUP, + data: requestParams, + bidRequest: currentBidRequest + }; + }; + + return bidRequests.map(function(currentBidRequest) { + return buildRequest(currentBidRequest, bidderRequest); + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {object} request: the built request object containing the initial bidRequest. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, request) { + var bidrequest = request.bidRequest; + var playerSize = []; + if (bidrequest.mediaTypes.video && bidrequest.mediaTypes.video.playerSize) { + // If mediaTypes is video, get size from mediaTypes.video.playerSize per http://prebid.org/blog/pbjs-3 + if (utils.isArray(bidrequest.mediaTypes.video.playerSize[0])) { + playerSize = bidrequest.mediaTypes.video.playerSize[0]; + } else { + playerSize = bidrequest.mediaTypes.video.playerSize; + } + } else if (bidrequest.mediaTypes.banner.sizes) { + // If mediaTypes is banner, get size from mediaTypes.banner.sizes per http://prebid.org/blog/pbjs-3 + playerSize = getBiggerSizeWithLimit(bidrequest.mediaTypes.banner.sizes, bidrequest.mediaTypes.banner.minSizeLimit, bidrequest.mediaTypes.banner.maxSizeLimit); + } else { + // Backward compatible code, in case size still pass by sizes in bid request + playerSize = getBiggerSize(bidrequest.sizes); + } + + if (typeof serverResponse == 'object' && typeof serverResponse.body == 'string') { + serverResponse = serverResponse.body; + } + + var xmlDoc; + try { + var parser = new DOMParser(); + xmlDoc = parser.parseFromString(serverResponse, 'application/xml'); + } catch (err) { + utils.logWarn('Prebid.js - ' + BIDDER_CODE + ' : ' + err); + return; + } + + const princingData = getPricing(xmlDoc); + const creativeId = getCreativeId(xmlDoc); + const dealId = getDealId(xmlDoc); + + const topWin = getTopMostWindow(); + if (!topWin.freewheelssp_cache) { + topWin.freewheelssp_cache = {}; + } + topWin.freewheelssp_cache[bidrequest.adUnitCode] = serverResponse; + + const bidResponses = []; + + if (princingData.price) { + const bidResponse = { + requestId: bidrequest.bidId, + cpm: princingData.price, + width: playerSize[0], + height: playerSize[1], + creativeId: creativeId, + currency: princingData.currency, + netRevenue: true, + ttl: 360, + dealId: dealId + }; + + if (bidrequest.mediaTypes.video) { + bidResponse.vastXml = serverResponse; + bidResponse.mediaType = 'video'; + } + + bidResponse.ad = formatAdHTML(bidrequest, playerSize); + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions && syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: USER_SYNC_URL + }]; + } else { + return []; + } + }, + +} +registerBidder(spec); diff --git a/modules/freewheel-sspBidAdapter.md b/modules/freewheel-sspBidAdapter.md index 70ab2415279..0086aac6567 100644 --- a/modules/freewheel-sspBidAdapter.md +++ b/modules/freewheel-sspBidAdapter.md @@ -13,15 +13,21 @@ Module that connects to Freewheel ssp's demand sources var adUnits = [ { code: 'test-div', - sizes: [[300, 250]], // a display size + + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ { bidder: "freewheel-ssp", params: { - zoneId : '41852' + zoneId : '277225' } } ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/gammaBidAdapter.js b/modules/gammaBidAdapter.js new file mode 100644 index 00000000000..5fd3c56b2c6 --- /dev/null +++ b/modules/gammaBidAdapter.js @@ -0,0 +1,101 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const ENDPOINT = 'https://hb.gammaplatform.com'; +const ENDPOINT_USERSYNC = 'https://cm-supply-web.gammaplatform.com'; +const BIDDER_CODE = 'gamma'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['gamma'], + supportedMediaTypes: ['banner', 'video'], + + /** + * 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.siteId || bid.params.zoneId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const serverRequests = []; + const bidderRequestReferer = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || ''; + for (var i = 0, len = bidRequests.length; i < len; i++) { + const gaxObjParams = bidRequests[i]; + serverRequests.push({ + method: 'GET', + url: ENDPOINT + '/adx/request?wid=' + gaxObjParams.params.siteId + '&zid=' + gaxObjParams.params.zoneId + '&hb=pbjs&bidid=' + gaxObjParams.bidId + '&urf=' + encodeURIComponent(bidderRequestReferer) + }); + } + return serverRequests; + }, + + /** + * 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) { + serverResponse = serverResponse.body; + + const bids = []; + + if (serverResponse.id) { + const bid = newBid(serverResponse); + bids.push(bid); + } + + return bids; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: ENDPOINT_USERSYNC + '/adx/usersync' + }]; + } + } +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @return Bid + */ +function newBid(serverBid) { + const bid = { + ad: serverBid.seatbid[0].bid[0].adm, + cpm: serverBid.seatbid[0].bid[0].price, + creativeId: serverBid.seatbid[0].bid[0].adid, + currency: serverBid.cur, + dealId: serverBid.seatbid[0].bid[0].dealid, + width: serverBid.seatbid[0].bid[0].w, + height: serverBid.seatbid[0].bid[0].h, + mediaType: serverBid.type, + netRevenue: true, + requestId: serverBid.id, + ttl: serverBid.seatbid[0].bid[0].ttl || 300 + }; + + if (serverBid.type == 'video') { + Object.assign(bid, { + vastXml: serverBid.seatbid[0].bid[0].vastXml, + vastUrl: serverBid.seatbid[0].bid[0].vastUrl, + ttl: 3600 + }); + } + + return bid; +} + +registerBidder(spec); diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js new file mode 100644 index 00000000000..1316d74e430 --- /dev/null +++ b/modules/gamoshiBidAdapter.js @@ -0,0 +1,332 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {Renderer} from '../src/Renderer.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +const ENDPOINTS = { + 'gamoshi': 'https://rtb.gamoshi.io' +}; + +const DEFAULT_TTL = 360; + +export const helper = { + getTopFrame: function () { + try { + return window.top === window ? 1 : 0; + } catch (e) { + } + return 0; + }, + startsWith: function (str, search) { + return str.substr(0, search.length) === search; + }, + getTopWindowDomain: function (url) { + const domainStart = url.indexOf('://') + '://'.length; + return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)); + }, + + getMediaType: function (bid) { + if (bid.ext) { + if (bid.ext.media_type) { + return bid.ext.media_type.toLowerCase(); + } else if (bid.ext.vast_url) { + return VIDEO; + } else { + return BANNER; + } + } + return BANNER; + } +}; + +export const spec = { + code: 'gamoshi', + aliases: ['gambid', 'cleanmedia', '9MediaOnline'], + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + return !!bid.params.supplyPartnerId && utils.isStr(bid.params.supplyPartnerId) && + (!bid.params['rtbEndpoint'] || utils.isStr(bid.params['rtbEndpoint'])) && + (!bid.params.bidfloor || utils.isNumber(bid.params.bidfloor)) && + (!bid.params['adpos'] || utils.isNumber(bid.params['adpos'])) && + (!bid.params['protocols'] || Array.isArray(bid.params['protocols'])) && + (!bid.params.instl || bid.params.instl === 0 || bid.params.instl === 1); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const {adUnitCode, auctionId, mediaTypes, params, sizes, transactionId} = bidRequest; + const baseEndpoint = params['rtbEndpoint'] || ENDPOINTS['gamoshi']; + const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : ''); + let url = config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; + + const rtbBidRequest = { + id: auctionId, + site: { + domain: helper.getTopWindowDomain(url), + page: url, + ref: bidderRequest.refererInfo.referer + }, + device: { + ua: navigator.userAgent, + dnt: utils.getDNT() ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + }, + imp: [], + ext: {}, + user: {ext: {}}, + source: {ext: {}}, + regs: {ext: {}} + }; + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) { + rtbBidRequest.ext.gdpr_consent = { + consent_string: gdprConsent.consentString, + consent_required: gdprConsent.gdprApplies + }; + + utils.deepSetValue(rtbBidRequest, 'regs.ext.gdpr', gdprConsent.gdprApplies === true ? 1 : 0); + utils.deepSetValue(rtbBidRequest, 'user.ext.consent', gdprConsent.consentString); + } + + if (validBidRequests[0].schain) { + utils.deepSetValue(rtbBidRequest, 'source.ext.schain', validBidRequests[0].schain); + } + + if (bidderRequest && bidderRequest.uspConsent) { + utils.deepSetValue(rtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + const imp = { + id: transactionId, + instl: params.instl === 1 ? 1 : 0, + tagid: adUnitCode, + bidfloor: params.bidfloor || 0, + bidfloorcur: 'USD', + secure: 1 + }; + + const hasFavoredMediaType = + params.favoredMediaType && this.supportedMediaTypes.includes(params.favoredMediaType); + + if (!mediaTypes || mediaTypes.banner) { + if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { + const bannerImp = Object.assign({}, imp, { + banner: { + w: sizes.length ? sizes[0][0] : 300, + h: sizes.length ? sizes[0][1] : 250, + pos: params.pos || 0, + topframe: utils.inIframe() ? 0 : 1 + } + }); + rtbBidRequest.imp.push(bannerImp); + } + } + + if (mediaTypes && mediaTypes.video) { + if (!hasFavoredMediaType || params.favoredMediaType === VIDEO) { + const playerSize = mediaTypes.video.playerSize || sizes; + const videoImp = Object.assign({}, imp, { + video: { + protocols: params.protocols || [1, 2, 3, 4, 5, 6], + pos: params.pos || 0, + ext: { + context: mediaTypes.video.context + } + } + }); + + if (utils.isArray(playerSize[0])) { + videoImp.video.w = playerSize[0][0]; + videoImp.video.h = playerSize[0][1]; + } else if (utils.isNumber(playerSize[0])) { + videoImp.video.w = playerSize[0]; + videoImp.video.h = playerSize[1]; + } else { + videoImp.video.w = 300; + videoImp.video.h = 250; + } + + rtbBidRequest.imp.push(videoImp); + } + } + + let eids = []; + if (bidRequest && bidRequest.userId) { + addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 'ID5ID'); + addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID'); + } + if (eids.length > 0) { + rtbBidRequest.user.ext.eids = eids; + } + + if (rtbBidRequest.imp.length === 0) { + return; + } + + return { + method: 'POST', + url: rtbEndpoint, + data: rtbBidRequest, + bidRequest + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const response = serverResponse && serverResponse.body; + if (!response) { + utils.logError('empty response'); + return []; + } + + const bids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); + let outBids = []; + + bids.forEach(bid => { + const outBid = { + requestId: bidRequest.bidRequest.bidId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: DEFAULT_TTL, + creativeId: bid.crid || bid.adid, + netRevenue: true, + currency: bid.cur || response.cur, + mediaType: helper.getMediaType(bid) + }; + + if (utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.' + outBid.mediaType)) { + if (outBid.mediaType === BANNER) { + outBids.push(Object.assign({}, outBid, {ad: bid.adm})); + } else if (outBid.mediaType === VIDEO) { + const context = utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.video.context'); + outBids.push(Object.assign({}, outBid, { + vastUrl: bid.ext.vast_url, + vastXml: bid.adm, + renderer: context === 'outstream' ? newRenderer(bidRequest.bidRequest, bid) : undefined + })); + } + } + }); + return outBids; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + let gdprApplies = false; + let consentString = ''; + let uspConsentString = ''; + + if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { + gdprApplies = gdprConsent.gdprApplies; + } + let gdpr = gdprApplies ? 1 : 0; + + if (gdprApplies && gdprConsent.consentString) { + consentString = encodeURIComponent(gdprConsent.consentString) + } + + if (uspConsent) { + uspConsentString = encodeURIComponent(uspConsent); + } + + const macroValues = { + gdpr: gdpr, + consent: consentString, + uspConsent: uspConsentString + }; + + serverResponses.forEach(resp => { + if (resp.body) { + const bidResponse = resp.body; + if (bidResponse.ext && Array.isArray(bidResponse.ext['utrk'])) { + bidResponse.ext['utrk'] + .forEach(pixel => { + const url = replaceMacros(pixel.url, macroValues); + syncs.push({type: pixel.type, url}); + }); + } + + if (Array.isArray(bidResponse.seatbid)) { + bidResponse.seatbid.forEach(seatBid => { + if (Array.isArray(seatBid.bid)) { + seatBid.bid.forEach(bid => { + if (bid.ext && Array.isArray(bid.ext['utrk'])) { + bid.ext['utrk'] + .forEach(pixel => { + const url = replaceMacros(pixel.url, macroValues); + syncs.push({type: pixel.type, url}); + }); + } + }); + } + }); + } + } + }); + + return syncs; + } +}; + +function newRenderer(bidRequest, bid, rendererOptions = {}) { + const renderer = Renderer.install({ + url: (bidRequest.params && bidRequest.params.rendererUrl) || (bid.ext && bid.ext.renderer_url) || 'https://s.gamoshi.io/video/latest/renderer.js', + config: rendererOptions, + loaded: false, + }); + try { + renderer.setRender(renderOutstream); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function renderOutstream(bid) { + bid.renderer.push(() => { + const unitId = bid.adUnitCode + '/' + bid.adId; + window['GamoshiPlayer'].renderAd({ + id: unitId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: document.getElementById(bid.adUnitCode), + width: bid.width, + height: bid.height, + events: { + ALL_ADS_COMPLETED: () => window.setTimeout(() => { + window['GamoshiPlayer'].removeAd(unitId); + }, 300) + }, + vastUrl: bid.vastUrl, + vastXml: bid.vastXml + }); + }); +} + +function addExternalUserId(eids, value, source, rtiPartner) { + if (utils.isStr(value)) { + eids.push({ + source, + uids: [{ + id: value, + ext: { + rtiPartner + } + }] + }); + } +} + +function replaceMacros(url, macros) { + return url + .replace('[GDPR]', macros.gdpr) + .replace('[CONSENT]', macros.consent) + .replace('[US_PRIVACY]', macros.uspConsent); +} + +registerBidder(spec); diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js new file mode 100644 index 00000000000..97eaedd92be --- /dev/null +++ b/modules/gdprEnforcement.js @@ -0,0 +1,384 @@ +/** + * This module gives publishers extra set of features to enforce individual purposes of TCF v2 + */ + +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { hasDeviceAccess } from '../src/utils.js'; +import adapterManager, { gdprDataHandler } from '../src/adapterManager.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { registerSyncInner } from '../src/adapters/bidderFactory.js'; +import { getHook } from '../src/hook.js'; +import { validateStorageEnforcement } from '../src/storageManager.js'; +import events from '../src/events.js'; +import { EVENTS } from '../src/constants.json'; + +const TCF2 = { + 'purpose1': { id: 1, name: 'storage' }, + 'purpose2': { id: 2, name: 'basicAds' }, + 'purpose7': { id: 7, name: 'measurement' } +} + +/* + These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher. +*/ +const DEFAULT_RULES = [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] +}, { + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] +}]; + +export let purpose1Rule; +export let purpose2Rule; +export let purpose7Rule; + +export let enforcementRules; + +const storageBlocked = []; +const biddersBlocked = []; +const analyticsBlocked = []; + +let addedDeviceAccessHook = false; + +/** + * Returns gvlId for Bid Adapters. If a bidder does not have an associated gvlId, it returns 'undefined'. + * @param {string=} bidderCode - The 'code' property on the Bidder spec. + * @retuns {number} gvlId + */ +function getGvlid(bidderCode) { + let gvlid; + bidderCode = bidderCode || config.getCurrentBidder(); + if (bidderCode) { + const gvlMapping = config.getConfig('gvlMapping'); + if (gvlMapping && gvlMapping[bidderCode]) { + gvlid = gvlMapping[bidderCode]; + } else { + const bidder = adapterManager.getBidAdapter(bidderCode); + if (bidder && bidder.getSpec) { + gvlid = bidder.getSpec().gvlid; + } + } + } + return gvlid; +} + +/** + * Returns gvlId for userId module. If a userId modules does not have an associated gvlId, it returns 'undefined'. + * @param {Object} userIdModule + * @retuns {number} gvlId + */ +function getGvlidForUserIdModule(userIdModule) { + let gvlId; + const gvlMapping = config.getConfig('gvlMapping'); + if (gvlMapping && gvlMapping[userIdModule.name]) { + gvlId = gvlMapping[userIdModule.name]; + } else { + gvlId = userIdModule.gvlid; + } + return gvlId; +} + +/** + * Returns gvlId for analytics adapters. If a analytics adapter does not have an associated gvlId, it returns 'undefined'. + * @param {string} code - 'provider' property on the analytics adapter config + * @returns {number} gvlId + */ +function getGvlidForAnalyticsAdapter(code) { + let gvlId; + const gvlMapping = config.getConfig('gvlMapping'); + if (gvlMapping && gvlMapping[code]) { + gvlId = gvlMapping[code]; + } else { + gvlId = adapterManager.getAnalyticsAdapter(code).gvlid; + } + return gvlId; +} + +/** + * This function takes in a rule and consentData and validates against the consentData provided. Depending on what it returns, + * the caller may decide to suppress a TCF-sensitive activity. + * @param {Object} rule - enforcement rules set in config + * @param {Object} consentData - gdpr consent data + * @param {string=} currentModule - Bidder code of the current module + * @param {number=} gvlId - GVL ID for the module + * @returns {boolean} + */ +export function validateRules(rule, consentData, currentModule, gvlId) { + const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id; + + // return 'true' if vendor present in 'vendorExceptions' + if (includes(rule.vendorExceptions || [], currentModule)) { + return true; + } + + // get data from the consent string + const purposeConsent = utils.deepAccess(consentData, `vendorData.purpose.consents.${purposeId}`); + const vendorConsent = utils.deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); + const liTransparency = utils.deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`); + + /* + Since vendor exceptions have already been handled, the purpose as a whole is allowed if it's not being enforced + or the user has consented. Similar with vendors. + */ + const purposeAllowed = rule.enforcePurpose === false || purposeConsent === true; + const vendorAllowed = rule.enforceVendor === false || vendorConsent === true; + + /* + Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming + LI for Basic Ads (Purpose 2). Prebid.js can't check to see who's declaring what legal basis, so if LI has been + established for Purpose 2, allow the auction to take place and let the server sort out the legal basis calculation. + */ + if (purposeId === 2) { + return (purposeAllowed && vendorAllowed) || (liTransparency === true); + } + + return purposeAllowed && vendorAllowed; +} + +/** + * This hook checks whether module has permission to access device or not. Device access include cookie and local storage + * @param {Function} fn reference to original function (used by hook logic) + * @param {Number=} gvlid gvlid of the module + * @param {string=} moduleName name of the module + */ +export function deviceAccessHook(fn, gvlid, moduleName, result) { + result = Object.assign({}, { + hasEnforcementHook: true + }); + if (!hasDeviceAccess()) { + utils.logWarn('Device access is disabled by Publisher'); + result.valid = false; + fn.call(this, gvlid, moduleName, result); + } else { + const consentData = gdprDataHandler.getConsentData(); + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + const curBidder = config.getCurrentBidder(); + // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder + if (curBidder && (curBidder != moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { + gvlid = getGvlid(curBidder); + } else { + gvlid = getGvlid(moduleName); + } + const curModule = moduleName || curBidder; + let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid); + if (isAllowed) { + result.valid = true; + fn.call(this, gvlid, moduleName, result); + } else { + curModule && utils.logWarn(`TCF2 denied device access for ${curModule}`); + result.valid = false; + storageBlocked.push(curModule); + fn.call(this, gvlid, moduleName, result); + } + } else { + // The module doesn't enforce TCF1.1 strings + result.valid = true; + fn.call(this, gvlid, moduleName, result); + } + } else { + result.valid = true; + fn.call(this, gvlid, moduleName, result); + } + } +} + +/** + * This hook checks if a bidder has consent for user sync or not + * @param {Function} fn reference to original function (used by hook logic) + * @param {...any} args args + */ +export function userSyncHook(fn, ...args) { + const consentData = gdprDataHandler.getConsentData(); + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + const gvlid = getGvlid(); + const curBidder = config.getCurrentBidder(); + let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); + if (isAllowed) { + fn.call(this, ...args); + } else { + utils.logWarn(`User sync not allowed for ${curBidder}`); + storageBlocked.push(curBidder); + } + } else { + // The module doesn't enforce TCF1.1 strings + fn.call(this, ...args); + } + } else { + fn.call(this, ...args); + } +} + +/** + * This hook checks if user id module is given consent or not + * @param {Function} fn reference to original function (used by hook logic) + * @param {Submodule[]} submodules Array of user id submodules + * @param {Object} consentData GDPR consent data + */ +export function userIdHook(fn, submodules, consentData) { + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + let userIdModules = submodules.map((submodule) => { + const gvlid = getGvlidForUserIdModule(submodule.submodule); + const moduleName = submodule.submodule.name; + let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); + if (isAllowed) { + return submodule; + } else { + utils.logWarn(`User denied permission to fetch user id for ${moduleName} User id module`); + storageBlocked.push(moduleName); + } + return undefined; + }).filter(module => module) + fn.call(this, userIdModules, { ...consentData, hasValidated: true }); + } else { + // The module doesn't enforce TCF1.1 strings + fn.call(this, submodules, consentData); + } + } else { + fn.call(this, submodules, consentData); + } +} + +/** + * Checks if bidders are allowed in the auction. + * Enforces "purpose 2 (Basic Ads)" of TCF v2.0 spec + * @param {Function} fn - Function reference to the original function. + * @param {Array} adUnits + */ +export function makeBidRequestsHook(fn, adUnits, ...args) { + const consentData = gdprDataHandler.getConsentData(); + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + adUnits.forEach(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => { + const currBidder = bid.bidder; + const gvlId = getGvlid(currBidder); + if (includes(biddersBlocked, currBidder)) return false; + const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); + if (!isAllowed) { + utils.logWarn(`TCF2 blocked auction for ${currBidder}`); + biddersBlocked.push(currBidder); + } + return isAllowed; + }); + }); + fn.call(this, adUnits, ...args); + } else { + // The module doesn't enforce TCF1.1 strings + fn.call(this, adUnits, ...args); + } + } else { + fn.call(this, adUnits, ...args); + } +} + +/** + * Checks if Analytics adapters are allowed to send data to their servers for furhter processing. + * Enforces "purpose 7 (Measurement)" of TCF v2.0 spec + * @param {Function} fn - Function reference to the original function. + * @param {Array} config - Configuration object passed to pbjs.enableAnalytics() + */ +export function enableAnalyticsHook(fn, config) { + const consentData = gdprDataHandler.getConsentData(); + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + if (!utils.isArray(config)) { + config = [config] + } + config = config.filter(conf => { + const analyticsAdapterCode = conf.provider; + const gvlid = getGvlidForAnalyticsAdapter(analyticsAdapterCode); + const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); + if (!isAllowed) { + analyticsBlocked.push(analyticsAdapterCode); + utils.logWarn(`TCF2 blocked analytics adapter ${conf.provider}`); + } + return isAllowed; + }); + fn.call(this, config); + } else { + // This module doesn't enforce TCF1.1 strings + fn.call(this, config); + } + } else { + fn.call(this, config); + } +} + +/** + * Compiles the TCF2.0 enforcement results into an object, which is emitted as an event payload to "tcf2Enforcement" event. + */ +function emitTCF2FinalResults() { + // remove null and duplicate values + const formatArray = function (arr) { + return arr.filter((i, k) => i !== null && arr.indexOf(i) === k); + } + const tcf2FinalResults = { + storageBlocked: formatArray(storageBlocked), + biddersBlocked: formatArray(biddersBlocked), + analyticsBlocked: formatArray(analyticsBlocked) + }; + + events.emit(EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); +} + +events.on(EVENTS.AUCTION_END, emitTCF2FinalResults); + +/* + Set of callback functions used to detect presence of a TCF rule, passed as the second argument to find(). +*/ +const hasPurpose1 = (rule) => { return rule.purpose === TCF2.purpose1.name } +const hasPurpose2 = (rule) => { return rule.purpose === TCF2.purpose2.name } +const hasPurpose7 = (rule) => { return rule.purpose === TCF2.purpose7.name } + +/** + * A configuration function that initializes some module variables, as well as adds hooks + * @param {Object} config - GDPR enforcement config object + */ +export function setEnforcementConfig(config) { + const rules = utils.deepAccess(config, 'gdpr.rules'); + if (!rules) { + utils.logWarn('TCF2: enforcing P1 and P2'); + enforcementRules = DEFAULT_RULES; + } else { + enforcementRules = rules; + } + + purpose1Rule = find(enforcementRules, hasPurpose1); + purpose2Rule = find(enforcementRules, hasPurpose2); + purpose7Rule = find(enforcementRules, hasPurpose7); + + if (!purpose1Rule) { + purpose1Rule = DEFAULT_RULES[0]; + } + + if (!purpose2Rule) { + purpose2Rule = DEFAULT_RULES[1]; + } + + if (purpose1Rule && !addedDeviceAccessHook) { + addedDeviceAccessHook = true; + validateStorageEnforcement.before(deviceAccessHook, 49); + registerSyncInner.before(userSyncHook, 48); + // Using getHook as user id and gdprEnforcement are both optional modules. Using import will auto include the file in build + getHook('validateGdprEnforcement').before(userIdHook, 47); + } + if (purpose2Rule) { + getHook('makeBidRequests').before(makeBidRequestsHook); + } + + if (purpose7Rule) { + getHook('enableAnalyticsCb').before(enableAnalyticsHook); + } +} + +config.getConfig('consentManagement', config => setEnforcementConfig(config.consentManagement)); diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index e1652602951..134dd0b4fc9 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -1,5 +1,5 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { isInteger } from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { isInteger } from '../src/utils.js'; const BIDDER_CODE = 'getintent'; const IS_NET_REVENUE = true; diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js new file mode 100644 index 00000000000..969c04bb451 --- /dev/null +++ b/modules/glimpseBidAdapter.js @@ -0,0 +1,58 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'glimpse'; + +export const spec = { + code: BIDDER_CODE, + url: 'https://api.glimpseprotocol.io/cloud/v1/vault/prebid', + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + try { + const { placementId } = bid.params; + return typeof placementId === 'string' && placementId.length > 0; + } catch (error) { + return false; + } + }, + + buildRequests: (validBidRequests, bidderRequest) => { + const { url, code: bidderCode } = spec; + + const bids = validBidRequests.map((request) => { + delete request.mediaTypes; + return request; + }); + + const sdk = { + source: 'pbjs', + version: '$prebid.version$', + }; + + const payload = { + ...bidderRequest, + bidderCode, + bids, + sdk, + }; + + return { + method: 'POST', + url, + data: JSON.stringify(payload), + }; + }, + + interpretResponse: (serverResponse, _) => { + const bids = []; + try { + const { body } = serverResponse; + bids.push(...body); + } catch (error) {} + + return bids; + }, +}; + +registerBidder(spec); diff --git a/modules/glimpseBidAdapter.md b/modules/glimpseBidAdapter.md new file mode 100644 index 00000000000..024cc9ebfa7 --- /dev/null +++ b/modules/glimpseBidAdapter.md @@ -0,0 +1,101 @@ +# Overview + +``` +Module Name: Glimpse Protocol Bid Adapter +Module Type: Bidder Adapter +Maintainer: hello@glimpseprotocol.io +``` + +# Description + +This module connects publishers to Glimpse Protocol's demand sources via Prebid.js. Our innovative marketplace protects +consumer privacy while allowing precise targeting. It is compliant with GDPR, DPA and CCPA. + +This adapter supports Banner. + +# Test Parameters + +```javascript +var adUnits = [ + { + code: 'banner-div-a', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'glimpse', + params: { + placementId: 'glimpse-demo-300x250', + }, + }, + ], + }, + { + code: 'banner-div-b', + mediaTypes: { + banner: { + sizes: [[320, 50]], + }, + }, + bids: [ + { + bidder: 'glimpse', + params: { + placementId: 'glimpse-demo-320x50', + }, + }, + ], + }, + { + code: 'banner-div-c', + mediaTypes: { + banner: { + sizes: [[970, 250]], + }, + }, + bids: [ + { + bidder: 'glimpse', + params: { + placementId: 'glimpse-demo-970x250', + }, + }, + ], + }, + { + code: 'banner-div-d', + mediaTypes: { + banner: { + sizes: [[728, 90]], + }, + }, + bids: [ + { + bidder: 'glimpse', + params: { + placementId: 'glimpse-demo-728x90', + }, + }, + ], + }, + { + code: 'banner-div-e', + mediaTypes: { + banner: { + sizes: [[300, 600]], + }, + }, + bids: [ + { + bidder: 'glimpse', + params: { + placementId: 'glimpse-demo-300x600', + }, + }, + ], + }, +]; +``` diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js new file mode 100644 index 00000000000..d9dc8f7641a --- /dev/null +++ b/modules/gmosspBidAdapter.js @@ -0,0 +1,134 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'gmossp'; +const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.sid); + }, + + /** + * 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) { + const bidRequests = []; + + const url = bidderRequest.refererInfo.referer; + const cur = getCurrencyType(); + const dnt = utils.getDNT() ? '1' : '0'; + + for (let i = 0; i < validBidRequests.length; i++) { + let queryString = ''; + + const request = validBidRequests[i]; + const tid = request.transactionId; + const bid = request.bidId; + const ver = '$prebid.version$'; + const sid = utils.getBidIdParameter('sid', request.params); + + queryString = utils.tryAppendQueryString(queryString, 'tid', tid); + queryString = utils.tryAppendQueryString(queryString, 'bid', bid); + queryString = utils.tryAppendQueryString(queryString, 'ver', ver); + queryString = utils.tryAppendQueryString(queryString, 'sid', sid); + queryString = utils.tryAppendQueryString(queryString, 'url', url); + queryString = utils.tryAppendQueryString(queryString, 'cur', cur); + queryString = utils.tryAppendQueryString(queryString, 'dnt', dnt); + + bidRequests.push({ + method: 'GET', + url: ENDPOINT, + data: queryString + }); + } + return bidRequests; + }, + + /** + * 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 (bidderResponse, requests) { + const res = bidderResponse.body; + + if (utils.isEmpty(res)) { + return []; + } + + try { + res.imps.forEach(impTracker => { + const tracker = utils.createTrackPixelHtml(impTracker); + res.ad += tracker; + }); + } catch (error) { + utils.logError('Error appending tracking pixel', error); + } + + const bid = { + requestId: res.bid, + cpm: res.price, + currency: res.cur, + width: res.w, + height: res.h, + ad: res.ad, + creativeId: res.creativeId, + netRevenue: true, + ttl: res.ttl || 300 + }; + + return [bid]; + }, + + /** + * 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 (!serverResponses.length) { + return syncs; + } + + serverResponses.forEach(res => { + if (syncOptions.pixelEnabled && res.body && res.body.syncs.length) { + res.body.syncs.forEach(sync => { + syncs.push({ + type: 'image', + url: sync + }) + }) + } + }) + return syncs; + }, + +}; + +function getCurrencyType() { + if (config.getConfig('currency.adServerCurrency')) { + return config.getConfig('currency.adServerCurrency'); + } + return 'JPY'; +} + +registerBidder(spec); diff --git a/modules/gmosspBidAdapter.md b/modules/gmosspBidAdapter.md new file mode 100644 index 00000000000..4f63582ef9a --- /dev/null +++ b/modules/gmosspBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: GMOSSP Bid Adapter +Module Type: Bidder Adapter +Maintainer: dev@ml.gmo-am.jp +``` + +# Description +Connects to GMOSSP exchange for bids. +GMOSSP bid adapter supports Banner. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [320, 50], + [320, 100] + ] + } + }, + bids: [{ + bidder: 'gmossp', + params: { + sid: '61590' + } + }] + } +]; +``` \ No newline at end of file diff --git a/modules/googleAnalyticsAdapter.js b/modules/googleAnalyticsAdapter.js index 2e569cd7ba1..a8fc83ae62e 100644 --- a/modules/googleAnalyticsAdapter.js +++ b/modules/googleAnalyticsAdapter.js @@ -2,10 +2,10 @@ * ga.js - analytics adapter for google analytics */ -var events = require('../src/events'); -var utils = require('../src/utils'); +var events = require('../src/events.js'); +var utils = require('../src/utils.js'); var CONSTANTS = require('../src/constants.json'); -var adapterManager = require('../src/adapterManager').default; +var adapterManager = require('../src/adapterManager.js').default; var BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js new file mode 100644 index 00000000000..48b72671d6a --- /dev/null +++ b/modules/gptPreAuction.js @@ -0,0 +1,101 @@ +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { getHook } from '../src/hook.js'; +import find from 'core-js-pure/features/array/find.js'; + +const MODULE_NAME = 'GPT Pre-Auction'; +export let _currentConfig = {}; +let hooksAdded = false; + +export const appendGptSlots = adUnits => { + const { customGptSlotMatching } = _currentConfig; + + if (!utils.isGptPubadsDefined()) { + return; + } + + const adUnitMap = adUnits.reduce((acc, adUnit) => { + acc[adUnit.code] = adUnit; + return acc; + }, {}); + + window.googletag.pubads().getSlots().forEach(slot => { + const matchingAdUnitCode = find(Object.keys(adUnitMap), customGptSlotMatching + ? customGptSlotMatching(slot) + : utils.isAdUnitCodeMatchingSlot(slot)); + + if (matchingAdUnitCode) { + const adUnit = adUnitMap[matchingAdUnitCode]; + adUnit.fpd = adUnit.fpd || {}; + adUnit.fpd.context = adUnit.fpd.context || {}; + + const context = adUnit.fpd.context; + context.adServer = context.adServer || {}; + context.adServer.name = 'gam'; + context.adServer.adSlot = slot.getAdUnitPath(); + } + }); +}; + +export const appendPbAdSlot = adUnit => { + adUnit.fpd = adUnit.fpd || {}; + adUnit.fpd.context = adUnit.fpd.context || {}; + const context = adUnit.fpd.context; + const { customPbAdSlot } = _currentConfig; + + if (customPbAdSlot) { + context.pbAdSlot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adServer.adSlot')); + return; + } + + // use context.pbAdSlot if set + if (context.pbAdSlot) { + return; + } + // use data attribute 'data-adslotid' if set + try { + const adUnitCodeDiv = document.getElementById(adUnit.code); + if (adUnitCodeDiv.dataset.adslotid) { + context.pbAdSlot = adUnitCodeDiv.dataset.adslotid; + return; + } + } catch (e) {} + // banner adUnit, use GPT adunit if defined + if (context.adServer) { + context.pbAdSlot = context.adServer.adSlot; + return; + } + context.pbAdSlot = adUnit.code; +}; + +export const makeBidRequestsHook = (fn, adUnits, ...args) => { + appendGptSlots(adUnits); + adUnits.forEach(adUnit => { + appendPbAdSlot(adUnit); + }); + return fn.call(this, adUnits, ...args); +}; + +const handleSetGptConfig = moduleConfig => { + _currentConfig = utils.pick(moduleConfig, [ + 'enabled', enabled => enabled !== false, + 'customGptSlotMatching', customGptSlotMatching => + typeof customGptSlotMatching === 'function' && customGptSlotMatching, + 'customPbAdSlot', customPbAdSlot => typeof customPbAdSlot === 'function' && customPbAdSlot, + ]); + + if (_currentConfig.enabled) { + if (!hooksAdded) { + getHook('makeBidRequests').before(makeBidRequestsHook); + hooksAdded = true; + } + } else { + utils.logInfo(`${MODULE_NAME}: Turning off module`); + _currentConfig = {}; + getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove(); + hooksAdded = false; + } +}; + +config.getConfig('gptPreAuction', config => handleSetGptConfig(config.gptPreAuction)); +handleSetGptConfig({}); diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index f2a340c7850..b4b741ac783 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -1,11 +1,13 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { Renderer } from '../src/Renderer'; -import { VIDEO, BANNER } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'grid'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hb'; -const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=iow_labs'; +const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; +const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -39,79 +41,22 @@ export const spec = { * * @param {BidRequest[]} validBidRequests - an array of bids * @param {bidderRequest} bidderRequest bidder request object - * @return ServerRequest Info describing the request to the server. + * @return {ServerRequest[]} Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; - const bids = validBidRequests || []; - let reqId; - - bids.forEach(bid => { - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); - } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); + const oldFormatBids = []; + const newFormatBids = []; + validBidRequests.forEach((bid) => { + bid.params.useNewFormat ? newFormatBids.push(bid) : oldFormatBids.push(bid); }); - - const payload = { - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; - } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } + const requests = []; + if (newFormatBids.length) { + requests.push(buildNewRequest(newFormatBids, bidderRequest)); } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, - }; + if (oldFormatBids.length) { + requests.push(buildOldRequest(oldFormatBids, bidderRequest)); + } + return requests; }, /** * Unpack the response from the server into a list of bids. @@ -123,7 +68,6 @@ export const spec = { interpretResponse: function(serverResponse, bidRequest) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; - const bidsMap = bidRequest.bidsMap; let errorMessage; @@ -134,13 +78,13 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, bidResponses); + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses); }); } if (errorMessage) utils.logError(errorMessage); return bidResponses; }, - getUserSyncs: function (syncOptions, responses, gdprConsent) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (!hasSynced && syncOptions.pixelEnabled) { let params = ''; @@ -151,6 +95,9 @@ export const spec = { params += `&gdpr_consent=${gdprConsent.consentString}`; } } + if (uspConsent) { + params += `&us_privacy=${uspConsent}`; + } hasSynced = true; return { @@ -161,6 +108,16 @@ export const spec = { } }; +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + function _getBidFromResponse(respItem) { if (!respItem) { utils.logError(LOG_ERROR_MESS.emptySeatbid); @@ -172,68 +129,76 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, bidResponses) { +function _addBidResponse(serverBid, bidRequest, 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) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - const slot = awaitingBids[sizeId][0]; - - const bid = slot.bids.shift(); - - 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: false, - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid - }; - - if (serverBid.content_type === 'video') { - bidResponse.vastXml = serverBid.adm; - bidResponse.mediaType = VIDEO; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }); - } - } else { - bidResponse.ad = serverBid.adm; - bidResponse.mediaType = BANNER; + let bid = null; + let slot = null; + const bidsMap = bidRequest.bidsMap; + if (bidRequest.newFormat) { + bid = bidsMap[serverBid.impid]; + } else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + const sizeId = `${serverBid.w}x${serverBid.h}`; + if (awaitingBids[sizeId]) { + slot = awaitingBids[sizeId][0]; + bid = slot.bids.shift(); } - bidResponses.push(bidResponse); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } - if (!slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } + if (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: false, + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid + }; + + if (serverBid.content_type === 'video') { + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = VIDEO; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL }); } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; + } + bidResponses.push(bidResponse); + + if (slot && !slot.bids.length) { + slot.parents.forEach(({parent, key, uid}) => { + const index = parent[key].indexOf(slot); + if (index > -1) { + parent[key].splice(index, 1); + } + if (!parent[key].length) { + delete parent[key]; + if (!utils.getKeys(parent).length) { + delete bidsMap[uid]; + } + } + }); } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; } } if (errorMessage) { @@ -241,6 +206,326 @@ function _addBidResponse(serverBid, bidsMap, bidResponses) { } } +function buildOldRequest(validBidRequests, bidderRequest) { + const auids = []; + const bidsMap = {}; + const slotsMapByUid = {}; + const sizeMap = {}; + const bids = validBidRequests || []; + let pageKeywords = null; + let reqId; + + bids.forEach(bid => { + reqId = bid.bidderRequestId; + const {params: {uid}, adUnitCode, mediaTypes} = bid; + auids.push(uid); + const sizesId = utils.parseSizesInput(bid.sizes); + + if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { + pageKeywords = utils.transformBidderParamKeywords(bid.params.keywords); + } + + const addedSizes = {}; + sizesId.forEach((sizeId) => { + addedSizes[sizeId] = true; + }); + const bannerSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'banner.sizes')); + const videoSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'video.playerSize')); + bannerSizesId.concat(videoSizesId).forEach((sizeId) => { + if (!addedSizes[sizeId]) { + addedSizes[sizeId] = true; + sizesId.push(sizeId); + } + }); + + if (!slotsMapByUid[uid]) { + slotsMapByUid[uid] = {}; + } + const slotsMap = slotsMapByUid[uid]; + if (!slotsMap[adUnitCode]) { + slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + } else { + slotsMap[adUnitCode].bids.push(bid); + } + const slot = slotsMap[adUnitCode]; + + sizesId.forEach((sizeId) => { + sizeMap[sizeId] = true; + if (!bidsMap[uid]) { + bidsMap[uid] = {}; + } + + if (!bidsMap[uid][sizeId]) { + bidsMap[uid][sizeId] = [slot]; + } else { + bidsMap[uid][sizeId].push(slot); + } + slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); + }); + }); + + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + + const payload = { + auids: auids.join(','), + sizes: utils.getKeys(sizeMap).join(','), + r: reqId, + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$' + }; + + if (pageKeywords) { + payload.keywords = JSON.stringify(pageKeywords); + } + + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + payload.u = bidderRequest.refererInfo.referer; + } + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap: bidsMap + } +} + +function buildNewRequest(validBidRequests, bidderRequest) { + let pageKeywords = null; + let jwpseg = null; + let content = null; + let schain = null; + let userId = null; + let user = null; + let userExt = null; + let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest; + + const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const imp = []; + const bidsMap = {}; + + validBidRequests.forEach((bid) => { + if (!bidderRequestId) { + bidderRequestId = bid.bidderRequestId; + } + if (!auctionId) { + auctionId = bid.auctionId; + } + if (!schain) { + schain = bid.schain; + } + if (!userId) { + userId = bid.userId; + } + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, realTimeData} = bid; + bidsMap[bidId] = bid; + if (!pageKeywords && !utils.isEmpty(keywords)) { + pageKeywords = utils.transformBidderParamKeywords(keywords); + } + if (realTimeData && realTimeData.jwTargeting) { + if (!jwpseg && realTimeData.jwTargeting.segments) { + jwpseg = realTimeData.segments; + } + if (!content && realTimeData.content) { + content = realTimeData.content; + } + } + let impObj = { + id: bidId, + tagid: uid.toString(), + ext: { + divid: adUnitCode + } + }; + + if (!mediaTypes || mediaTypes[BANNER]) { + const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); + if (banner) { + impObj.banner = banner; + } + } + if (mediaTypes && mediaTypes[VIDEO]) { + const video = createVideoRequest(bid, mediaTypes[VIDEO]); + if (video) { + impObj.video = video; + } + } + + if (impObj.banner || impObj.video) { + imp.push(impObj); + } + }); + + const source = { + tid: auctionId, + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' + } + }; + + if (schain) { + source.ext.schain = schain; + } + + const tmax = config.getConfig('bidderTimeout') || timeout; + + let request = { + id: bidderRequestId, + site: { + page: referer + }, + tmax, + source, + imp + }; + + if (content) { + request.site.content = content; + } + + if (jwpseg && jwpseg.length) { + user = { + data: [{ + name: 'iow_labs_pub_data', + segment: jwpseg.map((seg) => { + return {name: 'jwpseg', value: seg}; + }) + }] + }; + } + + if (gdprConsent && gdprConsent.consentString) { + userExt = {consent: gdprConsent.consentString}; + } + + if (userId) { + userExt = userExt || {}; + if (userId.tdid) { + userExt.unifiedid = userId.tdid; + } + if (userId.id5id) { + userExt.id5id = userId.id5id; + } + if (userId.digitrustid && userId.digitrustid.data && userId.digitrustid.data.id) { + userExt.digitrustid = userId.digitrustid.data.id; + } + if (userId.lipb && userId.lipb.lipbid) { + userExt.liveintentid = userId.lipb.lipbid; + } + } + + if (userExt && Object.keys(userExt).length) { + user = user || {}; + user.ext = userExt; + } + + if (user) { + request.user = user; + } + + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + + if (pageKeywords) { + request.ext = { + keywords: pageKeywords + }; + } + + if (gdprConsent && gdprConsent.gdprApplies) { + request.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 + } + } + } + + if (uspConsent) { + if (!request.regs) { + request.regs = {ext: {}}; + } + request.regs.ext.us_privacy = uspConsent; + } + + return { + method: 'POST', + url: NEW_ENDPOINT_URL, + data: JSON.stringify(request), + newFormat: true, + bidsMap + }; +} + +function createVideoRequest(bid, mediaType) { + const {playerSize, mimes, durationRangeSec} = mediaType; + const size = (playerSize || bid.sizes || [])[0]; + if (!size) return; + + let result = utils.parseGPTSingleSizeArrayToRtbSize(size); + + if (mimes) { + result.mimes = mimes; + } + + if (durationRangeSec && durationRangeSec.length === 2) { + result.minduration = durationRangeSec[0]; + result.maxduration = durationRangeSec[1]; + } + + return result; +} + +function createBannerRequest(bid, mediaType) { + const sizes = mediaType.sizes || bid.sizes; + if (!sizes || !sizes.length) return; + + let format = sizes.map((size) => utils.parseGPTSingleSizeArrayToRtbSize(size)); + let result = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]); + + if (format.length) { + result.format = format + } + return result; +} + function outstreamRender (bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ diff --git a/modules/gridBidAdapter.md b/modules/gridBidAdapter.md index 4720ee3d808..77b9bbf0f36 100644 --- a/modules/gridBidAdapter.md +++ b/modules/gridBidAdapter.md @@ -32,7 +32,11 @@ Grid bid adapter supports Banner and Video (instream and outstream). bidder: "grid", params: { uid: 2, - priceType: 'gross' + priceType: 'gross', + keywords: { + brandsafety: ['disaster'], + topic: ['stress', 'fear'] + } } } ] @@ -51,4 +55,4 @@ Grid bid adapter supports Banner and Video (instream and outstream). ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js new file mode 100644 index 00000000000..ffd6c1b250c --- /dev/null +++ b/modules/gridNMBidAdapter.js @@ -0,0 +1,234 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'gridNM'; +const ENDPOINT_URL = 'https://grid.bidswitch.net/hbnm'; +const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; +const TIME_TO_LIVE = 360; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +let hasSynced = false; + +const LOG_ERROR_MESS = { + noAdm: 'Bid from response has no adm parameter - ', + noPrice: 'Bid from response has no price parameter - ', + wrongContentType: 'Bid from response has wrong content_type parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [ VIDEO ], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + let invalid = + !bid.params.source || !utils.isStr(bid.params.source) || + !bid.params.secid || !utils.isStr(bid.params.secid) || + !bid.params.pubid || !utils.isStr(bid.params.pubid); + + if (!invalid) { + invalid = !bid.params.video || !bid.params.video.protocols || !bid.params.video.mimes; + } + if (!invalid) { + const {protocols, mimes} = bid.params.video; + invalid = !utils.isArray(mimes) || !mimes.length || mimes.filter((it) => !(it && utils.isStr(it))).length; + if (!invalid) { + invalid = !utils.isArray(protocols) || !protocols.length || protocols.filter((it) => !(utils.isNumber(it) && it > 0 && !(it % 1))).length; + } + } + return !invalid; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @param {bidderRequest} bidderRequest bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const bids = validBidRequests || []; + const requests = []; + + bids.forEach(bid => { + const {params, bidderRequestId, sizes} = bid; + const payload = { + sizes: utils.parseSizesInput(sizes).join(','), + r: bidderRequestId, + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$' + }; + + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + payload.u = bidderRequest.refererInfo.referer; + } + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + } + + requests.push({ + method: 'POST', + url: ENDPOINT_URL + '?' + utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bid: bid, + data: params // content + }); + }); + + return requests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body; + const bidResponses = []; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } + + if (!errorMessage && serverResponse.seatbid) { + const serverBid = _getBidFromResponse(serverResponse.seatbid[0]); + if (serverBid) { + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else if (!serverBid.price) errorMessage = LOG_ERROR_MESS.noPrice + JSON.stringify(serverBid); + else if (serverBid.content_type !== 'video') errorMessage = LOG_ERROR_MESS.wrongContentType + serverBid.content_type; + if (!errorMessage) { + const bid = bidRequest.bid; + if (!serverBid.w || !serverBid.h) { + const size = utils.parseSizesInput(bid.sizes)[0].split('x'); + serverBid.w = size[0]; + serverBid.h = size[1]; + } + const bidResponse = { + requestId: bid.bidId, + bidderCode: spec.code, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid || bid.bidderRequestId, + currency: 'USD', + netRevenue: false, + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid, + vastXml: serverBid.adm, + mediaType: VIDEO, + adResponse: { + content: serverBid.adm + } + }; + + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL + }); + } + bidResponses.push(bidResponse); + } + } + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + if (!hasSynced && syncOptions.pixelEnabled) { + let params = ''; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent) { + params += `&us_privacy=${uspConsent}`; + } + + hasSynced = true; + return { + type: 'image', + url: SYNC_URL + params + }; + } + } +}; + +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 outstreamRender (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +function createRenderer (bid, rendererParams) { + const renderer = Renderer.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + +export function resetUserSync() { + hasSynced = false; +} + +export function getSyncUrl() { + return SYNC_URL; +} + +registerBidder(spec); diff --git a/modules/gridNMBidAdapter.md b/modules/gridNMBidAdapter.md new file mode 100644 index 00000000000..6decdde7f4c --- /dev/null +++ b/modules/gridNMBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +Module Name: The Grid Media Bidder Adapter +Module Type: Bidder Adapter +Maintainer: grid-tech@themediagrid.com + +# Description + +Module that connects to Grid demand source to fetch bids. +Grid bid adapter supports Banner and Video (instream and outstream). + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + playerSize: [728, 90], + context: 'outstream' + } + }, + bids: [ + { + bidder: "gridNM", + params: { + source: 'jwp', + secid: '11', + pubid: '22', + video: { + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [1,2,3,4,5,6] + } + } + } + ] + } + ]; +``` diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 2325e1bc448..ebeb46e3c44 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -1,15 +1,23 @@ -import * as utils from '../src/utils' +import * as utils from '../src/utils.js' -import { config } from '../src/config' -import includes from 'core-js/library/fn/array/includes'; -import { registerBidder } from '../src/adapters/bidderFactory' +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +import { config } from '../src/config.js' +import { getStorageManager } from '../src/storageManager.js'; +import includes from 'core-js-pure/features/array/includes'; +import { registerBidder } from '../src/adapters/bidderFactory.js' + +const storage = getStorageManager(); const BIDDER_CODE = 'gumgum' const ALIAS_BIDDER_CODE = ['gg'] const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp` -const DT_CREDENTIALS = { member: 'YcXr87z2lpbB' } +const JCSI = { t: 0, rq: 8, pbv: '$prebid.version$' } +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO] const TIME_TO_LIVE = 60 +const DELAY_REQUEST_TIME = 1800000; // setting to 30 mins +let invalidRequestIds = {}; let browserParams = {}; let pageViewId = null @@ -23,7 +31,7 @@ function _getBrowserParams(topWindowUrl) { function getNetworkSpeed () { const connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection) const Mbps = connection && (connection.downlink || connection.bandwidth) - return Mbps ? Math.round(Mbps * 1024) : null // 1 megabit -> 1024 kilobits + return Mbps ? Math.round(Mbps * 1024) : null } function getOgURL () { let ogURL = '' @@ -53,9 +61,9 @@ function _getBrowserParams(topWindowUrl) { sw: topScreen.width, sh: topScreen.height, pu: topUrl, - ce: utils.cookiesAreEnabled(), + ce: storage.cookiesAreEnabled(), dpr: topWindow.devicePixelRatio || 1, - jcsi: JSON.stringify({ t: 0, rq: 8 }), + jcsi: JSON.stringify(JCSI), ogu: getOgURL() } @@ -85,10 +93,6 @@ function _getTradeDeskIDParam(userId) { function _getDigiTrustQueryParams(userId) { let digiTrustId = userId.digitrustid && userId.digitrustid.data; - if (!digiTrustId) { - const digiTrustUser = (window.DigiTrust && window.DigiTrust.getUser) ? window.DigiTrust.getUser(DT_CREDENTIALS) : {}; - digiTrustId = (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || ''; - } // Verify there is an ID and this user has not opted out if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { return {}; @@ -131,11 +135,21 @@ function isBidRequestValid (bid) { params, adUnitCode } = bid; + const id = params.inScreen || params.inScreenPubID || params.inSlot || params.ICV || params.video || params.inVideo; + + if (invalidRequestIds[id]) { + utils.logWarn(`[GumGum] Please check the implementation for ${id} for the placement ${adUnitCode}`); + return false; + } switch (true) { case !!(params.inScreen): break; + case !!(params.inScreenPubID): break; case !!(params.inSlot): break; case !!(params.ICV): break; + case !!(params.video): break; + case !!(params.inVideo): break; + case !!(params.videoPubID): break; default: utils.logWarn(`[GumGum] No product selected for the placement ${adUnitCode}, please check your implementation.`); return false; @@ -149,6 +163,69 @@ function isBidRequestValid (bid) { return true; } +/** + * Renames vid params from mediatypes.video keys + * @param {Object} attributes + * @returns {Object} + */ +function _getVidParams (attributes) { + const { + minduration: mind, + maxduration: maxd, + linearity: li, + startdelay: sd, + placement: pt, + protocols = [], + playerSize = [] + } = attributes; + const sizes = utils.parseSizesInput(playerSize); + const [viw, vih] = sizes[0] && sizes[0].split('x'); + let pr = ''; + + if (protocols.length) { + pr = protocols.join(','); + } + + return { + mind, + maxd, + li, + sd, + pt, + pr, + viw, + vih + }; +} + +/** + * Gets bidfloor + * @param {Object} mediaTypes + * @param {Number} bidfloor + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor (mediaTypes, bidfloor, bid) { + const curMediaType = Object.keys(mediaTypes)[0] || 'banner'; + let floor = bidfloor || 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + /** * Make a server request from the list of BidRequests. * @@ -158,24 +235,36 @@ function isBidRequestValid (bid) { function buildRequests (validBidRequests, bidderRequest) { const bids = []; const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const uspConsent = bidderRequest && bidderRequest.uspConsent; + const timeout = config.getConfig('bidderTimeout'); + const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; utils._each(validBidRequests, bidRequest => { - const timeout = config.getConfig('bidderTimeout'); const { bidId, + mediaTypes = {}, params = {}, schain, transactionId, userId = {} } = bidRequest; - const data = {}; - const sizes = bidRequest.mediaTypes && bidRequest.mediaTypes.banner && bidRequest.mediaTypes.banner.sizes; - const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + const bidFloor = _getFloor(mediaTypes, params.bidfloor, bidRequest); + let sizes = [1, 1]; + let data = {}; + + if (mediaTypes.banner) { + sizes = mediaTypes.banner.sizes; + } else if (mediaTypes.video) { + sizes = mediaTypes.video.playerSize; + } + if (pageViewId) { - data.pv = pageViewId + data.pv = pageViewId; } - if (params.bidfloor) { - data.fp = params.bidfloor; + + if (bidFloor) { + data.fp = bidFloor; } + if (params.inScreenPubID) { data.pubId = params.inScreenPubID; data.pi = 2; @@ -192,12 +281,30 @@ function buildRequests (validBidRequests, bidderRequest) { data.ni = parseInt(params.ICV, 10); data.pi = 5; } + if (params.videoPubID) { + data = Object.assign(data, _getVidParams(mediaTypes.video)); + data.pubId = params.videoPubID; + data.pi = 7; + } + if (params.video) { + data = Object.assign(data, _getVidParams(mediaTypes.video)); + data.t = params.video; + data.pi = 7; + } + if (params.inVideo) { + data = Object.assign(data, _getVidParams(mediaTypes.video)); + data.t = params.inVideo; + data.pi = 6; + } if (gdprConsent) { data.gdprApplies = gdprConsent.gdprApplies ? 1 : 0; } if (data.gdprApplies) { data.gdprConsent = gdprConsent.consentString; } + if (uspConsent) { + data.uspConsent = uspConsent; + } if (schain && schain.nodes) { data.schain = _serializeSupplyChainObj(schain); } @@ -208,7 +315,7 @@ function buildRequests (validBidRequests, bidderRequest) { tId: transactionId, pi: data.pi, selector: params.selector, - sizes: sizes || bidRequest.sizes, + sizes, url: BID_ENDPOINT, method: 'GET', data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId), _getTradeDeskIDParam(userId)) @@ -226,6 +333,19 @@ function buildRequests (validBidRequests, bidderRequest) { function interpretResponse (serverResponse, bidRequest) { const bidResponses = [] const serverResponseBody = serverResponse.body + + if (!serverResponseBody || serverResponseBody.err) { + const data = bidRequest.data || {} + const id = data.t || data.si || data.ni || data.pubId; + const delayTime = serverResponseBody ? serverResponseBody.err.drt : DELAY_REQUEST_TIME; + invalidRequestIds[id] = { productId: data.pi, timestamp: new Date().getTime() }; + + setTimeout(() => { + !!invalidRequestIds[id] && delete invalidRequestIds[id]; + }, delayTime); + utils.logWarn(`[GumGum] Please check the implementation for ${id}`); + } + const defaultResponse = { ad: { price: 0, @@ -240,12 +360,14 @@ function interpretResponse (serverResponse, bidRequest) { ad: { price: cpm, id: creativeId, - markup + markup, + cur }, cw: wrapper, pag: { pvid - } + }, + jcsi } = Object.assign(defaultResponse, serverResponseBody) let data = bidRequest.data || {} let product = data.pi @@ -259,6 +381,10 @@ function interpretResponse (serverResponse, bidRequest) { height = '1' } + if (jcsi) { + serverResponseBody.jcsi = JCSI + } + // update Page View ID from server response pageViewId = pvid @@ -266,10 +392,12 @@ function interpretResponse (serverResponse, bidRequest) { bidResponses.push({ // dealId: DEAL_ID, // referrer: REFERER, + ...(product === 7 && { vastXml: markup }), ad: wrapper ? getWrapperCode(wrapper, Object.assign({}, serverResponseBody, { bidRequest })) : markup, + ...(product === 6 && {ad: markup}), cpm: isTestUnit ? 0.1 : cpm, creativeId, - currency: 'USD', + currency: cur || 'USD', height, netRevenue: true, requestId: bidRequest.id, @@ -309,6 +437,7 @@ export const spec = { isBidRequestValid, buildRequests, interpretResponse, - getUserSyncs + getUserSyncs, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES } registerBidder(spec) diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md index 3abc8a246c9..f47666e9628 100644 --- a/modules/gumgumBidAdapter.md +++ b/modules/gumgumBidAdapter.md @@ -37,6 +37,18 @@ var adUnits = [ } } ] + },{ + code: 'test-div', + sizes: [[300, 50]], + bids: [ + { + bidder: 'gumgum', + params: { + inVideo: 'ggumtest', // GumGum Zone ID given to the client + bidfloor: 0.03 // CPM bid floor + } + } + ] } ]; ``` diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js new file mode 100644 index 00000000000..0d2c22a3f68 --- /dev/null +++ b/modules/h12mediaBidAdapter.js @@ -0,0 +1,209 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import find from 'core-js-pure/features/array/find.js'; +const BIDDER_CODE = 'h12media'; +const DEFAULT_URL = 'https://bidder.h12-media.com/prebid/'; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_TTL = 360; +const DEFAULT_NET_REVENUE = false; + +export const spec = { + code: BIDDER_CODE, + aliases: ['h12'], + + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.pubid); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + const requestUrl = validBidRequests[0].params.endpointdom || DEFAULT_URL; + const isiframe = !((window.self === window.top) || window.frameElement); + const screenSize = getClientDimensions(); + const docSize = getDocumentDimensions(); + + const bidrequests = validBidRequests.map((bidRequest) => { + const bidderParams = bidRequest.params; + const adUnitElement = document.getElementById(bidRequest.adUnitCode); + const ishidden = !isVisible(adUnitElement); + const coords = { + x: adUnitElement && adUnitElement.getBoundingClientRect().x, + y: adUnitElement && adUnitElement.getBoundingClientRect().y, + }; + + return { + bidId: bidRequest.bidId, + transactionId: bidRequest.transactionId, + adunitId: bidRequest.adUnitCode, + pubid: bidderParams.pubid, + placementid: bidderParams.placementid || '', + size: bidderParams.size || '', + adunitSize: bidRequest.mediaTypes.banner.sizes || [], + coords, + ishidden, + }; + }); + + return { + method: 'POST', + url: requestUrl, + options: {withCredentials: false}, + data: { + gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false, + gdpr_cs: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? bidderRequest.gdprConsent.consentString : '', + topLevelUrl: window.top.location.href, + refererUrl: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer : '', + isiframe, + version: '$prebid.version$', + visitorInfo: { + localTime: getLocalDateFormatted(), + dayOfWeek: new Date().getDay(), + screenWidth: screenSize[0], + screenHeight: screenSize[1], + docWidth: docSize[0], + docHeight: docSize[1], + scrollbarx: window.scrollX, + scrollbary: window.scrollY, + }, + bidrequests, + }, + }; + }, + + interpretResponse: function(serverResponse, bidRequests) { + let bidResponses = []; + try { + const serverBody = serverResponse.body; + if (serverBody) { + if (serverBody.bids) { + serverBody.bids.forEach(bidBody => { + const bidRequest = find(bidRequests.data.bidrequests, bid => bid.bidId === bidBody.bidId); + const bidResponse = { + currency: serverBody.currency || DEFAULT_CURRENCY, + netRevenue: serverBody.netRevenue || DEFAULT_NET_REVENUE, + ttl: serverBody.ttl || DEFAULT_TTL, + requestId: bidBody.bidId, + cpm: bidBody.cpm, + width: bidBody.width, + height: bidBody.height, + creativeId: bidBody.creativeId, + ad: bidBody.ad, + meta: bidBody.meta, + mediaType: 'banner', + }; + if (bidRequest) { + bidResponse.pubid = bidRequest.pubid; + bidResponse.placementid = bidRequest.placementid; + bidResponse.size = bidRequest.size; + } + bidResponses.push(bidResponse); + }); + } + } + return bidResponses; + } catch (err) { + utils.logError(err); + } + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + const serverBody = serverResponses[0].body; + const syncs = []; + gdprConsent = gdprConsent || { + gdprApplies: false, consentString: '', + }; + + if (serverBody) { + if (serverBody.bids) { + serverBody.bids.forEach(bidBody => { + const userSyncUrls = bidBody.usersync || []; + const userSyncUrlProcess = url => { + return url + .replace('{gdpr}', gdprConsent.gdprApplies) + .replace('{gdpr_cs}', gdprConsent.consentString); + } + + userSyncUrls.forEach(sync => { + if (syncOptions.iframeEnabled && sync.type === 'iframe' && sync.url) { + syncs.push({ + type: 'iframe', + url: userSyncUrlProcess(sync.url), + }); + } + if (syncOptions.pixelEnabled && sync.type === 'image' && sync.url) { + syncs.push({ + type: 'image', + url: userSyncUrlProcess(sync.url), + }); + } + }); + }); + } + } + + return syncs; + }, +} + +function getContext(elem) { + return elem && window.document.body.contains(elem) ? window : (window.top.document.body.contains(elem) ? top : undefined); +} + +function isDefined(val) { + return (val !== null) && (typeof val !== 'undefined'); +} + +function getIsHidden(elem) { + let lastElem = elem; + let elemHidden = false; + let m; + m = 0; + + do { + m = m + 1; + try { + if ( + getContext(elem).getComputedStyle(lastElem).getPropertyValue('display') === 'none' || + getContext(elem).getComputedStyle(lastElem).getPropertyValue('visibility') === 'hidden' + ) { + return true; + } else { + elemHidden = false; + lastElem = lastElem.parentElement; + } + } catch (o) { + return false; + } + } while ((m < 250) && (lastElem != null) && (elemHidden === false)) + return elemHidden; +} + +function isVisible(element) { + return element && isDefined(getContext(element)) && !getIsHidden(element); +} + +function getClientDimensions() { + try { + const t = window.top.innerWidth || window.top.document.documentElement.clientWidth || window.top.document.body.clientWidth; + const e = window.top.innerHeight || window.top.document.documentElement.clientHeight || window.top.document.body.clientHeight; + return [Math.round(t), Math.round(e)]; + } catch (i) { + return [0, 0]; + } +} + +function getDocumentDimensions() { + try { + const D = window.top.document; + return [D.body.offsetWidth, Math.max(D.body.scrollHeight, D.documentElement.scrollHeight, D.body.offsetHeight, D.documentElement.offsetHeight, D.body.clientHeight, D.documentElement.clientHeight)] + } catch (t) { + return [-1, -1] + } +} + +function getLocalDateFormatted() { + const two = num => ('0' + num).slice(-2); + const d = new Date(); + return `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())} ${two(d.getHours())}:${two(d.getMinutes())}:${two(d.getSeconds())}`; +} + +registerBidder(spec); diff --git a/modules/h12mediaBidAdapter.md b/modules/h12mediaBidAdapter.md new file mode 100644 index 00000000000..7430bf11b90 --- /dev/null +++ b/modules/h12mediaBidAdapter.md @@ -0,0 +1,48 @@ +# Overview + +Module Name: H12 Media Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@h12-media.com + +# Description + +Module that connects to H12 Media demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'div-1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: "h12media", + params: { + pubid: '123', + sizes: '300x250', + } + } + ] + },{ + code: 'div-2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [ + { + bidder: "h12media", + params: { + pubid: '123', + placementid: '321' + } + } + ] + } + ]; +``` diff --git a/modules/hpmdnetworkBidAdapter.js b/modules/hpmdnetworkBidAdapter.js index 2642a0c9f65..5cc28ab6362 100644 --- a/modules/hpmdnetworkBidAdapter.js +++ b/modules/hpmdnetworkBidAdapter.js @@ -1,5 +1,5 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'hpmdnetwork'; const BIDDER_CODE_ALIAS = 'hpmd'; diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js new file mode 100644 index 00000000000..5671756e0b2 --- /dev/null +++ b/modules/hybridBidAdapter.js @@ -0,0 +1,198 @@ +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { auctionManager } from '../src/auctionManager.js' +import { BANNER, VIDEO } from '../src/mediaTypes.js' +import {Renderer} from '../src/Renderer.js'; +import find from 'core-js-pure/features/array/find.js'; + +const BIDDER_CODE = 'hybrid'; +const DSP_ENDPOINT = 'https://hbe198.hybrid.ai/prebidhb'; +const TRAFFIC_TYPE_WEB = 1; +const PLACEMENT_TYPE_BANNER = 1; +const PLACEMENT_TYPE_VIDEO = 2; +const TTL = 60; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +const placementTypes = { + 'banner': PLACEMENT_TYPE_BANNER, + 'video': PLACEMENT_TYPE_VIDEO +}; + +function buildBidRequests(validBidRequests) { + return utils._map(validBidRequests, function(validBidRequest) { + const params = validBidRequest.params; + const bidRequest = { + bidId: validBidRequest.bidId, + transactionId: validBidRequest.transactionId, + sizes: validBidRequest.sizes, + placement: placementTypes[params.placement], + placeId: params.placeId + }; + + return bidRequest; + }) +} + +const outstreamRender = bid => { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: bid.vastXml + } + }); + }); +} + +const createRenderer = (bid) => { + const renderer = Renderer.install({ + targetId: bid.adUnitCode, + url: RENDERER_URL, + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + +function buildBid(bidData) { + const bid = { + requestId: bidData.bidId, + cpm: bidData.price, + width: bidData.width, + height: bidData.height, + creativeId: bidData.bidId, + currency: bidData.currency, + netRevenue: true, + ttl: TTL + }; + + if (bidData.placement === PLACEMENT_TYPE_VIDEO) { + bid.vastXml = bidData.content; + bid.mediaType = VIDEO; + + let adUnit = find(auctionManager.getAdUnits(), function (unit) { + return unit.transactionId === bidData.transactionId; + }); + + if (adUnit) { + bid.width = adUnit.mediaTypes.video.playerSize[0][0]; + bid.height = adUnit.mediaTypes.video.playerSize[0][1]; + + if (adUnit.mediaTypes.video.context === 'outstream') { + bid.renderer = createRenderer(bid); + } + } + } else { + bid.ad = bidData.content; + bid.mediaType = BANNER; + } + + return bid; +} + +function getMediaTypeFromBid(bid) { + return bid.mediaTypes && Object.keys(bid.mediaTypes)[0] +} + +function hasVideoMandatoryParams(mediaTypes) { + const isHasVideoContext = !!mediaTypes.video && (mediaTypes.video.context === 'instream' || mediaTypes.video.context === 'outstream'); + + const isPlayerSize = + !!utils.deepAccess(mediaTypes, 'video.playerSize') && + utils.isArray(utils.deepAccess(mediaTypes, 'video.playerSize')); + + return isHasVideoContext && isPlayerSize; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + placementTypes: placementTypes, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid(bid) { + return ( + !!bid.params.placeId && + !!bid.params.placement && + ( + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'banner') || + (getMediaTypeFromBid(bid) === VIDEO && bid.params.placement === 'video' && hasVideoMandatoryParams(bid.mediaTypes)) + ) + ); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests(validBidRequests, bidderRequest) { + const payload = { + url: bidderRequest.refererInfo.referer, + cmp: !!bidderRequest.gdprConsent, + trafficType: TRAFFIC_TYPE_WEB, + bidRequests: buildBidRequests(validBidRequests) + }; + + if (payload.cmp) { + const gdprApplies = bidderRequest.gdprConsent.gdprApplies; + if (gdprApplies !== undefined) payload['ga'] = gdprApplies; + payload['cs'] = bidderRequest.gdprConsent.consentString; + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: DSP_ENDPOINT, + data: payloadString, + options: { + contentType: 'application/json' + } + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + let bidRequests = JSON.parse(bidRequest.data).bidRequests; + const serverBody = serverResponse.body; + + if (serverBody && serverBody.bids && utils.isArray(serverBody.bids)) { + return utils._map(serverBody.bids, function(bid) { + let rawBid = find(bidRequests, function (item) { + return item.bidId === bid.bidId; + }); + bid.placement = rawBid.placement; + bid.transactionId = rawBid.transactionId; + bid.placeId = rawBid.placeId; + return buildBid(bid); + }); + } else { + return []; + } + } + +} +registerBidder(spec); diff --git a/modules/hybridBidAdapter.md b/modules/hybridBidAdapter.md new file mode 100644 index 00000000000..245f010970a --- /dev/null +++ b/modules/hybridBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + + +**Module Name**: Hybrid.ai Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@hybrid.ai + +# Description + +You can use this adapter to get a bid from Hybrid.ai + +About us: https://hybrid.ai + +## Sample Banner Ad Unit + +```js +var adUnits = [{ + code: 'banner_ad_unit', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [{ + bidder: "hybrid", + params: { + placement: "banner", // required + placeId: "5af45ad34d506ee7acad0c26" // required + } + }] +}]; +``` + +## Sample Video Ad Unit + +```js +var adUnits = [{ + code: 'video_ad_unit', + mediaTypes: { + video: { + context: 'outstream', // required + playerSize: [640, 480] // required + } + }, + bids: [{ + bidder: 'hybrid', + params: { + placement: "video", // required + placeId: "5af45ad34d506ee7acad0c26" // required + } + }] +}]; +``` + diff --git a/modules/iasBidAdapter.js b/modules/iasBidAdapter.js index 3115daeb56b..733c123e769 100644 --- a/modules/iasBidAdapter.js +++ b/modules/iasBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ias'; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 6ed56b89d6d..ae3c7e55863 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -1,13 +1,22 @@ /** * This module adds ID5 to the User ID module * The {@link module:modules/userId} module is required - * @module modules/unifiedIdSystem + * @module modules/id5IdSystem * @requires module:modules/userId */ -import * as utils from '../src/utils' -import {ajax} from '../src/ajax'; -import {submodule} from '../src/hook'; +import * as utils from '../src/utils.js' +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'id5Id'; +const GVLID = 131; +const BASE_NB_COOKIE_NAME = 'id5id.1st'; +const NB_COOKIE_EXP_DAYS = (30 * 24 * 60 * 60 * 1000); // 30 days + +const storage = getStorageManager(GVLID, MODULE_NAME); /** @type {Submodule} */ export const id5IdSubmodule = { @@ -16,6 +25,13 @@ export const id5IdSubmodule = { * @type {string} */ name: 'id5Id', + + /** + * Vendor id of ID5 + * @type {Number} + */ + gvlid: GVLID, + /** * decode the stored id value for passing to bid requests * @function decode @@ -23,41 +39,114 @@ export const id5IdSubmodule = { * @returns {(Object|undefined)} */ decode(value) { - return (value && typeof value['ID5ID'] === 'string') ? { 'id5id': value['ID5ID'] } : undefined; + if (value && typeof value.ID5ID === 'string') { + // don't lose our legacy value from cache + return { 'id5id': value.ID5ID }; + } else if (value && typeof value.universal_uid === 'string') { + return { 'id5id': value.universal_uid }; + } else { + return undefined; + } }, + /** * performs action to obtain id and return a value in the callback's response argument - * @function + * @function getId * @param {SubmoduleParams} [configParams] * @param {ConsentData} [consentData] * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ getId(configParams, consentData, cacheIdObj) { - if (!configParams || typeof configParams.partner !== 'number') { - utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); + if (!hasRequiredParams(configParams)) { return undefined; } const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; const gdprConsentString = hasGdpr ? consentData.consentString : ''; - const storedUserId = this.decode(cacheIdObj); - const url = `https://id5-sync.com/g/v1/${configParams.partner}.json?1puid=${storedUserId ? storedUserId.id5id : ''}&gdpr=${hasGdpr}&gdpr_consent=${gdprConsentString}`; + const url = `https://id5-sync.com/g/v2/${configParams.partner}.json?gdpr_consent=${gdprConsentString}&gdpr=${hasGdpr}`; + const referer = getRefererInfo(); + const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : ''; + const pubId = (cacheIdObj && cacheIdObj.ID5ID) ? cacheIdObj.ID5ID : ''; // TODO: remove when 1puid isn't needed + const data = { + 'partner': configParams.partner, + '1puid': pubId, // TODO: remove when 1puid isn't needed + 'nbPage': incrementNb(configParams), + 'o': 'pbjs', + 'pd': configParams.pd || '', + 'rf': referer.referer, + 's': signature, + 'top': referer.reachedTop ? 1 : 0, + 'u': referer.stack[0] || window.location.href, + 'v': '$prebid.version$' + }; const resp = function (callback) { - ajax(url, response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + resetNb(configParams); + } catch (error) { + utils.logError(error); + } } + callback(responseObj); + }, + error: error => { + utils.logError(`id5Id: ID fetch encountered an error`, error); + callback(); } - callback(responseObj); - }, undefined, { method: 'GET', withCredentials: true }); + }; + ajax(url, callbacks, JSON.stringify(data), { method: 'POST', withCredentials: true }); }; return {callback: resp}; + }, + + /** + * Similar to Submodule#getId, this optional method returns response to for id that exists already. + * If IdResponse#id is defined, then it will be written to the current active storage even if it exists already. + * If IdResponse#callback is defined, then it'll called at the end of auction. + * It's permissible to return neither, one, or both fields. + * @function extendId + * @param {SubmoduleParams} configParams + * @param {Object} cacheIdObj - existing id, if any + * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. + */ + extendId(configParams, cacheIdObj) { + incrementNb(configParams); + return cacheIdObj; } }; +function hasRequiredParams(configParams) { + if (!configParams || typeof configParams.partner !== 'number') { + utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); + return false; + } + return true; +} +function nbCookieName(configParams) { + return hasRequiredParams(configParams) ? `${BASE_NB_COOKIE_NAME}_${configParams.partner}_nb` : undefined; +} +function nbCookieExpStr(expDays) { + return (new Date(Date.now() + expDays)).toUTCString(); +} +function storeNbInCookie(configParams, nb) { + storage.setCookie(nbCookieName(configParams), nb, nbCookieExpStr(NB_COOKIE_EXP_DAYS), 'Lax'); +} +function getNbFromCookie(configParams) { + const cacheNb = storage.getCookie(nbCookieName(configParams)); + return (cacheNb) ? parseInt(cacheNb) : 0; +} +function incrementNb(configParams) { + const nb = (getNbFromCookie(configParams) + 1); + storeNbInCookie(configParams, nb); + return nb; +} +function resetNb(configParams) { + storeNbInCookie(configParams, 0); +} + submodule('userId', id5IdSubmodule); diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index ffbfe466035..c516c06d11a 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -7,7 +7,7 @@ import * as utils from '../src/utils.js' import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook'; +import {submodule} from '../src/hook.js'; /** @type {Submodule} */ export const identityLinkSubmodule = { @@ -42,38 +42,48 @@ export const identityLinkSubmodule = { // use protocol relative urls for http or https const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? '&ct=1&cv=' + gdprConsentString : ''}`; let resp; - // if ats library is initialised, use it to retrieve envelope. If not use standard third party endpoint - if (window.ats) { - resp = function(callback) { + resp = function(callback) { + // Check ats during callback so it has a chance to initialise. + // If ats library is available, use it to retrieve envelope. If not use standard third party endpoint + if (window.ats) { + utils.logInfo('ATS exists!'); window.ats.retrieveEnvelope(function (envelope) { if (envelope) { + utils.logInfo('An envelope can be retrieved from ATS!'); callback(JSON.parse(envelope).envelope); } else { getEnvelope(url, callback); } }); - } - } else { - resp = function (callback) { + } else { getEnvelope(url, callback); } - } + }; + return {callback: resp}; } -} +}; // return envelope from third party endpoint function getEnvelope(url, callback) { - ajax(url, response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); + utils.logInfo('A 3P retrieval is attempted!'); + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logInfo(error); + } } + callback((responseObj && responseObj.envelope) ? responseObj.envelope : ''); + }, + error: error => { + utils.logInfo(`identityLink: ID fetch encountered an error`, error); + callback(); } - callback(responseObj.envelope); - }, undefined, {method: 'GET', withCredentials: true}); + }; + ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); } submodule('userId', identityLinkSubmodule); diff --git a/modules/imonomyBidAdapter.js b/modules/imonomyBidAdapter.js index 91086c2dd32..b9205943f65 100644 --- a/modules/imonomyBidAdapter.js +++ b/modules/imonomyBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'imonomy'; const ENDPOINT = 'https://b.imonomy.com/openrtb/hb/00000'; diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index cd7ab1bd50b..3c000258ede 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,13 +1,16 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { config } from '../src/config'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; const BIDDER_CODE = 'improvedigital'; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const spec = { - version: '6.0.1', + version: '7.1.0', code: BIDDER_CODE, + gvlid: 253, aliases: ['id'], supportedMediaTypes: [BANNER, NATIVE, VIDEO], @@ -43,6 +46,10 @@ export const spec = { requestParameters.gdpr = bidderRequest.gdprConsent.consentString; } + if (bidderRequest && bidderRequest.uspConsent) { + requestParameters.usPrivacy = bidderRequest.uspConsent; + } + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { requestParameters.referrer = bidderRequest.refererInfo.referer; } @@ -57,7 +64,7 @@ export const spec = { if (requestObj.errors && requestObj.errors.length > 0) { utils.logError('ID WARNING 0x01'); } - + requestObj.requests.forEach(request => request.bidderRequest = bidderRequest); return requestObj.requests; }, @@ -67,7 +74,7 @@ export const spec = { * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse, bidRequest) { + interpretResponse: function (serverResponse, {bidderRequest}) { const bids = []; utils._each(serverResponse.body.bid, function (bidObject) { if (!bidObject.price || bidObject.price === null || @@ -75,7 +82,7 @@ export const spec = { (!bidObject.adm && !bidObject.native)) { return; } - + const bidRequest = utils.getBidRequest(bidObject.id, [bidderRequest]); const bid = {}; if (bidObject.native) { @@ -90,6 +97,14 @@ export const spec = { } else if (bidObject.ad_type && bidObject.ad_type === 'video') { bid.vastXml = bidObject.adm; bid.mediaType = VIDEO; + if (isOutstreamVideo(bidRequest)) { + bid.adResponse = { + content: bid.vastXml, + height: bidObject.h, + width: bidObject.w + }; + bid.renderer = createRenderer(bidRequest); + } } else { // Banner let nurl = ''; @@ -108,7 +123,7 @@ export const spec = { // Deal ID. Composite ads can have multiple line items and the ID of the first // dealID line item will be used. - if (utils.isNumber(bidObject.lid) && bidObject.buying_type === 'deal_id') { + if (utils.isNumber(bidObject.lid) && bidObject.buying_type && bidObject.buying_type !== 'rtb') { bid.dealId = bidObject.lid; } else if (Array.isArray(bidObject.lid) && Array.isArray(bidObject.buying_type) && @@ -116,7 +131,7 @@ export const spec = { let isDeal = false; bidObject.buying_type.forEach((bt, i) => { if (isDeal) return; - if (bt === 'deal_id') { + if (bt && bt !== 'rtb') { isDeal = true; bid.dealId = bidObject.lid[i]; } @@ -132,10 +147,6 @@ export const spec = { if (!bid.width || !bid.height) { bid.width = 1; bid.height = 1; - if (bidRequest.sizes) { - bid.width = bidRequest.sizes[0][0]; - bid.height = bidRequest.sizes[0][1]; - } } bids.push(bid); @@ -171,9 +182,47 @@ export const spec = { }; function isInstreamVideo(bid) { + const mediaTypes = Object.keys(utils.deepAccess(bid, 'mediaTypes', {})); + const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + return bid.mediaType === 'video' || (mediaTypes.length === 1 && videoMediaType && context !== 'outstream'); +} + +function isOutstreamVideo(bid) { const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - return bid.mediaType === 'video' || (videoMediaType && context !== 'outstream'); + return videoMediaType && context === 'outstream'; +} + +function outstreamRender(bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, handleOutstreamRendererEvents.bind(null, bid)); + }); +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); +} + +function createRenderer(bidRequest) { + const renderer = Renderer.install({ + id: bidRequest.adUnitCode, + url: RENDERER_URL, + loaded: false, + config: utils.deepAccess(bidRequest, 'renderer.options'), + adUnitCode: bidRequest.adUnitCode + }); + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; } function getNormalizedBidRequest(bid) { @@ -213,7 +262,7 @@ function getNormalizedBidRequest(bid) { normalizedBidRequest.keyValues = keyValues; } - if (config.getConfig('improvedigital.usePrebidSizes') === true && !isInstreamVideo(bid) && bid.sizes && bid.sizes.length > 0) { + if (config.getConfig('improvedigital.usePrebidSizes') === true && !isInstreamVideo(bid) && !isOutstreamVideo(bid) && bid.sizes && bid.sizes.length > 0) { normalizedBidRequest.format = bid.sizes; } else if (singleSizeFilter && singleSizeFilter.w && singleSizeFilter.h) { normalizedBidRequest.size = {}; @@ -343,7 +392,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { AD_SERVER_BASE_URL: 'ice.360yield.com', END_POINT: endPoint || 'hb', AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-6.2.0', + CLIENT_VERSION: 'JS-6.3.0', MAX_URL_LENGTH: 2083, ERROR_CODES: { MISSING_PLACEMENT_PARAMS: 2, @@ -497,6 +546,9 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (requestParameters.gdpr || requestParameters.gdpr === 0) { impressionBidRequestObject.gdpr = requestParameters.gdpr; } + if (requestParameters.usPrivacy) { + impressionBidRequestObject.us_privacy = requestParameters.usPrivacy; + } if (requestParameters.schain) { impressionBidRequestObject.schain = requestParameters.schain; } diff --git a/modules/innityBidAdapter.js b/modules/innityBidAdapter.js new file mode 100644 index 00000000000..aae79818daf --- /dev/null +++ b/modules/innityBidAdapter.js @@ -0,0 +1,58 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'innity'; +const ENDPOINT = 'https://as.innity.com/synd/'; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.pub && bid.params.zone); + }, + buildRequests: function(validBidRequests, bidderRequest) { + let refererInfo = ''; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo.referer || ''; + } + return validBidRequests.map(bidRequest => { + let parseSized = utils.parseSizesInput(bidRequest.sizes); + let arrSize = parseSized[0].split('x'); + return { + method: 'GET', + url: ENDPOINT, + data: { + cb: utils.timestamp(), + ver: 2, + hb: 1, + output: 'js', + pub: bidRequest.params.pub, + zone: bidRequest.params.zone, + url: encodeURIComponent(refererInfo), + width: arrSize[0], + height: arrSize[1], + vpw: window.screen.width, + vph: window.screen.height, + callback: 'json', + callback_uid: bidRequest.bidId, + auction: bidRequest.auctionId, + }, + }; + }); + }, + interpretResponse: function(serverResponse, request) { + const res = serverResponse.body; + const bidResponse = { + requestId: res.callback_uid, + cpm: parseFloat(res.cpm) / 100, + width: res.width, + height: res.height, + creativeId: res.creative_id, + currency: 'USD', + netRevenue: true, + ttl: 60, + ad: '' + res.tag, + }; + return [bidResponse]; + } +} +registerBidder(spec); diff --git a/modules/innityBidAdapter.md b/modules/innityBidAdapter.md index b359a591ce8..1751df0edf6 100644 --- a/modules/innityBidAdapter.md +++ b/modules/innityBidAdapter.md @@ -11,8 +11,12 @@ Innity Bidder Adapter for Prebid.js. # Test Parameters ``` var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - sizes: [[300, 250]], + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, bids: [{ bidder: 'innity', params: { diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js new file mode 100644 index 00000000000..2a55b5280db --- /dev/null +++ b/modules/inskinBidAdapter.js @@ -0,0 +1,374 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'inskin'; + +const CONFIG = { + 'inskin': { + 'BASE_URI': 'https://mfad.inskinad.com/api/v2' + } +}; + +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.networkId && bid.params.siteId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - the full bidder request object + * @return ServerRequest Info describing the request to the server. + */ + + buildRequests: function(validBidRequests, bidderRequest) { + // 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; + } + + let ENDPOINT_URL; + + const data = Object.assign({ + placements: [], + time: Date.now(), + user: {}, + url: bidderRequest.refererInfo.referer, + enableBotFiltering: true, + includePricingData: true, + parallel: true + }, validBidRequests[0].params); + + data.keywords = data.keywords || []; + const restrictions = []; + + if (bidderRequest && bidderRequest.gdprConsent) { + data.consent = { + gdprVendorId: 150, + gdprConsentString: bidderRequest.gdprConsent.consentString, + // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true + gdprConsentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + }; + + if (bidderRequest.gdprConsent.apiVersion === 2) { + const purposes = [ + {id: 1, kw: 'nocookies'}, + {id: 2, kw: 'nocontext'}, + {id: 3, kw: 'nodmp'}, + {id: 4, kw: 'nodata'}, + {id: 7, kw: 'noclicks'}, + {id: 9, kw: 'noresearch'} + ]; + + const d = bidderRequest.gdprConsent.vendorData; + + if (d) { + if (d.purposeOneTreatment) { + data.keywords.push('cst-nodisclosure'); + restrictions.push('nodisclosure'); + } + + purposes.map(p => { + if (!checkConsent(p.id, d)) { + data.keywords.push('cst-' + p.kw); + restrictions.push(p.kw); + } + }); + } + } + } + + validBidRequests.map(bid => { + let config = CONFIG[bid.bidder]; + ENDPOINT_URL = config.BASE_URI; + + const placement = Object.assign({ + divName: bid.bidId, + adTypes: bid.adTypes || getSize(bid.sizes), + eventIds: [40, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295] + }, bid.params); + + placement.adTypes.push(5, 9, 163, 2163, 3006); + + if (restrictions.length) { + placement.properties = placement.properties || {}; + placement.properties.restrictions = restrictions; + } + + if (placement.networkId && placement.siteId) { + data.placements.push(placement); + } + }); + + ret.data = JSON.stringify(data); + ret.bidRequest = validBidRequests; + ret.url = ENDPOINT_URL; + + 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 bidResponses = []; + let bidsMap = {}; + + bids = bidRequest.bidRequest; + + serverResponse = (serverResponse || {}).body; + for (let i = 0; i < bids.length; i++) { + bid = {}; + bidObj = bids[i]; + bidId = bidObj.bidId; + + bidsMap[bidId] = bidObj; + + if (serverResponse) { + const decision = serverResponse.decisions && serverResponse.decisions[bidId]; + const data = decision && decision.contents && decision.contents[0] && decision.contents[0].data; + const pubCPM = data && data.customData && data.customData.pubCPM; + const clearPrice = decision && decision.pricing && decision.pricing.clearPrice; + const price = pubCPM || clearPrice; + + if (decision && price) { + decision.impressionUrl += ('&property:pubcpm=' + price); + bidObj.price = price; + + bid.requestId = bidId; + bid.cpm = price; + bid.width = decision.width; + bid.height = decision.height; + bid.ad = retrieveAd(bidId, decision); + bid.currency = 'USD'; + bid.creativeId = decision.adId; + bid.ttl = 360; + bid.netRevenue = true; + + bidResponses.push(bid); + } + } + } + + if (bidResponses.length) { + window.addEventListener('message', function(e) { + if (!e.data || e.data.from !== 'ism-bid') { + return; + } + + const decision = serverResponse.decisions && serverResponse.decisions[e.data.bidId]; + if (!decision) { + return; + } + + const id = 'ism_tag_' + Math.floor((Math.random() * 10e16)); + window[id] = { + plr_AdSlot: e.source.frameElement, + bidId: e.data.bidId, + bidPrice: bidsMap[e.data.bidId].price, + serverResponse + }; + const script = document.createElement('script'); + script.src = 'https://cdn.inskinad.com/isfe/publishercode/' + bidsMap[e.data.bidId].params.siteId + '/default.js?autoload&id=' + id; + document.getElementsByTagName('head')[0].appendChild(script); + }); + } + + return bidResponses; + }, + + getUserSyncs: function(syncOptions) { + const userSyncs = []; + + if (syncOptions.pixelEnabled) { + userSyncs.push({ + type: 'image', + url: 'https://e.serverbid.com/udb/9969/match?redir=https%3A%2F%2Fmfad.inskinad.com%2Fudb%2F9874%2Fpool%2Fset%2Fi.gif%3FpoolId%3D9969%26poolKey%3D' + }); + userSyncs.push({ + type: 'image', + url: 'https://ssum.casalemedia.com/usermatchredir?s=185638&cb=https%3A%2F%2Fmfad.inskinad.com%2Fudb%2F9874%2Fsync%2Fi.gif%3FpartnerId%3D1%26userId%3D' + }); + } + + if (syncOptions.iframeEnabled) { + userSyncs.push({ + type: 'iframe', + url: 'https://ssum-sec.casalemedia.com/usermatch?s=184665&cb=https%3A%2F%2Fmfad.inskinad.com%2Fudb%2F9874%2Fsync%2Fi.gif%3FpartnerId%3D1%26userId%3D' + }); + } + + return userSyncs; + } +}; + +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 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; +} + +function retrieveAd(bidId, decision) { + return " ``` +# Ad Unit and Setup: For Testing (Video Instream) -# Ad Unit and Setup: For Testing (Native) +```html + + + +``` +# Ad Unit and Setup: For Testing (Video Outstream) ```html +``` + +# Ad Unit and Setup: For Testing (Native) + +```html + + + + diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js new file mode 100644 index 00000000000..288526d3cc5 --- /dev/null +++ b/modules/mediasquareBidAdapter.js @@ -0,0 +1,153 @@ +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'mediasquare'; +const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' +const BIDDER_URL_TEST = 'https://bidder-test.mediasquare.fr/' +const BIDDER_ENDPOINT_AUCTION = 'msq_prebid'; +const BIDDER_ENDPOINT_SYNC = 'cookie_sync'; +const BIDDER_ENDPOINT_WINNING = 'winning'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['msq'], // short code + supportedMediaTypes: [BANNER], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.owner && bid.params.code); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + let codes = []; + let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; + const test = config.getConfig('debug') ? 1 : 0; + let adunitValue = null; + Object.keys(validBidRequests).forEach(key => { + adunitValue = validBidRequests[key]; + codes.push({ + owner: adunitValue.params.owner, + code: adunitValue.params.code, + adunit: adunitValue.adUnitCode, + bidId: adunitValue.bidId, + auctionId: adunitValue.auctionId, + transactionId: adunitValue.transactionId, + mediatypes: adunitValue.mediaTypes + }); + }); + const payload = { + codes: codes, + referer: encodeURIComponent(bidderRequest.refererInfo.referer) + }; + if (bidderRequest) { // modules informations (gdpr, ccpa, schain, userId) + if (bidderRequest.gdprConsent) { + payload.gdpr = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + if (bidderRequest.schain) { payload.schain = bidderRequest.schain; } + if (bidderRequest.userId) { payload.userId = bidderRequest.userId; } + }; + if (test) { payload.debug = true; } + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: endpoint + BIDDER_ENDPOINT_AUCTION, + data: payloadString, + }; + }, + /** + * 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 headerValue = serverResponse.headers.get('some-response-header'); + const bidResponses = []; + let bidResponse = null; + let value = null; + if (serverBody.hasOwnProperty('responses')) { + Object.keys(serverBody['responses']).forEach(key => { + value = serverBody['responses'][key]; + bidResponse = { + requestId: value['bid_id'], + cpm: value['cpm'], + width: value['width'], + height: value['height'], + creativeId: value['creative_id'], + currency: value['currency'], + netRevenue: value['net_revenue'], + ttl: value['ttl'], + ad: value['ad'], + mediasquare: { + 'bidder': value['bidder'], + 'code': value['code'] + } + }; + if (value.hasOwnProperty('deal_id')) { bidResponse['dealId'] = value['deal_id']; } + bidResponses.push(bidResponse); + }); + } + return bidResponses; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + let params = ''; + let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; + if (serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { + return serverResponses[0].body.cookies; + } else { + if (gdprConsent && typeof gdprConsent.consentString === 'string') { params += typeof gdprConsent.gdprApplies === 'boolean' ? `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}` : `&gdpr_consent=${gdprConsent.consentString}`; } + if (uspConsent && typeof uspConsent === 'string') { params += '&uspConsent=' + uspConsent } + return { + type: 'iframe', + url: endpoint + BIDDER_ENDPOINT_SYNC + '?type=iframe' + params + }; + } + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function(bid) { + // fires a pixel to confirm a winning bid + let params = []; + let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; + let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'auctionId', 'requestId'] + if (bid.hasOwnProperty('mediasquare')) { + if (bid['mediasquare'].hasOwnProperty('bidder')) { params.push('bidder=' + bid['mediasquare']['bidder']); } + if (bid['mediasquare'].hasOwnProperty('code')) { params.push('code=' + bid['mediasquare']['code']); } + }; + for (let i = 0; i < paramsToSearchFor.length; i++) { + if (bid.hasOwnProperty(paramsToSearchFor[i])) { params.push(paramsToSearchFor[i] + '=' + bid[paramsToSearchFor[i]]); } + } + if (params.length > 0) { params = '?' + params.join('&'); } + ajax(endpoint + BIDDER_ENDPOINT_WINNING + params, null); + return true; + } + +} +registerBidder(spec); diff --git a/modules/mediasquareBidAdapter.md b/modules/mediasquareBidAdapter.md new file mode 100644 index 00000000000..f9f8d4be908 --- /dev/null +++ b/modules/mediasquareBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: MediaSquare Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@mediasquare.fr +``` + +# Description + +Connects to Mediasquare network for bids. + +Mediasquare bid adapter supports Banner only for the time being. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'mediasquare', + params: { + owner: "test", + code: "publishername_atf_desktop_rg_pave" + } + }] + }, +]; +``` diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 154c00f6a06..957b9a1d703 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -1,8 +1,10 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; -import * as urlUtils from '../src/url'; -import {BANNER, NATIVE} from '../src/mediaTypes'; -import {config} from '../src/config'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); const DEFAULT_CUR = 'USD'; const BIDDER_CODE = 'mgid'; const ENDPOINT_URL = 'https://prebid.mgid.com/prebid/'; @@ -120,7 +122,7 @@ export const spec = { } const info = pageInfo(); const page = info.location || utils.deepAccess(bidderRequest, 'refererInfo.referer') || utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl'); - const hostname = urlUtils.parse(page).hostname; + const hostname = utils.parseUrl(page).hostname; let domain = extractDomainFromHost(hostname) || hostname; const accountId = setOnAny(validBidRequests, 'params.accountId'); const muid = getLocalStorageSafely('mgMuidn'); @@ -232,7 +234,7 @@ export const spec = { /\${AUCTION_PRICE}/, cpm ); - pixel(bid.nurl); + utils.triggerPixel(bid.nurl); } if (bid.isBurl) { if (bid.mediaType === BANNER) { @@ -245,7 +247,7 @@ export const spec = { /\${AUCTION_PRICE}/, cpm ); - pixel(bid.burl); + utils.triggerPixel(bid.burl); } } utils.logInfo(LOG_INFO_PREFIX + `onBidWon`); @@ -331,10 +333,6 @@ function extractDomainFromHost(pageHost) { return domain; } -function pixel(url) { - (document.createElement('IMG')).src = url; -} - function getLanguage() { const language = navigator.language ? 'language' : 'userLanguage'; const lang2 = navigator[language].split('-')[0]; @@ -346,7 +344,7 @@ function getLanguage() { function getLocalStorageSafely(key) { try { - return localStorage.getItem(key); + return storage.getDataFromLocalStorage(key); } catch (e) { return null; } @@ -354,7 +352,7 @@ function getLocalStorageSafely(key) { function setLocalStorageSafely(key, val) { try { - return localStorage.setItem(key, val); + return storage.setDataInLocalStorage(key, val); } catch (e) { return null; } diff --git a/modules/microadBidAdapter.js b/modules/microadBidAdapter.js index 0d4da3e72f7..9611946b495 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -1,5 +1,5 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'microad'; diff --git a/modules/mobfoxBidAdapter.js b/modules/mobfoxBidAdapter.js index 961cb97e8da..7c356e71089 100644 --- a/modules/mobfoxBidAdapter.js +++ b/modules/mobfoxBidAdapter.js @@ -1,4 +1,4 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const utils = require('../src/utils.js'); const BIDDER_CODE = 'mobfox'; diff --git a/modules/mobsmartBidAdapter.js b/modules/mobsmartBidAdapter.js index ba02aef38ef..e5ff38ec69a 100644 --- a/modules/mobsmartBidAdapter.js +++ b/modules/mobsmartBidAdapter.js @@ -1,5 +1,5 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'mobsmart'; const ENDPOINT = 'https://prebid.mobsmart.net/prebid/endpoint'; diff --git a/modules/my6senseBidAdapter.js b/modules/my6senseBidAdapter.js index 29a593fc467..018baa37461 100644 --- a/modules/my6senseBidAdapter.js +++ b/modules/my6senseBidAdapter.js @@ -1,6 +1,6 @@ -import { BANNER, NATIVE } from '../src/mediaTypes'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; -const {registerBidder} = require('../src/adapters/bidderFactory'); +const {registerBidder} = require('../src/adapters/bidderFactory.js'); const BIDDER_CODE = 'my6sense'; const END_POINT = 'https://hb.mynativeplatform.com/pub2/web/v1.15.0/hbwidget.json'; const END_POINT_METHOD = 'POST'; diff --git a/modules/mytargetBidAdapter.js b/modules/mytargetBidAdapter.js index 005ef9e6a20..bcf8e7ad17f 100644 --- a/modules/mytargetBidAdapter.js +++ b/modules/mytargetBidAdapter.js @@ -1,7 +1,6 @@ -import * as utils from '../src/utils'; -import * as url from '../src/url'; -import { config } from '../src/config'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'mytarget'; const BIDDER_URL = 'https://ad.mail.ru/hbid_prebid/'; @@ -29,7 +28,7 @@ function getSiteName(referrer) { let sitename = config.getConfig('mytarget.sitename'); if (!sitename) { - sitename = url.parse(referrer).hostname; + sitename = utils.parseUrl(referrer).hostname; } return sitename; diff --git a/modules/nafdigitalBidAdapter.js b/modules/nafdigitalBidAdapter.js index 7bbfd8b38dd..d64e079b52a 100644 --- a/modules/nafdigitalBidAdapter.js +++ b/modules/nafdigitalBidAdapter.js @@ -1,8 +1,7 @@ -import * as utils from '../src/utils'; -import * as url from '../src/url'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; -import { config } from '../src/config'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'nafdigital'; const URL = 'https://nafdigitalbidder.com/hb'; @@ -56,7 +55,7 @@ function buildRequests(bidReqs, bidderRequest) { id: utils.getUniqueIdentifierStr(), imp: nafdigitalImps, site: { - domain: url.parse(referrer).host, + domain: utils.parseUrl(referrer).host, page: referrer, publisher: { id: publisherId diff --git a/modules/nanointeractiveBidAdapter.js b/modules/nanointeractiveBidAdapter.js index 13944cd5a35..42a343efc03 100644 --- a/modules/nanointeractiveBidAdapter.js +++ b/modules/nanointeractiveBidAdapter.js @@ -1,6 +1,9 @@ -import * as utils from '../src/utils'; -import {config} from '../src/config'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); export const BIDDER_CODE = 'nanointeractive'; export const END_POINT_URL = 'https://ad.audiencemanager.de'; @@ -97,7 +100,7 @@ function createSingleBidRequest(bid, bidderRequest) { function createSingleBidResponse(serverBid) { if (serverBid.userId) { - localStorage.setItem('lsUserId', serverBid.userId); + storage.setDataInLocalStorage('lsUserId', serverBid.userId); } return { requestId: serverBid.id, @@ -147,8 +150,8 @@ function getEndpointUrl() { } function getLsUserId() { - if (localStorage.getItem('lsUserId') != null) { - return localStorage.getItem('lsUserId'); + if (storage.getDataFromLocalStorage('lsUserId') != null) { + return storage.getDataFromLocalStorage('lsUserId'); } return null; } diff --git a/modules/nasmediaAdmixerBidAdapter.js b/modules/nasmediaAdmixerBidAdapter.js new file mode 100644 index 00000000000..fd7a7baa58a --- /dev/null +++ b/modules/nasmediaAdmixerBidAdapter.js @@ -0,0 +1,85 @@ + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const ADMIXER_ENDPOINT = 'https://adn.admixer.co.kr:10443/prebid/ad_req'; +const BIDDER_CODE = 'nasmediaAdmixer'; + +const DEFAULT_BID_TTL = 360; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_REVENUE = false; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!(bid && bid.params && bid.params.media_key && bid.params.adunit_id); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + const payload = { + media_key: bid.params.media_key, + adunit_id: bid.params.adunit_id, + req_id: bid.bidId, + referrer: bidderRequest.refererInfo.referer, + os: getOs(), + platform: getPlatform(), + }; + + return { + method: 'GET', + url: ADMIXER_ENDPOINT, + data: payload, + } + }) + }, + + 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 getOs() { + let ua = navigator.userAgent; + if (ua.match(/(iPhone|iPod|iPad)/)) { + return 'ios'; + } else if (ua.match(/Android/)) { + return 'android'; + } else if (ua.match(/Window/)) { + return 'windows'; + } else { + return 'etc'; + } +} + +function getPlatform() { + return (isMobile()) ? 'm_web' : 'pc_web'; +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent.toLowerCase()); +} + +registerBidder(spec); diff --git a/modules/nasmediaAdmixerBidAdapter.md b/modules/nasmediaAdmixerBidAdapter.md index 44797e413a8..096acf27f61 100644 --- a/modules/nasmediaAdmixerBidAdapter.md +++ b/modules/nasmediaAdmixerBidAdapter.md @@ -5,25 +5,29 @@ 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 + mediaTypes: { + banner: { // banner size + sizes: [[300, 250]] + } + }, bids: [ { bidder: 'nasmediaAdmixer', params: { - ax_key: 'ajj7jba3', //required parameter + media_key: '19038695', //required + adunit_id: '24190632', //required } } ] diff --git a/modules/netIdSystem.js b/modules/netIdSystem.js new file mode 100644 index 00000000000..cfe78d9f488 --- /dev/null +++ b/modules/netIdSystem.js @@ -0,0 +1,40 @@ +/** + * This module adds netId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/netIdSystem + * @requires module:modules/userId + */ + +import {submodule} from '../src/hook.js'; + +/** @type {Submodule} */ +export const netIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'netId', + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode(value) { + return (value && typeof value['netId'] === 'string') ? { 'netId': value['netId'] } : undefined; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [configParams] + * @param {ConsentData} [consentData] + * @param {(Object|undefined)} cacheIdObj + * @returns {IdResponse|undefined} + */ + getId(configParams) { + /* currently not possible */ + return {}; + } +}; + +submodule('userId', netIdSubmodule); diff --git a/modules/newborntownWebBidAdapter.js b/modules/newborntownWebBidAdapter.js index 103fac7ba6a..56c63e2bb4d 100644 --- a/modules/newborntownWebBidAdapter.js +++ b/modules/newborntownWebBidAdapter.js @@ -1,6 +1,9 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {BANNER, NATIVE} from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); const BIDDER_CODE = 'newborntownWeb'; const REQUEST_URL = 'https://us-west.solortb.com/adx/api/rtb?from=4' @@ -53,10 +56,10 @@ export const spec = { return null; } var guid; - if (localStorage.getItem('sax_user_id') == null) { - localStorage.setItem('sax_user_id', generateGUID()) + if (storage.getDataFromLocalStorage('sax_user_id') == null) { + storage.setDataInLocalStorage('sax_user_id', generateGUID()) } - guid = localStorage.getItem('sax_user_id') + guid = storage.getDataFromLocalStorage('sax_user_id') utils._each(validBidRequests, function(bidRequest) { const bidRequestObj = bidRequest.params var req = { diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 8093f6e8f7c..af1f0562ba4 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'nextMillennium'; const HOST = 'https://brainlyads.com'; diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js new file mode 100644 index 00000000000..02ebcd3f87a --- /dev/null +++ b/modules/nextrollBidAdapter.js @@ -0,0 +1,345 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; + +import find from 'core-js-pure/features/array/find.js'; + +const BIDDER_CODE = 'nextroll'; +const BIDDER_ENDPOINT = 'https://d.adroll.com/bid/prebid/'; +const ADAPTER_VERSION = 5; + +export const spec = { + code: BIDDER_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 (bidRequest) { + return bidRequest !== undefined && !!bidRequest.params && !!bidRequest.bidId; + }, + + /** + * 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) { + let topLocation = utils.parseUrl(utils.deepAccess(bidderRequest, 'refererInfo.referer')); + + return validBidRequests.map((bidRequest) => { + return { + method: 'POST', + options: { + withCredentials: true, + }, + url: BIDDER_ENDPOINT, + data: { + id: bidRequest.bidId, + imp: { + id: bidRequest.bidId, + bidfloor: utils.getBidIdParameter('bidfloor', bidRequest.params), + banner: _getBanner(bidRequest), + native: _getNative(utils.deepAccess(bidRequest, 'mediaTypes.native')), + ext: { + zone: { + id: utils.getBidIdParameter('zoneId', bidRequest.params) + }, + nextroll: { + adapter_version: ADAPTER_VERSION + } + } + }, + + user: _getUser(validBidRequests), + site: _getSite(bidRequest, topLocation), + seller: _getSeller(bidRequest), + device: _getDevice(bidRequest), + regs: _getRegs(bidderRequest) + } + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + if (!serverResponse.body) { + return []; + } else { + let response = serverResponse.body + let bids = response.seatbid.reduce((acc, seatbid) => acc.concat(seatbid.bid), []); + return bids.map((bid) => _buildResponse(response, bid)); + } + } +} + +function _getBanner(bidRequest) { + let sizes = _getSizes(bidRequest); + if (sizes === undefined) return undefined; + return {format: sizes}; +} + +function _getNative(mediaTypeNative) { + if (mediaTypeNative === undefined) return undefined; + let assets = _getNativeAssets(mediaTypeNative); + if (assets === undefined || assets.length == 0) return undefined; + return { + request: { + native: { + assets: assets + } + } + }; +} + +/* + id: Unique numeric id for the asset + kind: OpenRTB kind of asset. Supported: title, img and data. + key: Name of property that comes in the mediaType.native object. + type: OpenRTB type for that spefic kind of asset. + required: Overrides the asset required field configured, only overrides when is true. +*/ +const NATIVE_ASSET_MAP = [ + {id: 1, kind: 'title', key: 'title', required: true}, + {id: 2, kind: 'img', key: 'image', type: 3, required: true}, + {id: 3, kind: 'img', key: 'icon', type: 1}, + {id: 4, kind: 'img', key: 'logo', type: 2}, + {id: 5, kind: 'data', key: 'sponsoredBy', type: 1}, + {id: 6, kind: 'data', key: 'body', type: 2} +]; + +const ASSET_KIND_MAP = { + title: _getTitleAsset, + img: _getImageAsset, + data: _getDataAsset, +}; + +function _getAsset(mediaTypeNative, assetMap) { + const asset = mediaTypeNative[assetMap.key]; + if (asset === undefined) return undefined; + const assetFunc = ASSET_KIND_MAP[assetMap.kind]; + return { + id: assetMap.id, + required: (assetMap.required || !!asset.required) ? 1 : 0, + [assetMap.kind]: assetFunc(asset, assetMap) + }; +} + +function _getTitleAsset(title, _assetMap) { + return {len: title.len || 0}; +} + +function _getMinAspectRatio(aspectRatio, property) { + if (!utils.isPlainObject(aspectRatio)) return 1; + + const ratio = aspectRatio['ratio_' + property]; + const min = aspectRatio['min_' + property]; + + if (utils.isNumber(ratio)) return ratio; + if (utils.isNumber(min)) return min; + + return 1; +} + +function _getImageAsset(image, assetMap) { + const sizes = image.sizes; + const aspectRatio = image.aspect_ratios ? image.aspect_ratios[0] : undefined; + + return { + type: assetMap.type, + w: (sizes ? sizes[0] : undefined), + h: (sizes ? sizes[1] : undefined), + wmin: _getMinAspectRatio(aspectRatio, 'width'), + hmin: _getMinAspectRatio(aspectRatio, 'height'), + }; +} + +function _getDataAsset(data, assetMap) { + return { + type: assetMap.type, + len: data.len || 0 + }; +} + +function _getNativeAssets(mediaTypeNative) { + return NATIVE_ASSET_MAP + .map(assetMap => _getAsset(mediaTypeNative, assetMap)) + .filter(asset => asset !== undefined); +} + +function _getUser(requests) { + const id = utils.deepAccess(requests, '0.userId.nextroll'); + if (id === undefined) { + return; + } + + return { + ext: { + eid: [{ + 'source': 'nextroll', + id + }] + } + }; +} + +function _buildResponse(bidResponse, bid) { + let response = { + requestId: bidResponse.id, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + dealId: bidResponse.dealId, + currency: 'USD', + netRevenue: true, + ttl: 300 + }; + if (utils.isStr(bid.adm)) { + response.mediaType = BANNER; + response.ad = utils.replaceAuctionPrice(bid.adm, bid.price); + } else { + response.mediaType = NATIVE; + response.native = _getNativeResponse(bid.adm, bid.price); + } + return response; +} + +const privacyLink = 'https://info.evidon.com/pub_info/573'; +const privacyIcon = 'https://c.betrad.com/pub/icon1.png'; + +function _getNativeResponse(adm, price) { + let baseResponse = { + clickTrackers: (adm.link && adm.link.clicktrackers) || [], + jstracker: adm.jstracker || [], + clickUrl: utils.replaceAuctionPrice(adm.link.url, price), + impressionTrackers: adm.imptrackers.map(impTracker => utils.replaceAuctionPrice(impTracker, price)), + privacyLink: privacyLink, + privacyIcon: privacyIcon + }; + return adm.assets.reduce((accResponse, asset) => { + const assetMaps = NATIVE_ASSET_MAP.filter(assetMap => assetMap.id === asset.id && asset[assetMap.kind] !== undefined); + if (assetMaps.length === 0) return accResponse; + const assetMap = assetMaps[0]; + accResponse[assetMap.key] = _getAssetResponse(asset, assetMap); + return accResponse; + }, baseResponse); +} + +function _getAssetResponse(asset, assetMap) { + switch (assetMap.kind) { + case 'title': + return asset.title.text; + + case 'img': + return { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + + case 'data': + return asset.data.value; + } +} + +function _getSite(bidRequest, topLocation) { + return { + page: topLocation.href, + domain: topLocation.hostname, + publisher: { + id: utils.getBidIdParameter('publisherId', bidRequest.params) + } + }; +} + +function _getSeller(bidRequest) { + return { + id: utils.getBidIdParameter('sellerId', bidRequest.params) + }; +} + +function _getSizes(bidRequest) { + if (!utils.isArray(bidRequest.sizes)) { + return undefined; + } + return bidRequest.sizes.filter(_isValidSize).map(size => { + return { + w: size[0], + h: size[1] + } + }); +} + +function _isValidSize(size) { + const isNumber = x => typeof x === 'number'; + return (size.length === 2 && isNumber(size[0]) && isNumber(size[1])); +} + +function _getDevice(_bidRequest) { + return { + ua: navigator.userAgent, + language: navigator['language'], + os: _getOs(navigator.userAgent.toLowerCase()), + osv: _getOsVersion(navigator.userAgent) + }; +} + +function _getRegs(bidderRequest) { + if (!bidderRequest || !bidderRequest.uspConsent) { + return undefined; + } + return { + ext: { + us_privacy: bidderRequest.uspConsent + } + }; +} + +function _getOs(userAgent) { + const osTable = { + 'android': /android/i, + 'ios': /iphone|ipad/i, + 'mac': /mac/i, + 'linux': /linux/i, + 'windows': /windows/i + }; + + return find(Object.keys(osTable), os => { + if (userAgent.match(osTable[os])) { + return os; + } + }) || 'etc'; +} + +function _getOsVersion(userAgent) { + const clientStrings = [ + { s: 'Android', r: /Android/ }, + { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, + { s: 'Mac OS X', r: /Mac OS X/ }, + { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: 'Linux', r: /(Linux|X11)/ }, + { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, + { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, + { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, + { s: 'Windows Vista', r: /Windows NT 6.0/ }, + { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, + { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, + { s: 'UNIX', r: /UNIX/ }, + { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } + ]; + let cs = find(clientStrings, cs => cs.r.test(userAgent)); + return cs ? cs.s : 'unknown'; +} + +registerBidder(spec); diff --git a/modules/nextrollBidAdapter.md b/modules/nextrollBidAdapter.md new file mode 100644 index 00000000000..c706839c453 --- /dev/null +++ b/modules/nextrollBidAdapter.md @@ -0,0 +1,77 @@ +# Overview + +``` +Module Name: NextRoll Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@nextroll.com +``` + +# Description + +Module that connects to NextRoll's bidders. +The NextRoll bid adapter supports banner and native format. + +# Test Parameters + +## Banner Example +``` javascript +var adUnits = [ + { + code: 'div-1', + mediatypes: { + banner: {sizes: [[300, 250], [160, 600]]} + }, + bids: [{ + bidder: 'nextroll', + params: { + bidfloor: 1, + zoneId: "13144370", + publisherId: "publisherid", + sellerId: "sellerid" + } + }] + }, + { + code: 'div-2', + mediatypes: { + banner: { + sizes: [[728, 90], [970, 250]] + } + }, + bids: [{ + bidder: 'nextroll', + params: { + bidfloor: 2.3, + zoneId: "13144370", + publisherId: "publisherid", + sellerId: "sellerid" + } + }] + } +] +``` + +## Native Example +```javascript +var adUnits = [ + { + code: 'div-1', + mediaTypes: { + native: { + title: { required: true, len: 80 }, + image: { required: true, sizes: [728, 90] }, + sponsoredBy: { required: false, len: 20 } + } + }, + bids: [{ + bidder: 'nextroll', + params: { + bidfloor: 1, + zoneId: "13144370", + publisherId: "publisherId", + sellerId: "sellerId", + } + }] + } +]; +``` \ No newline at end of file diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index ca916fe9482..f1a9092e31d 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -1,11 +1,29 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); const BIDDER_CODE = 'nobid'; -window.nobidVersion = '1.1.0'; +window.nobidVersion = '1.2.6'; +window.nobid = window.nobid || {}; +window.nobid.bidResponses = window.nobid.bidResponses || {}; +window.nobid.timeoutTotal = 0; +window.nobid.bidWonTotal = 0; +window.nobid.refreshCount = 0; function log(msg, obj) { utils.logInfo('-NoBid- ' + msg, obj) } +function nobidSetCookie(cname, cvalue, hours) { + var d = new Date(); + d.setTime(d.getTime() + (hours * 60 * 60 * 1000)); + var expires = 'expires=' + d.toUTCString(); + storage.setCookie(cname, cvalue, expires); +} +function nobidGetCookie(cname) { + return storage.getCookie(cname); +} function nobidBuildRequests(bids, bidderRequest) { var serializeState = function(divIds, siteId, adunits) { var filterAdUnitsByIds = function(divIds, adUnits) { @@ -36,6 +54,28 @@ function nobidBuildRequests(bids, bidderRequest) { } return gdprConsent; } + var uspConsent = function(bidderRequest) { + var uspConsent = ''; + if (bidderRequest && bidderRequest.uspConsent) { + uspConsent = bidderRequest.uspConsent; + } + return uspConsent; + } + var schain = function(bids) { + if (bids && bids.length > 0) { + return bids[0].schain + } + return null; + } + var coppa = function() { + if (config.getConfig('coppa') === true) { + return {'coppa': true}; + } + if (bids && bids.length > 0) { + return bids[0].coppa + } + return null; + } var topLocation = function(bidderRequest) { var ret = ''; if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { @@ -58,9 +98,11 @@ function nobidBuildRequests(bids, bidderRequest) { }; var clientDim = function() { try { - return `${screen.width}x${screen.height}`; + var width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + var height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + return `${width}x${height}`; } catch (e) { - console.error(e); + utils.logWarn('Could not parse screen dimensions, error details:', e); } } var state = {}; @@ -75,6 +117,11 @@ function nobidBuildRequests(bids, bidderRequest) { state['lang'] = (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage; state['ref'] = document.referrer; state['gdpr'] = gdprConsent(bidderRequest); + state['usp'] = uspConsent(bidderRequest); + const sch = schain(bids); + if (sch) state['schain'] = sch; + const cop = coppa(); + if (cop) state['coppa'] = cop; return state; } function newAdunit(adunitObject, adunits) { @@ -109,9 +156,6 @@ function nobidBuildRequests(bids, bidderRequest) { } else { a.g = {}; } - if (adunitObject.companion) { - a.c = adunitObject.companion; - } if (adunitObject.div) { removeByAttrValue(adunits, 'd', adunitObject.div); } @@ -121,13 +165,26 @@ function nobidBuildRequests(bids, bidderRequest) { if (adunitObject.siteId) { a.sid = adunitObject.siteId; } - /* {"BIDDER_ID":{"WxH":"TAG_ID", "WxH":"TAG_ID"}} */ - if (adunitObject.rtb) { - a.rtb = adunitObject.rtb; + if (adunitObject.placementId) { + a.pid = adunitObject.placementId; + } + if (adunitObject.ad_type) { + a.at = adunitObject.ad_type; + } + if (adunitObject.params) { + a.params = adunitObject.params; } adunits.push(a); return adunits; } + if (typeof window.nobid.refreshLimit !== 'undefined') { + if (window.nobid.refreshLimit < window.nobid.refreshCount) return false; + } + let ublock = nobidGetCookie('_ublock'); + if (ublock) { + log('Request blocked for user. hours: ', ublock); + return false; + } /* DISCOVER SLOTS */ var divids = []; var siteId = 0; @@ -138,10 +195,25 @@ function nobidBuildRequests(bids, bidderRequest) { divids.push(divid); var sizes = bid.sizes; siteId = (typeof bid.params['siteId'] != 'undefined' && bid.params['siteId']) ? bid.params['siteId'] : siteId; - if (siteId && bid.params && bid.params.tags) { - newAdunit({div: divid, sizes: sizes, rtb: bid.params.tags, siteId: siteId}, adunits); - } else if (siteId) { - newAdunit({div: divid, sizes: sizes, siteId: siteId}, adunits); + var placementId = bid.params['placementId']; + + var adType = 'banner'; + const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + if (bid.mediaType === VIDEO || (videoMediaType && (context === 'instream' || context === 'outstream'))) { + adType = 'video'; + } + + if (siteId) { + newAdunit({ + div: divid, + sizes: sizes, + siteId: siteId, + placementId: placementId, + ad_type: adType, + params: bid.params + }, + adunits); } } if (siteId) { @@ -153,17 +225,27 @@ function nobidBuildRequests(bids, bidderRequest) { function nobidInterpretResponse(response, bidRequest) { var findBid = function(divid, bids) { for (var i = 0; i < bids.length; i++) { - if (bids[i].adUnitCode === divid) { + if (bids[i].adUnitCode == divid) { return bids[i]; } } return false; } + var setRefreshLimit = function(response) { + if (response && typeof response.rlimit !== 'undefined') window.nobid.refreshLimit = response.rlimit; + } + var setUserBlock = function(response) { + if (response && typeof response.ublock !== 'undefined') { + nobidSetCookie('_ublock', '1', response.ublock); + } + } + setRefreshLimit(response); + setUserBlock(response); var bidResponses = []; for (var i = 0; response.bids && i < response.bids.length; i++) { var bid = response.bids[i]; if (bid.bdrid < 100 || !bidRequest || !bidRequest.bidderRequest || !bidRequest.bidderRequest.bids) continue; - nobid.bidResponses['' + bid.id] = bid; + window.nobid.bidResponses['' + bid.id] = bid; var reqBid = findBid(bid.divid, bidRequest.bidderRequest.bids); if (!reqBid) continue; const bidResponse = { @@ -177,19 +259,25 @@ function nobidInterpretResponse(response, bidRequest) { netRevenue: true, ttl: 300, ad: bid.adm, - mediaType: BANNER + mediaType: bid.atype || BANNER, }; + if (bid.vastUrl) { + bidResponse.vastUrl = bid.vastUrl; + } + if (bid.vastXml) { + bidResponse.vastXml = bid.vastXml; + } + if (bid.videoCacheKey) { + bidResponse.videoCacheKey = bid.videoCacheKey; + } + bidResponses.push(bidResponse); } return bidResponses; }; -window.nobid = window.nobid || {}; -nobid.bidResponses = nobid.bidResponses || {}; -nobid.timeoutTotal = 0; -nobid.bidWonTotal = 0; -nobid.renderTag = function(doc, id, win) { +window.nobid.renderTag = function(doc, id, win) { log('nobid.renderTag()', id); - var bid = nobid.bidResponses['' + id]; + var bid = window.nobid.bidResponses['' + id]; if (bid && bid.adm2) { log('nobid.renderTag() found tag', id); var markup = bid.adm2; @@ -199,9 +287,38 @@ nobid.renderTag = function(doc, id, win) { } log('nobid.renderTag() tag NOT FOUND *ERROR*', id); } +window.addEventListener('message', function (event) { + let key = event.message ? 'message' : 'data'; + var msg = '' + event[key]; + if (msg.substring(0, 'nbTagRenderer.requestAdMarkup|'.length) === 'nbTagRenderer.requestAdMarkup|') { + log('Prebid received nbTagRenderer.requestAdMarkup event'); + var adId = msg.substring(msg.indexOf('|') + 1); + if (window.nobid && window.nobid.bidResponses) { + var bid = window.nobid.bidResponses['' + adId]; + if (bid && bid.adm2) { + var markup = null; + if (bid.is_combo && bid.adm_combo) { + for (var i in bid.adm_combo) { + var combo = bid.adm_combo[i]; + if (!combo.done) { + markup = combo.adm; + combo.done = true; + break; + } + } + } else { + markup = bid.adm2; + } + if (markup) { + event.source.postMessage('nbTagRenderer.renderAdInSafeFrame|' + markup, '*'); + } + } + } + } +}, false); export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -224,20 +341,21 @@ export const spec = { var env = (typeof utils.getParameterByName === 'function') && (utils.getParameterByName('nobid-env')); if (!env) ret = 'https://ads.servenobid.com/'; else if (env == 'beta') ret = 'https://beta.servenobid.com/'; - else if (env == 'dev') ret = 'https://localhost:8282/'; + else if (env == 'dev') ret = '//localhost:8282/'; else if (env == 'qa') ret = 'https://qa-ads.nobid.com/'; return ret; } var buildEndpoint = function() { return resolveEndpoint() + 'adreq?cb=' + Math.floor(Math.random() * 11000); } - log('buildRequests', validBidRequests); + log('validBidRequests', validBidRequests); if (!validBidRequests || validBidRequests.length <= 0) { log('Empty validBidRequests'); return; } const payload = nobidBuildRequests(validBidRequests, bidderRequest); if (!payload) return; + window.nobid.refreshCount++; const payloadString = JSON.stringify(payload).replace(/'|&|#/g, '') const endpoint = buildEndpoint(); return { @@ -254,8 +372,8 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { - log('interpretResponse', serverResponse); - log('interpretResponse', bidRequest); + log('interpretResponse -> serverResponse', serverResponse); + log('interpretResponse -> bidRequest', bidRequest); return nobidInterpretResponse(serverResponse.body, bidRequest); }, @@ -266,7 +384,7 @@ export const spec = { * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, usPrivacy) { if (syncOptions.iframeEnabled) { let params = ''; if (gdprConsent && typeof gdprConsent.consentString === 'string') { @@ -277,10 +395,26 @@ export const spec = { params += `?gdpr_consent=${gdprConsent.consentString}`; } } + if (usPrivacy) { + if (params.length > 0) params += '&'; + else params += '?'; + params += 'usp_consent=' + usPrivacy; + } return [{ type: 'iframe', - url: 'https://s3.amazonaws.com/nobid-public/sync.html' + params + url: 'https://public.servenobid.com/sync.html' + params }]; + } else if (syncOptions.pixelEnabled && serverResponses.length > 0) { + let syncs = []; + if (serverResponses[0].body.syncs && serverResponses[0].body.syncs.length > 0) { + serverResponses[0].body.syncs.forEach(element => { + syncs.push({ + type: 'image', + url: element + }); + }) + } + return syncs; } else { utils.logWarn('-NoBid- Please enable iframe based user sync.', syncOptions); return []; @@ -292,14 +426,14 @@ export const spec = { * @param {data} Containing timeout specific data */ onTimeout: function(data) { - nobid.timeoutTotal++; - log('Timeout total: ' + nobid.timeoutTotal, data); - return nobid.timeoutTotal; + window.nobid.timeoutTotal++; + log('Timeout total: ' + window.nobid.timeoutTotal, data); + return window.nobid.timeoutTotal; }, onBidWon: function(data) { - nobid.bidWonTotal++; - log('BidWon total: ' + nobid.bidWonTotal, data); - return nobid.bidWonTotal; + window.nobid.bidWonTotal++; + log('BidWon total: ' + window.nobid.bidWonTotal, data); + return window.nobid.bidWonTotal; } } registerBidder(spec); diff --git a/modules/nobidBidAdapter.md b/modules/nobidBidAdapter.md index b80a7f6c4b6..9e47aa5f43f 100644 --- a/modules/nobidBidAdapter.md +++ b/modules/nobidBidAdapter.md @@ -6,6 +6,7 @@ biddercode: nobid hide: true media_types: banner gdpr_supported: true +usp_supported: true --- ### Bid Params diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index adee3e264ed..26b0a7dccc2 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -1,14 +1,16 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + const BIDDER_CODE = 'oneVideo'; export const spec = { code: 'oneVideo', + VERSION: '3.0.3', ENDPOINT: 'https://ads.adaptv.advertising.com/rtb/openrtb?ext_id=', + E2ETESTENDPOINT: 'https://ads-wc.v.ssp.yahoo.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'], + SYNC_ENDPOINT3: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1', + supportedMediaTypes: ['video', 'banner'], /** * Determines whether or not the given bid request is valid. * @@ -25,6 +27,15 @@ export const spec = { return false; } + // Prevend DAP Outstream validation, Banner DAP validation & Multi-Format adUnit support + if (bid.mediaTypes.video) { + if (bid.mediaTypes.video.context === 'outstream' && bid.params.video.display === 1) { + return false; + } + } else if (bid.mediaTypes.banner && !bid.params.video.display) { + return false; + } + // Pub Id validation if (typeof bid.params.pubId === 'undefined') { return false; @@ -43,11 +54,17 @@ export const spec = { let consentData = bidRequest ? bidRequest.gdprConsent : null; return bids.map(bid => { + let url = spec.ENDPOINT + let pubId = bid.params.pubId; + if (bid.params.video.e2etest) { + url = spec.E2ETESTENDPOINT; + pubId = 'HBExchange'; + } return { method: 'POST', /** removing adding local protocal since we * can get cookie data only if we call with https. */ - url: spec.ENDPOINT + bid.params.pubId, + url: url + pubId, data: getRequestData(bid, consentData, bidRequest), bidRequest: bid } @@ -82,18 +99,26 @@ export const spec = { creativeId: bid.crid, width: size.width, height: size.height, - mediaType: 'video', currency: response.cur, ttl: 100, netRevenue: true, adUnitCode: bidRequest.adUnitCode }; + + bidResponse.mediaType = (bidRequest.mediaTypes.banner) ? 'banner' : 'video' + if (bid.nurl) { bidResponse.vastUrl = bid.nurl; + } else if (bid.adm && bidRequest.params.video.display === 1) { + bidResponse.ad = bid.adm } else if (bid.adm) { bidResponse.vastXml = bid.adm; } - bidResponse.renderer = (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined; + + if (bidRequest.mediaTypes.video) { + bidResponse.renderer = (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined; + } + return bidResponse; }, /** @@ -103,7 +128,9 @@ export const spec = { * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, consentData = {}) { + let { gdprApplies, consentString = '' } = consentData; + if (syncOptions.pixelEnabled) { return [{ type: 'image', @@ -115,11 +142,11 @@ export const spec = { }, { type: 'image', - url: spec.SYNC_ENDPOINT3 + url: `https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0` + encodeURI(`&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}`) }, { type: 'image', - url: spec.SYNC_ENDPOINT4 + url: spec.SYNC_ENDPOINT3 }]; } } @@ -152,6 +179,8 @@ function getRequestData(bid, consentData, bidRequest) { bidfloor: bid.params.bidfloor, ext: { hb: 1, + prebidver: '$prebid.version$', + adapterver: spec.VERSION, } }], site: { @@ -198,6 +227,8 @@ function getRequestData(bid, consentData, bidRequest) { if (bid.params.video.rewarded) { bidData.imp[0].ext.rewarded = bid.params.video.rewarded } + bidData.imp[0].video.linearity = 1; + bidData.imp[0].video.protocols = bid.params.video.protocols || [2, 5]; } else if (bid.params.video.display == 1) { bidData.imp[0].banner = { mimes: bid.params.video.mimes, @@ -208,6 +239,14 @@ function getRequestData(bid, consentData, bidRequest) { if (bid.params.video.placement) { bidData.imp[0].banner.placement = bid.params.video.placement } + if (bid.params.video.maxduration) { + bidData.imp[0].banner.ext = bidData.imp[0].banner.ext || {} + bidData.imp[0].banner.ext.maxduration = bid.params.video.maxduration + } + if (bid.params.video.minduration) { + bidData.imp[0].banner.ext = bidData.imp[0].banner.ext || {} + bidData.imp[0].banner.ext.minduration = bid.params.video.minduration + } } if (bid.params.video.inventoryid) { bidData.imp[0].ext.inventoryid = bid.params.video.inventoryid @@ -224,6 +263,16 @@ function getRequestData(bid, consentData, bidRequest) { } } } + if (bid.params.video.hp == 1) { + bidData.source.ext.schain.nodes[0].hp = bid.params.video.hp; + } + } else if (bid.schain) { + bidData.source = { + ext: { + schain: bid.schain + } + } + bidData.source.ext.schain.nodes[0].rid = bidData.id; } if (bid.params.site && bid.params.site.id) { bidData.site.id = bid.params.site.id @@ -236,18 +285,28 @@ function getRequestData(bid, consentData, bidRequest) { bidData.regs.ext.gdpr = 1 } - if (consentData.consentString) { + if (consentData && consentData.consentString) { bidData.user = { ext: { consent: consentData.consentString } }; } + // ccpa support if (bidRequest && bidRequest.uspConsent) { bidData.regs.ext.us_privacy = bidRequest.uspConsent } } - + if (bid.params.video.e2etest) { + bidData.imp[0].bidfloor = null; + bidData.imp[0].video.w = 300; + bidData.imp[0].video.h = 250; + bidData.imp[0].video.mimes = ['video/mp4', 'application/javascript']; + bidData.imp[0].video.api = [2]; + bidData.site.page = 'https://verizonmedia.com'; + bidData.site.ref = 'https://verizonmedia.com'; + bidData.tmax = 1000; + } return bidData; } @@ -264,6 +323,7 @@ function newRenderer(bidRequest, bid) { bidRequest.renderer.url = 'https://cdn.vidible.tv/prod/hb-outstream-renderer/renderer.js'; bidRequest.renderer.render = function(bid) { setTimeout(function () { + // eslint-disable-next-line no-undef o2PlayerRender(bid); }, 700) }; diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md index 145af1a6fb9..72f251aac04 100644 --- a/modules/oneVideoBidAdapter.md +++ b/modules/oneVideoBidAdapter.md @@ -1,87 +1,296 @@ # Overview **Module Name**: One Video Bidder Adapter -**Module Type**: Bidder Adapter +**Module Type**: Bidder Adapter **Maintainer**: deepthi.neeladri.sravana@verizonmedia.com # Description +Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to fetch bids. -Connects to One Video demand source to fetch bids. - - -# Test Parameters for Video +# Integration Examples: +## Instream Video adUnit example & parameters +*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. +``` + var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [480, 640] + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + playbackmethod: [1,5], + sid: YOUR_VSSP_ORG_ID, + hp: 1, + rewarded: 1, + placement: 1, + inventoryid: 123, + minduration: 10, + maxduration: 30, + }, + site: { + id: 1, + page: 'https://verizonmedia.com', + referrer: 'https://verizonmedia.com' + }, + pubId: 'HBExchange' + } + } + ] + } + ] +``` +## Outstream Video adUnit example & parameters +*Note:* The Video SSP ad server will load it's own Outstream Renderer (player) as a fallback if no player is defined on the publisher page. The Outstream player will inject into the div id that has an identical adUnit code. +``` + var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [480, 640] + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + playbackmethod: [1,5], + sid: YOUR_VSSP_ORG_ID, + hp: 1, + rewarded: 1, + placement: 1, + inventoryid: 123, + minduration: 10, + maxduration: 30, + }, + site: { + id: 1, + page: 'https://verizonmedia.com', + referrer: 'https://verizonmedia.com' + }, + pubId: 'HBExchange' + } + } + ] + } + ] ``` - var adUnits = [ - { +## S2S / Video: Dynamic Ad Placement (DAP) adUnit example & parameters +*Note:* The Video SSP ad server will respond with HTML embed tag to be injected into an iFrame you create. +``` + var adUnits = [ + { code: 'video1', - sizes: [640,480], - mediaTypes: { - video: { - context: "instream" + mediaTypes: { + video: { + context: "instream", + playerSize: [480, 640] + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + position: 1, + display: 1 + }, + site: { + id: 1, + page: 'https://verizonmedia.com', + referrer: 'https://verizonmedia.com' + }, + pubId: 'HBExchangeDAP' } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - playerWidth: 480, - playerHeight: 640, - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], - position: 1, - delivery: [2], - playbackmethod: [1,5], - sid: , - rewarded: 1, - placement: 1, - inventoryid: 123 - }, - site: { - id: 1, - page: 'http://abhi12345.com', - referrer: 'http://abhi12345.com' - }, - pubId: 'brxd' - } - } - ] + } + ] + } +] +``` +## Prebid.js / Banner: Dynamic Ad Placement (DAP) adUnit example & parameters +*Note:* The Video SSP ad server will respond with HTML embed tag to be injected into an iFrame created by Google Ad Manager (GAM). +``` + var adUnits = [ + { + code: 'banner-1', + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 300, + playerHeight: 250, + mimes: ['video/mp4', 'application/javascript'], + display: 1 + }, + site: { + id: 1, + page: 'https://verizonmedia.com', + referrer: 'https://verizonmedia.com' + }, + pubId: 'HBExchangeDAP' + } + } + ] } ] ``` -# Test Parameters for banner request + +# End 2 End Testing Mode +By passing bid.params.video.e2etest = true you will be able to receive a test creative when connecting via VPN location U.S West Coast. This will allow you to trubleshoot how your player/ad-server parses and uses the VAST XML response. +This automatically sets default values for the outbound bid-request to respond from our test exchange. +No need to override the site/ref urls or change your pubId ``` - var adUnits = [ +var adUnits = [ + { + code: 'video-1', + mediaTypes: { + video: { + context: "instream", + playerSize: [480, 640] + } + }, + bids: [ { - code: 'video1', - sizes: [640,480], - mediaTypes: { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 300, + playerHeight: 250, + mimes: ['video/mp4', 'application/javascript'], + e2etest: true + } + pubId: 'YOUR_PUBLISHER_ID' + } + } + ] + } +] +``` + +# Supply Chain Object Support +The oneVideoBidAdapter supports 2 methods for passing/creating an schain object. +1. By passing your Video SSP Org ID in the bid.video.params.sid - The adapter will create a new schain object and our ad-server will fill in the data for you. +2. Using the Prebid Supply Chain Object Module - The adapter will capture the schain object +*Note:* You cannot pass both schain object and bid.video.params.sid together. Option 1 will always be the default. + +## Create new schain using bid.video.params.sid +sid = your Video SSP Organization ID. +This is for direct publishers only. +``` +var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [480, 640] + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { video: { - context: "instream" + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + sid: + }, + site: { + id: 1, + page: 'https://verizonmedia.com', + referrer: 'https://verizonmedia.com' + }, + pubId: 'HBExchange' } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - playerWidth: 480, - playerHeight: 640, - mimes: ['video/mp4', 'application/javascript'], - position: 1, - display: 1 - }, - site: { - id: 1, - page: 'http://abhi12345.com', - referrer: 'http://abhi12345.com' - }, - pubId: 'OneMDisplay' - } + } + ] + } + ] +``` + +## Pass global schain using pbjs.setConfig(SCHAIN_OBJECT) +For both Authorized resellers and direct publishers. +``` +pbjs.setConfig({ + "schain": { + "validation": "strict", + "config": { + "ver": "1.0", + "complete": 1, + "nodes": [{ + "asi": "some-platform.com", + "sid": "111111", + "hp": 1 + }] + } + } +}); + +var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [480, 640] + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + }, + site: { + id: 1, + page: 'https://verizonmedia.com', + referrer: 'https://verizonmedia.com' + }, + pubId: 'HBExchange' } - ] - } -] + } + ] + } + ] ``` diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 77c257fd7fb..fd66c8ce69f 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -1,18 +1,17 @@ 'use strict'; -const { registerBidder } = require('../src/adapters/bidderFactory'); +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { INSTREAM, OUTSTREAM } from '../src/video.js'; +import { Renderer } from '../src/Renderer.js'; +import find from 'core-js-pure/features/array/find.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const storage = getStorageManager(); const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; 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. @@ -21,15 +20,26 @@ const BANNER = 'banner'; * @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') { + if (typeof bid === 'undefined' || typeof bid.params === 'undefined' || typeof bid.params.pubId !== 'string') { return false; } + return isValid(BANNER, bid) || isValid(VIDEO, bid); +} - if (typeof bid.params.pubId !== 'string' || typeof bid.sizes === 'undefined' || bid.sizes.length === 0) { - return false; - } +export function hasTypeVideo(bid) { + return typeof bid.mediaTypes !== 'undefined' && typeof bid.mediaTypes.video !== 'undefined'; +} - return true; +export function isValid(type, bid) { + if (type === BANNER) { + return parseSizes(bid).length > 0; + } else if (type === VIDEO && hasTypeVideo(bid)) { + const context = bid.mediaTypes.video.context; + if (context === 'outstream' || context === 'instream') { + return parseVideoSize(bid).length > 0; + } + } + return false; } /** @@ -38,65 +48,156 @@ function isBidRequestValid(bid) { * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ - function buildRequests(validBidRequests, bidderRequest) { - const bids = validBidRequests.map(requestsToBids); - const bidObject = {'bids': bids}; - const pageInfo = getPageInfo(); - - const payload = Object.assign(bidObject, pageInfo); - + const payload = { + bids: requestsToBids(validBidRequests), + ...getPageInfo() + }; if (bidderRequest && bidderRequest.gdprConsent) { payload.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, consentRequired: bidderRequest.gdprConsent.gdprApplies }; } - - const payloadString = JSON.stringify(payload); - + if (bidderRequest && bidderRequest.uspConsent) { + payload.usPrivacy = bidderRequest.uspConsent; + } + if (bidderRequest && bidderRequest.userId) { + payload.userId = bidderRequest.userId; + } + try { + if (storage.hasLocalStorage()) { + payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid'); + } + } catch (e) {} return { method: 'POST', url: ENDPOINT, - data: payloadString + data: JSON.stringify(payload) } } -function interpretResponse(serverResponse, request) { - var body = serverResponse.body; +function interpretResponse(serverResponse, bidderRequest) { + const body = serverResponse.body; const bids = []; - - if (typeof serverResponse === 'string') { - try { - body = JSON.parse(serverResponse); - } catch (e) { - return bids; - } - } - + const requestData = JSON.parse(bidderRequest.data); if (!body || (body.nobid && body.nobid === true)) { return bids; } + if (!body.bids || !Array.isArray(body.bids) || body.bids.length === 0) { + return bids; + } + body.bids.forEach(({ + requestId, + cpm, + width, + height, + creativeId, + dealId, + currency, + mediaType, + ttl, + rendererUrl, + ad, + vastUrl, + videoCacheKey + }) => { + const responseBid = { + requestId, + cpm, + width, + height, + creativeId, + dealId: dealId == null ? dealId : '', + currency, + netRevenue: false, + meta: { + mediaType + }, + ttl: ttl || 300 + }; + if (mediaType === BANNER) { + responseBid.ad = ad; + } else if (mediaType === VIDEO) { + const {context, adUnitCode} = find(requestData.bids, (item) => item.bidId === requestId); + if (context === INSTREAM) { + responseBid.vastUrl = vastUrl; + responseBid.videoCacheKey = videoCacheKey; + } else if (context === OUTSTREAM) { + responseBid.vastXml = ad; + responseBid.vastUrl = vastUrl; + if (rendererUrl) { + responseBid.renderer = createRenderer({requestId, rendererUrl, adUnitCode}); + } + } + } + bids.push(responseBid); + }); + 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 || 300 - }); +function createRenderer(bid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: bid.requestId, + url: bid.rendererUrl, + config: rendererOptions, + adUnitCode: bid.adUnitCode, + loaded: false + }); + try { + renderer.setRender(onetagRenderer); + } catch (e) { + + } + return renderer; +} + +function onetagRenderer({renderer, width, height, vastXml, adUnitCode}) { + renderer.push(() => { + window.onetag.Player.init({ + width, + height, + vastXml, + nodeId: adUnitCode, + config: renderer.getConfig() }); + }); +} + +function getFrameNesting() { + let topmostFrame = window; + let parent = window.parent; + let currentFrameNesting = 0; + try { + while (topmostFrame !== topmostFrame.parent) { + parent = topmostFrame.parent; + // eslint-disable-next-line no-unused-expressions + parent.location.href; + topmostFrame = topmostFrame.parent; + } + } catch (e) { + currentFrameNesting = parent === topmostFrame.top ? 1 : 2; } + return { + topmostFrame, + currentFrameNesting + } +} - return bids; +function getDocumentVisibility(window) { + try { + if (typeof window.document.hidden !== 'undefined') { + return window.document.hidden; + } else if (typeof window.document['msHidden'] !== 'undefined') { + return window.document['msHidden']; + } else if (typeof window.document['webkitHidden'] !== 'undefined') { + return window.document['webkitHidden']; + } else { + return null; + } + } catch (e) { + return null; + } } /** @@ -104,98 +205,181 @@ function interpretResponse(serverResponse, request) { * @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, - oWidth: w.outerWidth, - oHeight: w.outerHeight, - sWidth: s.width, - sHeight: s.height, - aWidth: s.availWidth, - aHeight: s.availHeight, - sLeft: 'screenLeft' in w ? w.screenLeft : w.screenX, - sTop: 'screenTop' in w ? w.screenTop : w.screenY, + const { topmostFrame, currentFrameNesting } = getFrameNesting(); + return { + location: topmostFrame.location.href, + referrer: + topmostFrame.document.referrer !== '' + ? topmostFrame.document.referrer + : null, + masked: currentFrameNesting, + wWidth: topmostFrame.innerWidth, + wHeight: topmostFrame.innerHeight, + oWidth: topmostFrame.outerWidth, + oHeight: topmostFrame.outerHeight, + sWidth: topmostFrame.screen.width, + sHeight: topmostFrame.screen.height, + aWidth: topmostFrame.screen.availWidth, + aHeight: topmostFrame.screen.availHeight, + sLeft: 'screenLeft' in topmostFrame ? topmostFrame.screenLeft : topmostFrame.screenX, + sTop: 'screenTop' in topmostFrame ? topmostFrame.screenTop : topmostFrame.screenY, + xOffset: topmostFrame.pageXOffset, + yOffset: topmostFrame.pageYOffset, + docHidden: getDocumentVisibility(topmostFrame), + docHeight: topmostFrame.document.body ? topmostFrame.document.body.scrollHeight : null, hLength: history.length, - date: t.toUTCString(), - timeOffset: t.getTimezoneOffset() + timing: getTiming(), + version: { + prebid: '$prebid.version$', + adapter: '1.0.0' + } }; - - return params; } -function requestsToBids(bid) { - const toRet = {}; - - const params = bid.params; +function requestsToBids(bidRequests) { + const videoBidRequests = bidRequests.filter(bidRequest => hasTypeVideo(bidRequest)).map(bidRequest => { + const videoObj = {}; + setGeneralInfo.call(videoObj, bidRequest); + // Pass parameters + // Context: instream - outstream - adpod + videoObj['context'] = bidRequest.mediaTypes.video.context; + // MIME Video Types + videoObj['mimes'] = bidRequest.mediaTypes.video.mimes; + // Sizes + videoObj['playerSize'] = parseVideoSize(bidRequest); + // Other params + videoObj['protocols'] = bidRequest.mediaTypes.video.protocols; + videoObj['maxDuration'] = bidRequest.mediaTypes.video.maxduration; + videoObj['api'] = bidRequest.mediaTypes.video.api; + videoObj['type'] = VIDEO; + return videoObj; + }); + const bannerBidRequests = bidRequests.filter(bidRequest => isValid(BANNER, bidRequest)).map(bidRequest => { + const bannerObj = {}; + setGeneralInfo.call(bannerObj, bidRequest); + bannerObj['sizes'] = parseSizes(bidRequest); + bannerObj['type'] = BANNER; + return bannerObj; + }); + return videoBidRequests.concat(bannerBidRequests); +} - 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]}) +function setGeneralInfo(bidRequest) { + const params = bidRequest.params; + this['adUnitCode'] = bidRequest.adUnitCode; + this['bidId'] = bidRequest.bidId; + this['bidderRequestId'] = bidRequest.bidderRequestId; + this['auctionId'] = bidRequest.auctionId; + this['transactionId'] = bidRequest.transactionId; + this['pubId'] = params.pubId; + this['ext'] = params.ext; + if (params.pubClick) { + this['click'] = params.pubClick; + } + if (params.dealId) { + this['dealId'] = params.dealId; + } + const coords = getSpaceCoords(bidRequest.adUnitCode); + if (coords) { + this['coords'] = coords; } +} - toRet['pubId'] = params.pubId; - if (params.type) { - toRet['type'] = params.type; +function getSpaceCoords(id) { + const space = document.getElementById(id); + try { + const { top, left, width, height } = space.getBoundingClientRect(); + let window = space.ownerDocument.defaultView; + const coords = { top: top + window.pageYOffset, left: left + window.pageXOffset, width, height }; + let frame = window.frameElement; + while (frame != null) { + const { top, left } = frame.getBoundingClientRect(); + coords.top += top + window.pageYOffset; + coords.left += left + window.pageXOffset; + window = window.parent; + frame = window.frameElement; + } + return coords; + } catch (e) { + return null; } +} - if (params.pubClick) { - toRet['click'] = params.pubClick; +function getTiming() { + try { + if (window.performance != null && window.performance.timing != null) { + const timing = {}; + const perf = window.performance.timing; + timing.pageLoadTime = perf.loadEventEnd - perf.navigationStart; + timing.connectTime = perf.responseEnd - perf.requestStart; + timing.renderTime = perf.domComplete - perf.domLoading; + return timing; + } + } catch (e) { + return null; } + return null; +} - if (params.dealId) { - toRet['dealId'] = params.dealId; +function parseVideoSize(bid) { + const playerSize = bid.mediaTypes.video.playerSize; + if (typeof playerSize !== 'undefined' && Array.isArray(playerSize) && playerSize.length > 0) { + return getSizes(playerSize) } + return []; +} - return toRet; +function parseSizes(bid) { + let ret = []; + if (typeof bid.mediaTypes !== 'undefined' && typeof bid.mediaTypes.banner !== 'undefined' && typeof bid.mediaTypes.banner.sizes !== 'undefined' && Array.isArray(bid.mediaTypes.banner.sizes) && bid.mediaTypes.banner.sizes.length > 0) { + return getSizes(bid.mediaTypes.banner.sizes) + } + const isVideoBidRequest = hasTypeVideo(bid); + if (!isVideoBidRequest && bid.sizes && Array.isArray(bid.sizes)) { + return getSizes(bid.sizes); + } + return ret; } -function getUserSyncs(syncOptions, serverResponses, gdprConsent) { - const syncs = []; - if (syncOptions.iframeEnabled) { - const rnd = new Date().getTime(); - let params = '?cb=' + rnd; +function getSizes(sizes) { + const ret = []; + for (let i = 0; i < sizes.length; i++) { + const size = sizes[i]; + ret.push({width: size[0], height: size[1]}) + } + return ret; +} - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - params += '&gdpr_consent=' + gdprConsent.consentString; - if (typeof gdprConsent.gdprApplies === 'boolean') { - params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); - } +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + let syncs = []; + let params = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + params += '&gdpr_consent=' + gdprConsent.consentString; + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); } - + } + if (uspConsent && typeof uspConsent === 'string') { + params += '&us_privacy=' + uspConsent; + } + if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: USER_SYNC_ENDPOINT + params + url: USER_SYNC_ENDPOINT + '?cb=' + new Date().getTime() + params + }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: USER_SYNC_ENDPOINT + '?tag=img' + params }); } return syncs; } export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, interpretResponse: interpretResponse, @@ -203,5 +387,4 @@ export const spec = { }; -// Starting point registerBidder(spec); diff --git a/modules/onetagBidAdapter.md b/modules/onetagBidAdapter.md index 38872ad8280..7814403afbe 100644 --- a/modules/onetagBidAdapter.md +++ b/modules/onetagBidAdapter.md @@ -8,27 +8,53 @@ Maintainer: devops@onetag.com # Description -OneTag Bid Adapter supports only banner at present. +OneTag Bid Adapter supports banner and video at present. # Test Parameters ``` var adUnits = [ { - code: "test-div", + code: "banner-space", mediaTypes: { banner: { sizes: [[300, 250]] } }, - bids: [ - { - bidder: "onetag", - params: { - pubId: "your_publisher_id", // required - type: "banner" // optional. Default "banner" - }, + bids: [{ + bidder: "onetag", + params: { + pubId: "your_publisher_id" // required, testing pubId: "386276e072" } - ] - }]; + }] + }, { + code: 'video-instream-space', + mediaTypes: { + video: { + context: "instream", + mimes: ["video/mp4", "video/webm", "application/javascript", "video/ogg"], + playerSize: [640,480] + } + }, + bids: [{ + bidder: "onetag", + params: { + pubId: "your_publisher_id" // required, testing pubId: "386276e072" + } + }] + }, { + code: 'video-outstream-space', + mediaTypes: { + video: { + context: "outstream", + playerSize: [640,480] + } + }, + bids: [{ + bidder: "onetag", + params: { + pubId: "your_publisher_id" // required, testing pubId: "386276e072" + } + }] + }]; ``` diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js new file mode 100644 index 00000000000..55fca29fbf3 --- /dev/null +++ b/modules/onomagicBidAdapter.js @@ -0,0 +1,246 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'onomagic'; +const URL = 'https://bidder.onomagic.com/hb'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; + } + const onomagicImps = []; + const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); + utils._each(bidReqs, function (bid) { + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => utils.isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + if (bidFloor) { + imp.bidfloor = bidFloor; + } + onomagicImps.push(imp); + }); + const onomagicBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: onomagicImps, + site: { + domain: utils.parseUrl(referrer).host, + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + return { + method: 'POST', + url: URL, + data: JSON.stringify(onomagicBidReq), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + utils.logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if (typeof bid.params.publisherId === 'undefined') { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + if (!serverResponse.body || typeof serverResponse.body != 'object') { + utils.logWarn('Onomagic server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return []; + } + const { body: {id, seatbid} } = serverResponse; + try { + const onomagicBidResponses = []; + if (id && + seatbid && + seatbid.length > 0 && + seatbid[0].bid && + seatbid[0].bid.length > 0) { + seatbid[0].bid.map(onomagicBid => { + onomagicBidResponses.push({ + requestId: onomagicBid.impid, + cpm: parseFloat(onomagicBid.price), + width: parseInt(onomagicBid.w), + height: parseInt(onomagicBid.h), + creativeId: onomagicBid.crid || onomagicBid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(onomagicBid), + ttl: 60 + }); + }); + } + return onomagicBidResponses; + } catch (e) { + utils.logError(e, {id, seatbid}); + } +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += utils.createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return utils.getWindowTop().document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; +} + +function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBoundingBox(element, { w, h } = {}) { + let { width, height, left, top, right, bottom } = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return { width, height, left, top, right, bottom }; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, + right: rects[0].right, + top: rects[0].top, + bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([ { + left: 0, + top: 0, + right: topWin.innerWidth, + bottom: topWin.innerHeight + }, elementBoundingBox ]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +registerBidder(spec); diff --git a/modules/onomagicBidAdapter.md b/modules/onomagicBidAdapter.md new file mode 100644 index 00000000000..7222a506b2c --- /dev/null +++ b/modules/onomagicBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: Onomagic Bid Adapter +Module Type: Bidder Adapter +Maintainer: vyatsun@gmail.com +``` + +# Description + +Onomagic's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [{ + bidder: 'onomagic', + params: { + publisherId: 20167, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'onomagic', + params: { + publisherId: 20167 + } + }] + } +] +``` diff --git a/modules/open8BidAdapter.js b/modules/open8BidAdapter.js index eb2d71c6bd6..744cce2b5f9 100644 --- a/modules/open8BidAdapter.js +++ b/modules/open8BidAdapter.js @@ -1,8 +1,8 @@ -import { Renderer } from '../src/Renderer'; -import {ajax} from '../src/ajax'; -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { VIDEO, BANNER } from '../src/mediaTypes'; +import { Renderer } from '../src/Renderer.js'; +import {ajax} from '../src/ajax.js'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'open8'; const URL = 'https://as.vt.open8.com/v1/control/prebid'; diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 68d2670800d..7addfe68bc6 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -1,9 +1,9 @@ -import adapter from '../src/AnalyticsAdapter'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; -import { config } from '../src/config'; -import { ajax } from '../src/ajax'; -import * as utils from '../src/utils'; +import adapterManager from '../src/adapterManager.js'; +import { config } from '../src/config.js'; +import { ajax } from '../src/ajax.js'; +import * as utils from '../src/utils.js'; const { EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } diff --git a/modules/openxAnalyticsAdapter.md b/modules/openxAnalyticsAdapter.md new file mode 100644 index 00000000000..ac739f36c76 --- /dev/null +++ b/modules/openxAnalyticsAdapter.md @@ -0,0 +1,129 @@ + +# OpenX Analytics Adapter to Prebid.js +## Implementation Guide +#### Internal use only + +--- + +# About this Guide +This implementation guide walks through the flow of onboarding an alpha Publisher to test OpenX’s new Analytics Adapter. + +- [Adding OpenX Analytics Adapter to Prebid.js](#adding-openx-analytics-adapter-to-prebidjs) + - [Publisher Builds Prebid.js File Flow](#publisher-builds-prebidjs-file-flow) + - [OpenX Builds Prebid.js File Flow](#openx-builds-prebidjs-file-flow) +- [Website Configuration](#website-configuration) +- [Configuration Options](#configuration-options) +- [Viewing Data](#viewing-data) + +--- + +# Adding OpenX Analytics Adapter to Prebid.js +A Publisher has two options to add the OpenX Analytics Adapter to Prebid.js: + +1. [Publisher builds the Prebid.js file](#publisher-builds-prebid.js-file-flow): If the Publisher is familiar with building Prebid.js (through the command line and not through the download site), OpenX can provide to the Publisher only the Analytics Adapter code. + +2. [OpenX builds the Prebid.js file](#openx-builds-prebid.js-file-flow): If the Publisher is unfamiliar with building Prebid.js, the Publisher should advise OpenX which modules to include by going to the [Prebid download site](http://prebid.org/download.html) and selecting all the desired items (adapters and modules) for OpenX. + +--- + +## Publisher Builds Prebid.js File Flow +Use this option if the Publisher is building the Prebid.js file. + +1. OpenX sends Publisher the new Analytics Adapter code. + +2. Publisher replaces the file in `/modules/openxAnalyticsAdapter.js` with the file provided by OpenX. + +3. Publisher runs the build command in `` and includes `openxAnalyticsAdapter` as one of the modules to include. + + For example: + + ```shell + gulp build --modules=openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter,openxBidAdapter,openxAnalyticsAdapter,dfpAdServerVideo + ``` + +4. Publisher deploys the Prebid.js file from `/build/dist/prebid.js` to a website. + +--- + +## OpenX Builds Prebid.js File Flow +Use this option if OpenX is building the Prebid.js file on behalf of the Publisher. + +1. Publisher refers to the [Prebid download site](http://prebid.org/download.html) and sends a list of adapters and modules to OpenX. + + >Note: The Publisher must be aware that only Prebid 3.0+ is supported. + + For example (does not have to follow exact format): + + ```yaml + Prebid Version: 3.10+ + Modules: + Bidders + OpenX + Rubicon + Sovrn + Consent Management + US Privacy + User ID + IdentityLink ID + DFP Video + Supply Chain Object + Currency + ``` + +2. OpenX uses the information to build a package to the user’s specification and includes `openxAnalyticsAdapter` as an additional module. + +3. OpenX sends the built package to the Publisher. + +4. Publisher deploys the modified Prebid.js to a website. + +--- + +# Website Configuration +To configure your website, add the following code snippet to your website: + +```javascript +pbjs.que.push(function () { + pbjs.enableAnalytics([{ + provider: "openx", + options: { + publisherPlatformId: "OPENX_PROVIDED_PLATFORM_ID", // eg: "a3aece0c-9e80-4316-8deb-faf804779bd1" + publisherAccountId: PUBLISHER_ACCOUNT_ID, // eg: 537143056 + sampling: 0.05, // 5% sample rate + testCode: 'test-code-1' + } + }]); +}); +``` + +--- + +## Configuration Options +Configuration options are a follows: + +| Property | Type | Required? | Description | Example | +|:---|:---|:---|:---|:---| +| `publisherPlatformId` | `string` | Yes | Used to determine ownership of data. | `a3aece0c-9e80-4316-8deb-faf804779bd1` | +| `publisherAccountId` | `number` | Yes | Used to determine ownership of data. | `1537143056` | +| `sampling` | `number` | Yes | Sampling rate | Undefined or `1.00` - No sampling. Analytics is sent all the time.
0.5 - 50% of users will send analytics data. | +| `testCode` | `string` | No | Used to label analytics data for the purposes of tests.
This label is treated as a dimension and can be compared against other labels. | `timeout_config_1`
`timeout_config_2`
`timeout_default` | + +--- + +# Viewing Data +The Prebid Report available in the Reporting in the Cloud tool, allows you to view your data. + +**To view your data:** + +1. Log in to Reporting in the Cloud. + +2. In the top right, click on the **View** list and then select **Prebidreport**. + +3. On the left icon bar, click on the dimensions icon. + +4. Add the dimensions that you need. + +5. On the left icon bar, click on the metrics icon. + +6. Add the metrics (graphs) that you need. + + The data appears on the Analyze screen. diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 1e69dc01471..f4b6288cd55 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,22 +1,31 @@ -import {config} from '../src/config'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; -import {BANNER, VIDEO} from '../src/mediaTypes'; -import {parse} from '../src/url'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openx'; const BIDDER_CONFIG = 'hb_pb'; -const BIDDER_VERSION = '3.0.0'; - -const USER_ID_CODE_TO_QUERY_ARG = { - idl_env: 'lre', // liveramp - pubcid: 'pubcid', // publisher common id - tdid: 'ttduuid' // the trade desk +const BIDDER_VERSION = '3.0.3'; + +const DEFAULT_CURRENCY = 'USD'; + +export const USER_ID_CODE_TO_QUERY_ARG = { + britepoolid: 'britepoolid', // BritePool ID + criteoId: 'criteoid', // CriteoID + digitrustid: 'digitrustid', // DigiTrust + id5id: 'id5id', // ID5 ID + idl_env: 'lre', // LiveRamp IdentityLink + lipb: 'lipbid', // LiveIntent ID + netId: 'netid', // netID + parrableId: 'parrableid', // Parrable ID + pubcid: 'pubcid', // PubCommon ID + tdid: 'ttduuid', // The Trade Desk Unified ID }; export const spec = { code: BIDDER_CODE, + gvlid: 69, supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { const hasDelDomainOrPlatform = bidRequest.params.delDomain || bidRequest.params.platform; @@ -53,12 +62,13 @@ export const spec = { return mediaType === VIDEO ? createVideoBidResponses(oxResponseObj, serverRequest.payload) : createBannerBidResponses(oxResponseObj, serverRequest.payload); }, - getUserSyncs: function (syncOptions, responses) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; let url = utils.deepAccess(responses, '0.body.ads.pixels') || utils.deepAccess(responses, '0.body.pixels') || - 'https://u.openx.net/w/1.0/pd'; + generateDefaultSyncUrl(gdprConsent, uspConsent); + return [{ type: pixelType, url: url @@ -73,6 +83,23 @@ export const spec = { } }; +function generateDefaultSyncUrl(gdprConsent, uspConsent) { + let url = 'https://u.openx.net/w/1.0/pd'; + let queryParamStrings = []; + + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + + // CCPA + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + + return `${url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`; +} + function isVideoRequest(bidRequest) { return (utils.deepAccess(bidRequest, 'mediaTypes.video') && !utils.deepAccess(bidRequest, 'mediaTypes.banner')) || bidRequest.mediaType === VIDEO; } @@ -205,7 +232,7 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { defaultParams.ph = bids[0].params.platform; } - if (utils.deepAccess(bidderRequest, 'gdprConsent')) { + if (bidderRequest.gdprConsent) { let gdprConsentConfig = bidderRequest.gdprConsent; if (gdprConsentConfig.consentString !== undefined) { @@ -221,6 +248,10 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { } } + if (bidderRequest && bidderRequest.uspConsent) { + defaultParams.us_privacy = bidderRequest.uspConsent; + } + // normalize publisher common id if (utils.deepAccess(bids[0], 'crumbs.pubcid')) { utils.deepSetValue(bids[0], 'userId.pubcid', utils.deepAccess(bids[0], 'crumbs.pubcid')); @@ -236,9 +267,23 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { } function appendUserIdsToQueryParams(queryParams, userIds) { - utils._each(userIds, (userIdValue, userIdProviderKey) => { + utils._each(userIds, (userIdObjectOrValue, userIdProviderKey) => { + const key = USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]; + if (USER_ID_CODE_TO_QUERY_ARG.hasOwnProperty(userIdProviderKey)) { - queryParams[USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]] = userIdValue; + switch (userIdProviderKey) { + case 'digitrustid': + queryParams[key] = utils.deepAccess(userIdObjectOrValue, 'data.id'); + break; + case 'lipb': + queryParams[key] = userIdObjectOrValue.lipbid; + break; + case 'parrableId': + queryParams[key] = userIdObjectOrValue.eid; + break; + default: + queryParams[key] = userIdObjectOrValue; + } } }); @@ -296,8 +341,10 @@ function buildOXBannerRequest(bids, bidderRequest) { let customFloorsForAllBids = []; let hasCustomFloor = false; bids.forEach(function (bid) { - if (bid.params.customFloor) { - customFloorsForAllBids.push((Math.round(bid.params.customFloor * 100) / 100) * 1000); + let floor = getBidFloor(bid, BANNER); + + if (floor) { + customFloorsForAllBids.push(floor); hasCustomFloor = true; } else { customFloorsForAllBids.push(0); @@ -376,14 +423,18 @@ function generateVideoParameters(bid, bidderRequest) { queryParams.vmimes = oxVideoConfig.mimes; } + if (bid.params.test) { + queryParams.vtest = 1; + } + return queryParams; } function createVideoBidResponses(response, {bid, startTime}) { let bidResponses = []; - if (response !== undefined && response.vastUrl !== '' && response.pub_rev !== '') { - let vastQueryParams = parse(response.vastUrl).search || {}; + if (response !== undefined && response.vastUrl !== '' && response.pub_rev > 0) { + let vastQueryParams = utils.parseUrl(response.vastUrl).search || {}; let bidResponse = {}; bidResponse.requestId = bid.bidId; // default 5 mins @@ -391,9 +442,9 @@ function createVideoBidResponses(response, {bid, startTime}) { // 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.cpm = parseInt(response.pub_rev, 10) / 1000; + bidResponse.width = parseInt(response.width, 10); + bidResponse.height = parseInt(response.height, 10); bidResponse.creativeId = response.adid; bidResponse.vastUrl = response.vastUrl; bidResponse.mediaType = VIDEO; @@ -409,4 +460,20 @@ function createVideoBidResponses(response, {bid, startTime}) { return bidResponses; } +function getBidFloor(bidRequest, mediaType) { + let floorInfo = {}; + const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: currency, + mediaType: mediaType, + size: '*' + }); + } + let floor = floorInfo.floor || bidRequest.params.customFloor || 0; + + return Math.round(floor * 1000); // normalize to microCpm +} + registerBidder(spec); diff --git a/modules/optimeraBidAdapter.js b/modules/optimeraBidAdapter.js index bc4407ababa..b470e901ec6 100644 --- a/modules/optimeraBidAdapter.js +++ b/modules/optimeraBidAdapter.js @@ -1,5 +1,5 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { deepAccess } from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepAccess } from '../src/utils.js'; const BIDDER_CODE = 'optimera'; const SCORES_BASE_URL = 'https://dyv1bugovvq1g.cloudfront.net/'; diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index a3ed3e3f74d..e01746af487 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -1,18 +1,20 @@ -import {detectReferer} from '../src/refererDetection'; -import {ajax} from '../src/ajax'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storageManager = getStorageManager(); export const spec = { code: 'orbidder', - bidParams: {}, - orbidderHost: (() => { - let ret = 'https://orbidder.otto.de'; + hostname: 'https://orbidder.otto.de', + + getHostname() { + let ret = this.hostname; try { - ret = localStorage.getItem('ov_orbidder_host') || ret; + ret = storageManager.getDataFromLocalStorage('ov_orbidder_host') || ret; } catch (e) { } return ret; - })(), + }, isBidRequestValid(bid) { return !!(bid.sizes && bid.bidId && bid.params && @@ -23,6 +25,7 @@ export const spec = { }, buildRequests(validBidRequests, bidderRequest) { + const hostname = this.getHostname(); return validBidRequests.map((bidRequest) => { let referer = ''; if (bidderRequest && bidderRequest.refererInfo) { @@ -30,10 +33,11 @@ export const spec = { } const ret = { - url: `${spec.orbidderHost}/bid`, + url: `${hostname}/bid`, method: 'POST', options: { withCredentials: true }, data: { + v: $$PREBID_GLOBAL$$.version, pageUrl: referer, bidId: bidRequest.bidId, auctionId: bidRequest.auctionId, @@ -44,7 +48,6 @@ export const spec = { params: bidRequest.params } }; - spec.bidParams[bidRequest.bidId] = bidRequest.params; if (bidderRequest && bidderRequest.gdprConsent) { ret.data.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, @@ -72,24 +75,6 @@ export const spec = { } return bidResponses; }, - - onBidWon(bid) { - this.onHandler(bid, '/win'); - }, - - onHandler (bid, route) { - const getRefererInfo = detectReferer(window); - - bid.pageUrl = getRefererInfo().referer; - if (spec.bidParams[bid.requestId] && (typeof bid.params === 'undefined')) { - bid.params = [spec.bidParams[bid.requestId]]; - } - spec.ajaxCall(`${spec.orbidderHost}${route}`, JSON.stringify(bid)); - }, - - ajaxCall(endpoint, data) { - ajax(endpoint, null, data, { withCredentials: true }); - } }; registerBidder(spec); diff --git a/modules/otmBidAdapter.js b/modules/otmBidAdapter.js index ddb4d356f5c..23f6d434ae1 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -1,5 +1,5 @@ -import {BANNER} from '../src/mediaTypes'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; export const spec = { code: 'otm', diff --git a/modules/outconBidAdapter.js b/modules/outconBidAdapter.js index b636c18cf88..0c3ac90172a 100644 --- a/modules/outconBidAdapter.js +++ b/modules/outconBidAdapter.js @@ -1,4 +1,4 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'outcon'; diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 9bd4f21ff96..451ab654c53 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -1,21 +1,23 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; -import {config} from '../src/config'; -import {getPriceBucketString} from '../src/cpmBucketManager'; -import { Renderer } from '../src/Renderer' - +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {getPriceBucketString} from '../src/cpmBucketManager.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'ozone'; +const ALLOWED_LOTAME_PARAMS = ['oz_lotameid', 'oz_lotamepid', 'oz_lotametpid']; +// *** PROD *** const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const OZONECOOKIESYNC = 'https://elb.the-ozone-project.com/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; -const OZONEVERSION = '2.1.2'; - +const OZONEVERSION = '2.4.0'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], + cookieSyncBag: {'publisherId': null, 'siteId': null, 'userIdObject': {}}, // variables we want to make available to cookie sync + propertyBag: {'lotameWasOverridden': 0, 'pageId': null, 'buildRequestsStart': 0, 'buildRequestsEnd': 0}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ /** * Basic check to see whether required parameters are in the request. @@ -23,105 +25,147 @@ export const spec = { * @returns {boolean} */ isBidRequestValid(bid) { + utils.logInfo('OZONE: isBidRequestValid : ', config.getConfig(), bid); + let adUnitCode = bid.adUnitCode; // adunit[n].code + if (!(bid.params.hasOwnProperty('placementId'))) { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED'); + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } - if (!(bid.params.placementId).toString().match(/^[0-9]{10}$/)) { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters'); + if (!this.isValidPlacementId(bid.params.placementId)) { + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED'); + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens'); + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED'); + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters'); + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); + return false; + } + if (bid.params.hasOwnProperty('customParams')) { + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); return false; } if (bid.params.hasOwnProperty('customData')) { - if (typeof bid.params.customData !== 'object') { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is not an object'); + if (!Array.isArray(bid.params.customData)) { + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is not an Array', adUnitCode); + return false; + } + if (bid.params.customData.length < 1) { + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); + return false; + } + if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); + return false; + } + if (typeof bid.params.customData[0]['targeting'] != 'object') { + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); return false; } - } - if (bid.params.hasOwnProperty('customParams')) { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData'); - return false; } if (bid.params.hasOwnProperty('lotameData')) { if (typeof bid.params.lotameData !== 'object') { - utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object'); + utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object', adUnitCode); return false; } } if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { - if (!bid.mediaTypes.video.hasOwnProperty('context')) { - utils.logInfo('OZONE: [WARNING] No context key/value in bid. Rejecting bid: ', bid); + if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + utils.logError('OZONE: No video context key/value in bid. Rejecting bid: ', bid); return false; } - if (bid.mediaTypes.video.context !== 'outstream') { - utils.logInfo('OZONE: [WARNING] Only outstream video is supported. Rejecting bid: ', bid); + if (bid.mediaTypes[VIDEO].context !== 'instream' && bid.mediaTypes[VIDEO].context !== 'outstream') { + utils.logError('OZONE: video.context is invalid. Only instream/outstream video is supported. Rejecting bid: ', bid); return false; } } + // guard against hacks in GET parameters that we might allow + const arrLotameOverride = this.getLotameOverrideParams(); + // lotame override, test params. All 3 must be present, or none. + let lotameKeys = Object.keys(arrLotameOverride); + if (lotameKeys.length === ALLOWED_LOTAME_PARAMS.length) { + utils.logInfo('OZONE: VALIDATION : arrLotameOverride', arrLotameOverride); + for (let i in lotameKeys) { + if (!arrLotameOverride[ALLOWED_LOTAME_PARAMS[i]].toString().match(/^[0-9a-zA-Z]+$/)) { + utils.logError('OZONE: Only letters & numbers allowed in lotame override: ' + i.toString() + ': ' + arrLotameOverride[ALLOWED_LOTAME_PARAMS[i]].toString() + '. Rejecting bid: ', bid); + return false; + } + } + } else if (lotameKeys.length > 0) { + utils.logInfo('OZONE: VALIDATION : arrLotameOverride', arrLotameOverride); + utils.logError('OZONE: lotame override params are incomplete. You must set all ' + ALLOWED_LOTAME_PARAMS.length + ': ' + JSON.stringify(ALLOWED_LOTAME_PARAMS) + ', . Rejecting bid: ', bid); + return false; + } return true; }, + /** + * Split this out so that we can validate the placementId and also the override GET parameter ozstoredrequest + * @param placementId + */ + isValidPlacementId(placementId) { + return placementId.toString().match(/^[0-9]{10}$/); + }, + buildRequests(validBidRequests, bidderRequest) { - utils.logInfo('OZONE: ozone v' + OZONEVERSION + ' validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); + this.propertyBag.buildRequestsStart = new Date().getTime(); + utils.logInfo(`OZONE: buildRequests time: ${this.propertyBag.buildRequestsStart} ozone v ${OZONEVERSION} validBidRequests`, validBidRequests, 'bidderRequest', bidderRequest); + // First check - is there any config to block this request? + if (this.blockTheRequest()) { + return []; + } + let htmlParams = {'publisherId': '', 'siteId': ''}; + if (validBidRequests.length > 0) { + this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); + this.cookieSyncBag.siteId = utils.deepAccess(validBidRequests[0], 'params.siteId'); + this.cookieSyncBag.publisherId = utils.deepAccess(validBidRequests[0], 'params.publisherId'); + htmlParams = validBidRequests[0].params; + } + utils.logInfo('OZONE: cookie sync bag', this.cookieSyncBag); let singleRequest = config.getConfig('ozone.singleRequest'); singleRequest = singleRequest !== false; // undefined & true will be true utils.logInfo('OZONE: config ozone.singleRequest : ', singleRequest); - let htmlParams = validBidRequests[0].params; // the html page config params will be included in each element let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug'] if (bidderRequest && bidderRequest.gdprConsent) { utils.logInfo('OZONE: ADDING GDPR info'); - ozoneRequest.regs = {}; - ozoneRequest.regs.ext = {}; - ozoneRequest.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + let apiVersion = utils.deepAccess(bidderRequest.gdprConsent, 'apiVersion', '1'); + ozoneRequest.regs = {ext: {gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, apiVersion: apiVersion}}; if (ozoneRequest.regs.ext.gdpr) { ozoneRequest.user = ozoneRequest.user || {}; - if ( - bidderRequest.gdprConsent.vendorData && - bidderRequest.gdprConsent.vendorData.vendorConsents && - typeof bidderRequest.gdprConsent.consentString !== 'undefined' - ) { - utils.logInfo('OZONE: found all info we need for GDPR - will add info to request object'); - ozoneRequest.user.ext = {'consent': bidderRequest.gdprConsent.consentString}; - // are we able to make this request? - let vendorConsents = bidderRequest.gdprConsent.vendorData.vendorConsents; - let boolGdprConsentForOzone = vendorConsents[524]; - let arrGdprConsents = toFlatArray(bidderRequest.gdprConsent.vendorData.purposeConsents); - ozoneRequest.regs.ext.oz_con = boolGdprConsentForOzone ? 1 : 0; - ozoneRequest.regs.ext.gap = arrGdprConsents; - } + ozoneRequest.user.ext = {'consent': bidderRequest.gdprConsent.consentString}; } else { - utils.logInfo('OZONE: **** Failed to find required info for GDPR for request object, even though bidderRequest.gdprConsent is TRUE ****'); + utils.logInfo('OZONE: **** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); } } else { utils.logInfo('OZONE: WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object was present.'); } - + const getParams = this.getGetParametersAsObject(); + const ozTestMode = getParams.hasOwnProperty('oztestmode') ? getParams.oztestmode : null; // this can be any string, it's used for testing ads ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; + let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string + let lotameDataSingle = {}; // we will capture lotame data once & send it to the server as ext.ozone.lotameData + // build the array of params to attach to `imp` let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; + let placementId = placementIdOverrideFromGetParam || this.getPlacementId(ozoneBidRequest); // prefer to use a valid override param, else the bidRequest placement Id obj.id = ozoneBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder ozone made bid for unknown request ID: mb7953.859498327448. Ignoring." - obj.tagid = (ozoneBidRequest.params.placementId).toString(); + obj.tagid = placementId; obj.secure = window.location.protocol === 'https:' ? 1 : 0; // is there a banner (or nothing declared, so banner is the default)? let arrBannerSizes = []; - /* NOTE - if there is sizes element in the config root then there will be a mediaTypes.banner element automatically generated for us, so this code is deprecated */ if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { utils.logInfo('OZONE: no mediaTypes detected - will use the sizes array in the config root'); @@ -135,7 +179,13 @@ export const spec = { utils.logInfo('OZONE: setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); } if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { - obj.video = ozoneBidRequest.mediaTypes[VIDEO]; + utils.logInfo('OZONE: openrtb 2.5 compliant video'); + // examine all the video attributes in the config, and either put them into obj.video if allowed by IAB2.5 or else in to obj.video.ext + if (typeof ozoneBidRequest.mediaTypes[VIDEO] == 'object') { + let childConfig = utils.deepAccess(ozoneBidRequest, 'params.video', {}); + obj.video = this.unpackVideoConfigIntoIABformat(ozoneBidRequest.mediaTypes[VIDEO], childConfig); + obj.video = this.addVideoDefaults(obj.video, ozoneBidRequest.mediaTypes[VIDEO], childConfig); + } // we need to duplicate some of the video values let wh = getWidthAndHeightFromVideoObject(obj.video); utils.logInfo('OZONE: setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); @@ -144,13 +194,13 @@ export const spec = { obj.video.h = wh['h']; if (playerSizeIsNestedArray(obj.video)) { // this should never happen; it was in the original spec for this change though. utils.logInfo('OZONE: setting obj.video.format to be an array of objects'); - obj.video.format = [wh]; + obj.video.ext.format = [wh]; } else { utils.logInfo('OZONE: setting obj.video.format to be an object'); - obj.video.format = wh; + obj.video.ext.format = wh; } } else { - utils.logInfo('OZONE: cannot set w, h & format values for video; the config is not right'); + utils.logWarn('OZONE: cannot set w, h & format values for video; the config is not right'); } } // Native integration is not complete yet @@ -159,8 +209,8 @@ export const spec = { utils.logInfo('OZONE: setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); } } - // build the banner request using banner sizes we found in either possible location: if (arrBannerSizes.length > 0) { + // build the banner request using banner sizes we found in either possible location: obj.banner = { topframe: 1, w: arrBannerSizes[0][0] || 0, @@ -171,35 +221,79 @@ export const spec = { }; } // these 3 MUST exist - we check them in the validation method - obj.placementId = (ozoneBidRequest.params.placementId).toString(); - obj.publisherId = (ozoneBidRequest.params.publisherId).toString(); - obj.siteId = (ozoneBidRequest.params.siteId).toString(); + obj.placementId = placementId; // build the imp['ext'] object - obj.ext = {'prebid': {'storedrequest': {'id': (ozoneBidRequest.params.placementId).toString()}}, 'ozone': {}}; + obj.ext = {'prebid': {'storedrequest': {'id': placementId}}, 'ozone': {}}; obj.ext.ozone.adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' obj.ext.ozone.transactionId = ozoneBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit - obj.ext.ozone.oz_pb_v = OZONEVERSION; if (ozoneBidRequest.params.hasOwnProperty('customData')) { obj.ext.ozone.customData = ozoneBidRequest.params.customData; } - if (ozoneBidRequest.params.hasOwnProperty('lotameData')) { - obj.ext.ozone.lotameData = ozoneBidRequest.params.lotameData; + utils.logInfo('OZONE: obj.ext.ozone is ', obj.ext.ozone); + if (ozTestMode != null) { + utils.logInfo('OZONE: setting ozTestMode to ', ozTestMode); + if (obj.ext.ozone.hasOwnProperty('customData')) { + for (let i = 0; i < obj.ext.ozone.customData.length; i++) { + obj.ext.ozone.customData[i]['targeting']['oztestmode'] = ozTestMode; + } + } else { + obj.ext.ozone.customData = [{'settings': {}, 'targeting': {'oztestmode': ozTestMode}}]; + } + } else { + utils.logInfo('OZONE: no ozTestMode '); } - if (utils.deepAccess(ozoneBidRequest, 'crumbs.pubcid')) { - obj.ext.ozone.pubcid = ozoneBidRequest.crumbs.pubcid; + // now deal with lotame, including the optional override parameters + if (Object.keys(lotameDataSingle).length === 0) { // we've not yet found lotameData, see if we can get it from this bid request object + lotameDataSingle = this.tryGetLotameData(ozoneBidRequest); } return obj; }); - ozoneRequest.site = {'publisher': {'id': htmlParams.publisherId}, 'page': document.location.href}; - ozoneRequest.test = parseInt(getTestQuerystringValue()); // will be 1 or 0 + // in v 2.0.0 we moved these outside of the individual ad slots + let extObj = {'ozone': {'oz_pb_v': OZONEVERSION, 'oz_rw': placementIdOverrideFromGetParam ? 1 : 0, 'oz_lot_rw': this.propertyBag.lotameWasOverridden}}; + if (validBidRequests.length > 0) { + let userIds = this.findAllUserIds(validBidRequests[0]); + if (userIds.hasOwnProperty('pubcid')) { + extObj.ozone.pubcid = userIds.pubcid; + } + } + extObj.ozone.pv = this.getPageId(); // attach the page ID that will be common to all auciton calls for this page if refresh() is called + extObj.ozone.lotameData = lotameDataSingle; // 2.4.0 moved lotameData out of bid objects into the single ext.ozone area to remove duplication + let ozOmpFloorDollars = config.getConfig('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') + utils.logInfo('OZONE: oz_omp_floor dollar value = ', ozOmpFloorDollars); + if (typeof ozOmpFloorDollars === 'number') { + extObj.ozone.oz_omp_floor = ozOmpFloorDollars; + } else if (typeof ozOmpFloorDollars !== 'undefined') { + utils.logError('OZONE: oz_omp_floor is invalid - IF SET then this must be a number, representing dollar value eg. oz_omp_floor: 1.55. You have it set as a ' + (typeof ozOmpFloorDollars)); + } + let ozWhitelistAdserverKeys = config.getConfig('ozone.oz_whitelist_adserver_keys'); + let useOzWhitelistAdserverKeys = utils.isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; + extObj.ozone.oz_kvp_rw = useOzWhitelistAdserverKeys ? 1 : 0; + + var userExtEids = this.generateEids(validBidRequests); // generate the UserIDs in the correct format for UserId module + + ozoneRequest.site = { + 'publisher': {'id': htmlParams.publisherId}, + 'page': document.location.href, + 'id': htmlParams.siteId + }; + ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] == 'true') ? 1 : 0; + + // this is for 2.2.1 + // coppa compliance + if (config.getConfig('coppa') === true) { + utils.deepSetValue(ozoneRequest, 'regs.coppa', 1); + } + // return the single request object OR the array: if (singleRequest) { utils.logInfo('OZONE: buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. ozoneRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? ozoneRequest.imp = tosendtags; + ozoneRequest.ext = extObj; ozoneRequest.source = {'tid': bidderRequest.auctionId}; // RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + utils.deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); var ret = { method: 'POST', url: OZONEURI, @@ -207,7 +301,8 @@ export const spec = { bidderRequest: bidderRequest }; utils.logInfo('OZONE: buildRequests ozoneRequest for single = ', ozoneRequest); - utils.logInfo('OZONE: buildRequests going to return for single: ', ret); + this.propertyBag.buildRequestsEnd = new Date().getTime(); + utils.logInfo(`OZONE: buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; } // not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array. @@ -218,7 +313,9 @@ export const spec = { ozoneRequestSingle.id = imp.ext.ozone.transactionId; // Unique ID of the bid request, provided by the exchange. ozoneRequestSingle.auctionId = imp.ext.ozone.transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; + ozoneRequestSingle.ext = extObj; ozoneRequestSingle.source = {'tid': imp.ext.ozone.transactionId}; + utils.deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); utils.logInfo('OZONE: buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', @@ -227,7 +324,8 @@ export const spec = { bidderRequest: bidderRequest }; }); - utils.logInfo('OZONE: buildRequests going to return for non-single: ', arrRet); + this.propertyBag.buildRequestsEnd = new Date().getTime(); + utils.logInfo(`OZONE: buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); return arrRet; }, /** @@ -241,85 +339,528 @@ export const spec = { * @returns {*} */ interpretResponse(serverResponse, request) { + let startTime = new Date().getTime(); + utils.logInfo(`OZONE: interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); + utils.logInfo(`OZONE: serverResponse, request`, serverResponse, request); serverResponse = serverResponse.body || {}; - if (!serverResponse.hasOwnProperty('seatbid')) { return []; } - if (typeof serverResponse.seatbid !== 'object') { return []; } + // note that serverResponse.id value is the auction_id we might want to use for reporting reasons. + if (!serverResponse.hasOwnProperty('seatbid')) { + return []; + } + if (typeof serverResponse.seatbid !== 'object') { + return []; + } let arrAllBids = []; + let enhancedAdserverTargeting = config.getConfig('ozone.enhancedAdserverTargeting'); + utils.logInfo('OZONE: enhancedAdserverTargeting', enhancedAdserverTargeting); + if (typeof enhancedAdserverTargeting == 'undefined') { + enhancedAdserverTargeting = true; + } + utils.logInfo('OZONE: enhancedAdserverTargeting', enhancedAdserverTargeting); serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. + serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); + let ozOmpFloorDollars = config.getConfig('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') + let addOzOmpFloorDollars = typeof ozOmpFloorDollars === 'number'; + let ozWhitelistAdserverKeys = config.getConfig('ozone.oz_whitelist_adserver_keys'); + let useOzWhitelistAdserverKeys = utils.isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; + for (let i = 0; i < serverResponse.seatbid.length; i++) { let sb = serverResponse.seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { - const {defaultWidth, defaultHeight} = defaultSize(request.bidderRequest.bids[j]); // there should be the same number of bids as requests, so index [j] should always exist. + let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); + utils.logInfo(`OZONE seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); + const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); let thisBid = ozoneAddStandardProperties(sb.bid[j], defaultWidth, defaultHeight); - - // from https://github.com/prebid/Prebid.js/pull/1082 - if (utils.deepAccess(thisBid, 'ext.prebid.type') === VIDEO) { - utils.logInfo('OZONE: going to attach a renderer to:', j); - let renderConf = createObjectForInternalVideoRender(thisBid); - thisBid.renderer = Renderer.install(renderConf); - } else { - utils.logInfo('OZONE: bid is not a video, will not attach a renderer: ', j); + let videoContext = null; + let isVideo = false; + let bidType = utils.deepAccess(thisBid, 'ext.prebid.type'); + utils.logInfo(`OZONE: this bid type is : ${bidType}`, j); + if (bidType === VIDEO) { + isVideo = true; + videoContext = this.getVideoContextForBidId(thisBid.bidId, request.bidderRequest.bids); // should be instream or outstream (or null if error) + if (videoContext === 'outstream') { + utils.logInfo('OZONE: going to attach a renderer to OUTSTREAM video : ', j); + thisBid.renderer = newRenderer(thisBid.bidId); + } else { + utils.logInfo('OZONE: bid is not an outstream video, will not attach a renderer: ', j); + } } - - let ozoneInternalKey = thisBid.bidId; let adserverTargeting = {}; - // all keys for all bidders for this bid have to be added to all objects returned, else some keys will not be sent to ads? - let allBidsForThisBidid = ozoneGetAllBidsForBidId(ozoneInternalKey, serverResponse.seatbid); - // add all the winning & non-winning bids for this bidId: - utils.logInfo('OZONE: Going to iterate allBidsForThisBidId', allBidsForThisBidid); - Object.keys(allBidsForThisBidid).forEach(function(bidderName, index, ar2) { - adserverTargeting['oz_' + bidderName] = bidderName; - adserverTargeting['oz_' + bidderName + '_pb'] = String(allBidsForThisBidid[bidderName].price); - adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); - adserverTargeting['oz_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); - adserverTargeting['oz_' + bidderName + '_imp_id'] = String(allBidsForThisBidid[bidderName].impid); - adserverTargeting['oz_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); - adserverTargeting['oz_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); - if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { - adserverTargeting['oz_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); + if (enhancedAdserverTargeting) { + let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); + // add all the winning & non-winning bids for this bidId: + utils.logInfo('OZONE: Going to iterate allBidsForThisBidId', allBidsForThisBidid); + Object.keys(allBidsForThisBidid).forEach(function (bidderName, index, ar2) { + utils.logInfo(`OZONE: adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); + // let bidderName = bidderNameWH.split('_')[0]; + adserverTargeting['oz_' + bidderName] = bidderName; + adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); + adserverTargeting['oz_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); + adserverTargeting['oz_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); + adserverTargeting['oz_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); + if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { + adserverTargeting['oz_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); + } + if (addOzOmpFloorDollars) { + adserverTargeting['oz_' + bidderName + '_omp'] = allBidsForThisBidid[bidderName].price >= ozOmpFloorDollars ? '1' : '0'; + } + if (isVideo) { + adserverTargeting['oz_' + bidderName + '_vid'] = videoContext; // outstream or instream + } + let flr = utils.deepAccess(allBidsForThisBidid[bidderName], 'ext.bidder.ozone.floor', null); + if (flr != null) { + adserverTargeting['oz_' + bidderName + '_flr'] = flr; + } + let rid = utils.deepAccess(allBidsForThisBidid[bidderName], 'ext.bidder.ozone.ruleId', null); + if (rid != null) { + adserverTargeting['oz_' + bidderName + '_rid'] = rid; + } + if (bidderName.match(/^ozappnexus/)) { + adserverTargeting['oz_' + bidderName + '_sid'] = String(allBidsForThisBidid[bidderName].cid); + } + }); + } else { + if (useOzWhitelistAdserverKeys) { + utils.logWarn('OZONE: You have set a whitelist of adserver keys but this will be ignored because ozone.enhancedAdserverTargeting is set to false. No per-bid keys will be sent to adserver.'); + } else { + utils.logInfo('OZONE: ozone.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.'); } - }); + } // also add in the winning bid, to be sent to dfp - let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(ozoneInternalKey, serverResponse.seatbid); + let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); adserverTargeting['oz_auc_id'] = String(request.bidderRequest.auctionId); adserverTargeting['oz_winner'] = String(winningSeat); - adserverTargeting['oz_winner_auc_id'] = String(winningBid.id); - adserverTargeting['oz_winner_imp_id'] = String(winningBid.impid); - adserverTargeting['oz_response_id'] = String(serverResponse.id); - adserverTargeting['oz_pb_v'] = OZONEVERSION; + if (enhancedAdserverTargeting) { + adserverTargeting['oz_imp_id'] = String(winningBid.impid); + adserverTargeting['oz_pb_v'] = OZONEVERSION; + } + if (useOzWhitelistAdserverKeys) { // delete any un-whitelisted keys + utils.logInfo('OZONE: Going to filter out adserver targeting keys not in the whitelist: ', ozWhitelistAdserverKeys); + Object.keys(adserverTargeting).forEach(function(key) { if (ozWhitelistAdserverKeys.indexOf(key) === -1) { delete adserverTargeting[key]; } }); + } thisBid.adserverTargeting = adserverTargeting; arrAllBids.push(thisBid); } } + let endTime = new Date().getTime(); + utils.logInfo(`OZONE: interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); return arrAllBids; }, - getUserSyncs(optionsType, serverResponse) { + /** + * If a bidder bids for > 1 size for an adslot, allow only the highest bid + * @param seatbid object (serverResponse.seatbid) + */ + removeSingleBidderMultipleBids(seatbid) { + var ret = []; + for (let i = 0; i < seatbid.length; i++) { + let sb = seatbid[i]; + var retSeatbid = {'seat': sb.seat, 'bid': []}; + var bidIds = []; + for (let j = 0; j < sb.bid.length; j++) { + var candidate = sb.bid[j]; + if (utils.contains(bidIds, candidate.impid)) { + continue; // we've already fully assessed this impid, found the highest bid from this seat for it + } + bidIds.push(candidate.impid); + for (let k = j + 1; k < sb.bid.length; k++) { + if (sb.bid[k].impid === candidate.impid && sb.bid[k].price > candidate.price) { + candidate = sb.bid[k]; + } + } + retSeatbid.bid.push(candidate); + } + ret.push(retSeatbid); + } + return ret; + }, + // see http://prebid.org/dev-docs/bidder-adaptor.html#registering-user-syncs + getUserSyncs(optionsType, serverResponse, gdprConsent) { + utils.logInfo('OZONE: getUserSyncs optionsType, serverResponse, gdprConsent, cookieSyncBag', optionsType, serverResponse, gdprConsent, this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { return []; } if (optionsType.iframeEnabled) { + var arrQueryString = []; + if (document.location.search.match(/pbjs_debug=true/)) { + arrQueryString.push('pbjs_debug=true'); + } + arrQueryString.push('gdpr=' + (utils.deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); + arrQueryString.push('gdpr_consent=' + utils.deepAccess(gdprConsent, 'consentString', '')); + var objKeys = Object.getOwnPropertyNames(this.cookieSyncBag.userIdObject); + for (let idx in objKeys) { + let keyname = objKeys[idx]; + arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); + } + arrQueryString.push('publisherId=' + this.cookieSyncBag.publisherId); + arrQueryString.push('siteId=' + this.cookieSyncBag.siteId); + arrQueryString.push('cb=' + Date.now()); + + var strQueryString = arrQueryString.join('&'); + if (strQueryString.length > 0) { + strQueryString = '?' + strQueryString; + } + utils.logInfo('OZONE: getUserSyncs going to return cookie sync url : ' + OZONECOOKIESYNC + strQueryString); return [{ type: 'iframe', - url: OZONECOOKIESYNC + url: OZONECOOKIESYNC + strQueryString }]; } + }, + /** + * Find the bid matching the bidId in the request object + * get instream or outstream if this was a video request else null + * @return object|null + */ + getBidRequestForBidId(bidId, arrBids) { + for (let i = 0; i < arrBids.length; i++) { + if (arrBids[i].bidId === bidId) { // bidId in the request comes back as impid in the seatbid bids + return arrBids[i]; + } + } + return null; + }, + /** + * Locate the bid inside the arrBids for this bidId, then discover the video context, and return it. + * IF the bid cannot be found return null, else return a string. + * @param bidId + * @param arrBids + * @return string|null + */ + getVideoContextForBidId(bidId, arrBids) { + let requestBid = this.getBidRequestForBidId(bidId, arrBids); + if (requestBid != null) { + return utils.deepAccess(requestBid, 'mediaTypes.video.context', 'unknown') + } + return null; + }, + /** + * Look for pubcid & all the other IDs according to http://prebid.org/dev-docs/modules/userId.html + * @return map + */ + findAllUserIds(bidRequest) { + var ret = {}; + let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'digitrustid', 'criteortus']; + if (bidRequest.hasOwnProperty('userId')) { + for (let arrayId in searchKeysSingle) { + let key = searchKeysSingle[arrayId]; + if (bidRequest.userId.hasOwnProperty(key)) { + ret[key] = bidRequest.userId[key]; + } + } + var lipbid = utils.deepAccess(bidRequest.userId, 'lipb.lipbid'); + if (lipbid) { + ret['lipb'] = {'lipbid': lipbid}; + } + } + if (!ret.hasOwnProperty('pubcid')) { + var pubcid = utils.deepAccess(bidRequest, 'crumbs.pubcid'); + if (pubcid) { + ret['pubcid'] = pubcid; // if built with old pubCommonId module + } + } + return ret; + }, + /** + * get all the lotame override keys/values from the querystring. + * @return object containing zero or more keys/values + */ + getLotameOverrideParams() { + const arrGet = this.getGetParametersAsObject(); + utils.logInfo('OZONE: getLotameOverrideParams - arrGet=', arrGet); + let arrRet = {}; + for (let i in ALLOWED_LOTAME_PARAMS) { + if (arrGet.hasOwnProperty(ALLOWED_LOTAME_PARAMS[i])) { + arrRet[ALLOWED_LOTAME_PARAMS[i]] = arrGet[ALLOWED_LOTAME_PARAMS[i]]; + } + } + return arrRet; + }, + /** + * Boolean function to check that this lotame data is valid (check Audience.id) + */ + isLotameDataValid(lotameObj) { + if (!lotameObj.hasOwnProperty('Profile')) return false; + let prof = lotameObj.Profile; + if (!prof.hasOwnProperty('tpid')) return false; + if (!prof.hasOwnProperty('pid')) return false; + let audiences = utils.deepAccess(prof, 'Audiences.Audience'); + if (typeof audiences != 'object') { + return false; + } + for (var i = 0; i < audiences.length; i++) { + let aud = audiences[i]; + if (!aud.hasOwnProperty('id')) { + return false; + } + } + return true; // All Audiences objects have an 'id' key + }, + /** + * Use the arrOverride keys/vals to update the arrExisting lotame object. + * Ideally we will only be using the oz_lotameid value to update the audiences id, but in the event of bad/missing + * pid & tpid we will also have to use substitute values for those too. + * + * @param objOverride object will contain all the ALLOWED_LOTAME_PARAMS parameters + * @param lotameData object might be {} or contain the lotame data + */ + makeLotameObjectFromOverride(objOverride, lotameData) { + if ((lotameData.hasOwnProperty('Profile') && Object.keys(lotameData.Profile).length < 3) || + (!lotameData.hasOwnProperty('Profile'))) { // bad or empty lotame object (should contain pid, tpid & Audiences object) - build a total replacement + utils.logInfo('OZONE: makeLotameObjectFromOverride will return a full default lotame object'); + return { + 'Profile': { + 'tpid': objOverride['oz_lotametpid'], + 'pid': objOverride['oz_lotamepid'], + 'Audiences': {'Audience': [{'id': objOverride['oz_lotameid'], 'abbr': objOverride['oz_lotameid']}]} + } + }; + } + if (utils.deepAccess(lotameData, 'Profile.Audiences.Audience')) { + utils.logInfo('OZONE: makeLotameObjectFromOverride will return the existing lotame object with updated Audience by oz_lotameid'); + lotameData.Profile.Audiences.Audience = [{'id': objOverride['oz_lotameid'], 'abbr': objOverride['oz_lotameid']}]; + return lotameData; + } + utils.logInfo('OZONE: makeLotameObjectFromOverride Weird error - failed to find Profile.Audiences.Audience in lotame object. Will return the object as-is'); + return lotameData; + }, + /** + * Convenient method to get the value we need for the placementId - ONLY from the bidRequest - NOT taking into account any GET override ID + * @param bidRequest + * @return string + */ + getPlacementId(bidRequest) { + return (bidRequest.params.placementId).toString(); + }, + /** + * GET parameter introduced in 2.2.0 : ozstoredrequest + * IF the GET parameter exists then it must validate for placementId correctly + * IF there's a $_GET['ozstoredrequest'] & it's valid then return this. Else return null. + * @returns null|string + */ + getPlacementIdOverrideFromGetParam() { + let arr = this.getGetParametersAsObject(); + if (arr.hasOwnProperty('ozstoredrequest')) { + if (this.isValidPlacementId(arr.ozstoredrequest)) { + utils.logInfo('OZONE: using GET ozstoredrequest ' + arr.ozstoredrequest + ' to replace placementId'); + return arr.ozstoredrequest; + } else { + utils.logError('OZONE: GET ozstoredrequest FAILED VALIDATION - will not use it'); + } + } + return null; + }, + /** + * Produces external userid object + */ + addExternalUserId(eids, value, source, atype) { + if (utils.isStr(value)) { + eids.push({ + source, + uids: [{ + id: value, + atype + }] + }); + } + }, + /** + * Generate an object we can append to the auction request, containing user data formatted correctly for different ssps + * @param validBidRequests + * @return {Array} + */ + generateEids(validBidRequests) { + let eids = []; + this.handleTTDId(eids, validBidRequests); + const bidRequest = validBidRequests[0]; + if (bidRequest && bidRequest.userId) { + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid', 1); + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcommon', 1); + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteortus.${BIDDER_CODE}.userid`), 'criteortus', 1); + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1); + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.lipb.lipbid`), 'liveintent.com', 1); + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableId.eid`), 'parrable.com', 1); + } + return eids; + }, + handleTTDId(eids, validBidRequests) { + let ttdId = null; + let adsrvrOrgId = config.getConfig('adsrvrOrgId'); + if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.tdid'))) { + ttdId = validBidRequests[0].userId.tdid; + } else if (adsrvrOrgId && utils.isStr(adsrvrOrgId.TDID)) { + ttdId = adsrvrOrgId.TDID; + } + if (ttdId !== null) { + eids.push({ + 'source': 'adserver.org', + 'uids': [{ + 'id': ttdId, + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }); + } + }, + // Try to use this as the mechanism for reading GET params because it's easy to mock it for tests + getGetParametersAsObject() { + let items = location.search.substr(1).split('&'); + let ret = {}; + let tmp = null; + for (let index = 0; index < items.length; index++) { + tmp = items[index].split('='); + ret[tmp[0]] = tmp[1]; + } + return ret; + }, + /** + * Do we have to block this request? Could be due to config values (no longer checking gdpr) + * @return {boolean|*[]} true = block the request, else false + */ + blockTheRequest() { + // if there is an ozone.oz_request = false then quit now. + let ozRequest = config.getConfig('ozone.oz_request'); + if (typeof ozRequest == 'boolean' && !ozRequest) { + utils.logWarn('OZONE: Will not allow auction : ozone.oz_request is set to false'); + return true; + } + return false; + }, + /** + * This returns a random ID for this page. It starts off with the current ms timestamp then appends a random component + * @return {string} + */ + getPageId: function() { + if (this.propertyBag.pageId == null) { + let randPart = ''; + let allowable = '0123456789abcdefghijklmnopqrstuvwxyz'; + for (let i = 20; i > 0; i--) { + randPart += allowable[Math.floor(Math.random() * 36)]; + } + this.propertyBag.pageId = new Date().getTime() + '_' + randPart; + } + return this.propertyBag.pageId; + }, + /** + * handle the complexity of there possibly being lotameData override (may be valid/invalid) & there may or may not be lotameData present in the bidRequest + * NOTE THAT this will also set this.propertyBag.lotameWasOverridden=1 if we use lotame override + * @param ozoneBidRequest + * @return object representing the absolute lotameData we need to use. + */ + tryGetLotameData: function(ozoneBidRequest) { + const arrLotameOverride = this.getLotameOverrideParams(); + let ret = {}; + if (Object.keys(arrLotameOverride).length === ALLOWED_LOTAME_PARAMS.length) { + // all override params are present, override lotame object: + if (ozoneBidRequest.params.hasOwnProperty('lotameData')) { + ret = this.makeLotameObjectFromOverride(arrLotameOverride, ozoneBidRequest.params.lotameData); + } else { + ret = this.makeLotameObjectFromOverride(arrLotameOverride, {}); + } + this.propertyBag.lotameWasOverridden = 1; + } else if (ozoneBidRequest.params.hasOwnProperty('lotameData')) { + // no lotame override, use it as-is + if (this.isLotameDataValid(ozoneBidRequest.params.lotameData)) { + ret = ozoneBidRequest.params.lotameData; + } else { + utils.logError('OZONE: INVALID LOTAME DATA FOUND - WILL NOT USE THIS AT ALL ELSE IT MIGHT BREAK THE AUCTION CALL!', ozoneBidRequest.params.lotameData); + ret = {}; + } + } + return ret; + }, + unpackVideoConfigIntoIABformat(videoConfig, childConfig) { + let ret = {'ext': {}}; + ret = this._unpackVideoConfigIntoIABformat(ret, videoConfig); + ret = this._unpackVideoConfigIntoIABformat(ret, childConfig); + return ret; + }, + /** + * + * look in ONE object to get video config (we need to call this multiple times, so child settings override parent) + * @param ret + * @param objConfig + * @return {*} + * @private + */ + _unpackVideoConfigIntoIABformat(ret, objConfig) { + let arrVideoKeysAllowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; + for (const key in objConfig) { + var found = false; + arrVideoKeysAllowed.forEach(function(arg) { + if (arg === key) { + ret[key] = objConfig[key]; + found = true; + } + }); + if (!found) { + ret.ext[key] = objConfig[key]; + } + } + // handle ext separately, if it exists; we have probably built up an ext object already + if (objConfig.hasOwnProperty('ext') && typeof objConfig.ext === 'object') { + if (objConfig.hasOwnProperty('ext')) { + ret.ext = utils.mergeDeep(ret.ext, objConfig.ext); + } else { + ret.ext = objConfig.ext; + } + } + return ret; + }, + addVideoDefaults(objRet, videoConfig, childConfig) { + objRet = this._addVideoDefaults(objRet, videoConfig, false); + objRet = this._addVideoDefaults(objRet, childConfig, true); // child config will override parent config + return objRet; + }, + /** + * modify objRet, adding in default values + * @param objRet + * @param objConfig + * @param addIfMissing + * @return {*} + * @private + */ + _addVideoDefaults(objRet, objConfig, addIfMissing) { + // add inferred values & any default values we want. + let context = utils.deepAccess(objConfig, 'context'); + if (context === 'outstream') { + objRet.placement = 3; + } else if (context === 'instream') { + objRet.placement = 1; + } + let skippable = utils.deepAccess(objConfig, 'skippable', null); + if (skippable == null) { + if (addIfMissing && !objRet.hasOwnProperty('skip')) { + objRet.skip = skippable ? 1 : 0; + } + } else { + objRet.skip = skippable ? 1 : 0; + } + return objRet; } -} +}; + /** * add a page-level-unique adId element to all server response bids. - * NOTE that this is distructive - it mutates the serverResponse object sent in as a parameter + * NOTE that this is destructive - it mutates the serverResponse object sent in as a parameter * @param seatbid object (serverResponse.seatbid) * @returns seatbid object */ export function injectAdIdsIntoAllBidResponses(seatbid) { + utils.logInfo('OZONE: injectAdIdsIntoAllBidResponses', seatbid); for (let i = 0; i < seatbid.length; i++) { let sb = seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { - sb.bid[j]['adId'] = sb.bid[j]['impid'] + '-' + i; // modify the bidId per-bid, so each bid has a unique adId within this response, and dfp can select one. + // modify the bidId per-bid, so each bid has a unique adId within this response, and dfp can select one. + // 2020-06 we now need a second level of ID because there might be multiple identical impid's within a seatbid! + sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-${j}`; } } return seatbid; } + export function checkDeepArray(Arr) { if (Array.isArray(Arr)) { if (Array.isArray(Arr[0])) { @@ -331,6 +872,7 @@ export function checkDeepArray(Arr) { return Arr; } } + export function defaultSize(thebidObj) { if (!thebidObj) { utils.logInfo('OZONE: defaultSize received empty bid obj! going to return fixed default size'); @@ -359,7 +901,8 @@ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) let theseBids = serverResponseSeatBid[j].bid; let thisSeat = serverResponseSeatBid[j].seat; for (let k = 0; k < theseBids.length; k++) { - if (theseBids[k].impid === requestBidId) { // we've found a matching server response bid for this request bid + if (theseBids[k].impid === requestBidId) { + // we've found a matching server response bid for this request bid if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) { thisBidWinner = theseBids[k]; winningSeat = thisSeat; @@ -372,10 +915,10 @@ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) } /** - * Get a list of all the bids, for this bidId + * Get a list of all the bids, for this bidId. The keys in the response object will be {seatname} OR {seatname}{w}x{h} if seatname already exists * @param matchBidId * @param serverResponseSeatBid - * @returns {} = {ozone:{obj}, appnexus:{obj}, ... } + * @returns {} = {ozone|320x600:{obj}, ozone|320x250:{obj}, appnexus|300x250:{obj}, ... } */ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { let objBids = {}; @@ -383,8 +926,15 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { let theseBids = serverResponseSeatBid[j].bid; let thisSeat = serverResponseSeatBid[j].seat; for (let k = 0; k < theseBids.length; k++) { - if (theseBids[k].impid === matchBidId) { // we've found a matching server response bid for the request bid we're looking for - objBids[thisSeat] = theseBids[k]; + if (theseBids[k].impid === matchBidId) { + if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid + // objBids[`${thisSeat}${theseBids[k].w}x${theseBids[k].h}`] = theseBids[k]; + if (objBids[thisSeat]['price'] < theseBids[k].price) { + objBids[thisSeat] = theseBids[k]; + } + } else { + objBids[thisSeat] = theseBids[k]; + } } } } @@ -399,7 +949,7 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { export function getRoundedBid(price, mediaType) { const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); // might be string or object or nothing; if set then this takes precedence over 'priceGranularity' let objBuckets = config.getConfig('customPriceBucket'); // this is always an object - {} if strBuckets is not 'custom' - let strBuckets = config.getConfig('priceGranularity'); // priceGranularity value, always a string ** if priceGranularity is set to an object then it's always 'custom' + let strBuckets = config.getConfig('priceGranularity'); // priceGranularity value, always a string ** if priceGranularity is set to an object then it's always 'custom' ** let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); @@ -421,7 +971,7 @@ export function getRoundedBid(price, mediaType) { }; if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) { let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey]; - utils.logInfo('OZONE: looking for priceStringsKey:', priceStringsKey); + utils.logInfo('OZONE: getRoundedBid: looking for priceStringsKey:', priceStringsKey); return priceStringsObj[priceStringsKey]; } return priceStringsObj['auto']; @@ -443,7 +993,7 @@ export function getGranularityKeyName(mediaType, mediaTypeGranularity, strBucket if (typeof strBuckets === 'string') { return strBuckets; } - return 'auto'; // fall back to a default key - should literally never be needed though. + return 'auto'; // fall back to a default key - should literally never be needed. } /** @@ -475,75 +1025,10 @@ export function ozoneAddStandardProperties(seatBid, defaultWidth, defaultHeight) seatBid.netRevenue = true; seatBid.creativeId = seatBid.crid; seatBid.currency = 'USD'; - seatBid.ttl = 60; + seatBid.ttl = 300; return seatBid; } -/** - * we need to add test=1 or test=0 to the get params sent to the server. - * Get the value set as pbjs_debug= in the url, OR 0. - * @returns {*} - */ -export function getTestQuerystringValue() { - let searchString = window.location.search.substring(1); - let params = searchString.split('&'); - for (let i = 0; i < params.length; i++) { - let val = params[i].split('='); - if (val[0] === 'pbjs_debug') { - return val[1] === 'true' ? 1 : 0; - } - } - return 0; -} - -/** - * Generate a random number per ad; I'll use the current ms timestamp, then append 8 random alpha/numeric characters - * Randomness : 1 in 208 billion random combinations per-millisecond, non-repeating sequence. - * - * @returns {*} - */ -export function pgGuid() { - return new Date().getTime() + 'xxxxxxxx'.replace(/x/g, function(c) { - return Math.round((Math.random() * 36)).toString(36); - }); -} - -function createObjectForInternalVideoRender(bid) { - let obj = { - url: OZONE_RENDERER_URL, - callback: () => onOutstreamRendererLoaded(bid) - } - return obj; -} - -function onOutstreamRendererLoaded(bid) { - try { - bid.renderer.setRender(outstreamRender); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err) - } -} - -function outstreamRender(bid) { - window.ozoneVideo.outstreamRender(bid); -} - -/** - * convert {1: true, - 2: true, - 3: true, - 4: true, - 5: true} - to : [1,2,3,4,5] - * @param obj - */ -function toFlatArray(obj) { - let ret = []; - Object.keys(obj).forEach(function(key) { if (obj[key]) { ret.push(parseInt(key)); } }); - utils.logInfo('OZONE: toFlatArray:', obj, 'returning', ret); - return ret; -} - /** * * @param objVideo will be like {"playerSize":[640,480],"mimes":["video/mp4"],"context":"outstream"} or POSSIBLY {"playerSize":[[640,480]],"mimes":["video/mp4"],"context":"outstream"} @@ -591,17 +1076,45 @@ export function playerSizeIsNestedArray(objVideo) { */ function getPlayerSizeFromObject(objVideo) { utils.logInfo('OZONE: getPlayerSizeFromObject received object', objVideo); - if (!objVideo.hasOwnProperty('playerSize')) { - utils.logError('OZONE: getPlayerSizeFromObject FAILED: no playerSize in video object', objVideo); + let playerSize = utils.deepAccess(objVideo, 'playerSize'); + if (!playerSize) { + playerSize = utils.deepAccess(objVideo, 'ext.playerSize'); + } + if (!playerSize) { + utils.logError('OZONE: getPlayerSizeFromObject FAILED: no playerSize in video object or ext', objVideo); return null; } - let playerSize = objVideo.playerSize; if (typeof playerSize !== 'object') { utils.logError('OZONE: getPlayerSizeFromObject FAILED: playerSize is not an object/array', objVideo); return null; } return playerSize; } +/* + Rendering video ads - create a renderer instance, mark it as not loaded, set a renderer function. + The renderer function will not assume that the renderer script is loaded - it will push() the ultimate render function call + */ +function newRenderer(adUnitCode, rendererOptions = {}) { + const renderer = Renderer.install({ + url: OZONE_RENDERER_URL, + config: rendererOptions, + loaded: false, + adUnitCode + }); + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('OZONE Prebid Error calling setRender on renderer', err); + } + return renderer; +} +function outstreamRender(bid) { + utils.logInfo('OZONE: outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', bid); + // push to render queue because ozoneVideo may not be loaded yet + bid.renderer.push(() => { + window.ozoneVideo.outstreamRender(bid); + }); +} registerBidder(spec); -utils.logInfo('OZONE: ozoneBidAdapter ended'); +utils.logInfo('OZONE: ozoneBidAdapter was loaded'); diff --git a/modules/ozoneBidAdapter.md b/modules/ozoneBidAdapter.md index 1fe5e681e25..bc8cb6a6102 100644 --- a/modules/ozoneBidAdapter.md +++ b/modules/ozoneBidAdapter.md @@ -37,7 +37,7 @@ adUnits = [{ siteId: '4204204201', /* An ID used to identify a site within a publisher account - required */ placementId: '0420420421', /* an ID used to identify the piece of inventory - required - for appnexus test use 13144370. */ customData: [{"settings": {}, "targeting": {"key": "value", "key2": ["value1", "value2"]}}],/* optional array with 'targeting' placeholder for passing publisher specific key-values for targeting. */ - lotameData: {"key1": "value1", "key2": "value2"} /* optional JSON placeholder for passing Lotame DMP data */ + lotameData: {"Profile": {"tpid":"value","pid":"value","Audiences": {"Audience":[{"id":"value"},{"id":"value2"}]}}}, /* optional JSON placeholder for passing Lotame DMP data */ } }] }]; @@ -64,8 +64,7 @@ adUnits = [{ siteId: '4204204201', /* An ID used to identify a site within a publisher account - required */ customData: [{"settings": {}, "targeting": { "key": "value", "key2": ["value1", "value2"]}}] placementId: '0440440442', /* an ID used to identify the piece of inventory - required - for unruly test use 0440440442. */ - customData: [{"settings": {}, "targeting": {"key": "value", "key2": ["value1", "value2"]}}],/* optional array with 'targeting' placeholder for passing publisher specific key-values for targeting. */ - lotameData: {"key1": "value1", "key2": "value2"}, /* optional JSON placeholder for passing Lotame DMP data */ + lotameData: {"Profile": {"tpid":"value","pid":"value","Audiences": {"Audience":[{"id":"value"},{"id":"value2"}]}}}, /* optional JSON placeholder for passing Lotame DMP data */ video: { skippable: true, /* optional */ playback_method: ['auto_play_sound_off'], /* optional */ diff --git a/modules/padsquadBidAdapter.js b/modules/padsquadBidAdapter.js index 52e1147fe78..24b1d5be3be 100644 --- a/modules/padsquadBidAdapter.js +++ b/modules/padsquadBidAdapter.js @@ -1,6 +1,6 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; -import {BANNER} from '../src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER} from '../src/mediaTypes.js'; const ENDPOINT_URL = 'https://x.padsquad.com/auction'; @@ -83,6 +83,7 @@ export const spec = { ad: bid.adm, ttl: DEFAULT_BID_TTL, creativeId: bid.crid, + meta: { advertiserDomains: bid.adomain }, netRevenue: DEFAULT_NET_REVENUE, currency: DEFAULT_CURRENCY, }) diff --git a/modules/papyrusBidAdapter.js b/modules/papyrusBidAdapter.js index 490dcb46909..a27c5cf618a 100644 --- a/modules/papyrusBidAdapter.js +++ b/modules/papyrusBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const PAPYRUS_ENDPOINT = 'https://prebid.papyrus.global'; const PAPYRUS_CODE = 'papyrus'; diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index e8ad7f9acc1..75f89fffc14 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -5,11 +5,55 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils' -import {ajax} from '../src/ajax'; -import {submodule} from '../src/hook'; +import * as utils from '../src/utils.js' +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { uspDataHandler } from '../src/adapterManager.js'; +import { getStorageManager } from '../src/storageManager.js'; const PARRABLE_URL = 'https://h.parrable.com/prebid'; +const PARRABLE_COOKIE_NAME = '_parrable_id'; +const LEGACY_ID_COOKIE_NAME = '_parrable_eid'; +const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout'; +const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000; +const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; + +const storage = getStorageManager(); + +function getExpirationDate() { + const oneYearFromNow = new Date(utils.timestamp() + ONE_YEAR_MS); + return oneYearFromNow.toGMTString(); +} + +function deserializeParrableId(parrableIdStr) { + const parrableId = {}; + const values = parrableIdStr.split(','); + + values.forEach(function(value) { + const pair = value.split(':'); + // unpack a value of 1 as true + parrableId[pair[0]] = +pair[1] === 1 ? true : pair[1]; + }); + + return parrableId; +} + +function serializeParrableId(parrableId) { + let components = []; + + if (parrableId.eid) { + components.push('eid:' + parrableId.eid); + } + if (parrableId.ibaOptout) { + components.push('ibaOptout:1'); + } + if (parrableId.ccpaOptout) { + components.push('ccpaOptout:1'); + } + + return components.join(','); +} function isValidConfig(configParams) { if (!configParams) { @@ -20,15 +64,72 @@ function isValidConfig(configParams) { utils.logError('User ID - parrableId submodule requires partner list'); return false; } + if (configParams.storage) { + utils.logWarn('User ID - parrableId submodule does not require a storage config'); + } return true; } -function fetchId(configParams, consentData, currentStoredId) { +function readCookie() { + const parrableIdStr = storage.getCookie(PARRABLE_COOKIE_NAME); + if (parrableIdStr) { + return deserializeParrableId(decodeURIComponent(parrableIdStr)); + } + return null; +} + +function writeCookie(parrableId) { + if (parrableId) { + const parrableIdStr = encodeURIComponent(serializeParrableId(parrableId)); + storage.setCookie(PARRABLE_COOKIE_NAME, parrableIdStr, getExpirationDate(), 'lax'); + } +} + +function readLegacyCookies() { + const eid = storage.getCookie(LEGACY_ID_COOKIE_NAME); + const ibaOptout = (storage.getCookie(LEGACY_OPTOUT_COOKIE_NAME) === 'true'); + if (eid || ibaOptout) { + const parrableId = {}; + if (eid) { + parrableId.eid = eid; + } + if (ibaOptout) { + parrableId.ibaOptout = ibaOptout; + } + return parrableId; + } + return null; +} + +function migrateLegacyCookies(parrableId) { + if (parrableId) { + writeCookie(parrableId); + if (parrableId.eid) { + storage.setCookie(LEGACY_ID_COOKIE_NAME, '', EXPIRE_COOKIE_DATE); + } + if (parrableId.ibaOptout) { + storage.setCookie(LEGACY_OPTOUT_COOKIE_NAME, '', EXPIRE_COOKIE_DATE); + } + } +} + +function fetchId(configParams) { if (!isValidConfig(configParams)) return; + let parrableId = readCookie(); + if (!parrableId) { + parrableId = readLegacyCookies(); + migrateLegacyCookies(parrableId); + } + + const eid = (parrableId) ? parrableId.eid : null; + const refererInfo = getRefererInfo(); + const uspString = uspDataHandler.getConsentData(); + const data = { - eid: currentStoredId || null, - trackers: configParams.partner.split(',') + eid, + trackers: configParams.partner.split(','), + url: refererInfo.referer }; const searchParams = { @@ -36,28 +137,56 @@ function fetchId(configParams, consentData, currentStoredId) { _rand: Math.random() }; + if (uspString) { + searchParams.us_privacy = uspString; + } + const options = { method: 'GET', withCredentials: true }; const callback = function (cb) { - const onSuccess = (response) => { - let eid; - if (response) { - try { - let responseObj = JSON.parse(response); - eid = responseObj ? responseObj.eid : undefined; - } catch (error) { - utils.logError(error); + const callbacks = { + success: response => { + let newParrableId = parrableId ? utils.deepClone(parrableId) : {}; + if (response) { + try { + let responseObj = JSON.parse(response); + if (responseObj) { + if (responseObj.ccpaOptout !== true) { + newParrableId.eid = responseObj.eid; + } else { + newParrableId.eid = null; + newParrableId.ccpaOptout = true; + } + if (responseObj.ibaOptout === true) { + newParrableId.ibaOptout = true; + } + } + } catch (error) { + utils.logError(error); + cb(); + } + writeCookie(newParrableId); + cb(newParrableId); + } else { + utils.logError('parrableId: ID fetch returned an empty result'); + cb(); } + }, + error: error => { + utils.logError(`parrableId: ID fetch encountered an error`, error); + cb(); } - cb(eid); }; - ajax(PARRABLE_URL, onSuccess, searchParams, options); + ajax(PARRABLE_URL, callbacks, searchParams, options); }; - return { callback }; + return { + callback, + id: parrableId + }; }; /** @type {Submodule} */ @@ -70,11 +199,14 @@ export const parrableIdSubmodule = { /** * decode the stored id value for passing to bid requests * @function - * @param {Object|string} value + * @param {ParrableId} parrableId * @return {(Object|undefined} */ - decode(value) { - return (value && typeof value === 'string') ? { 'parrableid': value } : undefined; + decode(parrableId) { + if (parrableId && utils.isPlainObject(parrableId)) { + return { parrableId }; + } + return undefined; }, /** @@ -82,10 +214,10 @@ export const parrableIdSubmodule = { * @function * @param {SubmoduleParams} [configParams] * @param {ConsentData} [consentData] - * @returns {function(callback:function)} + * @returns {function(callback:function), id:ParrableId} */ - getId(configParams, consentData, currentStoredId) { - return fetchId(configParams, consentData, currentStoredId); + getId(configParams, gdprConsentData, currentStoredId) { + return fetchId(configParams); } }; diff --git a/modules/performaxBidAdapter.js b/modules/performaxBidAdapter.js new file mode 100644 index 00000000000..8e22a0b2da9 --- /dev/null +++ b/modules/performaxBidAdapter.js @@ -0,0 +1,56 @@ +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const CLIENT = 'hellboy:v0.0.1' +const BIDDER_CODE = 'performax'; +const BIDDER_SHORT_CODE = 'px'; +const ENDPOINT = 'https://dale.performax.cz/hb'; + +export const spec = { + code: BIDDER_CODE, + aliases: [BIDDER_SHORT_CODE], + + isBidRequestValid: function (bid) { + return !!bid.params.slotId; + }, + + buildUrl: function (validBidRequests, bidderRequest) { + const slotIds = validBidRequests.map(request => request.params.slotId); + let url = [`${ENDPOINT}?slotId[]=${slotIds.join()}`]; + url.push('client=' + CLIENT); + url.push('auctionId=' + bidderRequest.auctionId); + return url.join('&'); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return { + method: 'POST', + url: this.buildUrl(validBidRequests, bidderRequest), + data: {'validBidRequests': validBidRequests, 'bidderRequest': bidderRequest}, + options: {contentType: 'application/json'}, + } + }, + + buildHtml: function (ad) { + const keys = Object.keys(ad.data || {}); + return ad.code.replace( + new RegExp('\\$(' + keys.join('|') + ')\\$', 'g'), + (matched, key) => ad.data[key] || matched + ); + }, + + interpretResponse: function (serverResponse, request) { + let bidResponses = []; + for (let i = 0; i < serverResponse.body.length; i++) { + const ad = serverResponse.body[i].ad; + if (ad.type === 'empty') { + logWarn(`One of ads is empty (reason=${ad.reason})`); + continue; + } + serverResponse.body[i].ad = this.buildHtml(ad); + bidResponses.push(serverResponse.body[i]); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/performaxBidAdapter.md b/modules/performaxBidAdapter.md new file mode 100644 index 00000000000..4cf2984a79d --- /dev/null +++ b/modules/performaxBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Performax Bid Adapter +Module Type: Bidder Adapter +Maintainer: development@performax.cz +``` + +# Description + +Connects to Performax exchange for bids. + +Performax bid adapter supports Banner. + + +# Sample Banner Ad Unit: For Publishers + +```javascript + var adUnits = [ + { + code: 'performax-div', + sizes: [[300, 300]], + bids: [ + { + bidder: "performax", + params: { + slotId: 28 // required + } + } + ] + } + ]; +``` + +Where: +* slotId - id of slot in PX system diff --git a/modules/piximediaBidAdapter.js b/modules/piximediaBidAdapter.js index 635b946d724..2617cc8fe42 100644 --- a/modules/piximediaBidAdapter.js +++ b/modules/piximediaBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'piximedia'; const ENDPOINT = 'https://ad.piximedia.com/prebid'; diff --git a/modules/platformioBidAdapter.js b/modules/platformioBidAdapter.js new file mode 100644 index 00000000000..314f738ef81 --- /dev/null +++ b/modules/platformioBidAdapter.js @@ -0,0 +1,307 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +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, bidderRequest) => { + const request = { + id: bidRequests[0].bidderRequestId, + at: 2, + imp: bidRequests.map(slot => impression(slot)), + site: site(bidRequests), + app: app(bidRequests), + device: device(bidRequests), + }; + applyGdpr(bidderRequest, request); + return { + method: 'POST', + url: 'https://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 = idToBidMap[id].w; + bid.height = idToBidMap[id].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 banner(slot) { + if (slot.mediaType === 'banner' || utils.deepAccess(slot, 'mediaTypes.banner')) { + const sizes = utils.deepAccess(slot, 'mediaTypes.banner.sizes'); + if (sizes.length > 1) { + let format = []; + for (let f = 0; f < sizes.length; f++) { + format.push({'w': sizes[f][0], 'h': sizes[f][1]}); + } + return {'format': format}; + } else { + return { + w: sizes[0][0], + h: sizes[0][1] + } + } + } + return null; +} + +function videoImpression(slot) { + if (slot.mediaType === 'video' || utils.deepAccess(slot, 'mediaTypes.video')) { + const sizes = utils.deepAccess(slot, 'mediaTypes.video.playerSize'); + const video = { + w: sizes[0][0], + h: sizes[0][1], + 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: window.location.hostname, + }, + id: siteId.toString(), + ref: window.top.document.referrer, + page: window.location.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) { + utils.logError('platformio.parse', 'ERROR', ex); + } + return null; +} + +function applyGdpr(bidderRequest, ortbRequest) { + if (bidderRequest && bidderRequest.gdprConsent) { + ortbRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; + ortbRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; + } +} + +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; +} + +registerBidder(spec); diff --git a/modules/platformioBidAdapter.md b/modules/platformioBidAdapter.md index ff6335d1d70..863e023f0d7 100644 --- a/modules/platformioBidAdapter.md +++ b/modules/platformioBidAdapter.md @@ -14,7 +14,6 @@ Please use ```platformio``` as the bidder code. ``` var adUnits = [{ code: 'dfp-native-div', - mediaType: 'native', mediaTypes: { native: { title: { @@ -50,7 +49,7 @@ Please use ```platformio``` as the bidder code. mediaTypes: { banner: { sizes: [ - [300, 250] + [300, 250],[300,600] ], } }, @@ -59,16 +58,15 @@ Please use ```platformio``` as the bidder code. params: { pubId: '29521', siteId: '26049', - size: '300X250', placementId: '123', } }] }, { code: 'dfp-video-div', - sizes: [640, 480], mediaTypes: { video: { + playerSize: [[640, 480]], context: "instream" } }, @@ -77,7 +75,6 @@ Please use ```platformio``` as the bidder code. params: { pubId: '29521', siteId: '26049', - size: '640X480', placementId: '123', video: { skipppable: true, diff --git a/modules/playgroundxyzBidAdapter.md b/modules/playgroundxyzBidAdapter.md deleted file mode 100644 index c72b9f34dd3..00000000000 --- a/modules/playgroundxyzBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -``` -Module Name: Playground XYZ Bid Adapter -Module Type: Bidder Adapter -Maintainer: tech+prebid@playgroundxyz.com -``` - -# Description - -Connects to playgroundxyz ad server for bids. - -Playground XYZ bid adapter supports Banner. - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'playgroundxyz', - params: { - placementId: '10433394' - } - }] - } -]; -``` diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 835d09e2010..56e4fdfbfef 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -3,15 +3,15 @@ export const S2S_VENDORS = { 'appnexus': { adapter: 'prebidServer', enabled: true, - endpoint: '//prebid.adnxs.com/pbs/v1/openrtb2/auction', - syncEndpoint: '//prebid.adnxs.com/pbs/v1/cookie_sync', + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + syncEndpoint: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', timeout: 1000 }, 'rubicon': { adapter: 'prebidServer', enabled: true, - endpoint: '//prebid-server.rubiconproject.com/openrtb2/auction', - syncEndpoint: '//prebid-server.rubiconproject.com/cookie_sync', + endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + syncEndpoint: 'https://prebid-server.rubiconproject.com/cookie_sync', timeout: 500 } } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 0d41905d2fd..b3d559d956f 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1,16 +1,17 @@ -import Adapter from '../../src/adapter'; -import { createBid } from '../../src/bidfactory'; -import * as utils from '../../src/utils'; -import { STATUS, S2S, EVENTS } from '../../src/constants'; -import adapterManager from '../../src/adapterManager'; -import { config } from '../../src/config'; -import { VIDEO, NATIVE } from '../../src/mediaTypes'; -import { processNativeAdUnitParams } from '../../src/native'; -import { isValid } from '../../src/adapters/bidderFactory'; -import events from '../../src/events'; -import includes from 'core-js/library/fn/array/includes'; +import Adapter from '../../src/adapter.js'; +import { createBid } from '../../src/bidfactory.js'; +import * as utils from '../../src/utils.js'; +import { STATUS, S2S, EVENTS } from '../../src/constants.json'; +import adapterManager from '../../src/adapterManager.js'; +import { config } from '../../src/config.js'; +import { VIDEO, NATIVE } from '../../src/mediaTypes.js'; +import { processNativeAdUnitParams } from '../../src/native.js'; +import { isValid } from '../../src/adapters/bidderFactory.js'; +import events from '../../src/events.js'; +import includes from 'core-js-pure/features/array/includes.js'; import { S2S_VENDORS } from './config.js'; -import { ajax } from '../../src/ajax'; +import { ajax } from '../../src/ajax.js'; +import find from 'core-js-pure/features/array/find.js'; const getConfig = config.getConfig; @@ -70,6 +71,7 @@ config.setDefaults({ * @property {string} endpoint endpoint to contact * === optional params below === * @property {number} [timeout] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` + * @property {number} [defaultTtl] ttl for S2S bidders when pbs does not return a ttl on the response - defaults to 60` * @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 @@ -238,27 +240,6 @@ function doClientSideSyncs(bidders) { }); } -function _getDigiTrustQueryParams(bidRequest = {}) { - function getDigiTrustId(bidRequest) { - const bidRequestDigitrust = utils.deepAccess(bidRequest, 'bids.0.userId.digitrustid.data'); - if (bidRequestDigitrust) { - return bidRequestDigitrust; - } - - const digiTrustUser = config.getConfig('digiTrustId'); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; - } - let digiTrustId = getDigiTrustId(bidRequest); - // 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 - }; -} - function _appendSiteAppDevice(request, pageUrl) { if (!request) return; @@ -267,9 +248,17 @@ function _appendSiteAppDevice(request, pageUrl) { request.app = config.getConfig('app'); request.app.publisher = {id: _s2sConfig.accountId} } else { - request.site = { - publisher: { id: _s2sConfig.accountId }, - page: pageUrl + request.site = {}; + if (utils.isPlainObject(config.getConfig('site'))) { + request.site = config.getConfig('site'); + } + // set publisher.id if not already defined + if (!utils.deepAccess(request.site, 'publisher.id')) { + utils.deepSetValue(request.site, 'publisher.id', _s2sConfig.accountId); + } + // set site.page if not already defined + if (!request.site.page) { + request.site.page = pageUrl; } } if (typeof config.getConfig('device') === 'object') { @@ -286,6 +275,32 @@ function _appendSiteAppDevice(request, pageUrl) { } } +function addBidderFirstPartyDataToRequest(request) { + const bidderConfig = config.getBidderConfig(); + const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => { + const currBidderConfig = bidderConfig[bidder]; + if (currBidderConfig.fpd) { + const fpd = {}; + if (currBidderConfig.fpd.context) { + fpd.site = currBidderConfig.fpd.context; + } + if (currBidderConfig.fpd.user) { + fpd.user = currBidderConfig.fpd.user; + } + + acc.push({ + bidders: [ bidder ], + config: { fpd } + }); + } + return acc; + }, []); + + if (fpdConfigs.length) { + utils.deepSetValue(request, 'ext.prebid.bidderconfig', fpdConfigs); + } +} + // https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40 let nativeDataIdMap = { sponsoredBy: 1, // sponsored @@ -337,10 +352,55 @@ let nativeEventTrackerMethodMap = { */ let bidIdMap = {}; let nativeAssetCache = {}; // store processed native params to preserve + +/** + * map wurl to auction id and adId for use in the BID_WON event + */ +let wurlMap = {}; + +/** + * @param {string} auctionId + * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() + * @param {string} wurl events.winurl passed from prebidServer as wurl + */ +function addWurl(auctionId, adId, wurl) { + if ([auctionId, adId].every(utils.isStr)) { + wurlMap[`${auctionId}${adId}`] = wurl; + } +} + +/** + * @param {string} auctionId + * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() + */ +function removeWurl(auctionId, adId) { + if ([auctionId, adId].every(utils.isStr)) { + wurlMap[`${auctionId}${adId}`] = undefined; + } +} +/** + * @param {string} auctionId + * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() + * @return {(string|undefined)} events.winurl which was passed as wurl + */ +function getWurl(auctionId, adId) { + if ([auctionId, adId].every(utils.isStr)) { + return wurlMap[`${auctionId}${adId}`]; + } +} + +/** + * remove all cached wurls + */ +export function resetWurlMap() { + wurlMap = {}; +} + const OPEN_RTB_PROTOCOL = { buildRequest(s2sBidRequest, bidRequests, adUnits) { let imps = []; let aliases = {}; + const firstBidRequest = bidRequests[0]; // transform ad unit into array of OpenRTB impression objects adUnits.forEach(adUnit => { @@ -481,8 +541,32 @@ const OPEN_RTB_PROTOCOL = { const imp = { id: adUnit.code, ext, secure: _s2sConfig.secure }; + /** + * Prebid AdSlot + * @type {(string|undefined)} + */ + const pbAdSlot = utils.deepAccess(adUnit, 'fpd.context.pbAdSlot'); + if (typeof pbAdSlot === 'string' && pbAdSlot) { + utils.deepSetValue(imp, 'ext.context.data.pbadslot', pbAdSlot); + } + + /** + * GAM Ad Unit + * @type {(string|undefined)} + */ + const gamAdUnit = utils.deepAccess(adUnit, 'fpd.context.adServer.adSlot'); + if (typeof gamAdUnit === 'string' && gamAdUnit) { + utils.deepSetValue(imp, 'ext.context.data.adslot', gamAdUnit); + } + Object.assign(imp, mediaTypes); + // if storedAuctionResponse has been set, pass SRID + const storedAuctionResponseBid = find(firstBidRequest.bids, bid => (bid.adUnitCode === adUnit.code && bid.storedAuctionResponse)); + if (storedAuctionResponseBid) { + utils.deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', storedAuctionResponseBid.storedAuctionResponse.toString()); + } + if (imp.banner || imp.video || imp.native) { imps.push(imp); } @@ -500,6 +584,8 @@ const OPEN_RTB_PROTOCOL = { test: getConfig('debug') ? 1 : 0, ext: { prebid: { + // set ext.prebid.auctiontimestamp with the auction timestamp. Data type is long integer. + auctiontimestamp: firstBidRequest.auctionStart, targeting: { // includewinners is always true for openrtb includewinners: true, @@ -527,12 +613,7 @@ const OPEN_RTB_PROTOCOL = { request.cur = [adServerCur[0]]; } - _appendSiteAppDevice(request, bidRequests[0].refererInfo.referer); - - const digiTrust = _getDigiTrustQueryParams(bidRequests && bidRequests[0]); - if (digiTrust) { - utils.deepSetValue(request, 'user.ext.digitrust', digiTrust); - } + _appendSiteAppDevice(request, firstBidRequest.refererInfo.referer); // pass schain object if it is present const schain = utils.deepAccess(bidRequests, '0.bids.0.schain'); @@ -546,98 +627,28 @@ const OPEN_RTB_PROTOCOL = { request.ext.prebid.aliases = aliases; } - const bidUserId = utils.deepAccess(bidRequests, '0.bids.0.userId'); - if (bidUserId && typeof bidUserId === 'object' && (bidUserId.tdid || bidUserId.pubcid || bidUserId.parrableid || bidUserId.lipb || bidUserId.id5id || bidUserId.criteoId || bidUserId.britepoolid)) { - utils.deepSetValue(request, 'user.ext.eids', []); - - if (bidUserId.tdid) { - request.user.ext.eids.push({ - source: 'adserver.org', - uids: [{ - id: bidUserId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); - } - - if (bidUserId.pubcid) { - request.user.ext.eids.push({ - source: 'pubcid.org', - uids: [{ - id: bidUserId.pubcid, - }] - }); - } - - if (bidUserId.parrableid) { - request.user.ext.eids.push({ - source: 'parrable.com', - uids: [{ - id: bidUserId.parrableid - }] - }); - } - - if (bidUserId.lipb && bidUserId.lipb.lipbid) { - const liveIntent = { - source: 'liveintent.com', - uids: [{ - id: bidUserId.lipb.lipbid - }] - }; - - if (Array.isArray(bidUserId.lipb.segments) && bidUserId.lipb.segments.length) { - liveIntent.ext = { - segments: bidUserId.lipb.segments - }; - } - request.user.ext.eids.push(liveIntent); - } - - if (bidUserId.id5id) { - request.user.ext.eids.push({ - source: 'id5-sync.com', - uids: [{ - id: bidUserId.id5id, - }] - }); - } - - if (bidUserId.criteoId) { - request.user.ext.eids.push({ - source: 'criteo.com', - uids: [{ - id: bidUserId.criteoId - }] - }); - } - - if (bidUserId.britepoolid) { - request.user.ext.eids.push({ - source: 'britepool.com', - uids: [{ - id: bidUserId.britepoolid - }] - }); - } + const bidUserIdAsEids = utils.deepAccess(bidRequests, '0.bids.0.userIdAsEids'); + if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + utils.deepSetValue(request, 'user.ext.eids', bidUserIdAsEids); } if (bidRequests) { - if (bidRequests[0].gdprConsent) { + if (firstBidRequest.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 (typeof firstBidRequest.gdprConsent.gdprApplies === 'boolean') { + gdprApplies = firstBidRequest.gdprConsent.gdprApplies ? 1 : 0; } utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); - utils.deepSetValue(request, 'user.ext.consent', bidRequests[0].gdprConsent.consentString); + utils.deepSetValue(request, 'user.ext.consent', firstBidRequest.gdprConsent.consentString); + if (firstBidRequest.gdprConsent.addtlConsent && typeof firstBidRequest.gdprConsent.addtlConsent === 'string') { + utils.deepSetValue(request, 'user.ext.ConsentedProvidersSettings.consented_providers', firstBidRequest.gdprConsent.addtlConsent); + } } // US Privacy (CCPA) support - if (bidRequests[0].uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidRequests[0].uspConsent); + if (firstBidRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', firstBidRequest.uspConsent); } } @@ -645,6 +656,15 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'regs.coppa', 1); } + const commonFpd = getConfig('fpd') || {}; + if (commonFpd.context) { + utils.deepSetValue(request, 'site.ext.data', commonFpd.context); + } + if (commonFpd.user) { + utils.deepSetValue(request, 'user.ext.data', commonFpd.user); + } + addBidderFirstPartyDataToRequest(request); + return request; }, @@ -678,10 +698,28 @@ const OPEN_RTB_PROTOCOL = { bidRequest.serverResponseTimeMs = serverResponseTimeMs; } - const extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); + // Look for seatbid[].bid[].ext.prebid.bidid and place it in the bidResponse object for use in analytics adapters as 'pbsBidId' + const bidId = utils.deepAccess(bid, 'ext.prebid.bidid'); + if (utils.isStr(bidId)) { + bidObject.pbsBidId = bidId; + } + + // store wurl by auctionId and adId so it can be accessed from the BID_WON event handler + if (utils.isStr(utils.deepAccess(bid, 'ext.prebid.events.win'))) { + addWurl(bidRequest.auctionId, bidObject.adId, utils.deepAccess(bid, 'ext.prebid.events.win')); + } + + let extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' - if (extPrebidTargeting && typeof extPrebidTargeting === 'object') { + // The removal of hb_winurl and hb_bidid targeting values is temporary + // once we get through the transition, this block will be removed. + if (utils.isPlainObject(extPrebidTargeting)) { + // If wurl exists, remove hb_winurl and hb_bidid targeting attributes + if (utils.isStr(utils.deepAccess(bid, 'ext.prebid.events.win'))) { + extPrebidTargeting = utils.getDefinedParams(extPrebidTargeting, Object.keys(extPrebidTargeting) + .filter(i => (i.indexOf('hb_winurl') === -1 && i.indexOf('hb_bidid') === -1))); + } bidObject.adserverTargeting = extPrebidTargeting; } @@ -693,7 +731,7 @@ const OPEN_RTB_PROTOCOL = { bidObject.playerHeight = sizes[0]; bidObject.playerWidth = sizes[1]; - // try to get cache values from 'response.ext.prebid.cache' + // try to get cache values from 'response.ext.prebid.cache.js' // else try 'bid.ext.prebid.targeting' as fallback if (bid.ext.prebid.cache && typeof bid.ext.prebid.cache.vastXml === 'object' && bid.ext.prebid.cache.vastXml.cacheId && bid.ext.prebid.cache.vastXml.url) { bidObject.videoCacheKey = bid.ext.prebid.cache.vastXml.cacheId; @@ -778,10 +816,13 @@ const OPEN_RTB_PROTOCOL = { bidObject.creative_id = bid.crid; bidObject.creativeId = bid.crid; if (bid.burl) { bidObject.burl = bid.burl; } + bidObject.currency = (response.cur) ? response.cur : DEFAULT_S2S_CURRENCY; + bidObject.meta = bidObject.meta || {}; + if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } - // 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; + // TODO: Remove when prebid-server returns ttl and netRevenue + const configTtl = _s2sConfig.defaultTtl || DEFAULT_S2S_TTL; + bidObject.ttl = (bid.ttl) ? bid.ttl : configTtl; bidObject.netRevenue = (bid.netRevenue) ? bid.netRevenue : DEFAULT_S2S_NETREVENUE; bids.push({ adUnit: bid.impid, bid: bidObject }); @@ -793,6 +834,21 @@ const OPEN_RTB_PROTOCOL = { } }; +/** + * BID_WON event to request the wurl + * @param {Bid} bid the winning bid object + */ +function bidWonHandler(bid) { + const wurl = getWurl(bid.auctionId, bid.adId); + if (utils.isStr(wurl)) { + utils.logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`); + utils.triggerPixel(wurl); + + // remove from wurl cache, since the wurl url was called + removeWurl(bid.auctionId, bid.adId); + } +} + /** * Bidder adapter for Prebid Server */ @@ -876,6 +932,9 @@ export function PrebidServer() { doClientSideSyncs(requestedBidders); } + // Listen for bid won to call wurl + events.on(EVENTS.BID_WON, bidWonHandler); + return Object.assign(this, { callBids: baseAdapter.callBids, setBidderCode: baseAdapter.setBidderCode, diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index f3474abe95a..b98ca864cd5 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -1,6 +1,6 @@ -import {ajaxBuilder} from '../src/ajax'; -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; +import {ajaxBuilder} from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager @@ -9,7 +9,7 @@ const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint' const analyticsType = 'endpoint'; const analyticsName = 'Prebid Manager Analytics: '; -var utils = require('../src/utils'); +var utils = require('../src/utils.js'); var CONSTANTS = require('../src/constants.json'); let ajax = ajaxBuilder(0); @@ -20,6 +20,7 @@ var _startAuction = 0; var _bidRequestTimeout = 0; let flushInterval; var pmAnalyticsEnabled = false; +const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; var w = window; var d = document; @@ -68,6 +69,36 @@ prebidmanagerAnalytics.disableAnalytics = function() { prebidmanagerAnalytics.originDisableAnalytics(); }; +function collectUtmTagData() { + let newUtm = false; + let pmUtmTags = {}; + try { + utmTags.forEach(function (utmKey) { + let utmValue = utils.getParameterByName(utmKey); + if (utmValue !== '') { + newUtm = true; + } + pmUtmTags[utmKey] = utmValue; + }); + if (newUtm === false) { + utmTags.forEach(function (utmKey) { + let itemValue = localStorage.getItem(`pm_${utmKey}`); + if (itemValue.length !== 0) { + pmUtmTags[utmKey] = itemValue; + } + }); + } else { + utmTags.forEach(function (utmKey) { + localStorage.setItem(`pm_${utmKey}`, pmUtmTags[utmKey]); + }); + } + } catch (e) { + utils.logError(`${analyticsName}Error`, e); + pmUtmTags['error_utm'] = 1; + } + return pmUtmTags; +} + function flush() { if (!pmAnalyticsEnabled) { return; @@ -78,7 +109,8 @@ function flush() { pageViewId: _pageViewId, ver: _VERSION, bundleId: initOptions.bundleId, - events: _eventQueue + events: _eventQueue, + utmTags: collectUtmTagData(), }; ajax( diff --git a/modules/priceFloors.js b/modules/priceFloors.js new file mode 100644 index 00000000000..eb1f3aed84c --- /dev/null +++ b/modules/priceFloors.js @@ -0,0 +1,714 @@ +import { getGlobal } from '../src/prebidGlobal.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; +import { getHook } from '../src/hook.js'; +import { createBid } from '../src/bidfactory.js'; +import find from 'core-js-pure/features/array/find.js'; +import { getRefererInfo } from '../src/refererDetection.js'; + +/** + * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. + */ +const MODULE_NAME = 'Price Floors'; + +/** + * @summary Instantiate Ajax so we control the timeout + */ +const ajax = ajaxBuilder(10000); + +/** + * @summary Allowed fields for rules to have + */ +export let allowedFields = ['gptSlot', 'adUnitCode', 'size', 'domain', 'mediaType']; + +/** + * @summary This is a flag to indicate if a AJAX call is processing for a floors request +*/ +let fetching = false; + +/** + * @summary so we only register for our hooks once +*/ +let addedFloorsHook = false; + +/** + * @summary The config to be used. Can be updated via: setConfig or a real time fetch + */ +let _floorsConfig = {}; + +/** + * @summary If a auction is to be delayed by an ongoing fetch we hold it here until it can be resumed + */ +let _delayedAuctions = []; + +/** + * @summary Each auction can have differing floors data depending on execution time or per adunit setup + * So we will be saving each auction offset by it's auctionId in order to make sure data is not changed + * Once the auction commences + */ +export let _floorDataForAuction = {}; + +/** + * @summary Simple function to round up to a certain decimal degree + */ +function roundUp(number, precision) { + return Math.ceil(parseFloat(number) * Math.pow(10, precision)) / Math.pow(10, precision); +} + +let referrerHostname; +function getHostNameFromReferer(referer) { + referrerHostname = utils.parseUrl(referer, {noDecodeWholeURL: true}).hostname; + return referrerHostname; +} + +/** + * @summary floor field types with their matching functions to resolve the actual matched value + */ +export let fieldMatchingFunctions = { + 'size': (bidRequest, bidResponse) => utils.parseGPTSingleSizeArray(bidResponse.size) || '*', + 'mediaType': (bidRequest, bidResponse) => bidResponse.mediaType || 'banner', + 'gptSlot': (bidRequest, bidResponse) => utils.getGptSlotInfoForAdUnitCode(bidRequest.adUnitCode).gptSlot, + 'domain': (bidRequest, bidResponse) => referrerHostname || getHostNameFromReferer(getRefererInfo().referer), + 'adUnitCode': (bidRequest, bidResponse) => bidRequest.adUnitCode +} + +/** + * @summary Based on the fields array in floors data, it enumerates all possible matches based on exact match coupled with + * a "*" catch-all match + * Returns array of Tuple [exact match, catch all] for each field in rules file + */ +function enumeratePossibleFieldValues(floorFields, bidObject, responseObject) { + // generate combination of all exact matches and catch all for each field type + return floorFields.reduce((accum, field) => { + let exactMatch = fieldMatchingFunctions[field](bidObject, responseObject) || '*'; + // storing exact matches as lowerCase since we want to compare case insensitively + accum.push(exactMatch === '*' ? ['*'] : [exactMatch.toLowerCase(), '*']); + return accum; + }, []); +} + +/** + * @summary get's the first matching floor based on context provided. + * Generates all possible rule matches and picks the first matching one. + */ +export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) { + let fieldValues = enumeratePossibleFieldValues(utils.deepAccess(floorData, 'schema.fields') || [], bidObject, responseObject); + if (!fieldValues.length) return { matchingFloor: floorData.default }; + + // look to see iof a request for this context was made already + let matchingInput = fieldValues.map(field => field[0]).join('-'); + // if we already have gotten the matching rule from this matching input then use it! No need to look again + let previousMatch = utils.deepAccess(floorData, `matchingInputs.${matchingInput}`); + if (previousMatch) { + return previousMatch; + } + let allPossibleMatches = generatePossibleEnumerations(fieldValues, utils.deepAccess(floorData, 'schema.delimiter') || '|'); + let matchingRule = find(allPossibleMatches, hashValue => floorData.values.hasOwnProperty(hashValue)); + + let matchingData = { + matchingFloor: floorData.values[matchingRule] || floorData.default, + matchingData: allPossibleMatches[0], // the first possible match is an "exact" so contains all data relevant for anlaytics adapters + matchingRule + }; + // save for later lookup if needed + utils.deepSetValue(floorData, `matchingInputs.${matchingInput}`, {...matchingData}); + return matchingData; +} + +/** + * @summary Generates all possible rule hash's based on input array of array's + * The generated list is of all possible key matches based on fields input + * The list is sorted by least amount of * in rule to most with left most fields taking precedence + */ +function generatePossibleEnumerations(arrayOfFields, delimiter) { + return arrayOfFields.reduce((accum, currentVal) => { + let ret = []; + accum.map(obj => { + currentVal.map(obj1 => { + ret.push(obj + delimiter + obj1) + }); + }); + return ret; + }).sort((left, right) => left.split('*').length - right.split('*').length); +} + +/** + * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted + */ +export function getBiddersCpmAdjustment(bidderName, inputCpm) { + const adjustmentFunction = utils.deepAccess(getGlobal(), `bidderSettings.${bidderName}.bidCpmAdjustment`); + if (adjustmentFunction) { + return parseFloat(adjustmentFunction(inputCpm)); + } + return parseFloat(inputCpm); +} + +/** + * @summary This function takes the original floor and the adjusted floor in order to determine the bidders actual floor + * With js rounding errors with decimal division we utilize similar method as shown in cpmBucketManager.js + */ +export function calculateAdjustedFloor(oldFloor, newFloor) { + const pow = Math.pow(10, 10); + return ((oldFloor * pow) / (newFloor * pow) * (oldFloor * pow)) / pow; +} + +/** + * @summary gets the prebid set sizes depending on the input mediaType + */ +const getMediaTypesSizes = { + banner: (bid) => utils.deepAccess(bid, 'mediaTypes.banner.sizes') || [], + video: (bid) => utils.deepAccess(bid, 'mediaTypes.video.playerSize') || [], + native: (bid) => utils.deepAccess(bid, 'mediaTypes.native.image.sizes') ? [utils.deepAccess(bid, 'mediaTypes.native.image.sizes')] : [] +} + +/** + * @summary for getFloor only, before selecting a rule, if a bidAdapter asks for * in their getFloor params + * Then we may be able to get a better rule than the * ones depending on context of the adUnit + */ +function updateRequestParamsFromContext(bidRequest, requestParams) { + // if adapter asks for *'s then we can do some logic to infer if we can get a more specific rule based on context of bid + let mediaTypesOnBid = Object.keys(bidRequest.mediaTypes || {}); + // if there is only one mediaType then we can just use it + if (requestParams.mediaType === '*' && mediaTypesOnBid.length === 1) { + requestParams.mediaType = mediaTypesOnBid[0]; + } + // if they asked for * size, but for the given mediaType there is only one size, we can just use it + if (requestParams.size === '*' && mediaTypesOnBid.indexOf(requestParams.mediaType) !== -1 && getMediaTypesSizes[requestParams.mediaType] && getMediaTypesSizes[requestParams.mediaType](bidRequest).length === 1) { + requestParams.size = getMediaTypesSizes[requestParams.mediaType](bidRequest)[0]; + } + return requestParams; +} + +/** + * @summary This is the function which will return a single floor based on the input requests + * and matching it to a rule for the current auction + */ +export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: '*'}) { + let bidRequest = this; + let floorData = _floorDataForAuction[bidRequest.auctionId]; + if (!floorData || floorData.skipped) return {}; + + requestParams = updateRequestParamsFromContext(bidRequest, requestParams); + let floorInfo = getFirstMatchingFloor(floorData.data, {...bidRequest}, {mediaType: requestParams.mediaType, size: requestParams.size}); + let currency = requestParams.currency || floorData.data.currency; + + // if bidder asked for a currency which is not what floors are set in convert + if (floorInfo.matchingFloor && currency !== floorData.data.currency) { + try { + floorInfo.matchingFloor = getGlobal().convertCurrency(floorInfo.matchingFloor, floorData.data.currency, currency); + } catch (err) { + utils.logWarn(`${MODULE_NAME}: Unable to get currency conversion for getFloor for bidder ${bidRequest.bidder}. You must have currency module enabled with defaultRates in your currency config`); + // since we were unable to convert to the bidders requested currency, we send back just the actual floors currency to them + currency = floorData.data.currency; + } + } + + // if cpmAdjustment flag is true and we have a valid floor then run the adjustment on it + if (floorData.enforcement.bidAdjustment && floorInfo.matchingFloor) { + let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor); + floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + } + + if (floorInfo.matchingFloor) { + return { + floor: roundUp(floorInfo.matchingFloor, 4), + currency, + }; + } + return {}; +} + +/** + * @summary Takes a floorsData object and converts it into a hash map with appropriate keys + */ +export function getFloorsDataForAuction(floorData, adUnitCode) { + let auctionFloorData = utils.deepClone(floorData); + auctionFloorData.schema.delimiter = floorData.schema.delimiter || '|'; + auctionFloorData.values = normalizeRulesForAuction(auctionFloorData, adUnitCode); + // default the currency to USD if not passed in + auctionFloorData.currency = auctionFloorData.currency || 'USD'; + return auctionFloorData; +} + +/** + * @summary if adUnitCode needs to be added to the offset then it will add it else just return the values + */ +function normalizeRulesForAuction(floorData, adUnitCode) { + let fields = floorData.schema.fields; + let delimiter = floorData.schema.delimiter + + // if we are building the floor data form an ad unit, we need to append adUnit code as to not cause collisions + let prependAdUnitCode = adUnitCode && fields.indexOf('adUnitCode') === -1 && fields.unshift('adUnitCode'); + return Object.keys(floorData.values).reduce((rulesHash, oldKey) => { + let newKey = prependAdUnitCode ? `${adUnitCode}${delimiter}${oldKey}` : oldKey + // we store the rule keys as lower case for case insensitive compare + rulesHash[newKey.toLowerCase()] = floorData.values[oldKey]; + return rulesHash; + }, {}); +} + +/** + * @summary This function will take the adUnits and generate a floor data object to be used during the auction + * Only called if no set config or fetch level data has returned + */ +export function getFloorDataFromAdUnits(adUnits) { + return adUnits.reduce((accum, adUnit) => { + if (isFloorsDataValid(adUnit.floors)) { + // if values already exist we want to not overwrite them + if (!accum.values) { + accum = getFloorsDataForAuction(adUnit.floors, adUnit.code); + accum.location = 'adUnit'; + } else { + let newRules = getFloorsDataForAuction(adUnit.floors, adUnit.code).values; + // copy over the new rules into our values object + Object.assign(accum.values, newRules); + } + } + return accum; + }, {}); +} + +/** + * @summary This function takes the adUnits for the auction and update them accordingly as well as returns the rules hashmap for the auction + */ +export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { + adUnits.forEach((adUnit) => { + adUnit.bids.forEach(bid => { + if (floorData.skipped) { + delete bid.getFloor; + } else { + bid.getFloor = getFloor; + } + // information for bid and analytics adapters + bid.auctionId = auctionId; + bid.floorData = { + skipped: floorData.skipped, + skipRate: floorData.skipRate, + modelVersion: utils.deepAccess(floorData, 'data.modelVersion'), + location: utils.deepAccess(floorData, 'data.location', 'noData'), + floorProvider: floorData.floorProvider, + fetchStatus: _floorsConfig.fetchStatus + } + }); + }); +} + +export function pickRandomModel(modelGroups, weightSum) { + // we loop through the models subtracting the current model weight from our random number + // once we are at or below zero, we return the associated model + let random = Math.floor(Math.random() * weightSum + 1) + for (let i = 0; i < modelGroups.length; i++) { + random -= modelGroups[i].modelWeight; + if (random <= 0) { + return modelGroups[i]; + } + } +}; + +/** + * @summary Updates the adUnits accordingly and returns the necessary floorsData for the current auction + */ +export function createFloorsDataForAuction(adUnits, auctionId) { + let resolvedFloorsData = utils.deepClone(_floorsConfig); + // if using schema 2 pick a model here: + if (utils.deepAccess(resolvedFloorsData, 'data.floorsSchemaVersion') === 2) { + // merge the models specific stuff into the top level data settings (now it looks like floorsSchemaVersion 1!) + let { modelGroups, ...rest } = resolvedFloorsData.data; + resolvedFloorsData.data = Object.assign(rest, pickRandomModel(modelGroups, rest.modelWeightSum)); + } + + // if we do not have a floors data set, we will try to use data set on adUnits + let useAdUnitData = Object.keys(utils.deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0; + if (useAdUnitData) { + resolvedFloorsData.data = getFloorDataFromAdUnits(adUnits); + } else { + resolvedFloorsData.data = getFloorsDataForAuction(resolvedFloorsData.data); + } + // if we still do not have a valid floor data then floors is not on for this auction, so skip + if (Object.keys(utils.deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0) { + resolvedFloorsData.skipped = true; + } else { + // determine the skip rate now + const auctionSkipRate = utils.getParameterByName('pbjs_skipRate') || resolvedFloorsData.skipRate; + const isSkipped = Math.random() * 100 < parseFloat(auctionSkipRate); + resolvedFloorsData.skipped = isSkipped; + } + // add floorData to bids + updateAdUnitsForAuction(adUnits, resolvedFloorsData, auctionId); + return resolvedFloorsData; +} + +/** + * @summary This is the function which will be called to exit our module and continue the auction. + */ +export function continueAuction(hookConfig) { + // only run if hasExited + if (!hookConfig.hasExited) { + // if this current auction is still fetching, remove it from the _delayedAuctions + _delayedAuctions = _delayedAuctions.filter(auctionConfig => auctionConfig.timer !== hookConfig.timer); + + // We need to know the auctionId at this time. So we will use the passed in one or generate and set it ourselves + hookConfig.reqBidsConfigObj.auctionId = hookConfig.reqBidsConfigObj.auctionId || utils.generateUUID(); + + // now we do what we need to with adUnits and save the data object to be used for getFloor and enforcement calls + _floorDataForAuction[hookConfig.reqBidsConfigObj.auctionId] = createFloorsDataForAuction(hookConfig.reqBidsConfigObj.adUnits || getGlobal().adUnits, hookConfig.reqBidsConfigObj.auctionId); + + hookConfig.nextFn.apply(hookConfig.context, [hookConfig.reqBidsConfigObj]); + hookConfig.hasExited = true; + } +} + +function validateSchemaFields(fields) { + if (Array.isArray(fields) && fields.length > 0 && fields.every(field => allowedFields.indexOf(field) !== -1)) { + return true; + } + utils.logError(`${MODULE_NAME}: Fields recieved do not match allowed fields`); + return false; +} + +function isValidRule(key, floor, numFields, delimiter) { + if (typeof key !== 'string' || key.split(delimiter).length !== numFields) { + return false; + } + return typeof floor === 'number'; +} + +function validateRules(floorsData, numFields, delimiter) { + if (typeof floorsData.values !== 'object') { + return false; + } + // if an invalid rule exists we remove it + floorsData.values = Object.keys(floorsData.values).reduce((filteredRules, key) => { + if (isValidRule(key, floorsData.values[key], numFields, delimiter)) { + filteredRules[key] = floorsData.values[key]; + } + return filteredRules + }, {}); + // rules is only valid if at least one rule remains + return Object.keys(floorsData.values).length > 0; +} + +function modelIsValid(model) { + // schema.fields has only allowed attributes + if (!validateSchemaFields(utils.deepAccess(model, 'schema.fields'))) { + return false; + } + return validateRules(model, model.schema.fields.length, model.schema.delimiter || '|') +} + +/** + * @summary Mapping of floor schema version to it's corresponding validation + */ +const floorsSchemaValidation = { + 1: data => modelIsValid(data), + 2: data => { + // model groups should be an array with at least one element + if (!Array.isArray(data.modelGroups) || data.modelGroups.length === 0) { + return false; + } + // every model should have valid schema, as well as an accompanying modelWeight + data.modelWeightSum = 0; + return data.modelGroups.every(model => { + if (typeof model.modelWeight === 'number' && modelIsValid(model)) { + data.modelWeightSum += model.modelWeight; + return true; + } + return false; + }); + } +}; + +/** + * @summary Fields array should have at least one entry and all should match allowed fields + * Each rule in the values array should have a 'key' and 'floor' param + * And each 'key' should have the correct number of 'fields' after splitting + * on the delim. If rule does not match remove it. return if still at least 1 rule + */ +export function isFloorsDataValid(floorsData) { + if (typeof floorsData !== 'object') { + return false; + } + floorsData.floorsSchemaVersion = floorsData.floorsSchemaVersion || 1; + if (typeof floorsSchemaValidation[floorsData.floorsSchemaVersion] !== 'function') { + utils.logError(`${MODULE_NAME}: Unknown floorsSchemaVersion: `, floorsData.floorsSchemaVersion); + return false; + } + return floorsSchemaValidation[floorsData.floorsSchemaVersion](floorsData); +} + +/** + * @summary This function updates the global Floors Data field based on the new one passed in if it is valid + */ +export function parseFloorData(floorsData, location) { + if (floorsData && typeof floorsData === 'object' && isFloorsDataValid(floorsData)) { + utils.logInfo(`${MODULE_NAME}: A ${location} set the auction floor data set to `, floorsData); + return { + ...floorsData, + location + }; + } + utils.logError(`${MODULE_NAME}: The floors data did not contain correct values`, floorsData); +} + +/** + * + * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js + */ +export function requestBidsHook(fn, reqBidsConfigObj) { + // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions) + const hookConfig = { + reqBidsConfigObj, + context: this, + nextFn: fn, + haveExited: false, + timer: null + }; + + // If auction delay > 0 AND we are fetching -> Then wait until it finishes + if (_floorsConfig.auctionDelay > 0 && fetching) { + hookConfig.timer = setTimeout(() => { + utils.logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction`); + _floorsConfig.fetchStatus = 'timeout'; + continueAuction(hookConfig); + }, _floorsConfig.auctionDelay); + _delayedAuctions.push(hookConfig); + } else { + continueAuction(hookConfig); + } +} + +/** + * @summary If an auction was queued to be delayed (waiting for a fetch) then this function will resume + * those delayed auctions when delay is hit or success return or fail return + */ +function resumeDelayedAuctions() { + _delayedAuctions.forEach(auctionConfig => { + // clear the timeout + clearTimeout(auctionConfig.timer); + continueAuction(auctionConfig); + }); + _delayedAuctions = []; +} + +/** + * This function handles the ajax response which comes from the user set URL to fetch floors data from + * @param {object} fetchResponse The floors data response which came back from the url configured in config.floors + */ +export function handleFetchResponse(fetchResponse) { + fetching = false; + _floorsConfig.fetchStatus = 'success'; + let floorResponse; + try { + floorResponse = JSON.parse(fetchResponse); + } catch (ex) { + floorResponse = fetchResponse; + } + // Update the global floors object according to the fetched data + const fetchData = parseFloorData(floorResponse, 'fetch'); + if (fetchData) { + // set .data to it + _floorsConfig.data = fetchData; + // set skipRate override if necessary + _floorsConfig.skipRate = utils.isNumber(fetchData.skipRate) ? fetchData.skipRate : _floorsConfig.skipRate; + _floorsConfig.floorProvider = fetchData.floorProvider || _floorsConfig.floorProvider; + } + + // if any auctions are waiting for fetch to finish, we need to continue them! + resumeDelayedAuctions(); +} + +function handleFetchError(status) { + fetching = false; + _floorsConfig.fetchStatus = 'error'; + utils.logError(`${MODULE_NAME}: Fetch errored with: `, status); + + // if any auctions are waiting for fetch to finish, we need to continue them! + resumeDelayedAuctions(); +} + +/** + * This function handles sending and recieving the AJAX call for a floors fetch + * @param {object} floorsConfig the floors config coming from setConfig + */ +export function generateAndHandleFetch(floorEndpoint) { + // if a fetch url is defined and one is not already occuring, fire it! + if (floorEndpoint.url && !fetching) { + // default to GET and we only support GET for now + let requestMethod = floorEndpoint.method || 'GET'; + if (requestMethod !== 'GET') { + utils.logError(`${MODULE_NAME}: 'GET' is the only request method supported at this time!`); + } else { + ajax(floorEndpoint.url, { success: handleFetchResponse, error: handleFetchError }, null, { method: 'GET' }); + fetching = true; + } + } else if (fetching) { + utils.logWarn(`${MODULE_NAME}: A fetch is already occuring. Skipping.`); + } +} + +/** + * @summary Updates our allowedFields and fieldMatchingFunctions with the publisher defined new ones + */ +function addFieldOverrides(overrides) { + Object.keys(overrides).forEach(override => { + // we only add it if it is not already in the allowed fields and if the passed in value is a function + if (allowedFields.indexOf(override) === -1 && typeof overrides[override] === 'function') { + allowedFields.push(override); + fieldMatchingFunctions[override] = overrides[override]; + } + }); +} + +/** + * @summary This is the function which controls what happens during a pbjs.setConfig({...floors: {}}) is called + */ +export function handleSetFloorsConfig(config) { + _floorsConfig = utils.pick(config, [ + 'enabled', enabled => enabled !== false, // defaults to true + 'auctionDelay', auctionDelay => auctionDelay || 0, + 'floorProvider', floorProvider => utils.deepAccess(config, 'data.floorProvider', floorProvider), + 'endpoint', endpoint => endpoint || {}, + 'skipRate', () => !isNaN(utils.deepAccess(config, 'data.skipRate')) ? config.data.skipRate : config.skipRate || 0, + 'enforcement', enforcement => utils.pick(enforcement || {}, [ + 'enforceJS', enforceJS => enforceJS !== false, // defaults to true + 'enforcePBS', enforcePBS => enforcePBS === true, // defaults to false + 'floorDeals', floorDeals => floorDeals === true, // defaults to false + 'bidAdjustment', bidAdjustment => bidAdjustment !== false, // defaults to true + ]), + 'additionalSchemaFields', additionalSchemaFields => typeof additionalSchemaFields === 'object' && Object.keys(additionalSchemaFields).length > 0 ? addFieldOverrides(additionalSchemaFields) : undefined, + 'data', data => (data && parseFloorData(data, 'setConfig')) || _floorsConfig.data // do not overwrite if passed in data not valid + ]); + + // if enabled then do some stuff + if (_floorsConfig.enabled) { + // handle the floors fetch + generateAndHandleFetch(_floorsConfig.endpoint); + + if (!addedFloorsHook) { + // register hooks / listening events + // when auction finishes remove it's associated floor data after 3 seconds so we stil have it for latent responses + events.on(CONSTANTS.EVENTS.AUCTION_END, (args) => { + setTimeout(() => delete _floorDataForAuction[args.auctionId], 3000); + }); + + // we want our hooks to run after the currency hooks + getGlobal().requestBids.before(requestBidsHook, 50); + // if user has debug on then we want to allow the debugging module to run before this, assuming they are testing priceFloors + // debugging is currently set at 5 priority + getHook('addBidResponse').before(addBidResponseHook, utils.debugTurnedOn() ? 4 : 50); + addedFloorsHook = true; + } + } else { + utils.logInfo(`${MODULE_NAME}: Turning off module`); + + _floorsConfig = {}; + _floorDataForAuction = {}; + + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + getGlobal().requestBids.getHooks({hook: requestBidsHook}).remove(); + + addedFloorsHook = false; + } +} + +/** + * @summary Analytics adapters especially need context of what the floors module is doing in order + * to best create informed models. This function attaches necessary information to the bidResponse object for processing + */ +function addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm) { + bid.floorData = { + floorValue: floorInfo.matchingFloor, + floorRule: floorInfo.matchingRule, + floorCurrency: floorData.data.currency, + cpmAfterAdjustments: adjustedCpm, + enforcements: {...floorData.enforcement}, + matchedFields: {} + }; + floorData.data.schema.fields.forEach((field, index) => { + let matchedValue = floorInfo.matchingData.split(floorData.data.schema.delimiter)[index]; + bid.floorData.matchedFields[field] = matchedValue; + }); +} + +/** + * @summary takes the enforcement flags and the bid itself and determines if it should be floored + */ +function shouldFloorBid(floorData, floorInfo, bid) { + let enforceJS = utils.deepAccess(floorData, 'enforcement.enforceJS') !== false; + let shouldFloorDeal = utils.deepAccess(floorData, 'enforcement.floorDeals') === true || !bid.dealId; + let bidBelowFloor = bid.floorData.cpmAfterAdjustments < floorInfo.matchingFloor; + return enforceJS && (bidBelowFloor && shouldFloorDeal); +} + +/** + * @summary The main driving force of floors. On bidResponse we hook in and intercept bidResponses. + * And if the rule we find determines a bid should be floored we will do so. + */ +export function addBidResponseHook(fn, adUnitCode, bid) { + let floorData = _floorDataForAuction[this.bidderRequest.auctionId]; + // if no floor data or associated bidRequest then bail + const matchingBidRequest = find(this.bidderRequest.bids, bidRequest => bidRequest.bidId && bidRequest.bidId === bid.requestId); + if (!floorData || !bid || floorData.skipped || !matchingBidRequest) { + return fn.call(this, adUnitCode, bid); + } + + // get the matching rule + let floorInfo = getFirstMatchingFloor(floorData.data, {...matchingBidRequest}, {...bid, size: [bid.width, bid.height]}); + + if (!floorInfo.matchingFloor) { + utils.logWarn(`${MODULE_NAME}: unable to determine a matching price floor for bidResponse`, bid); + return fn.call(this, adUnitCode, bid); + } + + // determine the base cpm to use based on if the currency matches the floor currency + let adjustedCpm; + let floorCurrency = floorData.data.currency.toUpperCase(); + let bidResponseCurrency = bid.currency || 'USD'; // if an adapter does not set a bid currency and currency module not on it may come in as undefined + if (floorCurrency === bidResponseCurrency.toUpperCase()) { + adjustedCpm = bid.cpm; + } else if (bid.originalCurrency && floorCurrency === bid.originalCurrency.toUpperCase()) { + adjustedCpm = bid.originalCpm; + } else { + try { + adjustedCpm = getGlobal().convertCurrency(bid.cpm, bidResponseCurrency.toUpperCase(), floorCurrency); + } catch (err) { + utils.logError(`${MODULE_NAME}: Unable do get currency conversion for bidResponse to Floor Currency. Do you have Currency module enabled? ${bid}`); + return fn.call(this, adUnitCode, bid); + } + } + + // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists + adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm); + + // add necessary data information for analytics adapters / floor providers would possibly need + addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); + + // now do the compare! + if (shouldFloorBid(floorData, floorInfo, bid)) { + // bid fails floor -> throw it out + // create basic bid no-bid with necessary data fro analytics adapters + let flooredBid = createBid(CONSTANTS.STATUS.NO_BID, matchingBidRequest); + Object.assign(flooredBid, utils.pick(bid, [ + 'floorData', + 'width', + 'height', + 'mediaType', + 'currency', + 'originalCpm', + 'originalCurrency', + 'getCpmInNewCurrency', + ])); + flooredBid.status = CONSTANTS.BID_STATUS.BID_REJECTED; + // if floor not met update bid with 0 cpm so it is not included downstream and marked as no-bid + flooredBid.cpm = 0; + utils.logWarn(`${MODULE_NAME}: ${flooredBid.bidderCode}'s Bid Response for ${adUnitCode} was rejected due to floor not met`, bid); + return fn.call(this, adUnitCode, flooredBid); + } + return fn.call(this, adUnitCode, bid); +} + +config.getConfig('floors', config => handleSetFloorsConfig(config.floors)); diff --git a/modules/priceFloors.md b/modules/priceFloors.md new file mode 100644 index 00000000000..36ac07ee972 --- /dev/null +++ b/modules/priceFloors.md @@ -0,0 +1,64 @@ +## Dynamic Price Floors + +### Setup +```javascript +pbjs.setConfig({ + floors: { + enabled: true, //defaults to true + enforcement: { + floorDeals: false, //defaults to false + bidAdjustment: true, //defaults to true + enforceJS: true //defaults to true + }, + auctionDelay: 150, // in milliseconds defaults to 0 + floorProvider: 'awesomeFloorProviderName', // name of the floor provider (optional) + endpoint: { + url: 'http://localhost:1500/floor-domains', + method: 'GET' // Only get supported for now + }, + data: { + schema: { + fields: ['mediaType', 'size'] + }, + values: { + 'banner|300x250': 0.86, + 'banner|300x600': 0.97, + 'banner|728x90': 1.12, + 'banner|*': 0.54, + 'video|640x480': 6.76, + 'video|1152x648': 11.76, + 'video|*': 4.55, + '*|*': 0.30 + }, + default: 0.01 + } + } +}); +``` + +| Parameter | Description | +|------------------------|---------------------------------------------------------------------------------------------------------------------| +| enabled | Wether to turn off or on the floors module | +| enforcement | object of booleans which control certain features of the module | +| auctionDelay | The time to suspend and auction while waiting for a real time price floors fetch to come back | +| floorProvider | A string identifying the floor provider.| +| endpoint | An object describing the endpoint to retrieve floor data from. GET only | +| data | The data to be used to select appropriate floors. See schema for more detail | +| additionalSchemaFields | An object of additional fields to be used in a floor data object. The schema is KEY: function to retrieve the match | + +### Passing floors to Bid Adapters +Because it is possible for many rules to match any given bidRequest, (wether it be due to more than one size or more than one mediaType), an encapsolated function is to be passed to bidders which will allow bidders to have insight as to what the floor could be. + +The function `getFloor` will be attached to every bidRequestObject passed to bid adapters if the price floors are enabled for a given auction. + +This function can takes in an object with the following optional parameters: + +| Parameter | Description | Example | Default | +|-----------|------------------------------------------------------------------------|------------|---------| +| currency | The 3 character currency code which the bid adapter wants the floor in | "JPY" | "USD" | +| mediaType | The specific mediaType to get a floor for | "banner" | "*" | +| size | The specific size to get a matching floor on | [300, 250] | "*" | + +If a bid adapter passes in `*` as an attribute, then the `priceFloors` module will attempt to select the best rule based on context. + +For example, if an adapter passes in a `*`, but the bidRequest only has a single size and a single mediaType, then the `getFloor` function will attempt to get a rule for that size before matching with the `*` catch-all. Similarily, if mediaType can be inferred on the bidRequest, it will use it. diff --git a/modules/projectLimeLightBidAdapter.js b/modules/projectLimeLightBidAdapter.js index 2b80538a064..1beba906917 100644 --- a/modules/projectLimeLightBidAdapter.js +++ b/modules/projectLimeLightBidAdapter.js @@ -1,10 +1,9 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; -import {ajax} from '../src/ajax'; -import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {ajax} from '../src/ajax.js'; +import * as utils from '../src/utils.js'; const BIDDER_CODE = 'project-limelight'; -const URL = 'https://ads.project-limelight.com/hb'; /** * Determines whether or not the given bid response is valid. @@ -25,19 +24,6 @@ function isBidResponseValid(bid) { return false; } -function extractBidSizes(bid) { - const bidSizes = []; - - bid.sizes.forEach(size => { - bidSizes.push({ - width: size[0], - height: size[1] - }); - }); - - return bidSizes; -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -65,30 +51,10 @@ export const spec = { } catch (e) { utils.logMessage(e); winTop = window; - }; - const placements = []; - const request = { - 'secure': (location.protocol === 'https:'), - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'adUnits': placements - }; - for (let i = 0; i < validBidRequests.length; i++) { - const bid = validBidRequests[i]; - const params = bid.params; - placements.push({ - id: params.adUnitId, - bidId: bid.bidId, - transactionId: bid.transactionId, - sizes: extractBidSizes(bid), - type: params.adUnitType.toUpperCase() - }); } - return { - method: 'POST', - url: URL, - data: request - }; + const placements = utils.groupBy(validBidRequests.map(bidRequest => buildPlacement(bidRequest)), 'host') + return Object.keys(placements) + .map(host => buildRequest(winTop, host, placements[host].map(placement => placement.adUnit))); }, onBidWon: (bid) => { @@ -123,3 +89,34 @@ export const spec = { }; registerBidder(spec); + +function buildRequest(winTop, host, adUnits) { + return { + method: 'POST', + url: `https://${host}/hb`, + data: { + secure: (location.protocol === 'https:'), + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + adUnits: adUnits + } + } +} + +function buildPlacement(bidRequest) { + return { + host: bidRequest.params.host, + adUnit: { + id: bidRequest.params.adUnitId, + bidId: bidRequest.bidId, + transactionId: bidRequest.transactionId, + sizes: bidRequest.sizes.map(size => { + return { + width: size[0], + height: size[1] + } + }), + type: bidRequest.params.adUnitType.toUpperCase() + } + } +} diff --git a/modules/projectLimeLightBidAdapter.md b/modules/projectLimeLightBidAdapter.md index 71621983b89..15aa170cd2e 100644 --- a/modules/projectLimeLightBidAdapter.md +++ b/modules/projectLimeLightBidAdapter.md @@ -18,6 +18,7 @@ Module that connects to Project Limelight SSP demand sources bids: [{ bidder: 'project-limelight', params: { + host: 'ads.project-limelight.com', adUnitId: 0, adUnitType: 'banner' } @@ -34,6 +35,7 @@ var videoAdUnit = [{ bids: [{ bidder: 'project-limelight', params: { + host: 'ads.project-limelight.com', adUnitId: 0, adUnitType: 'video' } diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index 44d4acadee2..c546337c47f 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -1,43 +1,57 @@ -const { registerBidder } = require('../src/adapters/bidderFactory'); + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'proxistore'; +const storage = getStorageManager(); +const PROXISTORE_VENDOR_ID = 418; -function _getFormatSize(sizeArr) { - return { - width: sizeArr[0], - height: sizeArr[1] - } -} +function _createServerRequest(bidRequests, bidderRequest) { + const sizeIds = []; -function _createServerRequest(bidRequest, bidderRequest) { + bidRequests.forEach(function (bid) { + const sizeId = { + id: bid.bidId, + sizes: bid.sizes.map(function (size) { + return { + width: size[0], + height: size[1] + }; + }) + }; + sizeIds.push(sizeId); + }); const payload = { - bidId: bidRequest.bidId, - auctionId: bidRequest.auctionId, - transactionId: bidRequest.transactionId, - sizes: bidRequest.sizes.map(_getFormatSize), - website: bidRequest.params.website, - language: bidRequest.params.language, + auctionId: bidRequests[0].auctionId, + transactionId: bidRequests[0].auctionId, + bids: sizeIds, + website: bidRequests[0].params.website, + language: bidRequests[0].params.language, gdpr: { applies: false } }; - const options = { contentType: 'application/json', withCredentials: true }; if (bidderRequest && bidderRequest.gdprConsent) { - if ((typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') && bidderRequest.gdprConsent.gdprApplies) { + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) { payload.gdpr.applies = true; } - if ((typeof bidderRequest.gdprConsent.consentString === 'string') && bidderRequest.gdprConsent.consentString) { - payload.gdpr['consentString'] = bidderRequest.gdprConsent.consentString; + + if (typeof bidderRequest.gdprConsent.consentString === 'string' && bidderRequest.gdprConsent.consentString) { + payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; + } + + if (bidderRequest.gdprConsent.vendorData && bidderRequest.gdprConsent.vendorData.vendorConsents && typeof bidderRequest.gdprConsent.vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { + payload.gdpr.consentGiven = !!bidderRequest.gdprConsent.vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; } } return { method: 'POST', - url: bidRequest.params.url || '//abs.proxistore.com/' + bidRequest.params.language + '/v3/rtb/prebid', + url: bidRequests[0].params.url || 'https://abs.proxistore.com/' + payload.language + '/v3/rtb/prebid/multi', data: JSON.stringify(payload), options: options }; @@ -57,17 +71,31 @@ function _createBidResponse(response) { vastUrl: response.vastUrl, vastXml: response.vastXml, dealId: response.dealId - } + }; } - /** * Determines whether or not the given bid request is valid. * * @param bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ + function isBidRequestValid(bid) { - return !!(bid.params.website && bid.params.language); + const hasNoAd = function() { + if (!storage.hasLocalStorage()) { + return false; + } + const pxNoAds = storage.getDataFromLocalStorage(`PX_NoAds_${bid.params.website}`); + if (!pxNoAds) { + return false; + } else { + const storedDate = new Date(pxNoAds); + const now = new Date(); + const diff = Math.abs(storedDate.getTime() - now.getTime()) / 60000; + return diff <= 5; + } + } + return !!(bid.params.website && bid.params.language) && !hasNoAd(); } /** @@ -77,15 +105,11 @@ function isBidRequestValid(bid) { * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ + function buildRequests(bidRequests, bidderRequest) { - var requests = []; - for (var i = 0; i < bidRequests.length; i++) { - var prebidReq = _createServerRequest(bidRequests[i], bidderRequest); - requests.push(prebidReq); - } - return requests; + const request = _createServerRequest(bidRequests, bidderRequest); + return request; } - /** * Unpack the response from the server into a list of bids. * @@ -93,14 +117,26 @@ function buildRequests(bidRequests, bidderRequest) { * @param bidRequest Request original server request * @return An array of bids which were nested inside the server. */ + function interpretResponse(serverResponse, bidRequest) { + const itemName = `PX_NoAds_${websiteFromBidRequest(bidRequest)}`; if (serverResponse.body.length > 0) { + storage.removeDataFromLocalStorage(itemName, true); return serverResponse.body.map(_createBidResponse); } else { + storage.setDataInLocalStorage(itemName, new Date()); return []; } } +const websiteFromBidRequest = function(bidR) { + if (bidR.data) { + return JSON.parse(bidR.data).website + } else if (bidR.params.website) { + return bidR.params.website; + } +} + /** * Register the user sync pixels which should be dropped after the auction. * @@ -108,11 +144,12 @@ function interpretResponse(serverResponse, bidRequest) { * @param serverResponses List of server's responses. * @return The user syncs which should be dropped. */ + function getUserSyncs(syncOptions, serverResponses) { return []; } -const spec = { +export const spec = { code: BIDDER_CODE, isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, @@ -121,5 +158,3 @@ const spec = { }; registerBidder(spec); - -module.exports = spec; diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 038ad3bef2f..174fa6ffe6e 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -3,11 +3,13 @@ * 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'; -import events from '../src/events'; -import * as url from '../src/url'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); const ID_NAME = '_pubcid'; const OPTOUT_NAME = '_pubcid_optout'; @@ -37,10 +39,10 @@ export function setStorageItem(key, val, expires) { try { if (expires !== undefined && expires != null) { const expStr = (new Date(Date.now() + (expires * 60 * 1000))).toUTCString(); - localStorage.setItem(key + EXP_SUFFIX, expStr); + storage.setDataInLocalStorage(key + EXP_SUFFIX, expStr); } - localStorage.setItem(key, val); + storage.setDataInLocalStorage(key, val); } catch (e) { utils.logMessage(e); } @@ -55,18 +57,18 @@ export function getStorageItem(key) { let val = null; try { - const expVal = localStorage.getItem(key + EXP_SUFFIX); + const expVal = storage.getDataFromLocalStorage(key + EXP_SUFFIX); if (!expVal) { // If there is no expiry time, then just return the item - val = localStorage.getItem(key); + val = storage.getDataFromLocalStorage(key); } else { // Only return the item if it hasn't expired yet. // Otherwise delete the item. const expDate = new Date(expVal); const isValid = (expDate.getTime() - Date.now()) > 0; if (isValid) { - val = localStorage.getItem(key); + val = storage.getDataFromLocalStorage(key); } else { removeStorageItem(key); } @@ -84,8 +86,8 @@ export function getStorageItem(key) { */ export function removeStorageItem(key) { try { - localStorage.removeItem(key + EXP_SUFFIX); - localStorage.removeItem(key); + storage.removeDataFromLocalStorage(key + EXP_SUFFIX); + storage.removeDataFromLocalStorage(key); } catch (e) { utils.logMessage(e); } @@ -101,7 +103,7 @@ function readValue(name, type) { let value; if (!type) { type = pubcidConfig.typeEnabled; } if (type === COOKIE) { - value = getCookie(name); + value = storage.getCookie(name); } else if (type === LOCAL_STORAGE) { value = getStorageItem(name); } @@ -139,9 +141,9 @@ function queuePixelCallback(pixelUrl, id) { id = id || ''; // Use pubcid as a cache buster - const urlInfo = url.parse(pixelUrl); + const urlInfo = utils.parseUrl(pixelUrl); urlInfo.search.id = encodeURIComponent('pubcid:' + id); - const targetUrl = url.format(urlInfo); + const targetUrl = utils.buildUrl(urlInfo); events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler); @@ -223,18 +225,12 @@ export function requestBidHook(next, config) { export function setCookie(name, value, expires, sameSite) { let expTime = new Date(); expTime.setTime(expTime.getTime() + expires * 1000 * 60); - window.document.cookie = name + '=' + encodeURIComponent(value) + ';path=/;expires=' + - expTime.toGMTString() + - (sameSite ? ';SameSite=' + sameSite : ''); + storage.setCookie(name, value, expTime.toGMTString(), sameSite); } // Helper to read a cookie export function getCookie(name) { - if (name && window.document.cookie) { - let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)'); - return m ? decodeURIComponent(m[2]) : null; - } - return null; + return storage.getCookie(name); } /** @@ -269,12 +265,12 @@ export function setConfig({ enable, expInterval, type = 'html5,cookie', create, for (let i = 0; i < typeArray.length; ++i) { const name = typeArray[i].trim(); if (name === COOKIE) { - if (utils.cookiesAreEnabled()) { + if (storage.cookiesAreEnabled()) { pubcidConfig.typeEnabled = COOKIE; break; } } else if (name === LOCAL_STORAGE) { - if (utils.hasLocalStorage()) { + if (storage.hasLocalStorage()) { pubcidConfig.typeEnabled = LOCAL_STORAGE; break; } @@ -288,8 +284,8 @@ export function setConfig({ enable, expInterval, type = 'html5,cookie', create, export function initPubcid() { config.getConfig('pubcid', config => setConfig(config.pubcid)); - const optout = (utils.cookiesAreEnabled() && readValue(OPTOUT_NAME, COOKIE)) || - (utils.hasLocalStorage() && readValue(OPTOUT_NAME, LOCAL_STORAGE)); + const optout = (storage.cookiesAreEnabled() && readValue(OPTOUT_NAME, COOKIE)) || + (storage.hasLocalStorage() && readValue(OPTOUT_NAME, LOCAL_STORAGE)); if (!optout) { $$PREBID_GLOBAL$$.requestBids.before(requestBidHook); diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index 42a52c2b010..9516934de42 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -5,14 +5,16 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils'; -import * as url from '../src/url'; -import {submodule} from '../src/hook'; +import * as utils from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; const PUB_COMMON_ID = 'PublisherCommonId'; const MODULE_NAME = 'pubCommonId'; +const storage = getStorageManager(null, 'pubCommonId'); + /** @type {Submodule} */ export const pubCommonIdSubmodule = { /** @@ -32,9 +34,9 @@ export const pubCommonIdSubmodule = { } // Use pubcid as a cache buster - const urlInfo = url.parse(pixelUrl); + const urlInfo = utils.parseUrl(pixelUrl); urlInfo.search.id = encodeURIComponent('pubcid:' + id); - const targetUrl = url.format(urlInfo); + const targetUrl = utils.buildUrl(urlInfo); return function () { utils.triggerPixel(targetUrl); @@ -64,7 +66,7 @@ export const pubCommonIdSubmodule = { } catch (e) { } - const newId = (create) ? utils.generateUUID() : undefined; + const newId = (create && utils.hasDeviceAccess()) ? utils.generateUUID() : undefined; return { id: newId, callback: this.makeCallback(pixelUrl, newId) @@ -91,6 +93,33 @@ export const pubCommonIdSubmodule = { const callback = this.makeCallback(pixelUrl, storedId); return callback ? {callback: callback} : {id: storedId}; } + }, + + /** + * @param {string} domain + * @param {HTMLDocument} document + * @return {(string|undefined)} + */ + domainOverride: function () { + const domainElements = document.domain.split('.'); + const cookieName = `_gd${Date.now()}`; + for (let i = 0, topDomain; i < domainElements.length; i++) { + const nextDomain = domainElements.slice(i).join('.'); + + // write test cookie + storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); + + // read test cookie to verify domain was valid + if (storage.getCookie(cookieName) === '1') { + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + // cookie was written successfully using test domain so the topDomain is updated + topDomain = nextDomain; + } else { + // cookie failed to write using test domain so exit by returning the topDomain + return topDomain; + } + } } }; diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js new file mode 100644 index 00000000000..05f18f99a9a --- /dev/null +++ b/modules/pubgeniusBidAdapter.js @@ -0,0 +1,224 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { + deepAccess, + deepSetValue, + inIframe, + isArrayOfNums, + isInteger, + isNumber, + isStr, + logError, + parseQueryStringParameters, +} from '../src/utils.js'; + +const BIDDER_VERSION = '1.0.0'; +const BASE_URL = 'https://ortb.adpearl.io'; + +export const spec = { + code: 'pubgenius', + + supportedMediaTypes: [ BANNER ], + + isBidRequestValid(bid) { + const adUnitId = bid.params.adUnitId; + if (!isStr(adUnitId) && !isInteger(adUnitId)) { + logError('pubgenius bidder params: adUnitId must be a string or integer.'); + return false; + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes'); + return !!(sizes && sizes.length) && sizes.every(size => isArrayOfNums(size, 2)); + }, + + buildRequests: function (bidRequests, bidderRequest) { + const data = { + id: bidderRequest.auctionId, + imp: bidRequests.map(buildImp), + tmax: config.getConfig('bidderTimeout'), + ext: { + pbadapter: { + version: BIDDER_VERSION, + }, + }, + }; + + const site = buildSite(bidderRequest); + if (site) { + data.site = site; + } + + const gdpr = bidderRequest.gdprConsent; + if (gdpr) { + const applies = gdpr.gdprApplies; + const consent = gdpr.consentString; + deepSetValue(data, 'regs.ext.gdpr', numericBoolean(applies)); + if (applies && consent) { + deepSetValue(data, 'user.ext.consent', consent); + } + } + + const usp = bidderRequest.uspConsent; + if (usp) { + deepSetValue(data, 'regs.ext.us_privacy', usp); + } + + const schain = bidRequests[0].schain; + if (schain) { + deepSetValue(data, 'source.ext.schain', schain); + } + + if (config.getConfig('coppa')) { + deepSetValue(data, 'regs.coppa', 1); + } + + const bidUserIdAsEids = deepAccess(bidRequests, '0.userIdAsEids'); + if (bidUserIdAsEids && bidUserIdAsEids.length) { + const eids = bidUserIdAsEids.filter(eid => eid.source === 'adserver.org'); + if (eids.length) { + deepSetValue(data, 'user.ext.eids', eids); + } + } + + return { + method: 'POST', + url: `${getBaseUrl()}/prebid/auction`, + data, + }; + }, + + interpretResponse({ body }) { + const bidResponses = []; + const currency = body.cur || 'USD'; + const seatbids = body.seatbid; + + if (seatbids) { + seatbids.forEach(seatbid => { + seatbid.bid.forEach(bid => { + const bidResponse = interpretBid(bid); + bidResponse.currency = currency; + bidResponses.push(bidResponse); + }); + }); + } + + return bidResponses; + }, + + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = [] + + if (syncOptions.iframeEnabled) { + let params = {}; + + if (gdprConsent) { + params.gdpr = numericBoolean(gdprConsent.gdprApplies); + if (gdprConsent.consentString) { + params.consent = gdprConsent.consentString; + } + } + + if (uspConsent) { + params.us_privacy = uspConsent; + } + + const qs = parseQueryStringParameters(params); + syncs.push({ + type: 'iframe', + url: `${getBaseUrl()}/usersync/pixels.html?${qs}`, + }); + } + + return syncs; + }, + + onTimeout(data) { + ajax(`${getBaseUrl()}/prebid/events?type=timeout`, null, JSON.stringify(data), { + method: 'POST', + }); + }, +}; + +function buildImp(bid) { + const imp = { + id: bid.bidId, + banner: { + format: deepAccess(bid, 'mediaTypes.banner.sizes').map(size => ({ w: size[0], h: size[1] })), + topframe: numericBoolean(!inIframe()), + }, + tagid: String(bid.params.adUnitId), + }; + + const bidFloor = bid.params.bidFloor; + if (isNumber(bidFloor)) { + imp.bidfloor = bidFloor; + } + + const pos = bid.params.position; + if (isInteger(pos)) { + imp.banner.pos = pos; + } + + if (bid.params.test) { + deepSetValue(imp, 'ext.test', 1); + } + + return imp; +} + +function buildSite(bidderRequest) { + let site = null; + const { refererInfo } = bidderRequest; + + const pageUrl = config.getConfig('pageUrl') || refererInfo.canonicalUrl || refererInfo.referer; + if (pageUrl) { + site = site || {}; + site.page = pageUrl; + } + + if (refererInfo.reachedTop) { + try { + const pageRef = window.top.document.referrer; + if (pageRef) { + site = site || {}; + site.ref = pageRef; + } + } catch (e) {} + } + + return site; +} + +function interpretBid(bid) { + const bidResponse = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: bid.exp, + creativeId: bid.crid, + netRevenue: true, + }; + + if (bid.adomain && bid.adomain.length) { + bidResponse.meta = { + advertiserDomains: bid.adomain, + }; + } + + return bidResponse; +} + +function numericBoolean(value) { + return value ? 1 : 0; +} + +function getBaseUrl() { + const pubg = config.getConfig('pubgenius'); + return (pubg && pubg.endpoint) || BASE_URL; +} + +registerBidder(spec); diff --git a/modules/pubgeniusBidAdapter.md b/modules/pubgeniusBidAdapter.md new file mode 100644 index 00000000000..66851af9c3f --- /dev/null +++ b/modules/pubgeniusBidAdapter.md @@ -0,0 +1,65 @@ +# Overview + +``` +Module Name: pubGENIUS Bidder Adapter +Module Type: Bidder Adapter +Maintainer: meng@pubgenius.io +``` + +# Description + +Module that connects to pubGENIUS's demand sources + +# Test Parameters + +Test bids have $0.01 CPM by default. Use `bidFloor` in bidder params to control CPM for testing purposes. + +``` +var adUnits = [ + { + code: 'test-desktop-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'pubgenius', + params: { + adUnitId: '1000', + test: true + } + } + ] + }, + { + code: 'test-mobile-banner', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + bids: [ + { + bidder: 'pubgenius', + params: { + adUnitId: '1000', + bidFloor: 0.5, + test: true + } + } + ] + } +]; +``` + +# Optional Config + +By default, the adapter uses the page URL as provided in referer info by Prebid.js. +The following config overrides this behavior and specifies the URL to be used: +``` +pbjs.setConfig({ + pageUrl: 'https://example.com/top-page-url/' +}); +``` diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js new file mode 100755 index 00000000000..d94c098db5d --- /dev/null +++ b/modules/pubmaticAnalyticsAdapter.js @@ -0,0 +1,455 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +/// /////////// CONSTANTS ////////////// +const ADAPTER_CODE = 'pubmatic'; +const SEND_TIMEOUT = 2000; +const END_POINT_HOST = 'https://t.pubmatic.com/'; +const END_POINT_BID_LOGGER = END_POINT_HOST + 'wl?'; +const END_POINT_WIN_BID_LOGGER = END_POINT_HOST + 'wt?'; +const LOG_PRE_FIX = 'PubMatic-Analytics: '; +const cache = { + auctions: {} +}; +const SUCCESS = 'success'; +const NO_BID = 'no-bid'; +const ERROR = 'error'; +const REQUEST_ERROR = 'request-error'; +const TIMEOUT_ERROR = 'timeout-error'; +const EMPTY_STRING = ''; +const MEDIA_TYPE_BANNER = 'banner'; +const CURRENCY_USD = 'USD'; +const BID_PRECISION = 2; +// todo: input profileId and profileVersionId ; defaults to zero or one +const DEFAULT_PUBLISHER_ID = 0; +const DEFAULT_PROFILE_ID = 0; +const DEFAULT_PROFILE_VERSION_ID = 0; +const enc = window.encodeURIComponent; + +/// /////////// VARIABLES ////////////// +let publisherId = DEFAULT_PUBLISHER_ID; // int: mandatory +let profileId = DEFAULT_PROFILE_ID; // int: optional +let profileVersionId = DEFAULT_PROFILE_VERSION_ID; // int: optional +let s2sBidders = []; + +/// /////////// HELPER FUNCTIONS ////////////// + +function sizeToDimensions(size) { + return { + width: size.w || size[0], + height: size.h || size[1] + }; +} + +function validMediaType(type) { + return ({'banner': 1, 'native': 1, 'video': 1}).hasOwnProperty(type); +} + +function formatSource(src) { + if (typeof src === 'undefined') { + src = 'client'; + } else if (src === 's2s') { + src = 'server'; + } + return src.toLowerCase(); +} + +function setMediaTypes(types, bid) { + 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 [MEDIA_TYPE_BANNER]; +} + +function copyRequiredBidDetails(bid) { + return utils.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', + 'adUnit', () => utils.pick(bid, [ + 'adUnitCode', + 'transactionId', + 'sizes as dimensions', sizes => sizes.map(sizeToDimensions), + 'mediaTypes', (types) => setMediaTypes(types, bid) + ]) + ]); +} + +function setBidStatus(bid, args) { + switch (args.getStatusCode()) { + case CONSTANTS.STATUS.GOOD: + bid.status = SUCCESS; + delete bid.error; // it's possible for this to be set by a previous timeout + break; + case CONSTANTS.STATUS.NO_BID: + bid.status = NO_BID; + delete bid.error; + break; + default: + bid.status = ERROR; + bid.error = { + code: REQUEST_ERROR + }; + } +} + +function parseBidResponse(bid) { + return utils.pick(bid, [ + 'bidPriceUSD', () => { + // todo: check whether currency cases are handled here + if (typeof bid.currency === 'string' && bid.currency.toUpperCase() === CURRENCY_USD) { + return window.parseFloat(Number(bid.cpm).toFixed(BID_PRECISION)); + } + // use currency conversion function if present + if (typeof bid.getCpmInNewCurrency === 'function') { + return window.parseFloat(Number(bid.getCpmInNewCurrency(CURRENCY_USD)).toFixed(BID_PRECISION)); + } + utils.logWarn(LOG_PRE_FIX + 'Could not determine the Net cpm in USD for the bid thus using bid.cpm', bid); + return bid.cpm + }, + 'bidGrossCpmUSD', () => { + if (typeof bid.originalCurrency === 'string' && bid.originalCurrency.toUpperCase() === CURRENCY_USD) { + return window.parseFloat(Number(bid.originalCpm).toFixed(BID_PRECISION)); + } + // use currency conversion function if present + if (typeof getGlobal().convertCurrency === 'function') { + return window.parseFloat(Number(getGlobal().convertCurrency(bid.originalCpm, bid.originalCurrency, CURRENCY_USD)).toFixed(BID_PRECISION)); + } + utils.logWarn(LOG_PRE_FIX + 'Could not determine the Gross cpm in USD for the bid, thus using bid.originalCpm', bid); + return bid.originalCpm + }, + 'dealId', + 'currency', + 'cpm', () => window.parseFloat(Number(bid.cpm).toFixed(BID_PRECISION)), + 'originalCpm', () => window.parseFloat(Number(bid.originalCpm).toFixed(BID_PRECISION)), + 'originalCurrency', + 'dealChannel', + 'meta', + 'status', + 'error', + 'bidId', + 'mediaType', + 'params', + 'mi', + 'partnerImpId', // partner impression ID + 'dimensions', () => utils.pick(bid, [ + 'width', + 'height' + ]) + ]); +} + +function getDomainFromUrl(url) { + let a = window.document.createElement('a'); + a.href = url; + return a.hostname; +} + +function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { + highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; + return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { + let bid = adUnit.bids[bidId]; + partnerBids.push({ + 'pn': bid.bidder, + 'bidid': bid.bidId, + 'db': bid.bidResponse ? 0 : 1, + 'kgpv': bid.params.kgpv ? bid.params.kgpv : adUnitId, + 'kgpsv': bid.params.kgpv ? bid.params.kgpv : adUnitId, + 'psz': bid.bidResponse ? (bid.bidResponse.dimensions.width + 'x' + bid.bidResponse.dimensions.height) : '0x0', + 'eg': bid.bidResponse ? bid.bidResponse.bidGrossCpmUSD : 0, + 'en': bid.bidResponse ? bid.bidResponse.bidPriceUSD : 0, + 'di': bid.bidResponse ? (bid.bidResponse.dealId || EMPTY_STRING) : EMPTY_STRING, + 'dc': bid.bidResponse ? (bid.bidResponse.dealChannel || EMPTY_STRING) : EMPTY_STRING, + 'l1': bid.bidResponse ? bid.clientLatencyTimeMs : 0, + 'l2': 0, + 'ss': (s2sBidders.indexOf(bid.bidder) > -1) ? 1 : 0, + 't': (bid.status == ERROR && bid.error.code == TIMEOUT_ERROR) ? 1 : 0, + 'wb': (highestBid && highestBid.requestId === bid.bidId ? 1 : 0), + 'mi': bid.bidResponse ? (bid.bidResponse.mi || undefined) : undefined, + 'af': bid.bidResponse ? (bid.bidResponse.mediaType || undefined) : undefined, + 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, + 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, + 'piid': bid.bidResponse ? (bid.bidResponse.partnerImpId || EMPTY_STRING) : EMPTY_STRING + }); + return partnerBids; + }, []) +} + +function executeBidsLoggerCall(e, highestCpmBids) { + let auctionId = e.auctionId; + let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''; + let auctionCache = cache.auctions[auctionId]; + let outputObj = { s: [] }; + let pixelURL = END_POINT_BID_LOGGER; + + if (!auctionCache) { + return; + } + + if (auctionCache.sent) { + return; + } + + pixelURL += 'pubid=' + publisherId; + outputObj['pubid'] = '' + publisherId; + outputObj['iid'] = '' + auctionId; + outputObj['to'] = '' + auctionCache.timeout; + outputObj['purl'] = referrer; + outputObj['orig'] = getDomainFromUrl(referrer); + outputObj['tst'] = Math.round((new window.Date()).getTime() / 1000); + outputObj['pid'] = '' + profileId; + outputObj['pdvid'] = '' + profileVersionId; + outputObj['tgid'] = (function() { + var testGroupId = parseInt(config.getConfig('testGroupId') || 0); + if (testGroupId <= 15 && testGroupId >= 0) { + return testGroupId; + } + return 0; + })(); + + // GDPR support + if (auctionCache.gdprConsent) { + outputObj['cns'] = auctionCache.gdprConsent.consentString || ''; + outputObj['gdpr'] = auctionCache.gdprConsent.gdprApplies === true ? 1 : 0; + pixelURL += '&gdEn=1'; + } + + outputObj.s = Object.keys(auctionCache.adUnitCodes).reduce(function(slotsArray, adUnitId) { + let adUnit = auctionCache.adUnitCodes[adUnitId]; + let slotObject = { + 'sn': adUnitId, + 'sz': adUnit.dimensions.map(e => e[0] + 'x' + e[1]), + 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)) + }; + slotsArray.push(slotObject); + return slotsArray; + }, []); + + auctionCache.sent = true; + + ajax( + pixelURL, + null, + 'json=' + enc(JSON.stringify(outputObj)), + { + contentType: 'application/x-www-form-urlencoded', + withCredentials: true, + method: 'POST' + } + ); +} + +function executeBidWonLoggerCall(auctionId, adUnitId) { + const winningBidId = cache.auctions[auctionId].adUnitCodes[adUnitId].bidWon; + const winningBid = cache.auctions[auctionId].adUnitCodes[adUnitId].bids[winningBidId]; + let pixelURL = END_POINT_WIN_BID_LOGGER; + pixelURL += 'pubid=' + publisherId; + pixelURL += '&purl=' + enc(config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''); + pixelURL += '&tst=' + Math.round((new window.Date()).getTime() / 1000); + pixelURL += '&iid=' + enc(auctionId); + pixelURL += '&bidid=' + enc(winningBidId); + pixelURL += '&pid=' + enc(profileId); + pixelURL += '&pdvid=' + enc(profileVersionId); + pixelURL += '&slot=' + enc(adUnitId); + pixelURL += '&pn=' + enc(winningBid.bidder); + pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); + pixelURL += '&eg=' + enc(winningBid.bidResponse.bidGrossCpmUSD); + pixelURL += '&kgpv=' + enc(winningBid.params.kgpv || adUnitId); + pixelURL += '&piid=' + enc(winningBid.bidResponse.partnerImpId || EMPTY_STRING); + ajax( + pixelURL, + null, + null, + { + contentType: 'application/x-www-form-urlencoded', + withCredentials: true, + method: 'GET' + } + ); +} + +/// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// + +function auctionInitHandler(args) { + s2sBidders = (function() { + let s2sConf = config.getConfig('s2sConfig'); + return (s2sConf && utils.isArray(s2sConf.bidders)) ? s2sConf.bidders : []; + }()); + let cacheEntry = utils.pick(args, [ + 'timestamp', + 'timeout', + 'bidderDonePendingCount', () => args.bidderRequests.length, + ]); + cacheEntry.adUnitCodes = {}; + cacheEntry.referer = args.bidderRequests[0].refererInfo.referer; + cache.auctions[args.auctionId] = cacheEntry; +} + +function bidRequestedHandler(args) { + cache.auctions[args.auctionId].gdprConsent = args.gdprConsent || undefined; + args.bids.forEach(function(bid) { + if (!cache.auctions[args.auctionId].adUnitCodes.hasOwnProperty(bid.adUnitCode)) { + cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode] = { + bids: {}, + bidWon: false, + dimensions: bid.sizes + }; + } + cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId] = copyRequiredBidDetails(bid); + }) +} + +function bidResponseHandler(args) { + let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId]; + if (!bid) { + utils.logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); + return; + } + bid.source = formatSource(bid.source || args.source); + setBidStatus(bid, args); + bid.clientLatencyTimeMs = Date.now() - cache.auctions[args.auctionId].timestamp; + bid.bidResponse = parseBidResponse(args); +} + +function bidderDoneHandler(args) { + cache.auctions[args.auctionId].bidderDonePendingCount--; + args.bids.forEach(bid => { + let cachedBid = cache.auctions[bid.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId || bid.requestId]; + if (typeof bid.serverResponseTimeMs !== 'undefined') { + cachedBid.serverLatencyTimeMs = bid.serverResponseTimeMs; + } + if (!cachedBid.status) { + cachedBid.status = NO_BID; + } + if (!cachedBid.clientLatencyTimeMs) { + cachedBid.clientLatencyTimeMs = Date.now() - cache.auctions[bid.auctionId].timestamp; + } + }); +} + +function bidWonHandler(args) { + let auctionCache = cache.auctions[args.auctionId]; + auctionCache.adUnitCodes[args.adUnitCode].bidWon = args.requestId; + executeBidWonLoggerCall(args.auctionId, args.adUnitCode); +} + +function auctionEndHandler(args) { + // if for the given auction bidderDonePendingCount == 0 then execute logger call sooners + let highestCpmBids = getGlobal().getHighestCpmBids() || []; + setTimeout(() => { + executeBidsLoggerCall.call(this, args, highestCpmBids); + }, (cache.auctions[args.auctionId].bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT)); +} + +function bidTimeoutHandler(args) { + // db = 1 and t = 1 means bidder did NOT respond with a bid but we got a timeout notification + // db = 0 and t = 1 means bidder did respond with a bid but post timeout + args.forEach(badBid => { + let auctionCache = cache.auctions[badBid.auctionId]; + let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.requestId ]; + if (bid) { + bid.status = ERROR; + bid.error = { + code: TIMEOUT_ERROR + }; + } else { + utils.logWarn(LOG_PRE_FIX + 'bid not found'); + } + }); +} + +/// /////////// ADAPTER DEFINITION ////////////// + +let baseAdapter = adapter({analyticsType: 'endpoint'}); +let pubmaticAdapter = Object.assign({}, baseAdapter, { + + enableAnalytics(conf = {}) { + let error = false; + + if (typeof conf.options === 'object') { + if (conf.options.publisherId) { + publisherId = Number(conf.options.publisherId); + } + profileId = Number(conf.options.profileId) || DEFAULT_PROFILE_ID; + profileVersionId = Number(conf.options.profileVersionId) || DEFAULT_PROFILE_VERSION_ID; + } else { + utils.logError(LOG_PRE_FIX + 'Config not found.'); + error = true; + } + + if (!publisherId) { + utils.logError(LOG_PRE_FIX + 'Missing publisherId(Number).'); + error = true; + } + + if (error) { + utils.logError(LOG_PRE_FIX + 'Not collecting data due to error(s).'); + } else { + baseAdapter.enableAnalytics.call(this, conf); + } + }, + + disableAnalytics() { + publisherId = DEFAULT_PUBLISHER_ID; + profileId = DEFAULT_PROFILE_ID; + profileVersionId = DEFAULT_PROFILE_VERSION_ID; + s2sBidders = []; + baseAdapter.disableAnalytics.apply(this, arguments); + }, + + track({eventType, args}) { + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + auctionInitHandler(args); + break; + case CONSTANTS.EVENTS.BID_REQUESTED: + bidRequestedHandler(args); + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + bidResponseHandler(args); + break; + case CONSTANTS.EVENTS.BIDDER_DONE: + bidderDoneHandler(args); + break; + case CONSTANTS.EVENTS.BID_WON: + bidWonHandler(args); + break; + case CONSTANTS.EVENTS.AUCTION_END: + auctionEndHandler(args); + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + bidTimeoutHandler(args); + break; + } + } +}); + +/// /////////// ADAPTER REGISTRATION ////////////// + +adapterManager.registerAnalyticsAdapter({ + adapter: pubmaticAdapter, + code: ADAPTER_CODE +}); + +export default pubmaticAdapter; diff --git a/modules/pubmaticAnalyticsAdapter.md b/modules/pubmaticAnalyticsAdapter.md new file mode 100644 index 00000000000..097f0b3d792 --- /dev/null +++ b/modules/pubmaticAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# PubMatic Analytics Adapter + +``` +Module Name: PubMatic Analytics Adapter +Module Type: Analytics Adapter +Maintainer: header-bidding@pubmatic.com +``` + +## How to configure? +``` +pbjs.enableAnalytics({ + provider: 'pubmatic', + options: { + "publisherId": 12345 // please contact PubMatic to get a publisherId for yourself + } +}); +``` + +## Limitations: +- Supports only Banner and Video media-type +- Does not supports Native media type +- Does not supports instream-video creative-render tracker +- If a currency module is NOT included and a bidder responds in a non-USD currency then PubMatic analytics bidder will log values in original bid currency otherwise always logged in USD \ No newline at end of file diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 96670692267..d21854a57c4 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,19 +1,22 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes'; -import {config} from '../src/config'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; const ENDPOINT = 'https://hbopenbid.pubmatic.com/translator?source=prebid-client'; -const USYNCURL = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; +const USER_SYNC_URL_IFRAME = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; +const USER_SYNC_URL_IMAGE = 'https://image8.pubmatic.com/AdServer/ImgSync?p='; const DEFAULT_CURRENCY = 'USD'; const AUCTION_TYPE = 1; -const PUBMATIC_DIGITRUST_KEY = 'nFIn8aLzbd'; const UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html'; +const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) +const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender @@ -104,6 +107,60 @@ const dealChannelValues = { 5: 'PREF', 6: 'PMPG' }; +// BB stands for Blue BillyWig +const BB_RENDERER = { + bootstrapPlayer: function(bid) { + const config = { + code: bid.adUnitCode, + }; + + if (bid.vastXml) config.vastXml = bid.vastXml; + else if (bid.vastUrl) config.vastUrl = bid.vastUrl; + + if (!bid.vastXml && !bid.vastUrl) { + utils.logWarn(`${LOG_WARN_PREFIX}: No vastXml or vastUrl on bid, bailing...`); + return; + } + + const rendererId = BB_RENDERER.getRendererId(PUBLICATION, bid.rendererCode); + + const ele = document.getElementById(bid.adUnitCode); // NB convention + + let renderer; + + for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) { + if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) { + renderer = window.bluebillywig.renderers[rendererIndex]; + break; + } + } + + if (renderer) renderer.bootstrap(config, ele); + else utils.logWarn(`${LOG_WARN_PREFIX}: Couldn't find a renderer with ${rendererId}`); + }, + newRenderer: function(rendererCode, adUnitCode) { + var rendererUrl = RENDERER_URL.replace('$RENDERER', rendererCode); + const renderer = Renderer.install({ + url: rendererUrl, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(BB_RENDERER.outstreamRender); + } catch (err) { + utils.logWarn(`${LOG_WARN_PREFIX}: Error tying to setRender on renderer`, err); + } + + return renderer; + }, + outstreamRender: function(bid) { + bid.renderer.push(function() { BB_RENDERER.bootstrapPlayer(bid) }); + }, + getRendererId: function(pub, renderer) { + return `${pub}-${renderer}`; // NB convention! + } +}; let publisherId = 0; let isInvalidNativeRequest = false; @@ -496,6 +553,26 @@ function _createVideoRequest(bid) { return videoObj; } +// support for PMP deals +function _addPMPDealsInImpression(impObj, bid) { + if (bid.params.deals) { + if (utils.isArray(bid.params.deals)) { + bid.params.deals.forEach(function(dealId) { + if (utils.isStr(dealId) && dealId.length > 3) { + if (!impObj.pmp) { + impObj.pmp = { private_auction: 0, deals: [] }; + } + impObj.pmp.deals.push({ id: dealId }); + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: deal-id present in array bid.params.deals should be a strings with more than 3 charaters length, deal-id ignored: ' + dealId); + } + }); + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: bid.params.deals should be an array of strings.'); + } + } +} + function _createImpressionObject(bid, conf) { var impObj = {}; var bannerObj; @@ -516,6 +593,8 @@ function _createImpressionObject(bid, conf) { bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY }; + _addPMPDealsInImpression(impObj, bid); + if (bid.hasOwnProperty('mediaTypes')) { for (mediaTypes in bid.mediaTypes) { switch (mediaTypes) { @@ -563,95 +642,41 @@ function _createImpressionObject(bid, conf) { impObj.banner = bannerObj; } + _addFloorFromFloorModule(impObj, bid); + return impObj.hasOwnProperty(BANNER) || impObj.hasOwnProperty(NATIVE) || impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; } -function _getDigiTrustObject(key) { - function getDigiTrustId() { - let digiTrustUser = window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: key})); - 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 digiTrustId; -} - -function _handleDigitrustId(eids) { - let digiTrustId = _getDigiTrustObject(PUBMATIC_DIGITRUST_KEY); - if (digiTrustId !== null) { - eids.push({ - 'source': 'digitru.st', - 'uids': [{ - 'id': digiTrustId.id || '', - 'atype': 1, - 'ext': { - 'keyv': parseInt(digiTrustId.keyv) || 0 +function _addFloorFromFloorModule(impObj, bid) { + let bidFloor = -1; + // get lowest floor from floorModule + if (typeof bid.getFloor === 'function' && !config.getConfig('pubmatic.disableFloors')) { + [BANNER, VIDEO, NATIVE].forEach(mediaType => { + if (impObj.hasOwnProperty(mediaType)) { + let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + let mediaTypeFloor = parseFloat(floorInfo.floor); + bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)) } - }] + } }); } -} - -function _handleTTDId(eids, validBidRequests) { - let ttdId = null; - let adsrvrOrgId = config.getConfig('adsrvrOrgId'); - if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.tdid'))) { - ttdId = validBidRequests[0].userId.tdid; - } else if (adsrvrOrgId && utils.isStr(adsrvrOrgId.TDID)) { - ttdId = adsrvrOrgId.TDID; - } - - if (ttdId !== null) { - eids.push({ - 'source': 'adserver.org', - 'uids': [{ - 'id': ttdId, - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }); + // get highest from impObj.bidfllor and floor from floor module + // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1 + if (impObj.bidfloor) { + bidFloor = Math.max(bidFloor, impObj.bidfloor) } -} -/** - * Produces external userid object in ortb 3.0 model. - */ -function _addExternalUserId(eids, value, source, atype) { - if (utils.isStr(value)) { - eids.push({ - source, - uids: [{ - id: value, - atype - }] - }); - } + // assign value only if bidFloor is > 0 + impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : UNDEFINED); } function _handleEids(payload, validBidRequests) { - let eids = []; - _handleDigitrustId(eids); - _handleTTDId(eids, validBidRequests); - const bidRequest = validBidRequests[0]; - if (bidRequest && bidRequest.userId) { - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid.org', 1); - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.digitrustid.data.id`), 'digitru.st', 1); - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteoId`), 'criteo.com', 1);// replacing criteoRtus - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1); - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.lipb.lipbid`), 'liveintent.com', 1); - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableid`), 'parrable.com', 1); - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.britepoolid`), 'britepool.com', 1); - } - if (eids.length > 0) { - payload.user.eids = eids; + const bidUserIdAsEids = utils.deepAccess(validBidRequests, '0.userIdAsEids'); + if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + utils.deepSetValue(payload, 'user.eids', bidUserIdAsEids); } } @@ -747,7 +772,7 @@ function _blockedIabCategoriesValidation(payload, blockedIabCategories) { } }) .map(category => category.trim()) // trim all - .filter(function(category, index, arr) { // minimum 3 charaters length + .filter(function(category, index, arr) { // more than 3 charaters length if (category.length > 3) { return arr.indexOf(category) === index; // unique value only } else { @@ -792,8 +817,26 @@ function _handleDealCustomTargetings(payload, dctrArr, validBidRequests) { } } +function _assignRenderer(newBid, request) { + let bidParams, context, adUnitCode; + if (request.bidderRequest && request.bidderRequest.bids) { + for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { + if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === newBid.requestId) { + bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; + context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + adUnitCode = request.bidderRequest.bids[bidderRequestBidsIndex].adUnitCode; + } + } + if (context && context === 'outstream' && bidParams && bidParams.outstreamAU && adUnitCode) { + newBid.rendererCode = bidParams.outstreamAU; + newBid.renderer = BB_RENDERER.newRenderer(newBid.rendererCode, adUnitCode); + } + } +}; + export const spec = { code: BIDDER_CODE, + gvlid: 76, supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid @@ -813,6 +856,19 @@ export const spec = { utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); return false; } + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + utils.logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid); + return false; + } + if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU)) { + utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids outstreamAU is required. Rejecting bid: `, bid); + return false; + } + } else { + utils.logError(`${LOG_WARN_PREFIX}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid); + return false; + } } return true; } @@ -883,6 +939,7 @@ export const spec = { payload.ext.wrapper.profile = parseInt(conf.profId) || UNDEFINED; payload.ext.wrapper.version = parseInt(conf.verId) || UNDEFINED; payload.ext.wrapper.wiid = conf.wiid || UNDEFINED; + // eslint-disable-next-line no-undef payload.ext.wrapper.wv = $$REPO_AND_VERSION$$; payload.ext.wrapper.transactionId = conf.transactionId; payload.ext.wrapper.wp = 'pbjs'; @@ -895,6 +952,16 @@ export const spec = { payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim(); payload.site.domain = _getDomainFromURL(payload.site.page); + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + payload.site.content = config.getConfig('content'); + } + + // merge the device from config.getConfig('device') + if (typeof config.getConfig('device') === 'object') { + payload.device = Object.assign(payload.device, config.getConfig('device')); + } + // passing transactionId in source.tid utils.deepSetValue(payload, 'source.tid', conf.transactionId); @@ -928,10 +995,26 @@ export const spec = { _handleEids(payload, validBidRequests); _blockedIabCategoriesValidation(payload, blockedIabCategories); + // Note: Do not move this block up + // if site object is set in Prebid config then we need to copy required fields from site into app and unset the site object + if (typeof config.getConfig('app') === 'object') { + payload.app = config.getConfig('app'); + // not copying domain from site as it is a derived value from page + payload.app.publisher = payload.site.publisher; + payload.app.ext = payload.site.ext || UNDEFINED; + // We will also need to pass content object in app.content if app object is also set into the config; + // BUT do not use content object from config if content object is present in app as app.content + if (typeof payload.app.content !== 'object') { + payload.app.content = payload.site.content || UNDEFINED; + } + delete payload.site; + } + return { method: 'POST', url: ENDPOINT, - data: JSON.stringify(payload) + data: JSON.stringify(payload), + bidderRequest: bidderRequest }; }, @@ -965,7 +1048,10 @@ export const spec = { netRevenue: NET_REVENUE, ttl: 300, referrer: parsedReferrer, - ad: bid.adm + ad: bid.adm, + pm_seat: seatbidder.seat || null, + pm_dspid: bid.ext && bid.ext.dspid ? bid.ext.dspid : null, + partnerImpId: bid.id || '' // partner impression Id }; if (parsedRequest.imp && parsedRequest.imp.length > 0) { parsedRequest.imp.forEach(req => { @@ -978,6 +1064,7 @@ export const spec = { newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; newBid.vastXml = bid.adm; + _assignRenderer(newBid, request); break; case NATIVE: _parseNativeResponse(bid, newBid); @@ -998,9 +1085,17 @@ export const spec = { newBid.meta.buyerId = bid.ext.advid; } if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.advertiserDomains = bid.adomain; newBid.meta.clickUrl = bid.adomain[0]; } + // adserverTargeting + if (seatbidder.ext && seatbidder.ext.buyid) { + newBid.adserverTargeting = { + 'hb_buyid_pubmatic': seatbidder.ext.buyid + }; + } + bidResponses.push(newBid); }); }); @@ -1015,7 +1110,7 @@ export const spec = { * Register User Sync. */ getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { - let syncurl = USYNCURL + publisherId; + let syncurl = '' + publisherId; // Attaching GDPR Consent Params in UserSync url if (gdprConsent) { @@ -1036,10 +1131,13 @@ export const spec = { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: syncurl + url: USER_SYNC_URL_IFRAME + syncurl }]; } else { - utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); + return [{ + type: 'image', + url: USER_SYNC_URL_IMAGE + syncurl + }]; } }, diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index fc7766430a2..cd9398477f4 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -25,6 +25,7 @@ var adUnits = [ bidder: 'pubmatic', params: { publisherId: '156209', // required + oustreamAU: 'renderer_test_pubmatic', // required if mediaTypes-> video-> context is 'outstream'. This value can be get by BlueBillyWig Team. adSlot: 'pubmatic_test2', // optional pmzoneid: 'zone1, zone11', // optional lat: '40.712775', // optional @@ -35,7 +36,8 @@ var adUnits = [ kadfloor: '0.50', // optional currency: 'AUD', // optional (Value configured only in the 1st adunit will be passed on. < br/> Values if present in subsequent adunits, will be ignored.) dctr: 'key1=123|key2=345', // optional (Value configured only in the 1st adunit will be passed on. < br/> Values if present in subsequent adunits, will be ignored.) - bcat: ['IAB1-5', 'IAB1-7'] // Optional: Blocked IAB Categories. (Values from all slots will be combined and only unique values will be passed. An array of strings only. Each category should be a string of a length of more than 3 characters.) + bcat: ['IAB1-5', 'IAB1-7'], // Optional: Blocked IAB Categories. (Values from all slots will be combined and only unique values will be passed. An array of strings only. Each category should be a string of a length of more than 3 characters.) + deals: ['deal-id-1', 'deal-id-200'] // optional: PMP Deals, should be array of strings } }] }]; @@ -201,5 +203,7 @@ pbjs.setConfig({ ``` 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. -Note: PubMatic will return a test-bid if "pubmaticTest=true" is present in page URL +# Notes: +- PubMatic will return a test-bid if "pubmaticTest=true" is present in page URL +- PubMatic will set bid.adserverTargeting.hb_buyid_pubmatic targeting key while submitting a bid into Prebid diff --git a/modules/pubperfAnalyticsAdapter.js b/modules/pubperfAnalyticsAdapter.js new file mode 100644 index 00000000000..800ea1cd550 --- /dev/null +++ b/modules/pubperfAnalyticsAdapter.js @@ -0,0 +1,36 @@ +/** + * Analytics Adapter for Pubperf + */ + +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; + +var pubperfAdapter = adapter({ + global: 'pubperf_pbjs', + analyticsType: 'bundle', + handler: 'on' +}); + +pubperfAdapter.originEnableAnalytics = pubperfAdapter.enableAnalytics; + +pubperfAdapter.enableAnalytics = config => { + if (!config || !config.provider || config.provider !== 'pubperf') { + utils.logError('expected config.provider to equal pubperf'); + return; + } + if (!window['pubperf_pbjs']) { + utils.logError( + `Make sure that Pubperf tag from https://www.pubperf.com is included before the Prebid configuration.` + ); + return; + } + pubperfAdapter.originEnableAnalytics(config); +} + +adapterManager.registerAnalyticsAdapter({ + adapter: pubperfAdapter, + code: 'pubperf' +}); + +export default pubperfAdapter; diff --git a/modules/pubperfAnalyticsAdapter.md b/modules/pubperfAnalyticsAdapter.md new file mode 100644 index 00000000000..50ac3691dda --- /dev/null +++ b/modules/pubperfAnalyticsAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: Pubperf Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@transfon.com +``` + +# Description + +Transfon's pubperf analytics adaptor allows you to view detailed auction and prebid information in Meridian. Contact support@transfon.com for more information or to sign up for analytics. + +For more information, please visit https://www.pubperf.com. + + +# Sample pubperf tag to be placed before prebid tag + +``` +(function(i, s, o, g, r, a, m, z) {i['pubperf_pbjs'] = r;i[r] = i[r] || function() {z = Array.prototype.slice.call(arguments);z.unshift(+new Date());(i[r].q = i[r].q || []).push(z)}, i[r].t = 1, i[r].l = 1 * new Date();a = s.createElement(o),m = s.getElementsByTagName(o)[0];a.async = 1;a.src = g;m.parentNode.insertBefore(a, m)})(window, document, 'script', 'https://t.pubperf.com/t/b5a635e307.js', 'pubperf_pbjs'); +``` + +# Test Parameters +``` +{ + provider: 'pubperf' +} +``` diff --git a/modules/pubstackAnalyticsAdapter.js b/modules/pubstackAnalyticsAdapter.js new file mode 100644 index 00000000000..b1da40c5b89 --- /dev/null +++ b/modules/pubstackAnalyticsAdapter.js @@ -0,0 +1,15 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; + +const pubstackAnalytics = adapter({ + global: 'PubstackAnalytics', + handler: 'on', + analyticsType: 'bundle' +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: pubstackAnalytics, + code: 'pubstack', +}); + +export default pubstackAnalytics; diff --git a/modules/pubstackAnalyticsAdapter.md b/modules/pubstackAnalyticsAdapter.md new file mode 100644 index 00000000000..0b473bd39d7 --- /dev/null +++ b/modules/pubstackAnalyticsAdapter.md @@ -0,0 +1,9 @@ +# Overview + +Module Name: Pubstack Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@pubstack.io + +# Description + +This is the Pubstack Analytics Adapter. [Contact Guillaume from our Support Team](mailto:support@pubstack.io) to start analyzing your HB stack. diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index d3e596d91eb..915aeb58f99 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -1,8 +1,11 @@ -import {ajax} from '../src/ajax'; -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; +import {ajax} from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -const utils = require('../src/utils'); +import { getStorageManager } from '../src/storageManager.js'; +const utils = require('../src/utils.js'); + +const storage = getStorageManager(); /**** * PubWise.io Analytics @@ -59,14 +62,14 @@ function enrichWithUTM(dataBag) { if (newUtm === false) { for (let prop in utmKeys) { - let itemValue = localStorage.getItem(`pw-${prop}`); + let itemValue = storage.getDataFromLocalStorage(`pw-${prop}`); if (itemValue.length !== 0) { dataBag[prop] = itemValue; } } } else { for (let prop in utmKeys) { - localStorage.setItem(`pw-${prop}`, utmKeys[prop]); + storage.setDataInLocalStorage(`pw-${prop}`, utmKeys[prop]); } } } catch (e) { diff --git a/modules/pulsepointAnalyticsAdapter.js b/modules/pulsepointAnalyticsAdapter.js index fe5b48b6d00..375a817f257 100644 --- a/modules/pulsepointAnalyticsAdapter.js +++ b/modules/pulsepointAnalyticsAdapter.js @@ -2,8 +2,8 @@ * pulsepoint.js - Analytics Adapter for PulsePoint */ -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; var pulsepointAdapter = adapter({ global: 'PulsePointPrebidAnalytics', diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index b898618673e..33fdaa44100 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -1,7 +1,7 @@ /* eslint dot-notation:0, quote-props:0 */ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { Renderer } from '../src/Renderer'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; const NATIVE_DEFAULTS = { TITLE_LEN: 100, @@ -28,6 +28,8 @@ export const spec = { code: 'pulsepoint', + gvlid: 81, + aliases: ['pulseLite', 'pulsepointLite'], supportedMediaTypes: ['banner', 'native', 'video'], @@ -133,8 +135,8 @@ function bidResponseAvailable(request, response) { bid.height = idToBidMap[id].h; } else { bid.ad = idToBidMap[id].adm; - bid.width = idToImpMap[id].banner.w; - bid.height = idToImpMap[id].banner.h; + bid.width = idToBidMap[id].w || idToImpMap[id].banner.w; + bid.height = idToBidMap[id].h || idToImpMap[id].banner.h; } bids.push(bid); } @@ -161,14 +163,30 @@ function impression(slot) { * Produces an OpenRTB Banner object for the slot given. */ function banner(slot) { - const size = adSize(slot); + const sizes = parseSizes(slot); + const size = adSize(slot, sizes); return (slot.nativeParams || slot.params.video) ? null : { w: size[0], h: size[1], battr: slot.params.battr, + format: sizes }; } +/** + * Produce openrtb format objects based on the sizes configured for the slot. + */ +function parseSizes(slot) { + const sizes = utils.deepAccess(slot, 'mediaTypes.banner.sizes'); + if (sizes && utils.isArray(sizes)) { + return sizes.filter(sz => utils.isArray(sz) && sz.length === 2).map(sz => ({ + w: sz[0], + h: sz[1] + })); + } + return null; +} + /** * Produces an OpenRTB Video object for the slot given */ @@ -372,12 +390,14 @@ function parse(rawResponse) { /** * Determines the AdSize for the slot. */ -function adSize(slot) { +function adSize(slot, sizes) { 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]; + } else if (sizes && sizes.length > 0) { + return [sizes[0].w, sizes[0].h]; } return [1, 1]; } @@ -401,7 +421,7 @@ function user(bidRequest, bidderRequest) { addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo'); addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'identityLink'); addExternalUserId(ext.eids, bidRequest.userId.id5id, 'id5-sync.com'); - addExternalUserId(ext.eids, bidRequest.userId.parrableid, 'parrable.com'); + addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.parrableId.eid'), 'parrable.com'); // liveintent if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { addExternalUserId(ext.eids, bidRequest.userId.lipb.lipbid, 'liveintent.com'); diff --git a/modules/pxyzBidAdapter.js b/modules/pxyzBidAdapter.js new file mode 100644 index 00000000000..bd2189ccc39 --- /dev/null +++ b/modules/pxyzBidAdapter.js @@ -0,0 +1,195 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'pxyz'; +const URL = 'https://ads.playground.xyz/host-config/prebid?v=2'; +const DEFAULT_CURRENCY = 'USD'; + +export const spec = { + code: BIDDER_CODE, + + // This adapter was previously named playgroundxyz - this alias ensures + // backwards compatibility for publishers + aliases: ['playgroundxyz'], + + 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.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + const referer = bidderRequest.refererInfo.referer; + const parts = referer.split('/'); + + let protocol, hostname; + if (parts.length >= 3) { + protocol = parts[0]; + hostname = parts[2]; + } + + const payload = { + id: bidRequests[0].auctionId, + site: { + domain: protocol + '//' + hostname, + name: hostname, + page: referer, + }, + device: { + ua: navigator.userAgent, + language: navigator.language, + devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2, + }, + imp: bidRequests.map(mapImpression), + Regs: { ext: {} } + }; + + // GDPR + if (bidderRequest && bidderRequest.gdprConsent) { + const gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + const consentString = bidderRequest.gdprConsent.consentString; + utils.logInfo(`PXYZ: GDPR applies ${gdpr}`); + utils.logInfo(`PXYZ: GDPR consent string ${consentString}`); + payload.Regs.ext.gdpr = gdpr; + payload.User = { ext: { consent: consentString } }; + } + + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + utils.logInfo(`PXYZ: USP Consent ${bidderRequest.uspConsent}`); + payload.Regs.ext['us_privacy'] = bidderRequest.uspConsent; + } + + return { + method: 'POST', + url: URL, + data: JSON.stringify(payload), + bidderRequest + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bids = []; + + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { + errorMessage += `: ${serverResponse.error}`; + utils.logError(errorMessage); + } + return bids; + } + + if (!utils.isArray(serverResponse.seatbid)) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter `; + utils.logError(errorMessage += 'Malformed seatbid response'); + return bids; + } + + if (!serverResponse.seatbid) { + return bids; + } + + const currency = serverResponse.cur || DEFAULT_CURRENCY; + serverResponse.seatbid.forEach(sBid => { + if (sBid.hasOwnProperty('bid')) { + sBid.bid.forEach(iBid => { + if (iBid.price !== 0) { + const bid = newBid(iBid, currency); + bids.push(bid); + } + }); + } + }); + return bids; + }, + + getUserSyncs: function (syncOptions) { + return [{ + type: 'image', + url: '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID' + }]; + } +} + +function newBid(bid, currency) { + return { + requestId: bid.impid, + mediaType: BANNER, + cpm: bid.price, + creativeId: bid.adid, + ad: bid.adm, + width: bid.w, + height: bid.h, + ttl: 300, + netRevenue: true, + currency: currency, + }; +} + +function mapImpression(bid) { + return { + id: bid.bidId, + banner: mapBanner(bid), + ext: { + appnexus: { + placement_id: parseInt(bid.params.placementId, 10) + }, + pxyz: { + adapter: { + vendor: 'prebid', + prebid: '$prebid.version$' + } + } + } + }; +} + +function mapBanner(bid) { + return { + w: parseInt(bid.sizes[0][0], 10), + h: parseInt(bid.sizes[0][1], 10), + format: mapSizes(bid.sizes) + }; +} + +function mapSizes(bidSizes) { + const format = []; + bidSizes.forEach(size => { + format.push({ + w: parseInt(size[0], 10), + h: parseInt(size[1], 10) + }); + }); + return format; +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +registerBidder(spec); diff --git a/modules/pxyzBidAdapter.md b/modules/pxyzBidAdapter.md new file mode 100644 index 00000000000..eced908eed8 --- /dev/null +++ b/modules/pxyzBidAdapter.md @@ -0,0 +1,37 @@ +# Overview + +``` +Module Name: Playground XYZ Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech+prebid@playgroundxyz.com +``` + +# Description + +Connects to the Playground XYZ marketplace for demand. + +This bid adapter supports the Banner media type only. + +# Test Parameters + +```js +var adUnits = [ + // Banner adUnit + { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + sizes: [[300, 250]], + bids: [{ + bidder: 'pxyz', + params: { + placementId: '13473562' + } + }] + } +]; +``` + diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 93f8e398a9d..91022d70df9 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,11 +1,16 @@ -import * as utils from '../src/utils'; -import { ajax } from '../src/ajax'; -import { config } from '../src/config'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'quantcast'; const DEFAULT_BID_FLOOR = 0.0000000001; +const QUANTCAST_VENDOR_ID = '11'; +// Check other required purposes on server +const PURPOSE_DATA_COLLECT = '1'; + export const QUANTCAST_DOMAIN = 'qcx.quantserve.com'; export const QUANTCAST_TEST_DOMAIN = 's2s-canary.quantserve.com'; export const QUANTCAST_NET_REVENUE = true; @@ -72,13 +77,44 @@ function getDomain(url) { return url.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#]/)[0]; } +function checkTCFv1(vendorData) { + let vendorConsent = vendorData.vendorConsents && vendorData.vendorConsents[QUANTCAST_VENDOR_ID]; + let purposeConsent = vendorData.purposeConsents && vendorData.purposeConsents[PURPOSE_DATA_COLLECT]; + + return !!(vendorConsent && purposeConsent); +} + +function checkTCFv2(tcData) { + if (tcData.purposeOneTreatment && tcData.publisherCC === 'DE') { + // special purpose 1 treatment for Germany + return true; + } + + let restrictions = tcData.publisher ? tcData.publisher.restrictions : {}; + let qcRestriction = restrictions && restrictions[PURPOSE_DATA_COLLECT] + ? restrictions[PURPOSE_DATA_COLLECT][QUANTCAST_VENDOR_ID] + : null; + + if (qcRestriction === 0 || qcRestriction === 2) { + // Not allowed by publisher, or requires legitimate interest + return false; + } + + let vendorConsent = tcData.vendor && tcData.vendor.consents && tcData.vendor.consents[QUANTCAST_VENDOR_ID]; + let purposeConsent = tcData.purpose && tcData.purpose.consents && tcData.purpose.consents[PURPOSE_DATA_COLLECT]; + + return !!(vendorConsent && purposeConsent); +} + /** * 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, + GVLID: 11, supportedMediaTypes: ['banner', 'video'], + hasUserSynced: false, /** * Verify the `AdUnits.bids` response with `true` for valid request and `false` @@ -107,6 +143,21 @@ export const spec = { const page = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); const domain = getDomain(page); + // Check for GDPR consent for purpose 1, and drop request if consent has not been given + // Remaining consent checks are performed server-side. + if (gdprConsent.gdprApplies) { + if (gdprConsent.vendorData) { + if (gdprConsent.apiVersion === 1 && !checkTCFv1(gdprConsent.vendorData)) { + utils.logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v1`); + return; + } + if (gdprConsent.apiVersion === 2 && !checkTCFv2(gdprConsent.vendorData)) { + utils.logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v2`); + return; + } + } + } + let bidRequestsList = []; bids.forEach(bid => { @@ -141,6 +192,7 @@ export const spec = { gdprConsent: gdprConsent.consentString, uspSignal: uspConsent ? 1 : 0, uspConsent, + coppa: config.getConfig('coppa') === true ? 1 : 0, prebidJsVersion: '$prebid.version$' }; @@ -179,15 +231,16 @@ export const spec = { const response = serverResponse['body']; - if ( - response === undefined || - !response.hasOwnProperty('bids') || - utils.isEmpty(response.bids) - ) { + if (response === undefined || !response.hasOwnProperty('bids')) { utils.logError('Sub-optimal JSON received from Quantcast server'); return []; } + if (utils.isEmpty(response.bids)) { + // Shortcut response handling if no bids are present + return []; + } + const bidResponsesList = response.bids.map(bid => { const { ad, cpm, width, height, creativeId, currency, videoUrl, dealId } = bid; @@ -220,6 +273,27 @@ export const spec = { onTimeout(timeoutData) { const url = `${QUANTCAST_PROTOCOL}://${QUANTCAST_DOMAIN}:${QUANTCAST_PORT}/qchb_notify?type=timeout`; ajax(url, null, null); + }, + getUserSyncs(syncOptions, serverResponses) { + const syncs = [] + if (!this.hasUserSynced && syncOptions.pixelEnabled) { + const responseWithUrl = find(serverResponses, serverResponse => + utils.deepAccess(serverResponse.body, 'userSync.url') + ); + + if (responseWithUrl) { + const url = utils.deepAccess(responseWithUrl.body, 'userSync.url') + syncs.push({ + type: 'image', + url: url + }); + } + this.hasUserSynced = true; + } + return syncs; + }, + resetUserSync() { + this.hasUserSynced = false; } }; diff --git a/modules/quantcastBidAdapter.md b/modules/quantcastBidAdapter.md index 2b55eae9026..edbbc538b65 100644 --- a/modules/quantcastBidAdapter.md +++ b/modules/quantcastBidAdapter.md @@ -27,7 +27,10 @@ const adUnits = [{ battr: [1, 2] // OPTIONAL - Array of blocked creative attributes as per OpenRTB Spec List 5.3 } } - ] + ], + userSync: { + url: 'https://quantcast.com/pixelUrl' + } }]; ``` @@ -63,6 +66,9 @@ var adUnits = [{ } } } - ] + ], + userSync: { + url: 'https://quantcast.com/pixelUrl' + } }]; ``` diff --git a/modules/quantumBidAdapter.js b/modules/quantumBidAdapter.js deleted file mode 100644 index 729150b8b08..00000000000 --- a/modules/quantumBidAdapter.js +++ /dev/null @@ -1,320 +0,0 @@ -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 = 'https://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, bidderRequest) { - 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 = 'https://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; - - if (bidderRequest && bidderRequest.gdprConsent) { - qtxRequest.quantx_user_consent_string = bidderRequest.gdprConsent.consentString; - qtxRequest.quantx_gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; - }; - - const url = devEnpoint || ENDPOINT_URL; - - return { - method: 'GET', - bidId: bidId, - sizes: bid.sizes, - mediaType: mediaType, - renderMode: renderMode, - url: url, - 'data': qtxRequest, - bidderRequest - }; - }); - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, 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 (!utils.isEmpty(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 = 'https://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 = encodeURIComponent(serverBody.native.jstracker); - } - - if (serverBody.nurl) { - trackers.push(serverBody.nurl); - } - - let ad = {}; - ad['trackers'] = trackers; - ad['jstrackers'] = jstracker; - ad['eventtrackers'] = serverBody.native.eventtrackers || []; - - 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'] = 'https://resize-ssp.adux.com/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 = { - url: asset['img']['url'], - width: asset['img']['w'], - height: asset['img']['h'] - }; - break; - case 3: - native.body = asset['data']['value']; - break; - case 4: - native.image = { - url: asset['img']['url'], - width: asset['img']['w'], - height: asset['img']['h'] - }; - 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; - } - native.eventtrackers = native.eventtrackers || []; - - 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} serverResponse A successful response from the server - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function (syncOptions, serverResponse) { - const syncs = []; - utils._each(serverResponse, function(serverResponse) { - if (serverResponse.body && serverResponse.body.sync) { - utils._each(serverResponse.body.sync, function (pixel) { - syncs.push({ - type: 'image', - url: pixel - }); - }); - } - }); - return syncs; - } -} -registerBidder(spec); diff --git a/modules/quantumBidAdapter.md b/modules/quantumBidAdapter.md deleted file mode 100644 index 572ca9ecd37..00000000000 --- a/modules/quantumBidAdapter.md +++ /dev/null @@ -1,94 +0,0 @@ -# Overview - -``` -Module Name: Quantum Advertising Bid Adapter -Module Type: Bidder Adapter -Maintainer: support.mediareporting@adux.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/quantumdexBidAdapter.js b/modules/quantumdexBidAdapter.js new file mode 100644 index 00000000000..9b7a79755e3 --- /dev/null +++ b/modules/quantumdexBidAdapter.js @@ -0,0 +1,238 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +const BIDDER_CODE = 'quantumdex'; +const CONFIG = { + 'quantumdex': { + 'ENDPOINT': 'https://useast.quantumdex.io/auction/quantumdex', + 'USERSYNC': 'https://sync.quantumdex.io/usersync/quantumdex' + }, + 'valueimpression': { + 'ENDPOINT': 'https://useast.quantumdex.io/auction/adapter', + 'USERSYNC': 'https://sync.quantumdex.io/usersync/adapter' + } +}; + +var bidderConfig = CONFIG['quantumdex']; +var bySlotTargetKey = {}; +var bySlotSizesCount = {} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + aliases: ['valueimpression'], + isBidRequestValid: function (bid) { + if (!bid.params) { + return false; + } + if (!bid.params.siteId) { + return false; + } + if (!utils.deepAccess(bid, 'mediaTypes.banner') && !utils.deepAccess(bid, 'mediaTypes.video')) { + return false; + } + if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Quantumdex does not support multi type bids, favor banner over video + if (!utils.deepAccess(bid, 'mediaTypes.banner.sizes')) { + // sizes at the banner is required. + return false; + } + } else if (utils.deepAccess(bid, 'mediaTypes.video')) { + if (!utils.deepAccess(bid, 'mediaTypes.video.playerSize')) { + // playerSize is required for instream adUnits. + return false; + } + } + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + var bids = JSON.parse(JSON.stringify(validBidRequests)) + bidderConfig = CONFIG[bids[0].bidder]; + const payload = {}; + + bids.forEach(bidReq => { + var targetKey = 0; + if (bySlotTargetKey[bidReq.adUnitCode] != undefined) { + targetKey = bySlotTargetKey[bidReq.adUnitCode]; + } else { + var biggestSize = _getBiggestSize(bidReq.sizes); + if (biggestSize) { + if (bySlotSizesCount[biggestSize] != undefined) { + bySlotSizesCount[biggestSize]++ + targetKey = bySlotSizesCount[biggestSize]; + } else { + bySlotSizesCount[biggestSize] = 0; + targetKey = 0 + } + } + } + bySlotTargetKey[bidReq.adUnitCode] = targetKey; + bidReq.targetKey = targetKey; + }); + + payload.device = {}; + payload.device.ua = navigator.userAgent; + payload.device.height = window.top.innerHeight; + payload.device.width = window.top.innerWidth; + payload.device.dnt = _getDoNotTrack(); + payload.device.language = navigator.language; + + payload.site = {}; + payload.site.id = bids[0].params.siteId; + payload.site.page = _extractTopWindowUrlFromBidderRequest(bidderRequest); + payload.site.referrer = _extractTopWindowReferrerFromBidderRequest(bidderRequest); + payload.site.hostname = window.top.location.hostname; + + // Apply GDPR parameters to request. + payload.gdpr = {}; + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr.gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 'true' : 'false'; + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; + } + } + // Apply schain. + if (bids[0].schain) { + payload.schain = JSON.stringify(bids[0].schain) + } + // Apply us_privacy. + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + + payload.bids = bids; + + return { + method: 'POST', + url: bidderConfig.ENDPOINT, + data: payload, + withCredentials: true, + bidderRequests: bids + }; + }, + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + const serverBids = serverBody.bids; + // check overall response + if (!serverBody || typeof serverBody !== 'object') { + return []; + } + if (!serverBids || typeof serverBids !== 'object') { + return []; + } + + const bidResponses = []; + serverBids.forEach(bid => { + const bidResponse = { + requestId: bid.requestId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.creativeId, + dealId: bid.dealId, + currency: bid.currency, + netRevenue: bid.netRevenue, + ttl: bid.ttl, + mediaType: bid.mediaType + }; + if (bid.vastXml) { + bidResponse.vastXml = utils.replaceAuctionPrice(bid.vastXml, bid.cpm); + } else { + bidResponse.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm); + } + bidResponses.push(bidResponse); + }); + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + try { + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: bidderConfig.USERSYNC + }); + } + if (serverResponses.length > 0 && serverResponses[0].body && serverResponses[0].body.pixel) { + serverResponses[0].body.pixel.forEach(px => { + if (px.type === 'image' && syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: px.url + }); + } + if (px.type === 'iframe' && syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: px.url + }); + } + }); + } + } catch (e) { } + return syncs; + } +}; + +function _getBiggestSize(sizes) { + if (sizes.length <= 0) return false + var acreage = 0; + var index = 0; + for (var i = 0; i < sizes.length; i++) { + var currentAcreage = sizes[i][0] * sizes[i][1]; + if (currentAcreage >= acreage) { + acreage = currentAcreage; + index = i; + } + } + return sizes[index][0] + 'x' + sizes[index][1]; +} + +function _getDoNotTrack() { + if (window.top.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack) { + if (window.top.doNotTrack == '1' || navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') { + return 1; + } else { + return 0; + } + } else { + return 0; + } +} + +/** + * Extracts the page url from given bid request or use the (top) window location as fallback + * + * @param {*} bidderRequest + * @returns {string} + */ +function _extractTopWindowUrlFromBidderRequest(bidderRequest) { + if (bidderRequest && utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl')) { + return bidderRequest.refererInfo.canonicalUrl; + } + + try { + return window.top.location.href; + } catch (e) { + return window.location.href; + } +} + +/** + * Extracts the referrer from given bid request or use the (top) document referrer as fallback + * + * @param {*} bidderRequest + * @returns {string} + */ +function _extractTopWindowReferrerFromBidderRequest(bidderRequest) { + if (bidderRequest && utils.deepAccess(bidderRequest, 'refererInfo.referer')) { + return bidderRequest.refererInfo.referer; + } + + try { + return window.top.document.referrer; + } catch (e) { + return window.document.referrer; + } +} + +registerBidder(spec); diff --git a/modules/quantumdexBidAdapter.md b/modules/quantumdexBidAdapter.md new file mode 100644 index 00000000000..8c35ea8cb05 --- /dev/null +++ b/modules/quantumdexBidAdapter.md @@ -0,0 +1,56 @@ +# Overview + +``` +Module Name: Quantum Digital Exchange Bidder Adapter +Module Type: Bidder Adapter +Maintainer: ken@quantumdex.io +``` + +# Description + +Connects to Quantum Digital Exchange for bids. +Quantumdex bid adapter supports Banner and Video (Instream and Outstream) ads. + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [ + { + bidder: 'quantumdex', + params: { + siteId: 'quantumdex-site-id', // siteId provided by Quantumdex + } + } + ] + } +]; +``` + +# Video Test Parameters +``` +var videoAdUnit = { + code: 'test-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [ + { + bidder: 'quantumdex', + params: { + siteId: 'quantumdex-site-id', // siteId provided by Quantumdex + } + } + ] +}; +``` \ No newline at end of file diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index 026dc3c4af6..1f07cc07b91 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils'; -import {config} from '../src/config'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'rads'; const ENDPOINT_URL = 'https://rads.recognified.net/md.request.php'; diff --git a/modules/rakutenBidAdapter/index.js b/modules/rakutenBidAdapter/index.js new file mode 100644 index 00000000000..e567509b3c1 --- /dev/null +++ b/modules/rakutenBidAdapter/index.js @@ -0,0 +1,80 @@ +import { registerBidder } from '../../src/adapters/bidderFactory.js'; +import { BANNER } from '../../src/mediaTypes.js'; +import { config } from '../../src/config.js'; +const BIDDER_CODE = 'rakuten'; +const ENDPOINT = 'https://s-bid.rmp.rakuten.com/h'; +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: bid => !!bid.params.adSpotId, + buildRequests: (validBidRequests, bidderRequest) => { + const bidRequests = []; + validBidRequests.forEach(bid => { + var _a, _b; + const params = bid.params; + bidRequests.push({ + method: 'GET', + url: config.getConfig('rakuten.endpoint') || ENDPOINT, + data: { + bi: bid.bidId, + t: params.adSpotId, + s: document.location.protocol, + ua: navigator.userAgent, + l: navigator.browserLanguage || + navigator.language, + d: document.domain, + tp: bidderRequest.refererInfo.stack[0] || window.location.href, + pp: bidderRequest.refererInfo.referer, + gdpr: ((_a = bidderRequest.gdprConsent) === null || _a === void 0 ? void 0 : _a.gdprApplies) ? 1 : 0, + ...((_b = bidderRequest.gdprConsent) === null || _b === void 0 ? void 0 : _b.consentString) && { + cd: bidderRequest.gdprConsent.consentString + }, + ...bidderRequest.uspConsent && { + ccpa: bidderRequest.uspConsent + } + } + }); + }); + return bidRequests; + }, + interpretResponse: (response, request) => { + const sb = response.body; + const bidResponses = []; + if (sb.cpm && sb.ad) { + bidResponses.push({ + requestId: sb.bid_id, + cpm: sb.cpm, + width: sb.width || 0, + height: sb.height || 0, + creativeId: sb.creative_id || 0, + dealId: sb.deal_id || '', + currency: sb.currency || 'USD', + netRevenue: (typeof sb.net_revenue === 'undefined') ? true : !!sb.net_revenue, + mediaType: BANNER, + ttl: sb.ttl, + ad: sb.ad + }); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + if (syncOptions.pixelEnabled && serverResponses[0].body !== undefined) { + const bidResponseObj = serverResponses[0].body; + if (!bidResponseObj) { + return []; + } + if (bidResponseObj.sync_urls && bidResponseObj.sync_urls.length > 0) { + bidResponseObj.sync_urls.forEach(syncUrl => { + if (syncUrl && syncUrl !== 'null' && syncUrl.length > 0) { + syncs.push({ + type: 'image', + url: syncUrl + }); + } + }); + } + } + return syncs; + } +}; +registerBidder(spec); diff --git a/modules/rakutenBidAdapter/index.md b/modules/rakutenBidAdapter/index.md new file mode 100644 index 00000000000..cc6aa68151d --- /dev/null +++ b/modules/rakutenBidAdapter/index.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: Rakuten Bidder Adapter +Module Type: Bidder Adapter +Maintainer: @snapwich +``` + +# Description + +Bid adapter for Rakuten RSSP + +Rakuten bid adapter supports Banner currently. + +# Test Parameters + +``` + var adUnits = [ + { + code: 'test-ad-div', + sizes: [[300, 250]], + mediaTypes: {banner: {}}, + bids: [ + { + bidder: 'rakuten', + params: { + adSpotId: 42 + } + } + ] + } + ]; +``` diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js new file mode 100644 index 00000000000..c72bbdd658f --- /dev/null +++ b/modules/readpeakBidAdapter.js @@ -0,0 +1,310 @@ +import { logError, replaceAuctionPrice, parseUrl } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { NATIVE } from '../src/mediaTypes.js'; + +export const ENDPOINT = 'https://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, bidderRequest) => { + const currencyObj = config.getConfig('currency'); + const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; + + const request = { + id: bidRequests[0].bidderRequestId, + imp: bidRequests + .map(slot => impression(slot)) + .filter(imp => imp.native != null), + site: site(bidRequests, bidderRequest), + app: app(bidRequests), + device: device(), + cur: [currency], + 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(bidRequests, bidderRequest) { + const url = + config.getConfig('pageUrl') || + (bidderRequest && + bidderRequest.refererInfo && + bidderRequest.refererInfo.referer); + + const pubId = + bidRequests && bidRequests.length > 0 + ? bidRequests[0].params.publisherId + : '0'; + const siteId = + bidRequests && bidRequests.length > 0 ? bidRequests[0].params.siteId : '0'; + const appParams = bidRequests[0].params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString(), + domain: config.getConfig('publisherDomain') + }, + id: siteId ? siteId.toString() : pubId.toString(), + page: url, + domain: + (url && parseUrl(url).hostname) || config.getConfig('publisherDomain') + }; + } + 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 isMobile() { + return /(ios|ipod|ipad|iphone|android)/i.test(global.navigator.userAgent); +} + +function isConnectedTV() { + return /(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i.test( + global.navigator.userAgent + ); +} + +function device() { + return { + ua: navigator.userAgent, + language: + navigator.language || + navigator.browserLanguage || + navigator.userLanguage || + navigator.systemLanguage, + devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2 + }; +} + +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/realvuAnalyticsAdapter.js b/modules/realvuAnalyticsAdapter.js index 45d7babc19b..95e62397c2c 100644 --- a/modules/realvuAnalyticsAdapter.js +++ b/modules/realvuAnalyticsAdapter.js @@ -1,7 +1,10 @@ // RealVu Analytics Adapter -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); const utils = require('../src/utils.js'); @@ -779,7 +782,7 @@ export let lib = { writePos: function (a) { try { let v = a.x + ',' + a.y + ',' + a.w + ',' + a.h; - localStorage.setItem(this.keyPos(a), v); + storage.setDataInLocalStorage(this.keyPos(a), v); } catch (ex) { /* continue regardless of error */ } @@ -787,7 +790,7 @@ export let lib = { readPos: function (a) { try { - let s = localStorage.getItem(this.keyPos(a)); + let s = storage.getDataFromLocalStorage(this.keyPos(a)); if (s) { let v = s.split(','); a.x = parseInt(v[0], 10); @@ -806,7 +809,7 @@ export let lib = { incrMem: function(a, evt, name) { try { let k1 = this.keyPos(a) + '.' + name; - let vmem = localStorage.getItem(k1); + let vmem = storage.getDataFromLocalStorage(k1); if (vmem == null) vmem = '1:3'; let vr = vmem.split(':'); let nv = parseInt(vr[0], 10); @@ -819,7 +822,7 @@ export let lib = { if (evt == 'v') { nv |= 1; } - localStorage.setItem(k1, nv + ':' + nr); + storage.setDataInLocalStorage(k1, nv + ':' + nr); } catch (ex) { /* do nothing */ } @@ -827,7 +830,7 @@ export let lib = { score: function (a, name) { try { - let vstr = localStorage.getItem(this.keyPos(a) + '.' + name); + let vstr = storage.getDataFromLocalStorage(this.keyPos(a) + '.' + name); if (vstr != null) { let vr = vstr.split(':'); let nv = parseInt(vr[0], 10); diff --git a/modules/reklamstoreBidAdapter.js b/modules/reklamstoreBidAdapter.js index 02a94836836..3d78cf95978 100644 --- a/modules/reklamstoreBidAdapter.js +++ b/modules/reklamstoreBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'reklamstore'; const ENDPOINT_URL = 'https://ads.rekmob.com/m/prebid'; diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js new file mode 100644 index 00000000000..b3b8a647137 --- /dev/null +++ b/modules/relaidoBidAdapter.js @@ -0,0 +1,305 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const BIDDER_CODE = 'relaido'; +const BIDDER_DOMAIN = 'api.relaido.jp'; +const ADAPTER_VERSION = '1.0.1'; +const DEFAULT_TTL = 300; +const UUID_KEY = 'relaido_uuid'; + +const storage = getStorageManager(); + +function isBidRequestValid(bid) { + if (!utils.isSafariBrowser() && !hasUuid()) { + utils.logWarn('uuid is not found.'); + return false; + } + if (!utils.deepAccess(bid, 'params.placementId')) { + utils.logWarn('placementId param is reqeuired.'); + return false; + } + if (hasVideoMediaType(bid)) { + if (!isVideoValid(bid)) { + utils.logWarn('Invalid mediaType video.'); + return false; + } + } else if (hasBannerMediaType(bid)) { + if (!isBannerValid(bid)) { + utils.logWarn('Invalid mediaType banner.'); + return false; + } + } else { + utils.logWarn('Invalid mediaTypes input banner or video.'); + return false; + } + return true; +} + +function buildRequests(validBidRequests, bidderRequest) { + let bidRequests = []; + + for (let i = 0; i < validBidRequests.length; i++) { + const bidRequest = validBidRequests[i]; + const placementId = utils.getBidIdParameter('placementId', bidRequest.params); + const bidDomain = bidRequest.params.domain || BIDDER_DOMAIN; + const bidUrl = `https://${bidDomain}/bid/v1/prebid/${placementId}`; + const uuid = getUuid(); + const mediaType = getMediaType(bidRequest); + + let payload = { + version: ADAPTER_VERSION, + timeout_ms: bidderRequest.timeout, + ad_unit_code: bidRequest.adUnitCode, + auction_id: bidRequest.auctionId, + bidder: bidRequest.bidder, + bidder_request_id: bidRequest.bidderRequestId, + bid_requests_count: bidRequest.bidRequestsCount, + bid_id: bidRequest.bidId, + transaction_id: bidRequest.transactionId, + media_type: mediaType, + uuid: uuid, + }; + + if (hasVideoMediaType(bidRequest)) { + const playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + payload.width = playerSize[0][0]; + payload.height = playerSize[0][1]; + } else if (hasBannerMediaType(bidRequest)) { + const sizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + payload.width = sizes[0][0]; + payload.height = sizes[0][1]; + } + + // It may not be encoded, so add it at the end of the payload + payload.ref = bidderRequest.refererInfo.referer; + + bidRequests.push({ + method: 'GET', + url: bidUrl, + data: payload, + options: { + withCredentials: true + }, + bidId: bidRequest.bidId, + player: bidRequest.params.player, + width: payload.width, + height: payload.height, + mediaType: mediaType, + }); + } + + return bidRequests; +} + +function interpretResponse(serverResponse, bidRequest) { + const bidResponses = []; + const body = serverResponse.body; + if (!body || body.status != 'ok') { + return []; + } + + if (body.uuid) { + storage.setDataInLocalStorage(UUID_KEY, body.uuid); + } + + const playerUrl = bidRequest.player || body.playerUrl; + const mediaType = bidRequest.mediaType || VIDEO; + + let bidResponse = { + requestId: bidRequest.bidId, + width: bidRequest.width, + height: bidRequest.height, + cpm: body.price, + currency: body.currency, + creativeId: body.creativeId, + dealId: body.dealId || '', + ttl: body.ttl || DEFAULT_TTL, + netRevenue: true, + mediaType: mediaType, + }; + if (mediaType === VIDEO) { + bidResponse.vastXml = body.vast; + bidResponse.renderer = newRenderer(bidRequest.bidId, playerUrl); + } else { + const playerTag = createPlayerTag(playerUrl); + const renderTag = createRenderTag(bidRequest.width, bidRequest.height, body.vast); + bidResponse.ad = `
${playerTag}${renderTag}
`; + } + bidResponses.push(bidResponse); + + return bidResponses; +} + +function getUserSyncs(syncOptions, serverResponses) { + if (!syncOptions.iframeEnabled) { + return []; + } + let syncUrl = `https://${BIDDER_DOMAIN}/tr/v1/prebid/sync.html`; + if (serverResponses.length > 0) { + syncUrl = utils.deepAccess(serverResponses, '0.body.syncUrl') || syncUrl; + } + receiveMessage(); + return [{ + type: 'iframe', + url: syncUrl + }]; +} + +function onBidWon(bid) { + let query = utils.parseQueryStringParameters({ + placement_id: utils.deepAccess(bid, 'params.0.placementId'), + creative_id: utils.deepAccess(bid, 'creativeId'), + price: utils.deepAccess(bid, 'cpm'), + auction_id: utils.deepAccess(bid, 'auctionId'), + bid_id: utils.deepAccess(bid, 'requestId'), + ad_id: utils.deepAccess(bid, 'adId'), + ad_unit_code: utils.deepAccess(bid, 'adUnitCode'), + ref: window.location.href, + }).replace(/\&$/, ''); + const bidDomain = utils.deepAccess(bid, 'params.0.domain') || BIDDER_DOMAIN; + const burl = `https://${bidDomain}/tr/v1/prebid/win.gif?${query}`; + utils.triggerPixel(burl); +} + +function onTimeout(data) { + let query = utils.parseQueryStringParameters({ + placement_id: utils.deepAccess(data, '0.params.0.placementId'), + timeout: utils.deepAccess(data, '0.timeout'), + auction_id: utils.deepAccess(data, '0.auctionId'), + bid_id: utils.deepAccess(data, '0.bidId'), + ad_unit_code: utils.deepAccess(data, '0.adUnitCode'), + ref: window.location.href, + }).replace(/\&$/, ''); + const bidDomain = utils.deepAccess(data, '0.params.0.domain') || BIDDER_DOMAIN; + const timeoutUrl = `https://${bidDomain}/tr/v1/prebid/timeout.gif?${query}`; + utils.triggerPixel(timeoutUrl); +} + +function createPlayerTag(playerUrl) { + return ``; +} + +function createRenderTag(width, height, vast) { + return ``; +}; + +function newRenderer(bidId, playerUrl) { + const renderer = Renderer.install({ + id: bidId, + url: playerUrl, + loaded: false + }); + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('renderer.setRender Error', err); + } + return renderer; +} + +function outstreamRender(bid) { + bid.renderer.push(() => { + window.RelaidoPlayer.renderAd({ + adUnitCode: bid.adUnitCode, + width: bid.width, + height: bid.height, + vastXml: bid.vastXml, + mediaType: bid.mediaType, + }); + }); +} + +function receiveMessage() { + window.addEventListener('message', setUuid); +} + +function setUuid(e) { + if (utils.isPlainObject(e.data) && e.data.relaido_uuid) { + storage.setDataInLocalStorage(UUID_KEY, e.data.relaido_uuid); + window.removeEventListener('message', setUuid); + } +} + +function isBannerValid(bid) { + if (!isMobile()) { + return false; + } + const sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); + if (sizes && utils.isArray(sizes)) { + if (utils.isArray(sizes[0])) { + const width = sizes[0][0]; + const height = sizes[0][1]; + if (width >= 300 && height >= 250) { + return true; + } + } + } + return false; +} + +function isVideoValid(bid) { + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + if (playerSize && utils.isArray(playerSize) && playerSize.length > 0) { + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + if (context && context === 'outstream') { + return true; + } + } + return false; +} + +function hasUuid() { + return !!storage.getDataFromLocalStorage(UUID_KEY); +} + +function getUuid() { + return storage.getDataFromLocalStorage(UUID_KEY) || ''; +} + +export function isMobile() { + const ua = navigator.userAgent; + if (ua.indexOf('iPhone') > -1 || ua.indexOf('iPod') > -1 || (ua.indexOf('Android') > -1 && ua.indexOf('Tablet') == -1)) { + return true; + } + return false; +} + +function getMediaType(bid) { + if (hasVideoMediaType(bid)) { + return VIDEO; + } else if (hasBannerMediaType(bid)) { + return BANNER; + } + return ''; +} + +function hasBannerMediaType(bid) { + return !!utils.deepAccess(bid, 'mediaTypes.banner'); +} + +function hasVideoMediaType(bid) { + return !!utils.deepAccess(bid, 'mediaTypes.video'); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs, + onBidWon, + onTimeout +} + +registerBidder(spec); diff --git a/modules/relaidoBidAdapter.md b/modules/relaidoBidAdapter.md new file mode 100644 index 00000000000..459f772c66b --- /dev/null +++ b/modules/relaidoBidAdapter.md @@ -0,0 +1,48 @@ +# Overview + +``` +Module Name: Relaido Bidder Adapter +Module Type: Bidder Adapter +Maintainer: video-dev@cg.relaido.co.jp +``` + +# Description + +Connects to Relaido exchange for bids. + +Relaido bid adapter supports Outstream Video. + +# Test Parameters + +```javascript + var adUnits=[{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'relaido', + params: { + placementId: '9900' + } + }] + },{ + code: 'video-ad-player', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 360] + } + }, + bids: [{ + bidder: 'relaido', + params: { + placementId: '9900' + } + }] + }]; +``` \ No newline at end of file diff --git a/modules/reloadBidAdapter.js b/modules/reloadBidAdapter.js index d99de5d6c53..94ea4be281f 100644 --- a/modules/reloadBidAdapter.js +++ b/modules/reloadBidAdapter.js @@ -1,8 +1,12 @@ import { registerBidder } - from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; + from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); + const BIDDER_CODE = 'reload'; const VERSION_ADAPTER = '1.10'; export const spec = { @@ -390,14 +394,14 @@ function ReloadClientTool(args) { var stgFileStr = JSON.stringify(stgFileObj); - localStorage.setItem(name, stgFileStr); + storage.setDataInLocalStorage(name, stgFileStr); return true; } function _getItem (name) { try { - var obStgFileStr = localStorage.getItem(name); + var obStgFileStr = storage.getDataFromLocalStorage(name); if (obStgFileStr === null) return null; var stgFileObj = JSON.parse(obStgFileStr); diff --git a/modules/resultsmediaBidAdapter.js b/modules/resultsmediaBidAdapter.js new file mode 100644 index 00000000000..beb9991e1e2 --- /dev/null +++ b/modules/resultsmediaBidAdapter.js @@ -0,0 +1,269 @@ +'use strict'; + +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +function ResultsmediaAdapter() { + this.code = 'resultsmedia'; + this.aliases = ['resultsmedia']; + this.supportedMediaTypes = [VIDEO, BANNER]; + + let SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6]; + let SUPPORTED_VIDEO_MIMES = ['video/mp4']; + let SUPPORTED_VIDEO_PLAYBACK_METHODS = [1, 2, 3, 4]; + let SUPPORTED_VIDEO_DELIVERY = [1]; + let SUPPORTED_VIDEO_API = [1, 2, 5]; + let slotsToBids = {}; + let that = this; + let version = '2.1'; + + this.isBidRequestValid = function (bid) { + return !!(bid.params && bid.params.zoneId); + }; + + this.getUserSyncs = function (syncOptions, responses, gdprConsent) { + return []; + }; + + function frameImp(BRs, bidderRequest) { + var impList = []; + var isSecure = 0; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.stack.length) { + // clever trick to get the protocol + var el = document.createElement('a'); + el.href = bidderRequest.refererInfo.stack[0]; + isSecure = (el.protocol == 'https:') ? 1 : 0; + } + for (var i = 0; i < BRs.length; i++) { + slotsToBids[BRs[i].adUnitCode] = BRs[i]; + var impObj = {}; + impObj.id = BRs[i].adUnitCode; + impObj.secure = isSecure; + + if (utils.deepAccess(BRs[i], 'mediaTypes.banner') || utils.deepAccess(BRs[i], 'mediaType') === 'banner') { + let banner = frameBanner(BRs[i]); + if (banner) { + impObj.banner = banner; + } + } + if (utils.deepAccess(BRs[i], 'mediaTypes.video') || utils.deepAccess(BRs[i], 'mediaType') === 'video') { + impObj.video = frameVideo(BRs[i]); + } + if (!(impObj.banner || impObj.video)) { + continue; + } + impObj.ext = frameExt(BRs[i]); + impList.push(impObj); + } + return impList; + } + + function frameSite(bidderRequest) { + var site = { + domain: '', + page: '', + ref: '' + } + if (bidderRequest && bidderRequest.refererInfo) { + var ri = bidderRequest.refererInfo; + site.ref = ri.referer; + + if (ri.stack.length) { + site.page = ri.stack[ri.stack.length - 1]; + + // clever trick to get the domain + var el = document.createElement('a'); + el.href = ri.stack[0]; + site.domain = el.hostname; + } + } + return site; + } + + function frameDevice() { + return { + ua: navigator.userAgent, + ip: '', // Empty Ip string is required, server gets the ip from HTTP header + dnt: utils.getDNT() ? 1 : 0, + } + } + + function getValidSizeSet(dimensionList) { + let w = parseInt(dimensionList[0]); + let h = parseInt(dimensionList[1]); + // clever check for NaN + if (! (w !== w || h !== h)) { // eslint-disable-line + return [w, h]; + } + return false; + } + + function frameBanner(adUnit) { + // adUnit.sizes is scheduled to be deprecated, continue its support but prefer adUnit.mediaTypes.banner + var sizeList = adUnit.sizes; + if (adUnit.mediaTypes && adUnit.mediaTypes.banner) { + sizeList = adUnit.mediaTypes.banner.sizes; + } + var sizeStringList = utils.parseSizesInput(sizeList); + var format = []; + sizeStringList.forEach(function(size) { + if (size) { + var dimensionList = getValidSizeSet(size.split('x')); + if (dimensionList) { + format.push({ + 'w': dimensionList[0], + 'h': dimensionList[1], + }); + } + } + }); + if (format.length) { + return { + 'format': format + }; + } + + return false; + } + + function frameVideo(bid) { + var size = []; + if (utils.deepAccess(bid, 'mediaTypes.video.playerSize')) { + var dimensionSet = bid.mediaTypes.video.playerSize; + if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { + dimensionSet = bid.mediaTypes.video.playerSize[0]; + } + var validSize = getValidSizeSet(dimensionSet) + if (validSize) { + size = validSize; + } + } + return { + mimes: utils.deepAccess(bid, 'mediaTypes.video.mimes') || SUPPORTED_VIDEO_MIMES, + protocols: utils.deepAccess(bid, 'mediaTypes.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS, + w: size[0], + h: size[1], + startdelay: utils.deepAccess(bid, 'mediaTypes.video.startdelay') || 0, + skip: utils.deepAccess(bid, 'mediaTypes.video.skip') || 0, + playbackmethod: utils.deepAccess(bid, 'mediaTypes.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS, + delivery: utils.deepAccess(bid, 'mediaTypes.video.delivery') || SUPPORTED_VIDEO_DELIVERY, + api: utils.deepAccess(bid, 'mediaTypes.video.api') || SUPPORTED_VIDEO_API, + } + } + + function frameExt(bid) { + return { + bidder: { + zoneId: bid.params['zoneId'] + } + } + } + + function frameBid(BRs, bidderRequest) { + let bid = { + id: BRs[0].bidderRequestId, + imp: frameImp(BRs, bidderRequest), + site: frameSite(bidderRequest), + device: frameDevice(), + user: { + ext: { + consent: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? bidderRequest.gdprConsent.consentString : '' + } + }, + at: 1, + tmax: 1000, + regs: { + ext: { + gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false + } + } + }; + if (BRs[0].schain) { + bid.source = { + 'ext': { + 'schain': BRs[0].schain + } + } + } + return bid; + } + + 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]; + } + } + } + + this.buildRequests = function (BRs, bidderRequest) { + let fallbackZoneId = getFirstParam('zoneId', BRs); + if (fallbackZoneId === undefined || BRs.length < 1) { + return []; + } + + var uri = 'https://bid306.rtbsrv.com/bidder/?bid=3mhdom&zoneId=' + fallbackZoneId; + + var fat = /(^v|(\.0)+$)/gi; + var prebidVersion = '$prebid.version$'; + uri += '&hbv=' + prebidVersion.replace(fat, '') + ',' + version.replace(fat, ''); + + var bidRequest = frameBid(BRs, bidderRequest); + if (!bidRequest.imp.length) { + return {}; + } + + return { + method: 'POST', + url: uri, + data: JSON.stringify(bidRequest) + }; + }; + + this.interpretResponse = function (serverResponse) { + let responses = serverResponse.body || []; + let bids = []; + let i = 0; + + 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; + } + + for (i = 0; i < responses.length; i++) { + let bid = responses[i]; + let bidRequest = slotsToBids[bid.impid]; + let bidResponse = { + requestId: bidRequest.id, + bidderCode: that.code, + cpm: parseFloat(bid.price), + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: 'USD', + netRevenue: true, + ttl: 350 + }; + + if (bidRequest.mediaTypes && bidRequest.mediaTypes.video) { + bidResponse.vastUrl = bid.adm; + bidResponse.mediaType = 'video'; + bidResponse.ttl = 600; + } else { + bidResponse.ad = bid.adm; + } + bids.push(bidResponse); + } + + return bids; + }; +} + +export const spec = new ResultsmediaAdapter(); +registerBidder(spec); diff --git a/modules/resultsmediaBidAdapter.md b/modules/resultsmediaBidAdapter.md new file mode 100644 index 00000000000..0b65264c8e4 --- /dev/null +++ b/modules/resultsmediaBidAdapter.md @@ -0,0 +1,47 @@ +# Overview + +``` +Module Name: ResultsMedia (resultsmedia.com) Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@resultsmedia.COM +``` + +# Description + +Prebid adapter for ResultsMedia RTB. Requires approval and account setup. + +# Test Parameters + +## Web +``` + var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [300, 200] // banner sizes + ], + } + }, + bids: [{ + bidder: 'resultsmedia', + params: { + zoneId: 9999 + } + }] + }, { + code: 'video-ad-player', + mediaTypes: { + video: { + context: 'instream', // or 'outstream' + playerSize: [640, 480] // video player size + } + }, + bids: [{ + bidder: 'resultsmedia', + params: { + zoneId: 9999 + } + }] + }]; +``` \ No newline at end of file diff --git a/modules/revcontentBidAdapter.js b/modules/revcontentBidAdapter.js new file mode 100644 index 00000000000..b429f94eae0 --- /dev/null +++ b/modules/revcontentBidAdapter.js @@ -0,0 +1,275 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'revcontent'; +const NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['native'], + isBidRequestValid: function (bid) { + return (typeof bid.params.apiKey !== 'undefined' && typeof bid.params.userId !== 'undefined' && bid.hasOwnProperty('nativeParams')); + }, + buildRequests: (validBidRequests, bidderRequest) => { + const userId = validBidRequests[0].params.userId; + const widgetId = validBidRequests[0].params.widgetId; + const apiKey = validBidRequests[0].params.apiKey; + var domain = validBidRequests[0].params.domain; + var host = validBidRequests[0].params.endpoint; + + if (typeof host === 'undefined') { + host = 'trends.revcontent.com'; + } + + let serverRequests = []; + var refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo.referer; + } + + if (typeof domain === 'undefined') { + domain = extractHostname(refererInfo); + } + + var endpoint = 'https://' + host + '/rtb?apiKey=' + apiKey + '&userId=' + userId; + + if (!isNaN(widgetId) && widgetId > 0) { + endpoint = endpoint + '&widgetId=' + widgetId; + } + + let bidfloor = 0.1; + if (!isNaN(validBidRequests[0].params.bidfloor) && validBidRequests[0].params.bidfloor > 0) { + bidfloor = validBidRequests[0].params.bidfloor; + } + + const imp = validBidRequests.map((bid, id) => { + if (bid.hasOwnProperty('nativeParams')) { + const assets = utils._map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1 + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } + }).filter(Boolean); + + return { + id: id + 1, + tagid: bid.params.mid, + bidderRequestId: bid.bidderRequestId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + native: { + request: { + ver: '1.1', + context: 2, + contextsubtype: 21, + plcmttype: 1, + plcmtcnt: 1, + assets: assets + }, + ver: '1.1', + battr: [1, 3, 8, 11, 17] + }, + instl: 0, + bidfloor: bidfloor, + secure: '1' + }; + } + }); + + let data = { + id: bidderRequest.auctionId, + imp: imp, + site: { + id: widgetId, + domain: domain, + page: refererInfo, + cat: ['IAB17'], + publisher: { + id: userId, + domain: domain + } + }, + device: { + ua: navigator.userAgent, + language: 'en' + }, + user: { + id: 1 + }, + at: 2, + bcat: [ + 'IAB24', + 'IAB25', + 'IAB25-1', + 'IAB25-2', + 'IAB25-3', + 'IAB25-4', + 'IAB25-5', + 'IAB25-6', + 'IAB25-7', + 'IAB26', + 'IAB26-1', + 'IAB26-2', + 'IAB26-3', + 'IAB26-4' + ] + }; + serverRequests.push({ + method: 'POST', + options: { + contentType: 'application/json' + }, + url: endpoint, + data: JSON.stringify(data), + bid: validBidRequests + }); + + return serverRequests; + }, + interpretResponse: function (serverResponse, originalBidRequest) { + if (!serverResponse.body) { + return; + } + const seatbid = serverResponse.body.seatbid[0]; + const bidResponses = []; + + for (var x in seatbid.bid) { + let adm = JSON.parse(seatbid.bid[x]['adm']); + let ad = { + clickUrl: adm.link.url + }; + + adm.assets.forEach(asset => { + switch (asset.id) { + case 3: + ad['image'] = { + url: asset.img.url, + height: 1, + width: 1 + }; + break; + case 0: + ad['title'] = asset.title.text; + break; + case 5: + ad['sponsoredBy'] = asset.data.value; + break; + } + }); + + var size = originalBidRequest.bid[0].params.size; + + const bidResponse = { + bidder: BIDDER_CODE, + requestId: originalBidRequest.bid[0].bidId, + cpm: seatbid.bid[x]['price'], + creativeId: seatbid.bid[x]['adid'], + currency: 'USD', + netRevenue: true, + ttl: 360, + nurl: seatbid.bid[x]['nurl'], + bidderCode: 'revcontent', + mediaType: 'native', + native: ad, + width: size.width, + height: size.height, + ad: displayNative(ad, getTemplate(size, originalBidRequest.bid[0].params.template)) + }; + + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + onBidWon: function (bid) { + utils.triggerPixel(bid.nurl); + return true; + } +}; + +registerBidder(spec); + +function displayNative(ad, template) { + template = template.replace(/{image}/g, ad['image']['url']); + template = template.replace(/{title}/g, ad['title']); + template = template.replace(/{clickUrl}/g, ad['clickUrl']); + template = template.replace(/{sponsoredBy}/g, ad['sponsoredBy']); + return template; +} + +function getTemplate(size, customTemplate) { + if (typeof (customTemplate) !== 'undefined' && customTemplate !== '') { + return customTemplate; + } + + if (size.width == 300 && size.height == 250) { + return '

{title}

SEE MORE
'; + } + + if (size.width == 728 && size.height == 90) { + return '

{title}

>
'; + } + + if (size.width == 300 && size.height == 600) { + return '

{title}

>
'; + } + + return ''; +} + +function extractHostname(url) { + if (typeof url == 'undefined' || url == null) { + return ''; + } + var hostname; + if (url.indexOf('//') > -1) { + hostname = url.split('/')[2]; + } else { + hostname = url.split('/')[0]; + } + + hostname = hostname.split(':')[0]; + hostname = hostname.split('?')[0]; + + return hostname; +} diff --git a/modules/revcontentBidAdapter.md b/modules/revcontentBidAdapter.md new file mode 100644 index 00000000000..d9f44e8036c --- /dev/null +++ b/modules/revcontentBidAdapter.md @@ -0,0 +1,68 @@ +# Overview + +Module Name: Revcontent Adapater +Maintainer: aziz@revcontent.com + +# Description + +Revcontent Adpater + +# Test Parameters +``` + /* + Supported sizes: + ---------------- + 300x250 - Medium rectangle + 728x90 - Leaderboard + 300x600 - Half page or large skyscraper + */ + var size = {width: 300, height: 250}; + + var adUnits = [{ + code: '/19968336/header-bid-tag-1', + sizes: sizes, + mediaTypes: { + native: { + native: { + image: { + required: false, + sizes: sizes[0] + }, + title: { + required: false, + len: 140 + }, + clickUrl: { + required: false + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + } + } + } + }, + bids: [{ + bidder: 'revcontent', + params: { + size: size, + + /* -> Modify this section <- */ + apiKey: '8a33sdfsdfdsfsdfssss544f8sdfsdfsdfd3b1c', // Required + userId: 69565, // Required + widgetId: 599995, // Optional + domain: 'test.com', // Optional - Default referral hostname + endpoint: 'trends.revcontent.com' // Optional - Debug - Set different endpoint + bidfloor: 0.1, // Optional - BidFloor - Default 0.1 + /* + Optional - Set different template. Template variables: + {clickUrl} -> Target Url + {image} -> Image URL + {title} -> Ad Title + */ + template: '

{title}

SEE MORE
' + } + }] + }]; +``` diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index f7102e4be5a..1acbcfd0463 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -1,8 +1,8 @@ 'use strict'; -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; function RhythmOneBidAdapter() { this.code = 'rhythmone'; diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 2a4fa03c7d6..43bef356a73 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -1,7 +1,8 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; -import {config} from '../src/config'; -import {BANNER, VIDEO} from '../src/mediaTypes'; -import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'richaudience'; let REFERER = ''; @@ -45,7 +46,9 @@ export const spec = { numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), transactionId: bid.transactionId, timeout: config.getConfig('bidderTimeout'), - user: raiSetEids(bid) + user: raiSetEids(bid), + demand: raiGetDemandType(bid), + videoData: raiGetVideoInfo(bid) }; REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null) @@ -95,8 +98,18 @@ export const spec = { if (response.media_type === 'video') { bidResponse.vastXml = response.vastXML; + try { + if (JSON.parse(bidRequest.data).videoData.format == 'outstream') { + bidResponse.renderer = Renderer.install({ + url: 'https://cdn3.richaudience.com/prebidVideo/player.js' + }); + bidResponse.renderer.setRender(renderer); + } + } catch (e) { + bidResponse.ad = response.adm; + } } else { - bidResponse.ad = response.adm + bidResponse.ad = response.adm; } bidResponses.push(bidResponse); @@ -116,18 +129,31 @@ export const spec = { var rand = Math.floor(Math.random() * 9999999999); var syncUrl = ''; + var consent = ''; - gdprConsent && typeof gdprConsent.consentString === 'string' ? syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand + '&pubconsent=' + gdprConsent.consentString + '&euconsent=' + gdprConsent.consentString : syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand; + if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') { + consent = `consentString=’${gdprConsent.consentString}` + } if (syncOptions.iframeEnabled) { + syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand + if (consent != '') { + syncUrl += `&${consent}` + } syncs.push({ type: 'iframe', url: syncUrl }); - } else if (syncOptions.pixelEnabled && REFERER != null) { + } + + if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0) { + syncUrl = `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=${REFERER}`; + if (consent != '') { + syncUrl += `&${consent}` + } syncs.push({ type: 'image', - url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?euconsent=${gdprConsent.consentString}&referrer=${REFERER}` + url: syncUrl }); } return syncs @@ -140,8 +166,6 @@ function raiGetSizes(bid) { let raiNewSizes; if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) { raiNewSizes = bid.mediaTypes.banner.sizes - } else { - raiNewSizes = bid.sizes } if (raiNewSizes != null) { return raiNewSizes.map(size => ({ @@ -151,6 +175,28 @@ function raiGetSizes(bid) { } } +function raiGetDemandType(bid) { + let raiFormat = 'display'; + if (bid.mediaTypes != undefined) { + if (bid.mediaTypes.video != undefined) { + raiFormat = 'video'; + } + } + return raiFormat; +} + +function raiGetVideoInfo(bid) { + let videoData; + if (raiGetDemandType(bid) == 'video') { + videoData = { + format: bid.mediaTypes.video.context, + playerSize: bid.mediaTypes.video.playerSize, + mimes: bid.mediaTypes.video.mimes + }; + } + return videoData; +} + function raiSetEids(bid) { let eids = []; @@ -174,3 +220,24 @@ function raiSetUserId(bid, eids, source, value) { }); } } + +function renderer(bid) { + bid.renderer.push(() => { + renderAd(bid) + }); +} + +function renderAd(bid) { + let raOutstreamHBPassback = `${bid.vastXml}`; + let raPlayerHB = { + config: bid.params[0].player != undefined ? { + end: bid.params[0].player.end != null ? bid.params[0].player.end : 'close', + init: bid.params[0].player.init != null ? bid.params[0].player.init : 'close', + skin: bid.params[0].player.skin != null ? bid.params[0].player.skin : 'light', + } : {end: 'close', init: 'close', skin: 'light'}, + pid: bid.params[0].pid, + adUnit: bid.adUnitCode + }; + + window.raParams(raPlayerHB, raOutstreamHBPassback, true); +} diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index 11c60efc9dc..932cdb8f8de 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Rich Audience Bidder Adapter Module Type: Bidder Adapter -Maintainer: cert@richaudience.com +Maintainer: cert@richaudience.com ``` # Description @@ -15,7 +15,7 @@ Please reach out to your account manager for more information. # Test Parameters -## Web +## Web - DISPLAY ``` var adUnits = [ { @@ -45,12 +45,43 @@ Please reach out to your account manager for more information. ]; ``` +## Web - VIDEO +``` + var adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + + bids: [{ + bidder: 'richaudience', + params: { + pid: 'OjUW9KhuQV', + supplyType: 'site', + player: { + init: "open", + end: "close", + skin: "light" + } + } + }] + + }]; +``` + ## In-app ``` var adUnits = [ { code: 'test-div1', - sizes: [[300, 250],[300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, bids: [{ bidder: 'richaudience', params: { @@ -85,7 +116,18 @@ Rich Audience strongly recommends enabling user syncing through iframes. Be sure ```javascript pbjs.setConfig({ userSync: { - iframeEnabled: true - } + filterSettings: { + iframe: { + bidders: '*', + filter: 'include' + }, + image: { + bidders: '*', + filter: 'include' + } + }, + syncsPerBidder: 3, + syncDelay: 6000, + } }); ``` diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index e1cfa5c70bc..279b1b13051 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -1,7 +1,7 @@ -import {ajax} from '../src/ajax'; -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; -import * as utils from '../src/utils'; +import {ajax} from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; const analyticsType = 'endpoint'; diff --git a/modules/roxotAnalyticsAdapter.js b/modules/roxotAnalyticsAdapter.js index 45bcbc55947..b6857d69f45 100644 --- a/modules/roxotAnalyticsAdapter.js +++ b/modules/roxotAnalyticsAdapter.js @@ -1,10 +1,13 @@ -import adapter from '../src/AnalyticsAdapter'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; -import includes from 'core-js/library/fn/array/includes'; -import {ajaxBuilder} from '../src/ajax'; +import adapterManager from '../src/adapterManager.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import {ajaxBuilder} from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; -const utils = require('../src/utils'); +const storage = getStorageManager(); + +const utils = require('../src/utils.js'); let ajax = ajaxBuilder(0); const DEFAULT_EVENT_URL = 'pa.rxthdr.com/v3'; @@ -68,17 +71,17 @@ function detectDevice() { function checkIsNewFlag() { let key = buildLocalStorageKey(isNewKey); - let lastUpdate = Number(localStorage.getItem(key)); - localStorage.setItem(key, Date.now()); + let lastUpdate = Number(storage.getDataFromLocalStorage(key)); + storage.setDataInLocalStorage(key, Date.now()); return Date.now() - lastUpdate > isNewTtl; } function updateUtmTimeout() { - localStorage.setItem(buildLocalStorageKey(utmTtlKey), Date.now()); + storage.setDataInLocalStorage(buildLocalStorageKey(utmTtlKey), Date.now()); } function isUtmTimeoutExpired() { - let utmTimestamp = localStorage.getItem(buildLocalStorageKey(utmTtlKey)); + let utmTimestamp = storage.getDataFromLocalStorage(buildLocalStorageKey(utmTtlKey)); return (Date.now() - utmTimestamp) > utmTtl; } @@ -356,11 +359,11 @@ roxotAdapter.buildUtmTagData = function () { }); utmTags.forEach(function (utmTagKey) { if (utmTagsDetected) { - localStorage.setItem(buildLocalStorageKey(utmTagKey), utmTagData[utmTagKey]); + storage.setDataInLocalStorage(buildLocalStorageKey(utmTagKey), utmTagData[utmTagKey]); updateUtmTimeout(); } else { if (!isUtmTimeoutExpired()) { - utmTagData[utmTagKey] = localStorage.getItem(buildLocalStorageKey(utmTagKey)) ? localStorage.getItem(buildLocalStorageKey(utmTagKey)) : ''; + utmTagData[utmTagKey] = storage.getDataFromLocalStorage(buildLocalStorageKey(utmTagKey)) ? storage.getDataFromLocalStorage(buildLocalStorageKey(utmTagKey)) : ''; updateUtmTimeout(); } } diff --git a/modules/rtbdemandBidAdapter.js b/modules/rtbdemandBidAdapter.js index ac8e639ff03..be5fb39f53a 100644 --- a/modules/rtbdemandBidAdapter.js +++ b/modules/rtbdemandBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'rtbdemand'; const BIDDER_SERVER = 'bidding.rtbdemand.com'; diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 36c08fc8626..3337c3f1b59 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils'; -import { BANNER, NATIVE } from '../src/mediaTypes'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import includes from 'core-js/library/fn/array/includes'; +import * as utils from '../src/utils.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import includes from 'core-js-pure/features/array/includes.js'; const BIDDER_CODE = 'rtbhouse'; const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; @@ -46,9 +46,7 @@ export const spec = { site: mapSite(validBidRequests, bidderRequest), cur: DEFAULT_CURRENCY_ARR, test: validBidRequests[0].params.test || 0, - source: { - tid: validBidRequests[0].transactionId - } + source: mapSource(validBidRequests[0]), }; if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { const consentStr = (bidderRequest.gdprConsent.consentString) @@ -145,6 +143,52 @@ function mapSite(slot, bidderRequest) { } } +/** + * @param {object} slot Ad Unit Params by Prebid + * @returns {object} Source by OpenRTB 2.5 §3.2.2 + */ +function mapSource(slot) { + const source = { + tid: slot.transactionId, + }; + const schain = mapSchain(slot.schain); + if (schain) { + source.ext = { + schain: schain + } + } + return source; +} + +/** + * @param {object} schain object set by Publisher + * @returns {object} OpenRTB SupplyChain object + */ +function mapSchain(schain) { + if (!schain) { + return null; + } + if (!validateSchain(schain)) { + utils.logError('RTB House: required schain params missing'); + return null; + } + return schain; +} + +/** + * @param {object} schain object set by Publisher + * @returns {object} bool + */ +function validateSchain(schain) { + if (!schain.nodes) { + return false; + } + const requiredFields = ['asi', 'sid', 'hp']; + return schain.nodes.every(node => { + return requiredFields.every(field => node[field]); + }); +} + /** * @param {object} slot Ad Unit Params by Prebid * @returns {object} Request by OpenRTB Native Ads 1.1 §4 diff --git a/modules/rtbsapeBidAdapter.js b/modules/rtbsapeBidAdapter.js new file mode 100644 index 00000000000..8473ef4dbb3 --- /dev/null +++ b/modules/rtbsapeBidAdapter.js @@ -0,0 +1,142 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {OUTSTREAM} from '../src/video.js'; +import {Renderer} from '../src/Renderer.js'; +import {triggerPixel} from '../src/utils.js'; + +const BIDDER_CODE = 'rtbsape'; +const ENDPOINT = 'https://ssp-rtb.sape.ru/prebid'; +const RENDERER_SRC = 'https://cdn-rtb.sape.ru/js/player.js'; +const MATCH_SRC = 'https://www.acint.net/mc/?dp=141'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['sape'], + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.video) && bid.params && bid.params.placeId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests an array of bids + * @param bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let tz = (new Date()).getTimezoneOffset() + let padInt = (v) => (v < 10 ? '0' + v : '' + v); + + return { + url: ENDPOINT, + method: 'POST', + data: { + auctionId: bidderRequest.auctionId, + requestId: bidderRequest.bidderRequestId, + bids: validBidRequests, + timezone: (tz > 0 ? '-' : '+') + padInt(Math.floor(Math.abs(tz) / 60)) + ':' + padInt(Math.abs(tz) % 60), + refererInfo: bidderRequest.refererInfo + }, + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {{data: {bids: [{mediaTypes: {banner: boolean}}]}}} bidRequest Info describing the request to the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + if (!(serverResponse.body && Array.isArray(serverResponse.body.bids))) { + return []; + } + + let bids = {}; + bidRequest.data.bids.forEach(bid => bids[bid.bidId] = bid); + + return serverResponse.body.bids.map(bid => { + let requestBid = bids[bid.requestId]; + let context = utils.deepAccess(requestBid, 'mediaTypes.video.context'); + + if (context === OUTSTREAM && (bid.vastUrl || bid.vastXml)) { + let renderer = Renderer.install({ + id: bid.requestId, + url: RENDERER_SRC, + loaded: false + }); + + let muted = utils.deepAccess(requestBid, 'params.video.playerMuted'); + if (typeof muted === 'undefined') { + muted = true; + } + + bid.playerMuted = muted; + bid.renderer = renderer + + renderer.setRender(setOutstreamRenderer); + } + + return bid; + }); + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions) { + const sync = []; + if (syncOptions.iframeEnabled) { + sync.push({ + type: 'iframe', + url: MATCH_SRC + }); + } + return sync; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function(bid) { + if (bid.nurl) { + triggerPixel(bid.nurl); + } + } +} + +/** + * Initialize RtbSape outstream player + * + * @param bid + */ +function setOutstreamRenderer(bid) { + let props = {}; + if (bid.vastUrl) { + props.url = bid.vastUrl; + } + if (bid.vastXml) { + props.xml = bid.vastXml; + } + bid.renderer.push(() => { + let player = window.sapeRtbPlayerHandler(bid.adUnitCode, bid.width, bid.height, bid.playerMuted, {singleton: true}); + props.onComplete = () => player.destroy(); + props.onError = () => player.destroy(); + player.addSlot(props); + }); +} + +registerBidder(spec); diff --git a/modules/rtbsapeBidAdapter.md b/modules/rtbsapeBidAdapter.md new file mode 100644 index 00000000000..6b1afe3867d --- /dev/null +++ b/modules/rtbsapeBidAdapter.md @@ -0,0 +1,51 @@ +# Overview + +``` +Module Name: RtbSape Bid Adapter +Module Type: Bidder Adapter +Maintainer: sergey@sape.ru +``` + +# Description +Our module makes it easy to integrate RtbSape demand sources into your website. + +Supported Ad format: +* Banner +* Video (instream and outstream) + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'rtbsape', + params: { + placeId: 553307 + } + }] + }, + // Video adUnit + { + code: 'video-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [600, 340] + } + }, + bids: [{ + bidder: 'rtbsape', + params: { + placeId: 553309 + } + }] + } +]; +``` diff --git a/modules/rtbsolutionsBidAdapter.js b/modules/rtbsolutionsBidAdapter.js index ad75b9de25a..244ab8a4eba 100644 --- a/modules/rtbsolutionsBidAdapter.js +++ b/modules/rtbsolutionsBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; -import { ajax } from '../src/ajax'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'rtbsolutions'; const ENDPOINT_URL = 'https://dsp-eu-lb.rtbsolutions.pro/bid/hb'; diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index e7ba364c0e5..3aa7753d204 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -47,14 +47,23 @@ * @type {Object} */ -import {getGlobal} from '../../src/prebidGlobal'; +/** + * @property + * @summary timeout (if no auction dealy) + * @name ModuleConfig#timeout + * @type {number} + */ + +import {getGlobal} from '../../src/prebidGlobal.js'; import {config} from '../../src/config.js'; -import {targeting} from '../../src/targeting'; -import {getHook, module} from '../../src/hook'; -import * as utils from '../../src/utils'; +import {targeting} from '../../src/targeting.js'; +import {getHook, module} from '../../src/hook.js'; +import * as utils from '../../src/utils.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; +/** @type {number} */ +const DEF_TIMEOUT = 1000; /** @type {RtdSubmodule[]} */ let subModules = []; /** @type {ModuleConfig} */ @@ -100,7 +109,7 @@ function getProviderData(adUnits, callback) { const dataWaitTimeout = setTimeout(() => { processDone = true; callback(dataReceived); - }, _moduleConfig.auctionDelay); + }, _moduleConfig.auctionDelay || _moduleConfig.timeout || DEF_TIMEOUT); subModules.forEach(sm => { sm.getData(adUnits, onDataReceived); @@ -119,6 +128,32 @@ function getProviderData(adUnits, callback) { } } +/** + * delete invalid data received from provider + * this is to ensure working flow for GPT + * @param {Object} data received from provider + * @return {Object} valid data for GPT targeting + */ +export function validateProviderDataForGPT(data) { + // data must be an object, contains object with string as value + if (typeof data !== 'object') { + return {}; + } + for (let key in data) { + if (data.hasOwnProperty(key)) { + for (let innerKey in data[key]) { + if (data[key].hasOwnProperty(innerKey)) { + if (typeof data[key][innerKey] !== 'string') { + utils.logWarn(`removing ${key}: {${innerKey}:${data[key][innerKey]} } from GPT targeting because of invalid type (must be string)`); + delete data[key][innerKey]; + } + } + } + } + } + return data; +} + /** * run hook after bids request and before callback * get data from provider and set key values to primary ad server @@ -187,11 +222,16 @@ export function requestBidsHook(fn, reqBidsConfigObj) { * @param {Object} data - key values to set */ function setDataForPrimaryAdServer(data) { - if (!utils.isGptPubadsDefined()) { - utils.logError('window.googletag is not defined on the page'); - return; + data = validateProviderDataForGPT(data); + if (utils.isGptPubadsDefined()) { + targeting.setTargetingForGPT(data, null) + } else { + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + targeting.setTargetingForGPT(data, null); + }); } - targeting.setTargetingForGPT(data, null); } /** diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js new file mode 100644 index 00000000000..0bee4d926fc --- /dev/null +++ b/modules/rubiconAnalyticsAdapter.js @@ -0,0 +1,554 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_TIMEOUT, + BID_WON, + SET_TARGETING + }, + STATUS: { + GOOD, + NO_BID + }, + BID_STATUS: { + BID_REJECTED + } +} = CONSTANTS; + +let serverConfig; +config.getConfig('s2sConfig', ({s2sConfig}) => { + serverConfig = s2sConfig; +}); + +export const SEND_TIMEOUT = 3000; +const DEFAULT_INTEGRATION = 'pbjs'; + +const cache = { + auctions: {}, + targeting: {}, + timeouts: {}, +}; + +export function getHostNameFromReferer(referer) { + try { + rubiconAdapter.referrerHostname = utils.parseUrl(referer, {noDecodeWholeURL: true}).hostname; + } catch (e) { + utils.logError('Rubicon Analytics: Unable to parse hostname from supplied url: ', referer, e); + rubiconAdapter.referrerHostname = ''; + } + return rubiconAdapter.referrerHostname +}; + +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 utils.pick(bid, [ + 'bidder', + 'bidId', bidId => utils.deepAccess(bid, 'bidResponse.pbsBidId') || utils.deepAccess(bid, 'bidResponse.seatBidId') || 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 ? utils.pick(bidResponse, [ + 'bidPriceUSD', + 'dealId', + 'dimensions', + 'mediaType', + 'floorValue', + 'floorRule' + ]) : undefined + ]); + } + function formatBidWon(bid) { + return Object.assign(formatBid(bid), utils.pick(bid.adUnit, [ + 'adUnitCode', + 'transactionId', + 'videoAdFormat', () => bid.videoAdFormat, + 'mediaTypes' + ]), { + adserverTargeting: stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), + bidwonStatus: 'success', // hard-coded for now + accountId, + siteId: bid.siteId, + zoneId: bid.zoneId, + samplingFactor + }); + } + let auctionCache = cache.auctions[auctionId]; + let referrer = config.getConfig('pageUrl') || auctionCache.referrer; + let message = { + eventTimeMillis: Date.now(), + integration: config.getConfig('rubicon.int_type') || DEFAULT_INTEGRATION, + version: '$prebid.version$', + referrerUri: referrer, + referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer) + }; + const wrapperName = config.getConfig('rubicon.wrapperName'); + if (wrapperName) { + message.wrapperName = wrapperName; + } + 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] = utils.pick(bid.adUnit, [ + 'adUnitCode', + 'transactionId', + 'mediaTypes', + 'dimensions', + 'adserverTargeting', () => stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), + 'adSlot' + ]); + adUnit.bids = []; + adUnit.status = 'no-bid'; // default it to be no bid + } + + // Add site and zone id if not there and if we found a rubicon bidder + if ((!adUnit.siteId || !adUnit.zoneId) && rubiconAliases.indexOf(bid.bidder) !== -1) { + if (utils.deepAccess(bid, 'params.accountId') == accountId) { + adUnit.accountId = parseInt(accountId); + adUnit.siteId = parseInt(utils.deepAccess(bid, 'params.siteId')); + adUnit.zoneId = parseInt(utils.deepAccess(bid, 'params.zoneId')); + } + } + + 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; + }, {}); + + // We need to mark each cached bid response with its appropriate rubicon site-zone id + // This allows the bidWon events to have these params even in the case of a delayed render + Object.keys(auctionCache.bids).forEach(function (bidId) { + let adCode = auctionCache.bids[bidId].adUnit.adUnitCode; + Object.assign(auctionCache.bids[bidId], utils.pick(adUnitMap[adCode], ['accountId', 'siteId', 'zoneId'])); + }); + + let auction = { + clientTimeoutMillis: auctionCache.timeout, + samplingFactor, + accountId, + adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]) + }; + + // pick our of top level floor data we want to send! + if (auctionCache.floorData) { + if (auctionCache.floorData.location === 'noData') { + auction.floors = utils.pick(auctionCache.floorData, [ + 'location', + 'fetchStatus', + 'floorProvider as provider' + ]); + } else { + auction.floors = utils.pick(auctionCache.floorData, [ + 'location', + 'modelVersion as modelName', + 'skipped', + 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), + 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), + 'skipRate', + 'fetchStatus', + 'floorProvider as provider' + ]); + } + } + + 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 getBidPrice(bid) { + // get the cpm from bidResponse + let cpm; + let currency; + if (bid.status === BID_REJECTED && utils.deepAccess(bid, 'floorData.cpmAfterAdjustments')) { + // if bid was rejected and bid.floorData.cpmAfterAdjustments use it + cpm = bid.floorData.cpmAfterAdjustments; + currency = bid.floorData.floorCurrency; + } else if (typeof bid.currency === 'string' && bid.currency.toUpperCase() === 'USD') { + // bid is in USD use it + return Number(bid.cpm); + } else { + // else grab cpm + cpm = bid.cpm; + currency = bid.currency; + } + // if after this it is still going and is USD then return it. + if (currency === 'USD') { + return Number(cpm); + } + // otherwise we convert and return + try { + return Number(getGlobal().convertCurrency(cpm, currency, 'USD')); + } catch (err) { + utils.logWarn('Rubicon Analytics Adapter: Could not determine the bidPriceUSD of the bid ', bid); + } +} + +export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { + // The current bidResponse for this matching requestId/bidRequestId + let responsePrice = getBidPrice(bid) + // we need to compare it with the previous one (if there was one) + if (previousBidResponse && previousBidResponse.bidPriceUSD > responsePrice) { + return previousBidResponse; + } + return utils.pick(bid, [ + 'bidPriceUSD', () => responsePrice, + 'dealId', + 'status', + 'mediaType', + 'dimensions', () => utils.pick(bid, [ + 'width', + 'height' + ]), + 'seatBidId', + 'floorValue', () => utils.deepAccess(bid, 'floorData.floorValue'), + 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined + ]); +} + +let samplingFactor = 1; +let accountId; +// List of known rubicon aliases +// This gets updated on auction init to account for any custom aliases present +let rubiconAliases = ['rubicon']; + +/* + Checks the alias registry for any entries of the rubicon bid adapter. + adds to the rubiconAliases list if found +*/ +function setRubiconAliases(aliasRegistry) { + Object.keys(aliasRegistry).forEach(function (alias) { + if (aliasRegistry[alias] === 'rubicon') { + rubiconAliases.push(alias); + } + }); +} + +let baseAdapter = adapter({analyticsType: 'endpoint'}); +let rubiconAdapter = Object.assign({}, baseAdapter, { + referrerHostname: '', + 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: + // set the rubicon aliases + setRubiconAliases(adapterManager.aliasRegistry); + let cacheEntry = utils.pick(args, [ + 'timestamp', + 'timeout' + ]); + cacheEntry.bids = {}; + cacheEntry.bidsWon = {}; + cacheEntry.referrer = args.bidderRequests[0].refererInfo.referer; + const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData'); + if (floorData) { + cacheEntry.floorData = {...floorData}; + } + 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] = utils.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 utils.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', () => utils.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 auctionEntry = cache.auctions[args.auctionId]; + let bid = auctionEntry.bids[args.requestId]; + // If floor resolved gptSlot but we have not yet, then update the adUnit to have the adSlot name + if (!utils.deepAccess(bid, 'adUnit.adSlot') && utils.deepAccess(args, 'floorData.matchedFields.gptSlot')) { + bid.adUnit.adSlot = args.floorData.matchedFields.gptSlot; + } + // if we have not set enforcements yet set it + if (!utils.deepAccess(auctionEntry, 'floorData.enforcements') && utils.deepAccess(args, 'floorData.enforcements')) { + auctionEntry.floorData.enforcements = {...args.floorData.enforcements}; + } + if (!bid) { + utils.logError('Rubicon Anlytics Adapter Error: Could not find associated bid request for bid response with requestId: ', args.requestId); + break; + } + bid.source = formatSource(bid.source || args.source); + switch (args.getStatusCode()) { + case GOOD: + bid.status = 'success'; + delete bid.error; // it's possible for this to be set by a previous timeout + break; + case NO_BID: + bid.status = args.status === BID_REJECTED ? 'rejected' : 'no-bid'; + delete bid.error; + break; + default: + bid.status = 'error'; + bid.error = { + code: 'request-error' + }; + } + bid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].timestamp; + bid.bidResponse = parseBidResponse(args, bid.bidResponse); + break; + case BIDDER_DONE: + args.bids.forEach(bid => { + let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId || bid.requestId]; + if (typeof bid.serverResponseTimeMs !== 'undefined') { + cachedBid.serverLatencyMillis = bid.serverResponseTimeMs; + } + 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.requestId; + + // check if this BID_WON missed the boat, if so send by itself + if (auctionCache.sent === true) { + sendMessage.call(this, args.auctionId, args.requestId); + } 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 || badBid.requestId]; + 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 f42ed0be5b6..47ca694dc43 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,15 +1,17 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {config} from '../src/config'; -import {BANNER, VIDEO} from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; +const DEFAULT_PBS_INTEGRATION = 'pbjs'; // always use https, regardless of whether or not current page is secure export const FASTLANE_ENDPOINT = 'https://fastlane.rubiconproject.com/a/api/fastlane.json'; export const VIDEO_ENDPOINT = 'https://prebid-server.rubiconproject.com/openrtb2/auction'; export const SYNC_ENDPOINT = 'https://eus.rubiconproject.com/usync.html'; +const GVLID = 52; const DIGITRUST_PROP_NAMES = { FASTLANE: { id: 'dt.id', @@ -26,6 +28,7 @@ var sizeMap = { 1: '468x60', 2: '728x90', 5: '120x90', + 7: '125x125', 8: '120x600', 9: '160x600', 10: '300x600', @@ -67,7 +70,10 @@ var sizeMap = { 79: '980x300', 80: '980x400', 83: '480x300', + 85: '300x120', + 90: '548x150', 94: '970x310', + 95: '970x100', 96: '970x210', 101: '480x320', 102: '768x1024', @@ -91,19 +97,26 @@ var sizeMap = { 214: '980x360', 221: '1x1', 229: '320x180', + 230: '2000x1400', 232: '580x400', 234: '6x6', 251: '2x2', + 256: '480x820', 257: '400x600', + 258: '500x200', + 259: '998x200', 264: '970x1000', 265: '1920x1080', + 274: '1800x200', 278: '320x500', + 282: '320x400', 288: '640x380' }; utils._each(sizeMap, (item, key) => sizeMap[item] = key); export const spec = { code: 'rubicon', + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], /** * @param {object} bid @@ -171,6 +184,11 @@ export const spec = { // includebidderkeys always false for openrtb includebidderkeys: false, pricegranularity: getPriceGranularity(config) + }, + bidders: { + rubicon: { + integration: config.getConfig('rubicon.int_type') || DEFAULT_PBS_INTEGRATION + } } } } @@ -183,7 +201,17 @@ export const spec = { } } - const bidFloor = parseFloat(utils.deepAccess(bidRequest, 'params.floor')); + let bidFloor; + if (typeof bidRequest.getFloor === 'function' && !config.getConfig('rubicon.disableFloors')) { + let floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'video', + size: parseSizes(bidRequest, 'video') + }); + bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; + } else { + bidFloor = parseFloat(utils.deepAccess(bidRequest, 'params.floor')); + } if (!isNaN(bidFloor)) { data.imp[0].bidfloor = bidFloor; } @@ -215,7 +243,7 @@ export const spec = { } if (bidRequest.userId && typeof bidRequest.userId === 'object' && - (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb)) { + (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb || bidRequest.userId.idl_env)) { utils.deepSetValue(data, 'user.ext.eids', []); if (bidRequest.userId.tdid) { @@ -257,6 +285,16 @@ export const spec = { utils.deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments); } } + + // support identityLink (aka LiveRamp) + if (bidRequest.userId.idl_env) { + data.user.ext.eids.push({ + source: 'liveramp_idl', + uids: [{ + id: bidRequest.userId.idl_env + }] + }); + } } if (config.getConfig('coppa') === true) { @@ -267,6 +305,53 @@ export const spec = { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } + const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); + const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { + const bidderData = { + bidders: [ bidderRequest.bidderCode ], + config: { + fpd: {} + } + }; + + if (!utils.isEmpty(siteData)) { + bidderData.config.fpd.site = siteData; + } + + if (!utils.isEmpty(userData)) { + bidderData.config.fpd.user = userData; + } + + utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); + } + + /** + * Prebid AdSlot + * @type {(string|undefined)} + */ + const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + if (typeof pbAdSlot === 'string' && pbAdSlot) { + utils.deepSetValue(data.imp[0].ext, 'context.data.pbadslot', pbAdSlot); + } + + /** + * GAM Ad Unit + * @type {(string|undefined)} + */ + const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot'); + if (typeof gamAdUnit === 'string' && gamAdUnit) { + utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', gamAdUnit); + } + + // if storedAuctionResponse has been set, pass SRID + if (bidRequest.storedAuctionResponse) { + utils.deepSetValue(data.imp[0], 'ext.prebid.storedauctionresponse.id', bidRequest.storedAuctionResponse.toString()); + } + + // set ext.prebid.auctiontimestamp using auction time + utils.deepSetValue(data.imp[0], 'ext.prebid.auctiontimestamp', bidderRequest.auctionStart); + return { method: 'POST', url: VIDEO_ENDPOINT, @@ -433,7 +518,6 @@ export const spec = { 'x_source.tid': bidRequest.transactionId, 'x_source.pchain': params.pchain, 'p_screen_res': _getScreenResolution(), - 'kw': Array.isArray(params.keywords) ? params.keywords.join(',') : '', 'tk_user_key': params.userId, 'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4), 'p_geo.longitude': isNaN(parseFloat(longitude)) ? undefined : parseFloat(longitude).toFixed(4), @@ -441,6 +525,16 @@ export const spec = { 'rf': _getPageUrl(bidRequest, bidderRequest) }; + // If floors module is enabled and we get USD floor back, send it in rp_hard_floor else undfined + if (typeof bidRequest.getFloor === 'function' && !config.getConfig('rubicon.disableFloors')) { + let floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: '*' + }); + data['rp_hard_floor'] = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined; + } + // add p_pos only if specified and valid // For SRA we need to explicitly put empty semi colons so AE treats it as empty, instead of copying the latter value data['p_pos'] = (params.position === 'atf' || params.position === 'btf') ? params.position : ''; @@ -457,6 +551,11 @@ export const spec = { data['tg_v.LIseg'] = bidRequest.userId.lipb.segments.join(','); } } + + // support identityLink (aka LiveRamp) + if (bidRequest.userId.idl_env) { + data['x_liverampidl'] = bidRequest.userId.idl_env; + } } if (bidderRequest.gdprConsent) { @@ -472,21 +571,47 @@ export const spec = { } // visitor properties - if (params.visitor !== null && typeof params.visitor === 'object') { - Object.keys(params.visitor).forEach((key) => { - if (params.visitor[key] != null) { - data[`tg_v.${key}`] = params.visitor[key].toString(); // initialize array; - } - }); - } + const visitorData = Object.assign({}, params.visitor, config.getConfig('fpd.user')); + Object.keys(visitorData).forEach((key) => { + if (visitorData[key] != null && key !== 'keywords') { + data[`tg_v.${key}`] = typeof visitorData[key] === 'object' && !Array.isArray(visitorData[key]) + ? JSON.stringify(visitorData[key]) + : visitorData[key].toString(); // initialize array; + } + }); // inventory properties - if (params.inventory !== null && typeof params.inventory === 'object') { - Object.keys(params.inventory).forEach((key) => { - if (params.inventory[key] != null) { - data[`tg_i.${key}`] = params.inventory[key].toString(); - } - }); + const inventoryData = Object.assign({}, params.inventory, config.getConfig('fpd.context')); + Object.keys(inventoryData).forEach((key) => { + if (inventoryData[key] != null && key !== 'keywords') { + data[`tg_i.${key}`] = typeof inventoryData[key] === 'object' && !Array.isArray(inventoryData[key]) + ? JSON.stringify(inventoryData[key]) + : inventoryData[key].toString(); + } + }); + + // keywords + const keywords = (params.keywords || []).concat( + utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || [], + utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || []); + data.kw = Array.isArray(keywords) && keywords.length ? keywords.join(',') : ''; + + /** + * Prebid AdSlot + * @type {(string|undefined)} + */ + const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + if (typeof pbAdSlot === 'string' && pbAdSlot) { + data['tg_i.pbadslot'] = pbAdSlot.replace(/^\/+/, ''); + } + + /** + * GAM Ad Unit + * @type {(string|undefined)} + */ + const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot'); + if (typeof gamAdUnit === 'string' && gamAdUnit) { + data['tg_i.dfp_ad_unit_code'] = gamAdUnit.replace(/^\/+/, ''); } // digitrust properties @@ -586,7 +711,7 @@ export const spec = { bidObject.adserverTargeting = extPrebidTargeting; } - // try to get cache values from 'response.ext.prebid.cache' + // try to get cache values from 'response.ext.prebid.cache.js' // else try 'bid.ext.prebid.targeting' as fallback if (bid.ext.prebid.cache && typeof bid.ext.prebid.cache.vastXml === 'object' && bid.ext.prebid.cache.vastXml.cacheId && bid.ext.prebid.cache.vastXml.url) { bidObject.videoCacheKey = bid.ext.prebid.cache.vastXml.cacheId; diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js index eccc3aa1f8a..98b3b86671c 100644 --- a/modules/s2sTesting.js +++ b/modules/s2sTesting.js @@ -1,5 +1,5 @@ -import { config } from '../src/config'; -import { setS2STestingModule } from '../src/adapterManager'; +import { config } from '../src/config.js'; +import { setS2STestingModule } from '../src/adapterManager.js'; let s2sTesting = {}; diff --git a/modules/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js new file mode 100755 index 00000000000..0e53d2a300d --- /dev/null +++ b/modules/saambaaBidAdapter.js @@ -0,0 +1,401 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const ADAPTER_VERSION = '1.0'; +const BIDDER_CODE = 'saambaa'; + +export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip']; +export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; + +let pubid = ''; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid(bidRequest) { + if (typeof bidRequest != 'undefined') { + if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } + if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } + return true; + } else { return false; } + }, + + buildRequests(bids, bidderRequest) { + let requests = []; + let videoBids = bids.filter(bid => isVideoBidValid(bid)); + let bannerBids = bids.filter(bid => isBannerBidValid(bid)); + videoBids.forEach(bid => { + pubid = getVideoBidParam(bid, 'pubid'); + requests.push({ + method: 'POST', + url: VIDEO_ENDPOINT + pubid, + data: createVideoRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + + bannerBids.forEach(bid => { + pubid = getBannerBidParam(bid, 'pubid'); + + requests.push({ + method: 'POST', + url: BANNER_ENDPOINT + pubid, + data: createBannerRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + return requests; + }, + + interpretResponse(serverResponse, {bidRequest}) { + let response = serverResponse.body; + if (response !== null && utils.isEmpty(response) == false) { + if (isVideoBid(bidRequest)) { + let bidResponse = { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: VIDEO, + netRevenue: true + } + + if (response.seatbid[0].bid[0].adm) { + bidResponse.vastXml = response.seatbid[0].bid[0].adm; + bidResponse.adResponse = { + content: response.seatbid[0].bid[0].adm + }; + } else { + bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; + } + + return bidResponse; + } else { + return { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ad: response.seatbid[0].bid[0].adm, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: BANNER, + netRevenue: true + } + } + } + } +}; + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isVideoBidValid(bid) { + return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); +} + +function isBannerBidValid(bid) { + return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); +} + +function getVideoBidParam(bid, key) { + return utils.deepAccess(bid, 'params.video.' + key) || utils.deepAccess(bid, 'params.' + key); +} + +function getBannerBidParam(bid, key) { + return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key); +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function getDoNotTrack() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} + +function findAndFillParam(o, key, value) { + try { + if (typeof value === 'function') { + o[key] = value(); + } else { + o[key] = value; + } + } catch (ex) {} +} + +function getOsVersion() { + let clientStrings = [ + { s: 'Android', r: /Android/ }, + { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, + { s: 'Mac OS X', r: /Mac OS X/ }, + { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: 'Linux', r: /(Linux|X11)/ }, + { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, + { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, + { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, + { s: 'Windows Vista', r: /Windows NT 6.0/ }, + { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, + { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, + { s: 'UNIX', r: /UNIX/ }, + { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } + ]; + let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); + return cs ? cs.s : 'unknown'; +} + +function getFirstSize(sizes) { + return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; +} + +function parseSizes(sizes) { + return utils.parseSizesInput(sizes).map(size => { + let [ width, height ] = size.split('x'); + return { + w: parseInt(width, 10) || undefined, + h: parseInt(height, 10) || undefined + }; + }); +} + +function getVideoSizes(bid) { + return parseSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +function getBannerSizes(bid) { + return parseSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +function getVideoTargetingParams(bid) { + return Object.keys(Object(bid.params.video)) + .filter(param => includes(VIDEO_TARGETING, param)) + .reduce((obj, param) => { + obj[ param ] = bid.params.video[ param ]; + return obj; + }, {}); +} + +function createVideoRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + let paramSize = getVideoBidParam(bid, 'size'); + let sizes = []; + + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getVideoSizes(bid); + } + const firstSize = getFirstSize(sizes); + + let video = getVideoTargetingParams(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1, + 'os': getOsVersion() + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getVideoBidParam(bid, 'placement'); + let floor = getVideoBidParam(bid, 'floor'); + if (floor == null) { floor = 0.5; } + + for (let j = 0; j < sizes.length; j++) { + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'video': Object.assign({ + 'id': utils.generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h, + 'mimes': DEFAULT_MIMES + }, video) + + }); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} + +function getTopWindowLocation(bidderRequest) { + let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + return utils.parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); +} + +function createBannerRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + + let paramSize = getBannerBidParam(bid, 'size'); + let sizes = []; + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getBannerSizes(bid); + } + + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1 + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getBannerBidParam(bid, 'placement'); + for (let j = 0; j < sizes.length; j++) { + let size = sizes[j]; + + let floor = getBannerBidParam(bid, 'floor'); + if (floor == null) { floor = 0.1; } + + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'banner': { + 'id': utils.generateUUID(), + 'pos': 0, + 'w': size['w'], + 'h': size['h'] + } + }); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} +registerBidder(spec); diff --git a/modules/saambaaBidAdapter.md b/modules/saambaaBidAdapter.md new file mode 100755 index 00000000000..2d391da7628 --- /dev/null +++ b/modules/saambaaBidAdapter.md @@ -0,0 +1,69 @@ +# Overview + +``` +Module Name: Saambaa Bidder Adapter +Module Type: Bidder Adapter +Maintainer: matt.voigt@saambaa.com +``` + +# Description + +Connects to Saambaa exchange for bids. + +Saambaa bid adapter supports Banner and Video ads currently. + +For more informatio + +# Sample Display Ad Unit: For Publishers +```javascript + +var displayAdUnit = [ +{ + code: 'display', + mediaTypes: { + banner: { + sizes: [[300, 250],[320, 50]] + } + } + bids: [{ + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: '320x50' + } + }] +}]; +``` + +# Sample Video Ad Unit: For Publishers +```javascript + +var videoAdUnit = { + code: 'video', + sizes: [320,480], + mediaTypes: { + video: { + playerSize : [[320, 480]], + context: 'instream' + } + }, + bids: [ + { + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: "320x480", + video: { + id: 123, + skip: 1, + mimes : ['video/mp4', 'application/javascript'], + playbackmethod : [2,6], + maxduration: 30 + } + } + } + ] + }; +``` \ No newline at end of file diff --git a/modules/scaleableAnalyticsAdapter.js b/modules/scaleableAnalyticsAdapter.js index c875dab7e18..955a08c065a 100644 --- a/modules/scaleableAnalyticsAdapter.js +++ b/modules/scaleableAnalyticsAdapter.js @@ -1,14 +1,23 @@ /* COPYRIGHT SCALEABLE LLC 2019 */ -import { ajax } from '../src/ajax'; +import { ajax } from '../src/ajax.js'; import CONSTANTS from '../src/constants.json'; -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; -import * as utils from '../src/utils'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; + +// Object.entries polyfill +const entries = Object.entries || function(obj) { + const ownProps = Object.keys(obj); + let i = ownProps.length; + let resArray = new Array(i); // preallocate the Array + while (i--) { resArray[i] = [ownProps[i], obj[ownProps[i]]]; } + + return resArray; +}; const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; const AUCTION_INIT = CONSTANTS.EVENTS.AUCTION_INIT; -const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; const BID_WON = CONSTANTS.EVENTS.BID_WON; const AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; @@ -35,9 +44,6 @@ let scaleableAnalytics = Object.assign({}, case BID_WON: onBidWon(args); break; - case BID_RESPONSE: - onBidResponse(args); - break; case BID_TIMEOUT: onBidTimeout(args); break; @@ -70,22 +76,93 @@ const sendDataToServer = data => ajax(URL, () => {}, JSON.stringify(data)); const onAuctionInit = args => { const config = scaleableAnalytics.config || {options: {}}; - for (let idx = args.adUnitCodes.length; idx--;) { - const data = { - event: 'request', - site: config.options.site, - adunit: args.adUnitCodes[idx] - }; + let adunitObj = {}; + let adunits = []; + + // Loop through adunit codes first + args.adUnitCodes.forEach((code) => { + adunitObj[code] = [{ + bidder: 'scaleable_adunit_request' + }] + }); + + // Loop through bidder requests and bids + args.bidderRequests.forEach((bidderObj) => { + bidderObj.bids.forEach((bidObj) => { + adunitObj[bidObj.adUnitCode].push({ + bidder: bidObj.bidder, + params: bidObj.params + }) + }); + }); + + entries(adunitObj).forEach(([adunitCode, bidRequests]) => { + adunits.push({ + code: adunitCode, + bidRequests: bidRequests + }); + }); - sendDataToServer(data); + const data = { + event: 'request', + site: config.options.site, + adunits: adunits } + + sendDataToServer(data); } // Handle all events besides requests and wins const onAuctionEnd = args => { - for (let adunit in auctionData) { - sendDataToServer(auctionData[adunit]); + const config = scaleableAnalytics.config || {options: {}}; + + let adunitObj = {}; + let adunits = []; + + // Add Bids Received + args.bidsReceived.forEach((bidObj) => { + if (!adunitObj[bidObj.adUnitCode]) { adunitObj[bidObj.adUnitCode] = []; } + + adunitObj[bidObj.adUnitCode].push({ + bidder: bidObj.bidderCode || bidObj.bidder, + cpm: bidObj.cpm, + currency: bidObj.currency, + dealId: bidObj.dealId, + type: bidObj.mediaType, + ttr: bidObj.timeToRespond, + size: bidObj.size + }); + }); + + // Add in other data (timeouts) as we push to adunits + entries(adunitObj).forEach(([adunitCode, bidsReceived]) => { + const bidData = bidsReceived.concat(auctionData[adunitCode] || []); + adunits.push({ + code: adunitCode, + bidData: bidData + }); + + delete auctionData[adunitCode]; + }); + + // Add in any missed auction data + entries(auctionData).forEach(([adunitCode, bidData]) => { + adunits.push({ + code: adunitCode, + bidData: bidData + }) + }); + + const data = { + event: 'bids', + site: config.options.site, + adunits: adunits } + + if (adunits.length) { sendDataToServer(data); } + + // Reset auctionData + auctionData = {} } // Bid Win Events occur after auction end @@ -98,51 +175,24 @@ const onBidWon = args => { adunit: args.adUnitCode, code: args.bidderCode, cpm: args.cpm, - ttr: args.timeToRespond + ttr: args.timeToRespond, + params: args.params }; sendDataToServer(data); } -const onBidResponse = args => { - const config = scaleableAnalytics.config || {options: {}}; - - if (!auctionData[args.adUnitCode]) { - auctionData[args.adUnitCode] = { - event: 'bids', - bids: [], - adunit: args.adUnitCode, - site: config.options.site - }; - } - - const currBidData = { - code: args.bidderCode, - cpm: args.cpm, - ttr: args.timeToRespond - }; - - auctionData[args.adUnitCode].bids.push(currBidData); -} - const onBidTimeout = args => { - const config = scaleableAnalytics.config || {options: {}}; - - for (let i = args.length; i--;) { - let currObj = args[i]; - + args.forEach(currObj => { if (!auctionData[currObj.adUnitCode]) { - auctionData[currObj.adUnitCode] = { - event: 'bids', - bids: [], - timeouts: [], - adunit: currObj.adUnitCode, - site: config.options.site - }; + auctionData[currObj.adUnitCode] = [] } - auctionData[currObj.adUnitCode].timeouts.push(currObj.bidder); - } + auctionData[currObj.adUnitCode].push({ + timeouts: 1, + bidder: currObj.bidder + }); + }); } adapterManager.registerAnalyticsAdapter({ diff --git a/modules/schain.js b/modules/schain.js index 45850aabc3d..d409e74df48 100644 --- a/modules/schain.js +++ b/modules/schain.js @@ -1,6 +1,6 @@ -import {config} from '../src/config'; -import {getGlobal} from '../src/prebidGlobal'; -import { isNumber, isStr, isArray, isPlainObject, hasOwn, logError, isInteger, _each } from '../src/utils'; +import { config } from '../src/config.js'; +import adapterManager from '../src/adapterManager.js'; +import { isNumber, isStr, isArray, isPlainObject, hasOwn, logError, isInteger, _each, logWarn } from '../src/utils.js'; // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md @@ -19,139 +19,165 @@ _each(MODE, mode => MODES.push(mode)); // validate the supply chain object export function isSchainObjectValid(schainObject, returnOnError) { + let failPrefix = 'Detected something wrong within an schain config:' + let failMsg = ''; + + function appendFailMsg(msg) { + failMsg += '\n' + msg; + } + + function printFailMsg() { + if (returnOnError === true) { + logError(failPrefix, schainObject, failMsg); + } else { + logWarn(failPrefix, schainObject, failMsg); + } + } + if (!isPlainObject(schainObject)) { - logError(schainErrorPrefix + `schain` + shouldBeAnObject); + appendFailMsg(`schain.config` + shouldBeAnObject); + printFailMsg(); if (returnOnError) return false; } // complete: Integer if (!isNumber(schainObject.complete) || !isInteger(schainObject.complete)) { - logError(schainErrorPrefix + `schain.complete` + shouldBeAnInteger); - if (returnOnError) return false; + appendFailMsg(`schain.config.complete` + shouldBeAnInteger); } // ver: String if (!isStr(schainObject.ver)) { - logError(schainErrorPrefix + `schain.ver` + shouldBeAString); - if (returnOnError) return false; + appendFailMsg(`schain.config.ver` + shouldBeAString); } // ext: Object [optional] if (hasOwn(schainObject, 'ext')) { if (!isPlainObject(schainObject.ext)) { - logError(schainErrorPrefix + `schain.ext` + shouldBeAnObject); - if (returnOnError) return false; + appendFailMsg(`schain.config.ext` + shouldBeAnObject); } } // nodes: Array of objects - let isEachNodeIsValid = true; if (!isArray(schainObject.nodes)) { - logError(schainErrorPrefix + `schain.nodes` + shouldBeAnArray); + appendFailMsg(`schain.config.nodes` + shouldBeAnArray); + printFailMsg(); if (returnOnError) return false; } else { - schainObject.nodes.forEach(node => { + schainObject.nodes.forEach((node, index) => { // asi: String if (!isStr(node.asi)) { - isEachNodeIsValid = isEachNodeIsValid && false; - logError(schainErrorPrefix + `schain.nodes[].asi` + shouldBeAString); + appendFailMsg(`schain.config.nodes[${index}].asi` + shouldBeAString); } // sid: String if (!isStr(node.sid)) { - isEachNodeIsValid = isEachNodeIsValid && false; - logError(schainErrorPrefix + `schain.nodes[].sid` + shouldBeAString); + appendFailMsg(`schain.config.nodes[${index}].sid` + shouldBeAString); } // hp: Integer if (!isNumber(node.hp) || !isInteger(node.hp)) { - isEachNodeIsValid = isEachNodeIsValid && false; - logError(schainErrorPrefix + `schain.nodes[].hp` + shouldBeAnInteger); + appendFailMsg(`schain.config.nodes[${index}].hp` + shouldBeAnInteger); } // rid: String [Optional] if (hasOwn(node, 'rid')) { if (!isStr(node.rid)) { - isEachNodeIsValid = isEachNodeIsValid && false; - logError(schainErrorPrefix + `schain.nodes[].rid` + shouldBeAString); + appendFailMsg(`schain.config.nodes[${index}].rid` + shouldBeAString); } } // name: String [Optional] if (hasOwn(node, 'name')) { if (!isStr(node.name)) { - isEachNodeIsValid = isEachNodeIsValid && false; - logError(schainErrorPrefix + `schain.nodes[].name` + shouldBeAString); + appendFailMsg(`schain.config.nodes[${index}].name` + shouldBeAString); } } // domain: String [Optional] if (hasOwn(node, 'domain')) { if (!isStr(node.domain)) { - isEachNodeIsValid = isEachNodeIsValid && false; - logError(schainErrorPrefix + `schain.nodes[].domain` + shouldBeAString); + appendFailMsg(`schain.config.nodes[${index}].domain` + shouldBeAString); } } // ext: Object [Optional] if (hasOwn(node, 'ext')) { if (!isPlainObject(node.ext)) { - isEachNodeIsValid = isEachNodeIsValid && false; - logError(schainErrorPrefix + `schain.nodes[].ext` + shouldBeAnObject); + appendFailMsg(`schain.config.nodes[${index}].ext` + shouldBeAnObject); } } }); } - if (returnOnError && !isEachNodeIsValid) { - return false; + if (failMsg.length > 0) { + printFailMsg(); + if (returnOnError) { + return false; + } } return true; } -export function copySchainObjectInAdunits(adUnits, schainObject) { - // copy schain object in all adUnits as adUnits[].bid.schain - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - bid.schain = schainObject; - }); - }); -} - export function isValidSchainConfig(schainObject) { if (schainObject === undefined) { return false; } if (!isPlainObject(schainObject)) { - logError(schainErrorPrefix + 'schain config will not be passed to bidders as schain is not an object.'); + logError(schainErrorPrefix + 'the following schain config will not be used as schain is not an object.', schainObject); return false; } return true; } -export function init(config) { +function resolveSchainConfig(schainObject, bidder) { let mode = MODE.STRICT; - getGlobal().requestBids.before(function(fn, reqBidsConfigObj) { - let schainObject = config.getConfig('schain'); - if (isValidSchainConfig(schainObject)) { - if (isStr(schainObject.validation) && MODES.indexOf(schainObject.validation) != -1) { - mode = schainObject.validation; - } - if (mode === MODE.OFF) { - // no need to validate - copySchainObjectInAdunits(reqBidsConfigObj.adUnits || getGlobal().adUnits, schainObject.config); + + if (isValidSchainConfig(schainObject)) { + if (isStr(schainObject.validation) && MODES.indexOf(schainObject.validation) != -1) { + mode = schainObject.validation; + } + if (mode === MODE.OFF) { + // no need to validate + return schainObject.config; + } else { + // if strict mode and config is invalid, reject config + throw error; otherwise allow config to go through + if (isSchainObjectValid(schainObject.config, !!(mode === MODE.STRICT))) { + return schainObject.config; } else { - if (isSchainObjectValid(schainObject.config, mode === MODE.STRICT)) { - copySchainObjectInAdunits(reqBidsConfigObj.adUnits || getGlobal().adUnits, schainObject.config); - } else { - logError(schainErrorPrefix + 'schain config will not be passed to bidders as it is not valid.'); - } + logError(schainErrorPrefix + `due to the 'strict' validation setting, this schain config will not be passed to bidder '${bidder}'. See above error for details.`); } } - // calling fn allows prebid to continue processing - return fn.call(this, reqBidsConfigObj); - }, 40); + } + return null; +} + +export function makeBidRequestsHook(fn, bidderRequests) { + function getSchainForBidder(bidder) { + let bidderSchain = bidderConfigs[bidder] && bidderConfigs[bidder].schain; + return bidderSchain || globalSchainConfig; + } + + const globalSchainConfig = config.getConfig('schain'); + const bidderConfigs = config.getBidderConfig(); + + bidderRequests.forEach(bidderRequest => { + let bidder = bidderRequest.bidderCode; + let schainConfig = getSchainForBidder(bidder); + + bidderRequest.bids.forEach(bid => { + let result = resolveSchainConfig(schainConfig, bidder); + if (result) { + bid.schain = result; + } + }); + }); + + fn(bidderRequests); +} + +export function init() { + adapterManager.makeBidRequests.after(makeBidRequestsHook); } -init(config) +init() diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js new file mode 100755 index 00000000000..d85ae856317 --- /dev/null +++ b/modules/seedingAllianceBidAdapter.js @@ -0,0 +1,225 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { NATIVE } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'seedingAlliance'; +const DEFAULT_CUR = 'EUR'; +const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=nativendo'; + +const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; + +const NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + + body: { + id: 1, + name: 'data', + type: 2 + }, + + sponsoredBy: { + id: 2, + name: 'data', + type: 1 + }, + + image: { + id: 3, + type: 3, + name: 'img' + }, + + cta: { + id: 4, + type: 12, + name: 'data' + }, + + icon: { + id: 5, + type: 1, + name: 'img' + } +}; + +export const spec = { + code: BIDDER_CODE, + + supportedMediaTypes: [NATIVE], + + isBidRequestValid: function(bid) { + return !!bid.params.adUnitId; + }, + + buildRequests: (validBidRequests, bidderRequest) => { + const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; + const tid = validBidRequests[0].transactionId; + const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; + let url = bidderRequest.refererInfo.referer; + + const imp = validBidRequests.map((bid, id) => { + const assets = utils._map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + + const asset = { + required: bidParams.required & 1 + }; + + if (props) { + asset.id = props.id; + + let w, h; + + if (bidParams.sizes) { + w = bidParams.sizes[0]; + h = bidParams.sizes[1]; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + w, + h + }; + + return asset; + } + }) + .filter(Boolean); + + if (bid.params.url) { + url = bid.params.url; + } + + return { + id: String(id + 1), + tagid: bid.params.adUnitId, + tid: tid, + pt: pt, + native: { + request: { + assets + } + } + }; + }); + + const request = { + id: bidderRequest.auctionId, + site: { + page: url + }, + device: { + ua: navigator.userAgent + }, + cur, + imp, + user: {}, + regs: { + ext: { + gdpr: 0 + } + } + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + utils.deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(request), + options: { + contentType: 'application/json' + }, + bids: validBidRequests + }; + }, + + interpretResponse: function(serverResponse, { bids }) { + if (utils.isEmpty(serverResponse.body)) { + return []; + } + + const { seatbid, cur } = serverResponse.body; + + const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + result[bid.impid - 1] = bid; + return result; + }, []); + + return bids + .map((bid, id) => { + const bidResponse = bidResponses[id]; + + if (bidResponse) { + return { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: 1000, + netRevenue: bid.netRevenue === 'net', + currency: cur, + mediaType: NATIVE, + bidderCode: BIDDER_CODE, + native: parseNative(bidResponse) + }; + } + }) + .filter(Boolean); + } +}; + +registerBidder(spec); + +function parseNative(bid) { + const {assets, link, imptrackers} = bid.adm.native; + + link.clicktrackers.forEach(function (clicktracker, index) { + link.clicktrackers[index] = clicktracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); + }); + + imptrackers.forEach(function (imptracker, index) { + imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); + }); + + const result = { + url: link.url, + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined + }; + + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = utils.deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} diff --git a/modules/seedingAllianceBidAdapter.md b/modules/seedingAllianceBidAdapter.md new file mode 100755 index 00000000000..c933e53695b --- /dev/null +++ b/modules/seedingAllianceBidAdapter.md @@ -0,0 +1,45 @@ +# Overview +Module Name: Seeding Alliance Bidder Adapter +Type: Seeding Alliance Adapter +Maintainer: tech@seeding-alliance.de + +# Description +Seeding Alliance Bidder Adapter for Prebid.js. + +# Test Parameters +``` +var adUnits = [{ + code: 'test-div', + + mediaTypes: { + native: { + title: { + required: true, + len: 50 + }, + body: { + required: true, + len: 350 + }, + url: { + required: true + }, + image: { + required: true, + sizes : [300, 175] + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'seedingAlliance', + params: { + url : "https://mockup.seeding-alliance.de/ssp-testing/native.html", + adUnitId: "2sq2o" + } + }] +}]; +``` + diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 845d8ffec9d..018339fabe4 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils' -import { registerBidder } from '../src/adapters/bidderFactory' -import { VIDEO, BANNER } from '../src/mediaTypes' +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { VIDEO, BANNER } from '../src/mediaTypes.js' const BIDDER_CODE = 'seedtag'; const SEEDTAG_ALIAS = 'st'; diff --git a/modules/segmentoBidAdapter.js b/modules/segmentoBidAdapter.js new file mode 100644 index 00000000000..a042bdf4942 --- /dev/null +++ b/modules/segmentoBidAdapter.js @@ -0,0 +1,85 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'segmento'; +const URL = 'https://prebid-bidder.rutarget.ru/bid'; +const SYNC_IFRAME_URL = 'https://tag.rutarget.ru/tag?event=otherPage&check=true&response=syncframe&synconly=true'; +const SYNC_IMAGE_URL = 'https://tag.rutarget.ru/tag?event=otherPage&check=true&synconly=true'; +const RUB = 'RUB'; +const TIME_TO_LIVE = 0; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: (bid) => { + return Boolean(bid && bid.params && !isNaN(bid.params.placementId)); + }, + buildRequests: (validBidRequests, bidderRequest) => { + const payload = { + places: [], + settings: { + currency: RUB, + referrer: bidderRequest.refererInfo && bidderRequest.refererInfo.referer + } + }; + + for (let i = 0; i < validBidRequests.length; i++) { + const bid = validBidRequests[i]; + + payload.places.push({ + id: bid.bidId, + placementId: bid.params.placementId, + sizes: bid.sizes + }); + } + + return { + method: 'POST', + url: URL, + data: payload + }; + }, + interpretResponse: (serverResponse) => { + const bids = serverResponse.body && serverResponse.body.bids; + if (!bids) { + return []; + } + + const bidResponses = []; + + for (let i = 0; i < bids.length; i++) { + const bid = bids[i]; + + bidResponses.push({ + requestId: bid.id, + cpm: bid.cpm, + width: bid.size.width, + height: bid.size.height, + creativeId: bid.creativeId, + currency: RUB, + netRevenue: true, + ttl: TIME_TO_LIVE, + adUrl: bid.displayUrl + }); + } + + return bidResponses; + }, + getUserSyncs: (syncOptions) => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: SYNC_IFRAME_URL + }]; + } + + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: SYNC_IMAGE_URL + }]; + } + + return []; + } +}; + +registerBidder(spec); diff --git a/modules/segmentoBidAdapter.md b/modules/segmentoBidAdapter.md new file mode 100644 index 00000000000..e64153195c5 --- /dev/null +++ b/modules/segmentoBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: Segmento Bidder Adapter +Module Type: Bidder Adapter +Maintainer: ssp@segmento.ru +``` + +# Description + +Module that connects to Segmento's demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[240,400],[160,600]], + } + }, + bids: [ + { + bidder: 'segmento', + params: { + placementId: -1 + } + } + ] + } +]; +``` diff --git a/modules/sekindoUMBidAdapter.js b/modules/sekindoUMBidAdapter.js index ad4da7a06cb..bea25173747 100644 --- a/modules/sekindoUMBidAdapter.js +++ b/modules/sekindoUMBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; export const spec = { code: 'sekindoUM', supportedMediaTypes: ['banner', 'video'], diff --git a/modules/serverbidBidAdapter.md b/modules/serverbidBidAdapter.md deleted file mode 100644 index 87b51e665e2..00000000000 --- a/modules/serverbidBidAdapter.md +++ /dev/null @@ -1,44 +0,0 @@ -# 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/sharedIdSystem.js b/modules/sharedIdSystem.js new file mode 100644 index 00000000000..5c2a3df0595 --- /dev/null +++ b/modules/sharedIdSystem.js @@ -0,0 +1,333 @@ +/** + * This module adds Shared ID support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/sharedIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; + +const MODULE_NAME = 'sharedId'; +const ID_SVC = 'https://id.sharedid.org/id'; +const DEFAULT_24_HOURS = 86400; +const OPT_OUT_VALUE = '00000000000000000000000000'; +// These values should NEVER change. If +// they do, we're no longer making ulids! +const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; // Crockford's Base32 +const ENCODING_LEN = ENCODING.length; +const TIME_MAX = Math.pow(2, 48) - 1; +const TIME_LEN = 10; +const RANDOM_LEN = 16; +const id = factory(); +/** + * Constructs cookie value + * @param value + * @param needsSync + * @returns {string} + */ +function constructCookieValue(value, needsSync) { + const cookieValue = {}; + cookieValue.id = value; + cookieValue.ts = utils.timestamp(); + if (needsSync) { + cookieValue.ns = true; + } + utils.logInfo('SharedId: cookie Value: ' + JSON.stringify(cookieValue)); + return cookieValue; +} + +/** + * Checks if id needs to be synced + * @param configParams + * @param storedId + * @returns {boolean} + */ +function isIdSynced(configParams, storedId) { + const needSync = storedId.ns; + if (needSync) { + return true; + } + if (!configParams || typeof configParams.syncTime !== 'number') { + utils.logInfo('SharedId: Sync time is not configured or is not a number'); + } + let syncTime = (!configParams || typeof configParams.syncTime !== 'number') ? DEFAULT_24_HOURS : configParams.syncTime; + if (syncTime > DEFAULT_24_HOURS) { + syncTime = DEFAULT_24_HOURS; + } + const cookieTimestamp = storedId.ts; + if (cookieTimestamp) { + var secondBetweenTwoDate = timeDifferenceInSeconds(utils.timestamp(), cookieTimestamp); + return secondBetweenTwoDate >= syncTime; + } + return false; +} + +/** + * Gets time difference in secounds + * @param date1 + * @param date2 + * @returns {number} + */ +function timeDifferenceInSeconds(date1, date2) { + const diff = (date1 - date2) / 1000; + return Math.abs(Math.round(diff)); +} + +/** + * id generation call back + * @param result + * @param callback + * @returns {{success: success, error: error}} + */ +function idGenerationCallback(callback) { + return { + success: function (responseBody) { + let value = {}; + if (responseBody) { + try { + let responseObj = JSON.parse(responseBody); + utils.logInfo('SharedId: Generated SharedId: ' + responseObj.sharedId); + value = constructCookieValue(responseObj.sharedId, false); + } catch (error) { + utils.logError(error); + } + } + callback(value); + }, + error: function (statusText, responseBody) { + const value = constructCookieValue(id(), true); + utils.logInfo('SharedId: Ulid Generated SharedId: ' + value.id); + callback(value); + } + } +} + +/** + * existing id generation call back + * @param result + * @param callback + * @returns {{success: success, error: error}} + */ +function existingIdCallback(storedId, callback) { + return { + success: function (responseBody) { + utils.logInfo('SharedId: id to be synced: ' + storedId.id); + if (responseBody) { + try { + let responseObj = JSON.parse(responseBody); + storedId = constructCookieValue(responseObj.sharedId, false); + utils.logInfo('SharedId: Older SharedId: ' + storedId.id); + } catch (error) { + utils.logError(error); + } + } + callback(storedId); + }, + error: function () { + utils.logInfo('SharedId: Sync error for id : ' + storedId.id); + callback(storedId); + } + } +} + +/** + * Encode the id + * @param value + * @returns {string|*} + */ +function encodeId(value) { + const result = {}; + const sharedId = (value && typeof value['id'] === 'string') ? value['id'] : undefined; + if (sharedId == OPT_OUT_VALUE) { + return undefined; + } + if (sharedId) { + const bidIds = { + id: sharedId, + } + const ns = (value && typeof value['ns'] === 'boolean') ? value['ns'] : undefined; + if (ns == undefined) { + bidIds.third = sharedId; + } + result.sharedid = bidIds; + utils.logInfo('SharedId: Decoded value ' + JSON.stringify(result)); + return result; + } + return sharedId; +} + +/** + * the factory to generate unique identifier based on time and current pseudorandom number + * @param {string} the current pseudorandom number generator + * @returns {function(*=): *} + */ +function factory(currPrng) { + if (!currPrng) { + currPrng = detectPrng(); + } + return function ulid(seedTime) { + if (isNaN(seedTime)) { + seedTime = Date.now(); + } + return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN, currPrng); + }; +} + +/** + * creates and logs the error message + * @function + * @param {string} error message + * @returns {Error} + */ +function createError(message) { + utils.logError(message); + const err = new Error(message); + err.source = 'sharedId'; + return err; +} + +/** + * gets a a random charcter from generated pseudorandom number + * @param {string} the generated pseudorandom number + * @returns {string} + */ +function randomChar(prng) { + let rand = Math.floor(prng() * ENCODING_LEN); + if (rand === ENCODING_LEN) { + rand = ENCODING_LEN - 1; + } + return ENCODING.charAt(rand); +} + +/** + * encodes the time based on the length + * @param now + * @param len + * @returns {string} encoded time. + */ +function encodeTime (now, len) { + if (isNaN(now)) { + throw new Error(now + ' must be a number'); + } + + if (Number.isInteger(now) === false) { + throw createError('time must be an integer'); + } + + if (now > TIME_MAX) { + throw createError('cannot encode time greater than ' + TIME_MAX); + } + if (now < 0) { + throw createError('time must be positive'); + } + + if (Number.isInteger(len) === false) { + throw createError('length must be an integer'); + } + if (len < 0) { + throw createError('length must be positive'); + } + + let mod; + let str = ''; + for (; len > 0; len--) { + mod = now % ENCODING_LEN; + str = ENCODING.charAt(mod) + str; + now = (now - mod) / ENCODING_LEN; + } + return str; +} + +/** + * encodes random character + * @param len + * @param prng + * @returns {string} + */ +function encodeRandom (len, prng) { + let str = ''; + for (; len > 0; len--) { + str = randomChar(prng) + str; + } + return str; +} + +/** + * detects the pseudorandom number generator and generates the random number + * @function + * @param {string} error message + * @returns {string} a random number + */ +function detectPrng(root) { + if (!root) { + root = typeof window !== 'undefined' ? window : null; + } + const browserCrypto = root && (root.crypto || root.msCrypto); + if (browserCrypto) { + return () => { + const buffer = new Uint8Array(1); + browserCrypto.getRandomValues(buffer); + return buffer[0] / 0xff; + }; + } + return () => Math.random(); +} + +/** @type {Submodule} */ +export const sharedIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{sharedid:{ id: string, third:string}} or undefined if value doesn't exists + */ + decode(value) { + return (value) ? encodeId(value) : undefined; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleParams} [configParams] + * @returns {sharedId} + */ + getId(configParams) { + const resp = function (callback) { + utils.logInfo('SharedId: Sharedid doesnt exists, new cookie creation'); + ajax(ID_SVC, idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + }, + + /** + * performs actions even if the id exists and returns a value + * @param configParams + * @param storedId + * @returns {{callback: *}} + */ + extendId(configParams, storedId) { + utils.logInfo('SharedId: Existing shared id ' + storedId.id); + const resp = function (callback) { + const needSync = isIdSynced(configParams, storedId); + if (needSync) { + utils.logInfo('SharedId: Existing shared id ' + storedId + ' is not synced'); + const sharedIdPayload = {}; + sharedIdPayload.sharedId = storedId.id; + const payloadString = JSON.stringify(sharedIdPayload); + ajax(ID_SVC, existingIdCallback(storedId, callback), payloadString, {method: 'POST', withCredentials: true}); + } + }; + return {callback: resp}; + } +}; + +// Register submodule for userId +submodule('userId', sharedIdSubmodule); diff --git a/modules/sharedIdSystem.md b/modules/sharedIdSystem.md new file mode 100644 index 00000000000..a4541c16c49 --- /dev/null +++ b/modules/sharedIdSystem.md @@ -0,0 +1,43 @@ +## Shared ID User ID Submodule + +Shared ID User ID Module generates a UUID that can be utilized to improve user matching.This module enables timely synchronization which handles sharedId.org optout. This module does not require any registration. + +### Building Prebid with Shared Id Support +Your Prebid build must include the modules for both **userId** and **sharedId** submodule. Follow the build instructions for Prebid as +explained in the top level README.md file of the Prebid source tree. + +ex: $ gulp build --modules=userId,sharedIdSystem + +### Prebid Params + +Individual params may be set for the Shared ID User ID Submodule. +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'sharedId', + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + name: 'sharedid', + type: 'cookie', + expires: 28 + }, + }] + } +}); +``` + +### Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Shared ID User ID Module integration. + +| Params under usersync.userIds[]| Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Shared ID module - `"sharedId"` | `"sharedId"` | +| params | Optional | Object | Details for sharedId syncing. | | +| params.syncTime | Optional | Object | Configuration to define the frequency(in seconds) of id synchronization. By default id is synchronized every 24 hours | 60 | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | +| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"sharedid"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `28` | diff --git a/modules/sharethroughAnalyticsAdapter.js b/modules/sharethroughAnalyticsAdapter.js index d32000373d2..5147b2a4275 100644 --- a/modules/sharethroughAnalyticsAdapter.js +++ b/modules/sharethroughAnalyticsAdapter.js @@ -1,6 +1,6 @@ -import adapter from '../src/AnalyticsAdapter'; -import adapterManager from '../src/adapterManager'; -const utils = require('../src/utils'); +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +const utils = require('../src/utils.js'); const emptyUrl = ''; const analyticsType = 'endpoint'; diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 68b2a23b8e4..0d183be05df 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,6 +1,7 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; -const VERSION = '3.1.0'; +const VERSION = '3.2.1'; const BIDDER_CODE = 'sharethrough'; const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; const DEFAULT_SIZE = [1, 1]; @@ -9,7 +10,8 @@ const DEFAULT_SIZE = [1, 1]; export const sharethroughInternal = { b64EncodeUnicode, handleIframe, - isLockedInFrame + isLockedInFrame, + getProtocol }; export const sharethroughAdapterSpec = { @@ -29,6 +31,9 @@ export const sharethroughAdapterSpec = { strVersion: VERSION }; + const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; + query.secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { query.consent_string = bidderRequest.gdprConsent.consentString; } @@ -37,6 +42,10 @@ export const sharethroughAdapterSpec = { query.consent_required = !!bidderRequest.gdprConsent.gdprApplies; } + if (bidderRequest && bidderRequest.uspConsent) { + query.us_privacy = bidderRequest.uspConsent + } + if (bidRequest.userId && bidRequest.userId.tdid) { query.ttduid = bidRequest.userId.tdid; } @@ -93,7 +102,8 @@ export const sharethroughAdapterSpec = { }]; }, - getUserSyncs: (syncOptions, serverResponses) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + const syncParams = uspConsent ? `&us_privacy=${uspConsent}` : ''; const syncs = []; const shouldCookieSync = syncOptions.pixelEnabled && serverResponses.length > 0 && @@ -102,7 +112,7 @@ export const sharethroughAdapterSpec = { if (shouldCookieSync) { serverResponses[0].body.cookieSyncUrls.forEach(url => { - syncs.push({ type: 'image', url: url }); + syncs.push({ type: 'image', url: url + syncParams }); }); } @@ -144,7 +154,7 @@ function generateAd(body, req) { if (req.strData.skipIframeBusting) { // Don't break out of iframe - adMarkup = adMarkup + ``; + adMarkup = adMarkup + ``; } else { // Add logic to the markup that detects whether or not in top level document is accessible // this logic will deploy sfp.js and/or iframe buster script(s) as appropriate @@ -172,7 +182,7 @@ function handleIframe () { window.document.getElementsByTagName('body')[0].appendChild(sfpIframeBusterJs); iframeBusterLoaded = true; } catch (e) { - console.error(e); + utils.logError('Trouble writing frame buster script, error details:', e); } } @@ -190,7 +200,7 @@ function handleIframe () { window.document.getElementsByTagName('body')[0].appendChild(sfpJs); } } catch (e) { - console.error(e); + utils.logError('Trouble writing sfp script, error details:', e); } } } @@ -236,4 +246,8 @@ function canAutoPlayHTML5Video() { } } +function getProtocol() { + return document.location.protocol; +} + registerBidder(sharethroughAdapterSpec); diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index 0f730b57033..d0eb8c6a589 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -1,10 +1,11 @@ -import * as utils from '../src/utils'; -import { config } from '../src/config'; -import { Renderer } from '../src/Renderer'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { VIDEO, BANNER } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { loadExternalScript } from '../src/adloader.js'; -const PROD_ENDPOINT = 'https://bs1.showheroes.com/api/v1/bid'; +const PROD_ENDPOINT = 'https://bs.showheroes.com/api/v1/bid'; const STAGE_ENDPOINT = 'https://bid-service.stage.showheroes.com/api/v1/bid'; const PROD_PUBLISHER_TAG = 'https://static.showheroes.com/publishertag.js'; const STAGE_PUBLISHER_TAG = 'https://pubtag.stage.showheroes.com/publishertag.js'; @@ -28,6 +29,7 @@ export const spec = { return !!bid.params.playerId; }, buildRequests: function(validBidRequests, bidderRequest) { + let adUnits = []; const pageURL = validBidRequests[0].params.contentPageUrl || bidderRequest.refererInfo.referer; const isStage = !!validBidRequests[0].params.stage; const isOutstream = utils.deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; @@ -36,54 +38,56 @@ export const spec = { const isNativeRender = utils.deepAccess(validBidRequests[0], 'renderer'); const outstreamOptions = utils.deepAccess(validBidRequests[0], 'params.outstreamOptions'); const isBanner = !!validBidRequests[0].mediaTypes.banner || (isOutstream && !(isCustomRender || isNativeRender || isNodeRender)); + const defaultSchain = validBidRequests[0].schain || {}; - let adUnits = validBidRequests.map((bid) => { + validBidRequests.forEach((bid) => { + const videoSizes = getVideoSizes(bid); + const bannerSizes = getBannerSizes(bid); const vpaidMode = utils.getBidIdParameter('vpaidMode', bid.params); - let sizes = bid.sizes.length === 1 ? bid.sizes[0] : bid.sizes; - if (sizes && !sizes.length) { - let mediaSize; - let mediaVideoSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - if (utils.isArray(mediaVideoSize)) { - mediaSize = mediaVideoSize; + const makeBids = (type, size) => { + let context = ''; + let streamType = 2; + + if (type === BANNER) { + streamType = 5; } else { - mediaSize = bid.mediaTypes.banner.sizes; - } - if (utils.isArray(mediaSize[0])) { - sizes = mediaSize[0]; - } else if (utils.isNumber(mediaSize[0])) { - sizes = mediaSize; + context = utils.deepAccess(bid, 'mediaTypes.video.context'); + if (vpaidMode && context === 'instream') { + streamType = 1; + } + if (context === 'outstream') { + streamType = 5; + } } - } - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - - let streamType = 2; + return { + type: streamType, + bidId: bid.bidId, + mediaType: type, + context: context, + playerId: utils.getBidIdParameter('playerId', bid.params), + auctionId: bidderRequest.auctionId, + bidderCode: BIDDER_CODE, + gdprConsent: bidderRequest.gdprConsent, + start: +new Date(), + timeout: 3000, + size: { + width: size[0], + height: size[1] + }, + params: bid.params, + schain: bid.schain || defaultSchain, + }; + }; - if (vpaidMode && context === 'instream') { - streamType = 1; - } - if (isBanner || context === 'outstream') { - streamType = 5; - } + videoSizes.forEach((size) => { + adUnits.push(makeBids(VIDEO, size)); + }); - return { - type: streamType, - bidId: bid.bidId, - mediaType: isBanner ? BANNER : VIDEO, - context: context, - playerId: utils.getBidIdParameter('playerId', bid.params), - auctionId: bidderRequest.auctionId, - bidderCode: BIDDER_CODE, - gdprConsent: bidderRequest.gdprConsent, - start: +new Date(), - timeout: 3000, - video: { - width: sizes[0], - height: sizes[1] - }, - params: bid.params, - }; + bannerSizes.forEach((size) => { + adUnits.push(makeBids(BANNER, size)); + }); }); return { @@ -93,6 +97,7 @@ export const spec = { data: { 'user': [], 'meta': { + 'adapterVersion': 2, 'pageURL': encodeURIComponent(pageURL), 'vastCacheEnabled': (!!config.getConfig('cache') && !isBanner && !outstreamOptions) || false, 'isDesktop': utils.getWindowTop().document.documentElement.clientWidth > 700, @@ -155,12 +160,12 @@ function createBids(bidRes, reqData) { bidUnit.cpm = bid.cpm; bidUnit.requestId = bid.bidId; bidUnit.currency = bid.currency; - bidUnit.mediaType = reqBid.mediaType || VIDEO; + bidUnit.mediaType = bid.mediaType || VIDEO; bidUnit.ttl = TTL; bidUnit.creativeId = 'c_' + bid.bidId; bidUnit.netRevenue = true; - bidUnit.width = bid.video.width; - bidUnit.height = bid.video.height; + bidUnit.width = bid.size.width; + bidUnit.height = bid.size.height; if (bid.vastXml) { bidUnit.vastXml = bid.vastXml; bidUnit.adResponse = { @@ -170,16 +175,16 @@ function createBids(bidRes, reqData) { if (bid.vastTag) { bidUnit.vastUrl = bid.vastTag; } - if (reqBid.mediaType === BANNER) { + if (bid.mediaType === BANNER) { bidUnit.ad = getBannerHtml(bid, reqBid, reqData); - } else if (reqBid.context === 'outstream') { + } else if (bid.context === 'outstream') { const renderer = Renderer.install({ id: bid.bidId, url: '//', config: { playerId: reqBid.playerId, - width: bid.video.width, - height: bid.video.height, + width: bid.size.width, + height: bid.size.height, vastUrl: bid.vastTag, vastXml: bid.vastXml, debug: reqData.debug, @@ -212,7 +217,7 @@ function outstreamRender(bid) { return; } - const slot = utils.getBidIdParameter('slot', bid.renderer.config); + const slot = utils.getBidIdParameter('slot', bid.renderer.config) || bid.adUnitCode; if (slot && window.document.getElementById(slot)) { window.document.getElementById(slot).appendChild(embedCode); } else if (slot) { @@ -230,12 +235,9 @@ function createOutstreamEmbedCode(bid) { const fragment = window.document.createDocumentFragment(); - const script = window.document.createElement('script'); - script.type = 'text/javascript'; - script.src = urls.pubTag; - script.onload = function () { + let script = loadExternalScript(urls.pubTag, 'outstream', function () { window.ShowheroesTag = this; - }; + }); script.setAttribute('data-player-host', urls.vlHost); const spot = window.document.createElement('div'); @@ -269,4 +271,19 @@ function getBannerHtml (bid, reqBid, reqData) { `; } +function getVideoSizes(bidRequest) { + return formatSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || []); +} + +function getBannerSizes(bidRequest) { + return formatSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []); +} + +function formatSizes(sizes) { + if (!sizes || !sizes.length) { + return [] + } + return Array.isArray(sizes[0]) ? sizes : [sizes]; +} + registerBidder(spec); diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js index 58dd3024c8a..303fbbc8995 100644 --- a/modules/sigmoidAnalyticsAdapter.js +++ b/modules/sigmoidAnalyticsAdapter.js @@ -1,11 +1,14 @@ /* 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 includes from 'core-js-pure/features/array/includes.js'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; +import adapterManager from '../src/adapterManager.js'; +import { getStorageManager } from '../src/storageManager.js'; -const utils = require('../src/utils'); +const storage = getStorageManager(); + +const utils = require('../src/utils.js'); const url = 'https://kinesis.us-east-1.amazonaws.com/'; const analyticsType = 'endpoint'; @@ -54,31 +57,31 @@ function buildSessionIdTimeoutLocalStorageKey() { function updateSessionId() { if (isSessionIdTimeoutExpired()) { let newSessionId = utils.generateUUID(); - localStorage.setItem(buildSessionIdLocalStorageKey(), newSessionId); + storage.setDataInLocalStorage(buildSessionIdLocalStorageKey(), newSessionId); } initOptions.sessionId = getSessionId(); updateSessionIdTimeout(); } function updateSessionIdTimeout() { - localStorage.setItem(buildSessionIdTimeoutLocalStorageKey(), Date.now()); + storage.setDataInLocalStorage(buildSessionIdTimeoutLocalStorageKey(), Date.now()); } function isSessionIdTimeoutExpired() { - let cpmSessionTimestamp = localStorage.getItem(buildSessionIdTimeoutLocalStorageKey()); + let cpmSessionTimestamp = storage.getDataFromLocalStorage(buildSessionIdTimeoutLocalStorageKey()); return Date.now() - cpmSessionTimestamp > sessionTimeout; } function getSessionId() { - return localStorage.getItem(buildSessionIdLocalStorageKey()) ? localStorage.getItem(buildSessionIdLocalStorageKey()) : ''; + return storage.getDataFromLocalStorage(buildSessionIdLocalStorageKey()) ? storage.getDataFromLocalStorage(buildSessionIdLocalStorageKey()) : ''; } function updateUtmTimeout() { - localStorage.setItem(buildUtmLocalStorageTimeoutKey(), Date.now()); + storage.setDataInLocalStorage(buildUtmLocalStorageTimeoutKey(), Date.now()); } function isUtmTimeoutExpired() { - let utmTimestamp = localStorage.getItem(buildUtmLocalStorageTimeoutKey()); + let utmTimestamp = storage.getDataFromLocalStorage(buildUtmLocalStorageTimeoutKey()); return (Date.now() - utmTimestamp) > utmTimeout; } @@ -219,11 +222,11 @@ sigmoidAdapter.buildUtmTagData = function () { }); utmTags.forEach(function(utmTagKey) { if (utmTagsDetected) { - localStorage.setItem(buildUtmLocalStorageKey(utmTagKey), utmTagData[utmTagKey]); + storage.setDataInLocalStorage(buildUtmLocalStorageKey(utmTagKey), utmTagData[utmTagKey]); updateUtmTimeout(); } else { if (!isUtmTimeoutExpired()) { - utmTagData[utmTagKey] = localStorage.getItem(buildUtmLocalStorageKey(utmTagKey)) ? localStorage.getItem(buildUtmLocalStorageKey(utmTagKey)) : ''; + utmTagData[utmTagKey] = storage.getDataFromLocalStorage(buildUtmLocalStorageKey(utmTagKey)) ? storage.getDataFromLocalStorage(buildUtmLocalStorageKey(utmTagKey)) : ''; updateUtmTimeout(); } } @@ -232,11 +235,14 @@ sigmoidAdapter.buildUtmTagData = function () { }; function send(eventType, data, sendDataType) { + // eslint-disable-next-line no-undef AWS.config.credentials = new AWS.Credentials({ accessKeyId: 'accesskey', secretAccessKey: 'secretkey' }); + // eslint-disable-next-line no-undef AWS.config.region = 'us-east-1'; + // eslint-disable-next-line no-undef AWS.config.credentials.get(function(err) { // attach event listener if (err) { @@ -244,6 +250,7 @@ function send(eventType, data, sendDataType) { return; } // create kinesis service object + // eslint-disable-next-line no-undef var kinesis = new AWS.Kinesis({ apiVersion: '2013-12-02' }); diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js new file mode 100644 index 00000000000..4df537e0eb3 --- /dev/null +++ b/modules/sizeMappingV2.js @@ -0,0 +1,594 @@ +/** + * This module adds support for the new size mapping spec, Advanced Size Mapping. It's documented here. https://github.com/prebid/Prebid.js/issues/4129 + * The implementation is an alternative to global sizeConfig. It introduces 'Ad Unit' & 'Bidder' level sizeConfigs and also supports 'labels' for conditional + * rendering. Read full API documentation on Prebid.org, http://prebid.org/dev-docs/modules/sizeMappingV2.html + */ + +import * as utils from '../src/utils.js'; +import { processNativeAdUnitParams } from '../src/native.js'; +import { adunitCounter } from '../src/adUnits.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { getHook } from '../src/hook.js'; +import { adUnitSetupChecks } from '../src/prebid.js'; + +// Allows for stubbing of these functions while writing unit tests. +export const internal = { + checkBidderSizeConfigFormat, + getActiveSizeBucket, + getFilteredMediaTypes, + getAdUnitDetail, + getRelevantMediaTypesForBidder, + isLabelActivated +}; + +/* + 'sizeMappingInternalStore' contains information on, whether a particular auction is using size mapping V2 (the new size mapping spec), + and it also contains additional information on each adUnit, such as, mediaTypes, activeViewport, etc. This information is required by + the 'getBids' function. +*/ + +export const sizeMappingInternalStore = createSizeMappingInternalStore(); + +function createSizeMappingInternalStore() { + const sizeMappingInternalStore = {}; + + return { + initializeStore: function (auctionId, isUsingSizeMappingBool) { + sizeMappingInternalStore[auctionId] = { + usingSizeMappingV2: isUsingSizeMappingBool, + adUnits: [] + }; + }, + getAuctionDetail: function (auctionId) { + return sizeMappingInternalStore[auctionId]; + }, + setAuctionDetail: function (auctionId, adUnitDetail) { + sizeMappingInternalStore[auctionId].adUnits.push(adUnitDetail); + } + } +} + +/* + Returns "true" if at least one of the adUnits in the adUnits array is using an Ad Unit and/or Bidder level sizeConfig, + otherwise, returns "false." +*/ +export function isUsingNewSizeMapping(adUnits) { + let isUsingSizeMappingBool = false; + adUnits.forEach(adUnit => { + if (adUnit.mediaTypes) { + // checks for the presence of sizeConfig property at the adUnit.mediaTypes object + Object.keys(adUnit.mediaTypes).forEach(mediaType => { + if (adUnit.mediaTypes[mediaType].sizeConfig) { + if (isUsingSizeMappingBool === false) { + isUsingSizeMappingBool = true; + } + } + }); + + // checks for the presence of sizeConfig property at the adUnit.bids[].bidder object + adUnit.bids.forEach(bidder => { + if (bidder.sizeConfig) { + if (isUsingSizeMappingBool === false) { + isUsingSizeMappingBool = true; + } + } + }); + } + }); + return isUsingSizeMappingBool; +} + +/** + This hooked function executes before the function 'checkAdUnitSetup', that is defined in /src/prebid.js. It's necessary to run this funtion before + because it applies a series of checks in order to determine the correctness of the 'sizeConfig' array, which, the original 'checkAdUnitSetup' function + does not recognize. + @params {Array} adUnits + @returns {Array} validateAdUnits - Unrecognized properties are deleted. +*/ +export function checkAdUnitSetupHook(adUnits) { + const validateSizeConfig = function (mediaType, sizeConfig, adUnitCode) { + let isValid = true; + const associatedProperty = { + banner: 'sizes', + video: 'playerSize', + native: 'active' + } + const propertyName = associatedProperty[mediaType]; + const conditionalLogMessages = { + banner: 'Removing mediaTypes.banner from ad unit.', + video: 'Removing mediaTypes.video.sizeConfig from ad unit.', + native: 'Removing mediaTypes.native.sizeConfig from ad unit.' + } + if (Array.isArray(sizeConfig)) { + sizeConfig.forEach((config, index) => { + const keys = Object.keys(config); + /* + Check #1 (Applies to 'banner', 'video' and 'native' media types.) + Verify that all config objects include 'minViewPort' and 'sizes' property. + If they do not, return 'false'. + */ + if (!(includes(keys, 'minViewPort') && includes(keys, propertyName))) { + utils.logError(`Ad unit ${adUnitCode}: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + return; + } + /* + Check #2 (Applies to 'banner', 'video' and 'native' media types.) + Verify that 'config.minViewPort' property is in [width, height] format. + If not, return false. + */ + if (!utils.isArrayOfNums(config.minViewPort, 2)) { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'minViewPort' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false + return; + } + /* + Check #3 (Applies only to 'banner' and 'video' media types.) + Verify that 'config.sizes' (in case of banner) or 'config.playerSize' (in case of video) + property is in [width, height] format. If not, return 'false'. + */ + if (mediaType === 'banner' || mediaType === 'video') { + let showError = false; + if (Array.isArray(config[propertyName])) { + const validatedSizes = adUnitSetupChecks.validateSizes(config[propertyName]); + if (config[propertyName].length > 0 && validatedSizes.length === 0) { + isValid = false; + showError = true; + } + } else { + // Either 'sizes' or 'playerSize' is not declared as an array, which makes it invalid by default. + isValid = false; + showError = true; + } + if (showError) { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of '${propertyName}' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + return; + } + } + /* + Check #4 (Applies only to 'native' media type) + Verify that 'config.active' is a 'boolean'. + If not, return 'false'. + */ + if (mediaType === 'native') { + if (typeof config[propertyName] !== 'boolean') { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'active' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + } + } + }); + } else { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'sizeConfig' in 'mediaTypes.${mediaType}.sizeConfig'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + return isValid; + } + + // If all checks have passed, isValid should equal 'true' + return isValid; + } + const validatedAdUnits = []; + adUnits.forEach(adUnit => { + const mediaTypes = adUnit.mediaTypes; + let validatedBanner, validatedVideo, validatedNative; + if (!mediaTypes || Object.keys(mediaTypes).length === 0) { + utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); + return; + } + if (mediaTypes.banner) { + if (mediaTypes.banner.sizes) { + // Ad unit is using 'mediaTypes.banner.sizes' instead of the new property 'sizeConfig'. Apply the old checks! + validatedBanner = adUnitSetupChecks.validateBannerMediaType(adUnit); + } else if (mediaTypes.banner.sizeConfig) { + // Ad unit is using the 'sizeConfig' property, 'mediaTypes.banner.sizeConfig'. Apply the new checks! + validatedBanner = utils.deepClone(adUnit); + const isBannerValid = validateSizeConfig('banner', mediaTypes.banner.sizeConfig, adUnit.code); + if (!isBannerValid) { + delete validatedBanner.mediaTypes.banner; + } else { + /* + Make sure 'sizes' field is always an array of arrays. If not, make it so. + For example, [] becomes [[]], and [360, 400] becomes [[360, 400]] + */ + validatedBanner.mediaTypes.banner.sizeConfig.forEach(config => { + if (!Array.isArray(config.sizes[0])) { + config.sizes = [config.sizes]; + } + }); + } + } else { + // Ad unit is invalid since it's mediaType property does not have either 'sizes' or 'sizeConfig' declared. + utils.logError(`Ad unit ${adUnit.code}: 'mediaTypes.banner' does not contain either 'sizes' or 'sizeConfig' property. Removing 'mediaTypes.banner' from ad unit.`); + validatedBanner = utils.deepClone(adUnit); + delete validatedBanner.mediaTypes.banner; + } + } + + if (mediaTypes.video) { + if (mediaTypes.video.playerSize) { + // Ad unit is using 'mediaTypes.video.playerSize' instead of the new property 'sizeConfig'. Apply the old checks! + validatedVideo = validatedBanner ? adUnitSetupChecks.validateVideoMediaType(validatedBanner) : adUnitSetupChecks.validateVideoMediaType(adUnit); + } else if (mediaTypes.video.sizeConfig) { + // Ad unit is using the 'sizeConfig' property, 'mediaTypes.video.sizeConfig'. Apply the new checks! + validatedVideo = validatedBanner || utils.deepClone(adUnit); + const isVideoValid = validateSizeConfig('video', mediaTypes.video.sizeConfig, adUnit.code); + if (!isVideoValid) { + delete validatedVideo.mediaTypes.video.sizeConfig; + } else { + /* + Make sure 'playerSize' field is always an array of arrays. If not, make it so. + For example, [] becomes [[]], and [640, 400] becomes [[640, 400]] + */ + validatedVideo.mediaTypes.video.sizeConfig.forEach(config => { + if (!Array.isArray(config.playerSize[0])) { + config.playerSize = [config.playerSize]; + } + }); + } + } + } + + if (mediaTypes.native) { + // Apply the old native checks + validatedNative = validatedVideo ? adUnitSetupChecks.validateNativeMediaType(validatedVideo) : validatedBanner ? adUnitSetupChecks.validateNativeMediaType(validatedBanner) : adUnitSetupChecks.validateNativeMediaType(adUnit); + + // Apply the new checks if 'mediaTypes.native.sizeConfig' detected + if (mediaTypes.native.sizeConfig) { + const isNativeValid = validateSizeConfig('native', mediaTypes.native.sizeConfig, adUnit.code); + if (!isNativeValid) { + delete validatedNative.mediaTypes.native.sizeConfig; + } + } + } + + const validatedAdUnit = Object.assign({}, validatedBanner, validatedVideo, validatedNative); + validatedAdUnits.push(validatedAdUnit); + }); + return validatedAdUnits; +} + +getHook('checkAdUnitSetup').before(function (fn, adUnits) { + const usingNewSizeMapping = isUsingNewSizeMapping(adUnits); + if (usingNewSizeMapping) { + // if adUnits are found using the sizeMappingV2 spec, we run additional checks on them for checking the validity of sizeConfig object + // in addition to running the base checks on the mediaType object and return the adUnit without calling the base function. + adUnits = checkAdUnitSetupHook(adUnits); + return fn.bail(adUnits); + } else { + // if presence of sizeMappingV2 spec is not detected on adUnits, we default back to the original checks defined in the base function. + return fn.call(this, adUnits); + } +}); + +// checks if the sizeConfig object declared at the Bidder level is in the right format or not. +export function checkBidderSizeConfigFormat(sizeConfig) { + let didCheckPass = true; + if (Array.isArray(sizeConfig) && sizeConfig.length > 0) { + sizeConfig.forEach(config => { + const keys = Object.keys(config); + if ((includes(keys, 'minViewPort') && + includes(keys, 'relevantMediaTypes')) && + utils.isArrayOfNums(config.minViewPort, 2) && + Array.isArray(config.relevantMediaTypes) && + config.relevantMediaTypes.length > 0 && + (config.relevantMediaTypes.length > 1 ? (config.relevantMediaTypes.every(mt => (includes(['banner', 'video', 'native'], mt)))) + : (['none', 'banner', 'video', 'native'].indexOf(config.relevantMediaTypes[0]) > -1))) { + didCheckPass = didCheckPass && true; + } else { + didCheckPass = false; + } + }); + } else { + didCheckPass = false; + } + return didCheckPass; +} + +getHook('getBids').before(function (fn, bidderInfo) { + // check if the adUnit is using sizeMappingV2 specs and store the result in _sizeMappingUsageMap. + if (typeof sizeMappingInternalStore.getAuctionDetail(bidderInfo.auctionId) === 'undefined') { + const isUsingSizeMappingBool = isUsingNewSizeMapping(bidderInfo.adUnits); + + // initialize sizeMappingInternalStore for the first time for a particular auction + sizeMappingInternalStore.initializeStore(bidderInfo.auctionId, isUsingSizeMappingBool); + } + if (sizeMappingInternalStore.getAuctionDetail(bidderInfo.auctionId).usingSizeMappingV2) { + // if adUnit is found using sizeMappingV2 specs, run the getBids function which processes the sizeConfig object + // and returns the bids array for a particular bidder. + + const bids = getBids(bidderInfo); + return fn.bail(bids); + } else { + // if not using sizeMappingV2, default back to the getBids function defined in adapterManager. + return fn.call(this, bidderInfo); + } +}); + +/** + * Given an Ad Unit or a Bid as an input, returns a boolean telling if the Ad Unit/ Bid is active based on label checks + * @param {Object} bidOrAdUnit - Either the Ad Unit object or the Bid object + * @param {Array} activeLabels - List of active labels passed as an argument to pbjs.requestBids function + * @param {string} adUnitCode - Unique string identifier for an Ad Unit. + * @param {number} adUnitInstance - Instance count of an 'Identical' ad unit. + * @returns {boolean} Represents if the Ad Unit or the Bid is active or not + */ +export function isLabelActivated(bidOrAdUnit, activeLabels, adUnitCode, adUnitInstance) { + let labelOperator; + const labelsFound = Object.keys(bidOrAdUnit).filter(prop => prop === 'labelAny' || prop === 'labelAll'); + if (labelsFound && labelsFound.length > 1) { + utils.logWarn(`Size Mapping V2:: ${(bidOrAdUnit.code) + ? (`Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Ad unit has multiple label operators. Using the first declared operator: ${labelsFound[0]}`) + : (`Ad Unit: ${adUnitCode}(${adUnitInstance}), Bidder: ${bidOrAdUnit.bidder} => Bidder has multiple label operators. Using the first declared operator: ${labelsFound[0]}`)}`); + } + labelOperator = labelsFound[0]; + + if (labelOperator && !activeLabels) { + utils.logWarn(`Size Mapping V2:: ${(bidOrAdUnit.code) + ? (`Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Found '${labelOperator}' on ad unit, but 'labels' is not set. Did you pass 'labels' to pbjs.requestBids() ?`) + : (`Ad Unit: ${adUnitCode}(${adUnitInstance}), Bidder: ${bidOrAdUnit.bidder} => Found '${labelOperator}' on bidder, but 'labels' is not set. Did you pass 'labels' to pbjs.requestBids() ?`)}`); + return true; + } + + if (labelOperator === 'labelAll' && Array.isArray(bidOrAdUnit[labelOperator])) { + if (bidOrAdUnit.labelAll.length === 0) { + utils.logWarn(`Size Mapping V2:: Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Ad unit has declared property 'labelAll' with an empty array.`); + return true; + } + return bidOrAdUnit.labelAll.every(label => includes(activeLabels, label)); + } else if (labelOperator === 'labelAny' && Array.isArray(bidOrAdUnit[labelOperator])) { + if (bidOrAdUnit.labelAny.length === 0) { + utils.logWarn(`Size Mapping V2:: Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Ad unit has declared property 'labelAny' with an empty array.`); + return true; + } + return bidOrAdUnit.labelAny.some(label => includes(activeLabels, label)); + } + return true; +} + +/** + * Processes the MediaTypes object and calculates the active size buckets for each Media Type. Uses `window.innerWidth` and `window.innerHeight` + * to calculate the width and height of the active Viewport. + * @param {MediaTypes} mediaTypes Contains information about supported media types for an Ad Unit and size information for each of those types + * @returns {FilteredMediaTypes} Filtered mediaTypes object with relevant media types filtered by size buckets based on activeViewPort size + */ +export function getFilteredMediaTypes(mediaTypes) { + let + activeViewportWidth, + activeViewportHeight, + transformedMediaTypes; + + transformedMediaTypes = utils.deepClone(mediaTypes); + + let activeSizeBucket = { + banner: undefined, + video: undefined, + native: undefined + } + + try { + activeViewportWidth = utils.getWindowTop().innerWidth; + activeViewportHeight = utils.getWindowTop().innerHeight; + } catch (e) { + utils.logWarn(`SizeMappingv2:: Unfriendly iframe blocks viewport size to be evaluated correctly`); + activeViewportWidth = window.innerWidth; + activeViewportHeight = window.innerHeight; + } + const activeViewport = [activeViewportWidth, activeViewportHeight]; + Object.keys(mediaTypes).map(mediaType => { + const sizeConfig = mediaTypes[mediaType].sizeConfig; + if (sizeConfig) { + activeSizeBucket[mediaType] = getActiveSizeBucket(sizeConfig, activeViewport); + const filteredSizeConfig = sizeConfig.filter(config => config.minViewPort === activeSizeBucket[mediaType] && isSizeConfigActivated(mediaType, config)); + transformedMediaTypes[mediaType] = Object.assign({ filteredSizeConfig }, mediaTypes[mediaType]); + + // transform mediaTypes object + const config = { + banner: 'sizes', + video: 'playerSize' + }; + + if (transformedMediaTypes[mediaType].filteredSizeConfig.length > 0) { + // map sizes or playerSize property in filteredSizeConfig object to transformedMediaTypes.banner.sizes if mediaType is banner + // or transformedMediaTypes.video.playerSize if the mediaType in video. + // doesn't apply to native mediaType since native doesn't have any property defining 'sizes' or 'playerSize'. + if (mediaType !== 'native') { + transformedMediaTypes[mediaType][config[mediaType]] = transformedMediaTypes[mediaType].filteredSizeConfig[0][config[mediaType]]; + } + } else { + delete transformedMediaTypes[mediaType]; + } + } + }) + + // filter out 'undefined' values from activeSizeBucket object and attach sizes/playerSize information against the active size bucket. + const sizeBucketToSizeMap = Object + .keys(activeSizeBucket) + .filter(mediaType => activeSizeBucket[mediaType] !== undefined) + .reduce((sizeBucketToSizeMap, mediaType) => { + sizeBucketToSizeMap[mediaType] = { + activeSizeBucket: activeSizeBucket[mediaType], + activeSizeDimensions: (mediaType === 'banner') ? ( + // banner mediaType gets deleted incase no sizes are specified for a given size bucket, that's why this check is necessary + (transformedMediaTypes.banner) ? (transformedMediaTypes.banner.sizes) : ([]) + ) : ((mediaType === 'video') ? ( + // video mediaType gets deleted incase no playerSize is specified for a given size bucket, that's why this check is necessary + (transformedMediaTypes.video) ? (transformedMediaTypes.video.playerSize) : ([]) + ) : ('NA')) + }; + return sizeBucketToSizeMap; + }, {}); + + return { mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes }; +}; + +/** + * Evaluates the given sizeConfig object and checks for various properties to determine if the sizeConfig is active or not. For example, + * let's suppose the sizeConfig is for a Banner media type. Then, if the sizes property is found empty, it returns false, else returns true. + * In case of a Video media type, it checks the playerSize property. If found empty, returns false, else returns true. + * In case of a Native media type, it checks the active property. If found false, returns false, if found true, returns true. + * @param {string} mediaType It can be 'banner', 'native' or 'video' + * @param {Object} sizeConfig Represents the sizeConfig object which is active based on the current viewport size + * @returns {boolean} Represents if the size config is active or not + */ +export function isSizeConfigActivated(mediaType, sizeConfig) { + switch (mediaType) { + case 'banner': + // we need this check, sizeConfig.sizes[0].length > 0, in place because a sizeBucket can have sizes: [], + // gets converted to sizes: [[]] in the checkAdUnitSetupHook function + return sizeConfig.sizes && sizeConfig.sizes.length > 0 && sizeConfig.sizes[0].length > 0; + case 'video': + // for why we need the last check, read the above comment + return sizeConfig.playerSize && sizeConfig.playerSize.length > 0 && sizeConfig.playerSize[0].length > 0; + case 'native': + return sizeConfig.active; + default: + return false; + } +} + +/** + * Returns the active size bucket for a given media type + * @param {Array} sizeConfig SizeConfig defines the characteristics of an Ad Unit categorised into multiple size buckets per media type + * @param {Array} activeViewport Viewport size of the browser in the form [w, h] (w -> width, h -> height) + * Calculated at the time of making call to pbjs.requestBids function + * @returns {Array} The active size bucket matching the activeViewPort, for example: [750, 0] + */ +export function getActiveSizeBucket(sizeConfig, activeViewport) { + let activeSizeBucket = []; + sizeConfig + .sort((a, b) => a.minViewPort[0] - b.minViewPort[0]) + .forEach(config => { + if (activeViewport[0] >= config.minViewPort[0]) { + if (activeViewport[1] >= config.minViewPort[1]) { + activeSizeBucket = config.minViewPort; + } else { + activeSizeBucket = []; + } + } + }) + return activeSizeBucket; +} + +export function getRelevantMediaTypesForBidder(sizeConfig, activeViewport) { + if (internal.checkBidderSizeConfigFormat(sizeConfig)) { + const activeSizeBucket = internal.getActiveSizeBucket(sizeConfig, activeViewport); + return sizeConfig.filter(config => config.minViewPort === activeSizeBucket)[0]['relevantMediaTypes']; + } + return []; +} + +// sets sizeMappingInternalStore for a given auctionId with relevant adUnit information returned from the call to 'getFilteredMediaTypes' function +// returns adUnit details object. +export function getAdUnitDetail(auctionId, adUnit, labels) { + // fetch all adUnits for an auction from the sizeMappingInternalStore + const adUnitsForAuction = sizeMappingInternalStore.getAuctionDetail(auctionId).adUnits; + + // check if the adUnit exists already in the sizeMappingInterStore (check for equivalence of 'code' && 'mediaTypes' properties) + const adUnitDetail = adUnitsForAuction.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code && utils.deepEqual(adUnitDetail.mediaTypes, adUnit.mediaTypes)); + + if (adUnitDetail.length > 0) { + adUnitDetail[0].cacheHits++; + return adUnitDetail[0]; + } else { + const identicalAdUnit = adUnitsForAuction.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code); + const adUnitInstance = identicalAdUnit.length > 0 && typeof identicalAdUnit[0].instance === 'number' ? identicalAdUnit[identicalAdUnit.length - 1].instance + 1 : 1; + const isLabelActivated = internal.isLabelActivated(adUnit, labels, adUnit.code, adUnitInstance); + const { mediaTypes = adUnit.mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = isLabelActivated && internal.getFilteredMediaTypes(adUnit.mediaTypes); + + const adUnitDetail = { + adUnitCode: adUnit.code, + mediaTypes, + sizeBucketToSizeMap, + activeViewport, + transformedMediaTypes, + instance: adUnitInstance, + isLabelActivated, + cacheHits: 0 + }; + + // set adUnitDetail in sizeMappingInternalStore against the correct 'auctionId'. + sizeMappingInternalStore.setAuctionDetail(auctionId, adUnitDetail); + isLabelActivated && utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Active size buckets after filtration: `, sizeBucketToSizeMap); + + return adUnitDetail; + } +} + +export function getBids({ bidderCode, auctionId, bidderRequestId, adUnits, labels, src }) { + return adUnits.reduce((result, adUnit) => { + if (adUnit.mediaTypes && utils.isValidMediaTypes(adUnit.mediaTypes)) { + const { activeViewport, transformedMediaTypes, instance: adUnitInstance, isLabelActivated, cacheHits } = internal.getAdUnitDetail(auctionId, adUnit, labels); + if (isLabelActivated) { + // check if adUnit has any active media types remaining, if not drop the adUnit from auction, + // else proceed to evaluate the bids object. + if (Object.keys(transformedMediaTypes).length === 0) { + cacheHits === 0 && utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); + return result; + } + result + .push(adUnit.bids.filter(bid => bid.bidder === bidderCode) + .reduce((bids, bid) => { + if (internal.isLabelActivated(bid, labels, adUnit.code, adUnitInstance)) { + // handle native params + const nativeParams = adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native'); + if (nativeParams) { + bid = Object.assign({}, bid, { + nativeParams: processNativeAdUnitParams(nativeParams) + }); + } + + bid = Object.assign({}, bid, utils.getDefinedParams(adUnit, ['mediaType', 'renderer'])); + + if (bid.sizeConfig) { + const relevantMediaTypes = internal.getRelevantMediaTypesForBidder(bid.sizeConfig, activeViewport); + if (relevantMediaTypes.length === 0) { + utils.logError(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bidderCode} => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`); + bid = Object.assign({}, bid); + } else if (relevantMediaTypes[0] !== 'none') { + const bidderMediaTypes = Object + .keys(transformedMediaTypes) + .filter(mt => relevantMediaTypes.indexOf(mt) > -1) + .reduce((mediaTypes, mediaType) => { + mediaTypes[mediaType] = transformedMediaTypes[mediaType]; + return mediaTypes; + }, {}); + + if (Object.keys(bidderMediaTypes).length > 0) { + bid = Object.assign({}, bid, { mediaTypes: bidderMediaTypes }); + } else { + utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); + return bids; + } + } else { + utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' is set to 'none' in sizeConfig for current viewport size. This bidder is disabled.`); + return bids; + } + } + bids.push(Object.assign({}, bid, { + adUnitCode: adUnit.code, + transactionId: adUnit.transactionId, + sizes: utils.deepAccess(transformedMediaTypes, 'banner.sizes') || utils.deepAccess(transformedMediaTypes, 'video.playerSize') || [], + mediaTypes: bid.mediaTypes || transformedMediaTypes, + bidId: bid.bid_id || utils.getUniqueIdentifierStr(), + bidderRequestId, + auctionId, + src, + bidRequestsCount: adunitCounter.getRequestsCounter(adUnit.code), + bidderRequestsCount: adunitCounter.getBidderRequestsCounter(adUnit.code, bid.bidder), + bidderWinsCount: adunitCounter.getBidderWinsCounter(adUnit.code, bid.bidder) + })); + return bids; + } else { + utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => Label check for this bidder has failed. This bidder is disabled.`); + return bids; + } + }, [])); + } else { + cacheHits === 0 && utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit is disabled due to failing label check.`); + } + } else { + utils.logWarn(`Size Mapping V2:: Ad Unit: ${adUnit.code} => Ad unit has declared invalid 'mediaTypes' or has not declared a 'mediaTypes' property`); + return result; + } + return result; + }, []).reduce(utils.flatten, []).filter(val => val !== ''); +} diff --git a/modules/slimcutBidAdapter.js b/modules/slimcutBidAdapter.js index 2aa75853301..d717f3a88bd 100644 --- a/modules/slimcutBidAdapter.js +++ b/modules/slimcutBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { ajax } from '../src/ajax'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'slimcut'; const ENDPOINT_URL = 'https://sb.freeskreen.com/pbr'; diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js new file mode 100644 index 00000000000..ce0edb1e19c --- /dev/null +++ b/modules/smaatoBidAdapter.js @@ -0,0 +1,268 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'smaato'; +const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; +const CLIENT = 'prebid_js_$prebid.version$_1.0' + +/** +* Transform BidRequest to OpenRTB-formatted BidRequest Object +* @param {Array} validBidRequests +* @param {any} bidderRequest +* @returns {string} +*/ +const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { + /** + * Turn incoming prebid sizes into openRtb format mapping. + * @param {*} sizes in format [[10, 10], [20, 20]] + * @returns array of openRtb format mappings [{w: 10, h: 10}, {w: 20, h: 20}] + */ + const parseSizes = (sizes) => { + return sizes.map((size) => { + return {w: size[0], h: size[1]}; + }) + } + + const imp = validBidRequests.map(br => { + const bannerMediaType = utils.deepAccess(br, 'mediaTypes.banner'); + const videoMediaType = utils.deepAccess(br, 'mediaTypes.video'); + let result = { + id: br.bidId, + tagid: utils.deepAccess(br, 'params.adspaceId') + } + + if (bannerMediaType) { + const sizes = parseSizes(utils.getAdUnitSizes(br)); + result.banner = { + w: sizes[0].w, + h: sizes[0].h, + format: sizes + } + } + + if (videoMediaType) { + result.video = { + mimes: videoMediaType.mimes, + minduration: videoMediaType.minduration, + startdelay: videoMediaType.startdelay, + linearity: videoMediaType.linearity, + w: videoMediaType.playerSize[0][0], + h: videoMediaType.playerSize[0][1], + maxduration: videoMediaType.maxduration, + skip: videoMediaType.skip, + protocols: videoMediaType.protocols, + ext: { + rewarded: videoMediaType.ext && videoMediaType.ext.rewarded ? videoMediaType.ext.rewarded : 0 + }, + skipmin: videoMediaType.skipmin, + api: videoMediaType.api + } + } + + return result; + }); + + const request = { + id: bidderRequest.auctionId, + at: 1, + imp, + cur: ['USD'], + tmax: bidderRequest.timeout, + site: { + id: window.location.hostname, + publisher: { + id: utils.deepAccess(validBidRequests[0], 'params.publisherId') + }, + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo.referer + }, + device: { + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + ua: navigator.userAgent, + dnt: utils.getDNT() ? 1 : 0, + h: screen.height, + w: screen.width + }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + user: { + ext: {} + }, + ext: { + client: CLIENT + } + }; + + Object.assign(request.user, config.getConfig('fpd.user')); + Object.assign(request.site, config.getConfig('fpd.context')); + + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { + utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent !== undefined) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + utils.logInfo('[SMAATO] OpenRTB Request:', request); + return JSON.stringify(request); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return typeof bid.params === 'object' && + typeof bid.params.publisherId === 'string' && + typeof bid.params.adspaceId === 'string'; + }, + + /** + * 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) => { + utils.logInfo('[SMAATO] Client version:', CLIENT); + return { + method: 'POST', + url: validBidRequests[0].params.endpoint || SMAATO_ENDPOINT, + data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), + options: { + withCredentials: true, + crossOrigin: true, + } + }; + }, + /** + * 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: (serverResponse, bidRequest) => { + // response is empty (HTTP 204) + if (utils.isEmpty(serverResponse.body)) { + utils.logInfo('[SMAATO] Empty response body HTTP 204, no bids'); + return []; // no bids + } + + let serverResponseHeaders = serverResponse.headers; + const smtAdType = serverResponseHeaders.get('X-SMT-ADTYPE'); + + const smtExpires = serverResponseHeaders.get('X-SMT-Expires'); + let ttlSec = 300; + utils.logInfo('[SMAATO] Expires:', smtExpires); + if (smtExpires) { + ttlSec = Math.floor((smtExpires - Date.now()) / 1000); + } + + const res = serverResponse.body; + utils.logInfo('[SMAATO] OpenRTB Response:', res); + + var bids = []; + res.seatbid.forEach(sb => { + sb.bid.forEach(b => { + let resultingBid = { + requestId: b.impid, + cpm: b.price || 0, + width: b.w, + height: b.h, + ttl: ttlSec, + creativeId: b.crid, + dealId: b.dealid || null, + netRevenue: true, + currency: res.cur, + meta: { + advertiserDomains: b.adomain, + networkName: b.bidderName, + agencyId: sb.seat + } + }; + + switch (smtAdType) { + case 'Img': + resultingBid.ad = createImgAd(b.adm); + resultingBid.meta.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Richmedia': + resultingBid.ad = createRichmediaAd(b.adm); + resultingBid.meta.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Video': + resultingBid.vastXml = b.adm; + resultingBid.meta.mediaType = VIDEO; + bids.push(resultingBid); + break; + default: + utils.logInfo('[SMAATO] Invalid ad type:', smtAdType); + } + }); + }); + + utils.logInfo('[SMAATO] Prebid bids:', bids); + return bids; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + const syncs = [] + return syncs; + } +} +registerBidder(spec); + +const createImgAd = (adm) => { + const image = JSON.parse(adm).image; + + let clickEvent = ''; + image.clicktrackers.forEach(src => { + clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + + let markup = `
`; + + image.impressiontrackers.forEach(src => { + markup += ``; + }); + + return markup + '
'; +}; + +const createRichmediaAd = (adm) => { + const rich = JSON.parse(adm).richmedia; + let clickEvent = ''; + rich.clicktrackers.forEach(src => { + clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + + let markup = `
${rich.mediadata.content}`; + + rich.impressiontrackers.forEach(src => { + markup += ``; + }); + + return markup + '
'; +}; diff --git a/modules/smaatoBidAdapter.md b/modules/smaatoBidAdapter.md new file mode 100644 index 00000000000..d26d7ecf64e --- /dev/null +++ b/modules/smaatoBidAdapter.md @@ -0,0 +1,64 @@ +# Overview + +``` +Module Name: Smaato Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@smaato.com +``` + +# Description + +The Smaato adapter requires setup and approval from the Smaato team, even for existing Smaato publishers. Please reach out to your account team or prebid@smaato.com for more information. + +# Test Parameters + +For banner adunits: + +``` +var adUnits = [{ + "code": "banner-unit", + "mediaTypes": { + "banner": { + "sizes": [320, 50] + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` + +For video adunits: + +``` +var adUnits = [{ + "code": "video unit", + "mediaTypes": { + "video": { + "context": "instream", + "playerSize": [640, 480], + "mimes": ["video/mp4"], + "minduration": 5, + "maxduration": 30, + "startdelay": 0, + "linearity": 1, + "protocols": [7], + "skip": 1, + "skipmin": 5, + "api": [7], + "ext": {"rewarded": 0} + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` \ No newline at end of file diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js new file mode 100644 index 00000000000..97dd43fc5ba --- /dev/null +++ b/modules/smartadserverBidAdapter.js @@ -0,0 +1,185 @@ +import * as utils from '../src/utils.js'; +import { + BANNER, + VIDEO +} from '../src/mediaTypes.js'; +import { + config +} from '../src/config.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + createEidsArray +} from './userId/eids.js'; +const BIDDER_CODE = 'smartadserver'; +export const spec = { + code: BIDDER_CODE, + aliases: ['smart'], // short 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: function (bid) { + return !!(bid.params && bid.params.siteId && bid.params.pageId && bid.params.formatId); + }, + + /** + * Serialize a supply chain object to a string uri encoded + * + * @param {*} schain object + */ + serializeSupplyChain: function(schain) { + if (!schain || !schain.nodes) return null; + const nodesProperties = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + return `${schain.ver},${schain.complete}!` + + schain.nodes.map(node => nodesProperties.map(prop => + node[prop] ? encodeURIComponent(node[prop]) : '') + .join(',')) + .join('!'); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest bidder request object + * @return {ServerRequest[]} Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + // use bidderRequest.bids[] to get bidder-dependent request info + // if your bidder supports multiple currencies, use config.getConfig(currency) + // to find which one the ad server needs + + // pull requested transaction ID from bidderRequest.bids[].transactionId + return validBidRequests.map(bid => { + // Common bid request attributes for banner, outstream and instream. + let 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, + pageDomain: bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer ? bidderRequest.refererInfo.referer : undefined, + transactionId: bid.transactionId, + timeout: config.getConfig('bidderTimeout'), + bidId: bid.bidId, + prebidVersion: '$prebid.version$', + schain: spec.serializeSupplyChain(bid.schain) + }; + + const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); + if (!videoMediaType) { + const bannerMediaType = utils.deepAccess(bid, 'mediaTypes.banner'); + payload.sizes = bannerMediaType.sizes.map(size => ({ + w: size[0], + h: size[1] + })); + } else if (videoMediaType && videoMediaType.context === 'instream') { + // Specific attributes for instream. + let playerSize = videoMediaType.playerSize[0]; + payload.isVideo = true; + payload.videoData = { + videoProtocol: bid.params.video.protocol, + playerWidth: playerSize[0], + playerHeight: playerSize[1], + adBreak: bid.params.video.startDelay || 1 + }; + } else { + return {}; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + + if (bid && bid.userId) { + payload.eids = createEidsArray(bid.userId); + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + + var payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: (bid.params.domain !== undefined ? bid.params.domain : 'https://prg.smartadserver.com') + '/prebid/v1', + data: payloadString, + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequestString + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequestString) { + const bidResponses = []; + let response = serverResponse.body; + try { + if (response) { + const bidRequest = JSON.parse(bidRequestString.data); + + let bidResponse = { + requestId: bidRequest.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 + }; + + if (bidRequest.isVideo) { + bidResponse.mediaType = VIDEO; + bidResponse.vastUrl = response.adUrl; + bidResponse.vastXml = response.ad; + } else { + bidResponse.adUrl = response.adUrl; + bidResponse.ad = response.ad; + } + + bidResponses.push(bidResponse); + } + } catch (error) { + utils.logError('Error while parsing smart server response', error); + } + 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 index c1fc0d819c5..c6f68363d7c 100644 --- a/modules/smartadserverBidAdapter.md +++ b/modules/smartadserverBidAdapter.md @@ -20,12 +20,16 @@ Please reach out to your Technical account manager for more information. var adUnits = [ { code: 'test-div', - sizes: [[300, 250]], // a display size + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, bids: [ { bidder: "smart", params: { - domain: 'http://ww251.smartadserver.com', + domain: 'https://prg.smartadserver.com', siteId: 207435, pageId: 896536, formatId: 62913, @@ -42,12 +46,16 @@ Please reach out to your Technical account manager for more information. var adUnits = [ { code: 'test-div', - sizes: [[300, 250]], // a display size + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, bids: [ { bidder: "smart", params: { - domain: 'http://ww251.smartadserver.com', + domain: 'https://prg.smartadserver.com', siteId: 207435, pageId: 896536, formatId: 65906, @@ -74,14 +82,14 @@ Please reach out to your Technical account manager for more information. bids: [{ bidder: "smart", params: { - domain: 'http://ww251.smartadserver.com', + domain: 'https://prg.smartadserver.com', siteId: 326147, pageId: 1153895, - formatId: 55710 + formatId: 55710, bidfloor: 5, video: { protocol: 6, - startDelay: 0 + startDelay: 1 } } }] diff --git a/modules/smartrtbBidAdapter.js b/modules/smartrtbBidAdapter.js index 43304aa50c0..12d5a0ae7da 100644 --- a/modules/smartrtbBidAdapter.js +++ b/modules/smartrtbBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils'; -import { config } from '../src/config'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'smartrtb'; function getDomain () { diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js new file mode 100644 index 00000000000..ff60d08e48b --- /dev/null +++ b/modules/smartyadsBidAdapter.js @@ -0,0 +1,106 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'smartyads'; +const AD_URL = 'https://ssp-nj.webtradehub.com/?c=o&m=multi'; +const URL_SYNC = 'https://ssp-nj.webtradehub.com/?c=o&m=cookie'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid['mediaType']) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placementId)); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + const len = validBidRequests.length; + + for (let i = 0; i < len; i++) { + let bid = validBidRequests[i]; + let traff = bid.params.traffic || BANNER + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [], + traffic: traff + }); + if (bid.schain) { + placements.schain = bid.schain; + } + } + return { + method: 'POST', + url: AD_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)) { + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + return [{ + type: 'image', + url: URL_SYNC + }]; + } + +}; + +registerBidder(spec); diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md index 5102a6fd128..4fe4d51a2e6 100644 --- a/modules/smartyadsBidAdapter.md +++ b/modules/smartyadsBidAdapter.md @@ -12,16 +12,59 @@ 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' + var adUnits = [ + // Will return static native ad. Assets are stored through user UI for each placement separetly + { + code: 'placementId_0', + mediaTypes: { + native: {} + }, + bids: [ + { + bidder: 'smartyads', + params: { + placementId: 0, + traffic: 'native' + } } - }] + ] + }, + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'smartyads', + params: { + placementId: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'smartyads', + params: { + placementId: 0, + traffic: 'video' + } + } + ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index ed7e02a51a8..f965310abdd 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -1,8 +1,8 @@ -import * as utils from '../src/utils'; -import { Renderer } from '../src/Renderer'; -import { config } from '../src/config'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { Renderer } from '../src/Renderer.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; export const spec = { code: 'smilewanted', diff --git a/modules/smmsBidAdapter.js b/modules/smmsBidAdapter.js index dafd4aba734..670caebe61b 100644 --- a/modules/smmsBidAdapter.js +++ b/modules/smmsBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'smms'; const ENDPOINT_BANNER = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/banner'; @@ -54,6 +54,7 @@ export const spec = { refererInfo = bidderRequest.refererInfo; } + // eslint-disable-next-line no-undef const g = (typeof (geparams) !== 'undefined' && typeof (geparams) == 'object' && geparams) ? geparams : {}; validBidRequests.forEach((bid, i) => { let endpoint = ENDPOINT_BANNER; diff --git a/modules/somoBidAdapter.js b/modules/somoBidAdapter.js new file mode 100644 index 00000000000..c6e31790005 --- /dev/null +++ b/modules/somoBidAdapter.js @@ -0,0 +1,289 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +const VIDEO_TARGETING = ['mimes', 'minduration', 'maxduration', 'protocols', + 'startdelay', 'linearity', 'skip', 'delivery', + 'pos', 'api', 'ext', 'battr']; +const BANNER_TARGETING = ['battr', 'btype', 'pos', 'mimes', 'ext']; + +const SITE_TARGETING = ['name', 'domain', 'cat', 'keywords', 'content'] +const APP_TARGETING = ['name', 'bundle', 'domain', 'storeUrl', 'cat', 'ver', 'keywords', 'content'] + +export const spec = { + + code: 'somo', + + supportedMediaTypes: [BANNER, VIDEO], + aliases: ['somoaudience'], + + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.placementId) + ), + + buildRequests: function(bidRequests, bidderRequest) { + return bidRequests.map(bidRequest => { + let da = openRtbRequest(bidRequest, bidderRequest); + + return { + method: 'POST', + url: 'https://publisher-east.mobileadtrading.com/rtb/bid?s=' + bidRequest.params.placementId.toString(), + data: da, + bidRequest: bidRequest + }; + }); + }, + + interpretResponse(response, request) { + return bidResponseAvailable(request, response); + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent) => { + const syncs = []; + var url = 'https://publisher-east.mobileadtrading.com/usersync'; + + if (syncOptions.pixelEnabled) { + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } + } + syncs.push({ + type: 'image', + url: url + }); + } + return syncs; + } +}; + +function bidResponseAvailable(bidRequest, bidResponse) { + let bidResponses = []; + + if (bidResponse.body) { + let bidData = bidResponse.body.seatbid[0].bid[0]; + const bid = { + requestId: bidResponse.body.id, + cpm: bidData.price, + width: bidData.w, + height: bidData.h, + ad: bidData.adm, + ttl: 360, + creativeId: bidData.crid, + adId: bidData.impid, + netRevenue: false, + currency: 'USD', + adUnitCode: bidRequest.bidRequest.adUnitCode + }; + if (isVideo(bidRequest.bidRequest)) { + bid.vastXml = bidData.adm; + bid.mediaType = 'video'; + } else { + bid.ad = bidData.adm; + bid.mediaType = 'banner'; + } + bidResponses.push(bid); + } + return bidResponses; +} + +function openRtbRequest(bidRequest, bidderRequest) { + var openRtbRequest = { + id: bidRequest.bidId, + imp: [openRtbImpression(bidRequest)], + at: 1, + tmax: 400, + site: openRtbSite(bidRequest, bidderRequest), + app: openRtbApp(bidRequest), + device: openRtbDevice(), + bcat: openRtbBCat(bidRequest), + badv: openRtbBAdv(bidRequest), + ext: { + prebid: '$prebid.version$', + }, + }; + if (typeof bidderRequest !== 'undefined') { + openRtbRequest = populateOpenRtbGdpr(bidderRequest.gdprConsent, openRtbRequest); + } + + return openRtbRequest; +} + +function populateOpenRtbGdpr(gdpr, bidRequest) { + if (gdpr && bidRequest && 'gdprApplies' in gdpr) { + if (!('reqs' in bidRequest)) { + bidRequest.reqs = {}; + } + if (!('ext' in bidRequest.reqs)) { + bidRequest.reqs.ext = {}; + } + bidRequest.reqs.ext.gdpr = gdpr.gdprApplies; + + if ('consentString' in gdpr) { + if (!('user' in bidRequest)) { + bidRequest.user = {}; + } + if (!('ext' in bidRequest.user)) { + bidRequest.user.ext = {}; + } + bidRequest.user.ext.consent = gdpr.consentString; + } + } + + return bidRequest; +} + +function openRtbImpression(bidRequest) { + const imp = { + 'id': bidRequest.bidId, + bidfloor: bidRequest.params.bidfloor || 0, + }; + if (isVideo(bidRequest)) { + imp.video = {}; + if (bidRequest.mediaTypes && + bidRequest.mediaTypes.video && + bidRequest.mediaTypes.video.sizes) { + const sizes = getSizes(bidRequest.mediaTypes.video.sizes); + imp.video.w = sizes[0]; + imp.video.h = sizes[1]; + } + if (bidRequest.params.video) { + Object.keys(bidRequest.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => imp.video[param] = bidRequest.params.video[param]); + } + } else { + imp.banner = { + topframe: 0 + }; + if (bidRequest.mediaTypes && + bidRequest.mediaTypes.banner && + bidRequest.mediaTypes.banner.sizes) { + const sizes = getSizes(bidRequest.mediaTypes.banner.sizes); + imp.banner.w = sizes[0]; + imp.banner.h = sizes[1]; + } + if (bidRequest.params.banner) { + Object.keys(bidRequest.params.banner) + .filter(param => includes(BANNER_TARGETING, param)) + .forEach(param => imp.banner[param] = bidRequest.params.banner[param]); + } + } + return imp; +} + +function isApp(bidRequest) { + if (bidRequest.params.app) { + return true; + } else { + return false; + } +} + +function openRtbSite(bidRequest, bidderRequest) { + if (!isApp(bidRequest)) { + const site = {}; + + if (bidderRequest && bidderRequest.refererInfo) { + site.ref = bidderRequest.refererInfo.referer; + site.page = bidderRequest.refererInfo.canonicalUrl; + } + + if (bidRequest.params.site) { + Object.keys(bidRequest.params.site) + .filter(param => includes(SITE_TARGETING, param)) + .forEach(param => site[param] = bidRequest.params.site[param]); + } + if (typeof site.domain === 'undefined' && + typeof site.page !== 'undefined') { + if (typeof window.URL === 'function') { + site.domain = (new window.URL(site.page)).hostname; + } else { + site.domain = getDomainFromUrl(site.page); + } + } + + return site; + } else { + return null; + } +} + +function getDomainFromUrl(url) { + var domain = url; + + if (url.indexOf('//') > -1) { + domain = url.split('/')[2]; + } else { + domain = url.split('/')[0]; + } + + domain = domain.split(':')[0]; + domain = domain.split('?')[0]; + + return domain; +} + +function openRtbApp(bidRequest) { + if (isApp(bidRequest)) { + const app = { + + } + Object.keys(bidRequest.params.app) + .filter(param => includes(APP_TARGETING, param)) + .forEach(param => app[param] = bidRequest.params.app[param]); + + return app; + } else { + return null; + } +} + +function openRtbDevice() { + return { + ip: 'check', + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + }; +} + +function openRtbBCat(bidRequest) { + if (utils.isArray(bidRequest.params.bcat)) { + return bidRequest.params.bcat; + } + return []; +} + +function openRtbBAdv(bidRequest) { + if (utils.isArray(bidRequest.params.badv)) { + return bidRequest.params.badv; + } + return []; +} + +function isVideo(format) { + return utils.deepAccess(format, 'mediaTypes.video') || format.mediaType == 'video'; +} + +/* Turn bid request sizes into compatible format */ +function getSizes(requestSizes) { + let width = 0; + let height = 0; + if (utils.isArray(requestSizes) && requestSizes.length === 2 && + !utils.isArray(requestSizes[0])) { + width = parseInt(requestSizes[0], 10); + height = parseInt(requestSizes[1], 10); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + width = parseInt(size[0], 10); + height = parseInt(size[1], 10); + break; + } + } + return [width, height]; +} + +registerBidder(spec); diff --git a/modules/somoBidAdapter.md b/modules/somoBidAdapter.md index de395478061..e8457fc0ca2 100644 --- a/modules/somoBidAdapter.md +++ b/modules/somoBidAdapter.md @@ -7,12 +7,16 @@ Connects to Somo Audience demand source. Please use ```somo``` as the bidder code. -For video integration, somoAudience returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction +For video integration, Somo Audience returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction # Test Site Parameters ``` var adUnits = [{ code: 'banner-ad-div', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, bids: [{ bidder: 'somo', params: { @@ -25,7 +29,11 @@ For video integration, somoAudience returns content as vastXML and requires the ``` var adUnits = [{ code: 'banner-ad-div', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, bids: [{ bidder: 'somo', params: { diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js index ab110cb4d01..d69276e915c 100644 --- a/modules/sonobiAnalyticsAdapter.js +++ b/modules/sonobiAnalyticsAdapter.js @@ -1,9 +1,9 @@ -import adapter from '../src/AnalyticsAdapter'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; -import {ajaxBuilder} from '../src/ajax'; +import adapterManager from '../src/adapterManager.js'; +import {ajaxBuilder} from '../src/ajax.js'; -const utils = require('../src/utils'); +const utils = require('../src/utils.js'); let ajax = ajaxBuilder(0); const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index eea25d98d90..a55992cec22 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,14 +1,13 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage } from '../src/utils'; -import { BANNER, VIDEO } from '../src/mediaTypes'; -import { config } from '../src/config'; -import { Renderer } from '../src/Renderer'; -import { userSync } from '../src/userSync'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; +import { userSync } from '../src/userSync.js'; const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); -const SONOBI_DIGITRUST_KEY = 'fhnS5drwmH'; const OUTSTREAM_REDNERER_URL = 'https://mtrx.go.sonobi.com/sbi_outstream_renderer.js'; export const spec = { @@ -100,14 +99,6 @@ export const spec = { if (deepAccess(validBidRequests[0], 'params.hfa')) { payload.hfa = deepAccess(validBidRequests[0], 'params.hfa'); - } else if (deepAccess(validBidRequests[0], 'userId.pubcid')) { - payload.hfa = `PRE-${validBidRequests[0].userId.pubcid}`; - } else if (deepAccess(validBidRequests[0], 'crumbs.pubcid')) { - payload.hfa = `PRE-${validBidRequests[0].crumbs.pubcid}`; - } - - if (deepAccess(validBidRequests[0], 'userId.tdid')) { - payload.tdid = validBidRequests[0].userId.tdid; } if (validBidRequests[0].params.referrer) { @@ -122,13 +113,6 @@ export const spec = { } } - const digitrust = _getDigiTrustObject(SONOBI_DIGITRUST_KEY); - - if (digitrust) { - payload.digid = digitrust.id; - payload.digkeyv = digitrust.keyv; - } - if (validBidRequests[0].schain) { payload.schain = JSON.stringify(validBidRequests[0].schain) } @@ -142,14 +126,24 @@ export const spec = { payload.kw = keywords; } + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + // If there is no key_maker data, then don't make the request. if (isEmpty(data)) { return null; } + let url = STR_ENDPOINT; + + if (deepAccess(validBidRequests[0], 'params.bid_request_url')) { + url = deepAccess(validBidRequests[0], 'params.bid_request_url'); + } + return { method: 'GET', - url: STR_ENDPOINT, + url: url, withCredentials: true, data: payload, bidderRequests: validBidRequests @@ -236,7 +230,7 @@ export const spec = { /** * Register User Sync. */ - getUserSyncs: (syncOptions, serverResponses) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { const syncs = []; try { if (syncOptions.pixelEnabled) { @@ -334,20 +328,6 @@ export function _getPlatform(context = window) { return 'desktop'; } -// https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide -function _getDigiTrustObject(key) { - function getDigiTrustId() { - let digiTrustUser = window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: key})); - 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 digiTrustId; -} - function newRenderer(adUnitCode, bid, rendererOptions = {}) { const renderer = Renderer.install({ id: bid.aid, diff --git a/modules/sortableAnalyticsAdapter.js b/modules/sortableAnalyticsAdapter.js index 17458065f9a..73ce1393c23 100644 --- a/modules/sortableAnalyticsAdapter.js +++ b/modules/sortableAnalyticsAdapter.js @@ -1,10 +1,10 @@ -import adapter from '../src/AnalyticsAdapter'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; -import * as utils from '../src/utils'; -import {ajax} from '../src/ajax'; -import {getGlobal} from '../src/prebidGlobal'; -import { config } from '../src/config'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import { config } from '../src/config.js'; const DEFAULT_PROTOCOL = 'https'; const DEFAULT_HOST = 'pa.deployads.com'; diff --git a/modules/sortableBidAdapter.js b/modules/sortableBidAdapter.js new file mode 100644 index 00000000000..f5d3a6b1bb5 --- /dev/null +++ b/modules/sortableBidAdapter.js @@ -0,0 +1,342 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'sortable'; +const SERVER_URL = 'https://c.deployads.com'; + +function setAssetRequired(native, asset) { + if (native.required) { + asset.required = 1; + } + return asset; +} + +function buildNativeRequest(nativeMediaType) { + const assets = []; + const title = nativeMediaType.title; + if (title) { + assets.push(setAssetRequired(title, { + title: {len: title.len} + })); + } + const img = nativeMediaType.image; + if (img) { + assets.push(setAssetRequired(img, { + img: { + type: 3, // Main + wmin: 1, + hmin: 1 + } + })); + } + const icon = nativeMediaType.icon; + if (icon) { + assets.push(setAssetRequired(icon, { + img: { + type: 1, // Icon + wmin: 1, + hmin: 1 + } + })); + } + const body = nativeMediaType.body; + if (body) { + assets.push(setAssetRequired(body, {data: {type: 2}})); + } + const cta = nativeMediaType.cta; + if (cta) { + assets.push(setAssetRequired(cta, {data: {type: 12}})); + } + const sponsoredBy = nativeMediaType.sponsoredBy; + if (sponsoredBy) { + assets.push(setAssetRequired(sponsoredBy, {data: {type: 1}})); + } + + utils._each(assets, (asset, id) => asset.id = id); + return { + ver: '1', + request: JSON.stringify({ + ver: '1', + assets + }) + }; +} + +function tryParseNativeResponse(adm) { + let native = null; + try { + native = JSON.parse(adm); + } catch (e) { + utils.logError('Sortable bid adapter unable to parse native bid response:\n\n' + e); + } + return native && native.native; +} + +function createImgObject(img) { + if (img.w || img.h) { + return { + url: img.url, + width: img.w, + height: img.h + }; + } else { + return img.url; + } +} + +function interpretNativeResponse(response) { + const native = {}; + if (response.link) { + native.clickUrl = response.link.url; + } + utils._each(response.assets, asset => { + switch (asset.id) { + case 1: + native.title = asset.title.text; + break; + case 2: + native.image = createImgObject(asset.img); + break; + case 3: + native.icon = createImgObject(asset.img); + break; + case 4: + native.body = asset.data.value; + break; + case 5: + native.cta = asset.data.value; + break; + case 6: + native.sponsoredBy = asset.data.value; + break; + } + }); + return native; +} + +function transformSyncs(responses, type, syncs) { + utils._each(responses, res => { + if (res.body && res.body.ext && res.body.ext.sync_dsps && res.body.ext.sync_dsps.length) { + utils._each(res.body.ext.sync_dsps, sync => { + if (sync[0] === type && sync[1]) { + syncs.push({type, url: sync[1]}); + } + }); + } + }); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + + isBidRequestValid: function(bid) { + const sortableConfig = config.getConfig('sortable'); + const haveSiteId = (sortableConfig && !!sortableConfig.siteId) || bid.params.siteId; + const validFloor = !bid.params.floor || utils.isNumber(bid.params.floor); + const validSize = /\d+x\d+/; + const validFloorSizeMap = !bid.params.floorSizeMap || + (utils.isPlainObject(bid.params.floorSizeMap) && + Object.keys(bid.params.floorSizeMap).every(size => + size.match(validSize) && utils.isNumber(bid.params.floorSizeMap[size]) + )) + const validKeywords = !bid.params.keywords || + (utils.isPlainObject(bid.params.keywords) && + Object.keys(bid.params.keywords).every(key => + utils.isStr(key) && utils.isStr(bid.params.keywords[key]) + )) + const isBanner = !bid.mediaTypes || bid.mediaTypes[BANNER] || !(bid.mediaTypes[NATIVE] || bid.mediaTypes[VIDEO]); + const bannerSizes = isBanner ? utils.deepAccess(bid, `mediaType.${BANNER}.sizes`) || bid.sizes : null; + return !!(bid.params.tagId && haveSiteId && validFloor && validFloorSizeMap && validKeywords && (!isBanner || + (bannerSizes && bannerSizes.length > 0 && bannerSizes.every(sizeArr => sizeArr.length == 2 && sizeArr.every(num => utils.isNumber(num)))))); + }, + + buildRequests: function(validBidReqs, bidderRequest) { + const sortableConfig = config.getConfig('sortable') || {}; + const globalSiteId = sortableConfig.siteId; + let loc = utils.parseUrl(bidderRequest.refererInfo.referer); + + const sortableImps = utils._map(validBidReqs, bid => { + const rv = { + id: bid.bidId, + tagid: bid.params.tagId, + ext: {} + }; + const bannerMediaType = utils.deepAccess(bid, `mediaTypes.${BANNER}`); + const nativeMediaType = utils.deepAccess(bid, `mediaTypes.${NATIVE}`); + const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); + if (bannerMediaType || !(nativeMediaType || videoMediaType)) { + const bannerSizes = (bannerMediaType && bannerMediaType.sizes) || bid.sizes; + rv.banner = { + format: utils._map(bannerSizes, ([width, height]) => ({w: width, h: height})) + }; + } + if (nativeMediaType) { + rv.native = buildNativeRequest(nativeMediaType); + } + if (videoMediaType && videoMediaType.context === 'instream') { + const video = {placement: 1}; + video.mimes = videoMediaType.mimes || []; + video.minduration = utils.deepAccess(bid, 'params.video.minduration') || 10; + video.maxduration = utils.deepAccess(bid, 'params.video.maxduration') || 60; + const startDelay = utils.deepAccess(bid, 'params.video.startdelay'); + if (startDelay != null) { + video.startdelay = startDelay; + } + if (videoMediaType.playerSize && videoMediaType.playerSize.length) { + const size = videoMediaType.playerSize[0]; + video.w = size[0]; + video.h = size[1]; + } + if (videoMediaType.api) { + video.api = videoMediaType.api; + } + if (videoMediaType.protocols) { + video.protocols = videoMediaType.protocols; + } + if (videoMediaType.playbackmethod) { + video.playbackmethod = videoMediaType.playbackmethod; + } + rv.video = video; + } + if (bid.params.floor) { + rv.bidfloor = bid.params.floor; + } + if (bid.params.keywords) { + rv.ext.keywords = bid.params.keywords; + } + if (bid.params.bidderParams) { + utils._each(bid.params.bidderParams, (params, partner) => { + rv.ext[partner] = params; + }); + } + if (bid.params.floorSizeMap) { + rv.ext.floorSizeMap = bid.params.floorSizeMap; + } + return rv; + }); + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const sortableBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: sortableImps, + source: { + ext: { + schain: validBidReqs[0].schain + } + }, + regs: { + ext: {} + }, + site: { + domain: loc.hostname, + page: loc.href, + ref: loc.href, + publisher: { + id: globalSiteId || validBidReqs[0].params.siteId, + }, + device: { + w: screen.width, + h: screen.height + }, + }, + }; + if (bidderRequest && bidderRequest.timeout > 0) { + sortableBidReq.tmax = bidderRequest.timeout; + } + if (gdprConsent) { + sortableBidReq.user = { + ext: { + consent: gdprConsent.consentString + } + }; + if (typeof gdprConsent.gdprApplies == 'boolean') { + sortableBidReq.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0 + } + } + if (bidderRequest.uspConsent) { + sortableBidReq.regs.ext.us_privacy = bidderRequest.uspConsent; + } + return { + method: 'POST', + url: `${SERVER_URL}/openrtb2/auction?src=$$REPO_AND_VERSION$$&host=${loc.hostname}`, + data: JSON.stringify(sortableBidReq), + options: {contentType: 'text/plain'} + }; + }, + + interpretResponse: function(serverResponse) { + const { body: {id, seatbid} } = serverResponse; + const sortableBids = []; + if (id && seatbid) { + utils._each(seatbid, seatbid => { + utils._each(seatbid.bid, bid => { + const bidObj = { + requestId: bid.impid, + cpm: parseFloat(bid.price), + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + dealId: bid.dealid || null, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ttl: 60 + }; + if (bid.adm) { + const adFormat = utils.deepAccess(bid, 'ext.ad_format') + if (adFormat === 'native') { + let native = tryParseNativeResponse(bid.adm); + if (!native) { + return; + } + bidObj.mediaType = NATIVE; + bidObj.native = interpretNativeResponse(native); + } else if (adFormat === 'instream') { + bidObj.mediaType = VIDEO; + bidObj.vastXml = bid.adm; + } else { + bidObj.mediaType = BANNER; + bidObj.ad = bid.adm; + if (bid.nurl) { + bidObj.ad += utils.createTrackPixelHtml(decodeURIComponent(bid.nurl)); + } + } + } else if (bid.nurl) { + bidObj.adUrl = bid.nurl; + } + if (bid.ext) { + bidObj[BIDDER_CODE] = bid.ext; + } + sortableBids.push(bidObj); + }); + }); + } + return sortableBids; + }, + + getUserSyncs: (syncOptions, responses) => { + const syncs = []; + if (syncOptions.iframeEnabled) { + transformSyncs(responses, 'iframe', syncs); + } + if (syncOptions.pixelEnabled) { + transformSyncs(responses, 'image', syncs); + } + return syncs; + }, + + onTimeout(details) { + fetch(`${SERVER_URL}/prebid/timeout`, { + method: 'POST', + body: JSON.stringify(details), + mode: 'no-cors', + headers: new Headers({ + 'Content-Type': 'text/plain' + }) + }); + } +}; + +registerBidder(spec); diff --git a/modules/sortableBidAdapter.md b/modules/sortableBidAdapter.md index 027d6390e87..c24ad85b752 100644 --- a/modules/sortableBidAdapter.md +++ b/modules/sortableBidAdapter.md @@ -16,7 +16,11 @@ Sortable's adapter integration to the Prebid library. Posts plain-text JSON to t var adUnits = [ { code: 'test-pb-leaderboard', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]], + } + }, bids: [{ bidder: 'sortable', params: { @@ -30,7 +34,11 @@ var adUnits = [ }] }, { code: 'test-pb-banner', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, bids: [{ bidder: 'sortable', params: { @@ -40,7 +48,11 @@ var adUnits = [ }] }, { code: 'test-pb-sidebar', - size: [[160, 600]], + mediaTypes: { + banner: { + sizes: [[160, 600]], + } + }, bids: [{ bidder: 'sortable', params: { @@ -51,6 +63,47 @@ var adUnits = [ } } }] + }, { + code: 'test-pb-native', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + sizes: [790, 294], + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'sortable', + params: { + tagId: 'test-pb-native', + siteId: 'prebid.example.com' + } + }] + }, { + code: 'test-pb-video', + mediaTypes: { + video: { + playerSize: [640,480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'sortable', + params: { + tagId: 'test-pb-video', + siteId: 'prebid.example.com' + } + } + ] } ] ``` diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js new file mode 100644 index 00000000000..0d2576edb05 --- /dev/null +++ b/modules/sovrnAnalyticsAdapter.js @@ -0,0 +1,327 @@ +import adapter from '../src/AnalyticsAdapter.js' +import adaptermanager from '../src/adapterManager.js' +import CONSTANTS from '../src/constants.json' +import {ajaxBuilder} from '../src/ajax.js' +import * as utils from '../src/utils.js' +import {config} from '../src/config.js' +import find from 'core-js-pure/features/array/find.js' +import includes from 'core-js-pure/features/array/includes.js' + +const ajax = ajaxBuilder(0) + +const { + EVENTS: { + AUCTION_END, + BID_REQUESTED, + BID_ADJUSTMENT, + BID_RESPONSE, + BID_WON + } +} = CONSTANTS + +let pbaUrl = 'https://pba.aws.lijit.com/analytics' +let currentAuctions = {}; +const analyticsType = 'endpoint' + +const getClosestTop = () => { + let topFrame = window; + let err = false; + try { + while (topFrame.parent.document !== topFrame.document) { + if (topFrame.parent.document) { + topFrame = topFrame.parent; + } else { + throw new Error(); + } + } + } catch (e) { + // bException = true; + } + + return { + topFrame, + err + }; +}; + +const getBestPageUrl = ({err: crossDomainError, topFrame}) => { + let sBestPageUrl = ''; + + if (!crossDomainError) { + // easy case- we can get top frame location + sBestPageUrl = topFrame.location.href; + } else { + try { + try { + sBestPageUrl = window.top.location.href; + } catch (e) { + let aOrigins = window.location.ancestorOrigins; + sBestPageUrl = aOrigins[aOrigins.length - 1]; + } + } catch (e) { + sBestPageUrl = topFrame.document.referrer; + } + } + + return sBestPageUrl; +}; +const rootURL = getBestPageUrl(getClosestTop()) + +let sovrnAnalyticsAdapter = Object.assign(adapter({url: pbaUrl, analyticsType}), { + track({ eventType, args }) { + try { + if (eventType === BID_WON) { + new BidWinner(this.sovrnId, args).send(); + return + } + if (args && args.auctionId && currentAuctions[args.auctionId] && currentAuctions[args.auctionId].status === 'complete') { + throw new Error('Event Received after Auction Close Auction Id ' + args.auctionId) + } + if (args && args.auctionId && currentAuctions[args.auctionId] === undefined) { + currentAuctions[args.auctionId] = new AuctionData(this.sovrnId, args.auctionId) + } + switch (eventType) { + case BID_REQUESTED: + currentAuctions[args.auctionId].bidRequested(args) + break + case BID_ADJUSTMENT: + currentAuctions[args.auctionId].originalBid(args) + break + case BID_RESPONSE: + currentAuctions[args.auctionId].adjustedBid(args) + break + case AUCTION_END: + currentAuctions[args.auctionId].send(); + break + } + } catch (e) { + new LogError(e, this.sovrnId, {eventType, args}).send() + } + }, +}) + +sovrnAnalyticsAdapter.getAuctions = function () { + return currentAuctions; +}; + +sovrnAnalyticsAdapter.originEnableAnalytics = sovrnAnalyticsAdapter.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +sovrnAnalyticsAdapter.enableAnalytics = function (config) { + let sovrnId = '' + if (config && config.options && (config.options.sovrnId || config.options.affiliateId)) { + sovrnId = config.options.sovrnId || config.options.affiliateId; + } else { + utils.logError('Need Sovrn Id to log auction results. Please contact a Sovrn representative if you do not know your Sovrn Id.') + return + } + sovrnAnalyticsAdapter.sovrnId = sovrnId; + if (config.options.pbaUrl) { + pbaUrl = config.options.pbaUrl; + } + sovrnAnalyticsAdapter.originEnableAnalytics(config) // call the base class function +}; + +adaptermanager.registerAnalyticsAdapter({ + adapter: sovrnAnalyticsAdapter, + code: 'sovrn' +}); + +/** Class Representing a Winning Bid */ +class BidWinner { + /** + * Creates a new bid winner + * @param {string} sovrnId - the affiliate id from the analytics config + * @param {*} event - the args object from the auction event + */ + constructor(sovrnId, event) { + this.body = {} + // eslint-disable-next-line no-undef + this.body.prebidVersion = $$REPO_AND_VERSION$$ + this.body.sovrnId = sovrnId + this.body.winningBid = JSON.parse(JSON.stringify(event)) + this.body.url = rootURL + this.body.payload = 'winner' + delete this.body.winningBid.ad + } + + /** + * Sends the auction to the the ingest server + */ + send() { + this.body.ts = utils.timestamp() + ajax( + pbaUrl, + null, + JSON.stringify(this.body), + { + contentType: 'application/json', + method: 'POST', + } + ) + } +} + +/** Class representing an Auction */ +class AuctionData { + /** + * Create a new auction data collector + * @param {string} sovrnId - the affiliate id from the analytics config + * @param {string} auctionId - the auction id from the auction event + */ + constructor(sovrnId, auctionId) { + this.auction = {} + // eslint-disable-next-line no-undef + this.auction.prebidVersion = $$REPO_AND_VERSION$$ + this.auction.sovrnId = sovrnId + this.auction.auctionId = auctionId + this.auction.payload = 'auction' + this.auction.timeouts = { + buffer: config.getConfig('timeoutBuffer'), + bidder: config.getConfig('bidderTimeout'), + } + this.auction.priceGranularity = config.getConfig('priceGranularity') + this.auction.url = rootURL + this.auction.requests = [] + this.auction.unsynced = [] + this.dropBidFields = ['auctionId', 'ad', 'requestId', 'bidderCode'] + + setTimeout(function(id) { + delete currentAuctions[id] + }, 300000, this.auction.auctionId) + } + + /** + * Record a bid request event + * @param {*} event - the args object from the auction event + */ + bidRequested(event) { + const eventCopy = JSON.parse(JSON.stringify(event)) + delete eventCopy.doneCbCallCount + delete eventCopy.auctionId + this.auction.requests.push(eventCopy) + } + + /** + * Finds the bid from the auction that the event is associated with + * @param {*} event - the args object from the auction event + * @return {*} - the bid + */ + findBid(event) { + const bidder = find(this.auction.requests, r => (r.bidderCode === event.bidderCode)) + if (!bidder) { + this.auction.unsynced.push(JSON.parse(JSON.stringify(event))) + } + let bid = find(bidder.bids, b => (b.bidId === event.requestId)) + + if (!bid) { + event.unmatched = true + bidder.bids.push(JSON.parse(JSON.stringify(event))) + } + return bid + } + + /** + * Records the original bid before any adjustments have been made + * @param {*} event - the args object from the auction event + * NOTE: the bid adjustment occurs before the bid response + * the bid adjustment seems to be the bid ready to be adjusted + */ + originalBid(event) { + let bid = this.findBid(event) + if (bid) { + Object.assign(bid, JSON.parse(JSON.stringify(event))) + this.dropBidFields.forEach((f) => delete bid[f]) + } + } + + /** + * Replaces original values with adjusted values and records the original values for changed values + * in bid.originalValues + * @param {*} event - the args object from the auction event + */ + adjustedBid(event) { + let bid = this.findBid(event) + if (bid) { + bid.originalValues = Object.keys(event).reduce((o, k) => { + if (JSON.stringify(bid[k]) !== JSON.stringify(event[k]) && !includes(this.dropBidFields, k)) { + o[k] = bid[k] + bid[k] = event[k] + } + return o + }, {}) + } + } + + /** + * Sends the auction to the the ingest server + */ + send() { + let maxBids = {} + this.auction.requests.forEach(request => { + request.bids.forEach(bid => { + maxBids[bid.adUnitCode] = maxBids[bid.adUnitCode] || {cpm: 0} + if (bid.cpm > maxBids[bid.adUnitCode].cpm) { + maxBids[bid.adUnitCode] = bid + } + }) + }) + Object.keys(maxBids).forEach(unit => { + maxBids[unit].isAuctionWinner = true + }) + this.auction.ts = utils.timestamp() + ajax( + pbaUrl, + () => { + currentAuctions[this.auction.auctionId] = {status: 'complete', auctionId: this.auction.auctionId} + }, + JSON.stringify(this.auction), + { + contentType: 'application/json', + method: 'POST', + } + ) + } +} +class LogError { + constructor(e, sovrnId, data) { + this.error = {} + this.error.payload = 'error' + this.error.message = e.message + this.error.stack = e.stack + this.error.data = data + // eslint-disable-next-line no-undef + this.error.prebidVersion = $$REPO_AND_VERSION$$ + this.error.sovrnId = sovrnId + this.error.url = rootURL + this.error.userAgent = navigator.userAgent + } + send() { + if (this.error.data && this.error.data.requests) { + this.error.data.requests.forEach(request => { + if (request.bids) { + request.bids.forEach(bid => { + if (bid.ad) { + delete bid.ad + } + }) + } + }) + } + if (ErrorEvent.data && this.error.data.ad) { + delete this.error.data.ad + } + this.error.ts = utils.timestamp() + ajax( + pbaUrl, + null, + JSON.stringify(this.error), + { + contentType: 'application/json', + method: 'POST', + } + ) + } +} + +export default sovrnAnalyticsAdapter; diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index daaa3da3aac..f3260668b74 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,7 +1,6 @@ -import * as utils from '../src/utils' -import {parse} from '../src/url' -import { registerBidder } from '../src/adapters/bidderFactory' -import { BANNER } from '../src/mediaTypes' +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER } from '../src/mediaTypes.js' export const spec = { code: 'sovrn', @@ -26,18 +25,13 @@ export const spec = { let sovrnImps = []; let iv; let schain; - let digitrust; + let unifiedID; utils._each(bidReqs, function (bid) { - if (!digitrust) { - const bidRequestDigitrust = utils.deepAccess(bid, 'userId.digitrustid.data'); - if (bidRequestDigitrust && (!bidRequestDigitrust.privacy || !bidRequestDigitrust.privacy.optout)) { - digitrust = { - id: bidRequestDigitrust.id, - keyv: bidRequestDigitrust.keyv - } - } + if (!unifiedID) { + unifiedID = utils.deepAccess(bid, 'userId.tdid'); } + if (bid.schain) { schain = schain || bid.schain; } @@ -48,6 +42,7 @@ export const spec = { bidSizes = bidSizes.filter(size => utils.isArray(size)) const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})) sovrnImps.push({ + adunitcode: bid.adUnitCode, id: bid.bidId, banner: { format: processedSizes, @@ -62,7 +57,7 @@ export const spec = { const page = bidderRequest.refererInfo.referer // clever trick to get the domain - const domain = parse(page).hostname + const domain = utils.parseUrl(page).hostname const sovrnBidReq = { id: utils.getUniqueIdentifierStr(), @@ -81,26 +76,30 @@ export const spec = { }; } - if (bidderRequest && bidderRequest.gdprConsent) { - sovrnBidReq.regs = { - ext: { - gdpr: +bidderRequest.gdprConsent.gdprApplies - }}; - sovrnBidReq.user = { - ext: { - consent: bidderRequest.gdprConsent.consentString - }}; + if (bidderRequest.gdprConsent) { + utils.deepSetValue(sovrnBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + utils.deepSetValue(sovrnBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString) + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(sovrnBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - if (digitrust) { - utils.deepSetValue(sovrnBidReq, 'user.ext.digitrust', { - id: digitrust.id, - keyv: digitrust.keyv - }) + if (unifiedID) { + const idArray = [{ + source: 'adserver.org', + uids: [ + { + id: unifiedID, + ext: { + rtiPartner: 'TDID' + } + } + ] + }] + utils.deepSetValue(sovrnBidReq, 'user.ext.eids', idArray) } - let url = `https://ap.lijit.com/rtb/bid?` + - `src=$$REPO_AND_VERSION$$`; + let url = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; if (iv) url += `&iv=${iv}`; return { @@ -110,8 +109,7 @@ export const spec = { options: {contentType: 'text/plain'} } } catch (e) { - console.log('error in build:') - console.log(e) + utils.logError('Could not build bidrequest, error deatils:', e); } }, @@ -146,26 +144,30 @@ export const spec = { } return sovrnBidResponses } catch (e) { - console.log('error in interpret:') - console.log(e) + utils.logError('Could not intrepret bidresponse, error deatils:', e); } }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { try { - let tracks = [] + const tracks = [] if (serverResponses && serverResponses.length !== 0) { if (syncOptions.iframeEnabled) { - let iidArr = serverResponses.filter(resp => utils.deepAccess(resp, 'body.ext.iid')) + const iidArr = serverResponses.filter(resp => utils.deepAccess(resp, 'body.ext.iid')) .map(resp => resp.body.ext.iid); - let consentString = ''; + const params = []; if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { - consentString = gdprConsent.consentString + params.push(['gdpr_consent', gdprConsent.consentString]); + } + if (uspConsent) { + params.push(['us_privacy', uspConsent]); } + if (iidArr[0]) { + params.push(['informer', iidArr[0]]); tracks.push({ type: 'iframe', - url: 'https://ap.lijit.com/beacon?informer=' + iidArr[0] + '&gdpr_consent=' + consentString, + url: 'https://ap.lijit.com/beacon?' + params.map(p => p.join('=')).join('&') }); } } @@ -177,7 +179,6 @@ export const spec = { .forEach(url => tracks.push({ type: 'image', url })) } } - return tracks } catch (e) { return [] diff --git a/modules/sovrnBidAdapter.md b/modules/sovrnBidAdapter.md index 3b189016a9f..2b5d21d5515 100644 --- a/modules/sovrnBidAdapter.md +++ b/modules/sovrnBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Sovrn Bid Adapter Module Type: Bidder Adapter -Maintainer: trand@sovrn.com +Maintainer: jrosendahl@sovrn.com ``` # Description @@ -44,4 +44,4 @@ var adUnits = [ }] } ] -``` \ No newline at end of file +``` diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index dda9406d391..a8d874c57e9 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils'; -import { Renderer } from '../src/Renderer'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import { Renderer } from '../src/Renderer.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'spotx'; const URL = 'https://search.spotxchange.com/openrtb/2.3/dados/'; @@ -10,6 +10,7 @@ export const GOOGLE_CONSENT = { consented_providers: ['3', '7', '11', '12', '15' export const spec = { code: BIDDER_CODE, + gvlid: 165, aliases: ['spotx'], supportedMediaTypes: [VIDEO], @@ -159,6 +160,22 @@ export const spec = { spotxReq.video.startdelay = 0 + Boolean(utils.getBidIdParameter('start_delay', bid.params)); } + if (utils.getBidIdParameter('min_duration', bid.params) != '') { + spotxReq.video.minduration = utils.getBidIdParameter('min_duration', bid.params); + } + + if (utils.getBidIdParameter('max_duration', bid.params) != '') { + spotxReq.video.maxduration = utils.getBidIdParameter('max_duration', bid.params); + } + + if (utils.getBidIdParameter('placement_type', bid.params) != '') { + spotxReq.video.ext.placement = utils.getBidIdParameter('placement_type', bid.params); + } + + if (utils.getBidIdParameter('position', bid.params) != '') { + spotxReq.video.ext.pos = utils.getBidIdParameter('position', bid.params); + } + if (bid.crumbs && bid.crumbs.pubcid) { pubcid = bid.crumbs.pubcid; } @@ -202,14 +219,14 @@ export const spec = { userExt.consent = bidderRequest.gdprConsent.consentString; if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - requestPayload.regs = { - ext: { - gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) - } - }; + utils.deepSetValue(requestPayload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } } + if (bidderRequest && bidderRequest.uspConsent) { + utils.deepSetValue(requestPayload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + // ID5 fied if (bid && bid.userId && bid.userId.id5id) { userExt.eids = userExt.eids || []; @@ -308,11 +325,17 @@ export const spec = { channel_id: serverResponseBody.id, cache_key: spotxBid.ext.cache_key, vastUrl: 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key, + videoCacheKey: spotxBid.ext.cache_key, mediaType: VIDEO, width: spotxBid.w, height: spotxBid.h }; + bid.meta = bid.meta || {}; + if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) { + bid.meta.advertiserDomains = spotxBid.adomain; + } + const context1 = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); const context2 = utils.deepAccess(currentBidRequest, 'params.ad_unit'); if (context1 == 'outstream' || context2 == 'outstream') { diff --git a/modules/staqAnalyticsAdapter.js b/modules/staqAnalyticsAdapter.js index eeeef700a7e..4e3914bccdd 100644 --- a/modules/staqAnalyticsAdapter.js +++ b/modules/staqAnalyticsAdapter.js @@ -1,10 +1,12 @@ -import adapter from '../src/AnalyticsAdapter'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; -import { getRefererInfo } from '../src/refererDetection'; -import { parse } from '../src/url'; -import * as utils from '../src/utils'; -import { ajax } from '../src/ajax'; +import adapterManager from '../src/adapterManager.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storageObj = getStorageManager(); const ANALYTICS_VERSION = '1.0.0'; const DEFAULT_QUEUE_TIMEOUT = 4000; @@ -212,10 +214,10 @@ const ORGANIC = '(organic)'; export let storage = { getItem: (name) => { - return localStorage.getItem(name); + return storageObj.getDataFromLocalStorage(name); }, setItem: (name, value) => { - localStorage.setItem(name, value); + storageObj.setDataInLocalStorage(name, value); } }; @@ -246,7 +248,7 @@ export function getUmtSource(pageUrl, referrer) { if (se) { return asUtm(se, ORGANIC, ORGANIC); } - let parsedUrl = parse(pageUrl); + let parsedUrl = utils.parseUrl(pageUrl); let [refHost, refPath] = getReferrer(referrer); if (refHost && refHost !== parsedUrl.hostname) { return asUtm(refHost, REFERRAL, REFERRAL, '', refPath); @@ -273,12 +275,12 @@ export function getUmtSource(pageUrl, referrer) { } function getReferrer(referrer) { - let ref = parse(referrer); + let ref = utils.parseUrl(referrer); return [ref.hostname, ref.pathname]; } function getUTM(pageUrl) { - let urlParameters = parse(pageUrl).search; + let urlParameters = utils.parseUrl(pageUrl).search; if (!urlParameters['utm_campaign'] || !urlParameters['utm_source']) { return; } diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js index b8fb58c0ab3..2200e01b089 100644 --- a/modules/stvBidAdapter.js +++ b/modules/stvBidAdapter.js @@ -1,8 +1,8 @@ -import * as utils from '../src/utils'; -import {config} from '../src/config'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'stv'; const VADS_ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js index fce3df7675b..1f8cb59f442 100644 --- a/modules/sublimeBidAdapter.js +++ b/modules/sublimeBidAdapter.js @@ -1,135 +1,217 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; const BIDDER_CODE = 'sublime'; +const BIDDER_GVLID = 114; const DEFAULT_BID_HOST = 'pbjs.sskzlabs.com'; const DEFAULT_CURRENCY = 'EUR'; const DEFAULT_PROTOCOL = 'https'; const DEFAULT_TTL = 600; -const SUBLIME_VERSION = '0.4.0'; +const SUBLIME_ANTENNA = 'antenna.ayads.co'; +const SUBLIME_VERSION = '0.5.2'; + +/** + * Debug log message + * @param {String} msg + * @param {Object=} obj + */ +export function log(msg, obj) { + utils.logInfo('SublimeBidAdapter - ' + msg, obj); +} + +// Default state +export const state = { + zoneId: '', + transactionId: '' +}; -export const spec = { - code: BIDDER_CODE, - aliases: [], +/** + * Set a new state + * @param {Object} value + */ +export function setState(value) { + Object.assign(state, value); + log('State has been updated :', state); +} + +/** + * Send pixel to our debug endpoint + * @param {string} eventName - Event name that will be send in the e= query string + */ +export function sendEvent(eventName) { + const ts = Date.now(); + const eventObject = { + t: ts, + tse: ts, + z: state.zoneId, + e: eventName, + src: 'pa', + puid: state.transactionId, + trId: state.transactionId, + ver: SUBLIME_VERSION, + }; + + log('Sending pixel for event: ' + eventName, eventObject); + + const queryString = utils.formatQS(eventObject); + utils.triggerPixel('https://' + SUBLIME_ANTENNA + '/?' + queryString); +} + +/** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return {Boolean} True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid(bid) { + return !!Number(bid.params.zoneId); +} + +/** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - An array of bids + * @param {Object} bidderRequest - Info describing the request to the server. + * @return {ServerRequest|ServerRequest[]} - Info describing the request to the server. + */ +function buildRequests(validBidRequests, bidderRequest) { + const commonPayload = { + pbav: SUBLIME_VERSION, + // Current Prebid params + prebidVersion: '$prebid.version$', + currencyCode: config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY, + timeout: (typeof bidderRequest === 'object' && !!bidderRequest) ? bidderRequest.timeout : config.getConfig('bidderTimeout'), + }; + + // RefererInfo + if (bidderRequest && bidderRequest.refererInfo) { + commonPayload.referer = bidderRequest.refererInfo.referer; + commonPayload.numIframes = bidderRequest.refererInfo.numIframes; + } + // GDPR handling + if (bidderRequest && bidderRequest.gdprConsent) { + commonPayload.gdprConsent = bidderRequest.gdprConsent.consentString; + commonPayload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + + return validBidRequests.map(bid => { + const bidHost = bid.params.bidHost || DEFAULT_BID_HOST; + const protocol = bid.params.protocol || DEFAULT_PROTOCOL; + + setState({ + transactionId: bid.transactionId, + zoneId: bid.params.zoneId, + debug: bid.params.debug || false, + }); - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: (bid) => { - return !!bid.params.zoneId; - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests An array of bids - * @param {Object} bidderRequest - Info describing the request to the server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: (validBidRequests, bidderRequest) => { - let commonPayload = { - sublimeVersion: SUBLIME_VERSION, - // Current Prebid params - prebidVersion: '$prebid.version$', - currencyCode: config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY, - timeout: config.getConfig('bidderTimeout'), + const bidPayload = { + adUnitCode: bid.adUnitCode, + auctionId: bid.auctionId, + bidder: bid.bidder, + bidderRequestId: bid.bidderRequestId, + bidRequestsCount: bid.bidRequestsCount, + requestId: bid.bidId, + sizes: bid.sizes.map(size => ({ + w: size[0], + h: size[1], + })), + transactionId: bid.transactionId, + zoneId: bid.params.zoneId, }; - // RefererInfo - if (bidderRequest && bidderRequest.refererInfo) { - commonPayload.referer = bidderRequest.refererInfo.referer; - commonPayload.numIframes = bidderRequest.refererInfo.numIframes; + const payload = Object.assign({}, commonPayload, bidPayload); + + return { + method: 'POST', + url: protocol + '://' + bidHost + '/bid', + data: payload, + options: { + contentType: 'application/json', + withCredentials: true + }, } - // GDPR handling - if (bidderRequest && bidderRequest.gdprConsent) { - commonPayload.gdprConsent = bidderRequest.gdprConsent.consentString; - commonPayload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + }); +} + +/** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequest An object with bid request informations + * @return {Bid[]} An array of bids which were nested inside the server. + */ +function interpretResponse(serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + + if (response) { + if (response.timeout || !response.ad || //gmi.test(response.ad)) { + return bidResponses; } - return validBidRequests.map(bid => { - let bidPayload = { - adUnitCode: bid.adUnitCode, - auctionId: bid.auctionId, - bidder: bid.bidder, - bidderRequestId: bid.bidderRequestId, - bidRequestsCount: bid.bidRequestsCount, - requestId: bid.bidId, - sizes: bid.sizes.map(size => ({ - w: size[0], - h: size[1], - })), - transactionId: bid.transactionId, - zoneId: bid.params.zoneId, - }; + // Setting our returned sizes object to default values + let returnedSizes = { + width: 1800, + height: 1000 + }; - let protocol = bid.params.protocol || DEFAULT_PROTOCOL; - let bidHost = bid.params.bidHost || DEFAULT_BID_HOST; - let payload = Object.assign({}, commonPayload, bidPayload); - - return { - method: 'POST', - url: protocol + '://' + bidHost + '/bid', - data: payload, - options: { - contentType: 'application/json', - withCredentials: true - }, - }; - }); - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @param {*} bidRequest An object with bid request informations - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (serverResponse, bidRequest) => { - const bidResponses = []; - const response = serverResponse.body; - - if (response) { - if (response.timeout || !response.ad || response.ad.match(//gmi)) { - return bidResponses; - } - - // Setting our returned sizes object to default values - let returnedSizes = { - width: 1800, - height: 1000 + // Verifying Banner sizes + if (bidRequest && bidRequest.data && bidRequest.data.w === 1 && bidRequest.data.h === 1) { + // If banner sizes are 1x1 we set our default size object to 1x1 + returnedSizes = { + width: 1, + height: 1 }; + } - // Verifying Banner sizes - if (bidRequest && bidRequest.data && bidRequest.data.w === 1 && bidRequest.data.h === 1) { - // If banner sizes are 1x1 we set our default size object to 1x1 - returnedSizes = { - width: 1, - height: 1 - }; - } - - const bidResponse = { - requestId: response.requestId || '', - cpm: response.cpm || 0, - width: response.width || returnedSizes.width, - height: response.height || returnedSizes.height, - creativeId: response.creativeId || 1, - dealId: response.dealId || 1, - currency: response.currency || DEFAULT_CURRENCY, - netRevenue: response.netRevenue || true, - ttl: response.ttl || DEFAULT_TTL, - ad: response.ad, - }; + const bidResponse = { + requestId: response.requestId || '', + cpm: response.cpm || 0, + width: response.width || returnedSizes.width, + height: response.height || returnedSizes.height, + creativeId: response.creativeId || 1, + dealId: response.dealId || 1, + currency: response.currency || DEFAULT_CURRENCY, + netRevenue: response.netRevenue || true, + ttl: response.ttl || DEFAULT_TTL, + ad: response.ad, + pbav: SUBLIME_VERSION + }; - bidResponses.push(bidResponse); - } + bidResponses.push(bidResponse); + } + + return bidResponses; +} + +/** + * Send pixel when bidWon event is triggered + * @param {Object} timeoutData + */ +function onBidWon(bid) { + log('Bid won', bid); + sendEvent('bidwon'); +} + +/** + * Send debug when we timeout + * @param {Object} timeoutData + */ +function onTimeout(timeoutData) { + log('Timeout from adapter', timeoutData); + sendEvent('bidtimeout'); +} - return bidResponses; - }, +export const spec = { + code: BIDDER_CODE, + gvlid: BIDDER_GVLID, + aliases: [], + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse, + onBidWon: onBidWon, + onTimeout: onTimeout, }; registerBidder(spec); diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 3064ac2063b..abbae1e7354 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -1,9 +1,10 @@ 'use strict'; -import { getAdUnitSizes, logWarn } from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, VIDEO } from '../src/mediaTypes'; -import includes from 'core-js/library/fn/array/includes'; +import { getAdUnitSizes, logWarn, deepSetValue } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import {config} from '../src/config.js'; const BID_HOST = 'https://prebid.technoratimedia.com'; const USER_SYNC_HOST = 'https://ad-cdn.technoratimedia.com'; @@ -44,6 +45,12 @@ export const spec = { }, imp: [] }; + + const schain = validBidReqs[0].schain; + if (schain) { + openRtbBidRequest.source = { ext: { schain } }; + } + let seatId = null; validBidReqs.forEach((bid, i) => { @@ -58,46 +65,31 @@ export const spec = { if (isNaN(bidFloor)) { logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`); } - let pos = parseInt(bid.params.pos); + let pos = parseInt(bid.params.pos, 10); if (isNaN(pos)) { logWarn(`Synacormedia: there is an invalid POS: ${bid.params.pos}`); pos = 0; } const videoOrBannerKey = this.isVideoBid(bid) ? 'video' : 'banner'; - getAdUnitSizes(bid) - .filter(size => BLOCKED_AD_SIZES.indexOf(size.join('x')) === -1) - .forEach((size, i) => { - if (!size || size.length != 2) { - return; - } - const size0 = size[0]; - const size1 = size[1]; - const imp = { - id: `${videoOrBannerKey.substring(0, 1)}${bid.bidId}-${size0}x${size1}`, - tagid: placementId - }; - if (bidFloor !== null && !isNaN(bidFloor)) { - imp.bidfloor = bidFloor; - } + const adSizes = getAdUnitSizes(bid) + .filter(size => BLOCKED_AD_SIZES.indexOf(size.join('x')) === -1); - const videoOrBannerValue = { - w: size0, - h: size1, - pos - }; - if (videoOrBannerKey === 'video') { - if (bid.mediaTypes.video) { - this.setValidVideoParams(bid.mediaTypes.video, bid.params.video); - } - if (bid.params.video) { - this.setValidVideoParams(bid.params.video, videoOrBannerValue); - } - } - imp[videoOrBannerKey] = videoOrBannerValue; - openRtbBidRequest.imp.push(imp); - }); + let imps = []; + if (videoOrBannerKey === 'banner') { + imps = this.buildBannerImpressions(adSizes, bid, placementId, pos, bidFloor, videoOrBannerKey); + } else if (videoOrBannerKey === 'video') { + imps = this.buildVideoImpressions(adSizes, bid, placementId, pos, bidFloor, videoOrBannerKey); + } + if (imps.length > 0) { + imps.forEach(i => openRtbBidRequest.imp.push(i)); + } }); + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(openRtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + if (openRtbBidRequest.imp.length && seatId) { return { method: 'POST', @@ -111,12 +103,76 @@ export const spec = { } }, + buildBannerImpressions: function(adSizes, bid, placementId, pos, bidFloor, videoOrBannerKey) { + let format = []; + let imps = []; + adSizes.forEach((size, i) => { + if (!size || size.length !== 2) { + return; + } + + format.push({ + w: size[0], + h: size[1], + }); + }); + + if (format.length > 0) { + const imp = { + id: `${videoOrBannerKey.substring(0, 1)}${bid.bidId}`, + banner: { + format, + pos + }, + tagid: placementId, + }; + if (bidFloor !== null && !isNaN(bidFloor)) { + imp.bidfloor = bidFloor; + } + imps.push(imp); + } + return imps; + }, + + buildVideoImpressions: function(adSizes, bid, placementId, pos, bidFloor, videoOrBannerKey) { + let imps = []; + adSizes.forEach((size, i) => { + if (!size || size.length != 2) { + return; + } + const size0 = size[0]; + const size1 = size[1]; + const imp = { + id: `${videoOrBannerKey.substring(0, 1)}${bid.bidId}-${size0}x${size1}`, + tagid: placementId + }; + if (bidFloor !== null && !isNaN(bidFloor)) { + imp.bidfloor = bidFloor; + } + + const videoOrBannerValue = { + w: size0, + h: size1, + pos + }; + if (bid.mediaTypes.video) { + this.setValidVideoParams(bid.mediaTypes.video, bid.params.video); + } + if (bid.params.video) { + this.setValidVideoParams(bid.params.video, videoOrBannerValue); + } + imp[videoOrBannerKey] = videoOrBannerValue; + imps.push(imp); + }); + return imps; + }, + setValidVideoParams: function (sourceObj, destObj) { Object.keys(sourceObj) .filter(param => includes(VIDEO_PARAMS, param) && sourceObj[param] !== null && (!isNaN(parseInt(sourceObj[param], 10)) || !(sourceObj[param].length < 1))) .forEach(param => destObj[param] = Array.isArray(sourceObj[param]) ? sourceObj[param] : parseInt(sourceObj[param], 10)); }, - interpretResponse: function(serverResponse) { + interpretResponse: function(serverResponse, bidRequest) { const updateMacros = (bid, r) => { return r ? r.replace(/\${AUCTION_PRICE}/g, bid.price) : r; }; @@ -133,8 +189,33 @@ export const spec = { seatbid.bid.forEach(bid => { const creative = updateMacros(bid, bid.adm); const nurl = updateMacros(bid, bid.nurl); - const [, impType, impid, width, height] = bid.impid.match(/^([vb])(.*)-(.*)x(.*)$/); - const isVideo = impType == 'v'; + const [, impType, impid] = bid.impid.match(/^([vb])(.*)$/); + let height = bid.h; + let width = bid.w; + const isVideo = impType === 'v'; + const isBanner = impType === 'b'; + if ((!height || !width) && bidRequest.data && bidRequest.data.imp && bidRequest.data.imp.length > 0) { + bidRequest.data.imp.forEach(req => { + if (bid.impid === req.id) { + if (isVideo) { + height = req.video.h; + width = req.video.w; + } else if (isBanner) { + let bannerHeight = 1; + let bannerWidth = 1; + if (req.banner.format && req.banner.format.length > 0) { + bannerHeight = req.banner.format[0].h; + bannerWidth = req.banner.format[0].w; + } + height = bannerHeight; + width = bannerWidth; + } else { + height = 1; + width = 1; + } + } + }); + } const bidObj = { requestId: impid, adId: bid.id.replace(/~/g, '-'), @@ -150,7 +231,9 @@ export const spec = { }; if (isVideo) { const [, uuid] = nurl.match(/ID=([^&]*)&?/); - bidObj.videoCacheKey = encodeURIComponent(uuid); + if (!config.getConfig('cache.url')) { + bidObj.videoCacheKey = encodeURIComponent(uuid); + } bidObj.vastUrl = nurl; } bids.push(bidObj); diff --git a/modules/synacormediaBidAdapter.md b/modules/synacormediaBidAdapter.md index 3a00aa45fed..fd71f07b3a3 100644 --- a/modules/synacormediaBidAdapter.md +++ b/modules/synacormediaBidAdapter.md @@ -24,9 +24,11 @@ https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_cache_id_synacorm ``` var adUnits = [{ code: 'test-div', - sizes: [ - [300, 250] - ], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, bids: [{ bidder: "synacormedia", params: { @@ -40,8 +42,10 @@ https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_cache_id_synacorm code: 'test-div2', mediaTypes: { video: { - context: 'instream', - playerSize: [[300, 250]], + context: 'instream', + playerSize: [ + [300, 250] + ], } }, bids: [{ diff --git a/modules/taphypeBidAdapter.js b/modules/taphypeBidAdapter.js index 63224596e3d..65c5a728127 100644 --- a/modules/taphypeBidAdapter.js +++ b/modules/taphypeBidAdapter.js @@ -1,4 +1,4 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; export const spec = { code: 'taphype', diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 696bd325016..08ae1854669 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -1,5 +1,5 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; -const utils = require('../src/utils'); +import {registerBidder} from '../src/adapters/bidderFactory.js'; +const utils = require('../src/utils.js'); const BIDDER_CODE = 'teads'; const ENDPOINT_URL = 'https://a.teads.tv/hb/bid-request'; const gdprStatus = { @@ -41,6 +41,9 @@ export const spec = { const bids = validBidRequests.map(buildRequestObject); const payload = { referrer: getReferrerInfo(bidderRequest), + pageReferrer: document.referrer, + networkBandwidth: getConnectionDownLink(window.navigator), + timeToFirstByte: getTimeToFirstByte(window), data: bids, deviceWidth: screen.width, hb_version: '$prebid.version$' @@ -54,13 +57,20 @@ export const spec = { if (bidderRequest && gdpr) { let isCmp = (typeof gdpr.gdprApplies === 'boolean') let isConsentString = (typeof gdpr.consentString === 'string') - let status = isCmp ? findGdprStatus(gdpr.gdprApplies, gdpr.vendorData) : gdprStatus.CMP_NOT_FOUND_OR_ERROR + let status = isCmp + ? findGdprStatus(gdpr.gdprApplies, gdpr.vendorData, gdpr.apiVersion) + : gdprStatus.CMP_NOT_FOUND_OR_ERROR payload.gdpr_iab = { consent: isConsentString ? gdpr.consentString : '', - status: status + status: status, + apiVersion: gdpr.apiVersion }; } + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent + } + const payloadString = JSON.stringify(payload); return { method: 'POST', @@ -92,38 +102,14 @@ export const spec = { creativeId: bid.creativeId, placementId: bid.placementId }; + if (bid.dealId) { + bidResponse.dealId = bid.dealId + } bidResponses.push(bidResponse); }); } return bidResponses; }, - - getUserSyncs: function(syncOptions, responses, gdprConsent) { - let queryParams = { - hb_provider: 'prebid', - hb_version: '$prebid.version$' - }; - - if (gdprConsent) { - let gdprIab = { - status: findGdprStatus(gdprConsent.gdprApplies, gdprConsent.vendorData), - consent: gdprConsent.consentString - }; - - queryParams.gdprIab = JSON.stringify(gdprIab) - } - - if (utils.deepAccess(responses[0], 'body.responses.0.placementId')) { - queryParams.placementId = responses[0].body.responses[0].placementId - }; - - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://sync.teads.tv/iframe?' + utils.parseQueryStringParameters(queryParams) - }]; - } - } }; function getReferrerInfo(bidderRequest) { @@ -134,15 +120,55 @@ function getReferrerInfo(bidderRequest) { return ref; } -function findGdprStatus(gdprApplies, gdprData) { - let status = gdprStatus.GDPR_APPLIES_PUBLISHER; +function getConnectionDownLink(nav) { + return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : ''; +} + +function getTimeToFirstByte(win) { + const performance = win.performance || win.webkitPerformance || win.msPerformance || win.mozPerformance; + + const ttfbWithTimingV2 = performance && + typeof performance.getEntriesByType === 'function' && + Object.prototype.toString.call(performance.getEntriesByType) === '[object Function]' && + performance.getEntriesByType('navigation')[0] && + performance.getEntriesByType('navigation')[0].responseStart && + performance.getEntriesByType('navigation')[0].requestStart && + performance.getEntriesByType('navigation')[0].responseStart >= 0 && + performance.getEntriesByType('navigation')[0].requestStart >= 0 && + Math.round( + performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart + ); + + if (ttfbWithTimingV2) { + return ttfbWithTimingV2.toString(); + } + const ttfbWithTimingV1 = performance && + performance.timing.responseStart && + performance.timing.requestStart && + performance.timing.responseStart >= 0 && + performance.timing.requestStart >= 0 && + performance.timing.responseStart - performance.timing.requestStart; + + return ttfbWithTimingV1 ? ttfbWithTimingV1.toString() : ''; +} + +function findGdprStatus(gdprApplies, gdprData, apiVersion) { + let status = gdprStatus.GDPR_APPLIES_PUBLISHER if (gdprApplies) { - if (gdprData.hasGlobalScope || gdprData.hasGlobalConsent) status = gdprStatus.GDPR_APPLIES_GLOBAL + if (isGlobalConsent(gdprData, apiVersion)) status = gdprStatus.GDPR_APPLIES_GLOBAL } else status = gdprStatus.GDPR_DOESNT_APPLY return status; } +function isGlobalConsent(gdprData, apiVersion) { + return gdprData && apiVersion === 1 + ? (gdprData.hasGlobalScope || gdprData.hasGlobalConsent) + : gdprData && apiVersion === 2 + ? !gdprData.isServiceSpecific + : false +} + function buildRequestObject(bid) { const reqObj = {}; let placementId = utils.getValue(bid.params, 'placementId'); diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index 842abbf1015..acc20f6b183 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -1,8 +1,8 @@ -import * as utils from '../src/utils'; -import {createBid as createBidFactory} from '../src/bidfactory'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {VIDEO} from '../src/mediaTypes'; -import {STATUS} from '../src/constants'; +import * as utils from '../src/utils.js'; +import {createBid as createBidFactory} from '../src/bidfactory.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {STATUS} from '../src/constants.json'; const BIDDER_CODE = 'telaria'; const DOMAIN = 'tremorhub.com'; @@ -119,8 +119,8 @@ export const spec = { } }; -function getSrcPageUrl(params) { - return (params && params['srcPageUrl']) || encodeURIComponent(document.location.href); +function getDefaultSrcPageUrl() { + return encodeURIComponent(document.location.href); } function getEncodedValIfNotEmpty(val) { @@ -175,7 +175,10 @@ export const getTimeoutUrl = function(timeoutData) { if (!utils.isEmpty(params)) { let url = `https://${EVENTS_ENDPOINT}`; - url += `?srcPageUrl=${getSrcPageUrl(params)}`; + params = Object.assign({ + srcPageUrl: getDefaultSrcPageUrl() + }, params); + url += `${getUrlParams(params)}`; url += '&hb=1&evt=TO'; @@ -221,9 +224,12 @@ function generateUrl(bid, bidderRequest) { url += (`&playerHeight=${height}`); } - url += `${getUrlParams(bid.params, bid.schain)}`; + const params = Object.assign({ + srcPageUrl: getDefaultSrcPageUrl() + }, bid.params); + delete params.adCode; - url += `&srcPageUrl=${getSrcPageUrl(bid.params)}`; + url += `${getUrlParams(params, bid.schain)}`; url += (`&transactionId=${bid.transactionId}`); @@ -278,6 +284,11 @@ function createBid(status, reqBid, response, width, height, bidderCode) { }); } + bid.meta = bid.meta || {}; + if (response && response.adomain && response.adomain.length > 0) { + bid.meta.advertiserDomains = response.adomain; + } + return bid; } diff --git a/modules/terceptAnalyticsAdapter.js b/modules/terceptAnalyticsAdapter.js new file mode 100644 index 00000000000..54da2bd06d2 --- /dev/null +++ b/modules/terceptAnalyticsAdapter.js @@ -0,0 +1,144 @@ +import { ajax } from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import * as utils from '../src/utils.js'; + +const emptyUrl = ''; +const analyticsType = 'endpoint'; +const terceptAnalyticsVersion = 'v1.0.0'; +const defaultHostName = 'us-central1-quikr-ebay.cloudfunctions.net'; +const defaultPathName = '/prebid-analytics'; + +let initOptions; +let auctionTimestamp; +let events = { + bids: [] +}; + +var terceptAnalyticsAdapter = 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.mediaTypes.banner.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 { + 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.getWindowLocation(); + if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { + Object.assign(data.auctionInit, { host: location.host, path: location.pathname, search: location.search }); + } + data.initOptions = initOptions; + + let terceptAnalyticsRequestUrl = utils.buildUrl({ + protocol: 'https', + hostname: (initOptions && initOptions.hostName) || defaultHostName, + pathname: (initOptions && initOptions.pathName) || defaultPathName, + search: { + auctionTimestamp: auctionTimestamp, + terceptAnalyticsVersion: terceptAnalyticsVersion, + prebidVersion: $$PREBID_GLOBAL$$.version + } + }); + + ajax(terceptAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); +} + +terceptAnalyticsAdapter.originEnableAnalytics = terceptAnalyticsAdapter.enableAnalytics; +terceptAnalyticsAdapter.enableAnalytics = function (config) { + initOptions = config.options; + terceptAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: terceptAnalyticsAdapter, + code: 'tercept' +}); + +export default terceptAnalyticsAdapter; diff --git a/modules/terceptAnalyticsAdapter.md b/modules/terceptAnalyticsAdapter.md new file mode 100644 index 00000000000..39578d06730 --- /dev/null +++ b/modules/terceptAnalyticsAdapter.md @@ -0,0 +1,22 @@ +# Overview +Module Name: Tercept Analytics Adapter +Module Type: Analytics Adapter +Maintainer: gourav.chindlur@tercept.com + +# Description + +Analytics adapter for prebid provided by Tercept. Contact gourav.chindlur@tercept.com for information. + +# Test Parameters + +``` +{ + provider: 'Tercept', + options : { + pubId : 50357 //id provided by Tercept + pubKey: 'xxx' //key provided by Tercept + hostName: 'us-central1-quikr-ebay.cloudfunctions.net' //Tercept endpoint host + pathName: '/prebid-analytics' //Tercept endpoint path + } +} +``` diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index 61dfec86662..91e36077e88 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -1,15 +1,12 @@ -import * as utils from '../src/utils'; +import * as utils from '../src/utils.js'; import { BANNER, NATIVE, VIDEO -} from '../src/mediaTypes'; +} from '../src/mediaTypes.js'; import { registerBidder -} from '../src/adapters/bidderFactory'; -import { - parse as parseUrl -} from '../src/url'; +} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'theadx'; const ENDPOINT_URL = 'https://ssp.theadx.com/request'; @@ -317,7 +314,7 @@ export const spec = { } let buildSiteComponent = (bidRequest, bidderRequest) => { - let loc = parseUrl(bidderRequest.refererInfo.referer, { + let loc = utils.parseUrl(bidderRequest.refererInfo.referer, { decodeSearchAsString: true }); diff --git a/modules/timBidAdapter.js b/modules/timBidAdapter.js index b615140ce7c..68c711f9935 100644 --- a/modules/timBidAdapter.js +++ b/modules/timBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import * as bidfactory from '../src/bidfactory'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as bidfactory from '../src/bidfactory.js'; var CONSTANTS = require('../src/constants.json'); const BIDDER_CODE = 'tim'; diff --git a/modules/topRTBBidAdapter.js b/modules/topRTBBidAdapter.js index 783d4008b33..c93bd8ccaac 100644 --- a/modules/topRTBBidAdapter.js +++ b/modules/topRTBBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {BANNER, VIDEO} from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'topRTB'; const ENDPOINT_URL = 'https://ssp.toprtb.com/ssp/rest/ReqAd?ref=www.google.com&hbid=0&adUnitId='; diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 081638c0adb..ec9d30c0e29 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -1,8 +1,7 @@ /* eslint-disable no-tabs */ -import { registerBidder } from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; -import { BANNER } from '../src/mediaTypes'; -import {parse as parseUrl} from '../src/url'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; export const ADAPTER_VERSION = '1'; const SUPPORTED_AD_TYPES = [BANNER]; @@ -73,7 +72,7 @@ registerBidder(spec); * Creates site description object */ function createSite(refInfo) { - let url = parseUrl(refInfo.referer); + let url = utils.parseUrl(refInfo.referer); let site = { 'domain': url.hostname, 'page': url.protocol + '://' + url.hostname + url.pathname diff --git a/modules/trendqubeBidAdapter.js b/modules/trendqubeBidAdapter.js new file mode 100644 index 00000000000..d0364b4acec --- /dev/null +++ b/modules/trendqubeBidAdapter.js @@ -0,0 +1,102 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'trendqube'; +const AD_URL = 'https://ads.trendqube.com/?c=o&m=multi'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + const len = validBidRequests.length; + + for (let i = 0; i < len; i++) { + let bid = validBidRequests[i]; + let sizes + if (bid.mediaTypes) { + if (bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + sizes = bid.mediaTypes[BANNER].sizes + } else if (bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + sizes = bid.mediaTypes[VIDEO].playerSize + } + } + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + sizes: sizes || [], + wPlayer: sizes ? sizes[0] : 0, + hPlayer: sizes ? sizes[1] : 0, + traffic: bid.params.traffic || BANNER, + schain: bid.schain || {} + }); + } + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/trendqubeBidAdapter.md b/modules/trendqubeBidAdapter.md new file mode 100644 index 00000000000..8b72c225575 --- /dev/null +++ b/modules/trendqubeBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: trendqube Bidder Adapter +Module Type: trendqube Bidder Adapter +``` + +# Description + +Module that connects to trendqube demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'trendqube', + params: { + placementId: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'trendqube', + params: { + placementId: 0, + traffic: 'video' + } + } + ] + } + ]; +``` diff --git a/modules/tribeosBidAdapter.js b/modules/tribeosBidAdapter.js new file mode 100644 index 00000000000..80361aa3fdb --- /dev/null +++ b/modules/tribeosBidAdapter.js @@ -0,0 +1,165 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as bidfactory from '../src/bidfactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +var CONSTANTS = require('../src/constants.json'); + +const BIDDER_CODE = 'tribeos'; +const ENDPOINT_URL = 'https://bidder.tribeos.tech/prebid/'; +const LOG_PREFIX = 'TRIBEOS: '; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} + * bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if (utils.isEmpty(bid.params.placementId)) { + utils.logError(LOG_PREFIX, 'placementId is required, please contact tribeOS for placementId. Bid details: ', JSON.stringify(bid)); + return false; + } + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - + * an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests) { + var requests = []; + for (var i = 0; i < validBidRequests.length; i++) { + requests.push(this.buidRTBRequest(validBidRequests[i])); + } + return requests; + }, + buidRTBRequest: function(bidReq) { + // build bid request object + + var placementId = bidReq.params.placementId; + var bidFloor = bidReq.params.bidfloor; + var placementCode = bidReq.params.placementCode; + + var adWidth = bidReq.mediaTypes.banner.sizes[0][0]; + var adHeight = bidReq.mediaTypes.banner.sizes[0][1]; + + // build bid request with impressions + var bidRequest = { + id: utils.getUniqueIdentifierStr(), + imp: [{ + id: bidReq.bidId, + banner: { + w: adWidth, + h: adHeight + }, + tagid: placementCode, + bidfloor: bidFloor + }], + site: { + domain: window.location.host, + page: window.location.href, + publisher: { + id: placementId + } + }, + device: { + 'language': (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + 'w': adWidth, + 'h': adHeight, + 'js': 1, + 'ua': navigator.userAgent + } + }; + + // apply gdpr + if (bidReq.gdprConsent) { + bidRequest.regs = {ext: {gdpr: bidReq.gdprConsent.gdprApplies ? 1 : 0}}; + bidRequest.user = {ext: {consent: bidReq.gdprConsent.consentString}}; + } + + bidRequest.bidId = bidReq.bidId; + var url = ENDPOINT_URL + placementId + '/requests'; + if (!utils.isEmpty(bidReq.params.endpointUrl)) { + url = bidReq.params.endpointUrl + placementId + '/requests'; + } + + return { + method: 'POST', + url: url, + data: JSON.stringify(bidRequest), + options: { withCredentials: true, contentType: 'application/json' }, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} + * serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const responseBody = serverResponse.body; + + utils.logInfo(LOG_PREFIX, 'response body: ', JSON.stringify(serverResponse)); + + if ((!responseBody || !responseBody.id)) { + return []; + } + const bidResponses = []; + responseBody.seatbid[0].bid.forEach(function(bidderBid) { + var responsePrice; + var placementCode = ''; + if (bidRequest) { + var bidResponse = bidfactory.createBid(1); + placementCode = bidRequest.placementCode; + bidRequest.status = CONSTANTS.STATUS.GOOD; + responsePrice = parseFloat(bidderBid.price); + if (responsePrice === 0) { + var bid = bidfactory.createBid(2); + bid.bidderCode = BIDDER_CODE; + bidResponses.push(bid); + + utils.logInfo(LOG_PREFIX, 'response price is zero. Response data: ', JSON.stringify(bidRequest)); + + return bidResponses; + } + bidResponse.placementCode = placementCode; + bidResponse.size = bidRequest.sizes; + bidResponse.creativeId = bidderBid.crid; + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.cpm = responsePrice; + bidResponse.ad = bidderBid.adm; + bidResponse.width = parseInt(bidderBid.w); + bidResponse.height = parseInt(bidderBid.h); + bidResponse.currency = responseBody.cur; + bidResponse.netRevenue = true; + bidResponse.requestId = bidderBid.impid; + bidResponse.ttl = 180; + + utils.logInfo(LOG_PREFIX, 'bid response data: ', JSON.stringify(bidResponse)); + utils.logInfo(LOG_PREFIX, 'bid request data: ', JSON.stringify(bidRequest)); + + bidResponses.push(bidResponse); + } + }); + return bidResponses; + }, + /** + * Register bidder specific code, which will execute if a bid from this + * bidder won the auction + * + * @param {Bid} + * The bid that won the auction + */ +// onBidWon: function(bid) { +// ajax(this.nurls[bid.requestId], null); +// } + +} +registerBidder(spec); diff --git a/modules/tribeosBidAdapter.md b/modules/tribeosBidAdapter.md new file mode 100644 index 00000000000..670810abec9 --- /dev/null +++ b/modules/tribeosBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: tribeOS Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dev@tribeos.io +``` + +# Description + +tribeOS adapter + +# Test Parameters +``` + var adUnits = [{ + code: 'test-tribeos', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: "tribeos", + params: { + placementId: '12345' // REQUIRED + } + }] + }]; +``` diff --git a/modules/trionBidAdapter.js b/modules/trionBidAdapter.js new file mode 100644 index 00000000000..37d54e60cd2 --- /dev/null +++ b/modules/trionBidAdapter.js @@ -0,0 +1,208 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); + +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, bidderRequest) { + var bidRequests = []; + + for (var i = 0; i < validBidRequests.length; i++) { + var bid = validBidRequests[i]; + + var trionUrlParams = buildTrionUrlParams(bid, bidderRequest); + + bidRequests.push({ + method: 'GET', + url: BID_REQUEST_BASE_URL, + bidRequest: bid, + data: trionUrlParams + }); + } + return bidRequests; + }, + + 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; + + 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); + } + } + + return bidResponses; + }, + getUserSyncs: function getUserSyncs(syncOptions, serverResponses, gdprConsent, usPrivacy) { + if (syncOptions.iframeEnabled) { + handlePostMessage(); + return [{ + type: 'iframe', + url: getSyncUrl(gdprConsent, usPrivacy) + }]; + } + } + +}; +registerBidder(spec); + +function getSyncUrl(gdprConsent, usPrivacy) { + var unParsedPubAndSection = getStorageData(BASE_KEY + 'lps') || ':'; + var pubSectionArray = unParsedPubAndSection.split(':') || []; + var pubId = pubSectionArray[0] || -1; + var sectionId = pubSectionArray[1] || -1; + var url = getPublisherUrl(); + var consentParams = ''; + if (gdprConsent) { + if (gdprConsent.consentString) { + consentParams += '&gc=' + encodeURIComponent(gdprConsent.consentString); + } + consentParams += '&g=' + (gdprConsent.gdprApplies ? 1 : 0); + } + if (usPrivacy) { + consentParams = '&up=' + encodeURIComponent(usPrivacy); + } + return USER_SYNC_URL + `?p=${pubId}&s=${sectionId}${consentParams}&u=${url}`; +} + +function getPublisherUrl() { + var url = ''; + try { + if (window.top == window) { + url = window.location.href; + } else { + try { + url = window.top.location.href; + } catch (e) { + url = document.referrer; + } + } + } catch (e) { + } + return url +} + +function buildTrionUrlParams(bid, bidderRequest) { + var pubId = utils.getBidIdParameter('pubId', bid.params); + var sectionId = utils.getBidIdParameter('sectionId', bid.params); + var url = getPublisherUrl(); + var bidSizes = getBidSizesFromBidRequest(bid); + var sizes = utils.parseSizesInput(bidSizes).join(','); + var isAutomated = (navigator && navigator.webdriver) ? '1' : '0'; + var isHidden = (document.hidden) ? '1' : '0'; + var visibilityState = encodeURIComponent(document.visibilityState); + + var intT = window.TR_INT_T && window.TR_INT_T != -1 ? window.TR_INT_T : null; + if (!intT) { + intT = getStorageData(BASE_KEY + 'int_t'); + } + if (intT) { + setStorageData(BASE_KEY + 'int_t', intT) + } + 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, 'vers', '$prebid.version$'); + if (url) { + trionUrl += 'url=' + url + '&'; + } + if (sizes) { + trionUrl += 'sizes=' + sizes + '&'; + } + if (intT) { + trionUrl = utils.tryAppendQueryString(trionUrl, 'int_t', encodeURIComponent(intT)); + } + trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_wd', isAutomated); + trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_hd', isHidden); + trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_vs', visibilityState); + if (bidderRequest && bidderRequest.gdprConsent) { + var gdpr = bidderRequest.gdprConsent; + if (gdpr) { + if (gdpr.consentString) { + trionUrl = utils.tryAppendQueryString(trionUrl, 'gdprc', encodeURIComponent(gdpr.consentString)); + } + trionUrl = utils.tryAppendQueryString(trionUrl, 'gdpr', (gdpr.gdprApplies ? 1 : 0)); + } + } + if (bidderRequest && bidderRequest.uspConsent) { + trionUrl = utils.tryAppendQueryString(trionUrl, 'usp', encodeURIComponent(bidderRequest.uspConsent)); + } + // remove the trailing "&" + if (trionUrl.lastIndexOf('&') === trionUrl.length - 1) { + trionUrl = trionUrl.substring(0, trionUrl.length - 1); + } + return trionUrl; +} + +function getBidSizesFromBidRequest(bid) { + return (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) ? bid.mediaTypes.banner.sizes : bid.sizes; +} + +function handlePostMessage() { + try { + if (window.addEventListener) { + window.addEventListener('message', acceptPostMessage); + } + } catch (e) { + } +} + +export function getStorageData(key) { + var item = null; + try { + if (storage.hasLocalStorage()) { + item = storage.getDataFromLocalStorage(key); + } + } catch (e) { + } + return item; +} + +export function setStorageData(key, item) { + try { + if (storage.hasLocalStorage()) { + storage.setDataInLocalStorage(key, item); + } + } catch (e) { + } +} + +export function acceptPostMessage(e) { + var message = e.data || ''; + if (!message.indexOf || !message.split || message.indexOf(BASE_KEY + 'userId') !== 0) { + return; + } + var intT = message.split(BASE_KEY + 'userId=')[1]; + if (intT) { + setStorageData(BASE_KEY + 'int_t', intT); + } +} diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 7120929059d..5ed9db0a567 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -1,6 +1,7 @@ -import { BANNER } from '../src/mediaTypes'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import * as utils from '../src/utils'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'triplelift'; const STR_ENDPOINT = 'https://tlx.3lift.com/header/auction?'; @@ -10,9 +11,13 @@ let consentString = null; export const tripleliftAdapterSpec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - isBidRequestValid: function(bid) { - return (typeof bid.params.inventoryCode !== 'undefined'); + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function (bid) { + if (bid.mediaTypes.video) { + let video = _getORTBVideo(bid); + if (!video.w || !video.h) return false; + } + return typeof bid.params.inventoryCode !== 'undefined'; }, buildRequests: function(bidRequests, bidderRequest) { @@ -46,6 +51,13 @@ export const tripleliftAdapterSpec = { tlCall = utils.tryAppendQueryString(tlCall, 'us_privacy', bidderRequest.uspConsent); } +<<<<<<< HEAD +======= + if (config.getConfig('coppa') === true) { + tlCall = utils.tryAppendQueryString(tlCall, 'coppa', true); + } + +>>>>>>> upstream/master if (tlCall.lastIndexOf('&') === tlCall.length - 1) { tlCall = tlCall.substring(0, tlCall.length - 1); } @@ -102,20 +114,24 @@ function _getSyncType(syncOptions) { function _buildPostBody(bidRequests) { let data = {}; let { schain } = bidRequests[0]; - data.imp = bidRequests.map(function(bid, index) { - return { + data.imp = bidRequests.map(function(bidRequest, index) { + let imp = { id: index, - tagid: bid.params.inventoryCode, - floor: bid.params.floor, - banner: { - format: _sizes(bid.sizes) - } + tagid: bidRequest.params.inventoryCode, + floor: _getFloor(bidRequest) }; + if (bidRequest.mediaTypes.video) { + imp.video = _getORTBVideo(bidRequest); + } else if (bidRequest.mediaTypes.banner) { + imp.banner = { format: _sizes(bidRequest.sizes) }; + }; + return imp; }); let eids = [ ...getUnifiedIdEids(bidRequests), - ...getIdentityLinkEids(bidRequests) + ...getIdentityLinkEids(bidRequests), + ...getCriteoEids(bidRequests) ]; if (eids.length > 0) { @@ -132,6 +148,33 @@ function _buildPostBody(bidRequests) { return data; } +function _getORTBVideo(bidRequest) { + // give precedent to mediaTypes.video + let video = { ...bidRequest.params.video, ...bidRequest.mediaTypes.video }; + if (!video.w) video.w = video.playerSize[0][0]; + if (!video.h) video.h = video.playerSize[0][1]; + if (video.context === 'instream') video.placement = 1; + // clean up oRTB object + delete video.playerSize; + return video; +} + +function _getFloor (bid) { + let floor = null; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: _sizes(bid.sizes) + }); + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } + return floor !== null ? floor : bid.params.floor; +} + function getUnifiedIdEids(bidRequests) { return getEids(bidRequests, 'tdid', 'adserver.org', 'TDID'); } @@ -140,6 +183,10 @@ function getIdentityLinkEids(bidRequests) { return getEids(bidRequests, 'idl_env', 'liveramp.com', 'idl'); } +function getCriteoEids(bidRequests) { + return getEids(bidRequests, 'criteoId', 'criteo.com', 'criteoId'); +} + function getEids(bidRequests, type, source, rtiPartner) { return bidRequests .map(getUserId(type)) // bids -> userIds of a certain type @@ -181,10 +228,11 @@ function _buildResponseObject(bidderRequest, bid) { let height = bid.height || 1; let dealId = bid.deal_id || ''; let creativeId = bid.crid || ''; + let breq = bidderRequest.bids[bid.imp_id]; if (bid.cpm != 0 && bid.ad) { bidResponse = { - requestId: bidderRequest.bids[bid.imp_id].bidId, + requestId: breq.bidId, cpm: bid.cpm, width: width, height: height, @@ -193,7 +241,13 @@ function _buildResponseObject(bidderRequest, bid) { creativeId: creativeId, dealId: dealId, currency: 'USD', - ttl: 33, + ttl: 300, + tl_source: bid.tl_source + }; + + if (breq.mediaTypes.video) { + bidResponse.vastXml = bid.ad; + bidResponse.mediaType = 'video'; }; }; return bidResponse; diff --git a/modules/tripleliftBidAdapter.md b/modules/tripleliftBidAdapter.md index d5f88a2bece..03dcee3b980 100644 --- a/modules/tripleliftBidAdapter.md +++ b/modules/tripleliftBidAdapter.md @@ -58,5 +58,25 @@ var adUnits = [{ floor: 0 } }] +}, { + code: 'instream-div-1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + } + }, + bids: [ + { + bidder: 'triplelift', + params: { + inventoryCode: 'instream_test', + video: { + mimes: ['video/mp4'], + w: 640, + h: 480, + }, + } + }] }]; ``` diff --git a/modules/truereachBidAdapter.js b/modules/truereachBidAdapter.js new file mode 100755 index 00000000000..2de7edbc04d --- /dev/null +++ b/modules/truereachBidAdapter.js @@ -0,0 +1,128 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const SUPPORTED_AD_TYPES = [BANNER]; +const BIDDER_CODE = 'truereach'; +const BIDDER_URL = 'https://ads.momagic.com/exchange/openrtb25/'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + + isBidRequestValid: function (bidRequest) { + return (bidRequest.params.site_id && bidRequest.params.bidfloor && + utils.deepAccess(bidRequest, 'mediaTypes.banner') && (utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0)); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return []; + } + + let queryParams = buildCommonQueryParamsFromBids(validBidRequests, bidderRequest); + + let siteId = utils.deepAccess(validBidRequests[0], 'params.site_id'); + + let url = BIDDER_URL + siteId + '?hb=1&transactionId=' + validBidRequests[0].transactionId; + + return { + method: 'POST', + url: url, + data: queryParams, + options: { withCredentials: true } + }; + }, + + interpretResponse: function ({ body: serverResponse }, serverRequest) { + const bidResponses = []; + + if ((!serverResponse || !serverResponse.id) || + (!serverResponse.seatbid || serverResponse.seatbid.length === 0 || !serverResponse.seatbid[0].bid || serverResponse.seatbid[0].bid.length === 0)) { + return bidResponses; + } + + let adUnits = serverResponse.seatbid[0].bid; + let bidderBid = adUnits[0]; + + let responseCPM = parseFloat(bidderBid.price); + if (responseCPM === 0) { + return bidResponses; + } + + let responseAd = bidderBid.adm; + + if (bidderBid.nurl) { + let responseNurl = ''; + responseAd += responseNurl; + } + + const bidResponse = { + requestId: bidderBid.impid, + cpm: responseCPM, + currency: serverResponse.cur || 'USD', + width: parseInt(bidderBid.w), + height: parseInt(bidderBid.h), + ad: decodeURIComponent(responseAd), + ttl: 180, + creativeId: bidderBid.crid, + netRevenue: false + }; + if (bidderBid.adomain && bidderBid.adomain.length) { + bidResponse.meta = { + advertiserDomains: bidderBid.adomain, + }; + } + + bidResponses.push(bidResponse); + + return bidResponses; + }, + +}; + +function buildCommonQueryParamsFromBids(validBidRequests, bidderRequest) { + let adW = 0; + let adH = 0; + let adSizes = Array.isArray(validBidRequests[0].params.sizes) ? validBidRequests[0].params.sizes : validBidRequests[0].sizes; + let sizeArrayLength = adSizes.length; + if (sizeArrayLength === 2 && typeof adSizes[0] === 'number' && typeof adSizes[1] === 'number') { + adW = adSizes[0]; + adH = adSizes[1]; + } else { + adW = adSizes[0][0]; + adH = adSizes[0][1]; + } + + let bidFloor = Number(utils.deepAccess(validBidRequests[0], 'params.bidfloor')); + + let domain = window.location.host; + let page = window.location.host + window.location.pathname + location.search + location.hash; + + let defaultParams = { + id: utils.getUniqueIdentifierStr(), + imp: [ + { + id: validBidRequests[0].bidId, + banner: { + w: adW, + h: adH + }, + bidfloor: bidFloor + } + ], + site: { + domain: domain, + page: page + }, + device: { + ua: window.navigator.userAgent + }, + tmax: config.getConfig('bidderTimeout') + }; + + return defaultParams; +} + +registerBidder(spec); diff --git a/modules/truereachBidAdapter.md b/modules/truereachBidAdapter.md new file mode 100644 index 00000000000..8a926565092 --- /dev/null +++ b/modules/truereachBidAdapter.md @@ -0,0 +1,44 @@ +# Overview + +``` +Module Name: TrueReach Bidder Adapter +Module Type: Bidder Adapter +Maintainer: mm.github@momagic.com +``` + +# Description + +Module that connects to TrueReach's demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'truereach', + params: { + site_id: '0142010a-8400-1b01-72cb-a553b9000009', + bidfloor: 0.1 + } + }] + }]; +``` + +# Bid Parameters + +`mediaTypes -> banner -> sizes` must be `defined`. + +Also, the following parameters are `required` to be set- + +| Name | Type | Description +| ---- | ---- | ----------- +| `site_id` | String | TrueReach provided site ID +| `bidfloor` | Number | Minimum price (CPM) in USD. Must be greater than 0. + +# Additional Details +[TrueReach Ads](http://doc.truereach.co.in/docs/prebid/js-bidder-adapter.html) diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 1e7b9ef5ef4..f412fa21a05 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { Renderer } from '../src/Renderer'; -import { VIDEO, BANNER } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = 'https://sofia.trustx.org/hb'; diff --git a/modules/turktelekomBidAdapter.js b/modules/turktelekomBidAdapter.js index cfb1c24052a..852e557290c 100644 --- a/modules/turktelekomBidAdapter.js +++ b/modules/turktelekomBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { Renderer } from '../src/Renderer'; -import { VIDEO, BANNER } from '../src/mediaTypes'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'turktelekom'; const ENDPOINT_URL = 'https://ssp.programattik.com/hb'; diff --git a/modules/ucfunnelAnalyticsAdapter.js b/modules/ucfunnelAnalyticsAdapter.js index 3a1f29c5225..7a471a1d3b4 100644 --- a/modules/ucfunnelAnalyticsAdapter.js +++ b/modules/ucfunnelAnalyticsAdapter.js @@ -1,8 +1,9 @@ -import {ajax} from '../src/ajax'; -import adapter from '../src/AnalyticsAdapter'; +import {ajax} from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; -import {logError, logInfo, deepClone} from '../src/utils'; +import adapterManager from '../src/adapterManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {logError, logInfo, deepClone} from '../src/utils.js'; const analyticsType = 'endpoint'; @@ -148,7 +149,7 @@ export const ucfunnelAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER, }, handleAuctionEnd(auctionEndArgs) { const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); - const highestCpmBids = pbjs.getHighestCpmBids(); + const highestCpmBids = getGlobal().getHighestCpmBids(); this.sendEventMessage('bid', this.createBidMessage(auctionEndArgs, highestCpmBids, cachedAuction.timeoutBids) ); diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js new file mode 100644 index 00000000000..f9982edef36 --- /dev/null +++ b/modules/ucfunnelBidAdapter.js @@ -0,0 +1,263 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import * as utils from '../src/utils.js'; +const storage = getStorageManager(); +const COOKIE_NAME = 'ucf_uid'; +const VER = 'ADGENT_PREBID-2018011501'; +const BIDDER_CODE = 'ucfunnel'; + +const VIDEO_CONTEXT = { + INSTREAM: 0, + OUSTREAM: 2 +} + +export const spec = { + code: BIDDER_CODE, + ENDPOINT: 'https://hb.aralego.com/header', + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + /** + * 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) { + const isVideoMediaType = (bid.mediaTypes && bid.mediaTypes.video != null); + const videoContext = (bid.mediaTypes && bid.mediaTypes.video != null) ? bid.mediaTypes.video.videoContext : ''; + + if (typeof bid.params !== 'object' || typeof bid.params.adid != 'string') { + return false; + } + + if (isVideoMediaType && videoContext === 'outstream') { + return false; + } + + return true; + }, + + /** + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ + buildRequests: function(bids, bidderRequest) { + return bids.map(bid => { + return { + method: 'GET', + url: spec.ENDPOINT, + data: getRequestData(bid, bidderRequest), + bidRequest: bid + } + }); + }, + + /** + * 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) { + const bidRequest = request.bidRequest; + const ad = ucfunnelResponseObj ? ucfunnelResponseObj.body : {}; + const videoPlayerSize = parseSizes(bidRequest); + + let bid = { + requestId: bidRequest.bidId, + cpm: ad.cpm || 0, + creativeId: ad.ad_id || bidRequest.params.adid, + dealId: ad.deal || null, + currency: 'USD', + netRevenue: true, + ttl: 1800 + }; + + if (bidRequest.params && bidRequest.params.bidfloor && ad.cpm && ad.cpm < bidRequest.params.bidfloor) { + bid.cpm = 0; + } + if (ad.creative_type) { + bid.mediaType = ad.creative_type; + } + + switch (ad.creative_type) { + case NATIVE: + let nativeAd = ad.native; + Object.assign(bid, { + width: 1, + height: 1, + native: { + title: nativeAd.title, + body: nativeAd.desc, + cta: nativeAd.ctatext, + sponsoredBy: nativeAd.sponsored, + image: nativeAd.image || nativeAd.image.url, + icon: nativeAd.icon || nativeAd.icon.url, + clickUrl: nativeAd.clickUrl, + clickTrackers: (nativeAd.clicktrackers) ? nativeAd.clicktrackers : [], + impressionTrackers: nativeAd.impressionTrackers, + } + }); + break; + case VIDEO: + Object.assign(bid, { + vastUrl: ad.vastUrl, + vastXml: ad.vastXml + }); + + if (videoPlayerSize && videoPlayerSize.length === 2) { + Object.assign(bid, { + width: videoPlayerSize[0], + height: videoPlayerSize[1] + }); + } + break; + case BANNER: + default: + var size = parseSizes(bidRequest); + Object.assign(bid, { + width: ad.width || size[0], + height: ad.height || size[1], + ad: ad.adm || '' + }); + } + + return [bid]; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: 'https://cdn.aralego.net/ucfad/cookie/sync.html' + }]; + } else if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: 'https://sync.aralego.com/idSync' + }]; + } + } +}; +registerBidder(spec); + +function transformSizes(requestSizes) { + if (typeof requestSizes === 'object' && requestSizes.length) { + return requestSizes[0]; + } +} + +function parseSizes(bid) { + let params = bid.params; + if (bid.mediaType === VIDEO) { + let size = []; + if (params.video && params.video.playerWidth && params.video.playerHeight) { + size = [ + params.video.playerWidth, + params.video.playerHeight + ]; + return size; + } + } + + return transformSizes(bid.sizes); +} + +function getSupplyChain(schain) { + var supplyChain = ''; + if (schain != null && schain.nodes) { + supplyChain = schain.ver + ',' + schain.complete; + for (let i = 0; i < schain.nodes.length; i++) { + supplyChain += '!'; + supplyChain += (schain.nodes[i].asi) ? encodeURIComponent(schain.nodes[i].asi) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].sid) ? encodeURIComponent(schain.nodes[i].sid) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].hp) ? encodeURIComponent(schain.nodes[i].hp) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].rid) ? encodeURIComponent(schain.nodes[i].rid) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].name) ? encodeURIComponent(schain.nodes[i].name) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].domain) ? encodeURIComponent(schain.nodes[i].domain) : ''; + } + } + return supplyChain; +} + +function getRequestData(bid, bidderRequest) { + const size = parseSizes(bid); + const language = navigator.language; + const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; + const userIdTdid = (bid.userId && bid.userId.tdid) ? bid.userId.tdid : ''; + const supplyChain = getSupplyChain(bid.schain); + // general bid data + let bidData = { + ver: VER, + ifr: 0, + bl: language, + je: 1, + dnt: dnt, + adid: bid.params.adid, + tdid: userIdTdid, + schain: supplyChain, + fp: bid.params.bidfloor + }; + + try { + bidData.host = window.top.location.hostname; + bidData.u = window.top.location.href; + bidData.xr = 0; + } catch (e) { + bidData.host = window.location.hostname; + bidData.u = document.referrer || window.location.href; + bidData.xr = 1; + } + + if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) { + bidData.ao = window.location.ancestorOrigins[window.location.ancestorOrigins.length - 1]; + } + + if (storage.cookiesAreEnabled()) { + let ucfUid = ''; + if (storage.getCookie(COOKIE_NAME) != undefined) { + ucfUid = storage.getCookie(COOKIE_NAME); + bidData.ucfUid = ucfUid; + } else { + ucfUid = utils.generateUUID(); + bidData.ucfUid = ucfUid; + storage.setCookie(COOKIE_NAME, ucfUid); + } + } + + if (size != undefined && size.length == 2) { + bidData.w = size[0]; + bidData.h = size[1]; + } + + if (bidderRequest && bidderRequest.uspConsent) { + Object.assign(bidData, { + usprivacy: bidderRequest.uspConsent + }); + } + if (bid.mediaTypes && bid.mediaTypes.video != null) { + const videoContext = bid.mediaTypes.video.context; + switch (videoContext) { + case 'outstream': + bidData.atype = VIDEO_CONTEXT.OUSTREAM; + break; + case 'instream': + default: + bidData.atype = VIDEO_CONTEXT.INSTREAM; + break; + } + } + + if (bidderRequest && bidderRequest.gdprConsent) { + Object.assign(bidData, { + gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + euconsent: bidderRequest.gdprConsent.consentString + }); + } + + return bidData; +} diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 03c815ab005..8368077a627 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -1,13 +1,19 @@ -import * as utils from '../src/utils'; -import { config } from '../src/config'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'underdogmedia'; -const UDM_ADAPTER_VERSION = '3.0V'; +const UDM_ADAPTER_VERSION = '3.5V'; const UDM_VENDOR_ID = '159'; const prebidVersion = '$prebid.version$'; +let USER_SYNCED = false; utils.logMessage(`Initializing UDM Adapter. PBJS Version: ${prebidVersion} with adapter version: ${UDM_ADAPTER_VERSION} Updated 20191028`); +// helper function for testing user syncs +export function resetUserSync() { + USER_SYNCED = false; +} + export const spec = { code: BIDDER_CODE, bidParams: [], @@ -31,7 +37,8 @@ export const spec = { tid: 1, dt: 10, sid: siteId, - sizes: sizes.join(',') + sizes: sizes.join(','), + version: UDM_ADAPTER_VERSION } if (bidderRequest && bidderRequest.gdprConsent) { @@ -47,6 +54,10 @@ export const spec = { } } + if (bidderRequest.uspConsent) { + data.uspConsent = bidderRequest.uspConsent; + } + if (!data.gdprApplies || data.consentGiven) { return { method: 'GET', @@ -57,6 +68,23 @@ export const spec = { } }, + getUserSyncs: function (syncOptions, serverResponses) { + if (!USER_SYNCED && serverResponses.length > 0 && serverResponses[0].body && serverResponses[0].body.userSyncs && serverResponses[0].body.userSyncs.length > 0) { + USER_SYNCED = true; + const userSyncs = serverResponses[0].body.userSyncs; + const syncs = userSyncs.filter(sync => { + const {type} = sync; + if (syncOptions.iframeEnabled && type === 'iframe') { + return true + } + if (syncOptions.pixelEnabled && type === 'image') { + return true + } + }) + return syncs; + } + }, + interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; bidRequest.bidParams.forEach(bidParam => { @@ -91,7 +119,7 @@ export const spec = { creativeId: mid.mid, currency: 'USD', netRevenue: false, - ttl: config.getConfig('_bidderTimeout'), + ttl: mid.ttl || 60, }; if (bidResponse.cpm <= 0) { @@ -114,9 +142,14 @@ export const spec = { }; function makeNotification(bid, mid, bidParam) { - var url = mid.notification_url; + let url = mid.notification_url; + + const versionIndex = url.indexOf(';version=') + if (versionIndex + 1) { + url = url.substring(0, versionIndex) + } - url += UDM_ADAPTER_VERSION; + url += `;version=${UDM_ADAPTER_VERSION}`; url += ';cb=' + Math.random(); url += ';qqq=' + (1 / bid.cpm); url += ';hbt=' + config.getConfig('_bidderTimeout'); diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index 0daa6d83e26..743cb07b21e 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,8 +2,9 @@ * Adapter to send bids to Undertone */ -import * as urlUtils from '../src/url'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import { deepAccess, parseUrl } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'undertone'; const URL = 'https://hb.undertone.com/hb'; @@ -51,8 +52,29 @@ function getGdprQueryParams(gdprConsent) { return `gdpr=${gdpr}&gdprstr=${gdprstr}`; } +function getBannerCoords(id) { + let element = document.getElementById(id); + let left = -1; + let top = -1; + if (element) { + left = element.offsetLeft; + top = element.offsetTop; + + let parent = element.offsetParent; + if (parent) { + left += parent.offsetLeft; + top += parent.offsetTop; + } + + return [left, top]; + } else { + return null; + } +} + export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { if (bid && bid.params && bid.params.publisherId) { bid.params.publisherId = parseInt(bid.params.publisherId); @@ -60,15 +82,19 @@ export const spec = { } }, buildRequests: function(validBidRequests, bidderRequest) { + const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + const pageSizeArray = vw == 0 || vh == 0 ? null : [vw, vh]; const payload = { 'x-ut-hb-params': [], 'commons': { 'adapterVersion': '$prebid.version$', - 'uids': validBidRequests[0].userId + 'uids': validBidRequests[0].userId, + 'pageSize': pageSizeArray } }; const referer = bidderRequest.refererInfo.referer; - const hostname = urlUtils.parse(referer).hostname; + const hostname = parseUrl(referer).hostname; let domain = extractDomainFromHost(hostname); const pageUrl = getCanonicalUrl() || referer; @@ -87,6 +113,7 @@ export const spec = { validBidRequests.map(bidReq => { const bid = { bidRequestId: bidReq.bidId, + coordinates: getBannerCoords(bidReq.adUnitCode), hbadaptor: 'prebid', url: pageUrl, domain: domain, @@ -95,8 +122,20 @@ export const spec = { sizes: bidReq.sizes, params: bidReq.params }; + const videoMediaType = deepAccess(bidReq, 'mediaTypes.video'); + if (videoMediaType) { + bid.video = { + playerSize: deepAccess(bidReq, 'mediaTypes.video.playerSize') || null, + streamType: deepAccess(bidReq, 'mediaTypes.video.context') || null, + playbackMethod: deepAccess(bidReq, 'params.video.playbackMethod') || null, + maxDuration: deepAccess(bidReq, 'params.video.maxDuration') || null, + skippable: deepAccess(bidReq, 'params.video.skippable') || null + }; + bid.mediaType = 'video'; + } payload['x-ut-hb-params'].push(bid); }); + return { method: 'POST', url: reqUrl, @@ -119,9 +158,14 @@ export const spec = { creativeId: bidRes.adId, currency: bidRes.currency, netRevenue: bidRes.netRevenue, - ttl: bidRes.ttl || 360, - ad: bidRes.ad + ttl: bidRes.ttl || 360 }; + if (bidRes.mediaType && bidRes.mediaType === 'video') { + bid.vastXml = bidRes.ad; + bid.mediaType = bidRes.mediaType; + } else { + bid.ad = bidRes.ad + } bids.push(bid); } }); diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js new file mode 100644 index 00000000000..33e60ad2789 --- /dev/null +++ b/modules/unicornBidAdapter.js @@ -0,0 +1,150 @@ +import * as utils from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); +const BIDDER_CODE = 'unicorn'; +const UNICORN_ENDPOINT = 'https://ds.uncn.jp/pb/0/bid.json'; +const UNICORN_DEFAULT_CURRENCY = 'JPY'; +const UNICORN_PB_COOKIE_KEY = '__pb_unicorn_aud'; + +/** + * Placement ID and Account ID are required. + * @param {BidRequest} bidRequest + * @returns {boolean} + */ +const isBidRequestValid = bidRequest => { + return !!bidRequest.adUnitCode && !!bidRequest.params.accountId; +}; + +/** + * @param {Array} validBidRequests + * @param {any} bidderRequest + * @returns {ServerRequest} + */ +export const buildRequests = (validBidRequests, bidderRequest) => { + return { + method: 'POST', + url: UNICORN_ENDPOINT, + data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) + }; +}; + +/** + * Transform BidRequest to OpenRTB-formatted BidRequest Object + * @param {Array} validBidRequests + * @param {any} bidderRequest + * @returns {string} + */ +function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { + utils.logInfo( + '[UNICORN] buildOpenRtbBidRequestPayload.validBidRequests:', + validBidRequests + ); + utils.logInfo( + '[UNICORN] buildOpenRtbBidRequestPayload.bidderRequest:', + bidderRequest + ); + const imp = validBidRequests.map(br => { + const sizes = utils.parseSizesInput(br.sizes)[0]; + return { + id: br.bidId, + banner: { + w: sizes.split('x')[0], + h: sizes.split('x')[1] + }, + tagid: utils.deepAccess(br, 'params.placementId') || br.adUnitCode, + secure: 1, + bidfloor: parseFloat(utils.deepAccess(br, 'params.bidfloorCpm') || 0) + }; + }); + const request = { + id: bidderRequest.auctionId, + at: 1, + imp, + cur: UNICORN_DEFAULT_CURRENCY, + site: { + id: utils.deepAccess(validBidRequests[0], 'params.mediaId') || '', + publisher: { + id: utils.deepAccess(validBidRequests[0], 'params.publisherId') || 0 + }, + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo.referer + }, + device: { + language: navigator.language, + ua: navigator.userAgent + }, + user: { + id: getUid() + }, + bcat: utils.deepAccess(validBidRequests[0], 'params.bcat') || [], + source: { + ext: { + stype: 'prebid_uncn', + bidder: BIDDER_CODE + } + }, + ext: { + accountId: utils.deepAccess(validBidRequests[0], 'params.accountId') + } + }; + utils.logInfo('[UNICORN] OpenRTB Formatted Request:', request); + return JSON.stringify(request); +} + +const interpretResponse = (serverResponse, request) => { + utils.logInfo('[UNICORN] interpretResponse.serverResponse:', serverResponse); + utils.logInfo('[UNICORN] interpretResponse.request:', request); + const res = serverResponse.body; + var bids = [] + if (res) { + res.seatbid.forEach(sb => { + sb.bid.forEach(b => { + bids.push({ + requestId: b.impid, + cpm: b.price || 0, + width: b.w, + height: b.h, + ad: b.adm, + ttl: 1000, + creativeId: b.crid, + netRevenue: false, + currency: res.cur + }) + }) + }); + } + utils.logInfo('[UNICORN] interpretResponse bids:', bids); + return bids; +}; + +/** + * Get or Create Uid for First Party Cookie + */ +const getUid = () => { + const ck = storage.getCookie(UNICORN_PB_COOKIE_KEY); + if (ck) { + return JSON.parse(ck)['uid']; + } else { + const newCk = { + uid: utils.generateUUID() + }; + const expireIn = new Date(Date.now() + 24 * 60 * 60 * 10000).toUTCString(); + storage.setCookie(UNICORN_PB_COOKIE_KEY, JSON.stringify(newCk), expireIn); + return newCk.uid; + } +}; + +export const spec = { + code: BIDDER_CODE, + aliases: ['uncn'], + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, +}; + +registerBidder(spec); diff --git a/modules/unicornBidAdapter.md b/modules/unicornBidAdapter.md new file mode 100644 index 00000000000..5b8c8268bcf --- /dev/null +++ b/modules/unicornBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +**Module Name**: UNICORN Bid Adapter +**Module Type**: Bidder Adapter +**Maintainer**: service+prebid.js@bulbit.jp + +# Description + +Module that connects to UNICORN. + +# Test Parameters + +```js + const adUnits = [{ + code: 'test-adunit1', // REQUIRED: adunit code + mediaTypes: { + banner: { + sizes: [[300, 250]] // a banner size + } + }, + bids: [{ + bidder: 'unicorn', + params: { + placementId: 'rectangle-ad-1', // OPTIONAL: If placementId is empty, adunit code will be used as placementId. + bidfloorCpm: 0.2, // OPTIONAL: Floor CPM (JPY) defaults to 0 + publisherId: 99999 // OPTIONAL: Account specific publisher id + mediaId: "uc" // OPTIONAL: Publisher specific media id + accountId: 12345, // REQUIRED: Account ID for charge request + bcat: ['IAB-1', 'IAB-2'] // OPTIONAL: blocked IAB categories + } + }] + }]; +``` diff --git a/modules/unifiedIdSystem.js b/modules/unifiedIdSystem.js index 12aa646a5d9..f916030d643 100644 --- a/modules/unifiedIdSystem.js +++ b/modules/unifiedIdSystem.js @@ -5,9 +5,9 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils' -import {ajax} from '../src/ajax'; -import {submodule} from '../src/hook' +import * as utils from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js' const MODULE_NAME = 'unifiedId'; @@ -39,20 +39,27 @@ export const unifiedIdSubmodule = { return; } // use protocol relative urls for http or https - const url = configParams.url || `//match.adsrvr.org/track/rid?ttd_pid=${configParams.partner}&fmt=json`; + const url = configParams.url || `https://match.adsrvr.org/track/rid?ttd_pid=${configParams.partner}&fmt=json`; const resp = function (callback) { - ajax(url, response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + } } + callback(responseObj); + }, + error: error => { + utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); } - callback(responseObj); - }, undefined, {method: 'GET', withCredentials: true}); + }; + ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); }; return {callback: resp}; } diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index 580357a1978..15fe0fefe8b 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils' -import { Renderer } from '../src/Renderer' -import { registerBidder } from '../src/adapters/bidderFactory' -import { VIDEO } from '../src/mediaTypes' +import * as utils from '../src/utils.js' +import { Renderer } from '../src/Renderer.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { VIDEO } from '../src/mediaTypes.js' function configureUniversalTag (exchangeRenderer) { if (!exchangeRenderer.config) throw new Error('UnrulyBidAdapter: Missing renderer config.') diff --git a/modules/userId/eids.js b/modules/userId/eids.js new file mode 100644 index 00000000000..72be98eca50 --- /dev/null +++ b/modules/userId/eids.js @@ -0,0 +1,167 @@ +import * as utils from '../../src/utils.js'; + +// Each user-id sub-module is expected to mention respective config here +const USER_IDS_CONFIG = { + + // key-name : {config} + + // intentIqId + 'intentIqId': { + source: 'intentiq.com', + atype: 1 + }, + + // pubCommonId + 'pubcid': { + source: 'pubcid.org', + atype: 1 + }, + + // unifiedId + 'tdid': { + source: 'adserver.org', + atype: 1, + getUidExt: function() { + return { + rtiPartner: 'TDID' + }; + } + }, + + // id5Id + 'id5id': { + source: 'id5-sync.com', + atype: 1 + }, + + // parrableId + 'parrableId': { + source: 'parrable.com', + atype: 1, + getValue: function(parrableId) { + if (parrableId.eid) { + return parrableId.eid; + } + if (parrableId.ccpaOptout) { + // If the EID was suppressed due to a non consenting ccpa optout then + // we still wish to provide this as a reason to the adapters + return ''; + } + return null; + }, + getUidExt: function(parrableId) { + const extendedData = utils.pick(parrableId, [ + 'ibaOptout', + 'ccpaOptout' + ]); + if (Object.keys(extendedData).length) { + return extendedData; + } + } + }, + + // identityLink + 'idl_env': { + source: 'liveramp.com', + atype: 1 + }, + + // liveIntentId + 'lipb': { + getValue: function(data) { + return data.lipbid; + }, + source: 'liveintent.com', + atype: 1, + getEidExt: function(data) { + if (Array.isArray(data.segments) && data.segments.length) { + return { + segments: data.segments + }; + } + } + }, + + // britepoolId + 'britepoolid': { + source: 'britepool.com', + atype: 1 + }, + + // lotamePanoramaId + lotamePanoramaId: { + source: 'crwdcntrl.net', + atype: 1, + }, + + // criteo + 'criteoId': { + source: 'criteo.com', + atype: 1 + }, + + // NetId + 'netId': { + source: 'netid.de', + atype: 1 + }, + // sharedid + 'sharedid': { + source: 'sharedid.org', + atype: 1, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + return (data && data.third) ? { + third: data.third + } : undefined; + } + } +}; + +// this function will create an eid object for the given UserId sub-module +function createEidObject(userIdData, subModuleKey) { + const conf = USER_IDS_CONFIG[subModuleKey]; + if (conf && userIdData) { + let eid = {}; + eid.source = conf['source']; + const value = utils.isFn(conf['getValue']) ? conf['getValue'](userIdData) : userIdData; + if (utils.isStr(value)) { + const uid = { id: value, atype: conf['atype'] }; + // getUidExt + if (utils.isFn(conf['getUidExt'])) { + const uidExt = conf['getUidExt'](userIdData); + if (uidExt) { + uid.ext = uidExt; + } + } + eid.uids = [uid]; + // getEidExt + if (utils.isFn(conf['getEidExt'])) { + const eidExt = conf['getEidExt'](userIdData); + if (eidExt) { + eid.ext = eidExt; + } + } + return eid; + } + } + return null; +} + +// this function will generate eids array for all available IDs in bidRequest.userId +// this function will be called by userId module +// if any adapter does not want any particular userId to be passed then adapter can use Array.filter(e => e.source != 'tdid') +export function createEidsArray(bidRequestUserId) { + let eids = []; + for (const subModuleKey in bidRequestUserId) { + if (bidRequestUserId.hasOwnProperty(subModuleKey)) { + const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey); + if (eid) { + eids.push(eid); + } + } + } + return eids; +} diff --git a/modules/userId/eids.md b/modules/userId/eids.md new file mode 100644 index 00000000000..f42b7e61a52 --- /dev/null +++ b/modules/userId/eids.md @@ -0,0 +1,92 @@ +## Example of eids array generated by UserId Module. +``` +userIdAsEids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }, + + { + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'parrable.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'liveramp.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'liveintent.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }], + ext: { + segments: ['s1', 's2'] + } + }, + + { + source: 'britepool.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'criteo.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'netid.de', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'sharedid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + third: 'some-random-id-value' + } + }] + } +] +``` diff --git a/modules/userId/index.js b/modules/userId/index.js index cbe5e4de55f..afdd93a57ba 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -37,6 +37,7 @@ * @summary decode a stored value for passing to bid requests * @name Submodule#decode * @param {Object|string} value + * @param {SubmoduleParams|undefined} configParams * @return {(Object|undefined)} */ @@ -63,6 +64,14 @@ * @property {(number|undefined)} refreshInSeconds - if not empty, this value defines the maximum time span in seconds before refreshing user ID stored in browser */ +/** + * @typedef {Object} LiveIntentCollectConfig + * @property {(string|undefined)} fpiStorageStrategy - defines whether the first party identifiers that LiveConnect creates and updates are stored in a cookie jar, local storage, or not created at all + * @property {(number|undefined)} fpiExpirationDays - the expiration time of an identifier created and updated by LiveConnect + * @property {(string|undefined)} collectorUrl - defines where the LiveIntentId signal pixels are pointing to + * @property {(string|undefined)} appId - the unique identifier of the application in question + */ + /** * @typedef {Object} SubmoduleParams * @property {(string|undefined)} partner - partner url param value @@ -72,7 +81,10 @@ * @property {(boolean|undefined)} extend - extend expiration time on each access. default is false. * @property {(string|undefined)} pid - placement id url param value * @property {(string|undefined)} publisherId - the unique identifier of the publisher in question + * @property {(string|undefined)} ajaxTimeout - the number of milliseconds a resolution request can take before automatically being terminated * @property {(array|undefined)} identifiersToResolve - the identifiers from either ls|cookie to be attached to the getId query + * @property {(string|undefined)} providedIdentifierName - defines the name of an identifier that can be found in local storage or in the cookie jar that can be sent along with the getId request. This parameter should be used whenever a customer is able to provide the most stable identifier possible + * @property {(LiveIntentCollectConfig|undefined)} liCollectConfig - the config for LiveIntent's collect requests */ /** @@ -96,20 +108,27 @@ * @property {(function|undefined)} callback - function that will return an id */ -import find from 'core-js/library/fn/array/find'; -import {config} from '../../src/config'; -import events from '../../src/events'; -import * as utils from '../../src/utils'; -import {getGlobal} from '../../src/prebidGlobal'; -import {gdprDataHandler} from '../../src/adapterManager'; +import find from 'core-js-pure/features/array/find.js'; +import {config} from '../../src/config.js'; +import events from '../../src/events.js'; +import * as utils from '../../src/utils.js'; +import {getGlobal} from '../../src/prebidGlobal.js'; +import {gdprDataHandler} from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; -import {module} from '../../src/hook'; +import {module, hook} from '../../src/hook.js'; +import {createEidsArray} from './eids.js'; +import { getCoreStorageManager } from '../../src/storageManager.js'; const MODULE_NAME = 'User ID'; const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const DEFAULT_SYNC_DELAY = 500; const NO_AUCTION_DELAY = 0; +const CONSENT_DATA_COOKIE_STORAGE_CONFIG = { + name: '_pbjs_userid_consent_data', + expires: 30 // 30 days expiration, which should match how often consent is refreshed by CMPs +}; +export const coreStorage = getCoreStorageManager('userid'); /** @type {string[]} */ let validStorageTypes = []; @@ -129,6 +148,9 @@ let configRegistry = []; /** @type {Submodule[]} */ let submoduleRegistry = []; +/** @type {(number|undefined)} */ +let timeoutID; + /** @type {(number|undefined)} */ export let syncDelay; @@ -141,23 +163,29 @@ export function setSubmoduleRegistry(submodules) { } /** - * @param {SubmoduleStorage} storage + * @param {SubmoduleContainer} submodule * @param {(Object|string)} value */ -function setStoredValue(storage, value) { +export function setStoredValue(submodule, value) { + /** + * @type {SubmoduleStorage} + */ + const storage = submodule.config.storage; + const domainOverride = (typeof submodule.submodule.domainOverride === 'function') ? submodule.submodule.domainOverride() : null; + try { const valueStr = utils.isPlainObject(value) ? JSON.stringify(value) : value; const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); if (storage.type === COOKIE) { - utils.setCookie(storage.name, valueStr, expiresStr, 'Lax'); + coreStorage.setCookie(storage.name, valueStr, expiresStr, 'Lax', domainOverride); if (typeof storage.refreshInSeconds === 'number') { - utils.setCookie(`${storage.name}_last`, new Date().toUTCString(), expiresStr); + coreStorage.setCookie(`${storage.name}_last`, new Date().toUTCString(), expiresStr, 'Lax', domainOverride); } } else if (storage.type === LOCAL_STORAGE) { - localStorage.setItem(`${storage.name}_exp`, expiresStr); - localStorage.setItem(storage.name, encodeURIComponent(valueStr)); + coreStorage.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); + coreStorage.setDataInLocalStorage(storage.name, encodeURIComponent(valueStr)); if (typeof storage.refreshInSeconds === 'number') { - localStorage.setItem(`${storage.name}_last`, new Date().toUTCString()); + coreStorage.setDataInLocalStorage(`${storage.name}_last`, new Date().toUTCString()); } } } catch (error) { @@ -175,15 +203,15 @@ function getStoredValue(storage, key = undefined) { let storedValue; try { if (storage.type === COOKIE) { - storedValue = utils.getCookie(storedKey); + storedValue = coreStorage.getCookie(storedKey); } else if (storage.type === LOCAL_STORAGE) { - const storedValueExp = localStorage.getItem(`${storage.name}_exp`); + const storedValueExp = coreStorage.getDataFromLocalStorage(`${storage.name}_exp`); // empty string means no expiration set if (storedValueExp === '') { - storedValue = localStorage.getItem(storedKey); + storedValue = coreStorage.getDataFromLocalStorage(storedKey); } else if (storedValueExp) { if ((new Date(storedValueExp)).getTime() - Date.now() > 0) { - storedValue = decodeURIComponent(localStorage.getItem(storedKey)); + storedValue = decodeURIComponent(coreStorage.getDataFromLocalStorage(storedKey)); } } } @@ -197,6 +225,72 @@ function getStoredValue(storage, key = undefined) { return storedValue; } +/** + * makes an object that can be stored with only the keys we need to check. + * excluding the vendorConsents object since the consentString is enough to know + * if consent has changed without needing to have all the details in an object + * @param consentData + * @returns {{apiVersion: number, gdprApplies: boolean, consentString: string}} + */ +function makeStoredConsentDataObject(consentData) { + const storedConsentData = { + consentString: '', + gdprApplies: false, + apiVersion: 0 + }; + + if (consentData) { + storedConsentData.consentString = consentData.consentString; + storedConsentData.gdprApplies = consentData.gdprApplies; + storedConsentData.apiVersion = consentData.apiVersion; + } + + return storedConsentData; +} + +/** + * puts the current consent data into cookie storage + * @param consentData + */ +export function setStoredConsentData(consentData) { + try { + const expiresStr = (new Date(Date.now() + (CONSENT_DATA_COOKIE_STORAGE_CONFIG.expires * (60 * 60 * 24 * 1000)))).toUTCString(); + coreStorage.setCookie(CONSENT_DATA_COOKIE_STORAGE_CONFIG.name, JSON.stringify(makeStoredConsentDataObject(consentData)), expiresStr, 'Lax'); + } catch (error) { + utils.logError(error); + } +} + +/** + * get the stored consent data from local storage, if any + * @returns {string} + */ +function getStoredConsentData() { + let storedValue; + try { + storedValue = JSON.parse(coreStorage.getCookie(CONSENT_DATA_COOKIE_STORAGE_CONFIG.name)); + } catch (e) { + utils.logError(e); + } + return storedValue; +} + +/** + * test if the consent object stored locally matches the current consent data. + * if there is nothing in storage, return true and we'll do an actual comparison next time. + * this way, we don't force a refresh for every user when this code rolls out + * @param storedConsentData + * @param consentData + * @returns {boolean} + */ +function storedConsentDataMatchesConsentData(storedConsentData, consentData) { + return ( + typeof storedConsentData === 'undefined' || + storedConsentData === null || + utils.deepEqual(storedConsentData, makeStoredConsentDataObject(consentData)) + ); +} + /** * test if consent module is present, applies, and is valid for local storage or cookies (purpose 1) * @param {ConsentData} consentData @@ -207,7 +301,10 @@ function hasGDPRConsent(consentData) { if (!consentData.consentString) { return false; } - if (consentData.vendorData && consentData.vendorData.purposeConsents && consentData.vendorData.purposeConsents['1'] === false) { + if (consentData.apiVersion === 1 && utils.deepAccess(consentData, 'vendorData.purposeConsents.1') === false) { + return false; + } + if (consentData.apiVersion === 2 && utils.deepAccess(consentData, 'vendorData.purpose.consents.1') === false) { return false; } } @@ -225,12 +322,12 @@ function processSubmoduleCallbacks(submodules, cb) { // if valid, id data should be saved to cookie/html storage if (idObj) { if (submodule.config.storage) { - setStoredValue(submodule.config.storage, idObj); + setStoredValue(submodule, idObj); } // cache decoded value (this is copied to every adUnit bid) submodule.idObj = submodule.submodule.decode(idObj); } else { - utils.logError(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); + utils.logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } done(); }); @@ -238,6 +335,7 @@ function processSubmoduleCallbacks(submodules, cb) { // clear callback, this prop is used to test if all submodule callbacks are complete below submodule.callback = undefined; }); + clearTimeout(timeoutID); } /** @@ -267,18 +365,20 @@ function addIdDataToAdUnitBids(adUnits, submodules) { return; } const combinedSubmoduleIds = getCombinedSubmoduleIds(submodules); + const combinedSubmoduleIdsAsEids = createEidsArray(combinedSubmoduleIds); if (Object.keys(combinedSubmoduleIds).length) { adUnits.forEach(adUnit => { adUnit.bids.forEach(bid => { // create a User ID object on the bid, bid.userId = combinedSubmoduleIds; + bid.userIdAsEids = combinedSubmoduleIdsAsEids; }); }); } } /** - * This is a common function that will initalize subModules if not already done and it will also execute subModule callbacks + * This is a common function that will initialize subModules if not already done and it will also execute subModule callbacks */ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { let delayed = false; @@ -302,9 +402,9 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { } } utils.logInfo(`${MODULE_NAME} - auction delayed by ${auctionDelay} at most to fetch ids`); - processSubmoduleCallbacks(submodulesWithCallbacks, continueCallback); - setTimeout(continueCallback, auctionDelay); + timeoutID = setTimeout(continueCallback, auctionDelay); + processSubmoduleCallbacks(submodulesWithCallbacks, continueCallback); } else { // wait for auction complete before processing submodule callbacks events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { @@ -358,19 +458,41 @@ function getUserIds() { return getCombinedSubmoduleIds(initializedSubmodules); } +/** + * This function will be exposed in global-name-space so that userIds stored by Prebid UserId module can be used by external codes as well. + * Simple use case will be passing these UserIds to A9 wrapper solution + */ +function getUserIdsAsEids() { + // initialize submodules only when undefined + initializeSubmodulesAndExecuteCallbacks(); + return createEidsArray(getCombinedSubmoduleIds(initializedSubmodules)); +} + +/** + * This hook returns updated list of submodules which are allowed to do get user id based on TCF 2 enforcement rules configured + */ +export const validateGdprEnforcement = hook('sync', function (submodules, consentData) { + return {userIdModules: submodules, hasValidated: consentData && consentData.hasValidated}; +}, 'validateGdprEnforcement'); + /** * @param {SubmoduleContainer[]} submodules * @param {ConsentData} consentData * @returns {SubmoduleContainer[]} initialized submodules */ function initSubmodules(submodules, consentData) { + // we always want the latest consentData stored, even if we don't execute any submodules + const storedConsentData = getStoredConsentData(); + setStoredConsentData(consentData); + // gdpr consent with purpose one is required, otherwise exit immediately - if (!hasGDPRConsent(consentData)) { + let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); + if (!hasValidated && !hasGDPRConsent(consentData)) { utils.logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); return []; } - return submodules.reduce((carry, submodule) => { + return userIdModules.reduce((carry, submodule) => { // There are two submodule configuration types to handle: storage or value // 1. storage: retrieve user id data from cookie/html storage or with the submodule's getId method // 2. value: pass directly to bids @@ -384,12 +506,8 @@ function initSubmodules(submodules, consentData) { refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > submodule.config.storage.refreshInSeconds * 1000); } - if (CONSTANTS.SUBMODULES_THAT_ALWAYS_REFRESH_ID[submodule.config.name] === true) { - refreshNeeded = true; - } - - if (!storedId || refreshNeeded) { - // No previously saved id. Request one from submodule. + if (!storedId || refreshNeeded || !storedConsentDataMatchesConsentData(storedConsentData, consentData)) { + // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. response = submodule.submodule.getId(submodule.config.params, consentData, storedId); } else if (typeof submodule.submodule.extendId === 'function') { // If the id exists already, give submodule a chance to decide additional actions that need to be taken @@ -399,7 +517,7 @@ function initSubmodules(submodules, consentData) { if (utils.isPlainObject(response)) { if (response.id) { // A getId/extendId result assumed to be valid user id data, which should be saved to users local storage or cookies - setStoredValue(submodule.config.storage, response.id); + setStoredValue(submodule, response.id); storedId = response.id; } @@ -411,7 +529,7 @@ function initSubmodules(submodules, consentData) { if (storedId) { // cache decoded value (this is copied to every adUnit bid) - submodule.idObj = submodule.submodule.decode(storedId); + submodule.idObj = submodule.submodule.decode(storedId, submodule.config.params); } } else if (submodule.config.value) { // cache decoded value (this is copied to every adUnit bid) @@ -420,7 +538,7 @@ function initSubmodules(submodules, consentData) { const response = submodule.submodule.getId(submodule.config.params, consentData, undefined); if (utils.isPlainObject(response)) { if (typeof response.callback === 'function') { submodule.callback = response.callback; } - if (response.id) { submodule.idObj = submodule.submodule.decode(response.id); } + if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config.params); } } } carry.push(submodule); @@ -516,24 +634,24 @@ export function init(config) { // list of browser enabled storage types validStorageTypes = [ - utils.localStorageIsEnabled() ? LOCAL_STORAGE : null, - utils.cookiesAreEnabled() ? COOKIE : null + coreStorage.localStorageIsEnabled() ? LOCAL_STORAGE : null, + coreStorage.cookiesAreEnabled() ? COOKIE : null ].filter(i => i !== null); // exit immediately if opt out cookie or local storage keys exists. - if (validStorageTypes.indexOf(COOKIE) !== -1 && (utils.getCookie('_pbjs_id_optout') || utils.getCookie('_pubcid_optout'))) { + if (validStorageTypes.indexOf(COOKIE) !== -1 && (coreStorage.getCookie('_pbjs_id_optout') || coreStorage.getCookie('_pubcid_optout'))) { utils.logInfo(`${MODULE_NAME} - opt-out cookie found, exit module`); return; } - // _pubcid_optout is checked for compatiblility with pubCommonId - if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && (localStorage.getItem('_pbjs_id_optout') || localStorage.getItem('_pubcid_optout'))) { + // _pubcid_optout is checked for compatibility with pubCommonId + if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && (coreStorage.getDataFromLocalStorage('_pbjs_id_optout') || coreStorage.getDataFromLocalStorage('_pubcid_optout'))) { utils.logInfo(`${MODULE_NAME} - opt-out localStorage found, exit module`); return; } // listen for config userSyncs to be set config.getConfig(conf => { - // Note: support for both 'userSync' and 'usersync' will be deprecated with Prebid.js 3.0 - const userSync = conf.userSync || conf.usersync; + // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 + const userSync = conf.userSync; if (userSync && userSync.userIds) { configRegistry = userSync.userIds; syncDelay = utils.isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; @@ -544,6 +662,7 @@ export function init(config) { // exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes. (getGlobal()).getUserIds = getUserIds; + (getGlobal()).getUserIdsAsEids = getUserIdsAsEids; } // init config update listener to start the application diff --git a/modules/userId/userId.md b/modules/userId/userId.md index b10aa2adc20..a47ecd9f08c 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -25,12 +25,13 @@ pbjs.setConfig({ }, { name: "id5Id", params: { - partner: 173 //Set your real ID5 partner ID here for production, please ask for one at http://id5.io/prebid + partner: 173, // Set your real ID5 partner ID here for production, please ask for one at https://id5.io/universal-id + pd: "some-pd-string" // See https://wiki.id5.io/display/PD/Prebid.js+UserId+Module for details }, storage: { type: "cookie", - name: "id5id", - expires: 5, // Expiration of cookies in days + name: "id5id.1st", + expires: 90, // Expiration of cookies in days refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' }, }, { @@ -38,11 +39,6 @@ pbjs.setConfig({ params: { // Replace partner with comma-separated (if more than one) Parrable Partner Client ID(s) for Parrable-aware bid adapters in use partner: "30182847-e426-4ff9-b2b5-9ca1324ea09b" - }, - storage: { - type: 'cookie', - name: '_parrable_eid', - expires: 365 } }, { name: 'identityLink', @@ -64,6 +60,16 @@ pbjs.setConfig({ name: '_li_pbid', expires: 60 } + }, { + name: 'sharedId', + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } }], syncDelay: 5000, auctionDelay: 1000 @@ -74,7 +80,7 @@ pbjs.setConfig({ Example showing `localStorage` for user id data for some submodules ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: "unifiedId", params: { @@ -113,6 +119,16 @@ pbjs.setConfig({ name: '_li_pbid', expires: 60 } + }, { + name: 'sharedId', + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } }], syncDelay: 5000 } @@ -122,7 +138,7 @@ pbjs.setConfig({ Example showing how to configure a `value` object to pass directly to bid adapters ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: "pubCommonId", value: { @@ -132,6 +148,10 @@ pbjs.setConfig({ { name: "id5Id", value: { "id5id": "ID5-abcdef" } + }, + { + name: "netId", + value: { "netId": "fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg" } }], syncDelay: 5000 } diff --git a/modules/userIdTargeting.js b/modules/userIdTargeting.js index 2a19bd62fbb..3ed8b2a14b5 100644 --- a/modules/userIdTargeting.js +++ b/modules/userIdTargeting.js @@ -1,8 +1,8 @@ -import {config} from '../src/config'; -import {getGlobal} from '../src/prebidGlobal'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; import CONSTANTS from '../src/constants.json'; -import events from '../src/events'; -import { isStr, isPlainObject, isBoolean, isFn, hasOwn, logInfo } from '../src/utils'; +import events from '../src/events.js'; +import { isStr, isPlainObject, isBoolean, isFn, hasOwn, logInfo } from '../src/utils.js'; const MODULE_NAME = 'userIdTargeting'; const GAM = 'GAM'; diff --git a/modules/userIdTargeting.md b/modules/userIdTargeting.md index f99fd5308b3..340c1b6abf2 100644 --- a/modules/userIdTargeting.md +++ b/modules/userIdTargeting.md @@ -8,7 +8,7 @@ pbjs.setConfig({ // your existing userIds config - usersync: { + userSync: { userIds: [{...}, ...] }, diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js new file mode 100644 index 00000000000..395953fb737 --- /dev/null +++ b/modules/vdoaiBidAdapter.js @@ -0,0 +1,119 @@ +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'vdo.ai'; +const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @return Array Info describing the request to the server. + * @param validBidRequests + * @param bidderRequest + */ + buildRequests: function (validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return []; + } + return validBidRequests.map(bidRequest => { + const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes)[0]; + const width = sizes.split('x')[0]; + const height = sizes.split('x')[1]; + const payload = { + placementId: bidRequest.params.placementId, + width: width, + height: height, + bidId: bidRequest.bidId, + referer: bidderRequest.refererInfo.referer, + id: bidRequest.auctionId + }; + bidRequest.params.bidFloor && (payload['bidFloor'] = bidRequest.params.bidFloor); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const creativeId = response.adid || 0; + // const width = response.w || 0; + const width = bidRequest.data.width; + // const height = response.h || 0; + const height = bidRequest.data.height; + const cpm = response.price || 0; + + response.rWidth = width; + response.rHeight = height; + + const adCreative = response.vdoCreative; + + if (width !== 0 && height !== 0 && cpm !== 0 && creativeId !== 0) { + // const dealId = response.dealid || ''; + const currency = response.cur || 'USD'; + const netRevenue = true; + // const referrer = bidRequest.data.referer; + const bidResponse = { + requestId: response.bidId, + cpm: cpm, + width: width, + height: height, + creativeId: creativeId, + // dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout'), + // referrer: referrer, + // ad: response.adm + ad: adCreative + }; + bidResponses.push(bidResponse); + } + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponse) { + let syncUrls = serverResponse[0] && serverResponse[0].body && serverResponse[0].body.cookiesync && serverResponse[0].body.cookiesync.bidder_status; + + if (syncOptions.iframeEnabled && syncUrls && syncUrls.length > 0) { + let prebidSyncUrls = syncUrls.map(syncObj => { + return { + url: syncObj.usersync.url, + type: 'iframe' + } + }) + return prebidSyncUrls; + } + return []; + }, + + onTImeout: function(data) {}, + onBidWon: function(bid) {}, + onSetTargeting: function(bid) {} +}; +registerBidder(spec); diff --git a/modules/vdoaiBidAdapter.md b/modules/vdoaiBidAdapter.md new file mode 100644 index 00000000000..81bd8e69c1d --- /dev/null +++ b/modules/vdoaiBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: VDO.AI Bidder Adapter +Module Type: Bidder Adapter +Maintainer: arjit@z1media.com +``` + +# Description + +Module that connects to VDO.AI's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] // a display size + } + }, + bids: [ + { + bidder: "vdo.ai", + params: { + placement: 'newsdv77', + bidFloor: 0.01 // Optional + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/viBidAdapter.js b/modules/viBidAdapter.js index 8d8cb06e47e..4a09a2d2fc1 100644 --- a/modules/viBidAdapter.js +++ b/modules/viBidAdapter.js @@ -1,5 +1,5 @@ -import { registerBidder } from '../src/adapters/bidderFactory'; -import * as mediaTypes from '../src/mediaTypes'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as mediaTypes from '../src/mediaTypes.js'; export function get(path, obj, notFound) { path = typeof path === 'string' ? path.split('.') : path; diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 4c1abd1c390..382833fbca9 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,10 +1,17 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {BANNER} from '../src/mediaTypes'; -export const URL = 'https://prebid.cootlogix.com'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const GLVID = 744; +const DEFAULT_SUB_DOMAIN = 'prebid'; const BIDDER_CODE = 'vidazoo'; +const BIDDER_VERSION = '1.0.0'; const CURRENCY = 'USD'; const TTL_SECONDS = 60 * 5; +const DEAL_ID_EXPIRY = 1000 * 60 * 15; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +const SESSION_ID_KEY = 'vidSid'; const INTERNAL_SYNC_TYPE = { IFRAME: 'iframe', IMAGE: 'img' @@ -13,31 +20,86 @@ const EXTERNAL_SYNC_TYPE = { IFRAME: 'iframe', IMAGE: 'image' }; +export const SUPPORTED_ID_SYSTEMS = { + 'britepoolid': 1, + 'criteoId': 1, + 'digitrustid': 1, + 'id5id': 1, + 'idl_env': 1, + 'lipb': 1, + 'netId': 1, + 'parrableId': 1, + 'pubcid': 1, + 'tdid': 1, +}; +const storage = getStorageManager(GLVID); + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.cootlogix.com`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} function isBidRequestValid(bid) { const params = bid.params || {}; - return !!(params.cId && params.pId); + return !!(extractCID(params) && extractPID(params)); } -function buildRequest(bid, topWindowUrl, size, bidderRequest) { - 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'); +function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { + const { params, bidId, userId, adUnitCode } = bid; + const { bidFloor, ext } = params; + const hashUrl = hashCode(topWindowUrl); + const dealId = getNextDealId(hashUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const sId = getVidazooSessionId(); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); - const dto = { - method: 'GET', - url: `${URL}/prebid/${cId}`, - data: { - url: encodeURIComponent(topWindowUrl), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - publisherId: pId, - consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString, - width, - height + let data = { + url: encodeURIComponent(topWindowUrl), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + adUnitCode: adUnitCode, + publisherId: pId, + sessionId: sId, + sizes: sizes, + dealId: dealId, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}` + }; + + appendUserIdsToRequestPayload(data, userId); + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent + } + + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data }; utils._each(ext, (value, key) => { @@ -47,15 +109,36 @@ function buildRequest(bid, topWindowUrl, size, bidderRequest) { return dto; } +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + utils._each(userIds, (userId, idSystemProviderName) => { + if (SUPPORTED_ID_SYSTEMS[idSystemProviderName]) { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = utils.deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + default: + payloadRef[key] = userId; + } + } + }); +} + function buildRequests(validBidRequests, bidderRequest) { const topWindowUrl = bidderRequest.refererInfo.referer; const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = utils.parseSizesInput(validBidRequest.sizes); - sizes.forEach(size => { - const request = buildRequest(validBidRequest, topWindowUrl, size, bidderRequest); - requests.push(request); - }); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + requests.push(request); }); return requests; } @@ -64,30 +147,37 @@ 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; + const { bidId } = request.data; + const { results } = serverResponse.body; + + let output = []; + try { - return [{ - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - ad: ad - }]; + results.forEach(result => { + const { creativeId, ad, price, exp, width, height, currency } = result; + if (!ad || !price) { + return; + } + output.push({ + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + ad: ad + }) + }); + return output; } catch (e) { return []; } } function getUserSyncs(syncOptions, responses) { - const {iframeEnabled, pixelEnabled} = syncOptions; + const { iframeEnabled, pixelEnabled } = syncOptions; if (iframeEnabled) { return [{ @@ -100,21 +190,19 @@ function getUserSyncs(syncOptions, responses) { 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: + const { body } = response; + const results = body ? body.results || [] : []; + results.forEach(result => { + (result.cookies || []).forEach(cookie => { + if (cookie.type === INTERNAL_SYNC_TYPE.IMAGE) { if (pixelEnabled && !lookup[cookie.src]) { syncs.push({ type: EXTERNAL_SYNC_TYPE.IMAGE, url: cookie.src }); } - break; - } + } + }); }); }); return syncs; @@ -123,8 +211,82 @@ function getUserSyncs(syncOptions, responses) { return []; } +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + } + return prefix + h; +} + +export function getNextDealId(key, expiry = DEAL_ID_EXPIRY) { + try { + const data = getStorageItem(key); + let currentValue = 0; + let timestamp; + + if (data && data.value && Date.now() - data.created < expiry) { + currentValue = data.value; + timestamp = data.created; + } + + const nextValue = currentValue + 1; + setStorageItem(key, nextValue, timestamp); + return nextValue; + } catch (e) { + return 0; + } +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getVidazooSessionId() { + return getStorageItem(SESSION_ID_KEY) || ''; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({ value, created }); + storage.setDataInLocalStorage(key, data); + } catch (e) { } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + export const spec = { code: BIDDER_CODE, + version: BIDDER_VERSION, supportedMediaTypes: [BANNER], isBidRequestValid, buildRequests, diff --git a/modules/vidazooBidAdapter.md b/modules/vidazooBidAdapter.md index 1e9dc47dd51..df7700cb05a 100644 --- a/modules/vidazooBidAdapter.md +++ b/modules/vidazooBidAdapter.md @@ -4,7 +4,7 @@ **Module Type:** Bidder Adapter -**Maintainer:** server-dev@getintent.com +**Maintainer:** dev@vidazoo.com # Description diff --git a/modules/videoNowBidAdapter.js b/modules/videoNowBidAdapter.js index 7b358f64939..b391af08d49 100644 --- a/modules/videoNowBidAdapter.js +++ b/modules/videoNowBidAdapter.js @@ -1,6 +1,7 @@ -import * as utils from '../src/utils' -import { registerBidder } from '../src/adapters/bidderFactory' -import { BANNER } from '../src/mediaTypes' +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER } from '../src/mediaTypes.js' +import { loadExternalScript } from '../src/adloader.js' const RTB_URL = 'https://bidder.videonow.ru/prebid' @@ -127,9 +128,7 @@ function createResponseBid(bidInfo, bidId, cur, placementId) { init[pId] = profileData // add vn_init js on the page - const scr = document.createElement('script') - scr.src = `${initPath}${~initPath.indexOf('?') ? '&' : '?'}profileId=${pId}` - el && el.appendChild(scr) + loadExternalScript(`${initPath}${~initPath.indexOf('?') ? '&' : '?'}profileId=${pId}`, 'outstream') } else { utils.logError(`bidAdapter ${BIDDER_CODE}: ${placementId} not found`) } @@ -171,10 +170,7 @@ function getUserSyncs(syncOptions, serverResponses) { function onBidWon(bid) { const { nurl } = bid || {} if (nurl) { - const img = document.createElement('img') - img.src = utils.replaceAuctionPrice(nurl, bid.cpm) - img.style.cssText = 'display:none !important;' - document.body.appendChild(img) + utils.triggerPixel(utils.replaceAuctionPrice(nurl, bid.cpm)); } } diff --git a/modules/videofyBidAdapter.js b/modules/videofyBidAdapter.js new file mode 100644 index 00000000000..11bc21303fd --- /dev/null +++ b/modules/videofyBidAdapter.js @@ -0,0 +1,300 @@ +import { VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'videofy'; +const TTL = 600; + +function avRenderer(bid) { + bid.renderer.push(function() { + let eventCallback = bid && bid.renderer && bid.renderer.handleVideoEvent ? bid.renderer.handleVideoEvent : null; + window.aniviewRenderer.renderAd({ + id: bid.adUnitCode + '_' + bid.adId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: bid.adUnitCode, + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + vastXml: bid.vastXml, + config: bid.params[0].rendererConfig, + eventsCallback: eventCallback, + bid: bid + }); + }); +} + +function newRenderer(bidRequest) { + const renderer = Renderer.install({ + url: 'https://player.srv-mars.com/script/6.1/prebidRenderer.js', + config: {}, + loaded: false, + }); + + try { + renderer.setRender(avRenderer); + } catch (err) { + } + + return renderer; +} + +function isBidRequestValid(bid) { + if (!bid.params || !bid.params.AV_PUBLISHERID || !bid.params.AV_CHANNELID) { return false; } + + return true; +} +let irc = 0; +function buildRequests(validBidRequests, bidderRequest) { + let bidRequests = []; + + for (let i = 0; i < validBidRequests.length; i++) { + let bidRequest = validBidRequests[i]; + var sizes = [[640, 480]]; + + if (bidRequest.mediaTypes && bidRequest.mediaTypes.video && bidRequest.mediaTypes.video.playerSize) { + sizes = bidRequest.mediaTypes.video.playerSize; + } else { + if (bidRequest.sizes) { + sizes = bidRequest.sizes; + } + } + if (sizes.length === 2 && typeof sizes[0] === 'number') { + sizes = [[sizes[0], sizes[1]]]; + } + + for (let j = 0; j < sizes.length; j++) { + let size = sizes[j]; + let playerWidth; + let playerHeight; + + if (size && size.length == 2) { + playerWidth = size[0]; + playerHeight = size[1]; + } else { + playerWidth = 640; + playerHeight = 480; + } + + let s2sParams = {}; + + for (var attrname in bidRequest.params) { + if (bidRequest.params.hasOwnProperty(attrname) && attrname.indexOf('AV_') == 0) { + s2sParams[attrname] = bidRequest.params[attrname]; + } + }; + + if (s2sParams.AV_APPPKGNAME && !s2sParams.AV_URL) { s2sParams.AV_URL = s2sParams.AV_APPPKGNAME; } + if (!s2sParams.AV_IDFA && !s2sParams.AV_URL) { + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + s2sParams.AV_URL = bidderRequest.refererInfo.referer; + } else { + s2sParams.AV_URL = window.location.href; + } + } + if (s2sParams.AV_IDFA && !s2sParams.AV_AID) { s2sParams.AV_AID = s2sParams.AV_IDFA; } + if (s2sParams.AV_AID && !s2sParams.AV_IDFA) { s2sParams.AV_IDFA = s2sParams.AV_AID; } + + s2sParams.cb = Math.floor(Math.random() * 999999999); + s2sParams.AV_WIDTH = playerWidth; + s2sParams.AV_HEIGHT = playerHeight; + s2sParams.bidWidth = playerWidth; + s2sParams.bidHeight = playerHeight; + s2sParams.bidId = bidRequest.bidId; + s2sParams.pbjs = 1; + s2sParams.tgt = 10; + s2sParams.s2s = '1'; + s2sParams.irc = irc; + irc++; + s2sParams.wpm = 1; + + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies) { + s2sParams.AV_GDPR = 1; + s2sParams.AV_CONSENT = bidderRequest.gdprConsent.consentString; + } + } + if (bidderRequest && bidderRequest.uspConsent) { + s2sParams.AV_CCPA = bidderRequest.uspConsent; + } + + let serverDomain = (bidRequest.params && bidRequest.params.serverDomain) ? bidRequest.params.serverDomain : 'servx.srv-mars.com'; + let servingUrl = 'https://' + serverDomain + '/api/adserver/vast3/'; + + bidRequests.push({ + method: 'GET', + url: servingUrl, + data: s2sParams, + bidRequest + }); + } + } + + return bidRequests; +} +function getCpmData(xml) { + let ret = {cpm: 0, currency: 'USD'}; + if (xml) { + let ext = xml.getElementsByTagName('Extensions'); + if (ext && ext.length > 0) { + ext = ext[0].getElementsByTagName('Extension'); + if (ext && ext.length > 0) { + for (var i = 0; i < ext.length; i++) { + if (ext[i].getAttribute('type') == 'ANIVIEW') { + let price = ext[i].getElementsByTagName('Cpm'); + if (price && price.length == 1) { + ret.cpm = price[0].textContent; + } + break; + } + } + } + } + } + return ret; +} +function interpretResponse(serverResponse, bidRequest) { + let bidResponses = []; + if (serverResponse && serverResponse.body) { + if (serverResponse.error) { + return bidResponses; + } else { + try { + let bidResponse = {}; + if (bidRequest && bidRequest.data && bidRequest.data.bidId && bidRequest.data.bidId !== '') { + let xmlStr = serverResponse.body; + let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); + if (xml && xml.getElementsByTagName('parsererror').length == 0) { + let cpmData = getCpmData(xml); + if (cpmData && cpmData.cpm > 0) { + bidResponse.requestId = bidRequest.data.bidId; + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.ad = ''; + bidResponse.cpm = cpmData.cpm; + bidResponse.width = bidRequest.data.AV_WIDTH; + bidResponse.height = bidRequest.data.AV_HEIGHT; + bidResponse.ttl = TTL; + bidResponse.creativeId = xml.getElementsByTagName('Ad') && xml.getElementsByTagName('Ad')[0] && xml.getElementsByTagName('Ad')[0].getAttribute('id') ? xml.getElementsByTagName('Ad')[0].getAttribute('id') : 'creativeId'; + bidResponse.currency = cpmData.currency; + bidResponse.netRevenue = true; + var blob = new Blob([xmlStr], { + type: 'application/xml' + }); + bidResponse.vastUrl = window.URL.createObjectURL(blob); + bidResponse.vastXml = xmlStr; + bidResponse.mediaType = VIDEO; + if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = newRenderer(bidRequest); } + + bidResponses.push(bidResponse); + } + } else {} + } else {} + } catch (e) {} + } + } else {} + + return bidResponses; +} + +function getSyncData(xml, options) { + let ret = []; + if (xml) { + let ext = xml.getElementsByTagName('Extensions'); + if (ext && ext.length > 0) { + ext = ext[0].getElementsByTagName('Extension'); + if (ext && ext.length > 0) { + for (var i = 0; i < ext.length; i++) { + if (ext[i].getAttribute('type') == 'ANIVIEW') { + let syncs = ext[i].getElementsByTagName('AdServingSync'); + if (syncs && syncs.length == 1) { + try { + let data = JSON.parse(syncs[0].textContent); + if (data && data.trackers && data.trackers.length) { + data = data.trackers; + for (var j = 0; j < data.length; j++) { + if (typeof data[j] === 'object' && typeof data[j].url === 'string' && data[j].e === 'inventory') { + if (data[j].t == 1 && options.pixelEnabled) { + ret.push({url: data[j].url, type: 'image'}); + } else { + if (data[j].t == 3 && options.iframeEnabled) { + ret.push({url: data[j].url, type: 'iframe'}); + } + } + } + } + } + } catch (e) {} + } + break; + } + } + } + } + } + return ret; +} + +function getUserSyncs(syncOptions, serverResponses) { + if (serverResponses && serverResponses[0] && serverResponses[0].body) { + if (serverResponses.error) { + return []; + } else { + try { + let xmlStr = serverResponses[0].body; + let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); + if (xml && xml.getElementsByTagName('parsererror').length == 0) { + let syncData = getSyncData(xml, syncOptions); + return syncData; + } + } catch (e) {} + } + } +} + +function onBidWon(bid) { + sendbeacon(bid, 17); +} + +function onTimeout(bid) { + sendbeacon(bid, 19); +} + +function onSetTargeting(bid) { + sendbeacon(bid, 20); +} + +function sendbeacon(bid, type) { + const bidCopy = { + bidder: bid.bidder, + cpm: bid.cpm, + originalCpm: bid.originalCpm, + currency: bid.currency, + originalCurrency: bid.originalCurrency, + timeToRespond: bid.timeToRespond, + statusMessage: bid.statusMessage, + width: bid.width, + height: bid.height, + size: bid.size, + params: bid.params, + status: bid.status, + adserverTargeting: bid.adserverTargeting, + ttl: bid.ttl + }; + const bidString = JSON.stringify(bidCopy); + const encodedBuf = window.btoa(bidString); + utils.triggerPixel('https://beacon.videofy.io/notification/rtb/beacon/?bt=' + type + '&bid=hcwqso&hb_j=' + encodedBuf, null); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon, + onTimeout, + onSetTargeting +}; + +registerBidder(spec); diff --git a/modules/videofyBidAdapter.md b/modules/videofyBidAdapter.md new file mode 100644 index 00000000000..b50eaf5672e --- /dev/null +++ b/modules/videofyBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Videofy Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support1@videofy.ai +``` + +# Description + +Connects to Videofy for bids. + +Videofy bid adapter supports Video ads currently. + +# Sample Ad Unit: For Publishers +```javascript +var videoAdUnit = [ +{ + code: 'video1', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream' + }, + }, + bids: [{ + bidder: 'videofy', + params: { + AV_PUBLISHERID: '55b78633181f4603178b4568', + AV_CHANNELID: '5d19dfca4b6236688c0a2fc4' + } + }] +}]; +``` + +``` diff --git a/modules/videoreachBidAdapter.js b/modules/videoreachBidAdapter.js new file mode 100644 index 00000000000..710920c54fc --- /dev/null +++ b/modules/videoreachBidAdapter.js @@ -0,0 +1,119 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +const utils = require('../src/utils.js'); +const BIDDER_CODE = 'videoreach'; +const ENDPOINT_URL = 'https://a.videoreach.com/hb/'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner'], + + isBidRequestValid: function(bid) { + return !!(bid.params.TagId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + let data = { + data: validBidRequests.map(function(bid) { + return { + TagId: utils.getValue(bid.params, 'TagId'), + adUnitCode: utils.getBidIdParameter('adUnitCode', bid), + bidId: utils.getBidIdParameter('bidId', bid), + bidderRequestId: utils.getBidIdParameter('bidderRequestId', bid), + auctionId: utils.getBidIdParameter('auctionId', bid), + transactionId: utils.getBidIdParameter('transactionId', bid) + } + }) + }; + + if (bidderRequest && bidderRequest.refererInfo) { + data.referrer = bidderRequest.refererInfo.referer; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + data.gdpr = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(data) + }; + }, + + interpretResponse: function(serverResponse) { + const bidResponses = []; + serverResponse = serverResponse.body; + + if (serverResponse.responses) { + serverResponse.responses.forEach(function (bid) { + const bidResponse = { + cpm: bid.cpm, + width: bid.width, + height: bid.height, + currency: bid.currency, + netRevenue: true, + ttl: bid.ttl, + ad: bid.ad, + requestId: bid.bidId, + creativeId: bid.creativeId + }; + bidResponses.push(bidResponse); + }); + } + return bidResponses; + }, + + getUserSyncs: function(syncOptions, responses, gdprConsent) { + const syncs = []; + + if (responses.length && responses[0].body.responses.length) { + let params = ''; + var gdpr; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += 'gdpr=' + gdprConsent.gdprApplies + '&gdpr_consent=' + gdprConsent.consentString; + } else { + params += 'gdpr_consent=' + gdprConsent.consentString; + } + } + + if (syncOptions.pixelEnabled) { + const SyncPixels = responses[0].body.responses[0].sync; + + if (SyncPixels) { + SyncPixels.forEach(sync => { + gdpr = (params) ? ((sync.split('?')[1] ? '&' : '?') + params) : ''; + + syncs.push({ + type: 'image', + url: sync + gdpr + }); + }); + } + } + + if (syncOptions.iframeEnabled) { + const SyncFrame = responses[0].body.responses[0].syncframe; + + if (SyncFrame) { + SyncFrame.forEach(sync => { + gdpr = (params) ? ((sync.split('?')[1] ? '&' : '?') + params) : ''; + + syncs.push({ + type: 'iframe', + url: sync + gdpr + }); + }); + } + } + } + + return syncs; + } +}; + +registerBidder(spec); diff --git a/modules/videoreachBidAdapter.md b/modules/videoreachBidAdapter.md index a68c9b26aeb..a0bc1081b1d 100644 --- a/modules/videoreachBidAdapter.md +++ b/modules/videoreachBidAdapter.md @@ -16,12 +16,17 @@ Use `videoreach` as bidder. ``` var adUnits = [{ code: 'your-slot', //use exactly the same code as your slot div id. - sizes: [[1, 1]], - bids: [{ - bidder: 'videoreach', - params: { - TagId: 'XXXXX' + mediaTypes: { + banner: { + sizes: [[1,1]] } + }, + bids: [{ + bidder: 'videoreach', + params: { + TagId: 'XXXXX' + } }] }]; + ``` diff --git a/modules/viewdeosDXBidAdapter.js b/modules/viewdeosDXBidAdapter.js index a591a28b18d..52894ef2dd9 100644 --- a/modules/viewdeosDXBidAdapter.js +++ b/modules/viewdeosDXBidAdapter.js @@ -1,11 +1,11 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import {VIDEO, BANNER} from '../src/mediaTypes'; -import {Renderer} from '../src/Renderer'; -import findIndex from 'core-js/library/fn/array/find-index'; - -const URL = '//hb.sync.viewdeos.com/auction/'; -const OUTSTREAM_SRC = '//player.sync.viewdeos.com/outstream-unit/2.01/outstream.min.js'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {VIDEO, BANNER} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import findIndex from 'core-js-pure/features/array/find-index.js'; + +const URL = 'https://ghb.sync.viewdeos.com/auction/'; +const OUTSTREAM_SRC = 'https://player.sync.viewdeos.com/outstream-unit/2.01/outstream.min.js'; const BIDDER_CODE = 'viewdeosDX'; const OUTSTREAM = 'outstream'; const DISPLAY = 'display'; @@ -112,7 +112,8 @@ function parseRTBResponse(serverResponse, bidderRequest) { }); if (serverBid.cpm !== 0 && requestId !== -1) { - const bid = createBid(serverBid, getMediaType(bidderRequest.bids[requestId])); + const bidReq = bidderRequest.bids[requestId]; + const bid = createBid(serverBid, getMediaType(bidReq), bidReq.params); bids.push(bid); } @@ -131,6 +132,10 @@ function bidToTag(bidRequests, bidderRequest) { tag.gdpr_consent = utils.deepAccess(bidderRequest, 'gdprConsent.consentString'); } + if (utils.deepAccess(bidderRequest, 'bidderRequest.uspConsent')) { + tag.us_privacy = bidderRequest.uspConsent; + } + for (let i = 0, length = bidRequests.length; i < length; i++) { Object.assign(tag, prepareRTBRequestParams(i, bidRequests[i])); } @@ -174,7 +179,7 @@ function getMediaType(bidderRequest) { * @param mediaType {Object} * @returns {object} */ -function createBid(bidResponse, mediaType) { +function createBid(bidResponse, mediaType, bidderParams) { const bid = { requestId: bidResponse.requestId, creativeId: bidResponse.cmpId, @@ -201,7 +206,7 @@ function createBid(bidResponse, mediaType) { Object.assign(bid, { mediaType: 'video', adResponse: bidResponse, - renderer: newRenderer(bidResponse.requestId) + renderer: newRenderer(bidResponse.requestId, bidderParams) }); } @@ -213,10 +218,11 @@ function createBid(bidResponse, mediaType) { * @param requestId * @returns {*} */ -function newRenderer(requestId) { +function newRenderer(requestId, bidderParams) { const renderer = Renderer.install({ id: requestId, url: OUTSTREAM_SRC, + config: bidderParams.outstream || {}, loaded: false }); @@ -231,12 +237,13 @@ function newRenderer(requestId) { */ function outstreamRender(bid) { bid.renderer.push(() => { - window.VOutstreamAPI.initOutstreams([{ + const opts = Object.assign({}, bid.renderer.getConfig(), { width: bid.width, height: bid.height, vastUrl: bid.vastUrl, elId: bid.adUnitCode - }]); + }); + window.VOutstreamAPI.initOutstreams([opts]); }); } diff --git a/modules/viewdeosDXBidAdapter.md b/modules/viewdeosDXBidAdapter.md index d9ede8cc312..a3fa34525b8 100644 --- a/modules/viewdeosDXBidAdapter.md +++ b/modules/viewdeosDXBidAdapter.md @@ -14,10 +14,10 @@ Get access to multiple demand partners across Viewdeos and maximize your yield w // Video instream adUnit { code: 'div-test-div', - sizes: [[640, 480]], mediaTypes: { video: { - context: 'instream' + context: 'instream', + playerSize: [[640, 480]] } }, bids: [{ @@ -31,16 +31,20 @@ Get access to multiple demand partners across Viewdeos and maximize your yield w // Video outstream adUnit { code: 'outstream-test-div', - sizes: [[640, 480]], mediaTypes: { video: { - context: 'outstream' + context: 'outstream', + playerSize: [[640, 480]] } }, bids: [{ bidder: 'viewdeosDX', params: { - aid: 331133 + aid: 331133, + outstream: { + default_volume:50, + video_controls:'show' + } } }] }, diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index b043d2b7709..511e658c947 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { config } from '../src/config'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'visx'; const ENDPOINT_URL = 'https://t.visx.net/hb'; const TIME_TO_LIVE = 360; @@ -36,6 +36,8 @@ export const spec = { config.getConfig('currency.adServerCurrency') || DEFAULT_CUR; let reqId; + let payloadSchain; + let payloadUserId; if (currencyWhiteList.indexOf(currency) === -1) { utils.logError(LOG_ERROR_MESS.notAllowedCurrency + currency); @@ -44,8 +46,14 @@ export const spec = { bids.forEach(bid => { reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; + const {params: {uid}, adUnitCode, schain, userId} = bid; auids.push(uid); + if (!payloadSchain && schain) { + payloadSchain = schain; + } + if (!payloadUserId && userId) { + payloadUserId = userId; + } const sizesId = utils.parseSizesInput(bid.sizes); if (!slotsMapByUid[uid]) { @@ -84,6 +92,22 @@ export const spec = { wrapperVersion: '$prebid.version$' }; + if (payloadSchain) { + payload.schain = JSON.stringify(payloadSchain); + } + + if (payloadUserId) { + if (payloadUserId.tdid) { + payload.tdid = payloadUserId.tdid; + } + if (payloadUserId.id5id) { + payload.id5 = payloadUserId.id5id; + } + if (payloadUserId.digitrustid && payloadUserId.digitrustid.data && payloadUserId.digitrustid.data.id) { + payload.dtid = payloadUserId.digitrustid.data.id; + } + } + if (bidderRequest) { if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { payload.u = bidderRequest.refererInfo.referer; @@ -179,7 +203,6 @@ function _addBidResponse(serverBid, bidsMap, currency, bidResponses, bidsWithout const bid = slot.bids.shift(); bidResponses.push({ requestId: bid.bidId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, diff --git a/modules/vmgBidAdapter.js b/modules/vmgBidAdapter.js index 2bed62c5d67..57a812ec466 100644 --- a/modules/vmgBidAdapter.js +++ b/modules/vmgBidAdapter.js @@ -1,4 +1,4 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'vmg'; const ENDPOINT = 'https://predict.vmg.nyc'; diff --git a/modules/vrtcalBidAdapter.js b/modules/vrtcalBidAdapter.js index 139f985ed67..ff24803f688 100644 --- a/modules/vrtcalBidAdapter.js +++ b/modules/vrtcalBidAdapter.js @@ -1,6 +1,6 @@ -import {registerBidder} from '../src/adapters/bidderFactory'; -import { BANNER } from '../src/mediaTypes'; -import {ajax} from '../src/ajax'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import {ajax} from '../src/ajax.js'; export const spec = { code: 'vrtcal', diff --git a/modules/vubleBidAdapter.js b/modules/vubleBidAdapter.js index 288390f01c9..d14f24e9993 100644 --- a/modules/vubleBidAdapter.js +++ b/modules/vubleBidAdapter.js @@ -1,8 +1,8 @@ // Vuble Adapter -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -import { Renderer } from '../src/Renderer'; +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'vuble'; @@ -108,6 +108,13 @@ export const spec = { adUnitCode: bidRequest.adUnitCode }; + if (bidderRequest && bidderRequest.gdprConsent) { + data.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + gdpr_applies: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + } + } + return { method: 'POST', url: url, diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js new file mode 100644 index 00000000000..b9114d4f1bf --- /dev/null +++ b/modules/waardexBidAdapter.js @@ -0,0 +1,217 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const domain = 'hb.justbidit.xyz'; +const httpsPort = 8843; +const path = '/prebid'; + +const ENDPOINT = `https://${domain}:${httpsPort}${path}`; + +const BIDDER_CODE = 'waardex'; + +/** + * @param {Array} requestSizes + * + * @returns {Array} + * */ +function transformSizes(requestSizes) { + let sizes = []; + if ( + Array.isArray(requestSizes) && + !Array.isArray(requestSizes[0]) + ) { + sizes[0] = { + width: parseInt(requestSizes[0], 10) || 0, + height: parseInt(requestSizes[1], 10) || 0, + }; + } else if ( + Array.isArray(requestSizes) && + Array.isArray(requestSizes[0]) + ) { + sizes = requestSizes.map(item => { + return { + width: parseInt(item[0], 10) || 0, + height: parseInt(item[1], 10) || 0, + } + }); + } + return sizes; +} + +/** + * @param {Object} banner + * @param {Array} banner.sizes + * + * @returns {Object} + * */ +function createBannerObject(banner) { + return { + sizes: transformSizes(banner.sizes), + }; +} + +/** + * @param {Array} validBidRequests + * + * @returns {Object} + * */ +function buildBidRequests(validBidRequests) { + return validBidRequests.map((validBidRequest) => { + const params = validBidRequest.params; + + const item = { + bidId: validBidRequest.bidId, + bidfloor: parseFloat(params.bidfloor) || 0, + position: parseInt(params.position) || 1, + instl: parseInt(params.instl) || 0, + }; + if (validBidRequest.mediaTypes[BANNER]) { + item[BANNER] = createBannerObject(validBidRequest.mediaTypes[BANNER]); + } + return item; + }); +} + +/** + * @param {Object} bidderRequest + * @param {String} bidderRequest.userAgent + * @param {String} bidderRequest.refererInfo + * @param {String} bidderRequest.uspConsent + * @param {Object} bidderRequest.gdprConsent + * @param {String} bidderRequest.gdprConsent.consentString + * @param {String} bidderRequest.gdprConsent.gdprApplies + * + * @returns {Object} - { + * ua: string, + * language: string, + * [referer]: string, + * [us_privacy]: string, + * [consent_string]: string, + * [consent_required]: string, + * [coppa]: boolean, + * } + * */ +function getCommonBidsData(bidderRequest) { + const payload = { + ua: navigator.userAgent || '', + language: navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '', + + }; + if (bidderRequest && bidderRequest.refererInfo) { + payload.referer = encodeURIComponent(bidderRequest.refererInfo.referer); + } + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies, + } + } + payload.coppa = !!config.getConfig('coppa'); + + return payload; +} + +/** + * this function checks either bid response is valid or noе + * + * @param {Object} bid + * @param {string} bid.requestId + * @param {number} bid.cpm + * @param {string} bid.creativeId + * @param {number} bid.ttl + * @param {string} bid.currency + * @param {number} bid.width + * @param {number} bid.height + * @param {string} bid.ad + * + * @returns {boolean} + * */ +function isBidValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + return Boolean(bid.width && bid.height && bid.ad); +} + +/** + * @param {Object} serverBid + * + * @returns {Object|null} + * */ +function createBid(serverBid) { + const bid = { + requestId: serverBid.id, + cpm: serverBid.price, + currency: 'USD', + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.crid, + netRevenue: true, + ttl: 3000, + ad: serverBid.adm, + dealId: serverBid.dealid, + meta: { + cid: serverBid.cid, + adomain: serverBid.adomain, + mediaType: serverBid.ext.mediaType + }, + }; + + return isBidValid(bid) ? bid : null; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => Boolean(bid.bidId && bid.params && +bid.params.zoneId), + + /** + * @param {Object[]} validBidRequests - array of valid bid requests + * @param {Object} bidderRequest - an array of valid bid requests + * + * */ + buildRequests(validBidRequests, bidderRequest) { + const payload = getCommonBidsData(bidderRequest); + payload.bidRequests = buildBidRequests(validBidRequests); + + let zoneId = ''; + if (validBidRequests[0] && validBidRequests[0].params && +validBidRequests[0].params.zoneId) { + zoneId = +validBidRequests[0].params.zoneId; + } + + const url = `${ENDPOINT}?pubId=${zoneId}`; + + return { + method: 'POST', + url, + data: payload + }; + }, + + /** + * Unpack the response from the server into a list of bids. + */ + interpretResponse(serverResponse, bidRequest) { + const bids = []; + serverResponse = serverResponse.body; + + if (serverResponse.seatbid && serverResponse.seatbid[0]) { + const oneSeatBid = serverResponse.seatbid[0]; + oneSeatBid.bid.forEach(serverBid => { + const bid = createBid(serverBid); + if (bid) { + bids.push(bid); + } + }); + } + return bids; + }, +} + +registerBidder(spec); diff --git a/modules/waardexBidAdapter.md b/modules/waardexBidAdapter.md new file mode 100644 index 00000000000..44ee720d31a --- /dev/null +++ b/modules/waardexBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +``` +Module Name: Waardex Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@prebid.org +``` + +# Description + +Connects to Waardex exchange for bids. + +Waardex bid adapter supports Banner. + +# Test Parameters + +``` + +var sizes = [ + [300, 250] +]; +var PREBID_TIMEOUT = 5000; +var FAILSAFE_TIMEOUT = 5000; + +var adUnits = [{ + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: sizes, + }, + }, + bids: [{ + bidder: 'waardex', + params: { + placementId: 13144370, + position: 1, // add position openrtb + bidfloor: 0.5, + instl: 0, // 1 - full screen + pubId: 1, + } + }] +},{ + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: sizes, + }, + }, + bids: [{ + bidder: 'waardex', + params: { + placementId: 333333333333, + position: 1, // add position openrtb + bidfloor: 0.5, + instl: 0, // 1 - full screen + pubId: 1, + } + }] +}]; +``` diff --git a/modules/welectBidAdapter.js b/modules/welectBidAdapter.js new file mode 100644 index 00000000000..3913cfde6a0 --- /dev/null +++ b/modules/welectBidAdapter.js @@ -0,0 +1,87 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'welect'; +const DEFAULT_DOMAIN = 'www.welect.de'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['wlt'], + supportedMediaTypes: ['video'], + + // 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 utils.deepAccess(bid, 'mediaTypes.video.context') === 'instream' && !!(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 (validBidRequests) { + return validBidRequests.map(bidRequest => { + let rawSizes = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; + let size = rawSizes[0] + + let domain = bidRequest.params.domain || DEFAULT_DOMAIN; + + let url = `https://${domain}/api/v2/preflight/by_alias/${bidRequest.params.placementId}`; + + let gdprConsent = null; + + if (bidRequest && bidRequest.gdprConsent) { + gdprConsent = { + gdpr_consent: + { + gdpr_applies: bidRequest.gdprConsent.gdprApplies, + gdpr_consent: bidRequest.gdprConsent.gdprConsent + } + } + } + + const data = { + width: size[0], + height: size[1], + bid_id: bidRequest.bidId, + ...gdprConsent + } + + return { + method: 'POST', + url: url, + data: data, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + } + }); + }, + /** + * 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 responseBody = serverResponse.body; + + if (typeof responseBody !== 'object' || responseBody.available !== true) { + return []; + } + + const bidResponses = []; + const bidResponse = responseBody.bidResponse; + bidResponses.push(bidResponse); + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/welectBidAdapter.md b/modules/welectBidAdapter.md new file mode 100644 index 00000000000..7f72a0bd949 --- /dev/null +++ b/modules/welectBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: Welect Bidder Adapter +Module Type: Welect Adapter +Maintainer: nick.duitz@9elements.com +``` + +# Description + +Module that connects to Welect's demand sources + +# Test Parameters +``` +var adUnits = [ + { + bidder: 'welect', + params: { + placementId: 'exampleId', + domain: 'www.welect.de' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + }; +]; +``` \ No newline at end of file diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js new file mode 100644 index 00000000000..3cea5ca57a1 --- /dev/null +++ b/modules/widespaceBidAdapter.js @@ -0,0 +1,258 @@ +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { + parseQueryStringParameters, + parseSizesInput +} from '../src/utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import find from 'core-js-pure/features/array/find.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +const BIDDER_CODE = 'widespace'; +const WS_ADAPTER_VERSION = '2.0.1'; +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, bidderRequest) { + 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(perfData => JSON.parse(perfData)); + 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; + } + + 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)), + 'gdprCmp': bidderRequest && bidderRequest.gdprConsent ? 1 : 0, + 'hb': '1', + '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-$prebid.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) { + DEMO_DATA_PARAMS.forEach((key) => { + if (bid.params.demo[key]) { + data[key] = bid.params.demo[key]; + } + }); + } + + // Include performance data + if (PERF_DATA[i]) { + Object.keys(PERF_DATA[i]).forEach((perfDataKey) => { + data[perfDataKey] = PERF_DATA[i][perfDataKey]; + }); + } + + // 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; + } + + // 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; + } + + // GDPR Consent info + if (data.gdprCmp) { + const {gdprApplies, consentString, vendorData} = bidderRequest.gdprConsent; + const hasGlobalScope = vendorData && vendorData.hasGlobalScope; + data.gdprApplies = gdprApplies ? 1 : gdprApplies === undefined ? '' : 0; + data.gdprConsentData = consentString; + data.gdprHasGlobalScope = hasGlobalScope ? 1 : hasGlobalScope === undefined ? '' : 0; + } + + // 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 (storage.hasLocalStorage()) { + storage.setDataInLocalStorage(name, value); + return true; + } else if (storage.cookiesAreEnabled()) { + const theDate = new Date(); + const expDate = new Date(theDate.setMonth(theDate.getMonth() + 12)).toGMTString(); + storage.setCookie(name, value, expDate); + return true; + } +} + +function getData(name, remove = true) { + let data = []; + if (storage.hasLocalStorage()) { + Object.keys(localStorage).filter((key) => { + if (key.indexOf(name) > -1) { + data.push(storage.getDataFromLocalStorage(key)); + if (remove) { + storage.removeDataFromLocalStorage(key); + } + } + }); + } + + if (storage.cookiesAreEnabled()) { + document.cookie.split(';').forEach((item) => { + let value = item.split('='); + if (value[0].indexOf(name) > -1) { + data.push(value[1]); + if (remove) { + storage.setCookie(value[0], '', 'Thu, 01 Jan 1970 00:00:01 GMT'); + } + } + }); + } + return data; +} + +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; +} + +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; +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +registerBidder(spec); diff --git a/modules/windtalkerBidAdapter.js b/modules/windtalkerBidAdapter.js new file mode 100644 index 00000000000..512cc7f839b --- /dev/null +++ b/modules/windtalkerBidAdapter.js @@ -0,0 +1,307 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +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: 'windtalker', + supportedMediaTypes: ['banner', 'native', 'video'], + + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.pubId && bid.params.placementId) + ), + buildRequests: (bidRequests, bidderRequest) => { + const request = { + id: bidRequests[0].bidderRequestId, + at: 2, + imp: bidRequests.map(slot => impression(slot)), + site: site(bidRequests), + app: app(bidRequests), + device: device(bidRequests), + }; + applyGdpr(bidderRequest, request); + return { + method: 'POST', + url: 'https://windtalkerdisplay.hb.adp3.net/', + 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 = idToBidMap[id].w; + bid.height = idToBidMap[id].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 banner(slot) { + if (slot.mediaType === 'banner' || utils.deepAccess(slot, 'mediaTypes.banner')) { + const sizes = utils.deepAccess(slot, 'mediaTypes.banner.sizes'); + if (sizes.length > 1) { + let format = []; + for (let f = 0; f < sizes.length; f++) { + format.push({'w': sizes[f][0], 'h': sizes[f][1]}); + } + return {'format': format}; + } else { + return { + w: sizes[0][0], + h: sizes[0][1] + } + } + } + return null; +} + +function videoImpression(slot) { + if (slot.mediaType === 'video' || utils.deepAccess(slot, 'mediaTypes.video')) { + const sizes = utils.deepAccess(slot, 'mediaTypes.video.playerSize'); + const video = { + w: sizes[0][0], + h: sizes[0][1], + 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: window.location.hostname, + }, + id: siteId.toString(), + ref: window.top.document.referrer, + page: window.location.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) { + utils.logError('windtalker.parse', 'ERROR', ex); + } + return null; +} + +function applyGdpr(bidderRequest, ortbRequest) { + if (bidderRequest && bidderRequest.gdprConsent) { + ortbRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; + ortbRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; + } +} + +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; +} + +registerBidder(spec); diff --git a/modules/windtalkerBidAdapter.md b/modules/windtalkerBidAdapter.md new file mode 100644 index 00000000000..f7441effc47 --- /dev/null +++ b/modules/windtalkerBidAdapter.md @@ -0,0 +1,86 @@ +# Overview + +**Module Name**: Windtalker Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: corbin@windtalker.io + +# Description + +Connects to Windtalker demand source to fetch bids. +Banner, Native, Video formats are supported. +Please use ```windtalker``` as the bidder code. + +# Test Parameters +``` + var adUnits = [{ + code: 'dfp-native-div', + mediaTypes: { + native: { + title: { + required: true, + len: 75 + }, + image: { + required: true + }, + body: { + len: 200 + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'windtalker', + params: { + pubId: '584971', + siteId: '584971', + 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],[300,600] + ], + } + }, + bids: [{ + bidder: 'windtalker', + params: { + pubId: '584971', + siteId: '584971', + placementId: '123', + } + }] + }, + { + code: 'dfp-video-div', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: "instream" + } + }, + bids: [{ + bidder: 'windtalker', + params: { + pubId: '584971', + siteId: '584971', + placementId: '123', + video: { + skipppable: true, + } + } + }] + } + ]; +``` diff --git a/modules/wipesBidAdapter.js b/modules/wipesBidAdapter.js new file mode 100644 index 00000000000..f381bcb68a0 --- /dev/null +++ b/modules/wipesBidAdapter.js @@ -0,0 +1,71 @@ +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'wipes'; +const ALIAS_BIDDER_CODE = ['wi']; +const SUPPORTED_MEDIA_TYPES = [BANNER] +const ENDPOINT_URL = 'https://adn-srv.reckoner-api.com/v1/prebid'; + +function isBidRequestValid(bid) { + switch (true) { + case !!(bid.params.asid): + break; + default: + utils.logWarn(`isBidRequestValid Error. ${bid.params}, please check your implementation.`); + return false; + } + return true; +} + +function buildRequests(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const bidId = bidRequest.bidId + const params = bidRequest.params; + const asid = params.asid; + return { + method: 'GET', + url: ENDPOINT_URL, + data: { + asid: asid, + bid_id: bidId, + } + } + }); +} + +function interpretResponse(serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const cpm = response.cpm || 0; + if (cpm !== 0) { + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const bidResponse = { + requestId: response.bid_id, + cpm: cpm, + width: response.width, + height: response.height, + creativeId: response.video_creative_id || 0, + dealId: response.deal_id, + currency: 'JPY', + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout'), + referrer: bidRequest.data.r || '', + mediaType: BANNER, + ad: response.ad_tag, + }; + bidResponses.push(bidResponse); + } + return bidResponses; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ALIAS_BIDDER_CODE, + isBidRequestValid, + buildRequests, + interpretResponse, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES +} +registerBidder(spec); diff --git a/modules/wipesBidAdapter.md b/modules/wipesBidAdapter.md new file mode 100644 index 00000000000..712bf0b15d8 --- /dev/null +++ b/modules/wipesBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: WIPES Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@3-shake.com +``` + +# Description + +Connect to WIPES for bids. +Publishers needs to be set up and approved by WIPES team to enable this adapter. +Please contact support@wipestream.com for further information. + +# Test Parameters +```javascript +var adUnits = [ + // adUnit + { + code: 'video-div', + mediaTypes: { + banner: { + sizes: [[160, 300]], + } + }, + bids: [{ + bidder: 'wipes', + params: { + asid: 'dWyPondh2EGB_bNlrVjzIXRZO9F0k1dpo0I8ZvQ' + } + }] + }] +``` diff --git a/modules/xhbBidAdapter.js b/modules/xhbBidAdapter.js index 8660889786e..9363eb97ddc 100644 --- a/modules/xhbBidAdapter.js +++ b/modules/xhbBidAdapter.js @@ -1,9 +1,9 @@ -import { Renderer } from '../src/Renderer'; -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; -import find from 'core-js/library/fn/array/find'; -import includes from 'core-js/library/fn/array/includes'; +import { Renderer } from '../src/Renderer.js'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; const BIDDER_CODE = 'xhb'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -126,7 +126,7 @@ export const spec = { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: 'https://acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html' + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' }]; } } diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 9af3de24cb1..b2a9176e342 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,8 +1,8 @@ -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' -import { Renderer } from '../src/Renderer' +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import find from 'core-js-pure/features/array/find.js' +import { VIDEO, BANNER } from '../src/mediaTypes.js' +import { Renderer } from '../src/Renderer.js' const ENDPOINT = 'https://ad.yieldlab.net' const BIDDER_CODE = 'yieldlab' @@ -37,14 +37,28 @@ export const spec = { utils._each(validBidRequests, function (bid) { adslotIds.push(bid.params.adslotId) if (bid.params.targeting) { - query.t = createQueryString(bid.params.targeting) + query.t = createTargetingString(bid.params.targeting) + } + if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { + query.ids = createUserIdString(bid.userIdAsEids) + } + if (bid.params.customParams && utils.isPlainObject(bid.params.customParams)) { + for (let prop in bid.params.customParams) { + query[prop] = bid.params.customParams[prop] + } } }) - if (bidderRequest && bidderRequest.gdprConsent) { - query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - if (query.gdpr) { - query.consent = bidderRequest.gdprConsent.consentString + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + query.pubref = bidderRequest.refererInfo.referer + } + + if (bidderRequest.gdprConsent) { + query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + if (query.gdpr) { + query.consent = bidderRequest.gdprConsent.consentString + } } } @@ -80,6 +94,8 @@ export const spec = { const primarysize = bidRequest.sizes.length === 2 && !utils.isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] const customsize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : primarysize const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : '' + const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : '' + const bidResponse = { requestId: bidRequest.bidId, cpm: matchedBid.price / 100, @@ -94,7 +110,7 @@ export const spec = { ad: `` } - if (isVideo(bidRequest)) { + if (isVideo(bidRequest, adType)) { const playersize = getPlayerSize(bidRequest) if (playersize) { bidResponse.width = playersize[0] @@ -124,10 +140,11 @@ export const spec = { /** * Is this a video format? * @param {Object} format + * @param {String} adtype * @returns {Boolean} */ -function isVideo (format) { - return utils.deepAccess(format, 'mediaTypes.video') +function isVideo (format, adtype) { + return utils.deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video' } /** @@ -159,6 +176,19 @@ function parseSize (size) { return size.split('x').map(Number) } +/** + * Creates a string out of an array of eids with source and uid + * @param {Array} eids + * @returns {String} + */ +function createUserIdString (eids) { + let str = [] + for (let i = 0; i < eids.length; i++) { + str.push(eids[i].source + ':' + eids[i].uids[0].id) + } + return str.join(',') +} + /** * Creates a querystring out of an object with key-values * @param {Object} obj @@ -174,6 +204,23 @@ function createQueryString (obj) { return str.join('&') } +/** + * Creates an unencoded targeting string out of an object with key-values + * @param {Object} obj + * @returns {String} + */ +function createTargetingString (obj) { + let str = [] + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + let key = p + let val = obj[p] + str.push(key + '=' + val) + } + } + return str.join('&') +} + /** * Handles an outstream response after the library is loaded * @param {Object} bid diff --git a/modules/yieldliftBidAdapter.js b/modules/yieldliftBidAdapter.js new file mode 100644 index 00000000000..87a598bd335 --- /dev/null +++ b/modules/yieldliftBidAdapter.js @@ -0,0 +1,144 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const ENDPOINT_URL = 'https://x.yieldlift.com/auction'; + +const DEFAULT_BID_TTL = 30; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; + +export const spec = { + code: 'yieldlift', + aliases: ['yl'], + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return (!!bid.params.unitId && typeof bid.params.unitId === 'string') || + (!!bid.params.networkId && typeof bid.params.networkId === 'string') || + (!!bid.params.publisherId && typeof bid.params.publisherId === 'string'); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + const publisherId = validBidRequests[0].params.publisherId; + const networkId = validBidRequests[0].params.networkId; + const impressions = validBidRequests.map(bidRequest => ({ + id: bidRequest.bidId, + banner: { + format: bidRequest.mediaTypes.banner.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1] + })) + }, + ext: { + exchange: { + unitId: bidRequest.params.unitId + } + } + })); + + const openrtbRequest = { + id: bidderRequest.auctionId, + imp: impressions, + site: { + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null + }, + ext: { + exchange: { + publisherId: publisherId, + networkId: networkId, + } + } + }; + + // adding schain object + if (validBidRequests[0].schain) { + utils.deepSetValue(openrtbRequest, 'source.ext.schain', validBidRequests[0].schain); + } + + // Attaching GDPR Consent Params + if (bidderRequest.gdprConsent) { + utils.deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + utils.deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest.uspConsent) { + utils.deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + const payloadString = JSON.stringify(openrtbRequest); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString, + }; + }, + + interpretResponse: function (serverResponse) { + const bidResponses = []; + const response = (serverResponse || {}).body; + // response is always one seat (exchange) with (optional) bids for each impression + if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid[0].bid.forEach(bid => { + bidResponses.push({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: DEFAULT_BID_TTL, + creativeId: bid.crid, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY, + }) + }) + } else { + utils.logInfo('yieldlift.interpretResponse :: no valid responses to interpret'); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + utils.logInfo('yieldlift.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + let syncs = []; + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncs; + } + + serverResponses.forEach(resp => { + const userSync = utils.deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + let syncDetails = []; + Object.keys(userSync).forEach(key => { + const value = userSync[key]; + if (value.syncs && value.syncs.length) { + syncDetails = syncDetails.concat(value.syncs); + } + }); + syncDetails.forEach(syncDetails => { + syncs.push({ + type: syncDetails.type === 'iframe' ? 'iframe' : 'image', + url: syncDetails.url + }); + }); + + if (!syncOptions.iframeEnabled) { + syncs = syncs.filter(s => s.type !== 'iframe') + } + if (!syncOptions.pixelEnabled) { + syncs = syncs.filter(s => s.type !== 'image') + } + } + }); + utils.logInfo('yieldlift.getUserSyncs result=%o', syncs); + return syncs; + }, + +}; +registerBidder(spec); diff --git a/modules/yieldliftBidAdapter.md b/modules/yieldliftBidAdapter.md new file mode 100644 index 00000000000..96b5de1b618 --- /dev/null +++ b/modules/yieldliftBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: YieldLift Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@yieldlift.com +``` + +# Description + +Module that connects to YieldLift's demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'yieldlift', + params: { + unitId: 'test' + } + }] + } +]; +``` diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index ccf565df5da..829b573ffd9 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,11 +1,10 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; 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 = utils.getWindowTop(); @@ -13,11 +12,11 @@ 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) { + * 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); }, /** @@ -26,7 +25,7 @@ export const spec = { * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(bidRequests, bidderRequest) { + buildRequests: function (bidRequests, bidderRequest) { let serverRequest = { p: [], page_url: bidderRequest.refererInfo.referer, @@ -34,20 +33,24 @@ export const spec = { pr: bidderRequest.refererInfo.referer, scrd: localWindow.devicePixelRatio || 0, dnt: getDNT(), - e: getEnvironment(), description: getPageDescription(), title: localWindow.document.title || '', w: localWindow.innerWidth, h: localWindow.innerHeight, userConsent: JSON.stringify({ // case of undefined, stringify will remove param - gdprApplies: bidderRequest && bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : undefined, - cmp: bidderRequest && bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : undefined + gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', + cmp: utils.deepAccess(bidderRequest, 'gdprConsent.consentString') || '' }), - us_privacy: bidderRequest && bidderRequest.us_privacy, + us_privacy: utils.deepAccess(bidderRequest, 'uspConsent') || '' }; - bidRequests.forEach((request) => { + const mtp = window.navigator.maxTouchPoints; + if (mtp) { + serverRequest.mtp = mtp; + } + + bidRequests.forEach(request => { serverRequest.p.push(addPlacement(request)); const pubcid = getId(request, 'pubcid'); if (pubcid) { @@ -61,8 +64,13 @@ export const spec = { if (tdid) { serverRequest.tdid = tdid; } + const criteoId = getId(request, 'criteoId'); + if (criteoId) { + serverRequest.cri_prebid = criteoId; + } if (request.schain) { - serverRequest.schain = JSON.stringify(request.schain); + serverRequest.schain = + JSON.stringify(request.schain); } }); serverRequest.p = '[' + serverRequest.p.toString() + ']'; @@ -70,18 +78,18 @@ export const spec = { method: 'GET', url: SERVER_ENDPOINT, data: serverRequest - } + }; }, /** * Makes Yieldmo Ad Server response compatible to Prebid specs * @param serverResponse successful response from Ad Server * @return {Bid[]} an array of bids */ - interpretResponse: function(serverResponse) { + interpretResponse: function (serverResponse) { let bids = []; let data = serverResponse.body; if (data.length > 0) { - data.forEach((response) => { + data.forEach(response => { if (response.cpm && response.cpm > 0) { bids.push(createNewBid(response)); } @@ -89,17 +97,10 @@ export const spec = { } return bids; }, - getUserSync: function(syncOptions) { - if (trackingEnabled(syncOptions)) { - return [{ - type: 'iframe', - url: SYNC_ENDPOINT + utils.getOrigin() - }]; - } else { - return []; - } + getUserSyncs: function () { + return []; } -} +}; registerBidder(spec); /*************************************** @@ -115,7 +116,7 @@ function addPlacement(request) { placement_id: request.adUnitCode, callback_id: request.bidId, sizes: request.mediaTypes.banner.sizes - } + }; if (request.params) { if (request.params.placementId) { placementInfo.ym_placement_id = request.params.placementId; @@ -128,9 +129,9 @@ function addPlacement(request) { } /** - * creates a new bid with response information - * @param response server response - */ + * creates a new bid with response information + * @param response server response + */ function createNewBid(response) { return { requestId: response['callback_id'], @@ -141,32 +142,18 @@ function createNewBid(response) { currency: CURRENCY, netRevenue: NET_REVENUE, ttl: TIME_TO_LIVE, - ad: response.ad + ad: response.ad, }; } /** - * Detects if tracking is allowed - * @returns false if dnt or if not iframe/pixel enabled + * Detects whether dnt is true + * @returns true if user enabled dnt */ -function trackingEnabled(options) { - return (isIOS() && !getDNT() && options.iframeEnabled); -} - -/** - * Detects whether we're in iOS - * @returns true if in iOS - */ -function isIOS() { - return /iPhone|iPad|iPod/i.test(window.navigator.userAgent); -} - -/** - * Detects whether dnt is true - * @returns true if user enabled dnt - */ function getDNT() { - return window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false; + return ( + window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false + ); } /** @@ -174,153 +161,14 @@ function getDNT() { */ 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. + 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; - } -} - -/** - * @returns true if we are running on the top window at dispatch time - */ -function isCodeOnPage() { - return window === window.parent; -} - -/** - * @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; - } -} - -/** -* @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; - } -} - -/** - * @returns true if the environment is a SafeFrame. - */ -function isSafeFrame() { - return window.$sf && window.$sf.ext; -} - -/** - * @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 true if we are in an iframe and can't access the top window. - */ -function isSandboxedIframe() { - return window.top !== window && !window.frameElement; -} - -/** - * 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; - } -} - -/** - * @returns true if the window has the attribute identifying MRAID - */ -function isMraid() { - return !!(window.mraid); -} - /** * Gets an id from the userId object if it exists * @param {*} request @@ -328,9 +176,5 @@ function isMraid() { * @returns an id if there is one, or undefined */ function getId(request, idType) { - let id; - if (request && request.userId && request.userId[idType] && typeof request.userId === 'object') { - id = request.userId[idType]; - } - return id; + return (typeof utils.deepAccess(request, 'userId') === 'object') ? request.userId[idType] : undefined; } diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md index ef5f07a4eb9..0f86d2507d1 100644 --- a/modules/yieldmoBidAdapter.md +++ b/modules/yieldmoBidAdapter.md @@ -4,6 +4,7 @@ Module Name: Yieldmo Bid Adapter Module Type: Bidder Adapter Maintainer: opensource@yieldmo.com +Note: Our ads will only render in mobile ``` # Description diff --git a/modules/yieldoneAnalyticsAdapter.js b/modules/yieldoneAnalyticsAdapter.js index dec14ff5d0f..542c0917708 100644 --- a/modules/yieldoneAnalyticsAdapter.js +++ b/modules/yieldoneAnalyticsAdapter.js @@ -1,18 +1,22 @@ -import {ajax} from '../src/ajax'; -import adapter from '../src/AnalyticsAdapter'; +import {ajax} from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; -import { targeting } from '../src/targeting'; -import { auctionManager } from '../src/auctionManager'; -import * as utils from '../src/utils'; +import adapterManager from '../src/adapterManager.js'; +import { targeting } from '../src/targeting.js'; +import { auctionManager } from '../src/auctionManager.js'; +import * as utils from '../src/utils.js'; const ANALYTICS_CODE = 'yieldone'; const analyticsType = 'endpoint'; // const VERSION = '1.0.0'; -const defaultUrl = '//pool.tsukiji.iponweb.net/hba'; +const defaultUrl = 'https://pool.tsukiji.iponweb.net/hba'; const requestedBidders = {}; const requestedBids = {}; const referrers = {}; +const ignoredEvents = {}; +ignoredEvents[CONSTANTS.EVENTS.BID_ADJUSTMENT] = true; +ignoredEvents[CONSTANTS.EVENTS.BIDDER_DONE] = true; +ignoredEvents[CONSTANTS.EVENTS.AUCTION_END] = true; let currentAuctionId = ''; let url = defaultUrl; @@ -20,6 +24,7 @@ let pubId = ''; function makeAdUnitNameMap() { if (window.googletag && window.googletag.pubads) { + // eslint-disable-next-line no-undef const p = googletag.pubads(); if (p && p.getSlots) { const slots = p.getSlots(); @@ -78,12 +83,11 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { args.forEach((bid) => { const reqBidId = `${bid.bidId}_${bid.auctionId}`; const reqBidderId = `${bid.bidder}_${bid.auctionId}`; - if (!eventsStorage[bid.auctionId]) eventsStorage[bid.auctionId] = []; + if (!eventsStorage[bid.auctionId]) eventsStorage[bid.auctionId] = {events: []}; if ((requestedBidders[reqBidderId] || reqBidders[bid.bidder]) && requestedBids[reqBidId]) { if (!reqBidders[bid.bidder]) { reqBidders[bid.bidder] = requestedBidders[reqBidderId]; - reqBidders[bid.bidder].pubId = pubId; - eventsStorage[bid.auctionId].push({eventType, params: reqBidders[bid.bidder]}); + eventsStorage[bid.auctionId].events.push({eventType, params: reqBidders[bid.bidder]}); delete requestedBidders[reqBidderId]; } reqBidders[bid.bidder].bids.push(requestedBids[reqBidId]); @@ -91,42 +95,52 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { } }); } else { - args.pubId = pubId; currentAuctionId = args.auctionId || currentAuctionId; if (currentAuctionId) { const eventsStorage = yieldoneAnalytics.eventsStorage; - if (!eventsStorage[currentAuctionId]) eventsStorage[currentAuctionId] = []; + if (!eventsStorage[currentAuctionId]) eventsStorage[currentAuctionId] = {events: []}; const referrer = args.refererInfo && args.refererInfo.referer; if (referrer && referrers[currentAuctionId] !== referrer) { referrers[currentAuctionId] = referrer; } - eventsStorage[currentAuctionId].push({ - eventType, - params: args - }); - } - } - if ( - eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON - ) { - args.adServerTargeting = targeting.getAllTargeting( - auctionManager.getAdUnitCodes(), - auctionManager.getBidsReceived() - ); - if (yieldoneAnalytics.eventsStorage[args.auctionId]) { - yieldoneAnalytics.eventsStorage[args.auctionId].forEach((it) => { - it.page = {url: referrers[currentAuctionId]}; - const adUnitNameMap = makeAdUnitNameMap(); - if (adUnitNameMap) { - addAdUnitName(it.params, adUnitNameMap); + const params = Object.assign({}, args); + delete params.ad; + if (params.bidsReceived) { + params.bidsReceived = params.bidsReceived.map((bid) => { + const res = Object.assign({}, bid); + delete res.ad; + return res; + }); + } + if (!ignoredEvents[eventType]) { + eventsStorage[currentAuctionId].events.push({eventType, params}); + } + + if ( + eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON + ) { + params.adServerTargeting = targeting.getAllTargeting( + auctionManager.getAdUnitCodes(), + auctionManager.getBidsReceived() + ); + if (yieldoneAnalytics.eventsStorage[currentAuctionId] && yieldoneAnalytics.eventsStorage[currentAuctionId].events.length) { + yieldoneAnalytics.eventsStorage[currentAuctionId].page = {url: referrers[currentAuctionId]}; + yieldoneAnalytics.eventsStorage[currentAuctionId].pubId = pubId; + yieldoneAnalytics.eventsStorage[currentAuctionId].wrapper_version = '$prebid.version$'; + const adUnitNameMap = makeAdUnitNameMap(); + if (adUnitNameMap) { + yieldoneAnalytics.eventsStorage[currentAuctionId].events.forEach((it) => { + addAdUnitName(it.params, adUnitNameMap); + }); + } } - }); + yieldoneAnalytics.sendStat(yieldoneAnalytics.eventsStorage[currentAuctionId], currentAuctionId); + } } - yieldoneAnalytics.sendStat(yieldoneAnalytics.eventsStorage[args.auctionId], args.auctionId); } }, - sendStat(events, auctionId) { - if (!events) return; + sendStat(data, auctionId) { + if (!data || !data.events || !data.events.length) return; delete yieldoneAnalytics.eventsStorage[auctionId]; ajax( url, @@ -134,7 +148,7 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { success: function() {}, error: function() {} }, - JSON.stringify(events), + JSON.stringify(data), { method: 'POST' } diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js new file mode 100644 index 00000000000..574967db291 --- /dev/null +++ b/modules/yieldoneBidAdapter.js @@ -0,0 +1,205 @@ +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'yieldone'; +const ENDPOINT_URL = 'https://y.one.impact-ad.jp/h_bid'; +const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; +const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/dac-video-prebid.min.js'; +const CMER_PLAYER_URL = 'https://an.cmertv.com/hb/renderer/cmertv-video-yone-prebid.min.js'; +const VIEWABLE_PERCENTAGE_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/prebid-adformat-config.js'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['y1'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function(bid) { + return !!(bid.params.placementId); + }, + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const params = bidRequest.params; + const placementId = params.placementId; + const cb = Math.floor(Math.random() * 99999999999); + const referrer = bidderRequest.refererInfo.referer; + const bidId = bidRequest.bidId; + const transactionId = bidRequest.transactionId; + const unitCode = bidRequest.adUnitCode; + const timeout = config.getConfig('bidderTimeout'); + const payload = { + v: 'hb1', + p: placementId, + cb: cb, + r: referrer, + uid: bidId, + tid: transactionId, + uc: unitCode, + tmax: timeout, + t: 'i' + }; + + const videoMediaType = utils.deepAccess(bidRequest, 'mediaTypes.video'); + if ((utils.isEmpty(bidRequest.mediaType) && utils.isEmpty(bidRequest.mediaTypes)) || + (bidRequest.mediaType === BANNER || (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER]))) { + const sizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; + payload.sz = utils.parseSizesInput(sizes).join(','); + } else if (bidRequest.mediaType === VIDEO || videoMediaType) { + const sizes = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; + const size = utils.parseSizesInput(sizes)[0]; + payload.w = size.split('x')[0]; + payload.h = size.split('x')[1]; + } + + 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 renderId = response.renderid || ''; + const currency = response.currency || 'JPY'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const referrer = bidRequest.data.r || ''; + const bidResponse = { + requestId: response.uid, + cpm: cpm, + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout'), + referrer: referrer + }; + + if (response.adTag && renderId === 'ViewableRendering') { + bidResponse.mediaType = BANNER; + let viewableScript = ` + + + `; + bidResponse.ad = viewableScript; + } else if (response.adTag) { + bidResponse.mediaType = BANNER; + bidResponse.ad = response.adTag; + } else if (response.adm) { + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = response.adm; + if (renderId === 'cmer') { + bidResponse.renderer = newCmerRenderer(response); + } else { + bidResponse.renderer = newRenderer(response); + } + } + + bidResponses.push(bidResponse); + } + return bidResponses; + }, + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_URL + }]; + } + }, +} + +function newRenderer(response) { + const renderer = Renderer.install({ + id: response.uid, + url: VIDEO_PLAYER_URL, + loaded: false, + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on newRenderer', err); + } + + return renderer; +} + +function outstreamRender(bid) { + bid.renderer.push(() => { + window.DACIVTPREBID.renderPrebid(bid); + }); +} + +function newCmerRenderer(response) { + const renderer = Renderer.install({ + id: response.uid, + url: CMER_PLAYER_URL, + loaded: false, + }); + + try { + renderer.setRender(cmerRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on newRenderer', err); + } + + return renderer; +} + +function cmerRender(bid) { + bid.renderer.push(() => { + window.CMERYONEPREBID.renderPrebid(bid); + }); +} + +registerBidder(spec); diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js new file mode 100644 index 00000000000..3c27ca9754a --- /dev/null +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -0,0 +1,255 @@ +import { ajax } from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import * as utils from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const storage = getStorageManager(); +const yuktamediaAnalyticsVersion = 'v3.0.0'; + +let initOptions; +let auctionTimestamp; + +const events = { + auctions: {} +}; +const localStoragePrefix = 'yuktamediaAnalytics_'; +const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; + +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 isNavigatorSendBeaconSupported() { + return ('navigator' in window) && ('sendBeacon' in window.navigator); +} + +function updateSessionId() { + if (isSessionIdTimeoutExpired()) { + let newSessionId = utils.generateUUID(); + storage.setDataInLocalStorage(localStoragePrefix.concat('session_id'), newSessionId); + } + initOptions.sessionId = getSessionId(); + updateSessionIdTimeout(); +} + +function updateSessionIdTimeout() { + storage.setDataInLocalStorage(localStoragePrefix.concat('session_timeout'), Date.now()); +} + +function isSessionIdTimeoutExpired() { + let cpmSessionTimestamp = storage.getDataFromLocalStorage(localStoragePrefix.concat('session_timeout')); + return Date.now() - cpmSessionTimestamp > 3600000; +} + +function getSessionId() { + return storage.getDataFromLocalStorage(localStoragePrefix.concat('session_id')) ? storage.getDataFromLocalStorage(localStoragePrefix.concat('session_id')) : ''; +} + +function isUtmTimeoutExpired() { + let utmTimestamp = storage.getDataFromLocalStorage(localStoragePrefix.concat('utm_timeout')); + return (Date.now() - utmTimestamp) > 3600000; +} + +function send(data, status) { + const location = utils.getWindowLocation(); + data.initOptions = Object.assign({ host: location.host, path: location.pathname, search: location.search }, initOptions); + + const yuktamediaAnalyticsRequestUrl = utils.buildUrl({ + protocol: 'https', + hostname: 'analytics-prebid.yuktamedia.com', + pathname: '/api/bids', + search: { + auctionTimestamp: auctionTimestamp, + yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, + prebidVersion: $$PREBID_GLOBAL$$.version + } + }); + + if (isNavigatorSendBeaconSupported()) { + window.navigator.sendBeacon(yuktamediaAnalyticsRequestUrl, JSON.stringify(data)); + } else { + ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); + } +} + +var yuktamediaAnalyticsAdapter = Object.assign(adapter({analyticsType: 'endpoint'}), { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + utils.logInfo(localStoragePrefix + 'AUCTION_INIT:', JSON.stringify(args)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + events.auctions[args.auctionId] = { bids: {} }; + auctionTimestamp = args.timestamp; + } + break; + case CONSTANTS.EVENTS.BID_REQUESTED: + utils.logInfo(localStoragePrefix + 'BID_REQUESTED:', JSON.stringify(args)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } + events.auctions[args.auctionId]['timeStamp'] = args.start; + args.bids.forEach(function (bidRequest) { + events.auctions[args.auctionId]['bids'][bidRequest.bidId] = { + bidder: bidRequest.bidder, + adUnit: bidRequest.adUnitCode, + sizes: utils.parseSizesInput(bidRequest.sizes).toString(), + isBid: false, + won: false, + timeout: false, + renderStatus: 'bid-requested', + bidId: bidRequest.bidId, + auctionId: args.auctionId + } + if (typeof initOptions.enableUserIdCollection !== 'undefined' && initOptions.enableUserIdCollection && typeof bidRequest['userId'] !== 'undefined') { + for (let [userIdProvider, userId] in Object.entries(bidRequest['userId'])) { + userIdProvider = typeof userIdProvider !== 'string' ? JSON.stringify(userIdProvider) : userIdProvider; + userId = typeof userId !== 'string' ? JSON.stringify(userId) : userId; + events.auctions[args.auctionId]['bids'][bidRequest.bidId]['userID-'.concat(userIdProvider)] = userId; + } + } + }); + } + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + utils.logInfo(localStoragePrefix + 'BID_RESPONSE:', JSON.stringify(args)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + let bidResponse = events.auctions[args.auctionId]['bids'][args.requestId]; + bidResponse.isBid = args.getStatusCode() === CONSTANTS.STATUS.GOOD; + bidResponse.cpm = args.cpm; + bidResponse.currency = args.currency; + bidResponse.netRevenue = args.netRevenue; + bidResponse.dealId = typeof args.dealId !== 'undefined' ? args.dealId : ''; + bidResponse.mediaType = args.mediaType; + if (bidResponse.mediaType === 'native') { + bidResponse.nativeTitle = typeof args['native']['title'] !== 'undefined' ? args['native']['title'] : ''; + bidResponse.nativeSponsoredBy = typeof args['native']['sponsoredBy'] !== 'undefined' ? args['native']['sponsoredBy'] : ''; + } + bidResponse.timeToRespond = args.timeToRespond; + bidResponse.requestTimestamp = args.requestTimestamp; + bidResponse.responseTimestamp = args.responseTimestamp; + bidResponse.bidForSize = args.size; + for (const [adserverTargetingKey, adserverTargetingValue] of Object.entries(args.adserverTargeting)) { + if (['body', 'icon', 'image', 'linkurl', 'host', 'path'].every((ele) => !adserverTargetingKey.includes(ele))) { + bidResponse['adserverTargeting-' + adserverTargetingKey] = adserverTargetingValue; + } + } + bidResponse.renderStatus = 'bid-response-received'; + } + } + break; + case CONSTANTS.EVENTS.NO_BID: + utils.logInfo(localStoragePrefix + 'NO_BID:', JSON.stringify(args)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const noBid = events.auctions[args.auctionId]['bids'][args.bidId]; + noBid.renderStatus = 'no-bid'; + } + } + break; + case CONSTANTS.EVENTS.BID_WON: + utils.logInfo(localStoragePrefix + 'BID_WON:', JSON.stringify(args)); + if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { + updateSessionId(); + } + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const wonBid = events.auctions[args.auctionId]['bids'][args.requestId]; + wonBid.won = true; + wonBid.renderStatus = 'bid-won'; + send({ 'bids': [wonBid] }, 'won'); + } + } + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + utils.logInfo(localStoragePrefix + 'BID_TIMEOUT:', JSON.stringify(args)); + if (args.length) { + args.forEach(timeout => { + if (typeof timeout !== 'undefined' && typeof timeout.auctionId !== 'undefined' && timeout.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const timeoutBid = events.auctions[timeout.auctionId].bids[timeout.bidId]; + timeoutBid.timeout = true; + timeoutBid.renderStatus = 'bid-timedout'; + } + } + }); + } + break; + case CONSTANTS.EVENTS.AUCTION_END: + utils.logInfo(localStoragePrefix + 'AUCTION_END:', JSON.stringify(args)); + if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { + updateSessionId(); + } + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + const bids = Object.values(events.auctions[args.auctionId]['bids']); + send({ 'bids': bids }, 'auctionEnd'); + } + break; + } + } + } +}); + +yuktamediaAnalyticsAdapter.buildUtmTagData = function (options) { + let utmTagData = {}; + let utmTagsDetected = false; + if (typeof options.enableUTMCollection !== 'undefined' && options.enableUTMCollection) { + utmTags.forEach(function (utmTagKey) { + let utmTagValue = getParameterByName(utmTagKey); + if (utmTagValue !== '') { + utmTagsDetected = true; + } + utmTagData[utmTagKey] = utmTagValue; + }); + utmTags.forEach(function (utmTagKey) { + if (utmTagsDetected) { + storage.setDataInLocalStorage(localStoragePrefix.concat(utmTagKey), utmTagData[utmTagKey]); + storage.setDataInLocalStorage(localStoragePrefix.concat('utm_timeout'), Date.now()); + } else { + if (!isUtmTimeoutExpired()) { + utmTagData[utmTagKey] = storage.getDataFromLocalStorage(localStoragePrefix.concat(utmTagKey)) ? storage.getDataFromLocalStorage(localStoragePrefix.concat(utmTagKey)) : ''; + storage.setDataInLocalStorage(localStoragePrefix.concat('utm_timeout'), Date.now()); + } + } + }); + } + return utmTagData; +}; + +yuktamediaAnalyticsAdapter.originEnableAnalytics = yuktamediaAnalyticsAdapter.enableAnalytics; +yuktamediaAnalyticsAdapter.enableAnalytics = function (config) { + if (config && config.options) { + if (typeof config.options.pubId === 'undefined' || typeof config.options.pubKey === 'undefined') { + utils.logError('Need pubId and pubKey to log auction results. Please contact a YuktaMedia representative if you do not know your pubId and pubKey.'); + return; + } + } + initOptions = Object.assign({}, config.options, this.buildUtmTagData(config.options)); + yuktamediaAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: yuktamediaAnalyticsAdapter, + code: 'yuktamedia' +}); + +export default yuktamediaAnalyticsAdapter; diff --git a/modules/yuktamediaAnalyticsAdapter.md b/modules/yuktamediaAnalyticsAdapter.md index a21675b6b1d..af47985c834 100644 --- a/modules/yuktamediaAnalyticsAdapter.md +++ b/modules/yuktamediaAnalyticsAdapter.md @@ -1,5 +1,5 @@ # Overview -Module Name: YuktaMedia Analytics Adapter +Module Name: YuktaOne Analytics by YuktaMedia Module Type: Analytics Adapter @@ -15,8 +15,11 @@ Analytics adapter for prebid provided by YuktaMedia. Contact info@yuktamedia.com { provider: 'yuktamedia', options : { - pubId : 50357 //id provided by YuktaMedia LLP - pubKey: 'xxx' //key provided by YuktaMedia LLP + pubId : 50357, // id provided by YuktaMedia LLP + pubKey: 'xxx', // key provided by YuktaMedia LLP + enableUTMCollection: true, // set true if want to collect utm info + enableSession: true, // set true if want to collect information by sessions + enableUserIdCollection: true // set true if want to collect user ID module info } } ``` diff --git a/modules/zedoBidAdapter.js b/modules/zedoBidAdapter.js new file mode 100644 index 00000000000..e75b9c82065 --- /dev/null +++ b/modules/zedoBidAdapter.js @@ -0,0 +1,342 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find'; +import { Renderer } from '../src/Renderer.js'; +import { getRefererInfo } from '../src/refererDetection.js'; + +const BIDDER_CODE = 'zedo'; +const SECURE_URL = 'https://saxp.zedo.com/asw/fmh.json'; +const DIM_TYPE = { + '7': 'display', + '9': 'display', + '14': 'display', + '70': 'SBR', + '83': 'CurtainRaiser', + '85': 'Inarticle', + '86': 'pswipeup', + '88': 'Inview', + '100': 'display', + '101': 'display', + '102': 'display', + '103': 'display' + // '85': 'pre-mid-post-roll', +}; +const SECURE_EVENT_PIXEL_URL = 'tt1.zedo.com/log/p.gif'; + +export const spec = { + code: BIDDER_CODE, + aliases: [], + supportedMediaTypes: [BANNER, VIDEO], + + /** + * 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 && bid.params.channelCode && bid.params.dimId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + let data = { + placements: [] + }; + bidRequests.map(bidRequest => { + let channelCode = parseInt(bidRequest.params.channelCode); + let network = parseInt(channelCode / 1000000); + let channel = channelCode % 1000000; + let dim = getSizes(bidRequest.sizes); + let placement = { + id: bidRequest.bidId, + network: network, + channel: channel, + publisher: bidRequest.params.pubId ? bidRequest.params.pubId : 0, + width: dim[0], + height: dim[1], + dimension: bidRequest.params.dimId, + version: '$prebid.version$', + keyword: '', + transactionId: bidRequest.transactionId + } + if (bidderRequest && bidderRequest.gdprConsent) { + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + data.gdpr = Number(bidderRequest.gdprConsent.gdprApplies); + } + data.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + // Add CCPA consent string + if (bidderRequest && bidderRequest.uspConsent) { + data.usp = bidderRequest.uspConsent; + } + + let dimType = DIM_TYPE[String(bidRequest.params.dimId)] + if (dimType) { + placement['renderers'] = [{ + 'name': dimType + }] + } else { // default to display + placement['renderers'] = [{ + 'name': 'display' + }] + } + data['placements'].push(placement); + }); + // adding schain object + if (bidRequests[0].schain) { + data['supplyChain'] = getSupplyChain(bidRequests[0].schain); + } + return { + method: 'GET', + url: SECURE_URL, + data: 'g=' + JSON.stringify(data) + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, request) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${request.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + utils.logError(errorMessage); + return bids; + } + + if (serverResponse.ad) { + serverResponse.ad.forEach(ad => { + const creativeBid = getCreative(ad); + if (creativeBid) { + if (parseInt(creativeBid.cpm) !== 0) { + const bid = newBid(ad, creativeBid, request); + bid.mediaType = parseMediaType(creativeBid); + bids.push(bid); + } + } + }); + } + return bids; + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (syncOptions.iframeEnabled) { + let url = 'https://tt3.zedo.com/rs/us/fcs.html'; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + url += `?gdpr_consent=${gdprConsent.consentString}`; + } + } + return [{ + type: 'iframe', + url: url + }]; + } + }, + + onTimeout: function (timeoutData) { + try { + logEvent('117', timeoutData); + } catch (e) { + utils.logError(e); + } + }, + + onBidWon: function (bid) { + try { + logEvent('116', [bid]); + } catch (e) { + utils.logError(e); + } + } + +}; + +function getSupplyChain (supplyChain) { + return { + complete: supplyChain.complete, + nodes: supplyChain.nodes + } +}; + +function getCreative(ad) { + return ad && ad.creatives && ad.creatives.length && find(ad.creatives, creative => creative.adId); +}; +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, creativeBid, bidderRequest) { + const bid = { + requestId: serverBid.slotId, + creativeId: creativeBid.adId, + network: serverBid.network, + adType: creativeBid.creativeDetails.type, + dealId: 99999999, + currency: 'USD', + netRevenue: true, + ttl: 300 + }; + + if (creativeBid.creativeDetails.type === 'VAST') { + Object.assign(bid, { + width: creativeBid.width, + height: creativeBid.height, + vastXml: creativeBid.creativeDetails.adContent, + cpm: parseInt(creativeBid.bidCpm) / 1000000, + ttl: 3600 + }); + const rendererOptions = utils.deepAccess( + bidderRequest, + 'renderer.options' + ); + let rendererUrl = 'https://ss3.zedo.com/gecko/beta/fmpbgt.min.js'; + Object.assign(bid, { + adResponse: serverBid, + renderer: getRenderer(bid.adUnitCode, serverBid.slotId, rendererUrl, rendererOptions) + }); + } else { + Object.assign(bid, { + width: creativeBid.width, + height: creativeBid.height, + cpm: parseInt(creativeBid.bidCpm) / 1000000, + ad: creativeBid.creativeDetails.adContent, + }); + } + + return bid; +} +/* Turn bid request sizes into compatible format */ +function getSizes(requestSizes) { + let width = 0; + let height = 0; + if (utils.isArray(requestSizes) && requestSizes.length === 2 && + !utils.isArray(requestSizes[0])) { + width = parseInt(requestSizes[0], 10); + height = parseInt(requestSizes[1], 10); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + width = parseInt(size[0], 10); + height = parseInt(size[1], 10); + break; + } + } + return [width, height]; +} + +function getRenderer(adUnitCode, rendererId, rendererUrl, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rendererId, + url: rendererUrl, + config: rendererOptions, + loaded: false, + }); + + try { + renderer.setRender(videoRenderer); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => utils.logMessage('ZEDO video impression'), + loaded: () => utils.logMessage('ZEDO video loaded'), + ended: () => { + utils.logMessage('ZEDO renderer video ended'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + return renderer; +} + +function videoRenderer(bid) { + // push to render queue + const refererInfo = getRefererInfo(); + let referrer = ''; + if (refererInfo) { + referrer = refererInfo.referer; + } + bid.renderer.push(() => { + let channelCode = utils.deepAccess(bid, 'params.0.channelCode') || 0; + let dimId = utils.deepAccess(bid, 'params.0.dimId') || 0; + let publisher = utils.deepAccess(bid, 'params.0.pubId') || 0; + let options = utils.deepAccess(bid, 'params.0.options') || {}; + let channel = (channelCode > 0) ? (channelCode - (bid.network * 1000000)) : 0; + + var rndr = new window.ZdPBTag(bid.adUnitCode, bid.network, bid.width, bid.height, bid.adType, bid.vastXml, channel, dimId, + (encodeURI(referrer) || ''), options); + rndr.renderAd(publisher); + }); +} + +function parseMediaType(creativeBid) { + const adType = creativeBid.creativeDetails.type; + if (adType === 'VAST') { + return VIDEO; + } else { + return BANNER; + } +} + +function logEvent(eid, data) { + let getParams = { + protocol: 'https', + hostname: SECURE_EVENT_PIXEL_URL, + search: getLoggingData(eid, data) + }; + let eventUrl = utils.buildUrl(getParams).replace(/&/g, ';'); + utils.triggerPixel(eventUrl); +} + +function getLoggingData(eid, data) { + data = (utils.isArray(data) && data) || []; + + let params = {}; + let channel, network, dim, publisher, adunitCode, timeToRespond, cpm; + data.map((adunit) => { + adunitCode = adunit.adUnitCode; + channel = utils.deepAccess(adunit, 'params.0.channelCode') || 0; + network = channel > 0 ? parseInt(channel / 1000000) : 0; + dim = utils.deepAccess(adunit, 'params.0.dimId') * 256 || 0; + publisher = utils.deepAccess(adunit, 'params.0.pubId') || 0; + timeToRespond = adunit.timeout ? adunit.timeout : adunit.timeToRespond; + cpm = adunit.cpm; + }); + let referrer = ''; + const refererInfo = getRefererInfo(); + if (refererInfo) { + referrer = refererInfo.referer; + } + params.n = network; + params.c = channel; + params.s = publisher; + params.x = dim; + params.ai = encodeURI('Prebid^zedo^' + adunitCode + '^' + cpm + '^' + timeToRespond); + params.pu = encodeURI(referrer) || ''; + params.eid = eid; + params.e = 'e'; + params.z = Math.random(); + + return params; +} + +registerBidder(spec); diff --git a/modules/zedoBidAdapter.md b/modules/zedoBidAdapter.md index e0f9101deaa..2f31e8aed9b 100644 --- a/modules/zedoBidAdapter.md +++ b/modules/zedoBidAdapter.md @@ -18,22 +18,23 @@ ZEDO has its own renderer and will render the video unit if not defined in the c # display ``` - var adUnits = [ - { - code: 'banner-ad-div', - sizes: [[300, 250], [728, 90]], - bids: [ - { - bidder: 'zedo', - params: { - channelCode: 2264004118, // required - dimId: 9, // required - pubId: 1 // optional - } + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], } - ] - } - ]; + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'zedo', + params: { + channelCode: 2264004735, //REQUIRED + dimId:9 //REQUIRED + } + }] + + }]; ``` # video ``` @@ -54,7 +55,7 @@ ZEDO has its own renderer and will render the video unit if not defined in the c bidder: 'zedo', params: { - channelCode: 2264004593, // required + channelCode: 2264004735, // required dimId: 85, // required pubId: 1 // optional } diff --git a/package-lock.json b/package-lock.json index cf4baaec4c0..4f3d2120d72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,34 +1,48 @@ { "name": "prebid.js", - "version": "3.1.0-pre", + "version": "3.27.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/compat-data": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.5.tgz", + "integrity": "sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw==", + "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "semver": "^5.5.0" } }, "@babel/core": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.5.tgz", - "integrity": "sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helpers": "^7.7.4", - "@babel/parser": "^7.7.5", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz", + "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.5", + "@babel/types": "^7.10.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "json5": "^2.1.0", - "lodash": "^4.17.13", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" @@ -52,751 +66,914 @@ } }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", "dev": true, "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.10.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz", - "integrity": "sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.4.tgz", - "integrity": "sha512-Biq/d/WtvfftWZ9Uf39hbPBYDUo986m5Bb4zhkeYDGUllF43D+nUe5M6Vuo6/8JDK/0YX/uBdeoQpyaNhNugZQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/compat-data": "^7.10.4", + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" } }, - "@babel/helper-call-delegate": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.7.4.tgz", - "integrity": "sha512-8JH9/B7J7tCYJ2PpWVpw9JhPuEVHztagNVuQAFBVFYluRMlpG7F1CgKEgGeL6KFqcsIa92ZYVj6DSc0XwmN1ZA==", + "@babel/helper-create-class-features-plugin": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz", - "integrity": "sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", "dev": true, "requires": { - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" } }, "@babel/helper-define-map": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz", - "integrity": "sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.7.4", - "@babel/types": "^7.7.4", - "lodash": "^4.17.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz", - "integrity": "sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", + "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", "dev": true, "requires": { - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.10.4" } }, "@babel/helper-hoist-variables": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz", - "integrity": "sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz", - "integrity": "sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.10.5" } }, "@babel/helper-module-imports": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz", - "integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.10.4" } }, "@babel/helper-module-transforms": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz", - "integrity": "sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.7.4", - "@babel/helper-simple-access": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4", - "lodash": "^4.17.13" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz", - "integrity": "sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.10.4" } }, "@babel/helper-plugin-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true }, "@babel/helper-regex": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", - "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", "dev": true, "requires": { - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz", - "integrity": "sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", + "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.7.4", - "@babel/helper-wrap-function": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-replace-supers": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz", - "integrity": "sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.7.4", - "@babel/helper-optimise-call-expression": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-simple-access": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz", - "integrity": "sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", "dev": true, "requires": { - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.10.4" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/helper-wrap-function": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz", - "integrity": "sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helpers": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz", - "integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", "dev": true, "requires": { - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, "requires": { + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", - "esutils": "^2.0.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz", - "integrity": "sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.7.4", - "@babel/plugin-syntax-async-generators": "^7.7.4" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz", - "integrity": "sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.7.4" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.7.4.tgz", - "integrity": "sha512-wQvt3akcBTfLU/wYoqm/ws7YOAQKu8EVJEvHip/mzkNtjaclQoCCIqKXFP5/eyfnfbQCDV3OLRIK3mIVyXuZlw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-json-strings": "^7.7.4" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz", - "integrity": "sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", + "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.7.4" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.10.4" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz", - "integrity": "sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.7.4" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz", + "integrity": "sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz", - "integrity": "sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz", - "integrity": "sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-dynamic-import": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz", - "integrity": "sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-json-strings": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.7.4.tgz", - "integrity": "sha512-QpGupahTQW1mHRXddMG5srgpHWqRLwJnJZKXTigB9RPFCCGbDGCgBeM/iC82ICXp414WeYx/tD54w7M2qRqTMg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz", - "integrity": "sha512-mObR+r+KZq0XhRVS2BrBKBpr5jqrqzlPvS9C9vuOf5ilSwzloAl7RPWLrgKdWS6IreaVrjHxTjtyqFiOisaCwg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz", - "integrity": "sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.4.tgz", - "integrity": "sha512-wdsOw0MvkL1UIgiQ/IFr3ETcfv1xb8RMM0H9wbiDyLaJFyiDg5oZvDLCXosIXmFeIlweML5iOBXAkqddkYNizg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.7.4.tgz", - "integrity": "sha512-zUXy3e8jBNPiffmqkHRNDdZM2r8DWhCB7HhcoyZjiK1TxYEluLHAvQuYnTT+ARqRpabWqy/NHkO6e3MsYB5YfA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.4.tgz", - "integrity": "sha512-zpUTZphp5nHokuy8yLlyafxCJ0rSlFoSHypTUWgpdwoDXWQcseaect7cJ8Ppk6nunOM6+5rPMkod4OYKPR5MUg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.7.4" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.7.4.tgz", - "integrity": "sha512-kqtQzwtKcpPclHYjLK//3lH8OFsCDuDJBaFhVwf8kqdnF6MN4l618UDlcA7TfRs3FayrHj+svYnSX8MC9zmUyQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.7.4.tgz", - "integrity": "sha512-2VBe9u0G+fDt9B5OV5DQH4KBf5DoiNkwFKOz0TCvBWvdAN2rOykCTkrL+jTLxfCAm76l9Qo5OqL7HBOx2dWggg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz", + "integrity": "sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.4.tgz", - "integrity": "sha512-sK1mjWat7K+buWRuImEzjNf68qrKcrddtpQo3swi9j7dUcG6y6R6+Di039QN2bD1dykeswlagupEmpOatFHHUg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.7.4", - "@babel/helper-define-map": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-optimise-call-expression": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.7.4.tgz", - "integrity": "sha512-bSNsOsZnlpLLyQew35rl4Fma3yKWqK3ImWMSC/Nc+6nGjC9s5NFWAer1YQ899/6s9HxO2zQC1WoFNfkOqRkqRQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.7.4.tgz", - "integrity": "sha512-4jFMXI1Cu2aXbcXXl8Lr6YubCn6Oc7k9lLsu8v61TZh+1jny2BWmdtvY9zSUlLdGUvcy9DMAWyZEOqjsbeg/wA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz", - "integrity": "sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.7.4.tgz", - "integrity": "sha512-g1y4/G6xGWMD85Tlft5XedGaZBCIVN+/P0bs6eabmcPP9egFleMAo65OOjlhcz1njpwagyY3t0nsQC9oTFegJA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.7.4.tgz", - "integrity": "sha512-MCqiLfCKm6KEA1dglf6Uqq1ElDIZwFuzz1WH5mTf8k2uQSxEJMbOIEh7IZv7uichr7PMfi5YVSrr1vz+ipp7AQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-for-of": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.7.4.tgz", - "integrity": "sha512-zZ1fD1B8keYtEcKF+M1TROfeHTKnijcVQm0yO/Yu1f7qoDoxEIc/+GX6Go430Bg84eM/xwPFp0+h4EbZg7epAA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.4.tgz", - "integrity": "sha512-E/x09TvjHNhsULs2IusN+aJNRV5zKwxu1cpirZyRPw+FyyIKEHPXTsadj48bVpc1R5Qq1B5ZkzumuFLytnbT6g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.7.4.tgz", - "integrity": "sha512-X2MSV7LfJFm4aZfxd0yLVFrEXAgPqYoDG53Br/tCKiKYfX0MjVjQeWPIhPHHsCqzwQANq+FLN786fF5rgLS+gw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.7.4.tgz", - "integrity": "sha512-9VMwMO7i69LHTesL0RdGy93JU6a+qOPuvB4F4d0kR0zyVjJRVJRaoaGjhtki6SzQUu8yen/vxPKN6CWnCUw6bA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz", - "integrity": "sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.7.5", - "@babel/helper-plugin-utils": "^7.0.0", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz", - "integrity": "sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.7.5", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.7.4", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.4.tgz", - "integrity": "sha512-y2c96hmcsUi6LrMqvmNDPBBiGCiQu0aYqpHatVVu6kD4mFEXKjyNxd/drc18XXAf9dv7UXjrZwBVmTTGaGP8iw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.4.tgz", - "integrity": "sha512-u2B8TIi0qZI4j8q4C51ktfO7E3cQ0qnaXFI1/OXITordD40tt17g/sXqgNNCcMTcBFKrUPcGDx+TBJuZxLx7tw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.4.tgz", - "integrity": "sha512-jBUkiqLKvUWpv9GLSuHUFYdmHg0ujC1JEYoZUfeOOfNydZXp1sXObgyPatpcwjWgsdBGsagWW0cdJpX/DO2jMw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.7.4" + "@babel/helper-create-regexp-features-plugin": "^7.10.4" } }, "@babel/plugin-transform-new-target": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.7.4.tgz", - "integrity": "sha512-CnPRiNtOG1vRodnsyGX37bHQleHE14B9dnnlgSeEs3ek3fHN1A1SScglTCg1sfbe7sRQ2BUcpgpTpWSfMKz3gg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.7.4.tgz", - "integrity": "sha512-ho+dAEhC2aRnff2JCA0SAK7V2R62zJd/7dmtoe7MHcso4C2mS+vZjn1Pb1pCVZvJs1mgsvv5+7sT+m3Bysb6eg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.7.4" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" } }, "@babel/plugin-transform-parameters": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz", - "integrity": "sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", "dev": true, "requires": { - "@babel/helper-call-delegate": "^7.7.4", - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.7.4.tgz", - "integrity": "sha512-MatJhlC4iHsIskWYyawl53KuHrt+kALSADLQQ/HkhTjX954fkxIEh4q5slL4oRAnsm/eDoZ4q0CIZpcqBuxhJQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz", - "integrity": "sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", "dev": true, "requires": { - "regenerator-transform": "^0.14.0" + "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.7.4.tgz", - "integrity": "sha512-OrPiUB5s5XvkCO1lS7D8ZtHcswIC57j62acAnJZKqGGnHP+TIc/ljQSrgdX/QyOTdEK5COAhuc820Hi1q2UgLQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz", - "integrity": "sha512-q+suddWRfIcnyG5YiDP58sT65AJDZSUhXQDZE3r04AuqD6d/XLaQPPXSBzP2zGerkgBivqtQm9XKGLuHqBID6Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.7.4.tgz", - "integrity": "sha512-8OSs0FLe5/80cndziPlg4R0K6HcWSM0zyNhHhLsmw/Nc5MaA49cAsnoJ/t/YZf8qkG7fD+UjTRaApVDB526d7Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz", + "integrity": "sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.7.4.tgz", - "integrity": "sha512-Ls2NASyL6qtVe1H1hXts9yuEeONV2TJZmplLONkMPUG158CtmnrzW5Q5teibM5UVOFjG0D3IC5mzXR6pPpUY7A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.7.4.tgz", - "integrity": "sha512-sA+KxLwF3QwGj5abMHkHgshp9+rRz+oY9uoRil4CyLtgEuE/88dpkeWgNk5qKVsJE9iSfly3nvHapdRiIS2wnQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.7.4.tgz", - "integrity": "sha512-KQPUQ/7mqe2m0B8VecdyaW5XcQYaePyl9R7IsKd+irzj6jvbhoGnRE+M0aNkyAzI07VfUQ9266L5xMARitV3wg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.4.tgz", - "integrity": "sha512-N77UUIV+WCvE+5yHw+oks3m18/umd7y392Zv7mYTpFqHtkpcc+QUz+gLJNTWVlWROIWeLqY0f3OjZxV5TcXnRw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.7.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.7.6.tgz", - "integrity": "sha512-k5hO17iF/Q7tR9Jv8PdNBZWYW6RofxhnxKjBMc0nG4JTaWvOTiPoO/RLFwAKcA4FpmuBFm6jkoqaRJLGi0zdaQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.4.tgz", + "integrity": "sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.10.4", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.10.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.10.4", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.10.4", + "browserslist": "^4.12.0", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", + "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.7.4", - "@babel/plugin-proposal-dynamic-import": "^7.7.4", - "@babel/plugin-proposal-json-strings": "^7.7.4", - "@babel/plugin-proposal-object-rest-spread": "^7.7.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.7.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.7.4", - "@babel/plugin-syntax-async-generators": "^7.7.4", - "@babel/plugin-syntax-dynamic-import": "^7.7.4", - "@babel/plugin-syntax-json-strings": "^7.7.4", - "@babel/plugin-syntax-object-rest-spread": "^7.7.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.7.4", - "@babel/plugin-syntax-top-level-await": "^7.7.4", - "@babel/plugin-transform-arrow-functions": "^7.7.4", - "@babel/plugin-transform-async-to-generator": "^7.7.4", - "@babel/plugin-transform-block-scoped-functions": "^7.7.4", - "@babel/plugin-transform-block-scoping": "^7.7.4", - "@babel/plugin-transform-classes": "^7.7.4", - "@babel/plugin-transform-computed-properties": "^7.7.4", - "@babel/plugin-transform-destructuring": "^7.7.4", - "@babel/plugin-transform-dotall-regex": "^7.7.4", - "@babel/plugin-transform-duplicate-keys": "^7.7.4", - "@babel/plugin-transform-exponentiation-operator": "^7.7.4", - "@babel/plugin-transform-for-of": "^7.7.4", - "@babel/plugin-transform-function-name": "^7.7.4", - "@babel/plugin-transform-literals": "^7.7.4", - "@babel/plugin-transform-member-expression-literals": "^7.7.4", - "@babel/plugin-transform-modules-amd": "^7.7.5", - "@babel/plugin-transform-modules-commonjs": "^7.7.5", - "@babel/plugin-transform-modules-systemjs": "^7.7.4", - "@babel/plugin-transform-modules-umd": "^7.7.4", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.7.4", - "@babel/plugin-transform-new-target": "^7.7.4", - "@babel/plugin-transform-object-super": "^7.7.4", - "@babel/plugin-transform-parameters": "^7.7.4", - "@babel/plugin-transform-property-literals": "^7.7.4", - "@babel/plugin-transform-regenerator": "^7.7.5", - "@babel/plugin-transform-reserved-words": "^7.7.4", - "@babel/plugin-transform-shorthand-properties": "^7.7.4", - "@babel/plugin-transform-spread": "^7.7.4", - "@babel/plugin-transform-sticky-regex": "^7.7.4", - "@babel/plugin-transform-template-literals": "^7.7.4", - "@babel/plugin-transform-typeof-symbol": "^7.7.4", - "@babel/plugin-transform-unicode-regex": "^7.7.4", - "@babel/types": "^7.7.4", - "browserslist": "^4.6.0", - "core-js-compat": "^3.4.7", - "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", - "semver": "^5.5.0" + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", + "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + } } }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" }, "dependencies": { "debug": { @@ -817,20 +994,21 @@ } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", "dev": true, "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, "@cnakazawa/watch": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", - "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, "requires": { "exec-sh": "^0.3.2", "minimist": "^1.2.0" @@ -887,10 +1065,17 @@ } } }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, "@jest/console": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "dev": true, "requires": { "@jest/source-map": "^24.9.0", "chalk": "^2.0.1", @@ -900,7 +1085,8 @@ "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true } } }, @@ -908,6 +1094,7 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", + "dev": true, "requires": { "@jest/console": "^24.7.1", "@jest/reporters": "^24.9.0", @@ -939,23 +1126,189 @@ "strip-ansi": "^5.0.0" }, "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "requires": { "ansi-regex": "^4.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" + } } } }, @@ -963,94 +1316,353 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "dev": true, "requires": { "@jest/fake-timers": "^24.9.0", "@jest/transform": "^24.9.0", "@jest/types": "^24.9.0", "jest-mock": "^24.9.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + } } }, "@jest/fake-timers": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "dev": true, "requires": { "@jest/types": "^24.9.0", "jest-message-util": "^24.9.0", "jest-mock": "^24.9.0" - } - }, - "@jest/reporters": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", - "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", - "requires": { - "@jest/environment": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "istanbul-lib-coverage": "^2.0.2", - "istanbul-lib-instrument": "^3.0.1", - "istanbul-lib-report": "^2.0.4", - "istanbul-lib-source-maps": "^3.0.1", - "istanbul-reports": "^2.2.6", - "jest-haste-map": "^24.9.0", - "jest-resolve": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-util": "^24.9.0", - "jest-worker": "^24.6.0", - "node-notifier": "^5.4.2", - "slash": "^2.0.0", - "source-map": "^0.6.0", - "string-length": "^2.0.0" }, "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "@jest/source-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", - "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.1.15", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "@jest/test-result": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", - "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", - "requires": { - "@jest/console": "^24.9.0", + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "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" + } + } + } + }, + "@jest/reporters": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", + "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.1", + "istanbul-reports": "^2.2.6", + "jest-haste-map": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.6.0", + "node-notifier": "^5.4.2", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "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==", + "dev": true + } + } + }, + "@jest/source-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", + "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/test-result": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", + "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "dev": true, + "requires": { + "@jest/console": "^24.9.0", "@jest/types": "^24.9.0", "@types/istanbul-lib-coverage": "^2.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + } } }, "@jest/test-sequencer": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz", "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==", + "dev": true, "requires": { "@jest/test-result": "^24.9.0", "jest-haste-map": "^24.9.0", @@ -1062,6 +1674,7 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "dev": true, "requires": { "@babel/core": "^7.1.0", "@jest/types": "^24.9.0", @@ -1081,41 +1694,250 @@ "write-file-atomic": "2.4.1" }, "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "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==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "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" + } } } }, "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.1.0.tgz", + "integrity": "sha512-GXigDDsp6ZlNMhXQDeuy/iYCDsRIHJabWtDzvnn36+aqFfG14JmFV0e/iXxY4SP9vbXSiPNOWdehU5MeqrYHBQ==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", - "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jsdevtools/coverage-istanbul-loader": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", + "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", + "dev": true, + "requires": { + "convert-source-map": "^1.7.0", + "istanbul-lib-instrument": "^4.0.3", + "loader-utils": "^2.0.0", + "merge-source-map": "^1.1.0", + "schema-utils": "^2.7.0" + } + }, + "@kiosked/ulid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@kiosked/ulid/-/ulid-3.0.0.tgz", + "integrity": "sha512-ZKt2KIgGHDaGfKt6FjYvCpDvBXZRRoE8b+wDOlAV76aXKpq6ITiSUnPYevR4y55NKDnwCvwOrjWe+aVOCAK8kQ==" + }, + "@sindresorhus/is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.0.0.tgz", + "integrity": "sha512-kqA5I6Yun7PBHk8WN9BBP1c7FfN2SrD05GuVSEYPqDb4nerv7HqYfgBfMIKmT/EuejURkJKLZuLyGKGs6WEG9w==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" } }, "@sinonjs/formatio": { @@ -1144,10 +1966,20 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, "@types/babel__core": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz", - "integrity": "sha512-8fBo0UR2CcwWxeX7WIIgJ7lXjasFxoYgRnFHUj+hRvKkpiBJbxhdAPTCY6/ZKM0uxANFVzt4yObSLuTiTnazDA==", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", + "dev": true, "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0", @@ -1169,376 +2001,1068 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "@types/babel__traverse": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.8.tgz", - "integrity": "sha512-yGeB2dHEdvxjP0y4UbRtQaSkXJ9649fYCmIdRoul5kfAoGCwxuCbMhag0k3RPfnuh9kPGm8x89btcfDEXdVWGw==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", + "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", "dev": true, "requires": { "@babel/types": "^7.3.0" } }, - "@types/crypto-js": { - "version": "3.1.43", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-3.1.43.tgz", - "integrity": "sha512-EHe/YKctU3IYNBsDmSOPX/7jLHPRlx8WaiDKSY9JCTnJ8XJeM4c0ZJvx+9Gxmr2s2ihI92R+3U/gNL1sq5oRuQ==" + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", + "dev": true }, "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true }, "@types/istanbul-lib-report": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", - "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" } }, "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "*", "@types/istanbul-lib-report": "*" } }, - "@types/jest": { - "version": "24.0.23", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.23.tgz", - "integrity": "sha512-L7MBvwfNpe7yVPTXLn32df/EK+AMBFAFvZrRuArGs7npEWnlziUXK+5GMIUTI4NIuwok3XibsjXCs5HxviYXjg==", + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dev": true, "requires": { - "jest-diff": "^24.3.0" + "@types/node": "*" } }, "@types/node": { - "version": "12.12.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.14.tgz", - "integrity": "sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA==" + "version": "14.0.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.24.tgz", + "integrity": "sha512-btt/oNOiDWcSuI721MdL8VQGnjsKjlTMdrKyTcLCKeQp/n4AAMFJ961wMbp+09y8WuGPClDEv07RIItdXKIXAA==", + "dev": true + }, + "@types/puppeteer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz", + "integrity": "sha512-t03eNKCvWJXhQ8wkc5C6GYuSqMEdKLOX0GLMGtks25YZr38wKZlKTwGM/BoAPVtdysX7Bb9tdwrDS1+NrW3RRA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true }, "@types/yargs": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", - "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "dev": true, "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", - "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==" + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "dev": true }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", "dev": true, + "optional": true, "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "@types/node": "*" } }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", - "dev": true - }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "@wdio/browserstack-service": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-6.1.15.tgz", + "integrity": "sha512-q8qLa44wGSB3tIuZ0yquvAZqr2W7vEwupWiOd1ct0CSYgd4yX/nLd8oypqJCc8jU1ZwNAhu+V3/6hszvwx+HbA==", + "dev": true, "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "@wdio/logger": "6.0.16", + "browserstack-local": "^1.4.5", + "got": "^11.0.2" } }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" - }, - "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=", + "@wdio/cli": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-6.3.4.tgz", + "integrity": "sha512-eXA4rR6DwhNtXx1Hxknwgl7jGt/q4ZErCB8aOX9rowEoPOxwPQStd6yJcqI2QE8+AC1S72PKC4w+0WImL+M6Bw==", "dev": true, "requires": { - "acorn": "^4.0.3" + "@wdio/config": "6.1.14", + "@wdio/logger": "6.0.16", + "@wdio/utils": "6.3.0", + "async-exit-hook": "^2.0.1", + "chalk": "^4.0.0", + "chokidar": "^3.0.0", + "cli-spinners": "^2.1.0", + "ejs": "^3.0.1", + "fs-extra": "^9.0.0", + "inquirer": "^7.0.0", + "lodash.flattendeep": "^4.4.0", + "lodash.pickby": "^4.6.0", + "lodash.union": "^4.6.0", + "mkdirp": "^1.0.4", + "recursive-readdir": "^2.2.2", + "webdriverio": "6.3.4", + "yargs": "^15.0.1", + "yarn-install": "^1.0.0" }, "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true - } - } - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", - "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } } } }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "@wdio/concise-reporter": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-6.3.0.tgz", + "integrity": "sha512-H7yILps+dKK1k4XoVE5HOVMpTHN321SFmjjMgtq1zfiC6Dph7Unl4ODmnyVLD5Kk3ycQ31PfOBr0QPyKnLUFiQ==", "dev": true, "requires": { - "acorn": "^3.0.4" + "@wdio/reporter": "6.3.0", + "chalk": "^4.0.0", + "pretty-ms": "^7.0.0" }, "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" - }, - "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": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "@wdio/config": { + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.1.14.tgz", + "integrity": "sha512-MXHMHwtkAblfnIxONs9aW//T9Fq5XIw3oH+tztcBRvNTTAIXmwHd+4sOjAwjpCdBSGs0C4kM/aTpGfwDZVURvQ==", "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "@wdio/logger": "6.0.16", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" } }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "@wdio/local-runner": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-6.3.4.tgz", + "integrity": "sha512-rKEhFXiNH6H2G86JTgy2cgtEFoNBZ50gRy+P1LEhc7Ko/dAYqYMC+Sy8lnbsDzxz6IZVlbubgs+y7GRREayqoQ==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "@wdio/logger": "6.0.16", + "@wdio/repl": "6.3.0", + "@wdio/runner": "6.3.4", + "async-exit-hook": "^2.0.1", + "stream-buffers": "^3.0.2" } }, - "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=", + "@wdio/logger": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.0.16.tgz", + "integrity": "sha512-VbH5UnQIG/3sSMV+Y38+rOdwyK9mVA9vuL7iOngoTafHwUjL1MObfN/Cex84L4mGxIgfxCu6GV48iUmSuQ7sqA==", "dev": true, "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } - } - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "requires": { - "ansi-wrap": "^0.1.0" + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "@wdio/mocha-framework": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-6.3.0.tgz", + "integrity": "sha512-3lLvzhDYWwOYmiJAjr2fm/nENq6g6uUOtkIeEQFp1kDyBQkDsH1PXGdFklQbRiQT8mAqOPhx1kvXrCA/XpWl7g==", + "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "@wdio/logger": "6.0.16", + "@wdio/utils": "6.3.0", + "expect-webdriverio": "^1.1.5", + "mocha": "^8.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "chokidar": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.3.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "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 + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mocha": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.0.1.tgz", + "integrity": "sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.3.1", + "debug": "3.2.6", + "diff": "4.0.2", + "escape-string-regexp": "1.0.5", + "find-up": "4.1.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "object.assign": "4.1.0", + "promise.allsettled": "1.0.2", + "serialize-javascript": "3.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.0", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, + "readdirp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.7" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } } }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "@wdio/protocols": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.3.0.tgz", + "integrity": "sha512-1GKzfyCTLW5WkFd3W7NLACih+zNWU7c8kFurbCQXDK1ko1obqJEs7ZjBr85q5XqMWburdks5rDjyml2iEB2LBg==", + "dev": true }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "@wdio/repl": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.3.0.tgz", + "integrity": "sha512-FT3flKOqNdZNG1uYl+QpOfdZIgKAWhLfoQ0s+wL0crLeDNIFvvM2qSDhRBRDYV7a0IFyBi8Z975WBn0dlH03Ig==", + "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "@wdio/utils": "6.3.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-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "@wdio/reporter": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-6.3.0.tgz", + "integrity": "sha512-vbwjJvSKZUtsWtQMhuVqT7ZP6RIFAH4+ienjNwW30QPDi38OujZgxC2ZqRoZKsxck6cfTgkxrXfNaxHN0/LHKg==", + "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "fs-extra": "^9.0.0" } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "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==", + "@wdio/runner": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-6.3.4.tgz", + "integrity": "sha512-+iOXfTODsSVf9LFBFKAEZqvPzfIClwFCKu7GGFZ7lrOF1svMNzT/0UY0ETsCBZe61Gr8xiI0wbCEly+0DbEh6w==", + "dev": true, "requires": { - "color-convert": "^1.9.0" + "@wdio/config": "6.1.14", + "@wdio/logger": "6.0.16", + "@wdio/utils": "6.3.0", + "deepmerge": "^4.0.0", + "gaze": "^1.1.2", + "webdriver": "6.3.0", + "webdriverio": "6.3.4" } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "@wdio/spec-reporter": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-6.3.0.tgz", + "integrity": "sha512-JGZAMcqiOloOw6xcIT5O8GORVaww6kslgH5kZGydVQyoNBj1ZKoLdEjqq2jklJsge1xsscdYdW9u9kMHwm25iA==", + "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "@wdio/reporter": "6.3.0", + "chalk": "^4.0.0", + "easy-table": "^1.1.1", + "pretty-ms": "^7.0.0" }, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "@wdio/sync": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-6.3.3.tgz", + "integrity": "sha512-WNq+hhkgk9LluKLP2nQ/9+EH8HNQnROFFHvYuznxb1aKj/zhZvqWuQPpmMWhPMBSTpkdbdLCYerZWKcamYOcJQ==", + "dev": true, "requires": { - "buffer-equal": "^1.0.0" + "@types/puppeteer": "^3.0.1", + "@wdio/logger": "6.0.16", + "fibers": "^4.0.1" } }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "@wdio/utils": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.3.0.tgz", + "integrity": "sha512-PbeC5fpieamgSAHf7S58MAyraGU1qKxnHdfGMG+ZIWiIo73oo4j/57CcH6ZawQ3YC1wEc/5q+VXg7N5hvqhJOQ==", "dev": true, "requires": { - "default-require-extensions": "^1.0.0" + "@wdio/logger": "6.0.16" } }, - "archiver": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", - "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=", + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, "requires": { - "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", - "zip-stream": "^1.2.0" - }, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "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.3" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "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.0.4" + }, + "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 + } + } + }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "dev": 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": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, + "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.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + }, + "dependencies": { + "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 + }, + "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 + } + } + }, + "ajv-keywords": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz", + "integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==", + "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.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "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 + }, + "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": { - "lodash": "^4.17.14" + "is-buffer": "^1.1.5" } } } }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "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.0" + } + }, + "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": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "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": "4.0.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-4.0.2.tgz", + "integrity": "sha512-B9IZjlGwaxF33UN4oPbfBkyA4V1SxNLeIhR1qY8sRXSsbdUkEHrrOvwlYFPx+8uQeCe9M+FG6KgO+imDmQ79CQ==", + "dev": true, + "requires": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.1.6", + "readable-stream": "^3.6.0", + "tar-stream": "^2.1.2", + "zip-stream": "^3.0.1" + }, + "dependencies": { + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true + } + } + }, "archiver-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", - "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "requires": { - "glob": "^7.0.0", - "graceful-fs": "^4.1.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", - "lodash": "^4.8.0", - "normalize-path": "^2.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" }, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "core-util-is": "~1.0.0", + "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.1" } } } @@ -1546,7 +3070,8 @@ "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true }, "argparse": { "version": "1.0.10", @@ -1560,12 +3085,14 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, "arr-filter": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, "requires": { "make-iterator": "^1.0.0" } @@ -1573,12 +3100,14 @@ "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==" + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true }, "arr-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, "requires": { "make-iterator": "^1.0.0" } @@ -1586,7 +3115,8 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true }, "array-differ": { "version": "1.0.0", @@ -1597,12 +3127,20 @@ "array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=" + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true }, "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true }, "array-find-index": { "version": "1.0.2", @@ -1622,19 +3160,21 @@ "dev": true }, "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" } }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, "requires": { "array-slice": "^1.0.0", "is-number": "^4.0.0" @@ -1643,7 +3183,8 @@ "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==" + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true } } }, @@ -1651,6 +3192,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, "requires": { "is-number": "^4.0.0" }, @@ -1658,19 +3200,22 @@ "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==" + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "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==" + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true }, "array-sort": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, "requires": { "default-compare": "^1.0.0", "get-value": "^2.0.6", @@ -1680,7 +3225,8 @@ "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==" + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, @@ -1693,17 +3239,29 @@ "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true }, "array.prototype.flat": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.2.tgz", - "integrity": "sha512-VXjh7lAL4KXKF2hY4FnEW9eRW6IhdvFW1sN/JwLbmECbCgACCnBHNyP3lFiYuttr0jxRN9Bsc5+G27dMseSWqQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.15.0", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.0-next.1" + } + }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" } }, "arraybuffer.slice": { @@ -1716,6 +3274,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -1729,6 +3288,14 @@ "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "assert": { @@ -1761,7 +3328,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "assertion-error": { "version": "1.1.0", @@ -1772,23 +3340,26 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", "dev": true }, "async-done": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.2", @@ -1799,17 +3370,26 @@ "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true }, "async-settle": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, "requires": { "async-done": "^1.2.2" } @@ -1817,22 +3397,40 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + } }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true }, "aws4": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", - "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", "dev": true }, "babel-code-frame": { @@ -1846,6 +3444,12 @@ "js-tokens": "^3.0.2" }, "dependencies": { + "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", @@ -1871,6 +3475,15 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "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.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -1911,6 +3524,12 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true } } }, @@ -2113,6 +3732,7 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "dev": true, "requires": { "@jest/transform": "^24.9.0", "@jest/types": "^24.9.0", @@ -2123,23 +3743,82 @@ "slash": "^2.0.0" }, "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true } } }, "babel-loader": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", - "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", + "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", "dev": true, "requires": { - "find-cache-dir": "^2.0.0", - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1", - "pify": "^4.0.1" + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } } }, "babel-messages": { @@ -2161,9 +3840,9 @@ } }, "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "dev": true, "requires": { "object.assign": "^4.1.0" @@ -2173,17 +3852,82 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "find-up": "^3.0.0", "istanbul-lib-instrument": "^3.3.0", "test-exclude": "^5.2.3" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "babel-plugin-jest-hoist": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "dev": true, "requires": { "@types/babel__traverse": "^7.0.6" } @@ -2822,6 +4566,7 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "dev": true, "requires": { "@babel/plugin-syntax-object-rest-spread": "^7.0.0", "babel-plugin-jest-hoist": "^24.9.0" @@ -2901,6 +4646,23 @@ "lodash": "^4.17.4", "mkdirp": "^0.5.1", "source-map-support": "^0.4.15" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } } }, "babel-runtime": { @@ -2910,6 +4672,13 @@ "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + } } }, "babel-template": { @@ -2986,6 +4755,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, "requires": { "arr-filter": "^1.1.1", "arr-flatten": "^1.0.1", @@ -3005,20 +4775,22 @@ "dev": true }, "bail": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", - "integrity": "sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "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.5", @@ -3033,6 +4805,7 @@ "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.0" } @@ -3041,6 +4814,7 @@ "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.0" } @@ -3049,6 +4823,7 @@ "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.0" } @@ -3057,6 +4832,7 @@ "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", @@ -3083,6 +4859,15 @@ "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", "dev": true }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -3093,6 +4878,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -3124,31 +4910,67 @@ "tryer": "^1.0.1" } }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", + "dev": true + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "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.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true }, "binaryextensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.2.0.tgz", - "integrity": "sha512-bHhs98rj/7i/RZpCSJ3uk55pLXOItjIrh2sRQZSM6OoktScX+LxJzvlU+FELp9j3TdcddTmmYArLSGptCTwjuw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", + "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", "dev": true, "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } } }, "blob": { @@ -3164,9 +4986,9 @@ "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==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", + "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", "dev": true }, "body": { @@ -3226,36 +5048,19 @@ "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==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "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.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "fill-range": "^7.0.1" } }, "brorand": { @@ -3264,15 +5069,22 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, + "browser-cookies": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browser-cookies/-/browser-cookies-1.2.0.tgz", + "integrity": "sha1-/KP/ubamOq3E2MCZnGtX0Pp9KbU=" + }, "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true }, "browser-resolve": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, "requires": { "resolve": "1.1.7" }, @@ -3280,7 +5092,8 @@ "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true } } }, @@ -3335,21 +5148,45 @@ "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "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.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" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", + "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.2", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "browserify-zlib": { @@ -3362,14 +5199,15 @@ } }, "browserslist": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", - "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001015", - "electron-to-chromium": "^1.3.322", - "node-releases": "^1.1.42" + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" } }, "browserstack": { @@ -3379,40 +5217,115 @@ "dev": true, "requires": { "https-proxy-agent": "^2.2.1" - } - }, - "browserstack-local": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.2.tgz", - "integrity": "sha512-fRaynjF0MvtyyfPRy2NFnVwxLyNtD28K/v9xRsIjUVf7xLc80NIm7Nfr3KXlFmWizhG91PL/UAOXlHkoxQjaNw==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1", - "is-running": "^2.0.0", - "ps-tree": "=1.1.1", - "temp-fs": "^0.9.9" - } + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "browserstack-local": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.5.tgz", + "integrity": "sha512-0/VdSv2YVXmcnwBb64XThMvjM1HnZJnPdv7CUgQbC5y/N9Wsr0Fu+j1oknE9fC/VPx9CpoSC6CJ0kza42skMSA==", + "dev": true, + "requires": { + "https-proxy-agent": "^4.0.0", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" + } }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "browserstacktunnel-wrapper": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.4.tgz", + "integrity": "sha512-GCV599FUUxNOCFl3WgPnfc5dcqq9XTmMXoxWpqkvmk0R9TOIoqmjENNU6LY6DtgIL6WfBVbg/jmWtnM5K6UYSg==", + "dev": true, "requires": { - "fast-json-stable-stringify": "2.x" + "https-proxy-agent": "^2.2.1", + "unzipper": "^0.9.3" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, "requires": { "node-int64": "^0.4.0" } }, "buffer": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", - "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "dev": true, "requires": { "base64-js": "^1.0.2", @@ -3444,7 +5357,8 @@ "buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=" + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true }, "buffer-fill": { "version": "1.0.0", @@ -3455,7 +5369,14 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-indexof-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz", + "integrity": "sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8=", + "dev": true }, "buffer-shims": { "version": "1.0.0", @@ -3469,29 +5390,11 @@ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, - "build": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/build/-/build-0.1.4.tgz", - "integrity": "sha1-cH/gJv/O3crL/c3zVur9pk8VEEY=", - "requires": { - "cssmin": "0.3.x", - "jsmin": "1.x", - "jxLoader": "*", - "moo-server": "*", - "promised-io": "*", - "timespan": "2.x", - "uglify-js": "1.x", - "walker": "1.x", - "winston": "*", - "wrench": "1.3.x" - }, - "dependencies": { - "uglify-js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.3.5.tgz", - "integrity": "sha1-S1v/+Rhu/7qoiOTJ6UvZ/EyUkp0=" - } - } + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "dev": true }, "builtin-status-codes": { "version": "3.0.0", @@ -3504,10 +5407,68 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "cac": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", + "integrity": "sha1-bSTO7Dcu/lybeYgIvH9JtHJCpO8=", + "dev": true, + "requires": { + "camelcase-keys": "^3.0.0", + "chalk": "^1.1.3", + "indent-string": "^3.0.0", + "minimist": "^1.2.0", + "read-pkg-up": "^1.0.1", + "suffix": "^0.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "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 + }, + "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.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "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.0.0" + } + }, + "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 + } + } + }, "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", @@ -3520,64 +5481,25 @@ "unset-value": "^1.0.0" } }, + "cacheable-lookup": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", + "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==", + "dev": true + }, "cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", "dev": true, "requires": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true - }, - "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "dev": true, - "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" - } - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, - "query-string": { - "version": "5.1.1", - "resolved": "http://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - } + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" } }, "caller-path": { @@ -3606,41 +5528,44 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "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=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", + "integrity": "sha1-/AxsNgNj9zd+N5O5oWvM8QcMHKQ=", "dev": true, "requires": { - "camelcase": "^2.0.0", + "camelcase": "^3.0.0", "map-obj": "^1.0.0" }, "dependencies": { "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true } } }, "caniuse-lite": { - "version": "1.0.30001015", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz", - "integrity": "sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ==", + "version": "1.0.30001105", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001105.tgz", + "integrity": "sha512-JupOe6+dGMr7E20siZHIZQwYqrllxotAhiaej96y6x00b/48rPt42o+SzOSCPbrpsDWvRja40Hwrj0g0q6LZJg==", "dev": true }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, "requires": { "rsvp": "^4.8.4" } @@ -3648,12 +5573,13 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, "ccount": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.4.tgz", - "integrity": "sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", + "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", "dev": true }, "center-align": { @@ -3680,10 +5606,20 @@ "type-detect": "^4.0.5" } }, + "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.0 <0.4" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -3691,33 +5627,33 @@ } }, "character-entities": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", - "integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", "dev": true }, "character-entities-html4": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.3.tgz", - "integrity": "sha512-SwnyZ7jQBCRHELk9zf2CN5AnGEc2nA+uKMZLHvcqhpPprjkYhiLn0DywMHgN5ttFZuITMATbh68M6VIVKwJbcg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", "dev": true }, "character-entities-legacy": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz", - "integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", "dev": true }, "character-reference-invalid": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz", - "integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "check-error": { @@ -3733,28 +5669,66 @@ "dev": true }, "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", + "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "chrome-launcher": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", + "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "dev": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^1.0.5", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^0.5.3", + "rimraf": "^3.0.2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, "cipher-base": { "version": "1.0.4", @@ -3776,6 +5750,7 @@ "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", @@ -3787,6 +5762,7 @@ "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.0" } @@ -3794,54 +5770,48 @@ } }, "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=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, + "cli-spinners": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.4.0.tgz", + "integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==", + "dev": true + }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "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=" + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true }, "clone-response": { "version": "1.0.2", @@ -3855,38 +5825,60 @@ "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=" + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true }, "cloneable-readable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, "requires": { "inherits": "^2.0.1", "process-nextick-args": "^2.0.0", "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "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=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "collapse-white-space": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.5.tgz", - "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", "dev": true }, "collection-map": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, "requires": { "arr-map": "^2.0.2", "for-own": "^1.0.0", @@ -3897,24 +5889,17 @@ "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.0" } }, - "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -3922,68 +5907,41 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "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==" - }, - "colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" - }, - "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", - "requires": { - "color": "3.0.x", - "text-hex": "1.0.x" - } - }, - "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.5.0" - } + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "comma-separated-tokens": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz", - "integrity": "sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", "dev": true }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "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", @@ -4000,7 +5958,8 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true }, "component-inherit": { "version": "0.0.3", @@ -4009,24 +5968,30 @@ "dev": true }, "compress-commons": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", - "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-3.0.0.tgz", + "integrity": "sha512-FyDqr8TKX5/X0qo+aVfaZ+PVmNJHJeckFBlq8jZGSJOgnynhfifoyl24qaqdUdDIBe0EVTHByN6NAkqYvE/2Xg==", "dev": true, "requires": { - "buffer-crc32": "^0.2.1", - "crc32-stream": "^2.0.0", - "normalize-path": "^2.0.0", - "readable-stream": "^2.0.0" + "buffer-crc32": "^0.2.13", + "crc32-stream": "^3.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^2.3.7" }, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "core-util-is": "~1.0.0", + "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.1" } } } @@ -4034,23 +5999,43 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "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.2.2", "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "concat-with-sourcemaps": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, "requires": { "source-map": "^0.6.1" }, @@ -4058,7 +6043,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -4121,6 +6107,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -4138,56 +6125,64 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true }, "copy-props": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "dev": true, "requires": { "each-props": "^1.3.0", "is-plain-object": "^2.0.1" } }, "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" }, "core-js-compat": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.4.8.tgz", - "integrity": "sha512-l3WTmnXHV2Sfu5VuD7EHE2w7y+K68+kULKt5RJg8ZJk3YhHF1qLD4O8v8AmNq+8vbOwnPFFDvds25/AoEvMqlQ==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", "dev": true, "requires": { - "browserslist": "^4.8.2", - "semver": "^6.3.0" + "browserslist": "^4.8.5", + "semver": "7.0.0" }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", "dev": true } } }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" + }, "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=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "coveralls": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.9.tgz", - "integrity": "sha512-nNBg3B1+4iDox5A5zqHKzUTiwl2ey4k2o0NEcVZYvl+GOSJdKBj4AJGKLv6h3SvWch7tABHePAQOSZWM9E2hMg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz", + "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==", "dev": true, "requires": { "js-yaml": "^3.13.1", "lcov-parse": "^1.0.0", "log-driver": "^1.2.7", - "minimist": "^1.2.0", - "request": "^2.88.0" + "minimist": "^1.2.5", + "request": "^2.88.2" } }, "crc": { @@ -4200,13 +6195,13 @@ } }, "crc32-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", - "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", + "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", "dev": true, "requires": { "crc": "^3.4.4", - "readable-stream": "^2.0.0" + "readable-stream": "^3.4.0" } }, "create-ecdh": { @@ -4217,6 +6212,14 @@ "requires": { "bn.js": "^4.1.0", "elliptic": "^6.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "create-hash": { @@ -4247,113 +6250,17 @@ } }, "criteo-direct-rsa-validate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.0.0.tgz", - "integrity": "sha512-BMstd/uhDJVd1zugn34VXlEcGl/ftN3cLS3ljudp/fWr3lq+ym6fUfXm3ru5c0G+rbio+7YFv7fkWjcK9A8z2w==", - "requires": { - "@types/crypto-js": "^3.1.43", - "@types/jest": "^24.0.23", - "@types/node": "^12.12.14", - "build": "^0.1.4", - "crypto-js": "^3.1.9-1", - "gulp": "^4.0.2", - "gulp-clean": "^0.4.0", - "gulp-concat": "^2.6.1", - "gulp-rename": "^1.4.0", - "gulp-typescript": "^6.0.0-alpha.1", - "gulp-uglify": "^3.0.2", - "jest": "^24.9.0", - "through2": "3.0.1", - "ts-jest": "^24.1.0", - "typescript": "^3.7.2" - }, - "dependencies": { - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=" - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=" - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "requires": { - "kind-of": "^1.1.0" - } - }, - "gulp-clean": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.4.0.tgz", - "integrity": "sha512-DARK8rNMo4lHOFLGTiHEJdf19GuoBDHqGUaypz+fOhrvOs3iFO7ntdYtdpNxv+AzSJBx/JfypF0yEj9ks1IStQ==", - "requires": { - "fancy-log": "^1.3.2", - "plugin-error": "^0.1.2", - "rimraf": "^2.6.2", - "through2": "^2.0.3", - "vinyl": "^2.1.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=" - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "requires": { - "readable-stream": "2 || 3" - } - } - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", + "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", + "lru-cache": "^4.0.1", "which": "^1.2.9" } }, @@ -4377,9 +6284,9 @@ } }, "crypto-js": { - "version": "3.1.9-1", - "resolved": "http://build-nexus.crto.in/repository/npm/crypto-js/-/crypto-js-3.1.9-1.tgz", - "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" }, "css": { "version": "2.2.4", @@ -4401,35 +6308,23 @@ } } }, - "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.0.0" - } - }, "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 }, - "cssmin": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssmin/-/cssmin-0.3.2.tgz", - "integrity": "sha1-3c5MVHtRCuDVlKjx+/iq+OLFwA0=" - }, "cssom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true }, "cssstyle": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "dev": true, "requires": { "cssom": "0.3.x" } @@ -4453,6 +6348,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, "requires": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -4462,6 +6358,7 @@ "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" } @@ -4470,6 +6367,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, "requires": { "abab": "^2.0.0", "whatwg-mimetype": "^2.2.0", @@ -4480,6 +6378,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, "requires": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -4489,9 +6388,9 @@ } }, "date-format": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", - "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", "dev": true }, "dateformat": { @@ -4543,20 +6442,30 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "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=" + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true }, "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + } } }, "deep-eql": { @@ -4569,33 +6478,52 @@ } }, "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", + "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", + "dev": true, "requires": { + "es-abstract": "^1.17.5", + "es-get-iterator": "^1.1.0", "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", + "is-date-object": "^1.0.2", + "is-regex": "^1.0.5", + "isarray": "^2.0.5", + "object-is": "^1.1.2", "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" + "object.assign": "^4.1.0", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "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=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "deepmerge": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.0.1.tgz", - "integrity": "sha512-VIPwiMJqJ13ZQfaCsIFnp5Me9tnjURiaIFxfz7EH0Ci0dTSQpZtSLrqOicXqEd/z2r+z+Klk9GzmnRsgpgbOsQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, "requires": { "kind-of": "^5.0.2" }, @@ -4603,7 +6531,8 @@ "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==" + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, @@ -4614,28 +6543,35 @@ "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.0" - } - } } }, "default-resolution": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=" + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "optional": true, + "requires": { + "clone": "^1.0.2" + } + }, + "defer-to-connect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", + "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==", + "dev": true }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -4644,6 +6580,7 @@ "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" @@ -4653,6 +6590,7 @@ "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.0" } @@ -4661,6 +6599,7 @@ "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.0" } @@ -4669,6 +6608,7 @@ "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", @@ -4686,7 +6626,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "depd": { "version": "1.1.2", @@ -4709,9 +6650,9 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detab": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.2.tgz", - "integrity": "sha512-Q57yPrxScy816TTE1P/uLRXLDKjXhvYTbfxS/e6lPD+YrqghbsMlGB9nQzj/zVtSPaF0DFPSdO916EWO4sQUyQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.3.tgz", + "integrity": "sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==", "dev": true, "requires": { "repeat-string": "^1.5.4" @@ -4720,7 +6661,8 @@ "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true }, "detect-indent": { "version": "4.0.0", @@ -4740,7 +6682,8 @@ "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true }, "detective": { "version": "4.7.1", @@ -4752,32 +6695,45 @@ "defined": "^1.0.0" } }, + "devtools": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.3.4.tgz", + "integrity": "sha512-dOcLdArp5/dJBzD8T5wcT2YgqkA22Mkqo0OS9cXz7JkHNgwOx1FI2Bq9GvP6o0TENHifYSYg3G0K/z0bacekqg==", + "dev": true, + "requires": { + "@wdio/config": "6.1.14", + "@wdio/logger": "6.0.16", + "@wdio/protocols": "6.3.0", + "@wdio/utils": "6.3.0", + "chrome-launcher": "^0.13.1", + "puppeteer-core": "^5.1.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + } + }, + "devtools-protocol": { + "version": "0.0.781568", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.781568.tgz", + "integrity": "sha512-9Uqnzy6m6zEStluH9iyJ3iHyaQziFnMnLeC8vK0eN6smiJmIx7+yB64d67C2lH/LZra+5cGscJAJsNXO+MdPMg==", + "dev": true + }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, - "diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "requires": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" - } - }, "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, "diff-sequences": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", - "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==" + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz", + "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==", + "dev": true }, "diffie-hellman": { "version": "5.0.3", @@ -4788,6 +6744,14 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "disparity": { @@ -4805,6 +6769,12 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true } } }, @@ -4891,17 +6861,93 @@ }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "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.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -4926,6 +6972,29 @@ } } }, + "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.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.0" + } + } + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -4935,12 +7004,65 @@ "locate-path": "^2.0.0" } }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "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 + }, + "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.0" + }, + "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.0" + } + } + } + }, + "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.0.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-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -4950,26 +7072,38 @@ "number-is-nan": "^1.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=", + "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": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" + "kind-of": "^3.0.2" }, "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "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.5" + } } } }, + "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.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -4980,6 +7114,27 @@ "path-exists": "^3.0.0" } }, + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -5005,29 +7160,28 @@ "dev": true }, "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "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.2.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, + "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-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "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": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } + "pify": "^3.0.0" } }, "pify": { @@ -5037,14 +7191,50 @@ "dev": true }, "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "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": "^2.0.0", + "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", - "path-type": "^2.0.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.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, "require-main-filename": { @@ -5063,6 +7253,12 @@ "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 + }, "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", @@ -5080,6 +7276,37 @@ } } }, + "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.0.0" + } + }, + "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-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 + }, + "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" + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -5130,6 +7357,53 @@ "yargs-parser": "^7.0.0" }, "dependencies": { + "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.2", + "parse-json": "^2.2.0", + "pify": "^2.0.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.2.0" + } + }, + "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.0.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.3.2", + "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", @@ -5175,6 +7449,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, "requires": { "webidl-conversions": "^4.0.2" } @@ -5197,6 +7472,23 @@ "dev": true, "requires": { "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "duplexer3": { @@ -5209,26 +7501,64 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", "readable-stream": "^2.0.0", "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "each-props": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, "requires": { "is-plain-object": "^2.0.1", "object.defaults": "^1.1.0" } }, + "easy-table": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.1.tgz", + "integrity": "sha512-C9Lvm0WFcn2RgxbMnTbXZenMIWcBtkzMr+dWqq/JsVoGFSVUVlPqeOa5LP5kM0I3zoOazFpckOEb2/0LDFfToQ==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0", + "wcwidth": ">=1.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 + } + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -5246,21 +7576,24 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "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.322", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", - "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==", - "dev": true + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.3.tgz", + "integrity": "sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg==", + "dev": true, + "requires": { + "jake": "^10.6.1" + } + }, + "electron-to-chromium": { + "version": "1.3.505", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.505.tgz", + "integrity": "sha512-Aunrp3HWtmdiJLIl+IPSFtEvJ/4Q9a3eKaxmzCthaZF1gbTbpHUTCU2zOVnFPH7r/AD7zQXyuFidYXzSHXBdsw==", + "dev": true }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -5270,27 +7603,28 @@ "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, - "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "requires": { - "env-variable": "0.0.x" - } - }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -5300,6 +7634,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "requires": { "once": "^1.4.0" } @@ -5307,7 +7642,7 @@ "engine.io": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha1-tgKBw1SEpw7gNR6g6/+D7IyVIqI=", + "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", "dev": true, "requires": { "accepts": "~1.3.4", @@ -5327,7 +7662,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -5348,7 +7683,7 @@ }, "engine.io-client": { "version": "3.2.1", - "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", "dev": true, "requires": { @@ -5374,7 +7709,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -5424,11 +7759,6 @@ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "dev": true }, - "env-variable": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", - "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" - }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -5451,33 +7781,63 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, "es-abstract": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.3.tgz", - "integrity": "sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw==", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", - "string.prototype.trimleft": "^2.1.0", - "string.prototype.trimright": "^2.1.0" + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } } }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -5496,15 +7856,16 @@ } }, "es5-shim": { - "version": "4.5.13", - "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.13.tgz", - "integrity": "sha512-xi6hh6gsvDE0MaW4Vp1lgNEBpVcCXRWfPXj5egDvtgLz4L9MEvNwYEMdJH+JJinWkwa8c3c3o5HduV7dB/e1Hw==", + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.14.tgz", + "integrity": "sha512-7SwlpL+2JpymWTt8sNLuC2zdhhc+wrfe5cMPI2j0o6WsPdfAiPwmFy2f0AocPB4RQVBOZ9kNTgi5YF7TdhkvEg==", "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=", + "dev": true, "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -5569,6 +7930,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, "requires": { "d": "^1.0.1", "ext": "^1.1.2" @@ -5578,6 +7940,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, "requires": { "d": "1", "es5-ext": "^0.10.46", @@ -5585,6 +7948,12 @@ "es6-symbol": "^3.1.1" } }, + "escalade": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -5593,29 +7962,27 @@ "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=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "escodegen": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", - "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, "requires": { - "esprima": "^3.1.3", + "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" }, "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "optional": true } } @@ -5678,12 +8045,39 @@ "text-table": "~0.2.0" }, "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "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 }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "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.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -5713,12 +8107,110 @@ "esutils": "^2.0.2" } }, + "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.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "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" + } + }, + "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.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" + } + }, + "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 + }, + "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 + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "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 + }, + "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.0.0" + } + }, + "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.0", + "signal-exit": "^3.0.2" + } + }, + "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", @@ -5727,6 +8219,12 @@ "requires": { "ansi-regex": "^3.0.0" } + }, + "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 } } }, @@ -5737,19 +8235,19 @@ "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==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "requires": { "debug": "^2.6.9", - "resolve": "^1.5.0" + "resolve": "^1.13.1" } }, "eslint-module-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.0.tgz", - "integrity": "sha512-kCo8pZaNz2dsAW7nCUjuVoI11EBXXpIzfNxmaoLhXoRDOnqXLC4iSGVRdZPhOitfbdEfMEfKOiENaK6wDPZEGw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, "requires": { "debug": "^2.6.9", @@ -5799,6 +8297,12 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "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 + }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -5811,23 +8315,24 @@ } }, "eslint-plugin-import": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.19.1.tgz", - "integrity": "sha512-x68131aKoCZlCae7rDXKSAQmbT5DQuManyXo2sK6fJJ0aK5CWAkv6A6HJZGgqC8IhjQxYPgo6/IY4Oz8AFsbBw==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", + "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", - "object.values": "^1.1.0", + "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { "find-up": { @@ -5885,14 +8390,11 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "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.2.0" - } + "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-type": { "version": "2.0.0", @@ -5903,12 +8405,6 @@ "pify": "^2.0.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", @@ -5929,6 +8425,12 @@ "find-up": "^2.0.0", "read-pkg": "^2.0.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, @@ -5979,9 +8481,9 @@ } }, "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "espree": { @@ -6001,12 +8503,20 @@ "dev": true }, "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } } }, "esrecurse": { @@ -6021,12 +8531,14 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true }, "etag": { "version": "1.8.1", @@ -6045,7 +8557,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -6056,26 +8568,18 @@ "split": "0.3", "stream-combiner": "~0.0.4", "through": "~2.3.1" - }, - "dependencies": { - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true - } } }, "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", "dev": true }, "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, "evp_bytestokey": { @@ -6098,6 +8602,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -6108,10 +8613,24 @@ "strip-eof": "^1.0.0" }, "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, "requires": { "pump": "^3.0.0" } @@ -6121,46 +8640,14 @@ "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" - }, - "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.2" - }, - "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.0" - } - } - } + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "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.3.3", "define-property": "^0.2.5", @@ -6175,6 +8662,7 @@ "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.0" } @@ -6183,6 +8671,7 @@ "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.0" } @@ -6190,26 +8679,59 @@ } }, "expand-range": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", - "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "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": { - "is-number": "^0.1.1", - "repeat-string": "^0.2.2" + "fill-range": "^2.1.0" }, "dependencies": { - "is-number": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", - "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", - "dev": true + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } }, - "repeat-string": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", - "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", + "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-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.0.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.5" + } } } }, @@ -6217,26 +8739,65 @@ "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" } }, "expect": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", - "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.1.0.tgz", + "integrity": "sha512-QbH4LZXDsno9AACrN9eM0zfnby9G+OsdNgZUohjg/P0mLy1O+/bzTAJGT6VSIjVCe8yKM6SzEl/ckEOFBT7Vnw==", + "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-styles": "^3.2.0", - "jest-get-type": "^24.9.0", - "jest-matcher-utils": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-regex-util": "^24.9.0" - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "@jest/types": "^26.1.0", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.0.0", + "jest-matcher-utils": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-regex-util": "^26.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "expect-webdriverio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-1.2.0.tgz", + "integrity": "sha512-nis1EL4LJSKvhqES6ojx1QqAZYtWAUHaVtwilXBXJELN2YZhwOcrfBT0AvxDvJXYKzgDDTED9STc9MwcK8KbYg==", + "dev": true, + "requires": { + "expect": "^26.0.1", + "jest-matcher-utils": "^26.0.1" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { "accepts": "~1.3.7", @@ -6283,19 +8844,22 @@ "type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==" + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true } } }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "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" @@ -6305,6 +8869,7 @@ "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" } @@ -6312,13 +8877,13 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, @@ -6326,6 +8891,7 @@ "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", @@ -6341,6 +8907,7 @@ "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.0" } @@ -6349,6 +8916,7 @@ "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.0" } @@ -6357,6 +8925,7 @@ "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.0" } @@ -6365,6 +8934,7 @@ "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.0" } @@ -6373,6 +8943,7 @@ "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", @@ -6381,10 +8952,40 @@ } } }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "faker": { "version": "3.1.0", @@ -6396,6 +8997,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, "requires": { "ansi-gray": "^0.1.1", "color-support": "^1.1.3", @@ -6404,25 +9006,22 @@ } }, "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=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "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=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "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=" - }, - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true }, "faye-websocket": { "version": "0.10.0", @@ -6442,24 +9041,28 @@ "bser": "2.1.1" } }, - "fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } }, "fibers": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-3.1.1.tgz", - "integrity": "sha512-dl3Ukt08rHVQfY8xGD0ODwyjwrRALtaghuqGH2jByYX1wpY+nAnRQjJ6Dbqq0DnVgNVQ9yibObzbF4IlPyiwPw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-4.0.3.tgz", + "integrity": "sha512-MW5VrDtTOLpKK7lzw4qD7Z9tXaAhdOmOED5RHzg3+HjUk+ibkjVW0Py2ERtdqgTXaerLkVkBy2AEmJiT6RMyzg==", "dev": true, "requires": { "detect-libc": "^1.0.3" } }, "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -6475,6 +9078,22 @@ "object-assign": "^4.0.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, + "optional": true + }, + "filelist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", + "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -6498,24 +9117,12 @@ "dev": true }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.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=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "to-regex-range": "^5.0.1" } }, "finalhandler": { @@ -6541,31 +9148,191 @@ "commondir": "^1.0.1", "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } } }, "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, "requires": { "detect-file": "^1.0.0", "is-glob": "^4.0.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" + }, + "dependencies": { + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "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" + } + } } }, "fined": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, "requires": { "expand-tilde": "^2.0.2", "is-plain-object": "^2.0.3", @@ -6577,7 +9344,17 @@ "flagged-respawn": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } }, "flat-cache": { "version": "1.3.4", @@ -6603,59 +9380,65 @@ } }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, "requires": { "inherits": "^2.0.3", "readable-stream": "^2.3.6" - } - }, - "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", - "dev": true, - "requires": { - "debug": "^3.0.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "ms": "^2.1.1" + "core-util-is": "~1.0.0", + "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.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, + "follow-redirects": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", + "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + "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.1" } }, + "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", @@ -6665,7 +9448,8 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, "fork-stream": { "version": "0.0.4", @@ -6677,6 +9461,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -6692,6 +9477,7 @@ "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" } @@ -6699,583 +9485,161 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "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-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "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.x", - "ncp": "~0.4.2", - "rimraf": "~2.2.0" - }, - "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=", - "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.1", - "mkdirp": "~0.3.5", - "walk": "^2.3.9" - }, - "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=" - }, - "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "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" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "optional": true, - "requires": { - "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" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "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.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "core-util-is": "~1.0.0", + "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.1" } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": 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-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "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.1", + "mkdirp": "~0.3.5", + "walk": "^2.3.9" + }, + "dependencies": { + "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": { - "ansi-regex": "^2.0.0" + "jsonfile": "~1.0.1", + "mkdirp": "0.3.x", + "ncp": "~0.4.2", + "rimraf": "~2.2.0" } }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } + "jsonfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", + "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", + "dev": true }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": 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.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": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, "requires": { - "string-width": "^1.0.2 || 2" + "minimist": "^1.2.5" } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "optional": true } } }, "fun-hooks": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-0.9.7.tgz", - "integrity": "sha512-2G0S9op/u+Ye/IJVPS++bs8oDuC/x7MpTKPkJ85U0PBfocJYd+f8WQRN1gqaCa2egKwjOOtXYVFcu0ksns+lWQ==", + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-0.9.9.tgz", + "integrity": "sha512-821UhoYfO9Sg01wAl/QsDRB088BW0aeOqzC1PXLxSlB+kaUVbK+Vp6wMDHU5huZZopYxmMmv5jDkEYqDpK3hqg==", "requires": { "typescript-tuple": "^2.2.1" } @@ -7283,7 +9647,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -7300,10 +9665,17 @@ "globule": "^1.0.0" } }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-func-name": { "version": "2.0.0", @@ -7312,9 +9684,9 @@ "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=", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "dev": true }, "get-stdin": { @@ -7324,20 +9696,25 @@ "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 + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + "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" } @@ -7404,84 +9781,339 @@ }, "dependencies": { "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "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.0" + } + }, + "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": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.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.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "dependencies": { + "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.0" + } + }, + "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.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } + } + }, + "glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + }, + "dependencies": { + "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.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.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.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.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "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": "^2.0.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "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.0" + } + } } }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "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.0.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-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "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": { - "is-extglob": "^1.0.0" + "kind-of": "^3.0.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.5" + } + } } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, "requires": { - "is-extglob": "^2.1.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "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" } } } }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", - "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - } - }, - "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", - "requires": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "object.defaults": "^1.1.0" - } - }, "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.1", "is-windows": "^1.0.1", @@ -7492,6 +10124,7 @@ "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", @@ -7503,7 +10136,8 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true }, "globals-docs": { "version": "2.4.1", @@ -7512,9 +10146,9 @@ "dev": true }, "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", + "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", "dev": true, "requires": { "glob": "~7.1.1", @@ -7526,47 +10160,35 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, "requires": { "sparkles": "^1.0.0" } }, "got": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/got/-/got-11.5.1.tgz", + "integrity": "sha512-reQEZcEBMTGnujmQ+Wm97mJs/OK6INtO6HmLI+xt3+9CvnRwWjXutUvb2mqr+Ao4Lu05Rx6+udx9sOQAmExMxA==", + "dev": true, + "requires": { + "@sindresorhus/is": "^3.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.0", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true }, "grapheme-splitter": { "version": "1.0.4", @@ -7583,12 +10205,14 @@ "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true }, "gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, "requires": { "glob-watcher": "^5.0.3", "gulp-cli": "^2.2.0", @@ -7596,39 +10220,49 @@ "vinyl-fs": "^3.0.0" }, "dependencies": { + "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-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "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.1", "strip-ansi": "^3.0.1", "wrap-ansi": "^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=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true }, "gulp-cli": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", - "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, "requires": { "ansi-colors": "^1.0.1", "archy": "^1.0.0", @@ -7638,7 +10272,7 @@ "copy-props": "^2.0.1", "fancy-log": "^1.3.2", "gulplog": "^1.0.0", - "interpret": "^1.1.0", + "interpret": "^1.4.0", "isobject": "^3.0.1", "liftoff": "^3.1.0", "matchdep": "^2.0.0", @@ -7646,7 +10280,7 @@ "pretty-hrtime": "^1.0.0", "replace-homedir": "^1.0.0", "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", + "v8flags": "^3.2.0", "yargs": "^7.1.0" } }, @@ -7654,112 +10288,57 @@ "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.0" } }, - "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=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "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=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.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=", - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, "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=" + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "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.0.0", "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "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": { - "is-utf8": "^0.2.0" + "ansi-regex": "^2.0.0" } }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "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.1", "strip-ansi": "^3.0.1" @@ -7768,12 +10347,14 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true }, "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "dev": true, "requires": { "camelcase": "^3.0.0", "cliui": "^3.2.0", @@ -7787,15 +10368,17 @@ "string-width": "^1.0.2", "which-module": "^1.0.0", "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" + "yargs-parser": "5.0.0-security.0" } }, "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "dev": true, "requires": { - "camelcase": "^3.0.0" + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" } } } @@ -7886,9 +10469,9 @@ "dev": true }, "minimist": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", - "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", + "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==", "dev": true }, "object-keys": { @@ -7972,6 +10555,7 @@ "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.0", "through2": "^2.0.0", @@ -7981,7 +10565,7 @@ "gulp-connect": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.7.0.tgz", - "integrity": "sha1-fpJfXkw06/7fnzGFdpZuj+iEDVo=", + "integrity": "sha512-8tRcC6wgXMLakpPw9M7GRJIhxkYdgZsXwn7n56BA2bQYGLR9NOPhMzx7js+qYDy6vhNkbApGKURjAw1FjY4pNA==", "dev": true, "requires": { "ansi-colors": "^2.0.5", @@ -7998,7 +10582,7 @@ "ansi-colors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-2.0.5.tgz", - "integrity": "sha1-XaN4Jf7z51872kf3YNZL/RDhXhA=", + "integrity": "sha512-yAdfUZ+c2wetVNIFsNRn44THW+Lty6S5TwMpUfLA/UaGhiXbBv/F8E60/1hMLd0cnF/CDoWH8vzVaI5bAcHCjw==", "dev": true }, "http-errors": { @@ -8013,6 +10597,12 @@ "statuses": ">= 1.4.0 < 2" } }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", @@ -8130,6 +10720,12 @@ "lodash._reinterpolate": "^3.0.0", "lodash.escape": "^3.0.0" } + }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true } } }, @@ -8236,11 +10832,6 @@ "minimatch": "^3.0.3" } }, - "gulp-rename": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==" - }, "gulp-replace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.0.0.tgz", @@ -8250,6 +10841,23 @@ "istextorbinary": "2.2.1", "readable-stream": "^2.0.1", "replacestream": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "gulp-shell": { @@ -8262,6 +10870,14 @@ "gulp-util": "^3.0.7", "lodash": "^4.0.0", "through2": "^2.0.0" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } } }, "gulp-sourcemaps": { @@ -8291,43 +10907,11 @@ } } }, - "gulp-typescript": { - "version": "6.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", - "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", - "requires": { - "ansi-colors": "^4.1.1", - "plugin-error": "^1.0.1", - "source-map": "^0.7.3", - "through2": "^3.0.1", - "vinyl": "^2.2.0", - "vinyl-fs": "^3.0.3" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "requires": { - "readable-stream": "2 || 3" - } - } - } - }, "gulp-uglify": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "dev": true, "requires": { "array-each": "^1.0.1", "extend-shallow": "^3.0.2", @@ -8367,6 +10951,12 @@ "vinyl": "^0.5.0" }, "dependencies": { + "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", @@ -8386,12 +10976,6 @@ "supports-color": "^2.0.0" } }, - "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", @@ -8469,6 +11053,15 @@ "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", "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.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -8492,6 +11085,7 @@ "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.0" } @@ -8504,61 +11098,64 @@ "requires": { "duplexer": "^0.1.1", "pify": "^4.0.1" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } } }, "handlebars": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", - "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", "dev": true, "requires": { + "minimist": "^1.2.5", "neo-async": "^2.6.0", - "optimist": "^0.6.1", "source-map": "^0.6.1", - "uglify-js": "^3.1.4" + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" }, "dependencies": { "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" }, "dependencies": { "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" } } }, @@ -8566,6 +11163,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -8577,6 +11175,14 @@ "dev": true, "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } } }, "has-binary2": { @@ -8605,12 +11211,14 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "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" } @@ -8640,6 +11248,7 @@ "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", @@ -8650,15 +11259,43 @@ "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": { + "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-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.0.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.5" + } + } + } + }, "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.5" } @@ -8666,13 +11303,28 @@ } }, "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "hash.js": { @@ -8686,9 +11338,9 @@ } }, "hast-util-is-element": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.0.3.tgz", - "integrity": "sha512-C62CVn7jbjp89yOhhy7vrkSaB7Vk906Gtcw/Ihd+Iufnq+2pwOZjdPmpzpKLWJXPJBMDX3wXg4FqmdOayPcewA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.0.4.tgz", + "integrity": "sha512-NFR6ljJRvDcyPP5SbV7MyPBgF47X3BsskLnmw1U34yL+X6YC0MoBx9EyMg8Jtx4FzGH95jw8+c1VPLHaRA0wDQ==", "dev": true }, "hast-util-sanitize": { @@ -8728,25 +11380,22 @@ } }, "hast-util-whitespace": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.3.tgz", - "integrity": "sha512-AlkYiLTTwPOyxZ8axq2/bCwRUPjIPBfrHkXuCR92B38b3lSdU22R5F/Z4DL6a2kxWpekWq1w6Nj48tWat6GeRA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", + "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", "dev": true }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "highlight.js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.17.0.tgz", - "integrity": "sha512-PyO7FK7z8ZC7FqBlmAxm4d+1DYaoS6+uaxt9KGkyP1AnmGRLnWmNod1yp9BFjUyHoDF00k+V57gF6X9ifY7f/A==", - "dev": true, - "requires": { - "handlebars": "^4.5.3" - } + "version": "9.18.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", + "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==", + "dev": true }, "hmac-drbg": { "version": "1.0.1", @@ -8773,6 +11422,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, "requires": { "parse-passwd": "^1.0.0" } @@ -8784,28 +11434,36 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==" + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, "requires": { "whatwg-encoding": "^1.0.1" } }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "html-void-elements": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.4.tgz", - "integrity": "sha512-yMk3naGPLrfvUV9TdDbuYXngh/TpHbA6TrOw3HL9kS8yhwx7i309BReNg7CbAJXGE+UMJ6je5OqJ7lC63o6YuQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", + "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==", "dev": true }, "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, "http-errors": { @@ -8821,15 +11479,15 @@ } }, "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==", "dev": true }, "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "requires": { "eventemitter3": "^4.0.0", @@ -8841,12 +11499,23 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } }, + "http2-wrapper": { + "version": "1.0.0-beta.5.2", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", + "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -8854,19 +11523,19 @@ "dev": true }, "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "agent-base": "5", + "debug": "4" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -8880,12 +11549,6 @@ } } }, - "humanize-duration": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.15.3.tgz", - "integrity": "sha512-BMz6w8p3NVa6QP9wDtqUkXfwgBqDaZ5z/np0EYdoWrLqL849Onp6JWMXMhbHtuvO9jUThLN5H1ThRQ8dUWnYkA==", - "dev": true - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -8910,24 +11573,68 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, "requires": { "pkg-dir": "^3.0.0", "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } } }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "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.0" - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true }, "indexof": { "version": "0.0.1", @@ -8939,6 +11646,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -8952,61 +11660,87 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "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.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", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "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": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "strip-ansi": { + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "has-flag": "^4.0.0" } } } }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true }, "into-stream": { "version": "3.1.0", @@ -9022,6 +11756,7 @@ "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.0.0" } @@ -9029,17 +11764,19 @@ "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=" + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true }, "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, "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.1" @@ -9049,14 +11786,22 @@ "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.0.2" }, "dependencies": { + "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 + }, "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.5" } @@ -9064,9 +11809,9 @@ } }, "is-alphabetical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", - "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", "dev": true }, "is-alphanumeric": { @@ -9076,9 +11821,9 @@ "dev": true }, "is-alphanumerical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz", - "integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", "dev": true, "requires": { "is-alphabetical": "^1.0.0", @@ -9088,35 +11833,53 @@ "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", + "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", + "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=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", + "dev": true + }, "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true }, "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true }, "is-ci": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, "requires": { "ci-info": "^2.0.0" } @@ -9125,14 +11888,22 @@ "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.0.2" }, "dependencies": { + "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 + }, "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.5" } @@ -9140,20 +11911,22 @@ } }, "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true }, "is-decimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", - "integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", "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", @@ -9163,7 +11936,8 @@ "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==" + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, @@ -9191,68 +11965,71 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + "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=" + "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.0" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "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=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, "requires": { "is-extglob": "^2.1.1" } }, "is-hexadecimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", - "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", "dev": true }, "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=" + "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=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true }, "is-object": { "version": "1.0.1", @@ -9270,6 +12047,7 @@ "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" } @@ -9287,23 +12065,25 @@ "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=", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, "requires": { - "has": "^1.0.1" + "has-symbols": "^1.0.1" } }, "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" } @@ -9326,6 +12106,12 @@ "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", "dev": true }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, "is-ssh": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz", @@ -9338,7 +12124,14 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true }, "is-symbol": { "version": "1.0.3", @@ -9349,15 +12142,29 @@ "has-symbols": "^1.0.1" } }, + "is-typed-array": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", + "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.0", + "es-abstract": "^1.17.4", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "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" } @@ -9365,39 +12172,67 @@ "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + "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=" + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "dev": true }, "is-whitespace-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz", - "integrity": "sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", "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==" + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true }, "is-word-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.3.tgz", - "integrity": "sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", "dev": true }, "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + }, + "dependencies": { + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "dev": true + } + } }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isbinaryfile": { "version": "3.0.3", @@ -9411,17 +12246,20 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "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=" + "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=" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "istanbul": { "version": "0.4.5", @@ -9445,6 +12283,12 @@ "wordwrap": "^1.0.0" }, "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, "escodegen": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", @@ -9489,6 +12333,15 @@ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", @@ -9513,12 +12366,6 @@ "requires": { "has-flag": "^1.0.0" } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true } } }, @@ -9620,12 +12467,30 @@ "handlebars": "^4.0.3" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", @@ -9637,45 +12502,11 @@ } } }, - "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.0", - "istanbul-lib-instrument": "^1.7.3", - "loader-utils": "^1.1.0", - "schema-utils": "^0.3.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "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.2.1", - "semver": "^5.3.0" - } - } - } - }, "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true }, "istanbul-lib-hook": { "version": "1.2.2", @@ -9687,23 +12518,22 @@ } }, "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" }, "dependencies": { "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, @@ -9711,16 +12541,24 @@ "version": "2.0.8", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, "requires": { "istanbul-lib-coverage": "^2.0.5", "make-dir": "^2.1.0", "supports-color": "^6.1.0" }, "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -9731,6 +12569,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, "requires": { "debug": "^4.1.1", "istanbul-lib-coverage": "^2.0.5", @@ -9748,25 +12587,42 @@ "ms": "^2.1.1" } }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, "istanbul-reports": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", - "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "dev": true, "requires": { - "handlebars": "^4.1.2" + "html-escaper": "^2.0.0" } }, "istextorbinary": { @@ -9790,39 +12646,184 @@ "is-object": "^1.0.1" } }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "dev": true, + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + } + }, "jest": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", + "dev": true, "requires": { "import-local": "^2.0.0", "jest-cli": "^24.9.0" }, "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.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 + }, "jest-cli": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz", "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==", + "dev": true, + "requires": { + "@jest/core": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^13.3.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, "requires": { - "@jest/core": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "import-local": "^2.0.0", - "is-ci": "^2.0.0", - "jest-config": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "prompts": "^2.0.1", - "realpath-native": "^1.1.0", - "yargs": "^13.3.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" } }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, "requires": { "cliui": "^5.0.0", "find-up": "^3.0.0", @@ -9833,7 +12834,17 @@ "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -9842,16 +12853,40 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==", + "dev": true, "requires": { "@jest/types": "^24.9.0", "execa": "^1.0.0", "throat": "^4.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + } } }, "jest-config": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", + "dev": true, "requires": { "@babel/core": "^7.1.0", "@jest/test-sequencer": "^24.9.0", @@ -9870,23 +12905,238 @@ "micromatch": "^3.1.10", "pretty-format": "^24.9.0", "realpath-native": "^1.1.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "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" + } + } } }, "jest-diff": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", - "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.1.0.tgz", + "integrity": "sha512-GZpIcom339y0OXznsEKjtkfKxNdg7bVbEofK8Q6MnevTIiR1jNhDWKhRX6X0SDXJlwn3dy59nZ1z55fLkAqPWg==", + "dev": true, "requires": { - "chalk": "^2.0.1", - "diff-sequences": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "chalk": "^4.0.0", + "diff-sequences": "^26.0.0", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "jest-docblock": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", + "dev": true, "requires": { "detect-newline": "^2.1.0" } @@ -9895,18 +13145,66 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "dev": true, "requires": { "@jest/types": "^24.9.0", "chalk": "^2.0.1", "jest-get-type": "^24.9.0", "jest-util": "^24.9.0", "pretty-format": "^24.9.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + } } }, "jest-environment-jsdom": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", + "dev": true, "requires": { "@jest/environment": "^24.9.0", "@jest/fake-timers": "^24.9.0", @@ -9914,29 +13212,76 @@ "jest-mock": "^24.9.0", "jest-util": "^24.9.0", "jsdom": "^11.5.1" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + } } }, "jest-environment-node": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz", "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==", + "dev": true, "requires": { "@jest/environment": "^24.9.0", "@jest/fake-timers": "^24.9.0", "@jest/types": "^24.9.0", "jest-mock": "^24.9.0", "jest-util": "^24.9.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + } } }, "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==" + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "dev": true }, "jest-haste-map": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "dev": true, "requires": { "@jest/types": "^24.9.0", "anymatch": "^2.0.0", @@ -9950,12 +13295,174 @@ "micromatch": "^3.1.10", "sane": "^4.0.3", "walker": "^1.0.7" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "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.4", + "normalize-path": "^2.1.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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "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-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.0.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.5" + } + } + } + }, + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "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" + } + } } }, "jest-jasmine2": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz", "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==", + "dev": true, "requires": { "@babel/traverse": "^7.1.0", "@jest/environment": "^24.9.0", @@ -9973,47 +13480,426 @@ "jest-util": "^24.9.0", "pretty-format": "^24.9.0", "throat": "^4.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "diff-sequences": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", + "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "dev": true + }, + "expect": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", + "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.9.0" + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "jest-diff": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", + "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "jest-matcher-utils": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", + "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "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" + } + } } }, "jest-leak-detector": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz", "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==", + "dev": true, "requires": { "jest-get-type": "^24.9.0", "pretty-format": "^24.9.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + } } }, "jest-matcher-utils": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", - "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.1.0.tgz", + "integrity": "sha512-PW9JtItbYvES/xLn5mYxjMd+Rk+/kIt88EfH3N7w9KeOrHWaHrdYPnVHndGbsFGRJ2d5gKtwggCvkqbFDoouQA==", + "dev": true, "requires": { - "chalk": "^2.0.1", - "jest-diff": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "chalk": "^4.0.0", + "jest-diff": "^26.1.0", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.1.0.tgz", + "integrity": "sha512-dY0+UlldiAJwNDJ08SF0HdF32g9PkbF2NRK/+2iMPU40O6q+iSn1lgog/u0UH8ksWoPv0+gNq8cjhYO2MFtT0g==", + "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", + "@jest/types": "^26.1.0", "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" }, "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -10021,46 +13907,124 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "dev": true, "requires": { "@jest/types": "^24.9.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + } } }, "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true }, "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==" + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true }, "jest-resolve": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz", "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==", + "dev": true, "requires": { "@jest/types": "^24.9.0", "browser-resolve": "^1.11.3", "chalk": "^2.0.1", "jest-pnp-resolver": "^1.2.1", "realpath-native": "^1.1.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + } } }, "jest-resolve-dependencies": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz", "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==", + "dev": true, "requires": { "@jest/types": "^24.9.0", "jest-regex-util": "^24.3.0", "jest-snapshot": "^24.9.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "dev": true + } } }, "jest-runner": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", + "dev": true, "requires": { "@jest/console": "^24.7.1", "@jest/environment": "^24.9.0", @@ -10083,61 +14047,480 @@ "throat": "^4.0.0" }, "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "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==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "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" + } + } + } + }, + "jest-runtime": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", + "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.9.0", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^13.3.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "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.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.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.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-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 + }, + "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.0.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.5" + } + } + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } }, - "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } - } - } - }, - "jest-runtime": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", - "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", - "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.9.0", - "@jest/source-map": "^24.3.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/yargs": "^13.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.1.15", - "jest-config": "^24.9.0", - "jest-haste-map": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-mock": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.9.0", - "jest-snapshot": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "strip-bom": "^3.0.0", - "yargs": "^13.3.0" - }, - "dependencies": { + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "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" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, "requires": { "cliui": "^5.0.0", "find-up": "^3.0.0", @@ -10148,7 +14531,17 @@ "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -10156,12 +14549,14 @@ "jest-serializer": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", - "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==" + "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", + "dev": true }, "jest-snapshot": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz", "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==", + "dev": true, "requires": { "@babel/types": "^7.0.0", "@jest/types": "^24.9.0", @@ -10178,10 +14573,251 @@ "semver": "^6.2.0" }, "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "diff-sequences": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", + "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "dev": true + }, + "expect": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", + "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.9.0" + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "jest-diff": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", + "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "jest-matcher-utils": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", + "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "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" + } } } }, @@ -10189,6 +14825,7 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "dev": true, "requires": { "@jest/console": "^24.9.0", "@jest/fake-timers": "^24.9.0", @@ -10204,15 +14841,46 @@ "source-map": "^0.6.0" }, "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "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==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -10220,6 +14888,7 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", + "dev": true, "requires": { "@jest/types": "^24.9.0", "camelcase": "^5.3.1", @@ -10227,12 +14896,59 @@ "jest-get-type": "^24.9.0", "leven": "^3.1.0", "pretty-format": "^24.9.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + } } }, "jest-watcher": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz", "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==", + "dev": true, "requires": { "@jest/test-result": "^24.9.0", "@jest/types": "^24.9.0", @@ -10241,12 +14957,41 @@ "chalk": "^2.0.1", "jest-util": "^24.9.0", "string-length": "^2.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + } } }, "jest-worker": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, "requires": { "merge-stream": "^2.0.0", "supports-color": "^6.1.0" @@ -10256,22 +15001,18 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, "requires": { "has-flag": "^3.0.0" } } } }, - "js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { "version": "3.13.1", @@ -10286,12 +15027,14 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true }, "jsdom": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, "requires": { "abab": "^2.0.0", "acorn": "^5.5.3", @@ -10319,27 +15062,34 @@ "whatwg-url": "^6.4.1", "ws": "^5.2.0", "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } } }, "jsencrypt": { "version": "3.0.0-rc.1", - "resolved": "http://build-nexus.crto.in/repository/npm/jsencrypt/-/jsencrypt-3.0.0-rc.1.tgz", + "resolved": "https://registry.npmjs.org/jsencrypt/-/jsencrypt-3.0.0-rc.1.tgz", "integrity": "sha512-gcvGaqerlUJy1Kq6tNgPYteVEoWNemu+9hBe2CdsCIz4rVcwjoTQ72iD1W76/PRMlnkzG0yVh7nwOOMOOUfKmg==" }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "jsmin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jsmin/-/jsmin-1.0.1.tgz", - "integrity": "sha1-570NzWSWw79IYyNb9GGj2YqjuYw=" + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true }, "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, "json-loader": { @@ -10351,42 +15101,51 @@ "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==" + "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=" + "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=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "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=" + "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=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json5": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", - "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, "requires": { - "minimist": "^1.2.0" + "minimist": "^1.2.5" } }, "jsonfile": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", - "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", - "dev": true + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + } }, "jsonparse": { "version": "1.3.1", @@ -10398,6 +15157,7 @@ "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", @@ -10413,55 +15173,36 @@ "just-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=" + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true }, "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", "dev": true }, - "jxLoader": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jxLoader/-/jxLoader-0.1.1.tgz", - "integrity": "sha1-ATTqUUTlM7WU/B/yX/GU4jXFPs0=", - "requires": { - "js-yaml": "0.3.x", - "moo-server": "1.3.x", - "promised-io": "*", - "walker": "1.x" - }, - "dependencies": { - "js-yaml": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-0.3.7.tgz", - "integrity": "sha1-1znY7oZGHlSzVNan19HyrZoWf2I=" - } - } - }, "karma": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/karma/-/karma-3.1.4.tgz", - "integrity": "sha512-31Vo8Qr5glN+dZEVIpnPCxEGleqE0EY6CtC2X9TagRV3rRQ3SNrvfhddICkJgUK3AgqpeKSZau03QumTGhGoSw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", + "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", "dev": true, "requires": { "bluebird": "^3.3.0", "body-parser": "^1.16.1", - "chokidar": "^2.0.3", + "braces": "^3.0.2", + "chokidar": "^3.0.0", "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", "flatted": "^2.0.0", "glob": "^7.1.1", "graceful-fs": "^4.1.2", "http-proxy": "^1.13.0", "isbinaryfile": "^3.0.0", - "lodash": "^4.17.5", - "log4js": "^3.0.0", + "lodash": "^4.17.14", + "log4js": "^4.0.0", "mime": "^2.3.1", "minimatch": "^3.0.2", "optimist": "^0.6.1", @@ -10476,11 +15217,20 @@ }, "dependencies": { "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", "dev": true }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10499,13 +15249,13 @@ } }, "karma-browserstack-launcher": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.5.1.tgz", - "integrity": "sha512-zt9Ukow5A9WZHZXCFVO/h5kRsAdaZYeMNJK9Uan8v42amQXt3B/DZVxl24NCcAIxufKjW13UWd9iJ9knG9OCYw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.4.0.tgz", + "integrity": "sha512-bUQK84U+euDfOUfEjcF4IareySMOBNRLrrl9q6cttIe8f011Ir6olLITTYMOJDcGY58wiFIdhPHSPd9Pi6+NfQ==", "dev": true, "requires": { "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", + "browserstacktunnel-wrapper": "~2.0.2", "q": "~1.5.0" } }, @@ -10525,6 +15275,105 @@ "which": "^1.2.1" } }, + "karma-coverage": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.2.tgz", + "integrity": "sha512-zge5qiGEIKDdzWciQwP4p0LSac4k/L6VfrBsERMUn5mpDvxhv1sPVOrSlpzpi70T7NhuEy4bgnpAKIYuumIMCw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.1", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.0", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "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==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "karma-coverage-istanbul-reporter": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.4.3.tgz", @@ -10545,20 +15394,12 @@ } }, "karma-firefox-launcher": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.2.0.tgz", - "integrity": "sha512-j9Zp8M8+VLq1nI/5xZGfzeaEPtGQ/vk3G+Y8vpmFWLvKLNZ2TDjD6cu2dUu7lDbu1HXNgatsAX4jgCZTkR9qhQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz", + "integrity": "sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ==", "dev": true, "requires": { "is-wsl": "^2.1.0" - }, - "dependencies": { - "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", - "dev": true - } } }, "karma-ie-launcher": { @@ -10577,6 +15418,14 @@ "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": { @@ -10596,6 +15445,15 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "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.0.1" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -10671,6 +15529,26 @@ "requires": { "lodash": "^4.17.14" } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } } } }, @@ -10681,36 +15559,31 @@ "dev": true }, "keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz", + "integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==", "dev": true, "requires": { - "json-buffer": "3.0.0" + "json-buffer": "3.0.1" } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, - "kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "requires": { - "colornames": "^1.1.1" - } + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true }, "last-run": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, "requires": { "default-resolution": "^2.0.0", "es6-weak-map": "^2.0.1" @@ -10726,14 +15599,33 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, "requires": { "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "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" } @@ -10748,6 +15640,7 @@ "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.2" } @@ -10755,17 +15648,29 @@ "left-pad": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "dev": true, + "requires": { + "leven": "^3.1.0" + } }, "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" @@ -10775,6 +15680,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, "requires": { "extend": "^3.0.0", "findup-sync": "^3.0.0", @@ -10786,6 +15692,33 @@ "resolve": "^1.1.7" } }, + "lighthouse-logger": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz", + "integrity": "sha512-wzUvdIeJZhRsG6gpZfmSCfysaxNEr43i+QT+Hie94wvHDKFLi4n7C2GqZ4sTC+PH5b5iktmXJvU87rWvhP3lHw==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "marky": "^1.2.0" + } + }, + "listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=", + "dev": true + }, + "live-connect-js": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-1.1.10.tgz", + "integrity": "sha512-G/LJKN3b21DZILCQRyataC/znLvJRyogtu7mAkKlkhP9B9UJ8bcOL7ihW/clD2PsT4hVUkeabHhUGsPCmhsjFw==", + "requires": { + "@kiosked/ulid": "^3.0.0", + "abab": "^2.0.3", + "browser-cookies": "^1.2.0", + "tiny-hashes": "1.0.1" + } + }, "livereload-js": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", @@ -10793,64 +15726,49 @@ "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=", + "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.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true }, "lodash._basecopy": { "version": "3.0.1", @@ -10964,15 +15882,23 @@ "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "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" - } + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", + "dev": true }, "lodash.escape": { "version": "2.4.1", @@ -10985,6 +15911,18 @@ "lodash.keys": "~2.4.1" } }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -11004,13 +15942,16 @@ "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" - } + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=", + "dev": true + }, + "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": "2.4.1", @@ -11021,12 +15962,30 @@ "lodash._isnative": "~2.4.1", "lodash._shimkeys": "~2.4.1", "lodash.isobject": "~2.4.1" + }, + "dependencies": { + "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.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.pickby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", + "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=", + "dev": true }, "lodash.restparam": { "version": "3.6.1", @@ -11043,7 +16002,8 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true }, "lodash.template": { "version": "2.4.1", @@ -11058,6 +16018,18 @@ "lodash.keys": "~2.4.1", "lodash.templatesettings": "~2.4.1", "lodash.values": "~2.4.1" + }, + "dependencies": { + "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" + } + } } }, "lodash.templatesettings": { @@ -11070,6 +16042,12 @@ "lodash.escape": "~2.4.1" } }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", + "dev": true + }, "lodash.values": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", @@ -11079,6 +16057,12 @@ "lodash.keys": "~2.4.1" } }, + "lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=", + "dev": true + }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", @@ -11086,37 +16070,31 @@ "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==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "^2.4.2" } }, "log4js": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz", - "integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", + "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", "dev": true, "requires": { - "circular-json": "^0.5.5", - "date-format": "^1.2.0", - "debug": "^3.1.0", - "rfdc": "^1.1.2", - "streamroller": "0.7.0" + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", + "rfdc": "^1.1.4", + "streamroller": "^1.0.6" }, "dependencies": { - "circular-json": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz", - "integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==", - "dev": true - }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -11130,17 +16108,17 @@ } } }, - "logform": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", - "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", - "requires": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", - "ms": "^2.1.1", - "triple-beam": "^1.3.0" - } + "loglevel": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "dev": true + }, + "loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true }, "loglevelnext": { "version": "1.0.5", @@ -11165,15 +16143,16 @@ "dev": true }, "longest-streak": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz", - "integrity": "sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", "dev": true }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -11189,9 +16168,9 @@ } }, "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "dev": true }, "lru-cache": { @@ -11217,20 +16196,31 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, "requires": { "pify": "^4.0.1", "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } } }, "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "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.2.0" } @@ -11239,6 +16229,7 @@ "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" } @@ -11247,6 +16238,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, "requires": { "tmpl": "1.0.x" } @@ -11254,7 +16246,8 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true }, "map-obj": { "version": "1.0.1", @@ -11263,23 +16256,24 @@ "dev": true }, "map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "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.0" } }, "markdown-escapes": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", - "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", "dev": true }, "markdown-table": { @@ -11288,10 +16282,17 @@ "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", "dev": true }, + "marky": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.1.tgz", + "integrity": "sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==", + "dev": true + }, "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, "requires": { "findup-sync": "^2.0.0", "micromatch": "^3.0.4", @@ -11299,10 +16300,63 @@ "stack-trace": "0.0.10" }, "dependencies": { + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.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", @@ -11310,13 +16364,71 @@ "resolve-dir": "^1.0.1" } }, + "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-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.0" } + }, + "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.0.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.5" + } + } + } + }, + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "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" + } } } }, @@ -11384,9 +16496,9 @@ } }, "mdast-util-to-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.0.7.tgz", - "integrity": "sha512-P+gdtssCoHOX+eJUrrC30Sixqao86ZPlVjR5NEAoy0U79Pfxb1Y0Gntei0+GrnQD4T04X9xA8tcugp90cSmNow==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", + "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", "dev": true }, "mdast-util-toc": { @@ -11408,9 +16520,9 @@ "dev": true }, "github-slugger": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.1.tgz", - "integrity": "sha512-SsZUjg/P03KPzQBt7OxJPasGw6NRO5uOgiZ5RGXVud5iSIZ0eNZeNp5rTwCxtavrRUa/A77j8mePVc5lEvk0KQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", + "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", "dev": true, "requires": { "emoji-regex": ">=6.0.0 <=6.1.1" @@ -11442,6 +16554,14 @@ "dev": true, "requires": { "mimic-fn": "^1.0.0" + }, + "dependencies": { + "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 + } } }, "memoizee": { @@ -11462,124 +16582,69 @@ }, "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.3", - "readable-stream": "^2.0.1" - } - }, - "memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true - }, - "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.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" - }, - "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.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "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.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.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.2.0" - } - }, - "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.0" - } - }, - "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.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.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": "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.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.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=", + "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.3", + "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "core-util-is": "~1.0.0", + "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.1" } + } + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, + "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.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" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "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": { - "is-utf8": "^0.2.0" + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" } } } @@ -11589,10 +16654,28 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "methods": { "version": "1.1.2", @@ -11600,23 +16683,13 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "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.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "miller-rabin": { @@ -11627,6 +16700,14 @@ "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "mime": { @@ -11635,22 +16716,22 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", - "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" }, "mime-types": { - "version": "2.1.25", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", - "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "requires": { - "mime-db": "1.42.0" + "mime-db": "1.44.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==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "mimic-response": { @@ -11675,19 +16756,22 @@ "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.7" } }, "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -11697,6 +16781,7 @@ "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" } @@ -11704,19 +16789,16 @@ } }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true }, "mocha": { "version": "5.2.0", @@ -11737,12 +16819,6 @@ "supports-color": "5.4.0" }, "dependencies": { - "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 - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -11772,6 +16848,27 @@ "path-is-absolute": "^1.0.0" } }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "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" + } + }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -11838,6 +16935,38 @@ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -11846,10 +16975,26 @@ } } }, - "moo-server": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz", - "integrity": "sha1-XceVaVZaENbv7VQ5SR5p0jkuWPE=" + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } + } }, "ms": { "version": "2.0.0", @@ -11903,24 +17048,27 @@ "mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==" + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true, "optional": true }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -11938,7 +17086,8 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true }, "ncp": { "version": "0.4.2", @@ -11952,30 +17101,33 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "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=" + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "nise": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.2.tgz", - "integrity": "sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", "dev": true, "requires": { "@sinonjs/formatio": "^3.2.1", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", - "lolex": "^4.1.0", + "lolex": "^5.0.1", "path-to-regexp": "^1.7.0" }, "dependencies": { @@ -11996,10 +17148,13 @@ "dev": true }, "lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", - "dev": true + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } }, "path-to-regexp": { "version": "1.8.0", @@ -12015,7 +17170,8 @@ "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true }, "node-libs-browser": { "version": "2.2.1", @@ -12064,43 +17220,57 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } } } }, "node-modules-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true }, "node-notifier": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "dev": true, "requires": { "growly": "^1.3.0", "is-wsl": "^1.1.0", "semver": "^5.5.0", "shellwords": "^0.1.1", "which": "^1.3.0" - } - }, - "node-releases": { - "version": "1.1.42", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.42.tgz", - "integrity": "sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==", - "dev": true, - "requires": { - "semver": "^6.3.0" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true } } }, + "node-releases": { + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", + "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", + "dev": true + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -12114,6 +17284,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -12124,34 +17295,24 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true }, "now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, "requires": { "once": "^1.3.2" } }, - "npm-install-package": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz", - "integrity": "sha1-1+/jz816sAYUuJbqUxGdyaslkSU=", - "dev": true - }, "npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -12167,12 +17328,82 @@ "read-pkg": "^3.0.0", "shell-quote": "^1.6.1", "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "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.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.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.1" + } + }, + "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" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "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.3.2", + "path-type": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } } }, "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.0" } @@ -12186,17 +17417,20 @@ "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=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -12214,6 +17448,7 @@ "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.0", "define-property": "^0.2.5", @@ -12224,14 +17459,22 @@ "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.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 + }, "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.5" } @@ -12239,25 +17482,31 @@ } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" }, "object-is": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "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.0" } @@ -12266,6 +17515,7 @@ "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", @@ -12277,6 +17527,7 @@ "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.0.0", @@ -12285,18 +17536,20 @@ } }, "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.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.0" @@ -12327,6 +17580,7 @@ "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" } @@ -12335,19 +17589,20 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, "requires": { "for-own": "^1.0.0", "make-iterator": "^1.0.0" } }, "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", + "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" } @@ -12360,26 +17615,28 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "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" } }, - "one-time": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", - "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" - }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "^2.1.0" } }, "opener": { @@ -12395,12 +17652,21 @@ "dev": true, "requires": { "is-wsl": "^1.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "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.1", "wordwrap": "~0.0.2" @@ -12408,8 +17674,15 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true } } }, @@ -12431,8 +17704,26 @@ "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.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "os-browserify": { @@ -12483,6 +17774,12 @@ "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" } + }, + "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 } } }, @@ -12493,15 +17790,16 @@ "dev": true }, "p-cancelable": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==", "dev": true }, "p-each-series": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, "requires": { "p-reduce": "^1.0.0" } @@ -12509,7 +17807,8 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-is-promise": { "version": "1.1.0", @@ -12518,25 +17817,28 @@ "dev": true }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "requires": { "p-try": "^2.0.0" } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "^2.2.0" } }, "p-reduce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true }, "p-timeout": { "version": "2.0.1", @@ -12550,12 +17852,13 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, "parents": { @@ -12591,6 +17894,171 @@ "jest": "^24.9.0", "mkdirp": "^0.5.1", "npm-run-all": "^4.1.5" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true + }, + "cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "dev": true, + "requires": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "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 + }, + "got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "requires": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + } + }, + "p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "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 + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + } } }, "parse-entities": { @@ -12611,6 +18079,7 @@ "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.0", @@ -12656,23 +18125,31 @@ } }, "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "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", - "json-parse-better-errors": "^1.0.1" + "error-ex": "^1.2.0" } }, + "parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true + }, "parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true }, "parse-path": { "version": "3.0.4", @@ -12694,12 +18171,27 @@ "normalize-url": "^1.9.1", "parse-path": "^3.0.1", "protocols": "^1.4.0" + }, + "dependencies": { + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + } } }, "parse5": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true }, "parseqs": { "version": "0.0.5", @@ -12727,7 +18219,8 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true }, "path-browserify": { "version": "0.0.1", @@ -12738,17 +18231,20 @@ "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + "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=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "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=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-is-inside": { "version": "1.0.2", @@ -12759,12 +18255,14 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true }, "path-platform": { "version": "0.11.15", @@ -12776,6 +18274,7 @@ "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.0" } @@ -12783,7 +18282,8 @@ "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=" + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true }, "path-to-regexp": { "version": "0.1.7", @@ -12791,18 +18291,14 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pathval": { @@ -12821,9 +18317,9 @@ } }, "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -12839,31 +18335,47 @@ "integrity": "sha1-tuDI+plJTZTgURV1gCpZpcFC8og=", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true }, "pidtree": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", - "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", "dev": true }, "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + "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.0" } @@ -12872,27 +18384,41 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, "requires": { "node-modules-regexp": "^1.0.0" } }, "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, "requires": { - "find-up": "^3.0.0" + "find-up": "^4.0.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.0.1", "arr-diff": "^4.0.0", "arr-union": "^3.1.0", "extend-shallow": "^3.0.2" + }, + "dependencies": { + "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" + } + } } }, "pluralize": { @@ -12904,17 +18430,20 @@ "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "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=" + "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=" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true }, "prepend-http": { "version": "1.0.4", @@ -12929,27 +18458,58 @@ "dev": true }, "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.1.0.tgz", + "integrity": "sha512-GmeO1PEYdM+non4BKCj+XsPJjFOJIPnsLewqhDVoqY1xo0yNmDas7tC2XwpMrRAHR3MaE2hPo37deX5OisJ2Wg==", + "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "@jest/types": "^26.1.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "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=" + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "pretty-ms": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.0.tgz", + "integrity": "sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg==", + "dev": true, + "requires": { + "parse-ms": "^2.1.0" + } }, "private": { "version": "0.1.8", @@ -12966,7 +18526,8 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "progress": { "version": "2.0.3", @@ -12974,19 +18535,27 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "promised-io": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/promised-io/-/promised-io-0.3.5.tgz", - "integrity": "sha1-StIXuzZYvKrplGsXqGaOzYUeE1Y=" + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } }, "prompts": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.0.tgz", - "integrity": "sha512-NfbbPPg/74fT7wk2XYQ7hAIp9zJyZp5Fu19iRbORqqy1BhtrkZ0fPafBU+7bmn8ie69DpT0R6QpJIN2oisYjJg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", + "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", "dev": true, "requires": { "kleur": "^3.0.3", - "sisteransi": "^1.0.3" + "sisteransi": "^1.0.4" } }, "property-information": { @@ -13002,14 +18571,20 @@ "dev": true }, "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" + "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -13017,9 +18592,9 @@ "dev": true }, "ps-tree": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.1.tgz", - "integrity": "sha512-kef7fYYSKVqQffmzTMsVcUD1ObNJMp8sNSmHGlGKsZQyL/ht9MZKk86u0Rd1NhpTOAuhqwKCLLpktwkqz+MF8A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", "dev": true, "requires": { "event-stream": "=3.3.4" @@ -13032,9 +18607,9 @@ "dev": true }, "psl": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", - "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, "public-encrypt": { @@ -13049,12 +18624,21 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -13064,6 +18648,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, "requires": { "duplexify": "^3.6.0", "inherits": "^2.0.3", @@ -13074,6 +18659,7 @@ "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.1.0", "once": "^1.3.1" @@ -13084,7 +18670,60 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "puppeteer-core": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.2.1.tgz", + "integrity": "sha512-gLjEOrzwgcnwRH+sm4hS1TBqe2/DN248nRb2hYB7+lZ9kCuLuACNvuzlXILlPAznU3Ob+mEvVEBDcLuFa0zq3g==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.781568", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } }, "q": { "version": "1.5.1", @@ -13131,6 +18770,12 @@ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -13186,104 +18831,78 @@ } }, "react-is": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", - "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "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=", + "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": "^4.0.0", + "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "path-type": "^1.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=", + "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": "^2.0.0", - "read-pkg": "^3.0.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" }, "dependencies": { "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" - } - }, - "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" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "p-try": "^1.0.0" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "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": { - "p-limit": "^1.1.0" + "pinkie-promise": "^2.0.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 } } }, "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==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "requires": { - "core-util-is": "~1.0.0", - "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.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "picomatch": "^2.2.1" } }, "realpath-native": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "dev": true, "requires": { "util.promisify": "^1.0.0" } @@ -13292,10 +18911,20 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, "requires": { "resolve": "^1.1.6" } }, + "recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "dev": true, + "requires": { + "minimatch": "3.0.4" + } + }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -13304,18 +18933,29 @@ "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" + }, + "dependencies": { + "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.0" + } + } } }, "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", "dev": true }, "regenerate-unicode-properties": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", - "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", "dev": true, "requires": { "regenerate": "^1.4.0" @@ -13327,12 +18967,12 @@ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regenerator-transform": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", - "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "private": "^0.1.6" + "@babel/runtime": "^7.8.4" } }, "regex-cache": { @@ -13348,17 +18988,20 @@ "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" } }, "regexp.prototype.flags": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", - "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, "requires": { - "define-properties": "^1.1.2" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "regexpp": { @@ -13368,29 +19011,29 @@ "dev": true }, "regexpu-core": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", - "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", "dev": true, "requires": { "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.1.0", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.1.0" + "unicode-match-property-value-ecmascript": "^1.2.0" } }, "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", "dev": true }, "regjsparser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", - "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -13515,15 +19158,25 @@ "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.5", "is-utf8": "^0.2.1" + }, + "dependencies": { + "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 + } } }, "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.0", @@ -13533,17 +19186,20 @@ "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=" + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "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=" + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true }, "repeating": { "version": "2.0.1", @@ -13557,12 +19213,14 @@ "replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true }, "replace-homedir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, "requires": { "homedir-polyfill": "^1.0.1", "is-absolute": "^1.0.0", @@ -13578,12 +19236,30 @@ "escape-string-regexp": "^1.0.3", "object-assign": "^4.0.1", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -13592,7 +19268,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -13602,7 +19278,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, @@ -13612,35 +19288,31 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true } } }, - "request-promise": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", - "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", - "dev": true, - "requires": { - "bluebird": "^3.5.0", - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, "requires": { - "lodash": "^4.17.15" + "lodash": "^4.17.19" } }, "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, "requires": { - "request-promise-core": "1.1.3", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" } @@ -13648,12 +19320,14 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "require-uncached": { "version": "1.0.3", @@ -13680,18 +19354,25 @@ "dev": true }, "resolve": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", - "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" } }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==", + "dev": true + }, "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, "requires": { "resolve-from": "^3.0.0" }, @@ -13699,7 +19380,8 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true } } }, @@ -13707,6 +19389,7 @@ "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.0", "global-modules": "^1.0.0" @@ -13722,6 +19405,7 @@ "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" } @@ -13729,31 +19413,50 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true }, "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dev": true, + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "resq": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.7.1.tgz", + "integrity": "sha512-09u9Q5SAuJfAW5UoVAmvRtLvCOMaKP+djiixTXsZvPaojGKhuvc0Nfvp84U1rIfopJWEOXi5ywpCFwCk7mj8Xw==", "dev": true, "requires": { - "lowercase-keys": "^1.0.0" + "fast-deep-equal": "^2.0.1" + }, + "dependencies": { + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "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=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "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==" + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true }, "rfdc": { "version": "1.1.4", @@ -13762,9 +19465,9 @@ "dev": true }, "rgb2hex": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", - "integrity": "sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", + "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", "dev": true }, "right-align": { @@ -13777,11 +19480,12 @@ } }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "^7.0.5" } }, "ripemd160": { @@ -13797,16 +19501,14 @@ "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "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" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true }, "rx-lite": { "version": "4.0.8", @@ -13823,6 +19525,15 @@ "rx-lite": "*" } }, + "rxjs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -13838,6 +19549,7 @@ "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.10" } @@ -13857,6 +19569,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, "requires": { "@cnakazawa/watch": "^1.0.3", "anymatch": "^2.0.0", @@ -13867,31 +19580,180 @@ "micromatch": "^3.1.4", "minimist": "^1.1.1", "walker": "~1.0.5" + }, + "dependencies": { + "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.4", + "normalize-path": "^2.1.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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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.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-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.0.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.5" + } + } + } + }, + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "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" + } + } } }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "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=", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", "dev": true, "requires": { - "ajv": "^5.0.0" + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + } } }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true }, "semver-greatest-satisfied-range": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, "requires": { "sver-compat": "^1.5.0" } @@ -13923,6 +19785,29 @@ } } }, + "serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "requires": { + "type-fest": "^0.13.1" + }, + "dependencies": { + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz", + "integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==", + "dev": true + }, "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", @@ -13972,12 +19857,14 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -13989,6 +19876,7 @@ "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.0" } @@ -14020,6 +19908,7 @@ "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" } @@ -14027,7 +19916,8 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "shell-quote": { "version": "1.7.2", @@ -14036,9 +19926,9 @@ "dev": true }, "shelljs": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", - "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "dev": true, "requires": { "glob": "^7.0.0", @@ -14049,28 +19939,25 @@ "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "dev": true, "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" } }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, "sinon": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", @@ -14095,15 +19982,15 @@ } }, "sisteransi": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.4.tgz", - "integrity": "sha512-/ekMoM4NJ59ivGSfKapeG+FWtrmWvA1p6FBZwXrqojw90vJu8lBmrTxCMuBCydKtkaUe2zt4PlxeTKpjwMbyig==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "slice-ansi": { @@ -14113,12 +20000,21 @@ "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 + } } }, "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.1", "debug": "^2.2.0", @@ -14134,6 +20030,7 @@ "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.0" } @@ -14142,6 +20039,7 @@ "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.0" } @@ -14152,6 +20050,7 @@ "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.0", @@ -14162,6 +20061,7 @@ "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.0" } @@ -14170,6 +20070,7 @@ "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.0" } @@ -14178,6 +20079,7 @@ "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.0" } @@ -14186,6 +20088,7 @@ "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", @@ -14198,14 +20101,22 @@ "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.0" }, "dependencies": { + "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 + }, "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.5" } @@ -14215,7 +20126,7 @@ "socket.io": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", - "integrity": "sha1-oGnF/qvuPmshSnW0DOBlLhz7mYA=", + "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", "dev": true, "requires": { "debug": "~3.1.0", @@ -14229,7 +20140,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -14246,7 +20157,7 @@ "socket.io-client": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", - "integrity": "sha1-3LOBA0NqtFeN2wJmOK4vIbYjZx8=", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", "dev": true, "requires": { "backo2": "1.0.2", @@ -14274,7 +20185,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -14284,7 +20195,7 @@ }, "socket.io-parser": { "version": "3.2.0", - "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", "dev": true, "requires": { @@ -14302,7 +20213,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -14334,14 +20245,16 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true }, "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, "requires": { - "atob": "^2.1.1", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -14360,37 +20273,42 @@ "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=" + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true }, "space-separated-tokens": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz", - "integrity": "sha512-UyhMSmeIqZrQn2UdjYpxEkwY9JUrn8pP+7L4f91zRzOQuI8MF1FGLfYU9DKCYeLdo7LXMxwrX5zKFy7eeeVHuA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", "dev": true }, "sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true }, "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "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==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -14399,11 +20317,12 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true }, "split": { "version": "0.3.3", - "resolved": "http://registry.npmjs.org/split/-/split-0.3.3.tgz", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { @@ -14414,6 +20333,7 @@ "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.0" } @@ -14428,6 +20348,7 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -14443,23 +20364,37 @@ "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true }, "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } }, "state-toggle": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", - "integrity": "sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", "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" @@ -14469,6 +20404,7 @@ "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.0" } @@ -14483,7 +20419,8 @@ "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true }, "stream-array": { "version": "1.1.2", @@ -14531,11 +20468,34 @@ "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, + "stream-buffers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", + "dev": true + }, "stream-combiner": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { @@ -14550,12 +20510,30 @@ "requires": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==" + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true }, "stream-http": { "version": "2.8.3", @@ -14568,25 +20546,53 @@ "readable-stream": "^2.3.6", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true }, "streamroller": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", + "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", "dev": true, "requires": { - "date-format": "^1.2.0", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "readable-stream": "^2.3.0" + "async": "^2.6.2", + "date-format": "^2.0.0", + "debug": "^3.2.6", + "fs-extra": "^7.0.1", + "lodash": "^4.17.14" }, "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -14596,11 +20602,37 @@ "ms": "^2.1.1" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true } } }, @@ -14614,6 +20646,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, "requires": { "astral-regex": "^1.0.0", "strip-ansi": "^4.0.0" @@ -14622,12 +20655,14 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "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" } @@ -14639,65 +20674,53 @@ "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "string.prototype.padend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", - "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", + "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.4.3", - "function-bind": "^1.0.2" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, - "string.prototype.trimleft": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", - "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" } }, - "string.prototype.trimright": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", - "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" } }, "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==", + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -14715,17 +20738,22 @@ } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.0" } }, "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "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.0" + } }, "strip-bom-string": { "version": "1.0.0", @@ -14736,7 +20764,8 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-indent": { "version": "1.0.1", @@ -14748,9 +20777,9 @@ } }, "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=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, "subarg": { @@ -14762,10 +20791,17 @@ "minimist": "^1.1.0" } }, + "suffix": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", + "integrity": "sha1-zFgjFkag7xEC95R47zqSSP2chC8=", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -14774,6 +20810,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, "requires": { "es6-iterator": "^2.0.1", "es6-symbol": "^3.1.1" @@ -14782,7 +20819,8 @@ "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true }, "table": { "version": "4.0.2", @@ -14798,12 +20836,24 @@ "string-width": "^2.1.1" }, "dependencies": { + "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 + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "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 + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -14831,19 +20881,29 @@ "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", "dev": true }, + "tar-fs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", + "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", + "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", "dev": true, "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" } }, "temp-fs": { @@ -14853,17 +20913,6 @@ "dev": true, "requires": { "rimraf": "~2.5.2" - }, - "dependencies": { - "rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - } } }, "ternary-stream": { @@ -14886,6 +20935,21 @@ "requires": { "readable-stream": "^2.0.1" } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } } } }, @@ -14893,6 +20957,7 @@ "version": "5.2.3", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, "requires": { "glob": "^7.1.3", "minimatch": "^3.0.4", @@ -14900,22 +20965,106 @@ "require-main-filename": "^2.0.0" }, "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "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.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.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.1" + } + }, + "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-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" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "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.3.2", + "path-type": "^3.0.0" + } + }, "read-pkg-up": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, "requires": { "find-up": "^3.0.0", "read-pkg": "^3.0.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -14931,7 +21080,8 @@ "throat": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=" + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "dev": true }, "through": { "version": "2.3.8", @@ -14943,15 +21093,34 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "through2-filter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -14960,7 +21129,8 @@ "time-stamp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true }, "timed-out": { "version": "4.0.1", @@ -14987,10 +21157,10 @@ "next-tick": "1" } }, - "timespan": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz", - "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=" + "tiny-hashes": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tiny-hashes/-/tiny-hashes-1.0.1.tgz", + "integrity": "sha512-knIN5zj4fl7kW4EBU5sLP20DWUvi/rVouvJezV0UAym2DkQaqm365Nyc8F3QEiOvunNDMxR8UhcXd1d5g+Wg1g==" }, "tiny-lr": { "version": "1.1.1", @@ -15035,12 +21205,14 @@ "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true }, "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" @@ -15058,29 +21230,32 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "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.0.2" }, "dependencies": { + "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 + }, "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.5" } @@ -15091,6 +21266,7 @@ "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", @@ -15099,18 +21275,19 @@ } }, "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=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "^7.0.0" } }, "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" } @@ -15121,29 +21298,30 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, "requires": { "punycode": "^2.1.0" } }, + "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", @@ -15151,9 +21329,9 @@ "dev": true }, "trim-lines": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.2.tgz", - "integrity": "sha512-3GOuyNeTqk3FAqc3jOJtw7FTjYl94XBR5aD9QnDbK/T4CA9sW/J0l9RoaRPE9wyPP7NF331qnHnvJFBJ+IDkmQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.3.tgz", + "integrity": "sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA==", "dev": true }, "trim-newlines": { @@ -15169,20 +21347,15 @@ "dev": true }, "trim-trailing-lines": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz", - "integrity": "sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", + "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", "dev": true }, - "triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" - }, "trough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.4.tgz", - "integrity": "sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "dev": true }, "tryer": { @@ -15191,38 +21364,41 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "dev": true }, - "ts-jest": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.2.0.tgz", - "integrity": "sha512-Yc+HLyldlIC9iIK8xEN7tV960Or56N49MDP7hubCZUeI7EbIOTsas6rXCMB4kQjLACJ7eDOF4xWEO5qumpKsag==", + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "json5": "2.x", - "lodash.memoize": "4.x", - "make-error": "1.x", - "mkdirp": "0.x", - "resolve": "1.x", - "semver": "^5.5", - "yargs-parser": "10.x" + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, "requires": { - "camelcase": "^4.1.0" + "minimist": "^1.2.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -15233,6 +21409,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -15240,17 +21417,20 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": 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" } @@ -15261,6 +21441,12 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -15273,12 +21459,8 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "typescript": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", - "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==" + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true }, "typescript-compare": { "version": "0.0.2", @@ -15301,22 +21483,17 @@ "typescript-compare": "^0.0.2" } }, + "ua-parser-js": { + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", + "dev": true + }, "uglify-js": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz", - "integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==", - "dev": true, - "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", + "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", + "dev": true }, "uglify-to-browserify": { "version": "1.0.2", @@ -15389,15 +21566,27 @@ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "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=" + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true }, "undertaker": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "dev": true, "requires": { "arr-flatten": "^1.0.1", "arr-map": "^2.0.0", @@ -15413,16 +21602,17 @@ "undertaker-registry": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=" + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true }, "unherit": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.2.tgz", - "integrity": "sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "xtend": "^4.0.1" + "inherits": "^2.0.0", + "xtend": "^4.0.0" } }, "unicode-canonical-property-names-ecmascript": { @@ -15442,15 +21632,15 @@ } }, "unicode-match-property-value-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", - "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", "dev": true }, "unicode-property-aliases-ecmascript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", - "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", "dev": true }, "unified": { @@ -15471,6 +21661,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -15482,6 +21673,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -15509,9 +21701,9 @@ "dev": true }, "unist-util-position": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.0.4.tgz", - "integrity": "sha512-tWvIbV8goayTjobxDIr4zVTyG+Q7ragMSMeKC3xnPl9xzIc0+she8mxXLM3JVNDDsfARPbCd3XdzkyLdo7fF3g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", + "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==", "dev": true }, "unist-util-remove-position": { @@ -15547,6 +21739,12 @@ "unist-util-is": "^3.0.0" } }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -15556,6 +21754,7 @@ "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.0" @@ -15565,6 +21764,7 @@ "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.3", "has-values": "^0.1.4", @@ -15575,6 +21775,7 @@ "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" } @@ -15584,19 +21785,62 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "unzipper": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.9.15.tgz", + "integrity": "sha512-2aaUvO4RAeHDvOCuEtth7jrHFaCKTSXPqUkXwADaLBzGbgZGzUDccoEdJ5lW+3RmfpOZYNx0Rw6F6PUzM6caIA==", + "dev": true, + "requires": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + }, + "dependencies": { + "bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } } } }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -15604,7 +21848,8 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true }, "url": { "version": "0.11.0", @@ -15666,12 +21911,13 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "useragent": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha1-IX+UOtVAyyEoZYqyP8lg9qiMmXI=", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", "dev": true, "requires": { "lru-cache": "4.1.x", @@ -15690,15 +21936,19 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" } }, "utils-merge": { @@ -15707,14 +21957,16 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", + "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==", + "dev": true }, "v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, "requires": { "homedir-polyfill": "^1.0.1" } @@ -15723,6 +21975,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -15731,7 +21984,8 @@ "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=" + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true }, "vary": { "version": "1.1.2", @@ -15742,6 +21996,7 @@ "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", @@ -15758,6 +22013,14 @@ "replace-ext": "1.0.0", "unist-util-stringify-position": "^1.0.0", "vfile-message": "^1.0.0" + }, + "dependencies": { + "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 + } } }, "vfile-location": { @@ -15788,6 +22051,12 @@ "vfile-statistics": "^1.1.0" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", @@ -15814,6 +22083,15 @@ "strip-ansi": "^3.0.0" } }, + "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.0.0" + } + }, "supports-color": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", @@ -15826,21 +22104,22 @@ } }, "vfile-sort": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-2.2.1.tgz", - "integrity": "sha512-5dt7xEhC44h0uRQKhbM2JAe0z/naHphIZlMOygtMBM9Nn0pZdaX5fshhwWit9wvsuP8t/wp43nTDRRErO1WK8g==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-2.2.2.tgz", + "integrity": "sha512-tAyUqD2R1l/7Rn7ixdGkhXLD3zsg+XLAeUDUhXearjfIcpL1Hcsj5hHpCoy/gvfK/Ws61+e972fm0F7up7hfYA==", "dev": true }, "vfile-statistics": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-1.1.3.tgz", - "integrity": "sha512-CstaK/ebTz1W3Qp41Bt9Lj/2DmumFsCwC2sKahDNSPh0mPh7/UyMLCoU8ZBX34CRU0d61B4W41yIFsV0NKMZeA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-1.1.4.tgz", + "integrity": "sha512-lXhElVO0Rq3frgPvFBwahmed3X03vjPF8OcjKMy8+F1xU/3Q3QU3tKEDp743SFtb74PdF0UWpxPvtOP0GCLheA==", "dev": true }, "vinyl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, "requires": { "clone": "^2.1.1", "clone-buffer": "^1.0.0", @@ -15848,12 +22127,21 @@ "cloneable-readable": "^1.0.0", "remove-trailing-separator": "^1.0.1", "replace-ext": "^1.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + } } }, "vinyl-fs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, "requires": { "fs-mkdirp-stream": "^1.0.0", "glob-stream": "^6.1.0", @@ -15872,12 +22160,30 @@ "value-or-function": "^3.0.0", "vinyl": "^2.0.0", "vinyl-sourcemap": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + } } }, "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.0", @@ -15892,6 +22198,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, "requires": { "remove-trailing-separator": "^1.0.1" } @@ -15902,6 +22209,7 @@ "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.1" } @@ -15919,11 +22227,12 @@ "dev": true }, "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, "requires": { - "browser-process-hrtime": "^0.1.2" + "browser-process-hrtime": "^1.0.0" } }, "walk": { @@ -15939,128 +22248,334 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, "requires": { "makeerror": "1.0.x" } }, "watchpack": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", - "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", + "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", "dev": true, "requires": { - "chokidar": "^2.0.2", + "chokidar": "^3.4.0", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - } - }, - "wdio-browserstack-service": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/wdio-browserstack-service/-/wdio-browserstack-service-0.1.19.tgz", - "integrity": "sha512-ZAq20McWrQy80FQst+4cn1l5WRP9u+9DOKif2TarxYFzw/EmhdNg9TFcXBT5dxH+LcP5v47v7mXMmsO7B3+92Q==", - "dev": true, - "requires": { - "browserstack-local": "^1.3.7", - "request": "^2.81.0", - "request-promise": "^4.2.1" - } - }, - "wdio-concise-reporter": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/wdio-concise-reporter/-/wdio-concise-reporter-0.1.2.tgz", - "integrity": "sha1-+kmA768kszWfHqQz73thYxVDphQ=", - "dev": true - }, - "wdio-dot-reporter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/wdio-dot-reporter/-/wdio-dot-reporter-0.0.10.tgz", - "integrity": "sha512-A0TCk2JdZEn3M1DSG9YYbNRcGdx/YRw19lTiRpgwzH4qqWkO/oRDZRmi3Snn4L2j54KKTfPalBhlOtc8fojVgg==", - "dev": true - }, - "wdio-mocha-framework": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/wdio-mocha-framework/-/wdio-mocha-framework-0.6.4.tgz", - "integrity": "sha512-GZsXwoW60/fkkfqZJR/ZAdiALaM+hW+BbnTT9x214qPR4Pe5XeyYxhJNEdyf0dNI9625cMdkyZYaWoFHN5zDcA==", - "dev": true, - "requires": { - "babel-runtime": "^6.23.0", - "mocha": "^5.2.0", - "wdio-sync": "0.7.3" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" } }, - "wdio-spec-reporter": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/wdio-spec-reporter/-/wdio-spec-reporter-0.1.5.tgz", - "integrity": "sha512-MqvgTow8hFwhFT47q67JwyJyeynKodGRQCxF7ijKPGfsaG1NLssbXYc0JhiL7SiAyxnQxII0UxzTCd3I6sEdkg==", - "dev": true, - "requires": { - "babel-runtime": "~6.26.0", - "chalk": "^2.3.0", - "humanize-duration": "~3.15.0" - } - }, - "wdio-sync": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.3.tgz", - "integrity": "sha512-ukASSHOQmOxaz5HTILR0jykqlHBtAPsBpMtwhpiG0aW9uc7SO7PF+E5LhVvTG4ypAh+UGmY3rTjohOsqDr39jw==", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "fibers": "^3.0.0", - "object.assign": "^4.0.3" - } - }, - "webdriverio": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.14.4.tgz", - "integrity": "sha512-Knp2vzuzP5c5ybgLu+zTwy/l1Gh0bRP4zAr8NWcrStbuomm9Krn9oRF0rZucT6AyORpXinETzmeowFwIoo7mNA==", + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", "dev": true, + "optional": true, "requires": { - "archiver": "~2.1.0", - "babel-runtime": "^6.26.0", - "css-parse": "^2.0.0", - "css-value": "~0.0.1", - "deepmerge": "~2.0.1", - "ejs": "~2.5.6", - "gaze": "~1.1.2", - "glob": "~7.1.1", - "grapheme-splitter": "^1.0.2", - "inquirer": "~3.3.0", - "json-stringify-safe": "~5.0.1", - "mkdirp": "~0.5.1", - "npm-install-package": "~2.1.0", - "optimist": "~0.6.1", - "q": "~1.5.0", - "request": "^2.83.0", - "rgb2hex": "^0.1.9", - "safe-buffer": "~5.1.1", - "supports-color": "~5.0.0", - "url": "~0.11.0", - "wdio-dot-reporter": "~0.0.8", - "wgxpath": "~1.0.0" + "chokidar": "^2.1.8" }, "dependencies": { - "has-flag": { + "anymatch": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } }, - "supports-color": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.0.1.tgz", - "integrity": "sha512-7FQGOlSQ+AQxBNXJpVDj8efTA/FtyB5wcNE1omXXJ0cq6jm1jjDwuROlYDbnzHqdNPqliWFhcioCWSyav+xBnA==", + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, + "optional": true, "requires": { - "has-flag": "^2.0.0" + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.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, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.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, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "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, + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "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, + "optional": true, + "requires": { + "binary-extensions": "^1.0.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, + "optional": 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, + "optional": true, + "requires": { + "kind-of": "^3.0.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, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "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, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "optional": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webdriver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.3.0.tgz", + "integrity": "sha512-osHp5DX8eQ76Sy6/UYoECmDnUXwLhy5tlBeJh86er7S04FLAlmEqCvYXgxTSb8YtDjh9Xt9gP768RGhxR7ik5A==", + "dev": true, + "requires": { + "@wdio/config": "6.1.14", + "@wdio/logger": "6.0.16", + "@wdio/protocols": "6.3.0", + "@wdio/utils": "6.3.0", + "got": "^11.0.2", + "lodash.merge": "^4.6.1" + } + }, + "webdriverio": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.3.4.tgz", + "integrity": "sha512-/53xQEituEFTaJtZMgg5Uz3GXY1Otqyry0LA8dYLYUNkTK0yCa26DL4ycDnWE0i9wEYNFX6YHCgiqTJjHEjKAg==", + "dev": true, + "requires": { + "@types/puppeteer": "^3.0.1", + "@wdio/config": "6.1.14", + "@wdio/logger": "6.0.16", + "@wdio/repl": "6.3.0", + "@wdio/utils": "6.3.0", + "archiver": "^4.0.1", + "atob": "^2.1.2", + "css-value": "^0.0.1", + "devtools": "6.3.4", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^5.1.0", + "resq": "^1.6.0", + "rgb2hex": "^0.2.0", + "serialize-error": "^7.0.0", + "webdriver": "6.3.0" + } + }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true }, "webpack": { "version": "3.12.0", @@ -16093,27 +22608,21 @@ }, "dependencies": { "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, - "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true - }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "async": { @@ -16155,12 +22664,6 @@ } } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -16191,12 +22694,6 @@ "number-is-nan": "^1.0.0" } }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "json5": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", @@ -16215,6 +22712,28 @@ "strip-bom": "^3.0.0" } }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -16225,6 +22744,15 @@ "path-exists": "^3.0.0" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -16249,14 +22777,11 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "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.2.0" - } + "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-type": { "version": "2.0.0", @@ -16267,12 +22792,6 @@ "pify": "^2.0.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", @@ -16310,6 +22829,12 @@ "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 + }, "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", @@ -16327,6 +22852,21 @@ } } }, + "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.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "supports-color": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", @@ -16398,13 +22938,13 @@ } }, "webpack-bundle-analyzer": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz", - "integrity": "sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.8.0.tgz", + "integrity": "sha512-PODQhAYVEourCcOuU+NiYI7WdR8QyELZGgPvB1y2tjbUpbmcQOt5Q7jEK+ttd5se0KSBKD9SXHCEozS++Wllmw==", "dev": true, "requires": { - "acorn": "^6.0.7", - "acorn-walk": "^6.1.1", + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1", "bfj": "^6.1.1", "chalk": "^2.4.1", "commander": "^2.18.0", @@ -16419,9 +22959,21 @@ }, "dependencies": { "acorn": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", - "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "dev": true + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "ejs": { @@ -16430,6 +22982,15 @@ "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", "dev": true }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ws": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", @@ -16470,7 +23031,7 @@ }, "webpack-dev-middleware": { "version": "2.0.6", - "resolved": "http://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz", "integrity": "sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==", "dev": true, "requires": { @@ -16484,9 +23045,9 @@ }, "dependencies": { "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", "dev": true } } @@ -16501,6 +23062,23 @@ "log-symbols": "^2.1.0", "loglevelnext": "^1.0.1", "uuid": "^3.1.0" + }, + "dependencies": { + "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.0.1" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "webpack-sources": { @@ -16567,12 +23145,24 @@ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", "dev": true }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, "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-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", @@ -16647,12 +23237,6 @@ "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", @@ -16671,6 +23255,12 @@ "sha.js": "2.2.6" } }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, "enhanced-resolve": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", @@ -16705,15 +23295,6 @@ "is-posix-bracket": "^0.1.0" } }, - "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.1.0" - } - }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", @@ -16724,16 +23305,37 @@ } }, "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.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.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" } }, "glob-parent": { @@ -16763,6 +23365,74 @@ "integrity": "sha1-/s16GOfOXKar+5U+H4YhOknxYls=", "dev": true }, + "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.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "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.0.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-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.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "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" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", @@ -16779,23 +23449,14 @@ } }, "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "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.0.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" - } - }, "json5": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", @@ -16854,6 +23515,15 @@ "regex-cache": "^0.4.2" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "node-libs-browser": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.7.0.tgz", @@ -16883,6 +23553,14 @@ "url": "^0.11.0", "util": "^0.10.3", "vm-browserify": "0.0.4" + }, + "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 + } } }, "normalize-path": { @@ -16918,6 +23596,230 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "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.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "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 + }, + "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 + }, + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "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.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" + }, + "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.0" + } + }, + "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.0" + } + }, + "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.0.0" + } + }, + "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 + } + } + }, + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^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.0" + } + }, + "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.0" + } + } + } + }, + "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.0.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.5" + } + } + } + }, + "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.0.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.5" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, "replace-ext": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", @@ -16936,12 +23838,6 @@ "integrity": "sha1-F93t3F9yL7ZlAWWIlUYZd4ZzFbo=", "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", @@ -16957,6 +23853,16 @@ "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", "dev": true }, + "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" + } + }, "uglify-js": { "version": "2.7.5", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.5.tgz", @@ -17069,32 +23975,27 @@ } }, "websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dev": true, "requires": { - "http-parser-js": ">=0.4.0 <0.4.11", + "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "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=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, "whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, "requires": { "iconv-lite": "0.4.24" } @@ -17102,12 +24003,14 @@ "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true }, "whatwg-url": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, "requires": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -17118,14 +24021,97 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", + "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "dev": true, + "requires": { + "is-bigint": "^1.0.0", + "is-boolean-object": "^1.0.0", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-symbol": "^1.0.2" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "which-typed-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", + "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "es-abstract": "^1.17.5", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "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 + }, + "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" + } + } + } }, "window-size": { "version": "0.1.0", @@ -17140,44 +24126,60 @@ "dev": true }, "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "color-name": "~1.1.4" } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "wrench": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.3.9.tgz", - "integrity": "sha1-bxPsNRRTF+spLKX2UxORskQRFBE=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write": { "version": "0.2.1", @@ -17186,12 +24188,24 @@ "dev": true, "requires": { "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } } }, "write-file-atomic": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -17199,12 +24213,10 @@ } }, "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "requires": { - "async-limiter": "~1.0.0" - } + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true }, "x-is-string": { "version": "0.1.0", @@ -17215,7 +24227,8 @@ "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true }, "xmlhttprequest-ssl": { "version": "1.5.5", @@ -17226,12 +24239,14 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true }, "yallist": { "version": "2.1.2", @@ -17246,14 +24261,213 @@ "dev": true }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.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 + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yarn-install": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yarn-install/-/yarn-install-1.0.0.tgz", + "integrity": "sha1-V/RQULgu/VcYKzlzxUqgXLXSUjA=", + "dev": true, + "requires": { + "cac": "^3.0.3", + "chalk": "^1.1.3", + "cross-spawn": "^4.0.2" + }, + "dependencies": { + "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 + }, + "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.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "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.0.0" + } + }, + "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 + } + } + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", @@ -17261,15 +24475,14 @@ "dev": true }, "zip-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", - "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-3.0.1.tgz", + "integrity": "sha512-r+JdDipt93ttDjsOVPU5zaq5bAyY+3H19bDrThkvuVxC0xMQzU1PJcS6D+KrP3u96gH9XLomcHPb+2skoDjulQ==", "dev": true, "requires": { - "archiver-utils": "^1.3.0", - "compress-commons": "^1.2.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0" + "archiver-utils": "^2.1.0", + "compress-commons": "^3.0.0", + "readable-stream": "^3.6.0" } } } diff --git a/package.json b/package.json old mode 100755 new mode 100644 index 09008fe44ae..3cb04dc6f8a --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "3.1.0-pre", + "version": "4.4.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -24,16 +24,26 @@ "node": ">=8.9.0" }, "devDependencies": { - "@babel/core": "^7.2.2", - "@babel/preset-env": "^7.2.3", + "@babel/core": "^7.8.4", + "@babel/preset-env": "^7.8.4", + "@jsdevtools/coverage-istanbul-loader": "^3.0.3", + "@wdio/browserstack-service": "^6.1.4", + "@wdio/cli": "^6.1.5", + "@wdio/concise-reporter": "^6.1.5", + "@wdio/local-runner": "^6.1.7", + "@wdio/mocha-framework": "^6.1.6", + "@wdio/spec-reporter": "^6.1.5", + "@wdio/sync": "^6.1.5", "ajv": "5.5.2", "babel-loader": "^8.0.5", + "body-parser": "^1.19.0", "chai": "^4.2.0", - "coveralls": "^3.0.2", + "coveralls": "^3.1.0", + "deep-equal": "^2.0.3", "documentation": "^5.2.2", - "es5-shim": "^4.5.2", + "es5-shim": "^4.5.14", "eslint-config-standard": "^10.2.1", - "eslint-plugin-import": "^2.2.0", + "eslint-plugin-import": "^2.20.2", "eslint-plugin-node": "^5.1.0", "eslint-plugin-prebid": "file:./plugins/eslint", "eslint-plugin-promise": "^3.5.0", @@ -57,15 +67,15 @@ "gulp-util": "^3.0.0", "is-docker": "^1.1.0", "istanbul": "^0.4.5", - "istanbul-instrumenter-loader": "^3.0.0", - "karma": "^3.1.3", + "karma": "^4.0.0", "karma-babel-preprocessor": "^6.0.1", - "karma-browserstack-launcher": "^1.3.0", + "karma-browserstack-launcher": "1.4.0", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^2.2.0", + "karma-coverage": "^2.0.1", "karma-coverage-istanbul-reporter": "^1.3.0", "karma-es5-shim": "^0.0.4", - "karma-firefox-launcher": "^1.0.1", + "karma-firefox-launcher": "^1.3.0", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.5", @@ -78,32 +88,30 @@ "karma-webpack": "^3.0.5", "lodash": "^4.17.4", "mocha": "^5.0.0", + "morgan": "^1.10.0", "opn": "^5.4.0", "resolve-from": "^5.0.0", "sinon": "^4.1.3", "through2": "^2.0.3", "url-parse": "^1.0.5", - "wdio-browserstack-service": "^0.1.17", - "wdio-concise-reporter": "^0.1.2", - "wdio-mocha-framework": "^0.6.3", - "wdio-spec-reporter": "^0.1.5", - "webdriverio": "^4.13.2", + "webdriverio": "^6.1.5", "webpack": "^3.0.0", - "webpack-bundle-analyzer": "^3.3.2", + "webpack-bundle-analyzer": "^3.8.0", "webpack-stream": "^3.2.0", "yargs": "^1.3.1" }, "dependencies": { "babel-plugin-transform-object-assign": "^6.22.0", - "core-js": "^2.4.1", - "criteo-direct-rsa-validate": "^1.0.0", - "crypto-js": "^3.1.9-1", - "deep-equal": "^1.0.1", + "core-js": "^3.0.0", + "core-js-pure": "^3.6.5", + "criteo-direct-rsa-validate": "^1.1.0", + "crypto-js": "^3.3.0", "dlv": "1.1.3", "dset": "2.0.1", "express": "^4.15.4", - "fun-hooks": "^0.9.5", + "fun-hooks": "^0.9.9", "jsencrypt": "^3.0.0-rc.1", - "just-clone": "^1.0.2" + "just-clone": "^1.0.2", + "live-connect-js": "1.1.10" } } diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index 193455a47fd..a39bf9b26d5 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -31,6 +31,14 @@ function flagErrors(context, node, importPath) { ) { context.report(node, `import "${importPath}" cannot require module entry point`); } + + // don't allow extension-less local imports + if ( + !importPath.match(/^\w+/) && + !['.js', '.json'].includes(path.extname(absImportPath)) + ) { + context.report(node, `import "${importPath}" should include extension as .js or .json`); + } } } diff --git a/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index 16d56725e39..f3297412a35 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -1,8 +1,8 @@ -import CONSTANTS from './constants'; -import { ajax } from './ajax'; +import CONSTANTS from './constants.json'; +import { ajax } from './ajax.js'; -const events = require('./events'); -const utils = require('./utils'); +const events = require('./events.js'); +const utils = require('./utils.js'); const { EVENTS: { diff --git a/src/Renderer.js b/src/Renderer.js index f35e9fe354d..85bcbb383e8 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -1,6 +1,6 @@ -import { loadExternalScript } from './adloader'; -import * as utils from './utils'; -import find from 'core-js/library/fn/array/find'; +import { loadExternalScript } from './adloader.js'; +import * as utils from './utils.js'; +import find from 'core-js-pure/features/array/find.js'; const moduleCode = 'outstream'; /** @@ -37,12 +37,21 @@ export function Renderer(options) { this.process(); }); - if (!isRendererDefinedOnAdUnit(adUnitCode)) { - // we expect to load a renderer url once only so cache the request to load script - loadExternalScript(url, moduleCode, this.callback); - } else { - utils.logWarn(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${adUnitCode}`); - } + // use a function, not an arrow, in order to be able to pass "arguments" through + this.render = function () { + if (!isRendererDefinedOnAdUnit(adUnitCode)) { + // we expect to load a renderer url once only so cache the request to load script + loadExternalScript(url, moduleCode, this.callback); + } else { + utils.logWarn(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${adUnitCode}`); + } + + if (this._render) { + this._render.apply(this, arguments) // _render is expected to use push as appropriate + } else { + utils.logWarn(`No render function was provided, please use .setRender on the renderer`); + } + }.bind(this) // bind the function to this object to avoid 'this' errors } Renderer.install = function({ url, config, id, callback, loaded, adUnitCode }) { @@ -54,7 +63,7 @@ Renderer.prototype.getConfig = function() { }; Renderer.prototype.setRender = function(fn) { - this.render = fn; + this._render = fn; }; Renderer.prototype.setEventHandlers = function(handlers) { diff --git a/src/adServerManager.js b/src/adServerManager.js index 11ce087903d..af8fe34920e 100644 --- a/src/adServerManager.js +++ b/src/adServerManager.js @@ -1,5 +1,5 @@ -import { getGlobal } from './prebidGlobal'; -import { logWarn } from './utils'; +import { getGlobal } from './prebidGlobal.js'; +import { logWarn } from './utils.js'; const prebid = getGlobal(); diff --git a/src/adUnits.js b/src/adUnits.js index aa88cf6eac5..cdac649c5b8 100644 --- a/src/adUnits.js +++ b/src/adUnits.js @@ -1,4 +1,4 @@ -import { deepAccess } from './utils'; +import { deepAccess } from './utils.js'; let adUnits = {}; diff --git a/src/adapterManager.js b/src/adapterManager.js index f4b63a4d58b..5124ae99694 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -1,19 +1,20 @@ /** @module adaptermanger */ -import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp, getBidderRequest, bind } from './utils'; -import { getLabels, resolveStatus } from './sizeMapping'; -import { processNativeAdUnitParams, nativeAdapters } from './native'; -import { newBidder } from './adapters/bidderFactory'; -import { ajaxBuilder } from './ajax'; -import { config, RANDOM } from './config'; -import includes from 'core-js/library/fn/array/includes'; -import find from 'core-js/library/fn/array/find'; -import { adunitCounter } from './adUnits'; -import { getRefererInfo } from './refererDetection'; +import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp, getBidderRequest, bind } from './utils.js'; +import { getLabels, resolveStatus } from './sizeMapping.js'; +import { processNativeAdUnitParams, nativeAdapters } from './native.js'; +import { newBidder } from './adapters/bidderFactory.js'; +import { ajaxBuilder } from './ajax.js'; +import { config, RANDOM } from './config.js'; +import { hook } from './hook.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import find from 'core-js-pure/features/array/find.js'; +import { adunitCounter } from './adUnits.js'; +import { getRefererInfo } from './refererDetection.js'; var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); -var events = require('./events'); +var events = require('./events.js'); let s2sTestingModule; // store s2sTesting module if it's loaded let adapterManager = {}; @@ -65,8 +66,10 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) } bid = Object.assign({}, bid, getDefinedParams(adUnit, [ + 'fpd', 'mediaType', - 'renderer' + 'renderer', + 'storedAuctionResponse' ])); let { @@ -113,6 +116,8 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) }, []).reduce(flatten, []).filter(val => val !== ''); } +const hookedGetBids = hook('sync', getBids, 'getBids'); + function getAdUnitCopyForPrebidServer(adUnits) { let adaptersServerSide = _s2sConfig.bidders; let adUnitsCopy = utils.deepClone(adUnits); @@ -172,7 +177,13 @@ export let uspDataHandler = { } }; -adapterManager.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, labels) { +adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, auctionId, cbTimeout, labels) { + /** + * emit and pass adunits for external modification + * @see {@link https://github.com/prebid/Prebid.js/issues/4149|Issue} + */ + events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); + let bidRequests = []; let bidderCodes = getBidderCodes(adUnits); @@ -220,7 +231,7 @@ adapterManager.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTi auctionId, bidderRequestId, tid, - bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), + bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), auctionStart: auctionStart, timeout: _s2sConfig.timeout, src: CONSTANTS.S2S.SRC, @@ -255,7 +266,7 @@ adapterManager.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTi bidderCode, auctionId, bidderRequestId, - bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsClientCopy), labels, src: 'client'}), + bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsClientCopy), labels, src: 'client'}), auctionStart: auctionStart, timeout: cbTimeout, refererInfo @@ -282,7 +293,7 @@ adapterManager.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTi }); } return bidRequests; -}; +}, 'makeBidRequests'); adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbacks, requestBidsTimeout, onTimelyResponse) => { if (!bidRequests.length) { @@ -357,19 +368,25 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request request: requestCallbacks.request.bind(null, bidRequest.bidderCode), done: requestCallbacks.done } : undefined); - config.runWithBidder( - bidRequest.bidderCode, - bind.call( - adapter.callBids, - adapter, - bidRequest, - addBidResponse.bind(bidRequest), - doneCb.bind(bidRequest), - ajax, - onTimelyResponse, - config.callbackWithBidder(bidRequest.bidderCode) - ) - ); + const adapterDone = doneCb.bind(bidRequest); + try { + config.runWithBidder( + bidRequest.bidderCode, + bind.call( + adapter.callBids, + adapter, + bidRequest, + addBidResponse.bind(bidRequest), + adapterDone, + ajax, + onTimelyResponse, + config.callbackWithBidder(bidRequest.bidderCode) + ) + ); + } catch (e) { + utils.logError(`${bidRequest.bidderCode} Bid Adapter emitted an uncaught error when parsing their bidRequest`, {e, bidRequest}); + adapterDone(); + } }); }; @@ -409,7 +426,7 @@ adapterManager.registerBidAdapter = function (bidAdaptor, bidderCode, {supported } }; -adapterManager.aliasBidAdapter = function (bidderCode, alias) { +adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { let existingAlias = _bidderRegistry[alias]; if (typeof existingAlias === 'undefined') { @@ -435,7 +452,8 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias) { newAdapter.setBidderCode(alias); } else { let spec = bidAdaptor.getSpec(); - newAdapter = newBidder(Object.assign({}, spec, { code: alias })); + let gvlid = options && options.gvlid; + newAdapter = newBidder(Object.assign({}, spec, { code: alias, gvlid })); _aliasRegistry[alias] = bidderCode; } adapterManager.registerBidAdapter(newAdapter, alias, { @@ -450,11 +468,11 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias) { } }; -adapterManager.registerAnalyticsAdapter = function ({adapter, code}) { +adapterManager.registerAnalyticsAdapter = function ({adapter, code, gvlid}) { if (adapter && code) { if (typeof adapter.enableAnalytics === 'function') { adapter.code = code; - _analyticsRegistry[code] = adapter; + _analyticsRegistry[code] = { adapter, gvlid }; } else { utils.logError(`Prebid Error: Analytics adaptor error for analytics "${code}" analytics adapter must implement an enableAnalytics() function`); @@ -470,7 +488,7 @@ adapterManager.enableAnalytics = function (config) { } utils._each(config, adapterConfig => { - var adapter = _analyticsRegistry[adapterConfig.provider]; + var adapter = _analyticsRegistry[adapterConfig.provider].adapter; if (adapter) { adapter.enableAnalytics(adapterConfig); } else { @@ -478,12 +496,16 @@ adapterManager.enableAnalytics = function (config) { ${adapterConfig.provider}.`); } }); -}; +} adapterManager.getBidAdapter = function(bidder) { return _bidderRegistry[bidder]; }; +adapterManager.getAnalyticsAdapter = function(code) { + return _analyticsRegistry[code]; +} + // the s2sTesting module is injected when it's loaded rather than being imported // importing it causes the packager to include it even when it's not explicitly included in the build export function setS2STestingModule(module) { diff --git a/src/adapters/analytics/example.js b/src/adapters/analytics/example.js index 37ef0ff8fe3..1321612b688 100644 --- a/src/adapters/analytics/example.js +++ b/src/adapters/analytics/example.js @@ -2,7 +2,7 @@ * example.js - analytics adapter for Example Analytics Library example */ -import adapter from '../../AnalyticsAdapter'; +import adapter from '../../AnalyticsAdapter.js'; export default adapter( { diff --git a/src/adapters/analytics/example2.js b/src/adapters/analytics/example2.js index b04e8874e48..eadf994ce36 100644 --- a/src/adapters/analytics/example2.js +++ b/src/adapters/analytics/example2.js @@ -1,10 +1,11 @@ -import { ajax } from '../../../src/ajax'; +/* eslint-disable no-console */ +import { ajax } from '../../../src/ajax.js'; /** * example2.js - analytics adapter for Example2 Analytics Endpoint example */ -import adapter from '../../AnalyticsAdapter'; +import adapter from '../../AnalyticsAdapter.js'; const url = 'https://httpbin.org/post'; const analyticsType = 'endpoint'; diff --git a/src/adapters/analytics/libraries/example.js b/src/adapters/analytics/libraries/example.js index 55ef2b8ca45..0d758fd5513 100644 --- a/src/adapters/analytics/libraries/example.js +++ b/src/adapters/analytics/libraries/example.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ /** @module example */ window.ExampleAnalyticsGlobalObject = function(hander, type, data) { diff --git a/src/adapters/analytics/libraries/example2.js b/src/adapters/analytics/libraries/example2.js index 913f21177d0..68e814b1417 100644 --- a/src/adapters/analytics/libraries/example2.js +++ b/src/adapters/analytics/libraries/example2.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ /** @module example */ window.ExampleAnalyticsGlobalObject2 = function(hander, type, data) { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index b61aaa96169..3b6260efc88 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -1,17 +1,20 @@ -import Adapter from '../adapter'; -import adapterManager from '../adapterManager'; -import { config } from '../config'; -import { createBid } from '../bidfactory'; -import { userSync } from '../userSync'; -import { nativeBidIsValid } from '../native'; -import { isValidVideoBid } from '../video'; +import Adapter from '../adapter.js'; +import adapterManager from '../adapterManager.js'; +import { config } from '../config.js'; +import { createBid } from '../bidfactory.js'; +import { userSync } from '../userSync.js'; +import { nativeBidIsValid } from '../native.js'; +import { isValidVideoBid } from '../video.js'; import CONSTANTS from '../constants.json'; -import events from '../events'; -import includes from 'core-js/library/fn/array/includes'; -import { ajax } from '../ajax'; -import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, getBidderRequest, flatten, uniques, timestamp, setDataInLocalStorage, getDataFromLocalStorage, deepAccess, isArray } from '../utils'; -import { ADPOD } from '../mediaTypes'; -import { getHook } from '../hook'; +import events from '../events.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { ajax } from '../ajax.js'; +import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, getBidderRequest, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js'; +import { ADPOD } from '../mediaTypes.js'; +import { getHook, hook } from '../hook.js'; +import { getCoreStorageManager } from '../storageManager.js'; + +export const storage = getCoreStorageManager('bidderFactory'); /** * This file aims to support Adapters during the Prebid 0.x -> 1.x transition. @@ -103,7 +106,7 @@ import { getHook } from '../hook'; * @property {object} [native] Object for storing native creative assets * @property {object} [video] Object for storing video response data * @property {object} [meta] Object for storing bid meta data - * @property {string} [meta.iabSubCatId] The IAB subcategory ID + * @property {string} [meta.primaryCatId] The IAB primary category ID * @property [Renderer] renderer A Renderer which can be used as a default for this bid, * if the publisher doesn't override it. This is only relevant for Outstream Video bids. */ @@ -150,8 +153,14 @@ 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 })); + let aliasCode = alias; + let gvlid; + if (isPlainObject(alias)) { + aliasCode = alias.code; + gvlid = alias.gvlid; + } + adapterManager.aliasRegistry[aliasCode] = spec.code; + putBidder(Object.assign({}, spec, { code: aliasCode, gvlid })); }); } } @@ -304,6 +313,7 @@ export function newBidder(spec) { // creating a copy of original values as cpm and currency are modified later bid.originalCpm = bid.cpm; bid.originalCurrency = bid.currency; + bid.meta = bid.meta || Object.assign({}, bid[bidRequest.bidder]); const prebidBid = Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid); addBidWithCode(bidRequest.adUnitCode, prebidBid); } else { @@ -331,21 +341,7 @@ export function newBidder(spec) { }); function registerSyncs(responses, gdprConsent, uspConsent) { - if (spec.getUserSyncs && !adapterManager.aliasRegistry[spec.code]) { - let filterConfig = config.getConfig('userSync.filterSettings'); - let syncs = spec.getUserSyncs({ - iframeEnabled: !!(filterConfig && (filterConfig.iframe || filterConfig.all)), - pixelEnabled: !!(filterConfig && (filterConfig.image || filterConfig.all)), - }, responses, gdprConsent, uspConsent); - if (syncs) { - if (!Array.isArray(syncs)) { - syncs = [syncs]; - } - syncs.forEach((sync) => { - userSync.registerSync(sync.type, spec.code, sync.url) - }); - } - } + registerSyncInner(spec, responses, gdprConsent, uspConsent); } function filterAndWarn(bid) { @@ -357,6 +353,25 @@ export function newBidder(spec) { } } +export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent) { + const aliasSyncEnabled = config.getConfig('userSync.aliasSyncEnabled'); + if (spec.getUserSyncs && (aliasSyncEnabled || !adapterManager.aliasRegistry[spec.code])) { + let filterConfig = config.getConfig('userSync.filterSettings'); + let syncs = spec.getUserSyncs({ + iframeEnabled: !!(filterConfig && (filterConfig.iframe || filterConfig.all)), + pixelEnabled: !!(filterConfig && (filterConfig.image || filterConfig.all)), + }, responses, gdprConsent, uspConsent); + if (syncs) { + if (!Array.isArray(syncs)) { + syncs = [syncs]; + } + syncs.forEach((sync) => { + userSync.registerSync(sync.type, spec.code, sync.url) + }); + } + } +}, 'registerSyncs') + export function preloadBidderMappingFile(fn, adUnits) { if (!config.getConfig('adpod.brandCategoryExclusion')) { return fn.call(this, adUnits); @@ -373,27 +388,32 @@ export function preloadBidderMappingFile(fn, adUnits) { let info = bidderSpec.getSpec().getMappingFileInfo(); let refreshInDays = (info.refreshInDays) ? info.refreshInDays : DEFAULT_REFRESHIN_DAYS; let key = (info.localStorageKey) ? info.localStorageKey : bidderSpec.getSpec().code; - let mappingData = getDataFromLocalStorage(key); - if (!mappingData || timestamp() < mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { - ajax(info.url, - { - success: (response) => { - try { - response = JSON.parse(response); - let mapping = { - lastUpdated: timestamp(), - mapping: response.mapping + let mappingData = storage.getDataFromLocalStorage(key); + try { + mappingData = mappingData ? JSON.parse(mappingData) : undefined; + if (!mappingData || timestamp() > mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { + ajax(info.url, + { + success: (response) => { + try { + response = JSON.parse(response); + let mapping = { + lastUpdated: timestamp(), + mapping: response.mapping + } + storage.setDataInLocalStorage(key, JSON.stringify(mapping)); + } catch (error) { + logError(`Failed to parse ${bidder} bidder translation mapping file`); } - setDataInLocalStorage(key, JSON.stringify(mapping)); - } catch (error) { - logError(`Failed to parse ${bidder} bidder translation mapping file`); + }, + error: () => { + logError(`Failed to load ${bidder} bidder translation file`) } }, - error: () => { - logError(`Failed to load ${bidder} bidder translation file`) - } - }, - ); + ); + } + } catch (error) { + logError(`Failed to parse ${bidder} bidder translation mapping file`); } } }); @@ -412,7 +432,7 @@ export function getIabSubCategory(bidderCode, category) { if (bidderSpec.getSpec().getMappingFileInfo) { let info = bidderSpec.getSpec().getMappingFileInfo(); let key = (info.localStorageKey) ? info.localStorageKey : bidderSpec.getBidderCode(); - let data = getDataFromLocalStorage(key); + let data = storage.getDataFromLocalStorage(key); if (data) { try { data = JSON.parse(data); diff --git a/src/adloader.js b/src/adloader.js index aba4eaae250..1c18ce82b16 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,12 +1,13 @@ -import includes from 'core-js/library/fn/array/includes'; -import * as utils from './utils'; +import includes from 'core-js-pure/features/array/includes.js'; +import * as utils from './utils.js'; const _requestCache = {}; // The below list contains modules or vendors whom Prebid allows to load external JS. const _approvedLoadExternalJSList = [ 'criteo', 'outstream', - 'adagio' + 'adagio', + 'browsi' ] /** @@ -14,7 +15,7 @@ const _approvedLoadExternalJSList = [ * 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 - * @param {function} callback callback function to be called after the script is loaded. + * @param {function} [callback] callback function to be called after the script is loaded. */ export function loadExternalScript(url, moduleCode, callback) { if (!moduleCode || !url) { @@ -36,10 +37,11 @@ export function loadExternalScript(url, moduleCode, callback) { _requestCache[url].callbacks.push(callback); } } - return; + return _requestCache[url].tag; } _requestCache[url] = { loaded: false, + tag: null, callbacks: [] }; if (callback && typeof callback === 'function') { @@ -47,7 +49,7 @@ export function loadExternalScript(url, moduleCode, callback) { } utils.logWarn(`module ${moduleCode} is loading external JavaScript`); - requestResource(url, function () { + return requestResource(url, function () { _requestCache[url].loaded = true; try { for (let i = 0; i < _requestCache[url].callbacks.length; i++) { @@ -57,15 +59,14 @@ export function loadExternalScript(url, moduleCode, callback) { utils.logError('Error executing callback', 'adloader.js:loadExternalScript', e); } }); -}; -function requestResource(tagSrc, callback) { - var jptScript = document.createElement('script'); - jptScript.type = 'text/javascript'; - jptScript.async = true; + function requestResource(tagSrc, callback) { + var jptScript = document.createElement('script'); + jptScript.type = 'text/javascript'; + jptScript.async = true; + + _requestCache[url].tag = jptScript; - // Execute a callback if necessary - if (callback && typeof callback === 'function') { if (jptScript.readyState) { jptScript.onreadystatechange = function () { if (jptScript.readyState === 'loaded' || jptScript.readyState === 'complete') { @@ -78,10 +79,12 @@ function requestResource(tagSrc, callback) { callback(); }; } - } - jptScript.src = tagSrc; + jptScript.src = tagSrc; + + // add the new script tag to the page + utils.insertElement(jptScript); - // add the new script tag to the page - utils.insertElement(jptScript); -} + return jptScript; + } +}; diff --git a/src/adserver.js b/src/adserver.js index 1341e9166da..61af8862972 100644 --- a/src/adserver.js +++ b/src/adserver.js @@ -1,5 +1,5 @@ -import { formatQS } from './url'; -import { targeting } from './targeting'; +import { formatQS } from './utils.js'; +import { targeting } from './targeting.js'; // Adserver parent class const AdServer = function(attr) { diff --git a/src/ajax.js b/src/ajax.js index a9ec75b30ee..b1e4cbdbdff 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,7 +1,6 @@ -import {parse as parseURL, format as formatURL} from './url'; -import { config } from './config'; +import { config } from './config.js'; -var utils = require('./utils'); +var utils = require('./utils.js'); const XHR_DONE = 4; @@ -61,9 +60,9 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { } if (method === 'GET' && data) { - let urlInfo = parseURL(url, options); + let urlInfo = utils.parseUrl(url, options); Object.assign(urlInfo.search, data); - url = formatURL(urlInfo); + url = utils.buildUrl(urlInfo); } x.open(method, url, true); diff --git a/src/auction.js b/src/auction.js index fe1b70085e9..b82b4752479 100644 --- a/src/auction.js +++ b/src/auction.js @@ -57,23 +57,22 @@ * @property {function(): void} callBids - sends requests to all adapters for bids */ -import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue} from './utils'; -import { parse as parseURL } from './url'; -import { getPriceBucketString } from './cpmBucketManager'; -import { getNativeTargeting } from './native'; -import { getCacheUrl, store } from './videoCache'; -import { Renderer } from './Renderer'; -import { config } from './config'; -import { userSync } from './userSync'; -import { hook } from './hook'; -import find from 'core-js/library/fn/array/find'; -import { OUTSTREAM } from './video'; -import { VIDEO } from './mediaTypes'; +import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue, parseUrl} from './utils.js'; +import { getPriceBucketString } from './cpmBucketManager.js'; +import { getNativeTargeting } from './native.js'; +import { getCacheUrl, store } from './videoCache.js'; +import { Renderer } from './Renderer.js'; +import { config } from './config.js'; +import { userSync } from './userSync.js'; +import { hook } from './hook.js'; +import find from 'core-js-pure/features/array/find.js'; +import { OUTSTREAM } from './video.js'; +import { VIDEO } from './mediaTypes.js'; const { syncUsers } = userSync; -const utils = require('./utils'); -const adapterManager = require('./adapterManager').default; -const events = require('./events'); +const utils = require('./utils.js'); +const adapterManager = require('./adapterManager.js').default; +const events = require('./events.js'); const CONSTANTS = require('./constants.json'); export const AUCTION_STARTED = 'started'; @@ -120,7 +119,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a let _winningBids = []; let _timelyBidders = new Set(); - function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests) }; + function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests); } function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); } function addNoBid(noBid) { _noBids = _noBids.concat(noBid); } @@ -168,14 +167,14 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a _auctionEnd = Date.now(); events.emit(CONSTANTS.EVENTS.AUCTION_END, getProperties()); - bidsBackCallback(_adUnitCodes, function () { + bidsBackCallback(_adUnits, function () { try { if (_callback != null) { const adUnitCodes = _adUnitCodes; const bids = _bidsReceived .filter(utils.bind.call(adUnitsFilter, this, adUnitCodes)) .reduce(groupByPlacement, {}); - _callback.apply($$PREBID_GLOBAL$$, [bids, timedOut]); + _callback.apply($$PREBID_GLOBAL$$, [bids, timedOut, _auctionId]); _callback = null; } } catch (e) { @@ -213,62 +212,73 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a let bidRequests = adapterManager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout, _labels); utils.logInfo(`Bids Requested for Auction with id: ${_auctionId}`, bidRequests); - bidRequests.forEach(bidRequest => { - addBidRequests(bidRequest); - }); - - let requests = {}; if (bidRequests.length < 1) { utils.logWarn('No valid bid requests returned for auction'); auctionDone(); } else { - let call = { - bidRequests, - run: () => { - startAuctionTimer(); - - _auctionStatus = AUCTION_IN_PROGRESS; - - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, getProperties()); - - let callbacks = auctionCallbacks(auctionDone, this); - adapterManager.callBids(_adUnits, bidRequests, function(...args) { - addBidResponse.apply({ - dispatch: callbacks.addBidResponse, - bidderRequest: this - }, args) - }, callbacks.adapterDone, { - request(source, origin) { - increment(outstandingRequests, origin); - increment(requests, source); - - if (!sourceInfo[source]) { - sourceInfo[source] = { - SRA: true, - origin - }; - } - if (requests[source] > 1) { - sourceInfo[source].SRA = false; - } - }, - done(origin) { - outstandingRequests[origin]--; - if (queuedCalls[0]) { - if (runIfOriginHasCapacity(queuedCalls[0])) { - queuedCalls.shift(); - } + addBidderRequests.call({ + dispatch: addBidderRequestsCallback, + context: this + }, bidRequests); + } + } + + /** + * callback executed after addBidderRequests completes + * @param {BidRequest[]} bidRequests + */ + function addBidderRequestsCallback(bidRequests) { + bidRequests.forEach(bidRequest => { + addBidRequests(bidRequest); + }); + + let requests = {}; + let call = { + bidRequests, + run: () => { + startAuctionTimer(); + + _auctionStatus = AUCTION_IN_PROGRESS; + + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, getProperties()); + + let callbacks = auctionCallbacks(auctionDone, this); + adapterManager.callBids(_adUnits, bidRequests, function(...args) { + addBidResponse.apply({ + dispatch: callbacks.addBidResponse, + bidderRequest: this + }, args) + }, callbacks.adapterDone, { + request(source, origin) { + increment(outstandingRequests, origin); + increment(requests, source); + + if (!sourceInfo[source]) { + sourceInfo[source] = { + SRA: true, + origin + }; + } + if (requests[source] > 1) { + sourceInfo[source].SRA = false; + } + }, + done(origin) { + outstandingRequests[origin]--; + if (queuedCalls[0]) { + if (runIfOriginHasCapacity(queuedCalls[0])) { + queuedCalls.shift(); } } - }, _timeout, onTimelyResponse); - } - }; - - if (!runIfOriginHasCapacity(call)) { - utils.logWarn('queueing auction due to limited endpoint capacity'); - queuedCalls.push(call); + } + }, _timeout, onTimelyResponse); } + }; + + if (!runIfOriginHasCapacity(call)) { + utils.logWarn('queueing auction due to limited endpoint capacity'); + queuedCalls.push(call); } function runIfOriginHasCapacity(call) { @@ -344,6 +354,10 @@ export const addBidResponse = hook('async', function(adUnitCode, bid) { this.dispatch.call(this.bidderRequest, adUnitCode, bid); }, 'addBidResponse'); +export const addBidderRequests = hook('sync', function(bidderRequests) { + this.dispatch.call(this.context, bidderRequests); +}, 'addBidderRequests'); + export const bidsBackCallback = hook('async', function (adUnits, callback) { if (callback) { callback(); @@ -469,7 +483,7 @@ export const callPrebidCache = hook('async', function(auctionInstance, bidRespon afterBidAdded(); } } - }); + }, bidderRequest); }, 'callPrebidCache'); // Postprocess the bids so that all the universal properties exist, no matter which bidder they came from. @@ -636,7 +650,7 @@ export function getStandardBidderSettings(mediaType, bidderCode, bidReq) { // Adding hb_cache_host if (config.getConfig('cache.url') && (!bidderCode || utils.deepAccess(bidderSettings, `${bidderCode}.sendStandardTargeting`) !== false)) { - const urlInfo = parseURL(config.getConfig('cache.url')); + const urlInfo = parseUrl(config.getConfig('cache.url')); if (typeof find(adserverTargeting, targetingKeyVal => targetingKeyVal.key === TARGETING_KEYS.CACHE_HOST) === 'undefined') { adserverTargeting.push(createKeyVal(TARGETING_KEYS.CACHE_HOST, function(bidResponse) { diff --git a/src/auctionManager.js b/src/auctionManager.js index 2040a6387b0..3d4bd0afe99 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -16,9 +16,9 @@ * @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 './auction'; -import find from 'core-js/library/fn/array/find'; +import { uniques, flatten, logWarn } from './utils.js'; +import { newAuction, getStandardBidderSettings, AUCTION_COMPLETED } from './auction.js'; +import find from 'core-js-pure/features/array/find.js'; const CONSTANTS = require('./constants.json'); @@ -38,7 +38,7 @@ export function newAuctionManager() { bid.status = CONSTANTS.BID_STATUS.RENDERED; auction.addWinningBid(bid); } else { - utils.logWarn(`Auction not found when adding winning bid`); + logWarn(`Auction not found when adding winning bid`); } }; diff --git a/src/config.js b/src/config.js index 19a582e86d5..3284be52296 100644 --- a/src/config.js +++ b/src/config.js @@ -12,22 +12,23 @@ * @property {(string|Object)} [video-outstream] */ -import { isValidPriceConfig } from './cpmBucketManager'; -import find from 'core-js/library/fn/array/find'; -import includes from 'core-js/library/fn/array/includes'; -import Set from 'core-js/library/fn/set'; -import { parseQS } from './url'; +import { isValidPriceConfig } from './cpmBucketManager.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import Set from 'core-js-pure/features/set'; +import { mergeDeep } from './utils.js'; -const from = require('core-js/library/fn/array/from'); -const utils = require('./utils'); -const CONSTANTS = require('./constants'); +const from = require('core-js-pure/features/array/from.js'); +const utils = require('./utils.js'); +const CONSTANTS = require('./constants.json'); -const DEFAULT_DEBUG = (parseQS(window.location.search)[CONSTANTS.DEBUG_MODE] || '').toUpperCase() === 'TRUE'; +const DEFAULT_DEBUG = utils.getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; const DEFAULT_BIDDER_TIMEOUT = 3000; const DEFAULT_PUBLISHER_DOMAIN = window.location.origin; const DEFAULT_ENABLE_SEND_ALL_BIDS = true; const DEFAULT_DISABLE_AJAX_TIMEOUT = false; const DEFAULT_BID_CACHE = false; +const DEFAULT_DEVICE_ACCESS = true; const DEFAULT_TIMEOUTBUFFER = 400; @@ -158,6 +159,18 @@ export function newConfig() { this._useBidCache = val; }, + /** + * deviceAccess set to false will disable setCookie, getCookie, hasLocalStorage + * @type {boolean} + */ + _deviceAccess: DEFAULT_DEVICE_ACCESS, + get deviceAccess() { + return this._deviceAccess; + }, + set deviceAccess(val) { + this._deviceAccess = val; + }, + _bidderSequence: DEFAULT_BIDDER_SEQUENCE, get bidderSequence() { return this._bidderSequence; @@ -186,7 +199,6 @@ export function newConfig() { set disableAjaxTimeout(val) { this._disableAjaxTimeout = val; }, - }; if (config) { @@ -243,7 +255,7 @@ export function newConfig() { memo[topic] = currBidderConfig[topic]; } else { if (utils.isPlainObject(currBidderConfig[topic])) { - memo[topic] = Object.assign({}, config[topic], currBidderConfig[topic]); + memo[topic] = mergeDeep({}, config[topic], currBidderConfig[topic]); } else { memo[topic] = currBidderConfig[topic]; } @@ -429,14 +441,23 @@ export function newConfig() { function callbackWithBidder(bidder) { return function(cb) { return function(...args) { - return runWithBidder(bidder, utils.bind.call(cb, this, ...args)) + if (typeof cb === 'function') { + return runWithBidder(bidder, utils.bind.call(cb, this, ...args)) + } else { + utils.logWarn('config.callbackWithBidder callback is not a function'); + } } } } + function getCurrentBidder() { + return currBidder; + } + resetConfig(); return { + getCurrentBidder, getConfig, setConfig, setDefaults, diff --git a/src/constants.json b/src/constants.json index d993a6531ad..1b5feda6a05 100644 --- a/src/constants.json +++ b/src/constants.json @@ -33,9 +33,11 @@ "BID_WON": "bidWon", "BIDDER_DONE": "bidderDone", "SET_TARGETING": "setTargeting", + "BEFORE_REQUEST_BIDS": "beforeRequestBids", "REQUEST_BIDS": "requestBids", "ADD_AD_UNITS": "addAdUnits", - "AD_RENDER_FAILED" : "adRenderFailed" + "AD_RENDER_FAILED": "adRenderFailed", + "TCF2_ENFORCEMENT": "tcf2Enforcement" }, "AD_RENDER_FAILED_REASON" : { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocuemnt", @@ -94,9 +96,7 @@ }, "BID_STATUS" : { "BID_TARGETING_SET": "targetingSet", - "RENDERED": "rendered" - }, - "SUBMODULES_THAT_ALWAYS_REFRESH_ID": { - "parrableId": true + "RENDERED": "rendered", + "BID_REJECTED": "bidRejected" } } diff --git a/src/cpmBucketManager.js b/src/cpmBucketManager.js index 24586988a13..a6b76cc38e2 100644 --- a/src/cpmBucketManager.js +++ b/src/cpmBucketManager.js @@ -1,5 +1,5 @@ -import find from 'core-js/library/fn/array/find'; -const utils = require('./utils'); +import find from 'core-js-pure/features/array/find.js'; +const utils = require('./utils.js'); const _defaultPrecision = 2; const _lgPriceConfig = { diff --git a/src/debugging.js b/src/debugging.js index 98ff67f4707..dc479f74674 100644 --- a/src/debugging.js +++ b/src/debugging.js @@ -1,11 +1,12 @@ -import { config } from './config'; -import { logMessage as utilsLogMessage, logWarn as utilsLogWarn } from './utils'; -import { addBidResponse } from './auction'; +import { config } from './config.js'; +import { logMessage as utilsLogMessage, logWarn as utilsLogWarn } from './utils.js'; +import { addBidderRequests, addBidResponse } from './auction.js'; const OVERRIDE_KEY = '$$PREBID_GLOBAL$$:debugging'; -export let boundHook; +export let addBidResponseBound; +export let addBidderRequestsBound; function logMessage(msg) { utilsLogMessage('DEBUG: ' + msg); @@ -15,52 +16,113 @@ function logWarn(msg) { utilsLogWarn('DEBUG: ' + msg); } -function removeHook() { - addBidResponse.getHooks({hook: boundHook}).remove() +function addHooks(overrides) { + addBidResponseBound = addBidResponseHook.bind(overrides); + addBidResponse.before(addBidResponseBound, 5); + + addBidderRequestsBound = addBidderRequestsHook.bind(overrides); + addBidderRequests.before(addBidderRequestsBound, 5); +} + +function removeHooks() { + addBidResponse.getHooks({hook: addBidResponseBound}).remove(); + addBidderRequests.getHooks({hook: addBidderRequestsBound}).remove(); } -function enableOverrides(overrides, fromSession = false) { +export function enableOverrides(overrides, fromSession = false) { config.setConfig({'debug': true}); + removeHooks(); + addHooks(overrides); logMessage(`bidder overrides enabled${fromSession ? ' from session' : ''}`); - - removeHook(); - - boundHook = addBidResponseHook.bind(overrides); - addBidResponse.before(boundHook, 5); } export function disableOverrides() { - removeHook(); + removeHooks(); logMessage('bidder overrides disabled'); } +/** + * @param {{bidder:string, adUnitCode:string}} overrideObj + * @param {string} bidderCode + * @param {string} adUnitCode + * @returns {boolean} + */ +export function bidExcluded(overrideObj, bidderCode, adUnitCode) { + if (overrideObj.bidder && overrideObj.bidder !== bidderCode) { + return true; + } + if (overrideObj.adUnitCode && overrideObj.adUnitCode !== adUnitCode) { + return true; + } + return false; +} + +/** + * @param {string[]} bidders + * @param {string} bidderCode + * @returns {boolean} + */ +export function bidderExcluded(bidders, bidderCode) { + return (Array.isArray(bidders) && bidders.indexOf(bidderCode) === -1); +} + +/** + * @param {Object} overrideObj + * @param {Object} bidObj + * @param {Object} bidType + * @returns {Object} bidObj with overridden properties + */ +export function applyBidOverrides(overrideObj, bidObj, bidType) { + return Object.keys(overrideObj).filter(key => (['adUnitCode', 'bidder'].indexOf(key) === -1)).reduce(function(result, key) { + logMessage(`bidder overrides changed '${result.adUnitCode}/${result.bidderCode}' ${bidType}.${key} from '${result[key]}.js' to '${overrideObj[key]}'`); + result[key] = overrideObj[key]; + return result; + }, bidObj); +} + export function addBidResponseHook(next, adUnitCode, bid) { - let overrides = this; - if (Array.isArray(overrides.bidders) && overrides.bidders.indexOf(bid.bidderCode) === -1) { + const overrides = this; + + if (bidderExcluded(overrides.bidders, bid.bidderCode)) { logWarn(`bidder '${bid.bidderCode}' excluded from auction by bidder overrides`); return; } if (Array.isArray(overrides.bids)) { - overrides.bids.forEach(overrideBid => { - if (overrideBid.bidder && overrideBid.bidder !== bid.bidderCode) { - return; - } - if (overrideBid.adUnitCode && overrideBid.adUnitCode !== adUnitCode) { - return; + overrides.bids.forEach(function(overrideBid) { + if (!bidExcluded(overrideBid, bid.bidderCode, adUnitCode)) { + applyBidOverrides(overrideBid, bid, 'bidder'); } + }); + } - bid = Object.assign({}, bid); + next(adUnitCode, bid); +} - Object.keys(overrideBid).filter(key => ['bidder', 'adUnitCode'].indexOf(key) === -1).forEach((key) => { - let value = overrideBid[key]; - logMessage(`bidder overrides changed '${adUnitCode}/${bid.bidderCode}' bid.${key} from '${bid[key]}' to '${value}'`); - bid[key] = value; +export function addBidderRequestsHook(next, bidderRequests) { + const overrides = this; + + const includedBidderRequests = bidderRequests.filter(function(bidderRequest) { + if (bidderExcluded(overrides.bidders, bidderRequest.bidderCode)) { + logWarn(`bidRequest '${bidderRequest.bidderCode}' excluded from auction by bidder overrides`); + return false; + } + return true; + }); + + if (Array.isArray(overrides.bidRequests)) { + includedBidderRequests.forEach(function(bidderRequest) { + overrides.bidRequests.forEach(function(overrideBid) { + bidderRequest.bids.forEach(function(bid) { + if (!bidExcluded(overrideBid, bidderRequest.bidderCode, bid.adUnitCode)) { + applyBidOverrides(overrideBid, bid, 'bidRequest'); + } + }); }); }); } - next(adUnitCode, bid); + next(includedBidderRequests); } export function getConfig(debugging) { diff --git a/src/events.js b/src/events.js index 6ef3f7a46cb..e7a11635476 100644 --- a/src/events.js +++ b/src/events.js @@ -1,8 +1,8 @@ /** * events.js */ -var utils = require('./utils'); -var CONSTANTS = require('./constants'); +var utils = require('./utils.js'); +var CONSTANTS = require('./constants.json'); var slice = Array.prototype.slice; var push = Array.prototype.push; @@ -113,14 +113,14 @@ module.exports = (function () { utils._each(event[id].que, function (_handler) { var que = event[id].que; if (_handler === handler) { - que.splice(utils.indexOf.call(que, _handler), 1); + que.splice(que.indexOf(_handler), 1); } }); } else { utils._each(event.que, function (_handler) { var que = event.que; if (_handler === handler) { - que.splice(utils.indexOf.call(que, _handler), 1); + que.splice(que.indexOf(_handler), 1); } }); } diff --git a/src/hook.js b/src/hook.js index 220e1c39111..9050bf2f7dc 100644 --- a/src/hook.js +++ b/src/hook.js @@ -1,5 +1,5 @@ -import funHooks from 'fun-hooks/no-eval'; +import funHooks from 'fun-hooks/no-eval/index.js'; export let hook = funHooks({ ready: funHooks.SYNC | funHooks.ASYNC | funHooks.QUEUE diff --git a/src/native.js b/src/native.js index 6252eb133b3..e41d7740ffa 100644 --- a/src/native.js +++ b/src/native.js @@ -1,5 +1,5 @@ -import { deepAccess, getBidRequest, getKeyByValue, insertHtmlIntoIframe, logError, triggerPixel } from './utils'; -import includes from 'core-js/library/fn/array/includes'; +import { deepAccess, getBidRequest, getKeyByValue, insertHtmlIntoIframe, logError, triggerPixel } from './utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; const CONSTANTS = require('./constants.json'); diff --git a/src/prebid.js b/src/prebid.js index f1ead426496..827ba2e2270 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,24 +1,25 @@ /** @module pbjs */ -import { getGlobal } from './prebidGlobal'; -import { flatten, uniques, isGptPubadsDefined, adUnitsFilter, isArrayOfNums } from './utils'; -import { listenMessagesFromCreative } from './secureCreatives'; +import { getGlobal } from './prebidGlobal.js'; +import { flatten, uniques, isGptPubadsDefined, adUnitsFilter, isArrayOfNums } from './utils.js'; +import { listenMessagesFromCreative } from './secureCreatives.js'; import { userSync } from './userSync.js'; -import { config } from './config'; -import { auctionManager } from './auctionManager'; -import { targeting } from './targeting'; -import { hook } from './hook'; -import { sessionLoader } from './debugging'; -import includes from 'core-js/library/fn/array/includes'; -import { adunitCounter } from './adUnits'; -import { isRendererRequired, executeRenderer } from './Renderer'; -import { createBid } from './bidfactory'; +import { config } from './config.js'; +import { auctionManager } from './auctionManager.js'; +import { targeting } from './targeting.js'; +import { hook } from './hook.js'; +import { sessionLoader } from './debugging.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { adunitCounter } from './adUnits.js'; +import { isRendererRequired, executeRenderer } from './Renderer.js'; +import { createBid } from './bidfactory.js'; +import { storageCallbacks } from './storageManager.js'; const $$PREBID_GLOBAL$$ = getGlobal(); const CONSTANTS = require('./constants.json'); const utils = require('./utils.js'); -const adapterManager = require('./adapterManager').default; -const events = require('./events'); +const adapterManager = require('./adapterManager.js').default; +const events = require('./events.js'); const { triggerUserSyncs } = userSync; /* private variables */ @@ -68,70 +69,110 @@ function setRenderSize(doc, width, height) { } } -export const checkAdUnitSetup = hook('sync', function (adUnits) { - function validateSizes(sizes, targLength) { - let cleanSizes = []; - if (utils.isArray(sizes) && ((targLength) ? sizes.length === targLength : sizes.length > 0)) { - // check if an array of arrays or array of numbers - if (sizes.every(sz => isArrayOfNums(sz, 2))) { - cleanSizes = sizes; - } else if (isArrayOfNums(sizes, 2)) { - cleanSizes.push(sizes); +function validateSizes(sizes, targLength) { + let cleanSizes = []; + if (utils.isArray(sizes) && ((targLength) ? sizes.length === targLength : sizes.length > 0)) { + // check if an array of arrays or array of numbers + if (sizes.every(sz => isArrayOfNums(sz, 2))) { + cleanSizes = sizes; + } else if (isArrayOfNums(sizes, 2)) { + cleanSizes.push(sizes); + } + } + return cleanSizes; +} + +function validateBannerMediaType(adUnit) { + const validatedAdUnit = utils.deepClone(adUnit); + const banner = validatedAdUnit.mediaTypes.banner; + const bannerSizes = validateSizes(banner.sizes); + if (bannerSizes.length > 0) { + banner.sizes = bannerSizes; + // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.banner.sizes + validatedAdUnit.sizes = bannerSizes; + } else { + utils.logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); + delete validatedAdUnit.mediaTypes.banner + } + return validatedAdUnit; +} + +function validateVideoMediaType(adUnit) { + const validatedAdUnit = utils.deepClone(adUnit); + const video = validatedAdUnit.mediaTypes.video; + if (video.playerSize) { + let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; + + const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen); + if (videoSizes.length > 0) { + if (tarPlayerSizeLen === 2) { + utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); } + video.playerSize = videoSizes; + // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.video.playerSize + validatedAdUnit.sizes = videoSizes; + } 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 validatedAdUnit.mediaTypes.video.playerSize; } - return cleanSizes; } + return validatedAdUnit; +} - return adUnits.filter(adUnit => { +function validateNativeMediaType(adUnit) { + const validatedAdUnit = utils.deepClone(adUnit); + const native = validatedAdUnit.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 validatedAdUnit.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 validatedAdUnit.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 validatedAdUnit.mediaTypes.native.icon.sizes; + } + return validatedAdUnit; +} + +export const adUnitSetupChecks = { + validateBannerMediaType, + validateVideoMediaType, + validateNativeMediaType, + validateSizes +}; + +export const checkAdUnitSetup = hook('sync', function (adUnits) { + const validatedAdUnits = []; + + adUnits.forEach(adUnit => { const mediaTypes = adUnit.mediaTypes; + let validatedBanner, validatedVideo, validatedNative; if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); - return false; + return; } if (mediaTypes.banner) { - const bannerSizes = validateSizes(mediaTypes.banner.sizes); - if (bannerSizes.length > 0) { - mediaTypes.banner.sizes = bannerSizes; - // TODO eventually remove this internal copy once we're ready to deprecate bidders from reading this adUnit.sizes property - adUnit.sizes = bannerSizes; - } else { - utils.logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); - delete adUnit.mediaTypes.banner; - } + validatedBanner = validateBannerMediaType(adUnit); } if (mediaTypes.video) { - const video = mediaTypes.video; - if (video.playerSize) { - let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; - const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen); - if (videoSizes.length > 0) { - adUnit.sizes = video.playerSize = videoSizes; - } 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; - } - } + validatedVideo = validatedBanner ? validateVideoMediaType(validatedBanner) : validateVideoMediaType(adUnit); } if (mediaTypes.native) { - const nativeObj = mediaTypes.native; - if (nativeObj.image && nativeObj.image.sizes && !Array.isArray(nativeObj.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 (nativeObj.image && nativeObj.image.aspect_ratios && !Array.isArray(nativeObj.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 (nativeObj.icon && nativeObj.icon.sizes && !Array.isArray(nativeObj.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; - } + validatedNative = validatedVideo ? validateNativeMediaType(validatedVideo) : validatedBanner ? validateNativeMediaType(validatedBanner) : validateNativeMediaType(adUnit); } - return true; + + const validatedAdUnit = Object.assign({}, validatedBanner, validatedVideo, validatedNative); + + validatedAdUnits.push(validatedAdUnit); }); + + return validatedAdUnits; }, 'checkAdUnitSetup'); /// /////////////////////////////// @@ -164,7 +205,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @returns {Object} returnObj return bids */ -$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { +$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { return $$PREBID_GLOBAL$$.getAdserverTargeting(adUnitCode)[adUnitCode]; }; @@ -250,7 +291,7 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching let targetingSet = targeting.getAllTargeting(adUnit); // first reset any old targeting - targeting.resetPresetTargeting(adUnit); + targeting.resetPresetTargeting(adUnit, customSlotMatching); // now set new targeting keys targeting.setTargetingForGPT(targetingSet, customSlotMatching); @@ -272,7 +313,7 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ -$$PREBID_GLOBAL$$.setTargetingForAst = function(adUnitCodes) { +$$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { utils.logError('window.apntag is not defined on the page'); @@ -420,6 +461,8 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + adUnits = checkAdUnitSetup(adUnits); + if (adUnitCodes && adUnitCodes.length) { // if specific adUnitCodes supplied filter adUnits for those codes adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); @@ -428,8 +471,6 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo adUnitCodes = adUnits && adUnits.map(unit => unit.code); } - adUnits = checkAdUnitSetup(adUnits); - /* * for a given adunit which supports a set of mediaTypes * and a given bidder which supports a set of mediaTypes @@ -438,7 +479,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo */ adUnits.forEach(adUnit => { // get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present - const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}); + const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || { 'banner': 'banner' }); // get the bidder's mediaTypes const allBidders = adUnit.bids.map(bid => bid.bidder); @@ -484,7 +525,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo return; } - const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId}); + const auction = auctionManager.createAuction({ adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId }); let adUnitsLen = adUnits.length; if (adUnitsLen > 15) { @@ -493,9 +534,23 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo adUnitCodes.forEach(code => targeting.setLatestAuctionForAdUnit(code, auction.getAuctionId())); auction.callBids(); - return auction; }); +export function executeCallbacks(fn, reqBidsConfigObj) { + runAll(storageCallbacks); + runAll(enableAnalyticsCallbacks); + fn.call(this, reqBidsConfigObj); + function runAll(queue) { + var queued; + while ((queued = queue.shift())) { + queued(); + } + } +} + +// This hook will execute all storage callbacks which were registered before gdpr enforcement hook was added. Some bidders, user id modules use storage functions when module is parsed but gdpr enforcement hook is not added at that stage as setConfig callbacks are yet to be called. Hence for such calls we execute all the stored callbacks just before requestBids. At this hook point we will know for sure that gdprEnforcement module is added or not +$$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); + /** * * Add adunit(s) @@ -613,22 +668,30 @@ $$PREBID_GLOBAL$$.createBid = function (statusCode) { * @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) { + +// Stores 'enableAnalytics' callbacks for later execution. +const enableAnalyticsCallbacks = []; + +const enableAnalyticsCb = hook('async', function (config) { if (config && !utils.isEmpty(config)) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.enableAnalytics for: ', config); adapterManager.enableAnalytics(config); } else { utils.logError('$$PREBID_GLOBAL$$.enableAnalytics should be called with option {}'); } +}, 'enableAnalyticsCb'); + +$$PREBID_GLOBAL$$.enableAnalytics = function (config) { + enableAnalyticsCallbacks.push(enableAnalyticsCb.bind(this, config)); }; /** * @alias module:pbjs.aliasBidder */ -$$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { +$$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); if (bidderCode && alias) { - adapterManager.aliasBidAdapter(bidderCode, alias); + adapterManager.aliasBidAdapter(bidderCode, alias, options); } else { utils.logError('bidderCode and alias must be passed as arguments', '$$PREBID_GLOBAL$$.aliasBidder'); } @@ -802,7 +865,7 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * the Prebid script has been fully loaded. * @alias module:pbjs.cmd.push */ -$$PREBID_GLOBAL$$.cmd.push = function(command) { +$$PREBID_GLOBAL$$.cmd.push = function (command) { if (typeof command === 'function') { try { command.call(); @@ -817,7 +880,7 @@ $$PREBID_GLOBAL$$.cmd.push = function(command) { $$PREBID_GLOBAL$$.que.push = $$PREBID_GLOBAL$$.cmd.push; function processQueue(queue) { - queue.forEach(function(cmd) { + queue.forEach(function (cmd) { if (typeof cmd.called === 'undefined') { try { cmd.call(); @@ -832,7 +895,7 @@ function processQueue(queue) { /** * @alias module:pbjs.processQueue */ -$$PREBID_GLOBAL$$.processQueue = function() { +$$PREBID_GLOBAL$$.processQueue = function () { hook.ready(); processQueue($$PREBID_GLOBAL$$.que); processQueue($$PREBID_GLOBAL$$.cmd); diff --git a/src/refererDetection.js b/src/refererDetection.js index 2acb0b68716..60198678666 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -8,7 +8,7 @@ * Canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage */ -import { logWarn } from './utils'; +import { logWarn } from './utils.js'; export function detectReferer(win) { /** diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 7584c5d2bf7..34de2be275c 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -3,18 +3,19 @@ access to a publisher page from creative payloads. */ -import events from './events'; -import { fireNativeTrackers, getAssetMessage } from './native'; -import { EVENTS } from './constants'; -import { isSlotMatchingAdUnitCode, logWarn, replaceAuctionPrice } from './utils'; -import { auctionManager } from './auctionManager'; -import find from 'core-js/library/fn/array/find'; -import { isRendererRequired, executeRenderer } from './Renderer'; +import events from './events.js'; +import { fireNativeTrackers, getAssetMessage } from './native.js'; +import { EVENTS } from './constants.json'; +import { logWarn, replaceAuctionPrice } from './utils.js'; +import { auctionManager } from './auctionManager.js'; +import find from 'core-js-pure/features/array/find.js'; +import { isRendererRequired, executeRenderer } from './Renderer.js'; +import includes from 'core-js-pure/features/array/includes.js'; const BID_WON = EVENTS.BID_WON; export function listenMessagesFromCreative() { - addEventListener('message', receiveMessage, false); + window.addEventListener('message', receiveMessage, false); } function receiveMessage(ev) { @@ -32,7 +33,7 @@ function receiveMessage(ev) { }); if (adObject && data.message === 'Prebid Request') { - _sendAdToCreative(adObject, data.adServerDomain, ev.source); + _sendAdToCreative(adObject, ev); // save winning bids auctionManager.addWinningBid(adObject); @@ -61,25 +62,25 @@ function receiveMessage(ev) { } } -export function _sendAdToCreative(adObject, remoteDomain, source) { +export function _sendAdToCreative(adObject, ev) { const { adId, ad, adUrl, width, height, renderer, cpm } = adObject; // rendering for outstream safeframe if (isRendererRequired(renderer)) { executeRenderer(renderer, adObject); } else if (adId) { resizeRemoteCreative(adObject); - source.postMessage(JSON.stringify({ + ev.source.postMessage(JSON.stringify({ message: 'Prebid Response', ad: replaceAuctionPrice(ad, cpm), adUrl: replaceAuctionPrice(adUrl, cpm), adId, width, height - }), remoteDomain); + }), ev.origin); } } -function resizeRemoteCreative({ adUnitCode, width, height }) { +function resizeRemoteCreative({ adId, adUnitCode, width, height }) { // resize both container div + iframe ['div', 'iframe'].forEach(elmType => { // not select element that gets removed after dfp render @@ -94,14 +95,14 @@ function resizeRemoteCreative({ adUnitCode, width, height }) { }); function getElementByAdUnit(elmType) { - let id = getElementIdBasedOnAdServer(adUnitCode); + let id = getElementIdBasedOnAdServer(adId, adUnitCode); let parentDivEle = document.getElementById(id); return parentDivEle && parentDivEle.querySelector(elmType); } - function getElementIdBasedOnAdServer(adUnitCode) { + function getElementIdBasedOnAdServer(adId, adUnitCode) { if (window.googletag) { - return getDfpElementId(adUnitCode) + return getDfpElementId(adId) } else if (window.apntag) { return getAstElementId(adUnitCode) } else { @@ -109,8 +110,12 @@ function resizeRemoteCreative({ adUnitCode, width, height }) { } } - function getDfpElementId(adUnitCode) { - return find(window.googletag.pubads().getSlots().filter(isSlotMatchingAdUnitCode(adUnitCode)), slot => slot).getSlotElementId() + function getDfpElementId(adId) { + return find(window.googletag.pubads().getSlots(), slot => { + return find(slot.getTargetingKeys(), key => { + return includes(slot.getTargeting(key), adId); + }); + }).getSlotElementId(); } function getAstElementId(adUnitCode) { diff --git a/src/sizeMapping.js b/src/sizeMapping.js index cde3b4a4d15..313da3f422a 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -1,6 +1,6 @@ -import { config } from './config'; -import {logWarn, isPlainObject, deepAccess, deepClone, getWindowTop} from './utils'; -import includes from 'core-js/library/fn/array/includes'; +import { config } from './config.js'; +import {logWarn, isPlainObject, deepAccess, deepClone, getWindowTop} from './utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; let sizeConfig = []; @@ -125,12 +125,18 @@ function evaluateSizeConfig(configs) { ) { let ruleMatch = false; - try { - ruleMatch = getWindowTop().matchMedia(config.mediaQuery).matches; - } catch (e) { - logWarn('Unfriendly iFrame blocks sizeConfig from being correctly evaluated'); - - ruleMatch = matchMedia(config.mediaQuery).matches; + // TODO: (Prebid - 4.0) Remove empty mediaQuery string check. Disallow empty mediaQuery in sizeConfig. + // Refer: https://github.com/prebid/Prebid.js/pull/4691, https://github.com/prebid/Prebid.js/issues/4810 for more details. + if (config.mediaQuery === '') { + ruleMatch = true; + } else { + try { + ruleMatch = getWindowTop().matchMedia(config.mediaQuery).matches; + } catch (e) { + logWarn('Unfriendly iFrame blocks sizeConfig from being correctly evaluated'); + + ruleMatch = matchMedia(config.mediaQuery).matches; + } } if (ruleMatch) { diff --git a/src/storageManager.js b/src/storageManager.js new file mode 100644 index 00000000000..0d88a8ccea1 --- /dev/null +++ b/src/storageManager.js @@ -0,0 +1,311 @@ +import { hook } from './hook.js'; +import * as utils from './utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const moduleTypeWhiteList = ['core', 'prebid-module']; + +export let storageCallbacks = []; + +/** + * Storage options + * @typedef {Object} storageOptions + * @property {Number=} gvlid - Vendor id + * @property {string} moduleName - Module name + * @property {string=} moduleType - Module type, value can be anyone of core or prebid-module + */ + +/** + * Returns list of storage related functions with gvlid, module name and module type in its scope. + * All three argument are optional here. Below shows the usage of of these + * - GVL Id: Pass GVL id if you are a vendor + * - Module name: All modules need to pass module name + * - Module type: Some modules may need these functions but are not vendor. e.g prebid core files in src and modules like currency. + * @param {storageOptions} options + */ +export function newStorageManager({gvlid, moduleName, moduleType} = {}) { + function isValid(cb) { + if (includes(moduleTypeWhiteList, moduleType)) { + let result = { + valid: true + } + return cb(result); + } else { + let value; + let hookDetails = { + hasEnforcementHook: false + } + validateStorageEnforcement(gvlid, moduleName, hookDetails, function(result) { + if (result && result.hasEnforcementHook) { + value = cb(result); + } else { + let result = { + hasEnforcementHook: false, + valid: utils.hasDeviceAccess() + } + value = cb(result); + } + }); + return value; + } + } + + /** + * @param {string} key + * @param {string} value + * @param {string} [expires=''] + * @param {string} [sameSite='/'] + * @param {string} [domain] domain (e.g., 'example.com' or 'subdomain.example.com'). + * If not specified, defaults to the host portion of the current document location. + * If a domain is specified, subdomains are always included. + * Domain must match the domain of the JavaScript origin. Setting cookies to foreign domains will be silently ignored. + */ + const setCookie = function (key, value, expires, sameSite, domain, done) { + let cb = function (result) { + if (result && result.valid) { + const domainPortion = (domain && domain !== '') ? ` ;domain=${encodeURIComponent(domain)}` : ''; + const expiresPortion = (expires && expires !== '') ? ` ;expires=${expires}` : ''; + const isNone = (sameSite != null && sameSite.toLowerCase() == 'none') + const secure = (isNone) ? '; Secure' : ''; + document.cookie = `${key}=${encodeURIComponent(value)}${expiresPortion}; path=/${domainPortion}${sameSite ? `; SameSite=${sameSite}` : ''}${secure}`; + } + } + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + }; + + /** + * @param {string} name + * @returns {(string|null)} + */ + const getCookie = function(name, done) { + let cb = function (result) { + if (result && result.valid) { + let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)'); + return m ? decodeURIComponent(m[2]) : null; + } + return null; + } + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + }; + + /** + * @returns {boolean} + */ + const localStorageIsEnabled = function (done) { + let cb = function (result) { + if (result && result.valid) { + try { + localStorage.setItem('prebid.cookieTest', '1'); + return localStorage.getItem('prebid.cookieTest') === '1'; + } catch (error) {} + } + return false; + } + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + } + + /** + * @returns {boolean} + */ + const cookiesAreEnabled = function (done) { + let cb = function (result) { + if (result && result.valid) { + if (utils.checkCookieSupport()) { + return true; + } + window.document.cookie = 'prebid.cookieTest'; + return window.document.cookie.indexOf('prebid.cookieTest') !== -1; + } + return false; + } + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + } + + /** + * @param {string} key + * @param {string} value + */ + const setDataInLocalStorage = function (key, value, done) { + let cb = function (result) { + if (result && result.valid) { + window.localStorage.setItem(key, value); + } + } + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + } + + /** + * @param {string} key + * @returns {(string|null)} + */ + const getDataFromLocalStorage = function (key, done) { + let cb = function (result) { + if (result && result.valid) { + return window.localStorage.getItem(key); + } + return null; + } + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + } + + /** + * @param {string} key + */ + const removeDataFromLocalStorage = function (key, done) { + let cb = function (result) { + if (result && result.valid) { + window.localStorage.removeItem(key); + } + } + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + } + + /** + * @returns {boolean} + */ + const hasLocalStorage = function (done) { + let cb = function (result) { + if (result && result.valid) { + try { + return !!window.localStorage; + } catch (e) { + utils.logError('Local storage api disabled'); + } + } + return false; + } + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + } + + /** + * Returns all cookie values from the jar whose names contain the `keyLike` + * Needs to exist in `utils.js` as it follows the StorageHandler interface defined in live-connect-js. If that module were to be removed, this function can go as well. + * @param {string} keyLike + * @return {[]} + */ + const findSimilarCookies = function(keyLike, done) { + let cb = function (result) { + if (result && result.valid) { + const all = []; + if (utils.hasDeviceAccess()) { + const cookies = document.cookie.split(';'); + while (cookies.length) { + const cookie = cookies.pop(); + let separatorIndex = cookie.indexOf('='); + separatorIndex = separatorIndex < 0 ? cookie.length : separatorIndex; + const cookieName = decodeURIComponent(cookie.slice(0, separatorIndex).replace(/^\s+/, '')); + if (cookieName.indexOf(keyLike) >= 0) { + all.push(decodeURIComponent(cookie.slice(separatorIndex + 1))); + } + } + } + return all; + } + } + + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(cb); + done(result); + }); + } else { + return isValid(cb); + } + } + + return { + setCookie, + getCookie, + localStorageIsEnabled, + cookiesAreEnabled, + setDataInLocalStorage, + getDataFromLocalStorage, + removeDataFromLocalStorage, + hasLocalStorage, + findSimilarCookies + } +} + +/** + * This hook validates the storage enforcement if gdprEnforcement module is included + */ +export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, hookDetails, callback) { + callback(hookDetails); +}, 'validateStorageEnforcement'); + +/** + * This function returns storage functions to access cookies and localstorage. This function will bypass the gdpr enforcement requirement. Prebid as a software needs to use storage in some scenarios and is not a vendor so GDPR enforcement rules does not apply on Prebid. + * @param {string} moduleName Module name + */ +export function getCoreStorageManager(moduleName) { + return newStorageManager({moduleName: moduleName, moduleType: 'core'}); +} + +/** + * Note: Core modules or Prebid modules like Currency, SizeMapping should use getCoreStorageManager + * This function returns storage functions to access cookies and localstorage. Bidders and User id modules should import this and use it in their module if needed. GVL ID and Module name are optional param but gvl id is needed for when gdpr enforcement module is used. + * @param {Number=} gvlid Vendor id + * @param {string=} moduleName BidderCode or module name + */ +export function getStorageManager(gvlid, moduleName) { + return newStorageManager({gvlid: gvlid, moduleName: moduleName}); +} + +export function resetData() { + storageCallbacks = []; +} diff --git a/src/targeting.js b/src/targeting.js index 7c24fdf59f4..5873eeeb559 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,10 +1,10 @@ -import { uniques, isGptPubadsDefined, getHighestCpm, getOldestHighestCpmBid, groupBy, isAdUnitCodeMatchingSlot, timestamp, deepAccess, deepClone, logError, logWarn, logInfo } from './utils'; -import { config } from './config'; -import { NATIVE_TARGETING_KEYS } from './native'; -import { auctionManager } from './auctionManager'; -import { sizeSupported } from './sizeMapping'; -import { ADPOD } from './mediaTypes'; -import includes from 'core-js/library/fn/array/includes'; +import { uniques, isGptPubadsDefined, getHighestCpm, getOldestHighestCpmBid, groupBy, isAdUnitCodeMatchingSlot, timestamp, deepAccess, deepClone, logError, logWarn, logInfo } from './utils.js'; +import { config } from './config.js'; +import { NATIVE_TARGETING_KEYS } from './native.js'; +import { auctionManager } from './auctionManager.js'; +import { sizeSupported } from './sizeMapping.js'; +import { ADPOD } from './mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js'; const utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); @@ -34,6 +34,7 @@ export let filters = { // If adUnitBidLimit is set above 0 return top N number of bids export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, adUnitBidLimit = 0) { const bids = []; + const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); // bucket by adUnitcode let buckets = groupBy(bidsReceived, 'adUnitCode'); // filter top bid for each bucket by bidder @@ -43,7 +44,7 @@ export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, a Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); // if adUnitBidLimit is set, pass top N number bids if (adUnitBidLimit > 0) { - bucketBids.sort((a, b) => b.cpm - a.cpm); + bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); bids.push(...bucketBids.slice(0, adUnitBidLimit)); } else { bids.push(...bucketBids); @@ -73,17 +74,23 @@ export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, a * "hb_pb": "2" * }] */ -export function sortByDealAndPriceBucket(a, b) { - if (a.adUnitTargeting.hb_deal !== undefined && b.adUnitTargeting.hb_deal === undefined) { - return -1; - } +export function sortByDealAndPriceBucketOrCpm(useCpm = false) { + return function(a, b) { + if (a.adserverTargeting.hb_deal !== undefined && b.adserverTargeting.hb_deal === undefined) { + return -1; + } - if ((a.adUnitTargeting.hb_deal === undefined && b.adUnitTargeting.hb_deal !== undefined)) { - return 1; - } + if ((a.adserverTargeting.hb_deal === undefined && b.adserverTargeting.hb_deal !== undefined)) { + return 1; + } - // assuming both values either have a deal or don't have a deal - sort by the hb_pb param - return b.adUnitTargeting.hb_pb - a.adUnitTargeting.hb_pb; + // assuming both values either have a deal or don't have a deal - sort by the hb_pb param + if (useCpm) { + return b.cpm - a.cpm; + } + + return b.adserverTargeting.hb_pb - a.adserverTargeting.hb_pb; + } } /** @@ -103,16 +110,18 @@ export function newTargeting(auctionManager) { latestAuctionForAdUnit[adUnitCode] = auctionId; }; - targeting.resetPresetTargeting = function(adUnitCode) { + targeting.resetPresetTargeting = function(adUnitCode, customSlotMatching) { if (isGptPubadsDefined()) { const adUnitCodes = getAdUnitCodes(adUnitCode); const adUnits = auctionManager.getAdUnits().filter(adUnit => includes(adUnitCodes, adUnit.code)); window.googletag.pubads().getSlots().forEach(slot => { + let customSlotMatchingFunc = utils.isFn(customSlotMatching) && customSlotMatching(slot); pbTargetingKeys.forEach(function(key) { // reset only registered adunits adUnits.forEach(function(unit) { if (unit.code === slot.getAdUnitPath() || - unit.code === slot.getSlotElementId()) { + unit.code === slot.getSlotElementId() || + (utils.isFn(customSlotMatchingFunc) && customSlotMatchingFunc(unit.code))) { slot.setTargeting(key, null); } }); @@ -231,13 +240,13 @@ export function newTargeting(auctionManager) { let targetingMap = Object.keys(targetingCopy).map(adUnitCode => { return { adUnitCode, - adUnitTargeting: targetingCopy[adUnitCode] + adserverTargeting: targetingCopy[adUnitCode] }; - }).sort(sortByDealAndPriceBucket); + }).sort(sortByDealAndPriceBucketOrCpm()); // iterate through the targeting based on above list and transform the keys into the query-equivalent and count characters return targetingMap.reduce(function (accMap, currMap, index, arr) { - let adUnitQueryString = convertKeysToQueryForm(currMap.adUnitTargeting); + let adUnitQueryString = convertKeysToQueryForm(currMap.adserverTargeting); // for the last adUnit - trim last encoded ampersand from the converted query string if ((index + 1) === arr.length) { diff --git a/src/url.js b/src/url.js deleted file mode 100644 index c63bca2ca41..00000000000 --- a/src/url.js +++ /dev/null @@ -1,55 +0,0 @@ -export function parseQS(query) { - return !query ? {} : query - .replace(/^\?/, '') - .split('&') - .reduce((acc, criteria) => { - let [k, v] = criteria.split('='); - if (/\[\]$/.test(k)) { - k = k.replace('[]', ''); - acc[k] = acc[k] || []; - acc[k].push(v); - } else { - acc[k] = v || ''; - } - return acc; - }, {}); -} - -export function formatQS(query) { - return Object - .keys(query) - .map(k => Array.isArray(query[k]) - ? query[k].map(v => `${k}[]=${v}`).join('&') - : `${k}=${query[k]}`) - .join('&'); -} - -export function parse(url, options) { - let parsed = document.createElement('a'); - if (options && 'noDecodeWholeURL' in options && options.noDecodeWholeURL) { - parsed.href = url; - } 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: (qsAsString) ? parsed.search : parseQS(parsed.search || ''), - hash: (parsed.hash || '').replace(/^#/, ''), - host: parsed.host || window.location.host - }; -} - -export function format(obj) { - return (obj.protocol || 'http') + '://' + - (obj.host || - obj.hostname + (obj.port ? `:${obj.port}` : '')) + - (obj.pathname || '') + - (obj.search ? `?${formatQS(obj.search || '')}` : '') + - (obj.hash ? `#${obj.hash}` : ''); -} diff --git a/src/userSync.js b/src/userSync.js index fb5f4172a0d..fceeb1d722d 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -1,23 +1,28 @@ -import * as utils from './utils'; -import { config } from './config'; -import includes from 'core-js/library/fn/array/includes'; +import * as utils from './utils.js'; +import { config } from './config.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { getCoreStorageManager } from './storageManager.js'; + +export const USERSYNC_DEFAULT_CONFIG = { + syncEnabled: true, + filterSettings: { + image: { + bidders: '*', + filter: 'include' + } + }, + syncsPerBidder: 5, + syncDelay: 3000, + auctionDelay: 0 +}; // Set userSync default values config.setDefaults({ - 'userSync': { - syncEnabled: true, - filterSettings: { - image: { - bidders: '*', - filter: 'include' - } - }, - syncsPerBidder: 5, - syncDelay: 3000, - auctionDelay: 0 - } + 'userSync': utils.deepClone(USERSYNC_DEFAULT_CONFIG) }); +const storage = getCoreStorageManager('usersync'); + /** * Factory function which creates a new UserSyncPool. * @@ -45,6 +50,20 @@ export function newUserSync(userSyncDependencies) { let usConfig = userSyncDependencies.config; // Update if it's (re)set config.getConfig('userSync', (conf) => { + // Added this logic for https://github.com/prebid/Prebid.js/issues/4864 + // if userSync.filterSettings does not contain image/all configs, merge in default image config to ensure image pixels are fired + if (conf.userSync) { + let fs = conf.userSync.filterSettings; + if (utils.isPlainObject(fs)) { + if (!fs.image && !fs.all) { + conf.userSync.filterSettings.image = { + bidders: '*', + filter: 'include' + }; + } + } + } + usConfig = Object.assign(usConfig, conf.userSync); }); @@ -283,7 +302,7 @@ export function newUserSync(userSyncDependencies) { return publicApi; } -const browserSupportsCookies = !utils.isSafariBrowser() && utils.cookiesAreEnabled(); +const browserSupportsCookies = !utils.isSafariBrowser() && storage.cookiesAreEnabled(); export const userSync = newUserSync({ config: config.getConfig('userSync'), diff --git a/src/utils.js b/src/utils.js index d3f37e5c7b5..f0fa57c4cff 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,11 +1,12 @@ -import { config } from './config'; +/* eslint-disable no-console */ +import { config } from './config.js'; import clone from 'just-clone'; -import find from 'core-js/library/fn/array/find'; -import includes from 'core-js/library/fn/array/includes'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; -const CONSTANTS = require('./constants'); +const CONSTANTS = require('./constants.json'); -export { default as deepAccess } from 'dlv/index'; +export { default as deepAccess } from 'dlv/index.js'; export { default as deepSetValue } from 'dset'; var tArr = 'Array'; @@ -35,7 +36,10 @@ export const internal = { logError, logWarn, logMessage, - logInfo + logInfo, + parseQS, + formatQS, + deepEqual }; var uniqueRef = {}; @@ -49,28 +53,6 @@ export let bind = function(a, b) { return b; }.bind(null, 1, uniqueRef)() === un }; }; -/* - * Substitutes into a string from a given map using the token - * Usage - * var str = 'text %%REPLACE%% this text with %%SOMETHING%%'; - * var map = {}; - * map['replace'] = 'it was subbed'; - * map['something'] = 'something else'; - * console.log(replaceTokenInString(str, map, '%%')); => "text it was subbed this text with something else" - */ -export function replaceTokenInString(str, map, token) { - _each(map, function (value, key) { - value = (value === undefined) ? '' : value; - - var keyString = token + key.toUpperCase() + token; - var re = new RegExp(keyString, 'g'); - - str = str.replace(re, value); - }); - - return str; -} - /* utility method to get incremental integer starting from 1 */ var getIncrementalInteger = (function () { var count = 0; @@ -119,7 +101,7 @@ export function getBidIdParameter(key, paramsObj) { export function tryAppendQueryString(existingUrl, key, value) { if (value) { - return existingUrl += key + '=' + encodeURIComponent(value) + '&'; + return existingUrl + key + '=' + encodeURIComponent(value) + '&'; } return existingUrl; @@ -127,12 +109,13 @@ export function tryAppendQueryString(existingUrl, key, value) { // parse a query string object passed in bid params // bid params should be an object such as {key: "value", key1 : "value1"} +// aliases to formatQS export function parseQueryStringParameters(queryObj) { let result = ''; for (var k in queryObj) { if (queryObj.hasOwnProperty(k)) { result += k + '=' + encodeURIComponent(queryObj[k]) + '&'; } } - + result = result.replace(/&$/, ''); return result; } @@ -319,53 +302,9 @@ export function createInvisibleIframe() { * and if it does return the value */ export function getParameterByName(name) { - var regexS = '[\\?&]' + name + '=([^&#]*)'; - var regex = new RegExp(regexS); - var results = regex.exec(window.location.search); - if (results === null) { - return ''; - } - - return decodeURIComponent(results[1].replace(/\+/g, ' ')); -} - -/** - * This function validates paramaters. - * @param {Object} paramObj [description] - * @param {string[]} requiredParamsArr [description] - * @return {boolean} Bool if paramaters are valid - */ -export function hasValidBidRequest(paramObj, requiredParamsArr, adapter) { - var found = false; - - function findParam(value, key) { - if (key === requiredParamsArr[i]) { - found = true; - } - } - - for (var i = 0; i < requiredParamsArr.length; i++) { - found = false; - - _each(paramObj, findParam); - - if (!found) { - logError('Params are missing for bid request. One of these required paramaters are missing: ' + requiredParamsArr, adapter); - return false; - } - } - - return true; + return parseQS(getWindowLocation().search)[name] || ''; } -// Handle addEventListener gracefully in older browsers -export function addEventHandler(element, event, func) { - if (element.addEventListener) { - element.addEventListener(event, func, true); - } else if (element.attachEvent) { - element.attachEvent('on' + event, func); - } -} /** * Return if the object is of the * given type. @@ -470,15 +409,6 @@ export function contains(a, obj) { return false; } -export let indexOf = (function () { - if (Array.prototype.indexOf) { - return Array.prototype.indexOf; - } - - // ie8 no longer supported - // return polyfills.indexOf; -}()); - /** * Map an array or object into another array * given a function @@ -646,32 +576,6 @@ export function createTrackPixelIframeHtml(url, encodeUri = true, sandbox = '') `; } -/** - * Returns iframe document in a browser agnostic way - * @param {Object} iframe reference - * @return {Object} iframe `document` reference - */ -export function getIframeDocument(iframe) { - if (!iframe) { - return; - } - - let doc; - try { - if (iframe.contentWindow) { - doc = iframe.contentWindow.document; - } else if (iframe.contentDocument.document) { - doc = iframe.contentDocument.document; - } else { - doc = iframe.contentDocument; - } - } catch (e) { - internal.logError('Cannot get iframe document', e); - } - - return doc; -} - export function getValueString(param, val, defaultValue) { if (val === undefined || val === null) { return defaultValue; @@ -791,16 +695,6 @@ export function adUnitsFilter(filter, bid) { return includes(filter, bid && bid.adUnitCode); } -/** - * Check if parent iframe of passed document supports content rendering via 'srcdoc' property - * @param {HTMLDocument} doc document to check support of 'srcdoc' - */ -export function isSrcdocSupported(doc) { - // Firefox is excluded due to https://bugzilla.mozilla.org/show_bug.cgi?id=1265961 - return doc.defaultView && doc.defaultView.frameElement && - 'srcdoc' in doc.defaultView.frameElement && !/firefox/i.test(navigator.userAgent); -} - export function deepClone(obj) { return clone(obj); } @@ -814,7 +708,7 @@ export function inIframe() { } export function isSafariBrowser() { - return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + return /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); } export function replaceAuctionPrice(str, cpm) { @@ -826,37 +720,20 @@ export function timestamp() { return new Date().getTime(); } -export function checkCookieSupport() { - if (window.navigator.cookieEnabled || !!document.cookie.length) { - return true; - } -} -export function cookiesAreEnabled() { - if (internal.checkCookieSupport()) { - return true; - } - window.document.cookie = 'prebid.cookieTest'; - return window.document.cookie.indexOf('prebid.cookieTest') != -1; -} - -export function getCookie(name) { - let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)'); - return m ? decodeURIComponent(m[2]) : null; -} - -export function setCookie(key, value, expires, sameSite) { - document.cookie = `${key}=${encodeURIComponent(value)}${(expires !== '') ? `; expires=${expires}` : ''}; path=/${sameSite ? `; SameSite=${sameSite}` : ''}`; +/** + * When the deviceAccess flag config option is false, no cookies should be read or set + * @returns {boolean} + */ +export function hasDeviceAccess() { + return config.getConfig('deviceAccess') !== false; } /** - * @returns {boolean} + * @returns {(boolean|undefined)} */ -export function localStorageIsEnabled () { - try { - localStorage.setItem('prebid.cookieTest', '1'); - return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) { - return false; +export function checkCookieSupport() { + if (window.navigator.cookieEnabled || !!document.cookie.length) { + return true; } } @@ -899,19 +776,6 @@ export function groupBy(xs, key) { }, {}); } -/** - * Returns content for a friendly iframe to execute a URL in script tag - * @param {string} url URL to be executed in a script tag in a friendly iframe - * and are macros left to be replaced if required - */ -export function createContentToExecuteExtScriptInFriendlyFrame(url) { - if (!url) { - return ''; - } - - return ``; -} - /** * Build an object consisting of only defined parameters to avoid creating an * object with defined keys and undefined values. @@ -1014,6 +878,24 @@ export function isSlotMatchingAdUnitCode(adUnitCode) { return (slot) => compareCodeAndSlot(slot, adUnitCode); } +/** + * @summary Uses the adUnit's code in order to find a matching gptSlot on the page + */ +export function getGptSlotInfoForAdUnitCode(adUnitCode) { + let matchingSlot; + if (isGptPubadsDefined()) { + // find the first matching gpt slot on the page + matchingSlot = find(window.googletag.pubads().getSlots(), isSlotMatchingAdUnitCode(adUnitCode)); + } + if (matchingSlot) { + return { + gptSlot: matchingSlot.getAdUnitPath(), + divId: matchingSlot.getSlotElementId() + } + } + return {}; +}; + /** * Constructs warning message for when unsupported bidders are dropped from an adunit * @param {Object} adUnit ad unit from which the bidder is being dropped @@ -1030,18 +912,6 @@ export function unsupportedBidderMessage(adUnit, bidder) { `; } -/** - * 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; -} - /** * Checks input is integer or not * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger @@ -1178,32 +1048,6 @@ export function convertTypes(types, params) { return params; } -export function setDataInLocalStorage(key, value) { - if (hasLocalStorage()) { - window.localStorage.setItem(key, value); - } -} - -export function getDataFromLocalStorage(key) { - if (hasLocalStorage()) { - return window.localStorage.getItem(key); - } -} - -export function removeDataFromLocalStorage(key) { - if (hasLocalStorage()) { - window.localStorage.removeItem(key); - } -} - -export function hasLocalStorage() { - try { - return !!window.localStorage; - } catch (e) { - logError('Local storage api disabled'); - } -} - export function isArrayOfNums(val, size) { return (isArray(val)) && ((size) ? val.length === size : true) && (val.every(v => isInteger(v))); } @@ -1266,3 +1110,108 @@ export function compareOn(property) { return 0; } } + +export function parseQS(query) { + return !query ? {} : query + .replace(/^\?/, '') + .split('&') + .reduce((acc, criteria) => { + let [k, v] = criteria.split('='); + if (/\[\]$/.test(k)) { + k = k.replace('[]', ''); + acc[k] = acc[k] || []; + acc[k].push(v); + } else { + acc[k] = v || ''; + } + return acc; + }, {}); +} + +export function formatQS(query) { + return Object + .keys(query) + .map(k => Array.isArray(query[k]) + ? query[k].map(v => `${k}[]=${v}`).join('&') + : `${k}=${query[k]}`) + .join('&'); +} + +export function parseUrl(url, options) { + let parsed = document.createElement('a'); + if (options && 'noDecodeWholeURL' in options && options.noDecodeWholeURL) { + parsed.href = url; + } 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: (qsAsString) ? parsed.search : internal.parseQS(parsed.search || ''), + hash: (parsed.hash || '').replace(/^#/, ''), + host: parsed.host || window.location.host + }; +} + +export function buildUrl(obj) { + return (obj.protocol || 'http') + '://' + + (obj.host || + obj.hostname + (obj.port ? `:${obj.port}` : '')) + + (obj.pathname || '') + + (obj.search ? `?${internal.formatQS(obj.search || '')}` : '') + + (obj.hash ? `#${obj.hash}` : ''); +} + +/** + * This function deeply compares two objects checking for their equivalence. + * @param {Object} obj1 + * @param {Object} obj2 + * @returns {boolean} + */ +export function deepEqual(obj1, obj2) { + if (obj1 === obj2) return true; + else if ((typeof obj1 === 'object' && obj1 !== null) && (typeof obj2 === 'object' && obj2 !== null)) { + if (Object.keys(obj1).length !== Object.keys(obj2).length) return false; + for (let prop in obj1) { + if (obj2.hasOwnProperty(prop)) { + if (!deepEqual(obj1[prop], obj2[prop])) { + return false; + } + } else { + return false; + } + } + return true; + } else { + return false; + } +} + +export function mergeDeep(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isPlainObject(target) && isPlainObject(source)) { + for (const key in source) { + if (isPlainObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else if (isArray(source[key])) { + if (!target[key]) { + Object.assign(target, { [key]: source[key] }); + } else if (isArray(target[key])) { + target[key] = target[key].concat(source[key]); + } + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return mergeDeep(target, ...sources); +} diff --git a/src/video.js b/src/video.js index 57f44a76764..befeb2ded39 100644 --- a/src/video.js +++ b/src/video.js @@ -1,8 +1,8 @@ -import adapterManager from './adapterManager'; -import { getBidRequest, deepAccess, logError } from './utils'; -import { config } from '../src/config'; -import includes from 'core-js/library/fn/array/includes'; -import { hook } from './hook'; +import adapterManager from './adapterManager.js'; +import { getBidRequest, deepAccess, logError } from './utils.js'; +import { config } from '../src/config.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { hook } from './hook.js'; const VIDEO_MEDIA_TYPE = 'video'; export const OUTSTREAM = 'outstream'; diff --git a/src/videoCache.js b/src/videoCache.js index 4a715cb1fe3..9f1fd7e4117 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -9,8 +9,9 @@ * This trickery helps integrate with ad servers, which set character limits on request params. */ -import { ajax } from './ajax'; -import { config } from '../src/config'; +import { ajax } from './ajax.js'; +import { config } from './config.js'; +import * as utils from './utils.js'; /** * @typedef {object} CacheableUrlBid @@ -70,6 +71,10 @@ function toStorageRequest(bid) { if (config.getConfig('cache.vasttrack')) { payload.bidder = bid.bidder; payload.bidid = bid.requestId; + // function has a thisArg set to bidderRequest for accessing the auctionStart + if (utils.isPlainObject(this) && this.hasOwnProperty('auctionStart')) { + payload.timestamp = this.auctionStart; + } } if (typeof bid.customCacheKey === 'string' && bid.customCacheKey !== '') { @@ -126,11 +131,12 @@ function shimStorageCallback(done) { * * @param {CacheableBid[]} bids A list of bid objects which should be cached. * @param {videoCacheStoreCallback} [done] An optional callback which should be executed after + * @param {BidderRequest} [bidderRequest] * the data has been stored in the cache. */ -export function store(bids, done) { +export function store(bids, done, bidderRequest) { const requestData = { - puts: bids.map(toStorageRequest) + puts: bids.map(toStorageRequest, bidderRequest) }; ajax(config.getConfig('cache.url'), shimStorageCallback(done), JSON.stringify(requestData), { diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 4cd5a854ac4..842bccd99b1 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -35,6 +35,6 @@ module.exports = { "no-unused-vars": "off", "no-use-before-define": "off", "no-useless-escape": "off", - "one-var": "off", + "one-var": "off" } }; diff --git a/test/fake-server/README.md b/test/fake-server/README.md new file mode 100644 index 00000000000..2e93fa44947 --- /dev/null +++ b/test/fake-server/README.md @@ -0,0 +1,28 @@ +## fake-server + +A simple http server that matches incoming requests to a stored list of `request-response` pair, and returns a fake response. The server is meant to replace actual calls to `appnexus` adapter (extendable to any other adapter) endpoint when the e2e tests runs. + + +## How to add a Request - Response pair ? + +All the `request-response` pairs are stored in the `fixtures/` directory. They are organized by their test group and test name. + +Follow the steps below to add another `request-response` pair. + +1. Inside the `/fixtures` directory, create a directory and give it a suitable name. + - If you are creating a one-off type of test, you can name this directory with a name that describes the test; for example `basic-banner`. + - If you plan to create a series of tests focusing on one feature/topic, then you can create a generic container directory to hold all your tests together; for example `longform`. + - If you did the latter case, please proceed to create the necessary test directories describing them with a meaningful and **unique** test name. +2. If you are planning to handle multiple bidder requests as part of your tests, you will need to create a specific directory for each request. For example, you could create a pair named like so `longform_biddersettings_1` and `longform_biddersettings_2`. +3. Once all your directories are created, inside the bottom test folder(s), create **three files**: + - `description.md` (Contains path of test page and spec file. Also, contains the ad unit that generates the **request-response** pair) + - `request.json` (This object will be matched against the acutal incoming request) + - `response.json` (This object will be returned as response of the fake-server, if the response object's request pair matchest the incoming request) + +For reference, please have a look at `fixtures/basic-banner` or `fixtures/longform` directories (as matching your scenario). + +## How is the server initiated ? + +When the command `gulp e2e-test --host=test.localhost` is executed, gulp task `test` automatically spawns the `fake-server` which runs on port `4444`. + +On execution of the tests, the server automatically stops. \ No newline at end of file diff --git a/test/fake-server/fake-responder.js b/test/fake-server/fake-responder.js new file mode 100644 index 00000000000..c884b00ca9c --- /dev/null +++ b/test/fake-server/fake-responder.js @@ -0,0 +1,75 @@ +/* eslint no-console: 0 */ +const deepEqual = require('deep-equal'); +const generateFixtures = require('./fixtures'); +const path = require('path'); + +// path to the fixture directory +const fixturesPath = path.join(__dirname, 'fixtures'); + +// An object storing 'Request-Response' pairs. +let REQ_RES_MAP = generateFixtures(fixturesPath); + +/** + * Matches 'req.body' with the responseBody pair + * @param {object} requestBody - `req.body` of incoming request hitting middleware 'fakeResponder'. + * @returns {objct} responseBody + */ +const matchResponse = function (requestBody) { + let actualUuids = []; + + const requestResponsePairs = Object.keys(REQ_RES_MAP).map(testName => REQ_RES_MAP[testName]); + + // delete 'uuid' property + requestBody.tags.forEach(body => { + // store the 'uuid' before deleting it. + actualUuids.push(body.uuid); + + // delete the 'uuid' + delete body.uuid; + }); + + ['sdk', 'referrer_detection', 'gdpr_consent'].forEach(prop => { + if (requestBody && requestBody[prop]) { + delete requestBody[prop]; + } + }); + + // delete 'uuid' from `expected request body` + requestResponsePairs + .forEach(reqRes => { reqRes.request.httpRequest && reqRes.request.httpRequest.body.tags.forEach(body => body.uuid && delete body.uuid) }); + + // match the 'actual' requestBody with the 'expected' requestBody and find the 'responseBody' + const responseBody = requestResponsePairs.filter(reqRes => reqRes.request.httpRequest && deepEqual(reqRes.request.httpRequest.body.tags, requestBody.tags))[0].response.httpResponse.body; + + // ENABLE THE FOLLOWING CODE FOR TROUBLE-SHOOTING FAKED REQUESTS; COMMENT AGAIN WHEN DONE + // console.log('value found for responseBody:', responseBody); + // responseBody.tags.forEach((tag, index) => { + // console.log('value found for responseBody.tag[', index, ']:ads', tag.ads); + // }); + + // copy the actual uuids to the responseBody + // TODO:: what if responseBody is 'undefined' + responseBody.tags.forEach(body => { + body.uuid = actualUuids.shift(); + }); + + return responseBody; +} + +/** + * An ExpressJs middleware function that checks the incoming Request Body + * and returns the corresponding Fake Response Body pertaining to that Request. + */ + +const fakeResponder = function (req, res, next) { + const request = JSON.parse(req.body); + + const response = matchResponse(request); + + res.type('json'); + res.write(JSON.stringify(response)); + + next(); +} + +module.exports = fakeResponder; diff --git a/test/fake-server/fixtures/basic-banner/description.md b/test/fake-server/fixtures/basic-banner/description.md new file mode 100644 index 00000000000..45ef571aaac --- /dev/null +++ b/test/fake-server/fixtures/basic-banner/description.md @@ -0,0 +1,34 @@ +Test Page - 'test/pages/banner.html' +Test Spec File - 'test/spec/e2e/banner/basic_banner_ad.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] +}, { + code: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: "appnexus", + params: { + placementId: 13144370 + } + }] +}]; +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-banner/request.json b/test/fake-server/fixtures/basic-banner/request.json new file mode 100644 index 00000000000..ea85b5a6842 --- /dev/null +++ b/test/fake-server/fixtures/basic-banner/request.json @@ -0,0 +1,61 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 300, + "height": 250 + }, + { + "width": 300, + "height": 600 + } + ], + "primary_size": { + "width": 300, + "height": 250 + }, + "ad_types": [ + "banner" + ], + "id": 13144370, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 1 + }, + { + "sizes": [ + { + "width": 300, + "height": 250 + }, + { + "width": 300, + "height": 600 + } + ], + "primary_size": { + "width": 300, + "height": 250 + }, + "ad_types": [ + "banner" + ], + "id": 13144370, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 1 + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-banner/response.json b/test/fake-server/fixtures/basic-banner/response.json new file mode 100644 index 00000000000..dc73d3fa3d2 --- /dev/null +++ b/test/fake-server/fixtures/basic-banner/response.json @@ -0,0 +1,92 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "tag_id": 13144370, + "auction_id": "8147841645883553832", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKzCKAzBAAAAwDWAAUBCJKPpe4FEKjwl-js2byJcRiq5MnUovf28WEqNgkAAAkCABEJBywAABkAAACA61HgPyEREgApEQkAMREb8GkwsqKiBjjtSEDtSEgAUABYnPFbYABotc95eACAAQGKAQCSAQNVU0SYAawCoAH6AagBAbABALgBAcABAMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3MzQ3MjE0Nik7AR0scicsIDk2ODQ2MDM1Nh4A8PWSAqUCIXp6ZmhVQWl1c0s0S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlJSUNhQUJ3Q0hncWdBRWtpQUVxa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQVozRkl5WjA1Tm9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcnJDdUNyb0RDVk5KVGpNNk5EY3pOZUFEcUJXSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkEg0QkFDSUJmOGuaAokBIUl3OTBCOikBJG5QRmJJQVFvQUQROFhEZ1B6b0pVMGxPTXpvME56TTFRS2dWUxFoDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJTDTrYdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9oZWxsb193b3JsZAVO8EA_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAOsAsgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw248F6YBACiBAsxMC43NS43NC42OagEkECyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggA4AQB8ATTgZcuiAUBmAUAoAX______wEDFAHABQDJBWmAFPA_0gUJCQkMcAAA2AUB4AUB8AUB-gUECAAQAJAGAJgGALgGAMEGCSM08L_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgHyBgIIAIAHAYgHAKAHAQ..&s=68cfb6ed042ea47f5d3fc2c32cc068500e542066", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "banner", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 96846035, + "media_type_id": 1, + "media_subtype_id": 1, + "cpm": 0.5, + "cpm_publisher_currency": 0.5, + "is_bin_price_applied": false, + "publisher_currency_code": "$", + "brand_category_id": 0, + "test": 99999, + "client_initiated_ad_counting": true, + "rtb": { + "banner": { + "content": "
", + "width": 300, + "height": 250 + }, + "trackers": [ + { + "impression_urls": [ + "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue&e=wqT_3QK7CKA7BAAAAwDWAAUBCJKPpe4FEKjwl-js2byJcRiq5MnUovf28WEqNgkAAAECCOA_EQEHNAAA4D8ZAAAAgOtR4D8hERIAKREJADERG6gwsqKiBjjtSEDtSEgCUNOBly5YnPFbYABotc95eJK4BYABAYoBA1VTRJIBAQbwUpgBrAKgAfoBqAEBsAEAuAEBwAEEyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTczNDcyMTQ2KTt1ZigncicsIDk2ODQ2MDM1Nh4A8PWSAqUCIXp6ZmhVQWl1c0s0S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlJSUNhQUJ3Q0hncWdBRWtpQUVxa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQVozRkl5WjA1Tm9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcnJDdUNyb0RDVk5KVGpNNk5EY3pOZUFEcUJXSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkEg0QkFDSUJmOGuaAokBIUl3OTBCOikBJG5QRmJJQVFvQUQROFhEZ1B6b0pVMGxPTXpvME56TTFRS2dWUxFoDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJTDTrYdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9oZWxsb193b3JsZAVO8EA_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAOsAsgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw248F6YBACiBAsxMC43NS43NC42OagEkECyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggB4AQB8ATTgZcuiAUBmAUAoAX______wEDFAHABQDJBWmIFPA_0gUJCQkMcAAA2AUB4AUB8AUB-gUECAAQAJAGAJgGALgGAMEGCSM08D_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgHyBgIIAIAHAYgHAKAHAQ..&s=951a029669a69e3f0c527c937c2d852be92802e1" + ], + "video_events": {} + } + ] + } + } + ] + }, + { + "tag_id": 13144370, + "auction_id": "8147841645883553832", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKzCKAzBAAAAwDWAAUBCJKPpe4FEKjwl-js2byJcRiq5MnUovf28WEqNgkAAAkCABEJBywAABkAAACA61HgPyEREgApEQkAMREb8GkwsqKiBjjtSEDtSEgAUABYnPFbYABotc95eACAAQGKAQCSAQNVU0SYAawCoAH6AagBAbABALgBAcABAMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3MzQ3MjE0Nik7AR0scicsIDk2ODQ2MDM1Nh4A8PWSAqUCIXp6ZmhVQWl1c0s0S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlJSUNhQUJ3Q0hncWdBRWtpQUVxa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQVozRkl5WjA1Tm9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcnJDdUNyb0RDVk5KVGpNNk5EY3pOZUFEcUJXSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkEg0QkFDSUJmOGuaAokBIUl3OTBCOikBJG5QRmJJQVFvQUQROFhEZ1B6b0pVMGxPTXpvME56TTFRS2dWUxFoDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJTDTrYdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9oZWxsb193b3JsZAVO8EA_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAOsAsgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw248F6YBACiBAsxMC43NS43NC42OagEkECyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggA4AQB8ATTgZcuiAUBmAUAoAX______wEDFAHABQDJBWmAFPA_0gUJCQkMcAAA2AUB4AUB8AUB-gUECAAQAJAGAJgGALgGAMEGCSM08L_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgHyBgIIAIAHAYgHAKAHAQ..&s=68cfb6ed042ea47f5d3fc2c32cc068500e542066", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "banner", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 968460356666, + "media_type_id": 1, + "media_subtype_id": 1, + "cpm": 0.5, + "cpm_publisher_currency": 0.5, + "is_bin_price_applied": false, + "publisher_currency_code": "$", + "brand_category_id": 0, + "client_initiated_ad_counting": true, + "rtb": { + "banner": { + "content": "
", + "width": 300, + "height": 250 + }, + "trackers": [ + { + "impression_urls": [ + "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue&e=wqT_3QK7CKA7BAAAAwDWAAUBCJKPpe4FEKjwl-js2byJcRiq5MnUovf28WEqNgkAAAECCOA_EQEHNAAA4D8ZAAAAgOtR4D8hERIAKREJADERG6gwsqKiBjjtSEDtSEgCUNOBly5YnPFbYABotc95eJK4BYABAYoBA1VTRJIBAQbwUpgBrAKgAfoBqAEBsAEAuAEBwAEEyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTczNDcyMTQ2KTt1ZigncicsIDk2ODQ2MDM1Nh4A8PWSAqUCIXp6ZmhVQWl1c0s0S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlJSUNhQUJ3Q0hncWdBRWtpQUVxa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQVozRkl5WjA1Tm9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcnJDdUNyb0RDVk5KVGpNNk5EY3pOZUFEcUJXSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkEg0QkFDSUJmOGuaAokBIUl3OTBCOikBJG5QRmJJQVFvQUQROFhEZ1B6b0pVMGxPTXpvME56TTFRS2dWUxFoDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJTDTrYdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9oZWxsb193b3JsZAVO8EA_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAOsAsgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw248F6YBACiBAsxMC43NS43NC42OagEkECyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggB4AQB8ATTgZcuiAUBmAUAoAX______wEDFAHABQDJBWmIFPA_0gUJCQkMcAAA2AUB4AUB8AUB-gUECAAQAJAGAJgGALgGAMEGCSM08D_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgHyBgIIAIAHAYgHAKAHAQ..&s=951a029669a69e3f0c527c937c2d852be92802e1" + ], + "video_events": {} + } + ] + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-instream/description.md b/test/fake-server/fixtures/basic-instream/description.md new file mode 100644 index 00000000000..5d185f23ad3 --- /dev/null +++ b/test/fake-server/fixtures/basic-instream/description.md @@ -0,0 +1,28 @@ +Test Page - 'test/pages/instream.html' +Test Spec File - 'test/spec/e2e/instream/basic_instream_ad.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: '13232361', + video: { + skipppable: false, + playback_methods: ['auto_play_sound_off'] + } + } + } + ] +}]; +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-instream/request.json b/test/fake-server/fixtures/basic-instream/request.json new file mode 100644 index 00000000000..db07ad3e98a --- /dev/null +++ b/test/fake-server/fixtures/basic-instream/request.json @@ -0,0 +1,34 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 13232361, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "require_asset_url": true, + "video": {}, + "hb_source": 1 + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-instream/response.json b/test/fake-server/fixtures/basic-instream/response.json new file mode 100644 index 00000000000..5f51034f0be --- /dev/null +++ b/test/fake-server/fixtures/basic-instream/response.json @@ -0,0 +1,42 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [{ + "tag_id": 13232361, + "auction_id": "1398509384102899505", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Finstream.html&e=wqT_3QKxCKAxBAAAAwDWAAUBCKXQzPAFELHeg_SAnKC0ExjCs_b6q5D9_0oqNgkAAAkCABEJBywAABkAAADgehQUQCEREgApEQkAMREb8Hkw6dGnBjjtSEDtSEgAUABYnPFbYABozbp1eACAAQGKAQCSAQNVU0SYAQGgAQGoAQGwAQC4AQPAAQDIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NzgzMTM3NjUpO3VmKCdyJywgOTc1MTc3NzEsIC4eAPD1kgK1AiF5andtb2dpMi1Md0tFTXVCd0M0WUFDQ2M4VnN3QURnQVFBUkk3VWhRNmRHbkJsZ0FZTUlHYUFCd0tIZ0dnQUU4aUFFR2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFVUU1FQjg2MXFwQUFBRkVESkFmZjUwcVFyNGVvXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHR2aThDcm9EQ1ZOSlRqTTZORGN6T09BRC1SaUlCQUNRQkFDWUJBSEJCBUUJAQh5UVEJCQEBFE5nRUFQRRGNAZAsNEJBQ0lCWUlscVFVAREBFDx3UHcuLpoCiQEhTGc5WkRRNjkBJG5QRmJJQVFvQUQVSFRVUURvSlUwbE9Nem8wTnpNNFFQa1lTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBSZUFBLsICP2h0dHA6Ly9wcmViaWQub3JnL2Rldi1kb2NzL3Nob3ctdmlkZW8td2l0aC1hLWRmcC12aWRlby10YWcuaHRtbNgCAOACrZhI6gIzaHQFSkh0ZXN0LmxvY2FsaG9zdDo5OTk5BRQ4L3BhZ2VzL2luc3RyZWFtBT7IgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvCanwXpgEAKIECzEwLjc1Ljc0LjY5qAS0LLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzM42gQCCADgBADwBMuBwC6IBQGYBQCgBf______AQMUAcAFAMkFaX8U8D_SBQkJCQx4AADYBQHgBQHwBcOVC_oFBAgAEACQBgGYBgC4BgDBBgklKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=6805157848bdfdf973d0dbafff4bb9b4c4ce7e60", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [{ + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQklKBNeAAAAABEx74AO4IBoExklKBNeAAAAACDLgcAuKAAw7Ug47UhA0-hISLuv1AFQ6dGnBljDlQtiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAcuBwC6wAQE.&s=07e6e5f2f03cc92e899c3ddbf4e2988e966caaa2&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 97517771, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10.000000, + "cpm_publisher_currency": 10.000000, + "publisher_currency_code": "$", + "brand_category_id": 36, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": ["auto_play_sound_on"], + "frameworks": ["vpaid_1_0", "vpaid_2_0"], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Finstream.html&e=wqT_3QKNCaCNBAAAAwDWAAUBCKXQzPAFELHeg_SAnKC0ExjCs_b6q5D9_0oqNgkAAAECCBRAEQEHNAAAFEAZAAAA4HoUFEAhERIAKREJADERG6gw6dGnBjjtSEDtSEgCUMuBwC5YnPFbYABozbp1eJG4BYABAYoBA1VTRJIBAQbwUpgBAaABAagBAbABALgBA8ABBMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3ODMxMzc2NSk7dWYoJ3InLCA5NzUxNzc3MSwgLh4A8PWSArUCIXlqd21vZ2kyLUx3S0VNdUJ3QzRZQUNDYzhWc3dBRGdBUUFSSTdVaFE2ZEduQmxnQVlNSUdhQUJ3S0hnR2dBRThpQUVHa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh6cldxa0FBQVVRTUVCODYxcXBBQUFGRURKQWZmNTBxUXI0ZW9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEdHZpOENyb0RDVk5KVGpNNk5EY3pPT0FELVJpSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkCw0QkFDSUJZSWxxUVUBEQEUPHdQdy4umgKJASFMZzlaRFE2OQEkblBGYklBUW9BRBVIVFVRRG9KVTBsT016bzBOek00UVBrWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8FJlQUEuwgI_aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc2hvdy12aWRlby13aXRoLWEtZGZwLXZpZGVvLXRhZy5odG1s2AIA4AKtmEjqAjNodAVKSHRlc3QubG9jYWxob3N0Ojk5OTkFFDgvcGFnZXMvaW5zdHJlYW0FPmjyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-4ElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw398F6YBACiBAsxMC43NS43NC42OagEtCyyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczONoEAggB4AQA8ATLgcAuiAUBmAUAoAX______wEDFAHABQDJBWnbFPA_0gUJCQkMeAAA2AUB4AUB8AXDlQv6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=68b9d39d60a72307a201e479000a8c7be5508188" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-native/description.md b/test/fake-server/fixtures/basic-native/description.md new file mode 100644 index 00000000000..950cc5da7d7 --- /dev/null +++ b/test/fake-server/fixtures/basic-native/description.md @@ -0,0 +1,62 @@ +Test Page - 'test/pages/native.html' +Test Spec File - 'test/spec/e2e/native/basic_native_ad.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: '/19968336/prebid_native_example_1', + sizes: [360, 360], + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13232354, + allowSmallerSizes: true + } + }] +}, { + code: '/19968336/prebid_native_example_2', + sizes: [ + [1, 1] + ], + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + }, + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13232354, + allowSmallerSizes: true + } + }] +}]; +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-native/request.json b/test/fake-server/fixtures/basic-native/request.json new file mode 100644 index 00000000000..08e75d5bd69 --- /dev/null +++ b/test/fake-server/fixtures/basic-native/request.json @@ -0,0 +1,81 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 1, + "height": 1 + } + ], + "ad_types": [ + "native" + ], + "id": 13232354, + "allow_smaller_sizes": true, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "native": { + "layouts": [ + { + "title": { + "required": true + }, + "main_image": { + "required": true + }, + "sponsored_by": { + "required": true + } + } + ] + }, + "hb_source": 1 + }, + { + "sizes": [ + { + "width": 1, + "height": 1 + } + ], + "ad_types": [ + "native" + ], + "id": 13232354, + "allow_smaller_sizes": true, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "native": { + "layouts": [ + { + "title": { + "required": true + }, + "description": { + "required": true + }, + "main_image": { + "required": true + }, + "sponsored_by": { + "required": true + }, + "icon": { + "required": false + } + } + ] + }, + "hb_source": 1 + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-native/response.json b/test/fake-server/fixtures/basic-native/response.json new file mode 100644 index 00000000000..fb85110663d --- /dev/null +++ b/test/fake-server/fixtures/basic-native/response.json @@ -0,0 +1,116 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "tag_id": 13232354, + "auction_id": "2566965852006062421", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fnative.html&e=wqT_3QLhB6DhAwAAAwDWAAUBCNDZme8FENWyhPv4uuzPIxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQgkQCEJCQgAACkRCQAxCQnwaSRAMOLRpwY47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEBwAEAyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTc1MzgyMjI0KTsBHThyJywgOTc0OTQ0MDMsIDEdHvDQkgKpAiFCenhPTlFqOC1Md0tFSVBMdmk0WUFDQ2M4VnN3QURnQVFBUkk3VWhRNHRHbkJsZ0FZSUlDYUFCd0FIZ0FnQUdtQVlnQl9GLVFBUUNZQVFDZ0FRR29BUU93QVFDNUFmT3RhcVFBQUNSQXdRSHpyV3FrQUFBa1FNa0JRTDdBTHVfbzFqX1pBUUFBQUFBQUFQQV80QUVBOVFFQUFBQUFtQUlBb0FJQXRRSUFBQUFBdlFJQUFBQUE0QUlBNkFJQS1BSUFnQU1CbUFNQnFBUDgBxIh1Z01KVTBsT016bzBOelF5NEFQbUZvZ0VBSkFFQUpnRUFjRQldBQEIREpCBQgJARgyQVFBOFFRCQ0BAVBQZ0VBSWdGaGlVLpoCiQEhYWdfb0o6LQEkblBGYklBUW9BRBVYDGtRRG8yhQAQUU9ZV1MRWAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0MoGVBQS7YAgDgAq2YSOoCMWh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5BRTwvC9wYWdlcy9uYXRpdmUuaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECzEwLjc1Ljc0LjY5qATruAKyBA4IABABGAAgACgAMAA4ArgEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQy2gQCCADgBAHwBIPLvi6IBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQkMeAAA2AUB4AUB8AWZ9CH6BQQIABAAkAYBmAYAuAYAwQYJJTTwv8gGANAG9S_aBhYKEAkUGQFQEAAYAOAGDPIGAggAgAcBiAcAoAdB&s=54514bb848c51d509dfe3e21af09b77edfe9738e", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "native", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 97494403, + "media_type_id": 12, + "media_subtype_id": 65, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 53, + "client_initiated_ad_counting": true, + "viewability": { + "config": "" + }, + "rtb": { + "native": { + "title": "This is a Prebid Native Creative", + "sponsored": "Prebid.org", + "main_img": { + "url": "http://vcdn.adnxs.com/p/creative-image/94/22/cd/0f/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg", + "width": 3000, + "height": 2250, + "prevent_crop": false + }, + "link": { + "url": "http://prebid.org/dev-docs/show-native-ads.html", + "click_trackers": [ + "http://sin3-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQFUZYY_XsZ8jKnKSKrrb42HQbOZdAAAAAOLoyQBtJAAAbSQAAAIAAACDpc8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAQQCAAAAALoA8BZszgAAAAA./bcr=AAAAAAAA8D8=/cnd=%21ag_oJQj8-LwKEIPLvi4YnPFbIAQoADEAAAAAAAAkQDoJU0lOMzo0NzQyQOYWSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeAA./cca=OTMyNSNTSU4zOjQ3NDI=/bn=89169/" + ] + }, + "impression_trackers": [ + "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fnative.html&e=wqT_3QLpB6DpAwAAAwDWAAUBCNDZme8FENWyhPv4uuzPIxiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlCDy74uWJzxW2AAaM26dXjRuAWAAQGKAQNVU0SSAQEG8FKYAQGgAQGoAQGwAQC4AQHAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NzUzODIyMjQpO3VmKCdyJywgOTc0OTQ0MDMsIC4eAPDQkgKpAiFCenhPTlFqOC1Md0tFSVBMdmk0WUFDQ2M4VnN3QURnQVFBUkk3VWhRNHRHbkJsZ0FZSUlDYUFCd0FIZ0FnQUdtQVlnQl9GLVFBUUNZQVFDZ0FRR29BUU93QVFDNUFmT3RhcVFBQUNSQXdRSHpyV3FrQUFBa1FNa0JRTDdBTHVfbzFqX1pBUUFBQUFBQUFQQV80QUVBOVFFQUFBQUFtQUlBb0FJQXRRSUFBQUFBdlFJQUFBQUE0QUlBNkFJQS1BSUFnQU1CbUFNQnFBUDgBxIh1Z01KVTBsT016bzBOelF5NEFQbUZvZ0VBSkFFQUpnRUFjRQldBQEIREpCBQgJARgyQVFBOFFRCQ0BAVBQZ0VBSWdGaGlVLpoCiQEhYWdfb0o6LQEkblBGYklBUW9BRBVYDGtRRG8yhQAQUU9ZV1MRWAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0MoGVBQS7YAgDgAq2YSOoCMWh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5BRTwlS9wYWdlcy9uYXRpdmUuaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECzEwLjc1Ljc0LjY5qATruAKyBA4IABABGAAgACgAMAA4ArgEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQy2gQCCAHgBAHwBEH6IIgFAZgFAKAF_xEBGAHABQDJBQAFARTwP9IFCQkFC3wAAADYBQHgBQHwBZn0IfoFBAgAEACQBgGYBgC4BgDBBgEhQAAA8D_IBgDQBvUv2gYWChAAOgEAUBAAGADgBgzyBgIIAIAHAYgHAKAHQQ..&s=6b203c7aee654cffa9c0b3771f6945f6d1e8d06c" + ], + "id": 97494403 + } + } + } + ] + }, + { + "tag_id": 13232354, + "auction_id": "6083251961435599864", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fnative.html&e=wqT_3QLhB6DhAwAAAwDWAAUBCNDZme8FEPjnxITbrYO2VBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQgkQCEJCQgAACkRCQAxCQnwaSRAMOLRpwY47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEBwAEAyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTc1MzgyMjI0KTsBHSxyJywgOTc0OTQyMDQ2HgDw0JICqQIhYlR3OWZBajgtTHdLRUx6SnZpNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUTR0R25CbGdBWUlJQ2FBQndBSGdBZ0FHbUFZZ0JfRi1RQVFDWUFRQ2dBUUdvQVFPd0FRQzVBZk90YXFRQUFDUkF3UUh6cldxa0FBQWtRTWtCeVdmYjBYeWIxVF9aQVFBQUFBQUFBUEFfNEFFQTlRRUFBQUFBbUFJQW9BSUF0UUlBQUFBQXZRSUFBQUFBNEFJQTZBSUEtQUlBZ0FNQm1BTUJxQVA4AcSIdWdNSlUwbE9Nem8wTnpReTRBUG1Gb2dFQUpBRUFKZ0VBY0UJXQUBCERKQgUICQEYMkFRQThRUQkNAQFUUGdFQUlnRmhpVS6aAokBIW9ROTNPUTYtASRuUEZiSUFRb0FEFVgMa1FEbzKFABBRT1lXUxFYDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQygZUFBLtgCAOACrZhI6gIxaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkFFPC8L3BhZ2VzL25hdGl2ZS5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBOu4ArIEDggAEAEYACAAKAAwADgCuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDLaBAIIAOAEAfAEvMm-LogFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJCQx4AADYBQHgBQHwBZn0IfoFBAgAEACQBgGYBgC4BgDBBgklNPC_yAYA0Ab1L9oGFgoQCRQZAVAQABgA4AYM8gYCCACABwGIBwCgB0E.&s=99a73b39ab82dd9384eee306ff03276ab688cfe5", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "native", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 97494204, + "media_type_id": 12, + "media_subtype_id": 65, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 53, + "client_initiated_ad_counting": true, + "viewability": { + "config": "" + }, + "rtb": { + "native": { + "title": "This is a Prebid Native Creative", + "desc": "This is a Prebid Native Creative. There are many like it, but this one is mine.", + "sponsored": "Prebid.org", + "icon": { + "url": "http://vcdn.adnxs.com/p/creative-image/1a/3e/e9/5b/1a3ee95b-06cd-4260-98c7-0258627c9197.png", + "width": 127, + "height": 83, + "prevent_crop": false + }, + "main_img": { + "url": "http://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg", + "width": 989, + "height": 742, + "prevent_crop": false + }, + "link": { + "url": "http://prebid.org/dev-docs/show-native-ads.html", + "click_trackers": [ + "http://sin3-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQPgzkbBtDWxUKnKSKrrb42HQbOZdAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAQQCAAAAALoAJhcX3AAAAAA./bcr=AAAAAAAA8D8=/cnd=%21oQ93OQj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJU0lOMzo0NzQyQOYWSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeAA./cca=OTMyNSNTSU4zOjQ3NDI=/bn=89169/" + ] + }, + "impression_trackers": [ + "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fnative.html&e=wqT_3QLpB6DpAwAAAwDWAAUBCNDZme8FEPjnxITbrYO2VBiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXjRuAWAAQGKAQNVU0SSAQEG8FKYAQGgAQGoAQGwAQC4AQHAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NzUzODIyMjQpO3VmKCdyJywgOTc0OTQyMDQsIC4eAPDQkgKpAiFiVHc5ZkFqOC1Md0tFTHpKdmk0WUFDQ2M4VnN3QURnQVFBUkk3VWhRNHRHbkJsZ0FZSUlDYUFCd0FIZ0FnQUdtQVlnQl9GLVFBUUNZQVFDZ0FRR29BUU93QVFDNUFmT3RhcVFBQUNSQXdRSHpyV3FrQUFBa1FNa0J5V2ZiMFh5YjFUX1pBUUFBQUFBQUFQQV80QUVBOVFFQUFBQUFtQUlBb0FJQXRRSUFBQUFBdlFJQUFBQUE0QUlBNkFJQS1BSUFnQU1CbUFNQnFBUDgBxIh1Z01KVTBsT016bzBOelF5NEFQbUZvZ0VBSkFFQUpnRUFjRQldBQEIREpCBQgJARgyQVFBOFFRCQ0BAVRQZ0VBSWdGaGlVLpoCiQEhb1E5M09RNi0BJG5QRmJJQVFvQUQVWAxrUURvMoUAEFFPWVdTEVgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDKBlQUEu2AIA4AKtmEjqAjFodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OQUU8LwvcGFnZXMvbmF0aXZlLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagE67gCsgQOCAAQARgAIAAoADAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDc0MtoEAggB4AQB8AS8yb4uiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSU48D_IBgDQBvUv2gYWChAAOgEAUBAAGADgBgzyBgIIAIAHAYgHAKAHQQ..&s=5c316c116b6e2bdb19f3950c4c769821e735688e" + ], + "id": 97494204 + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-outstream/description.md b/test/fake-server/fixtures/basic-outstream/description.md new file mode 100644 index 00000000000..c5855d6af15 --- /dev/null +++ b/test/fake-server/fixtures/basic-outstream/description.md @@ -0,0 +1,44 @@ +Test Page - 'test/pages/outstream.html' +Test Spec File - 'test/spec/e2e/outstream/basic_outstream_ad.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'video_ad_unit_1', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }] +}, { + code: 'video_ad_unit_2', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }] +}]; +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-outstream/request.json b/test/fake-server/fixtures/basic-outstream/request.json new file mode 100644 index 00000000000..611a518fc2d --- /dev/null +++ b/test/fake-server/fixtures/basic-outstream/request.json @@ -0,0 +1,50 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [{ + "sizes": [{ + "width": 640, + "height": 480 + }], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": ["video"], + "id": 13232385, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "video": { + "skippable": true, + "playback_method": ["auto_play_sound_off"] + }, + "hb_source": 1 + }, { + "sizes": [{ + "width": 640, + "height": 480 + }], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": ["video"], + "id": 13232385, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "video": { + "skippable": true, + "playback_method": ["auto_play_sound_off"] + }, + "hb_source": 1 + }], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/basic-outstream/response.json b/test/fake-server/fixtures/basic-outstream/response.json new file mode 100644 index 00000000000..13b4e527b1f --- /dev/null +++ b/test/fake-server/fixtures/basic-outstream/response.json @@ -0,0 +1,105 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "tag_id": 13232385, + "auction_id": "527250675737245396", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Foutstream.html&e=wqT_3QKwCKAwBAAAAwDWAAUBCOiWpO8FENTtnJej9sqoBxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQgkQCEJCQgAACkRCQAxCQnwaSRAMIHSpwY47UhA7UhIAFAAWJzxW2AAaOWljwF4AIABAYoBAJIBA1VTRJgBAaABAagBAbABALgBA8ABAMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3NTU1Mzg5NikFHTRyJywgOTc1MTc3NzEsIC4eAPCakgK1AiFUenpuS1FqWS1Md0tFTXVCd0M0WUFDQ2M4VnN3QURnQVFBUkk3VWhRZ2RLbkJsZ0FZSUlDYUFCd0FIZ0FnQUhBQVlnQkFKQUJBSmdCQUtBQkFhZ0JBN0FCQUxrQjg2MXFwQUFBSkVEQkFmT3RhcVFBQUNSQXlRSG5NQW5aODYzalA5a0JBQUFBQUFBQThEX2dBUUQxQVEBDyxDWUFnQ2dBZ0MxQWcFEAA5CQjwQERnQWdEb0FnRDRBZ0NBQXdHWUF3R29BOWo0dkFxNkF3bFRTVTR6T2pRNE16VGdBX2tXaUFRQWtBUUFtQVFCd1FRAU0JAQhNa0UJCQEBGERZQkFEeEIBCw0BLC1BUUFpQVhpSmFrRg0TOEE4RDgumgKJASFXdzkxSDo5ASRuUEZiSUFRb0FEFVhYa1FEb0pVMGxPTXpvME9ETTBRUGtXU1ENTwxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8EllQUEuwgI4aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc2hvdy1vdXRzdHJlYW0tdmlkZW8tYWRzLmh0bWzYAgDgAq2YSOoCNA1DSHRlc3QubG9jYWxob3N0Ojk5OTkFFBgvcGFnZXMvFUkALgE_yIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzLwmj8F6YBACiBAsxMC43NS43NC42OagEmM8CsgQSCAQQBBiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ4MzTaBAIIAOAEAPAEy4HALogFAZgFAKAF_____wUDFAHABQDJBWlyFPA_0gUJCQkMeAAA2AUB4AUB8AXDlQv6BQQIABAAkAYBmAYAuAYAwQYJJTTwv8gGANAG9S_aBhYKEAkUGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=9953ce4d94992db04897ab580bf81b3e274b2601", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQloC-ldAAAAABHUNucysitRBxloC-ldAAAAACDLgcAuKAAw7Ug47UhA0-hISLuv1AFQgdKnBljDlQtiAi0taAFwAXgAgAECiAEEkAGABZgB4AOgAQCoAcuBwC6wAQE.&s=979aee1106a0bea5609a3c23fdc46153ad6d9eec&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 97517771, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 36, + "renderer_url": "http://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js", + "renderer_id": 2, + "renderer_config": "{\"skippable\":{\"videoThreshold\":null,\"skipLocation\":\"top-right\"}}", + "client_initiated_ad_counting": true, + "viewability": { + "config": "tv=vh2-121&d=1x1&s=3479483&st=0&vctx=4&ts=1575553896&vc=iab;vid_ccr=1&vjs=http%3A%2F%2Fcdn.adnxs.com%2Fv%2Fvideo%2F182%2Ftrk.js&cb=http%3A%2F%2Fsin3-ib.adnxs.com%2Fvevent%3Fan_audit%3D0%26referrer%3Dhttp%253A%252F%252Ftest.localhost%253A9999%252Ftest%252Fpages%252Foutstream.html%26e%3DwqT_3QK4CKA4BAAAAwDWAAUBCOiWpO8FENTtnJej9sqoBxiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMIHSpwY47UhA7UhIAlDLgcAuWJzxW2AAaOWljwF4urgFgAEBigEDVVNEkgUG8FKYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NzU1NTM4OTYpO3VmKCdyJywgOTc1MTc3NzEsIC4eAPCakgK1AiFUenpuS1FqWS1Md0tFTXVCd0M0WUFDQ2M4VnN3QURnQVFBUkk3VWhRZ2RLbkJsZ0FZSUlDYUFCd0FIZ0FnQUhBQVlnQkFKQUJBSmdCQUtBQkFhZ0JBN0FCQUxrQjg2MXFwQUFBSkVEQkFmT3RhcVFBQUNSQXlRSG5NQW5aODYzalA5a0JBQUFBQUFBQThEX2dBUUQxQVEBDyxDWUFnQ2dBZ0MxQWcFEAA5CQjwQERnQWdEb0FnRDRBZ0NBQXdHWUF3R29BOWo0dkFxNkF3bFRTVTR6T2pRNE16VGdBX2tXaUFRQWtBUUFtQVFCd1FRAU0JAQhNa0UJCQEBGERZQkFEeEIBCw0BLC1BUUFpQVhpSmFrRg0TOEE4RDgumgKJASFXdzkxSDo5ASRuUEZiSUFRb0FEFVhYa1FEb0pVMGxPTXpvME9ETTBRUGtXU1ENTwxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8EllQUEuwgI4aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc2hvdy1vdXRzdHJlYW0tdmlkZW8tYWRzLmh0bWzYAgDgAq2YSOoCNA1DSHRlc3QubG9jYWxob3N0Ojk5OTkFFBgvcGFnZXMvFUkALgE_yIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzLwmj8F6YBACiBAsxMC43NS43NC42OagEmM8CsgQSCAQQBBiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ4MzTaBAIIAeAEAPAEy4HALogFAZgFAKAF_____wUDFAHABQDJBWl6FPA_0gUJCQkMeAAA2AUB4AUB8AXDlQv6BQQIABAAkAYBmAYAuAYAwQYJJTTwP8gGANAG9S_aBhYKEAkUGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA%26s%3D5adef946cd5f0d8db86d67860914e02ef6f91d6b&cet=0&cecb=&rdcb=http%3A%2F%2Fsin3-ib.adnxs.com%2Frd_log%3Fan_audit%3D0%26referrer%3Dhttp%253A%252F%252Ftest.localhost%253A9999%252Ftest%252Fpages%252Foutstream.html%26e%3DwqT_3QKMCaCMBAAAAwDWAAUBCOiWpO8FENTtnJej9sqoBxiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMIHSpwY47UhA7UhIAlDLgcAuWJzxW2AAaOWljwF4urgFgAEBigEDVVNEkgUG8FKYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NzU1NTM4OTYpO3VmKCdyJywgOTc1MTc3NzEsIC4eAPCakgK1AiFUenpuS1FqWS1Md0tFTXVCd0M0WUFDQ2M4VnN3QURnQVFBUkk3VWhRZ2RLbkJsZ0FZSUlDYUFCd0FIZ0FnQUhBQVlnQkFKQUJBSmdCQUtBQkFhZ0JBN0FCQUxrQjg2MXFwQUFBSkVEQkFmT3RhcVFBQUNSQXlRSG5NQW5aODYzalA5a0JBQUFBQUFBQThEX2dBUUQxQVEBDyxDWUFnQ2dBZ0MxQWcFEAA5CQjwQERnQWdEb0FnRDRBZ0NBQXdHWUF3R29BOWo0dkFxNkF3bFRTVTR6T2pRNE16VGdBX2tXaUFRQWtBUUFtQVFCd1FRAU0JAQhNa0UJCQEBGERZQkFEeEIBCw0BLC1BUUFpQVhpSmFrRg0TOEE4RDgumgKJASFXdzkxSDo5ASRuUEZiSUFRb0FEFVhYa1FEb0pVMGxPTXpvME9ETTBRUGtXU1ENTwxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8EllQUEuwgI4aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc2hvdy1vdXRzdHJlYW0tdmlkZW8tYWRzLmh0bWzYAgDgAq2YSOoCNA1DSHRlc3QubG9jYWxob3N0Ojk5OTkFFBgvcGFnZXMvFUkALgE_aPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFjIWACBMRUFGX05BTUUBHQgeCho2HQAIQVNUAT7gSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzDffwXpgEAKIECzEwLjc1Ljc0LjY5qASYzwKyBBIIBBAEGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDgzNNoEAggB4AQA8ATLgcAuiAUBmAUAoAX_____BQMUAcAFAMkFac4U8D_SBQkJCQx4AADYBQHgBQHwBcOVC_oFBAgAEACQBgGYBgC4BgDBBgklNPA_yAYA0Ab1L9oGFgoQCRQZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Df2a73057b50fb0c9e98a6093141472a4ed2e401e&bridge=1.11.0&rblog=auc=527250675737245396;bm=9325;sm=9325;cr=97517771;pl=13232385&vid_context=anoutstream;anbannerstream;anoverlayplayer" + }, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_off" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "content": "adnxs00:00:30.000" + } + } + } + ] + }, + { + "tag_id": 13232385, + "auction_id": "3449642271543746980", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Foutstream.html&e=wqT_3QKwCKAwBAAAAwDWAAUBCOiWpO8FEKTDpazn6-XvLxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQgkQCEJCQgAACkRCQAxCQnwaSRAMIHSpwY47UhA7UhIAFAAWJzxW2AAaOWljwF4AIABAYoBAJIBA1VTRJgBAaABAagBAbABALgBA8ABAMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3NTU1Mzg5NikFHTRyJywgOTc1MTc3NzEsIC4eAPCakgK1AiFvendNV2dqWS1Md0tFTXVCd0M0WUFDQ2M4VnN3QURnQVFBUkk3VWhRZ2RLbkJsZ0FZSUlDYUFCd0FIZ0FnQUhBQVlnQkFKQUJBSmdCQUtBQkFhZ0JBN0FCQUxrQjg2MXFwQUFBSkVEQkFmT3RhcVFBQUNSQXlRSHIwdDF2TTdMaVA5a0JBQUFBQUFBQThEX2dBUUQxQVEBDyxDWUFnQ2dBZ0MxQWcFEAA5CQjwQERnQWdEb0FnRDRBZ0NBQXdHWUF3R29BOWo0dkFxNkF3bFRTVTR6T2pRNE16VGdBX2tXaUFRQWtBUUFtQVFCd1FRAU0JAQhNa0UJCQEBGERZQkFEeEIBCw0BLC1BUUFpQVhpSmFrRg0TPEE4RDgumgKJASFXdzkxSFE2OQEkblBGYklBUW9BRBVYWGtRRG9KVTBsT016bzBPRE0wUVBrV1NRDU8MUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBJZUFBLsICOGh0dHA6Ly9wcmViaWQub3JnL2Rldi1kb2NzL3Nob3ctb3V0c3RyZWFtLXZpZGVvLWFkcy5odG1s2AIA4AKtmEjqAjQNQ0h0ZXN0LmxvY2FsaG9zdDo5OTk5BRQYL3BhZ2VzLxVJAC4BP8iAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My8Jo_BemAQAogQLMTAuNzUuNzQuNjmoBJjPArIEEggEEAQYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0ODM02gQCCADgBADwBMuBwC6IBQGYBQCgBf____8FAxQBwAUAyQVpchTwP9IFCQkJDHgAANgFAeAFAfAFw5UL-gUECAAQAJAGAZgGALgGAMEGCSU08L_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=e43b45a2391036da6d93eda546d8808de0169bb1", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQloC-ldAAAAABGkYYl1XpffLxloC-ldAAAAACDLgcAuKAAw7Ug47UhA0-hISLuv1AFQgdKnBljDlQtiAi0taAFwAXgAgAECiAEEkAGABZgB4AOgAQCoAcuBwC6wAQE.&s=414834fdc6e268f284292aedf8b01ee525c3e999&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 97517771, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 36, + "renderer_url": "http://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js", + "renderer_id": 2, + "renderer_config": "{\"skippable\":{\"videoThreshold\":null,\"skipLocation\":\"top-right\"}}", + "client_initiated_ad_counting": true, + "viewability": { + "config": "tv=vh2-121&d=1x1&s=3479483&st=0&vctx=4&ts=1575553896&vc=iab;vid_ccr=1&vjs=http%3A%2F%2Fcdn.adnxs.com%2Fv%2Fvideo%2F182%2Ftrk.js&cb=http%3A%2F%2Fsin3-ib.adnxs.com%2Fvevent%3Fan_audit%3D0%26referrer%3Dhttp%253A%252F%252Ftest.localhost%253A9999%252Ftest%252Fpages%252Foutstream.html%26e%3DwqT_3QK4CKA4BAAAAwDWAAUBCOiWpO8FEKTDpazn6-XvLxiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMIHSpwY47UhA7UhIAlDLgcAuWJzxW2AAaOWljwF4urgFgAEBigEDVVNEkgUG8FKYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NzU1NTM4OTYpO3VmKCdyJywgOTc1MTc3NzEsIC4eAPCakgK1AiFvendNV2dqWS1Md0tFTXVCd0M0WUFDQ2M4VnN3QURnQVFBUkk3VWhRZ2RLbkJsZ0FZSUlDYUFCd0FIZ0FnQUhBQVlnQkFKQUJBSmdCQUtBQkFhZ0JBN0FCQUxrQjg2MXFwQUFBSkVEQkFmT3RhcVFBQUNSQXlRSHIwdDF2TTdMaVA5a0JBQUFBQUFBQThEX2dBUUQxQVEBDyxDWUFnQ2dBZ0MxQWcFEAA5CQjwQERnQWdEb0FnRDRBZ0NBQXdHWUF3R29BOWo0dkFxNkF3bFRTVTR6T2pRNE16VGdBX2tXaUFRQWtBUUFtQVFCd1FRAU0JAQhNa0UJCQEBGERZQkFEeEIBCw0BLC1BUUFpQVhpSmFrRg0TPEE4RDgumgKJASFXdzkxSFE2OQEkblBGYklBUW9BRBVYWGtRRG9KVTBsT016bzBPRE0wUVBrV1NRDU8MUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBJZUFBLsICOGh0dHA6Ly9wcmViaWQub3JnL2Rldi1kb2NzL3Nob3ctb3V0c3RyZWFtLXZpZGVvLWFkcy5odG1s2AIA4AKtmEjqAjQNQ0h0ZXN0LmxvY2FsaG9zdDo5OTk5BRQYL3BhZ2VzLxVJAC4BP8iAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My8Jo_BemAQAogQLMTAuNzUuNzQuNjmoBJjPArIEEggEEAQYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0ODM02gQCCAHgBADwBMuBwC6IBQGYBQCgBf____8FAxQBwAUAyQVpehTwP9IFCQkJDHgAANgFAeAFAfAFw5UL-gUECAAQAJAGAZgGALgGAMEGCSU08D_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..%26s%3Dc2a8dac8959d9366981afc868ec5b54853090944&cet=0&cecb=&rdcb=http%3A%2F%2Fsin3-ib.adnxs.com%2Frd_log%3Fan_audit%3D0%26referrer%3Dhttp%253A%252F%252Ftest.localhost%253A9999%252Ftest%252Fpages%252Foutstream.html%26e%3DwqT_3QKMCaCMBAAAAwDWAAUBCOiWpO8FEKTDpazn6-XvLxiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMIHSpwY47UhA7UhIAlDLgcAuWJzxW2AAaOWljwF4urgFgAEBigEDVVNEkgUG8FKYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NzU1NTM4OTYpO3VmKCdyJywgOTc1MTc3NzEsIC4eAPCakgK1AiFvendNV2dqWS1Md0tFTXVCd0M0WUFDQ2M4VnN3QURnQVFBUkk3VWhRZ2RLbkJsZ0FZSUlDYUFCd0FIZ0FnQUhBQVlnQkFKQUJBSmdCQUtBQkFhZ0JBN0FCQUxrQjg2MXFwQUFBSkVEQkFmT3RhcVFBQUNSQXlRSHIwdDF2TTdMaVA5a0JBQUFBQUFBQThEX2dBUUQxQVEBDyxDWUFnQ2dBZ0MxQWcFEAA5CQjwQERnQWdEb0FnRDRBZ0NBQXdHWUF3R29BOWo0dkFxNkF3bFRTVTR6T2pRNE16VGdBX2tXaUFRQWtBUUFtQVFCd1FRAU0JAQhNa0UJCQEBGERZQkFEeEIBCw0BLC1BUUFpQVhpSmFrRg0TPEE4RDgumgKJASFXdzkxSFE2OQEkblBGYklBUW9BRBVYWGtRRG9KVTBsT016bzBPRE0wUVBrV1NRDU8MUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBJZUFBLsICOGh0dHA6Ly9wcmViaWQub3JnL2Rldi1kb2NzL3Nob3ctb3V0c3RyZWFtLXZpZGVvLWFkcy5odG1s2AIA4AKtmEjqAjQNQ0h0ZXN0LmxvY2FsaG9zdDo5OTk5BRQYL3BhZ2VzLxVJAC4BP2jyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-4ElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw338F6YBACiBAsxMC43NS43NC42OagEmM8CsgQSCAQQBBiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ4MzTaBAIIAeAEAPAEy4HALogFAZgFAKAF_____wUDFAHABQDJBWnOFPA_0gUJCQkMeAAA2AUB4AUB8AXDlQv6BQQIABAAkAYBmAYAuAYAwQYJJTTwP8gGANAG9S_aBhYKEAkUGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA%26s%3D4e9e218d8f075cb19c4a4e8da2a7e716fa15d3c5&bridge=1.11.0&rblog=auc=3449642271543746980;bm=9325;sm=9325;cr=97517771;pl=13232385&vid_context=anoutstream;anbannerstream;anoverlayplayer" + }, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_off" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "content": "adnxs00:00:30.000" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/index.js b/test/fake-server/fixtures/index.js new file mode 100644 index 00000000000..bd58bda5ad5 --- /dev/null +++ b/test/fake-server/fixtures/index.js @@ -0,0 +1,41 @@ +/* eslint-disable no-console */ + +const path = require('path'); +const fs = require('fs'); + +const REQ_RES_PAIRS = {}; + +/** + * @param {String} dirname - Path of the fixture directory + * @returns {object} reqResPair - An object containing 'request' - 'response' segregated by ad unit media type. + */ +function getReqResPairs (dirname) { + try { + const filenames = fs.readdirSync(dirname, { withFileTypes: true }); + filenames.forEach(filename => { + if (filename.isDirectory()) { + getReqResPairs(`${dirname}/${filename.name}`); + } else { + if (filename.name === 'request.json' || filename.name === 'response.json') { + const parentDir = path.basename(dirname); + if (!REQ_RES_PAIRS[parentDir]) { + REQ_RES_PAIRS[parentDir] = { + request: {}, + response: {} + } + } + if (filename.name === 'request.json') { + REQ_RES_PAIRS[parentDir]['request'] = JSON.parse(fs.readFileSync(`${dirname}/${filename.name}`, { encoding: 'utf-8' })); + } else { + REQ_RES_PAIRS[parentDir]['response'] = JSON.parse(fs.readFileSync(`${dirname}/${filename.name}`, { encoding: 'utf-8' })); + } + } + } + }); + return REQ_RES_PAIRS; + } catch (e) { + console.error(`Error:: ${e.message}`); + } +} + +module.exports = getReqResPairs; diff --git a/test/fake-server/fixtures/longform/longform_biddersettings_1/description.md b/test/fake-server/fixtures/longform/longform_biddersettings_1/description.md new file mode 100644 index 00000000000..207e851af74 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_biddersettings_1/description.md @@ -0,0 +1,41 @@ +Test Page - 'integrationExamples/longform/basic_w_bidderSettings.html' +Test Spec File - 'test/spec/e2e/longform/basic_w_bidderSettings.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: false + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + debug: true, + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_biddersettings_1/request.json b/test/fake-server/fixtures/longform/longform_biddersettings_1/request.json new file mode 100644 index 00000000000..aba76398093 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_biddersettings_1/request.json @@ -0,0 +1,387 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + } + ], + "user": {}, + "brand_category_uniqueness": true + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_biddersettings_1/response.json b/test/fake-server/fixtures/longform/longform_biddersettings_1/response.json new file mode 100644 index 00000000000..e3ea15d7c6e --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_biddersettings_1/response.json @@ -0,0 +1,114 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "2678252910506723691", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "3548675574061430850", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "8693167356543642173", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "7686428711280367086", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "3784359541475413084", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "7233136875958651734", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "159775901183771330", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "6558726890185052779", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "6624810255570939818", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "528384387675374412", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "2535665225687089273", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "2166694611986638079", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "9137369006412413609", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "3524702228053475248", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2d4806af582cf6", + "tag_id": 15394006, + "auction_id": "57990683038266307", + "nobid": true, + "ad_profile_id": 1182765 + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_biddersettings_2/description.md b/test/fake-server/fixtures/longform/longform_biddersettings_2/description.md new file mode 100644 index 00000000000..cafbb17f61b --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_biddersettings_2/description.md @@ -0,0 +1,28 @@ +Test Page - 'integrationExamples/longform/basic_w_bidderSettings.html' +Test Spec File - 'test/spec/e2e/longform/basic_w_bidderSettings.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: false + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_biddersettings_2/request.json b/test/fake-server/fixtures/longform/longform_biddersettings_2/request.json new file mode 100644 index 00000000000..f2f20700ffe --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_biddersettings_2/request.json @@ -0,0 +1,137 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + } + ], + "user": {}, + "brand_category_uniqueness": true + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_biddersettings_2/response.json b/test/fake-server/fixtures/longform/longform_biddersettings_2/response.json new file mode 100644 index 00000000000..e2332806dbb --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_biddersettings_2/response.json @@ -0,0 +1,188 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "245a09bd675168", + "tag_id": 15394006, + "auction_id": "3810681093956255668", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_bidderSettings.html&e=wqT_3QKVCKAVBAAAAwDWAAUBCKD4kfAFELT_vOy9yJDxNBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3MzUyMjI0KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIUxEMWZkUWlua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOE13Q2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFZVS10N09mcU9BXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTS1BRG9SaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJaRWxxUVUJE0RBRHdQdy4umgKJASFLdy1SRkE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV6UUtFWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9AUBZUFBLtgCAOACrZhI6gJTaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2JpZGRlclNldHRpbmdzLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagErLkEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTPaBAIIAOAEAPAE0uyfR4gFAZgFAKAF____________AcAFAMkFAGVbFPA_0gUJCQULfAAAANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=66ba37441db5e28c87ee52e729333fd9324333f9", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQkgfAReAAAAABG0P4_dQ0LiNBkgfAReAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=6931bf3569012d0e1f02cb5a2e88dfcafe00dba9&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_bidderSettings.html&e=wqT_3QLxCOhxBAAAAwDWAAUBCKD4kfAFELT_vOy9yJDxNBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4vLgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3MzUyMjI0KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFMRDFmZFFpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjhNd0NrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWVUtdDdPZnFPQV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU0tQURvUmlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWkVscVFVCRNEQUR3UHcuLpoCiQEhS3ctUkZBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVelFLRVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJTaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2JpZGRlclNldHRpbmdzLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTU9ERUxfTEVBRl9OQU1FEgDyAh4KGkMyHQDwlUFTVF9NT0RJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBKy5BLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUz2gQCCAHgBADwBGGFIIgFAZgFAKAF_xEBFAHABQDJBWm2FPA_0gUJCQkMeAAA2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=ea46ec90cf31744c7be2af5d5f239ab4eed29098" + } + } + } + ] + }, + { + "uuid": "245a09bd675168", + "tag_id": 15394006, + "auction_id": "7325897349627488405", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_bidderSettings.html&e=wqT_3QKUCKAUBAAAAwDWAAUBCKD4kfAFEJXRloy0mrTVZRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3MzUyMjI0KTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIXJUeTNJd2lta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOE13Q2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFVSzAtQ2hwcWRrXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTS1BRG9SaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJaRWxxUVUJE0RBRHdQdy4umgKJASFBQTlLQlE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV6UUtFWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9AUBZUFBLtgCAOACrZhI6gJTaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2JpZGRlclNldHRpbmdzLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagErLkEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTPaBAIIAOAEAPAEr-WfR4gFAZgFAKAF____________AcAFAMkFAGVbFPA_0gUJCQULeAAAANgFAeAFAfAF4Fj6BQQIABAAkAYBmAYAuAYAwQYBIDAAAPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=271fd8a0ccdfc36e320f707164588ed1b33e9861", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQkgfAReAAAAABGVqIVB09CqZRkgfAReAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICLS1oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=115ac2b842cf3efeb5acaeda94ddf05632c345ca&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_bidderSettings.html&e=wqT_3QLwCOhwBAAAAwDWAAUBCKD4kfAFEJXRloy0mrTVZRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV4vLgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3MzUyMjI0KTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiFyVHkzSXdpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjhNd0NrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVUswLUNocHFka18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU0tQURvUmlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWkVscVFVCRNEQUR3UHcuLpoCiQEhQUE5S0JRNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVelFLRVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJTaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2JpZGRlclNldHRpbmdzLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTU9ERUxfTEVBRl9OQU1FEgDyAh4KGkMyHQDwlUFTVF9NT0RJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBKy5BLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUz2gQCCAHgBADwBGGFIIgFAZgFAKAF_xEBFAHABQDJBWm2FPA_0gUJCQkMdAAA2AUB4AUB8AXgWPoFBAgAEACQBgGYBgC4BgDBBgkkKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=f73e060b15a6bbcca65f918a296f155b78c74eca" + } + } + } + ] + }, + { + "uuid": "245a09bd675168", + "tag_id": 15394006, + "auction_id": "968802322305726102", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_bidderSettings.html&e=wqT_3QKVCKAVBAAAAwDWAAUBCKD4kfAFEJbt7LTEkvi4DRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3MzUyMjI0KTsBHTByJywgMTQ5NDE0MTg4Nh8A8P2SArkCIUtUejN5d2lHa184UEVLekNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOE13Q2tBRUFtQUVBb0FFQnFBRURzQUVBdVFFajRyM1ZBQUFxUU1FQkktSzkxUUFBS2tESkFTT3dTTWVaYnVJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRGhwUF9EN29EQ1ZOSlRqTTZORGMxTS1BRG9SaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJaRWxxUVUJE0RBRHdQdy4umgKJASF0ZzY4Nmc2PQEkblBGYklBUW9BRBVIVHFRRG9KVTBsT016bzBOelV6UUtFWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9AUBZUFBLtgCAOACrZhI6gJTaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2JpZGRlclNldHRpbmdzLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagErLkEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTPaBAIIAOAEAPAErMKfR4gFAZgFAKAF____________AcAFAMkFAGVbFPA_0gUJCQULfAAAANgFAeAFAfAF8owB-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=e35825a106304da78477df1575f981395be21d5f", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQkgfAReAAAAABGWNptGlOBxDRkgfAReAAAAACCswp9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jyjAFiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAazCn0ewAQE.&s=677bedd5b2d21fdc3f940cbae279261c8f84c2e7&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149414188, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 13.00001, + "cpm_publisher_currency": 13.00001, + "publisher_currency_code": "$", + "brand_category_id": 32, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 29000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_bidderSettings.html&e=wqT_3QLxCOhxBAAAAwDWAAUBCKD4kfAFEJbt7LTEkvi4DRiq5MnUovf28WEqNgmOWItPAQAqQBGOWItPAQAqQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQrMKfR1ic8VtgAGjNunV4vLgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3MzUyMjI0KTt1ZigncicsIDE0OTQxNDE4OCwgMTUZH_D9kgK5AiFLVHozeXdpR2tfOFBFS3pDbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjhNd0NrQUVBbUFFQW9BRUJxQUVEc0FFQXVRRWo0cjNWQUFBcVFNRUJJLUs5MVFBQUtrREpBU093U01lWmJ1SV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RocFBfRDdvRENWTkpUak02TkRjMU0tQURvUmlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWkVscVFVCRNEQUR3UHcuLpoCiQEhdGc2ODZnNj0BJG5QRmJJQVFvQUQVSFRxUURvSlUwbE9Nem8wTnpVelFLRVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJTaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2JpZGRlclNldHRpbmdzLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTU9ERUxfTEVBRl9OQU1FEgDyAh4KGkMyHQDwlUFTVF9NT0RJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBKy5BLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUz2gQCCAHgBADwBGGFIIgFAZgFAKAF_xEBFAHABQDJBWm2FPA_0gUJCQkMeAAA2AUB4AUB8AXyjAH6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=0707b1385afa81517cd338f1516aeccc46fe33e1" + } + } + } + ] + }, + { + "uuid": "245a09bd675168", + "tag_id": 15394006, + "auction_id": "1273216786519070425", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "245a09bd675168", + "tag_id": 15394006, + "auction_id": "1769115862397582681", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_bidderSettings.html&e=wqT_3QKUCKAUBAAAAwDWAAUBCKD4kfAFENn63YWPsMrGGBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3MzUyMjI0KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIXp6djFzd2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOE13Q2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFZbW5MamdJaXVRXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTS1BRG9SaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJaRWxxUVUJE0RBRHdQdy4umgKJASFGdzkxRFE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV6UUtFWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9AUBZUFBLtgCAOACrZhI6gJTaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2JpZGRlclNldHRpbmdzLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagErLkEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTPaBAIIAOAEAPAExOefR4gFAZgFAKAF____________AcAFAMkFAGVbFPA_0gUJCQULeAAAANgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYBIDAAAPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=fb3a15e7ff747fccd750671b763e312d97083c72", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQkgfAReAAAAABFZfbfwgCmNGBkgfAReAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICLS1oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=7f4b8dd7c7dd9faecebac2e9ec6d7ef8da08da16&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_bidderSettings.html&e=wqT_3QLwCOhwBAAAAwDWAAUBCKD4kfAFENn63YWPsMrGGBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4vLgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3MzUyMjI0KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiF6enYxc3dpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjhNd0NrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWW1uTGpnSWl1UV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU0tQURvUmlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWkVscVFVCRNEQUR3UHcuLpoCiQEhRnc5MURRNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVelFLRVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJTaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2JpZGRlclNldHRpbmdzLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTU9ERUxfTEVBRl9OQU1FEgDyAh4KGkMyHQDwsEFTVF9NT0RJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBKy5BLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUz2gQCCAHgBADwBMTnn0eIBQGYBQCgBf___________wHABQDJBWm2FPA_0gUJCQkMdAAA2AUB4AUB8AWZPfoFBAgAEACQBgGYBgC4BgDBBgkkKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=3a27c12c33ebaaae43dbce1cafb0bae43b753fa0" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/description.md b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/description.md new file mode 100644 index 00000000000..45ae30a7a41 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/description.md @@ -0,0 +1,43 @@ +Test Page - 'integrationExamples/longform/basic_w_custom_adserver_translation.html' +Test Spec File - 'test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + }, + brandCategoryTranslation: { + translationFile: 'custom_adserver_translation.json' + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/request.json b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/request.json new file mode 100644 index 00000000000..e7497ac78f3 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/request.json @@ -0,0 +1,402 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + } + ], + "user": {}, + "brand_category_uniqueness": true + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/response.json b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/response.json new file mode 100644 index 00000000000..b4d8483a539 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_1/response.json @@ -0,0 +1,294 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "7360998998672342781", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKiCKAiBAAAAwDWAAUBCN73l_AFEP39-97ssuGTZhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIVNqMmZTQWlua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCdXVZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFaQmtTcHl1c2Q4XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGN6TS1BRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFOZzhKRnc2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNfC2Lmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEksYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAE0uyfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAZXZw2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYFIiwA8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=c92cbcde5c8bf8e053f86493dd4c4698da0392de", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQne-wVeAAAAABH9_t7LloUnZhne-wVeAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=8e35c1264cd1b4f1d89f929c3a4a334cf6a68eca&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL-COh-BAAAAwDWAAUBCN73l_AFEP39-97ssuGTZhiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV40rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFTajJmU0FpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnV1WUNrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWkJrU3B5dXNkOF8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjek0tQURyaGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhTmc4SkZ3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFLNFlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBlZUFBLtgCAOACrZhI6gJgaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2N1c3RvbV9hZHNlcnZlcl90cmFuc2xhATV8Lmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-8J9JRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBJLGBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBNLsn0eIBQGYBQCgBf______AQUUAcAFAMkFacMU8D_SBQkJCQx4AADYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgklKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=ca640dffaea7bfcf2b0f2e11d8877821189b74bb" + } + } + } + ] + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "1919339751435064934", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKiCKAiBAAAAwDWAAUBCN73l_AFEOasy7zbqrfRGhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIU1EMUZJQWlua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCdXVZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFRclZ0ZGpIUGVBXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGN6TS1BRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASE1QTdmLVE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNfC2Lmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEksYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAEi-GfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAZXZw2AUB4AUB8AXa1gL6BQQIABAAkAYBmAYAuAYAwQYFIiwA8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=c3130de64bc0b8df258e603dfb96f78550ab0c3c", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQne-wVeAAAAABFm1pK3Vd2iGhne-wVeAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=2c3cee10303d9b93531bed443c0781d905270598&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL-COh-BAAAAwDWAAUBCN73l_AFEOasy7zbqrfRGhiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV40rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFNRDFGSUFpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnV1WUNrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBUXJWdGRqSFBlQV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjek0tQURyaGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhNUE3Zi1RNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFLNFlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBlZUFBLtgCAOACrZhI6gJgaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2N1c3RvbV9hZHNlcnZlcl90cmFuc2xhATV8Lmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-8J9JRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBJLGBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBIvhn0eIBQGYBQCgBf______AQUUAcAFAMkFacMU8D_SBQkJCQx4AADYBQHgBQHwBdrWAvoFBAgAEACQBgGYBgC4BgDBBgklKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=996a3937245f03e5eefb0cb69917d2d8d7f60424" + } + } + } + ] + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "3257875652791896280", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "5756905673624319729", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "4205438746002589111", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "204849530930208960", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "3482944224379652843", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "2123689132466331410", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "6150444453316813936", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "2810956382376737966", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "7164199537578897638", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKhCKAhBAAAAwDWAAUBCN73l_AFEObhocvZsJa2Yxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIXNENXVfQWlta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCdXVZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFYSEZfdmZOei1NXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFDd19DQnc2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNfC2Lmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEksYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAEr-WfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAZXZs2AUB4AUB8AXgWPoFBAgAEACQBgGYBgC4BgDBBgUhLADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=2b9ed1e2e7f27fea52cbdc64cb700195bbd14d75", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQne-wVeAAAAABHmcGiZhVlsYxne-wVeAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICLS1oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=8ba7f141449cb45f8b6e12361a62d8d68aa9c812&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL9COh9BAAAAwDWAAUBCN73l_AFEObhocvZsJa2Yxiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV40rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiFzRDV1X0FpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnV1WUNrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWEhGX3ZmTnotTV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek0tQURyaGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhQ3dfQ0J3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFLNFlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBlZUFBLtgCAOACrZhI6gJgaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2N1c3RvbV9hZHNlcnZlcl90cmFuc2xhATV8Lmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-8J9JRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBJLGBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBK_ln0eIBQGYBQCgBf______AQUUAcAFAMkFacMU8D_SBQkJCQx0AADYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGCSQo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=527640949733fba32740156d743d421eb1fe2863" + } + } + } + ] + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "8404712946290777461", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKiCKAiBAAAAwDWAAUBCN73l_AFEPW6p5bQvOLRdBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCIWp6MkdiZ2lta184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCdXVZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFaQ0RhTlBDYk9NXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0BBRHdQdy4umgKJASFOUS0yRjo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56TXpRSzRZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwZWVBQS7YAgDgAq2YSOoCYGh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvbG9uZ2Zvcm0vYmFzaWNfd19jdXN0b21fYWRzZXJ2ZXJfdHJhbnNsYQE18LYuaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECzEwLjc1Ljc0LjY5qASSxgSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczM9oEAggA4AQA8ATf359HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABldnDYBQHgBQHwBay8FPoFBAgAEACQBgGYBgC4BgDBBgUiLADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=ed84428f432d6e743bcad6f7862aed957bece82b", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQne-wVeAAAAABF13ckC5YmjdBne-wVeAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=7024b063fcacac27e184ed097ad5878c4dd4dc1d&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL-COh-BAAAAwDWAAUBCN73l_AFEPW6p5bQvOLRdBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV40rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiFqejJHYmdpbWtfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnV1WUNrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWkNEYU5QQ2JPTV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek0tQURyaGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNAQUR3UHcuLpoCiQEhTlEtMkY6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNXwuaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFjIWACBMRUFGX05BTUUBHQgeCho2HQAIQVNUAT7wn0lGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEksYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAeAEAPAE39-fR4gFAZgFAKAF______8BBRQBwAUAyQVpwxTwP9IFCQkJDHgAANgFAeAFAfAFrLwU-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=aefe9625d8e866e048a156abdf26d7c626e6a398" + } + } + } + ] + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "4063389973481762703", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKiCKAiBAAAAwDWAAUBCN73l_AFEI_fjorv9IOyOBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUwNDYyKTsBHTByJywgMTQ5NDE3NjY5Nh8A8P2SArkCIV96eHRIQWlsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCdXVZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFSVExWM2x4c2VJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFEZy1VQ1E2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOek16UUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNfC2Lmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEksYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAExd2fR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAZXZw2AUB4AUB8AXC8hf6BQQIABAAkAYBmAYAuAYAwQYFIiwA8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=2fb33b5204f9b75c58d89487725a9d55139f9ff4", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQne-wVeAAAAABGPr0Pxpg9kOBne-wVeAAAAACDF3Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jC8hdiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAcXdn0ewAQE.&s=f9ec8d0b81c1cf4eefc58a7990f4a0a78440725f&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417669, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 4, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL-CKB-BAAAAwDWAAUBCN73l_AFEI_fjorv9IOyOBiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZCQkI4D8hCQkIJEApEQkAMQkJsOA_MNbJqwc47UhA7UhIAlDF3Z9HWJzxW2AAaM26dXjSuAWAAQGKAQNVU0SSAQEG8FWYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjx1ZignYScsIDI1Mjk4ODUsIDE1Nzc0NTA0NjIpO3VmKCdyJywgMTQ5NDE3NjY5LCAxNRkf8P2SArkCIV96eHRIQWlsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCdXVZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFSVExWM2x4c2VJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFEZy1VQ1E2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOek16UUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNXwuaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFjIWACBMRUFGX05BTUUBHQgeCho2HQAIQVNUAT7wn0lGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEksYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAeAEAPAExd2fR4gFAZgFAKAF______8BBRQBwAUAyQVpwxTwP9IFCQkJDHgAANgFAeAFAfAFwvIX-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=6b79542e3cee0319d8cb83d1daf127c3aeea9b28" + } + } + } + ] + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "5097385192927446024", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "201bba5bb8827", + "tag_id": 15394006, + "auction_id": "2612757136292876686", + "nobid": true, + "ad_profile_id": 1182765 + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/description.md b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/description.md new file mode 100644 index 00000000000..45ae30a7a41 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/description.md @@ -0,0 +1,43 @@ +Test Page - 'integrationExamples/longform/basic_w_custom_adserver_translation.html' +Test Spec File - 'test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + }, + brandCategoryTranslation: { + translationFile: 'custom_adserver_translation.json' + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/request.json b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/request.json new file mode 100644 index 00000000000..f4cea46918f --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/request.json @@ -0,0 +1,142 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + } + ], + "user": {}, + "brand_category_uniqueness": true + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/response.json b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/response.json new file mode 100644 index 00000000000..6a81de25ebe --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_custom_adserver_translation_2/response.json @@ -0,0 +1,188 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "285a2f41615348", + "tag_id": 15394006, + "auction_id": "2837696487158070058", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKiCKAiBAAAAwDWAAUBCNSDmPAFEKr2uMu5veGwJxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUxOTg4KTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCIV9EemhKUWlta184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCN3VZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFTbnRDdFVIdU9BXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TmVBRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmOGtxUVUJE0RBRHdQdy4umgKJASFOdzh1Rnc2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek0xUUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNfDtLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEq8YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzXaBAIIAOAEAPAE39-fR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAAAAAAADYBQHgBQHwBay8FPoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPC_0Ab1L9oGFgoQAAAAAAU3DQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=3414eb9e83df14945d2dbfb00e17fb3f2bad2e33", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnUAQZeAAAAABEqO26Z64VhJxnUAQZeAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=e5a720a9884e1df845be9c33658ab69f4c56981e&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL-COh-BAAAAwDWAAUBCNSDmPAFEKr2uMu5veGwJxiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV41rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUxOTg4KTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiFfRHpoSlFpbWtfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjd1WUNrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBU250Q3RVSHVPQV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek5lQURyaGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjhrcVFVCRNEQUR3UHcuLpoCiQEhTnc4dUZ3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNMVFLNFlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBlZUFBLtgCAOACrZhI6gJgaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2N1c3RvbV9hZHNlcnZlcl90cmFuc2xhATV8Lmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-8J9JRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBKvGBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzM12gQCCAHgBADwBN_fn0eIBQGYBQCgBf______AQUUAcAFAMkFacMU8D_SBQkJCQx4AADYBQHgBQHwBay8FPoFBAgAEACQBgGYBgC4BgDBBgklKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=99418325b8f5ec79e22c1a9aedd73f03de616c2d" + } + } + } + ] + }, + { + "uuid": "285a2f41615348", + "tag_id": 15394006, + "auction_id": "8688113570839045503", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKhCKAhBAAAAwDWAAUBCNSDmPAFEP_K8rCtqJjJeBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUxOTg4KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIUN6d3Rud2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCN3VZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFWUzRZeVVvR09JXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TmVBRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmOGtxUVUJE0RBRHdQdy4umgKJASFKQTlsRUE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek0xUUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNfDXLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEq8YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzXaBAIIAOAEAPAExOefR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAAAAAAADYBQHgBQHwBZk9-gUECAAQAJAGAZgGALgGAMEGBSEsAPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=2e7c9d18300402e2e183667711728f7743b70a2b", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQnUAQZeAAAAABF_pRzWQmGSeBnUAQZeAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICLS1oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=febf12010c66ac7247f571f03c33175ca9036b32&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL9COh9BAAAAwDWAAUBCNSDmPAFEP_K8rCtqJjJeBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV41rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUxOTg4KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiFDend0bndpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjd1WUNrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVlM0WXlVb0dPSV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek5lQURyaGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjhrcVFVCRNEQUR3UHcuLpoCiQEhSkE5bEVBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNMVFLNFlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBlZUFBLtgCAOACrZhI6gJgaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2N1c3RvbV9hZHNlcnZlcl90cmFuc2xhATV8Lmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-8J9JRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBKvGBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzM12gQCCAHgBADwBMTnn0eIBQGYBQCgBf______AQUUAcAFAMkFacMU8D_SBQkJCQx0AADYBQHgBQHwBZk9-gUECAAQAJAGAZgGALgGAMEGCSQo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=92aa9e9c89384c435ab9c7fa62b963d8fc087ef7" + } + } + } + ] + }, + { + "uuid": "285a2f41615348", + "tag_id": 15394006, + "auction_id": "4162295099171231907", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKhCKAhBAAAAwDWAAUBCNSDmPAFEKOxzaPwqtzhORiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUxOTg4KTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIWREMGtXZ2lta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCN3VZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFhSFRjUzNjWS1VXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TmVBRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmOGtxUVUJE0RBRHdQdy4umgKJASFEUTg2Q0E2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek0xUUs0WVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8GVlQUEu2AIA4AKtmEjqAmBodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfY3VzdG9tX2Fkc2VydmVyX3RyYW5zbGEBNfDXLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagEq8YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzXaBAIIAOAEAPAEr-WfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAAAAAAADYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGBSEsAPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=95047e919846faea401c778106fb33dae0c95b02", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQnUAQZeAAAAABGjWHMEV3HDORnUAQZeAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICLS1oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=50350aadc603f4bb6c59888515d60e9182da0eb8&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL9COh9BAAAAwDWAAUBCNSDmPAFEKOxzaPwqtzhORiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV41rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUxOTg4KTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiFkRDBrV2dpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjd1WUNrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBYUhUY1MzY1ktVV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek5lQURyaGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjhrcVFVCRNEQUR3UHcuLpoCiQEhRFE4NkNBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNMVFLNFlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBlZUFBLtgCAOACrZhI6gJgaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X2N1c3RvbV9hZHNlcnZlcl90cmFuc2xhATV8Lmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-8J9JRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQLMTAuNzUuNzQuNjmoBKvGBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzM12gQCCAHgBADwBK_ln0eIBQGYBQCgBf______AQUUAcAFAMkFacMU8D_SBQkJCQx0AADYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGCSQo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=82c42e6aa09e7c842254552ae524791fa3693bbb" + } + } + } + ] + }, + { + "uuid": "285a2f41615348", + "tag_id": 15394006, + "auction_id": "1076114531988487576", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QKiCKAiBAAAAwDWAAUBCNSDmPAFEJj7vIbyjsj3Dhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NDUxOTg4KTsBHTByJywgMTQ5NDE3NjY5Nh8A8P2SArkCIVRqMWVUZ2lsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCN3VZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFZOUNHX2M3MHRvXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGN6TmVBRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmOGtxUVUJE0BBRHdQdy4umgKJASFFQThNQzo9ASRuUEZiSUFRb0FEFUhUa1FEb0pVMGxPTXpvME56TTFRSzRZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwZWVBQS7YAgDgAq2YSOoCYGh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvbG9uZ2Zvcm0vYmFzaWNfd19jdXN0b21fYWRzZXJ2ZXJfdHJhbnNsYQE18O0uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECzEwLjc1Ljc0LjY5qASrxgSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggA4AQA8ATF3Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAFwvIX-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAAAABTcNAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=c49afba7ca4b5193f7da37b17e1fbfbee2328f61", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "http://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnUAQZeAAAAABGYPc8gdyDvDhnUAQZeAAAAACDF3Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jC8hdiAi0taAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAcXdn0ewAQE.&s=47618eb9096567900e84fd1c6aff09d753b2fe91&event_type=1", + "usersync_url": "http%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417669, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 4, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "http://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_custom_adserver_translation.html&e=wqT_3QL-CKB-BAAAAwDWAAUBCNSDmPAFEJj7vIbyjsj3Dhiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZCQkI4D8hCQkIJEApEQkAMQkJsOA_MNbJqwc47UhA7UhIAlDF3Z9HWJzxW2AAaM26dXjWuAWAAQGKAQNVU0SSAQEG8FWYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjx1ZignYScsIDI1Mjk4ODUsIDE1Nzc0NTE5ODgpO3VmKCdyJywgMTQ5NDE3NjY5LCAxNRkf8P2SArkCIVRqMWVUZ2lsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCN3VZQ2tBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFZOUNHX2M3MHRvXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGN6TmVBRHJoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmOGtxUVUJE0BBRHdQdy4umgKJASFFQThNQzo9ASRuUEZiSUFRb0FEFUhUa1FEb0pVMGxPTXpvME56TTFRSzRZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwZWVBQS7YAgDgAq2YSOoCYGh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvbG9uZ2Zvcm0vYmFzaWNfd19jdXN0b21fYWRzZXJ2ZXJfdHJhbnNsYQE1fC5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAIExFQUZfTkFNRQEdCB4KGjYdAAhBU1QBPvCfSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECzEwLjc1Ljc0LjY5qASrxgSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggB4AQA8ATF3Z9HiAUBmAUAoAX______wEFFAHABQDJBWnDFPA_0gUJCQkMeAAA2AUB4AUB8AXC8hf6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=72d1ec3db5baaa29c1b0e5f07c012db606675fe5" + } + } + } + ] + }, + { + "uuid": "285a2f41615348", + "tag_id": 15394006, + "auction_id": "7495588537924508785", + "nobid": true, + "ad_profile_id": 1182765 + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_priceGran_1/description.md b/test/fake-server/fixtures/longform/longform_priceGran_1/description.md new file mode 100644 index 00000000000..8bc4d242f46 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_priceGran_1/description.md @@ -0,0 +1,62 @@ +Test Page - 'integrationExamples/longform/basic_w_priceGran.html' +Test Spec File - 'test/spec/e2e/longform/basic_w_priceGran.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: false + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +const customConfigObject = { + 'buckets': [{ + 'precision': 2, // default is 2 if omitted - means 2.1234 rounded to 2 decimal places = 2.12 + 'min': 0, + 'max': 5, + 'increment': 0.01 // from $0 to $5, 1-cent increments + }, + { + 'precision': 2, + 'min': 5, + 'max': 8, + 'increment': 0.05 // from $5 to $8, round down to the previous 5-cent increment + }, + { + 'precision': 2, + 'min': 8, + 'max': 40, + 'increment': 0.5 // from $8 to $40, round down to the previous 50-cent increment + }] +}; + +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + }, + priceGranularity: customConfigObject +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_priceGran_1/request.json b/test/fake-server/fixtures/longform/longform_priceGran_1/request.json new file mode 100644 index 00000000000..aba76398093 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_priceGran_1/request.json @@ -0,0 +1,387 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + } + ], + "user": {}, + "brand_category_uniqueness": true + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_priceGran_1/response.json b/test/fake-server/fixtures/longform/longform_priceGran_1/response.json new file mode 100644 index 00000000000..4665aa4ef9c --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_priceGran_1/response.json @@ -0,0 +1,366 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "1249897353793397796", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKSCKASBAAAAwDWAAUBCMmFp_AFEKTIuZTW4KGsERiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCITFqdjBlZ2lta184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFXU1JOVU1OTk9jXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFTUTgtR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAE39-fR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAGlkdADYBQHgBQHwBay8FPoFBAgAEACQBgGYBgC4BgDBBgkkKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=e9887c670ae9fcb7eb2a0253037c64c3587f4bcb", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnKwgleAAAAABEkZI5iBYdYERnJwgleAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=68a5c49da3a6ecf3dfc0835cb3da72b3b2c7b080&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLuCOhuBAAAAwDWAAUBCMmFp_AFEKTIuZTW4KGsERiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV4w7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiExanYwZWdpbWtfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBV1NSTlVNTk5PY18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhU1E4LUd3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX01PREVMX0xFQUZfTkFNRRIA8gIeChpDVVNUT00RHQhBU1QBC_CQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBK_mBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBGGCIIgFAZgFAKAF_xEBFAHABQDJBWmzFPA_0gUJCQkMeAAA2AUB4AUB8AWsvBT6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=9514ae5f8aae1ee9dddd24dce3e812ae76e0e783" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "4278372095219023172", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKSCKASBAAAAwDWAAUBCMmFp_AFEMT65MOLmPWvOxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIUxEeUhqUWlua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFaQWJScDluWS1FXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASEtQTVuX2c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAEi-GfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAGlkdADYBQHgBQHwBdrWAvoFBAgAEACQBgGYBgC4BgDBBgkkKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=987d4bb0c7611d41b5974ec412469da2241084cd", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnKwgleAAAAABFEPXm4wNRfOxnJwgleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=75aaa9ec84807690ceff60e39fbba6625240f9f3&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLuCOhuBAAAAwDWAAUBCMmFp_AFEMT65MOLmPWvOxiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4w7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFMRHlIalFpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWkFiUnA5blktRV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhLUE1bl9nNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX01PREVMX0xFQUZfTkFNRRIA8gIeChpDVVNUT00RHQhBU1QBC_CQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBK_mBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBGGCIIgFAZgFAKAF_xEBFAHABQDJBWmzFPA_0gUJCQkMeAAA2AUB4AUB8AXa1gL6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=9872a378866ce61fc366ca8c34bdd9302fa41a9b" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "8860024420878272196", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKRCKARBAAAAwDWAAUBCMmFp_AFEMSN8Z3LqMj6ehiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIU9EMjhLZ2lta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFkZExBS3hfN09nXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFIdzlLREE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAEr-WfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAGlkcADYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGCSMo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=1289862cbe265fd9af13d2aab9635e3232421ec1", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQnKwgleAAAAABHERryzRCH1ehnJwgleAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=0b094204d5c18803149e081bd2bc0077d25ebb14&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLtCOhtBAAAAwDWAAUBCMmFp_AFEMSN8Z3LqMj6ehiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV4w7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiFPRDI4S2dpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBZGRMQUt4XzdPZ18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhSHc5S0RBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX01PREVMX0xFQUZfTkFNRRIA8gIeChpDVVNUT00RHQhBU1QBC_CQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBK_mBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBGGCIIgFAZgFAKAF_xEBFAHABQDJBWmzFPA_0gUJCQkMdAAA2AUB4AUB8AXgWPoFBAgAEACQBgGYBgC4BgDBBgkkKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=f35771975371bb250fd6701914534b4f595fcf68" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "5236733650551797458", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKRCKARBAAAAwDWAAUBCMmFp_AFENLt5YOosKfWSBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIThUMjJsZ2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFkczByb2Ytbk9VXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFOZzkxRkE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAExOefR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAGlkcADYBQHgBQHwBZk9-gUECAAQAJAGAZgGALgGAMEGCSMo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=e997907677e4e2f9b641d51b522a901b85944899", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQnKwgleAAAAABHSdnmAgp2sSBnJwgleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=d70eef0df40cece50465a13d05a2dc11b65eadeb&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLtCOhtBAAAAwDWAAUBCMmFp_AFENLt5YOosKfWSBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4w7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiE4VDIybGdpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBZHMwcm9mLW5PVV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhTmc5MUZBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX01PREVMX0xFQUZfTkFNRRIA8gIeChpDVVNUT00RHQhBU1QBC_DtSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBK_mBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBMTnn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AWZPfoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPA_0Ab1L9oGFgoQAGn1FQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=e8b6716ad3d2fcbdb79976f72d60f8e90ce8f5a6" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "4987762881548953446", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "201567478388503336", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKSCKASBAAAAwDWAAUBCMmFp_AFEKjm-NzbkYfmAhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIXV6NlFDd2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFkejE5MUdLNk8wXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0BBRHdQdy4umgKJASFTZy1SRzo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56TXpRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0DgFlQUEu2AIA4AKtmEjqAk5odHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcHJpY2VHcmFuLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qASv5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczM9oEAggA4AQA8ATS7J9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAaWR0ANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGCSQo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=4a887316a3197dfae07c1443a4debc62a2f17fef", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnKwgleAAAAABEoM567jRzMAhnJwgleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=eef6278d136f8df4879846840f97933e9d67388a&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLuCOhuBAAAAwDWAAUBCMmFp_AFEKjm-NzbkYfmAhiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4w7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiF1ejZRQ3dpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBZHoxOTFHSzZPMF8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNAQUR3UHcuLpoCiQEhU2ctUkc6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8JplQUEu2AIA4AKtmEjqAk5odHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcHJpY2VHcmFuLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTU9ERUxfTEVBRl9OQU1FEgDyAh4KGkNVU1RPTREdCEFTVAEL8JBJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAeAEAPAEYYIgiAUBmAUAoAX_EQEUAcAFAMkFabMU8D_SBQkJCQx4AADYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgklKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=e68b089e4fc94aa7566784ccbff299e50c8bc090" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "3876520534914199302", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "4833995299234629234", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "8352235304492782614", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKSCKASBAAAAwDWAAUBCMmFp_AFEJaok6aeuMb0cxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE0MTg4Nh8A8P2SArkCIUpUMS1FZ2lHa184UEVLekNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFFajRyM1ZBQUFxUU1FQkktSzkxUUFBS2tESkFmQ1lIN1I1bmVzXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRGhwUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASExUTY4OFE2PQEkblBGYklBUW9BRBVIVHFRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAErMKfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAGlkdADYBQHgBQHwBfKMAfoFBAgAEACQBgGYBgC4BgDBBgkkKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=7081f4ad4ae9837aaff4b4f59abc4ce7f8b02cb6", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnKwgleAAAAABEW1MTkwRnpcxnJwgleAAAAACCswp9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jyjAFiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAazCn0ewAQE.&s=71f281c81d1ae269bde275b84aa955c833ab1dea&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149414188, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 13.00001, + "cpm_publisher_currency": 13.00001, + "publisher_currency_code": "$", + "brand_category_id": 32, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 29000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLuCOhuBAAAAwDWAAUBCMmFp_AFEJaok6aeuMb0cxiq5MnUovf28WEqNgmOWItPAQAqQBGOWItPAQAqQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQrMKfR1ic8VtgAGjNunV4w7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxNDE4OCwgMTUZH_D9kgK5AiFKVDEtRWdpR2tfOFBFS3pDbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRRWo0cjNWQUFBcVFNRUJJLUs5MVFBQUtrREpBZkNZSDdSNW5lc18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RocFBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhMVE2ODhRNj0BJG5QRmJJQVFvQUQVSFRxUURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX01PREVMX0xFQUZfTkFNRRIA8gIeChpDVVNUT00RHQhBU1QBC_CQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBK_mBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBGGCIIgFAZgFAKAF_xEBFAHABQDJBWmzFPA_0gUJCQkMeAAA2AUB4AUB8AXyjAH6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=05fc37623521011853ff69d194aa6d692b6c0504" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "2318891724556922037", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "5654583906891472332", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKSCKASBAAAAwDWAAUBCMmFp_AFEMy7oJeqw8e8Thiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE3NjY5Nh8A8P2SArkCIXZ6Ml9mZ2lsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFhYzY5MFRlZi1rXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0BBRHdQdy4umgKJASFJZzhjRDo9ASRuUEZiSUFRb0FEFUhUa1FEb0pVMGxPTXpvME56TXpRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0DgFlQUEu2AIA4AKtmEjqAk5odHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcHJpY2VHcmFuLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qASv5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczM9oEAggA4AQA8ATF3Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAaWR0ANgFAeAFAfAFwvIX-gUECAAQAJAGAZgGALgGAMEGCSQo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=f929565158c7caa5b85e88fa456cdd04d0e4b6d8", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnKwgleAAAAABHMHeiiGh55ThnJwgleAAAAACDF3Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jC8hdiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAcXdn0ewAQE.&s=a93773067b8588465b9c007e19970bd9e08c1b6c&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417669, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 4, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLuCKBuBAAAAwDWAAUBCMmFp_AFEMy7oJeqw8e8Thiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZCQkI4D8hCQkIJEApEQkAMQkJsOA_MNbJqwc47UhA7UhIAlDF3Z9HWJzxW2AAaM26dXjDuAWAAQGKAQNVU0SSAQEG8FWYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjx1ZignYScsIDI1Mjk4ODUsIDE1Nzc2OTc5OTMpO3VmKCdyJywgMTQ5NDE3NjY5LCAxNRkf8P2SArkCIXZ6Ml9mZ2lsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFhYzY5MFRlZi1rXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0BBRHdQdy4umgKJASFJZzhjRDo9ASRuUEZiSUFRb0FEFUhUa1FEb0pVMGxPTXpvME56TXpRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwmmVBQS7YAgDgAq2YSOoCTmh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvbG9uZ2Zvcm0vYmFzaWNfd19wcmljZUdyYW4uaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFkNVU1RPTV9NT0RFTF9MRUFGX05BTUUSAPICHgoaQ1VTVE9NER0IQVNUAQvwkElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qASv5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczM9oEAggB4AQA8ARhgiCIBQGYBQCgBf8RARQBwAUAyQVpsxTwP9IFCQkJDHgAANgFAeAFAfAFwvIX-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=a3a24cbf148bb959f539e883af3d118f64e81bc9" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "2268711976967571175", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "8379392370800588084", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "6225030428438795793", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "1592368529919250324", + "nobid": true, + "ad_profile_id": 1182765 + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_priceGran_2/description.md b/test/fake-server/fixtures/longform/longform_priceGran_2/description.md new file mode 100644 index 00000000000..8bc4d242f46 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_priceGran_2/description.md @@ -0,0 +1,62 @@ +Test Page - 'integrationExamples/longform/basic_w_priceGran.html' +Test Spec File - 'test/spec/e2e/longform/basic_w_priceGran.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: false + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +const customConfigObject = { + 'buckets': [{ + 'precision': 2, // default is 2 if omitted - means 2.1234 rounded to 2 decimal places = 2.12 + 'min': 0, + 'max': 5, + 'increment': 0.01 // from $0 to $5, 1-cent increments + }, + { + 'precision': 2, + 'min': 5, + 'max': 8, + 'increment': 0.05 // from $5 to $8, round down to the previous 5-cent increment + }, + { + 'precision': 2, + 'min': 8, + 'max': 40, + 'increment': 0.5 // from $8 to $40, round down to the previous 50-cent increment + }] +}; + +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + }, + priceGranularity: customConfigObject +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_priceGran_2/request.json b/test/fake-server/fixtures/longform/longform_priceGran_2/request.json new file mode 100644 index 00000000000..f2f20700ffe --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_priceGran_2/request.json @@ -0,0 +1,137 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + } + ], + "user": {}, + "brand_category_uniqueness": true + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_priceGran_2/response.json b/test/fake-server/fixtures/longform/longform_priceGran_2/response.json new file mode 100644 index 00000000000..ee7494ea665 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_priceGran_2/response.json @@ -0,0 +1,188 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "6123799897847039642", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKRCKARBAAAAwDWAAUBCMmFp_AFEJqN_JX98Yb-VBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIUN6dzV3Z2lta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFYQm5aNWdRbi1NXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0RBRHdQdy4umgKJASFJQS1IREE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDPaBAIIAOAEAPAEr-WfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAGlkcADYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGCSMo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=a7e4ff14c60153db90971365f90e514f45875324", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQnJwgleAAAAABGaBr_Sjxv8VBnJwgleAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=842283d9de78fba7e92fac09f95bb63902a0b54a&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLtCOhtBAAAAwDWAAUBCMmFp_AFEJqN_JX98Yb-VBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV4zrgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiFDenc1d2dpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWEJuWjVnUW4tTV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNEQUR3UHcuLpoCiQEhSUEtSERBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX01PREVMX0xFQUZfTkFNRRIA8gIeChpDVVNUT00RHQhBU1QBC_CQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBK_mBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQz2gQCCAHgBADwBGGCIIgFAZgFAKAF_xEBFAHABQDJBWmzFPA_0gUJCQkMdAAA2AUB4AUB8AXgWPoFBAgAEACQBgGYBgC4BgDBBgkkKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=0fb74d544be103e1440c3ee8f7abc14d2c322d15" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "889501690217653627", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKSCKASBAAAAwDWAAUBCMmFp_AFEPvKoYSxoomsDBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIWt6eTlHUWlua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFRSzgzNzR1VnVVXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0RBRHdQdy4umgKJASFTd19PR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDPaBAIIAOAEAPAE0uyfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAGlkdADYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgkkKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=78ac70aeeae1da43c90efd248fb30a4ee630ffd5", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnJwgleAAAAABF7ZYgQEyVYDBnJwgleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=f21e2a522ddcaf0a67bc7d52f70288fdf7e6f3dd&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLuCOhuBAAAAwDWAAUBCMmFp_AFEPvKoYSxoomsDBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4zrgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFrenk5R1FpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBUUs4Mzc0dVZ1VV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNEQUR3UHcuLpoCiQEhU3dfT0d3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX01PREVMX0xFQUZfTkFNRRIA8gIeChpDVVNUT00RHQhBU1QBC_CQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBK_mBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQz2gQCCAHgBADwBGGCIIgFAZgFAKAF_xEBFAHABQDJBWmzFPA_0gUJCQkMeAAA2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=69611db1024ddb77e0754087ddbeae68a00633a1" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "2793012314322059080", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKSCKASBAAAAwDWAAUBCMmFp_AFEMj-1IPuvLHhJhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE0MTg4Nh8A8P2SArkCIXhqb2ZBZ2lHa184UEVLekNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFFajRyM1ZBQUFxUU1FQkktSzkxUUFBS2tESkFRQWhlSGt0VHVRXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRGhwUF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0RBRHdQdy4umgKJASExZzc1OFE2PQEkblBGYklBUW9BRBVIVHFRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDPaBAIIAOAEAPAErMKfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAGlkdADYBQHgBQHwBfKMAfoFBAgAEACQBgGYBgC4BgDBBgkkKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=243f58d85b09468de2fe485662c950a86b5d90fb", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnJwgleAAAAABFIP3Xg5sXCJhnJwgleAAAAACCswp9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jyjAFiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAazCn0ewAQE.&s=99dd40ab6ae736c6aa7c96b5319e1723ea581e0d&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149414188, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 13.00001, + "cpm_publisher_currency": 13.00001, + "publisher_currency_code": "$", + "brand_category_id": 32, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 29000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLuCOhuBAAAAwDWAAUBCMmFp_AFEMj-1IPuvLHhJhiq5MnUovf28WEqNgmOWItPAQAqQBGOWItPAQAqQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQrMKfR1ic8VtgAGjNunV4zrgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxNDE4OCwgMTUZH_D9kgK5AiF4am9mQWdpR2tfOFBFS3pDbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRRWo0cjNWQUFBcVFNRUJJLUs5MVFBQUtrREpBUUFoZUhrdFR1UV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RocFBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNEQUR3UHcuLpoCiQEhMWc3NThRNj0BJG5QRmJJQVFvQUQVSFRxUURvSlUwbE9Nem8wTnpRelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCaZUFBLtgCAOACrZhI6gJOaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3ByaWNlR3Jhbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX01PREVMX0xFQUZfTkFNRRIA8gIeChpDVVNUT00RHQhBU1QBC_CQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBK_mBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQz2gQCCAHgBADwBGGCIIgFAZgFAKAF_xEBFAHABQDJBWmzFPA_0gUJCQkMeAAA2AUB4AUB8AXyjAH6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=d9a3c817696f263b6e6d81f7251827fa54a47c37" + } + } + } + ] + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "45194178065897765", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2def02900a6cda", + "tag_id": 15394006, + "auction_id": "3805126675549039795", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QKSCKASBAAAAwDWAAUBCMmFp_AFELPZ2uvQ0aHnNBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIWNUM1hkZ2lua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCOXFZRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFjbUQzMExTME9VXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0BBRHdQdy4umgKJASEtUTZrXzo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56UXpRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0DgFlQUEu2AIA4AKtmEjqAk5odHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcHJpY2VHcmFuLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qASv5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0M9oEAggA4AQA8ASL4Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAaWR0ANgFAeAFAfAF2tYC-gUECAAQAJAGAZgGALgGAMEGCSQo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=bbeaa584bea9afedf6dcab51b1616988441dfa22", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQnJwgleAAAAABGzrHYNjYbONBnJwgleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=b7e937b5acc3d8b910f6b08c3a40e04aa10818cd&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_priceGran.html&e=wqT_3QLuCOhuBAAAAwDWAAUBCMmFp_AFELPZ2uvQ0aHnNBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4zrgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk3OTkzKTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFjVDNYZGdpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQjlxWURrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBY21EMzBMUzBPVV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNAQUR3UHcuLpoCiQEhLVE2a186PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8JplQUEu2AIA4AKtmEjqAk5odHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcHJpY2VHcmFuLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTU9ERUxfTEVBRl9OQU1FEgDyAh4KGkNVU1RPTREdCEFTVAEL8N5JRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEr-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDPaBAIIAeAEAPAEi-GfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAAAAAAADYBQHgBQHwBdrWAvoFBAgAEACQBgGYBgC4BgDBBgAAYeYo8D_QBvUv2gYWChABDy4BAFAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=279827127eba3204bc3a152b8abaf701260eb494" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_requireExactDuration_1/description.md b/test/fake-server/fixtures/longform/longform_requireExactDuration_1/description.md new file mode 100644 index 00000000000..8fe815912e8 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_requireExactDuration_1/description.md @@ -0,0 +1,40 @@ +Test Page - 'integrationExamples/longform/basic_w_requireExactDuration.html' +Test Spec File - 'test/spec/e2e/longform/basic_w_requireExactDuration.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_requireExactDuration_1/request.json b/test/fake-server/fixtures/longform/longform_requireExactDuration_1/request.json new file mode 100644 index 00000000000..1e036c367c6 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_requireExactDuration_1/request.json @@ -0,0 +1,401 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 15, + "maxduration": 15 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_requireExactDuration_1/response.json b/test/fake-server/fixtures/longform/longform_requireExactDuration_1/response.json new file mode 100644 index 00000000000..b91a5a3d523 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_requireExactDuration_1/response.json @@ -0,0 +1,330 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "4424969993715088689", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCM2Op_AFELH6mcm82Km0PRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIWhUNEwzZ2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFjek5XT196NC1VXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFTZy1SR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEu8J8uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCADgBADwBNLsn0eIBQGYBQCgBf______AQUUAcAFAMkFaWIU8D_SBQkJCQx4AADYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgklKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=3d8c006f4f85ecffd49c500554c3852b9079ff2b", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQlNxwleAAAAABExfSbJw6ZoPRlNxwleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=e2c6cedf67a96613ea8851673ebcfdd25a19435c&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL5COh5BAAAAwDWAAUBCM2Op_AFELH6mcm82Km0PRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4lbgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFoVDRMM2dpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQm5LY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBY3pOV09fejQtVV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhU2ctUkd3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBeZUFBLtgCAOACrZhI6gJZaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3JlcXVpcmVFeGFjdER1cmEBLnwuaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFjIWACBMRUFGX05BTUUBHQgeCho2HQAIQVNUAT7wkElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATC5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczM9oEAggB4AQA8ARhjSCIBQGYBQCgBf8RARQBwAUAyQVpvhTwP9IFCQkJDHgAANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=de23f822f483b6e85615d4297d872262310c240d" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "2013100091803497646", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "2659371493620557151", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCM2Op_AFEN_KtpiJif_zJBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIWR6eUJxQWlua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFWSHZpWGF0Q09zXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASEtQTVuX2c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEu8J8uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCADgBADwBIvhn0eIBQGYBQCgBf______AQUUAcAFAMkFaWIU8D_SBQkJCQx4AADYBQHgBQHwBdrWAvoFBAgAEACQBgGYBgC4BgDBBgklKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=eafba287adec58427d1679f43a84ebb19223c4e7", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQlNxwleAAAAABFfpQ2TSPznJBlNxwleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=b335b2c79378c1699d54cf9ffe097958bd989a0b&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL5COh5BAAAAwDWAAUBCM2Op_AFEN_KtpiJif_zJBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4lbgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFkenlCcUFpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQm5LY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVkh2aVhhdENPc18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhLUE1bl9nNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBeZUFBLtgCAOACrZhI6gJZaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3JlcXVpcmVFeGFjdER1cmEBLnwuaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFjIWACBMRUFGX05BTUUBHQgeCho2HQAIQVNUAT7wkElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATC5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczM9oEAggB4AQA8ARhjSCIBQGYBQCgBf8RARQBwAUAyQVpvhTwP9IFCQkJDHgAANgFAeAFAfAF2tYC-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=66b9e769da1e1d68b202605fc178fc172046e9c5" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "5424637592449788792", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "3470330348822422583", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "4415549097692431196", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "1628359298176905427", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "1949183409076770477", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "4707958683377993236", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "2499032734231846767", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "1295788165766409083", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKcCKAcBAAAAwDWAAUBCM2Op_AFEPvWpeWKj-T9ERiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCITR6eVM5d2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFlS0tONGIwQS00XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFOZzkxRkE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEu8J8uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCADgBADwBMTnn0eIBQGYBQCgBf______AQUUAcAFAMkFaWIU8D_SBQkJCQx0AADYBQHgBQHwBZk9-gUECAAQAJAGAZgGALgGAMEGCSQo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=3cb170cdf53cd6a6bfec0676659daeb6170895e3", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQlNxwleAAAAABF7a6mseJD7ERlNxwleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=45f5cce314725120ec769afaacbb7aa92d32e674&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL4COh4BAAAAwDWAAUBCM2Op_AFEPvWpeWKj-T9ERiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4lbgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiE0enlTOXdpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQm5LY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBZUtLTjRiMEEtNF8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhTmc5MUZBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBeZUFBLtgCAOACrZhI6gJZaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3JlcXVpcmVFeGFjdER1cmEBLnwuaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFjIWACBMRUFGX05BTUUBHQgeCho2HQAIQVNUAT7w7UlGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATC5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczM9oEAggB4AQA8ATE559HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwP9AG9S_aBhYKEACJABUBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=5c9f03b9c5c6cc2bf070d8cdc6c9af4b06595879" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "702761892273189154", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKcCKAcBAAAAwDWAAUBCM2Op_AFEKLi_7T7xq3gCRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE4NDg2Nh8A8P2SArkCITdUeVNDd2lva184UEVQYmpuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFHSTh5N21BQUFzUU1FQmlQTXU1Z0FBTEVESkFYQTM4QzJIcnVFXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHFKUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFaQThESlE2PQEkblBGYklBUW9BRBVIVHNRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEu8J8uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCADgBADwBPbjn0eIBQGYBQCgBf______AQUUAcAFAMkFaWIU8D_SBQkJCQx0AADYBQHgBQHwBf0F-gUECAAQAJAGAZgGALgGAMEGCSQo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=f459a78a1b20c9643b90d7491f22593d79cff253", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQlNxwleAAAAABEi8Z-2N7bACRlNxwleAAAAACD2459HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1j9BWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgB9uOfR7ABAQ..&s=83289d261bced5258930a1ce2464260a96565241&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418486, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 14.00001, + "cpm_publisher_currency": 14.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL4COh4BAAAAwDWAAUBCM2Op_AFEKLi_7T7xq3gCRiq5MnUovf28WEqNgmOWItPAQAsQBGOWItPAQAsQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ9uOfR1ic8VtgAGjNunV4lbgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTt1ZigncicsIDE0OTQxODQ4NiwgMTUZH_D9kgK5AiE3VHlTQ3dpb2tfOFBFUGJqbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQm5LY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRR0k4eTdtQUFBc1FNRUJpUE11NWdBQUxFREpBWEEzOEMySHJ1RV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RxSlBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNEQUR3UHcuLpoCiQEhWkE4REpRNj0BJG5QRmJJQVFvQUQVSFRzUURvSlUwbE9Nem8wTnpNelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBeZUFBLtgCAOACrZhI6gJZaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3JlcXVpcmVFeGFjdER1cmEBLnwuaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFjIWACBMRUFGX05BTUUBHQgeCho2HQAIQVNUAT7w7UlGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATC5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDczM9oEAggB4AQA8AT2459HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAF_QX6BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwP9AG9S_aBhYKEACJABUBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=16a432c0a05db78fa40fefc7967796ff2a2e8444" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "8192047453391406704", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCM2Op_AFEPCMp9SW-P_XcRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCIXhUdGdYZ2lHa184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFFajRyM1ZBQUFxUU1FQkktSzkxUUFBS2tESkFSR0FWSjZORXVNXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRGhwUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0BBRHdQdy4umgKJASFKUThlRDo9ASRuUEZiSUFRb0FEFUhUcVFEb0pVMGxPTXpvME56TXpRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwXmVBQS7YAgDgAq2YSOoCWWh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvbG9uZ2Zvcm0vYmFzaWNfd19yZXF1aXJlRXhhY3REdXJhAS7wny5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEwuYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MzPaBAIIAOAEAPAE39-fR4gFAZgFAKAF______8BBRQBwAUAyQVpYhTwP9IFCQkJDHgAANgFAeAFAfAFrLwU-gUECAAQAJAGAZgGALgGAMEGCSUo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=8bf90e0756a9265ab6ac029e883e14803447a7fb", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQlNxwleAAAAABFwxolqwf-vcRlNxwleAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=cf9ea0df7655a5c6150a527399cb2852c61ec14a&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 13.00001, + "cpm_publisher_currency": 13.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL5COh5BAAAAwDWAAUBCM2Op_AFEPCMp9SW-P_XcRiq5MnUovf28WEqNgmOWItPAQAqQBGOWItPAQAqQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV4lbgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiF4VHRnWGdpR2tfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQm5LY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRRWo0cjNWQUFBcVFNRUJJLUs5MVFBQUtrREpBUkdBVko2TkV1TV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RocFBfRDdvRENWTkpUak02TkRjek0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCZjBrcVFVCRNAQUR3UHcuLpoCiQEhSlE4ZUQ6PQEkblBGYklBUW9BRBVIVHFRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEufC5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAIExFQUZfTkFNRQEdCB4KGjYdAAhBU1QBPvCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBGGNIIgFAZgFAKAF_xEBFAHABQDJBWm-FPA_0gUJCQkMeAAA2AUB4AUB8AWsvBT6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=cefb5f476892ed335cd8b0fc20fabf7650b1d2e3" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "9041697983972949142", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCM2Op_AFEJb5yqiVjaS9fRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE3NjY5Nh8A8P2SArkCIURUMkpEd2lsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYeHdUOEhkUmVzXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFJZzhjRGc2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEu8J8uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCADgBADwBMXdn0eIBQGYBQCgBf______AQUUAcAFAMkFaWIU8D_SBQkJCQx4AADYBQHgBQHwBcLyF_oFBAgAEACQBgGYBgC4BgDBBgklKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=308ea3c3363b379ef62dbb6c7a91d1d91d9ee47a", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQlNxwleAAAAABGWvBJVaZB6fRlNxwleAAAAACDF3Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jC8hdiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAcXdn0ewAQE.&s=70a33aa1a57d812d9da5c321e898197836ed16f8&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417669, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 4, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL5CKB5BAAAAwDWAAUBCM2Op_AFEJb5yqiVjaS9fRiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZCQkI4D8hCQkIJEApEQkAMQkJsOA_MNbJqwc47UhA7UhIAlDF3Z9HWJzxW2AAaM26dXiVuAWAAQGKAQNVU0SSAQEG8FWYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjx1ZignYScsIDI1Mjk4ODUsIDE1Nzc2OTkxNDkpO3VmKCdyJywgMTQ5NDE3NjY5LCAxNRkf8P2SArkCIURUMkpEd2lsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYeHdUOEhkUmVzXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGN6TS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJmMGtxUVUJE0RBRHdQdy4umgKJASFJZzhjRGc2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOek16UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEufC5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAIExFQUZfTkFNRQEdCB4KGjYdAAhBU1QBPvDeSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzMz2gQCCAHgBADwBMXdn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXC8hf6BQQIABAAkAYBmAYAuAYAwQYAAGHxKPA_0Ab1L9oGFgoQAQ8uAQBQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=ee15793b41a9d22ffad9bd46878a47821d6044fa" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "356000177781223639", + "nobid": true, + "ad_profile_id": 1182765 + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_requireExactDuration_2/description.md b/test/fake-server/fixtures/longform/longform_requireExactDuration_2/description.md new file mode 100644 index 00000000000..8fe815912e8 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_requireExactDuration_2/description.md @@ -0,0 +1,40 @@ +Test Page - 'integrationExamples/longform/basic_w_requireExactDuration.html' +Test Spec File - 'test/spec/e2e/longform/basic_w_requireExactDuration.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_requireExactDuration_2/request.json b/test/fake-server/fixtures/longform/longform_requireExactDuration_2/request.json new file mode 100644 index 00000000000..83877ff9ac0 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_requireExactDuration_2/request.json @@ -0,0 +1,141 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "minduration": 30, + "maxduration": 30 + } + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_requireExactDuration_2/response.json b/test/fake-server/fixtures/longform/longform_requireExactDuration_2/response.json new file mode 100644 index 00000000000..e776170328e --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_requireExactDuration_2/response.json @@ -0,0 +1,188 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "198494455120718841", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKcCKAcBAAAAwDWAAUBCM2Op_AFEPnX6_r7tMzgAhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIXpUMDRwQWlta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFhejg2NVNoMGVJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMwTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZc2xxUVUJE0RBRHdQdy4umgKJASFKQTkzRFE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelEzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEu8J8uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQ32gQCCADgBADwBK_ln0eIBQGYBQCgBf______AQUUAcAFAMkFaWIU8D_SBQkJCQx0AADYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGCSQo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=645b6e0a37eb3a315bc3208365bd4fc03e1ecd18", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQlNxwleAAAAABH561q_pzHBAhlNxwleAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=35a21bf0bef1ca2c138e37a2e025ad523b5a1db2&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL4COh4BAAAAwDWAAUBCM2Op_AFEPnX6_r7tMzgAhiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV4xbgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiF6VDA0cEFpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQm5LY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBYXo4NjVTaDBlSV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjME4tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWXNscVFVCRNEQUR3UHcuLpoCiQEhSkE5M0RRNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRM1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBeZUFBLtgCAOACrZhI6gJZaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193X3JlcXVpcmVFeGFjdER1cmEBLnwuaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFjIWACBMRUFGX05BTUUBHQgeCho2HQAIQVNUAT7wkElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATC5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0N9oEAggB4AQA8ARhjSCIBQGYBQCgBf8RARQBwAUAyQVpvhTwP9IFCQkJDHQAANgFAeAFAfAF4Fj6BQQIABAAkAYBmAYAuAYAwQYJJCjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=cf3130989b2b4fe5271240d65055b85d1192b78a" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "998265386177420565", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCM2Op_AFEJW6sbXGoqPtDRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCIVdqM1Jid2lHa184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFFajRyM1ZBQUFxUU1FQkktSzkxUUFBS2tESkFmcXpCNjduMU9rXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRGhwUF9EN29EQ1ZOSlRqTTZORGMwTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZc2xxUVUJE0BBRHdQdy4umgKJASFLZzlMRDo9ASRuUEZiSUFRb0FEFUhUcVFEb0pVMGxPTXpvME56UTNRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwXmVBQS7YAgDgAq2YSOoCWWh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvbG9uZ2Zvcm0vYmFzaWNfd19yZXF1aXJlRXhhY3REdXJhAS7wny5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEwuYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDfaBAIIAOAEAPAE39-fR4gFAZgFAKAF______8BBRQBwAUAyQVpYhTwP9IFCQkJDHgAANgFAeAFAfAFrLwU-gUECAAQAJAGAZgGALgGAMEGCSUo8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=33f01ddfb15bc84d7bf134a168aeb9d9de76fa57", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQlNxwleAAAAABEVXaxmFI3aDRlNxwleAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=023640de7c18a0d0e80b2456144b89a351455cda&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 13.00001, + "cpm_publisher_currency": 13.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL5COh5BAAAAwDWAAUBCM2Op_AFEJW6sbXGoqPtDRiq5MnUovf28WEqNgmOWItPAQAqQBGOWItPAQAqQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV4xbgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiFXajNSYndpR2tfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQm5LY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRRWo0cjNWQUFBcVFNRUJJLUs5MVFBQUtrREpBZnF6QjY3bjFPa18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RocFBfRDdvRENWTkpUak02TkRjME4tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWXNscVFVCRNAQUR3UHcuLpoCiQEhS2c5TEQ6PQEkblBGYklBUW9BRBVIVHFRRG9KVTBsT016bzBOelEzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEufC5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAIExFQUZfTkFNRQEdCB4KGjYdAAhBU1QBPvCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQ32gQCCAHgBADwBGGNIIgFAZgFAKAF_xEBFAHABQDJBWm-FPA_0gUJCQkMeAAA2AUB4AUB8AWsvBT6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=515ab42a7fdcad8fcf34e4fb98b1e076a75006a9" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "8884527464400177295", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKcCKAcBAAAAwDWAAUBCM2Op_AFEI-RmcaB1Yumexiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCITVqcmtHUWlta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFYb0paejlaR09NXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMwTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZc2xxUVUJE0BBRHdQdy4umgKJASFPdy1pRjo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56UTNRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwXmVBQS7YAgDgAq2YSOoCWWh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvbG9uZ2Zvcm0vYmFzaWNfd19yZXF1aXJlRXhhY3REdXJhAS7wny5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEwuYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDfaBAIIAOAEAPAExOefR4gFAZgFAKAF______8BBRQBwAUAyQVpYhTwP9IFCQkJDHQAANgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYJJCjwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=dc7d874e52ac39980ec1070a7769632be56a2f00", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQlNxwleAAAAABGPSMYYqC5MexlNxwleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=9fd739e9f0a6054296f9bb5168c49a89a425795c&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL4COh4BAAAAwDWAAUBCM2Op_AFEI-RmcaB1Yumexiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4xbgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiE1anJrR1FpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQm5LY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWG9KWno5WkdPTV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjME4tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWXNscVFVCRNAQUR3UHcuLpoCiQEhT3ctaUY6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelEzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEufC5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAIExFQUZfTkFNRQEdCB4KGjYdAAhBU1QBPvDtSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQ32gQCCAHgBADwBMTnn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AWZPfoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPA_0Ab1L9oGFgoQAIkAFQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=1f8ea8f07f781fe149beb0d65dd25ad725a12f3b" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "1279521442385130931", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCM2Op_AFELPr8f6Pv_HgERiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5MTQ5KTsBHTByJywgMTQ5NDE3NjY5Nh8A8P2SArkCIW9EeDJEQWlsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFkb01fcFZkVGVVXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGMwTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZc2xxUVUJE0RBRHdQdy4umgKJASFKdzlKRHc2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOelEzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEu8J8uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQ32gQCCADgBADwBMXdn0eIBQGYBQCgBf______AQUUAcAFAMkFaWIU8D_SBQkJCQx4AADYBQHgBQHwBcLyF_oFBAgAEACQBgGYBgC4BgDBBgklKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=4b14660ae659b3d301ec6c40f0f096bb88db145b", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQlNxwleAAAAABGzddz_-MXBERlNxwleAAAAACDF3Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jC8hdiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAcXdn0ewAQE.&s=5f277bcab624f05c9d3a3a9c111961f3f33ccca2&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417669, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 4, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_w_requireExactDuration.html&e=wqT_3QL5CKB5BAAAAwDWAAUBCM2Op_AFELPr8f6Pv_HgERiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZCQkI4D8hCQkIJEApEQkAMQkJsOA_MNbJqwc47UhA7UhIAlDF3Z9HWJzxW2AAaM26dXjFuAWAAQGKAQNVU0SSAQEG8FWYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjx1ZignYScsIDI1Mjk4ODUsIDE1Nzc2OTkxNDkpO3VmKCdyJywgMTQ5NDE3NjY5LCAxNRkf8P2SArkCIW9EeDJEQWlsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCbktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFkb01fcFZkVGVVXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGMwTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZc2xxUVUJE0RBRHdQdy4umgKJASFKdzlKRHc2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOelEzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8F5lQUEu2AIA4AKtmEjqAllodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dfcmVxdWlyZUV4YWN0RHVyYQEufC5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAIExFQUZfTkFNRQEdCB4KGjYdAAhBU1QBPvDeSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMLmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQ32gQCCAHgBADwBMXdn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXC8hf6BQQIABAAkAYBmAYAuAYAwQYAAGHxKPA_0Ab1L9oGFgoQAQ8uAQBQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=7bb7b75e4769a1260eaed2c79752ee542b4d28ce" + } + } + } + ] + }, + { + "uuid": "25593f19ac7ed2", + "tag_id": 15394006, + "auction_id": "7664937561033023835", + "nobid": true, + "ad_profile_id": 1182765 + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/description.md b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/description.md new file mode 100644 index 00000000000..159ebbcc30b --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/description.md @@ -0,0 +1,40 @@ +Test Page - 'integrationExamples/longform/basic_wo_brandCategoryExclusion.html' +Test Spec File - 'test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: false + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: false + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/request.json b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/request.json new file mode 100644 index 00000000000..2d1fa3f16bf --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/request.json @@ -0,0 +1,386 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/response.json b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/response.json new file mode 100644 index 00000000000..bfef650e07a --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_1/response.json @@ -0,0 +1,654 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "6316075342634007031", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEPfztqL2oc3TVxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIV96eWNLZ2lua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFZYkYwSW1fZGU0XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0BBRHdQdy4umgKJASE5dzR0Xzo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56VXdRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0UwFlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCADgBADwBIvhn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXa1gL6BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAaakRAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=bb81a081c756fd493253bf765c06ff46888f009a", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABH3uU1kDzWnVxk3yQleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=a3da30186f69a5ce2edcfc14fa3a31b92ee70060&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEPfztqL2oc3TVxiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFfenljS2dpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWWJGMEltX2RlNF8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNAQUR3UHcuLpoCiQEhOXc0dF86PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFkNVU1RPTQ0WQExFQUZfTkFNRRIA8gIeChpDMh0ACEFTVAEo8JBJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEy-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTDaBAIIAeAEAPAEYZAgiAUBmAUAoAX_EQEUAcAFAMkFacEU8D_SBQkJCQx4AADYBQHgBQHwBdrWAvoFBAgAEACQBgGYBgC4BgDBBgklKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=62b9db151b3739399e0970c74fafc4bb61486510" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "8259815099516488747", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKfCKAfBAAAAwDWAAUBCLeSp_AFEKuY2qihwrDQchiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIW1UeUFCd2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFYNml4eGFGdE8wXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASFOUTg3RkE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ATE559HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAAGmpDQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=8a55db8e788616ef057647b49c0560296fdacb65", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQk3yQleAAAAABErjBYVEsKgchk3yQleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=75d46be63f76fd4062f354a56c692855da366148&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL7COh7BAAAAwDWAAUBCLeSp_AFEKuY2qihwrDQchiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiFtVHlBQndpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWDZpeHhhRnRPMF8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhTlE4N0ZBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPDtSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBMTnn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AWZPfoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPA_0Ab1L9oGFgoQAIkDFQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=59e0ab1f626c2e4dc5c4f6e6837b77559cf502b4" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "7960926407704980889", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEJnboLK5jLm9bhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCIUZEeFl4QWlta184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFhSUpVQVhXMC1JXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASFTQThFR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ATf359HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAFrLwU-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAGmpEQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=8d00bc7576c3cc19fe8b3fb4aa42966583741dfa", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABGZLUiWY-R6bhk3yQleAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=c813392cbb81d43466d5d949b9bebc2305e6fe7d&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEJnboLK5jLm9bhiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiFGRHhZeEFpbWtfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBYUlKVUFYVzAtSV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhU0E4RUd3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBGGQIIgFAZgFAKAF_xEBFAHABQDJBWnBFPA_0gUJCQkMeAAA2AUB4AUB8AWsvBT6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=284760a6e2d4f226b639d29237e9c1aa25ca49a9" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "7561862402601574638", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEO7By9umucj4aBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCITREcWpId2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFTSVA1dzg5RGVRXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0BBRHdQdy4umgKJASFTUTlYRzo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56VXdRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0UwFlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCADgBADwBNLsn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAaakRAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=917e8af2ed1c82091f487b6abd78de47b99e9e46", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABHu4HJryiHxaBk3yQleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=ff73768bfb14e9ae603064fc91e95b60c8d872e2&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEO7By9umucj4aBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiE0RHFqSHdpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBU0lQNXc4OURlUV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNAQUR3UHcuLpoCiQEhU1E5WEc6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFkNVU1RPTQ0WQExFQUZfTkFNRRIA8gIeChpDMh0ACEFTVAEo8JBJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEy-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTDaBAIIAeAEAPAEYZAgiAUBmAUAoAX_EQEUAcAFAMkFacEU8D_SBQkJCQx4AADYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgklKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=84c66b632a2930d28092e00985ee154ba2e97288" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "6577796711489765634", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEIKC0sii6MGkWxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIVBUem53UWlua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFlaE5hMWl1Y3V3XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASE5dzR0X2c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ASL4Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAF2tYC-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAGmpEQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=ea4a54786a8f3cf5177b3372ce97682da060c358", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABECgRQpQgdJWxk3yQleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=778fbf3014328ec0b01d6ebd7b306be32cf79950&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEIKC0sii6MGkWxiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFQVHpud1FpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBZWhOYTFpdWN1d18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhOXc0dF9nNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBGGQIIgFAZgFAKAF_xEBFAHABQDJBWnBFPA_0gUJCQkMeAAA2AUB4AUB8AXa1gL6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=28a3b583912b95528519f63a75c0fe2d06064e7b" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "2418275491761094586", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFELr_z7v0l9zHIRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIUF6MUJSZ2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFVNUE2dXpUVy1ZXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASFTUTlYR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ATS7J9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAGmpEQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=c563626304ebb31fd6f1644cc2d4098133dec096", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABG6_3NHv3CPIRk3yQleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=88c9fbb2b9dc273865a5f9238543a29224908b26&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFELr_z7v0l9zHIRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFBejFCUmdpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVTVBNnV6VFctWV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhU1E5WEd3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBGGQIIgFAZgFAKAF_xEBFAHABQDJBWnBFPA_0gUJCQkMeAAA2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=e76aeb8daad477f4428631fc89d277d4a4646ded" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "5990197965284733361", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFELHjwfj9qd2QUxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIURUeDF3d2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFmWXBYSEoxUGVNXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0BBRHdQdy4umgKJASFTUTlYRzo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56VXdRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0UwFlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCADgBADwBNLsn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAaakRAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=e443347514ff5f6a52a2811f591f9a346061a756", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABGxcRDfT3UhUxk3yQleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=50348eb78116f7d35ca5ac6f6fa4c5548a6ceffc&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFELHjwfj9qd2QUxiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFEVHgxd3dpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBZllwWEhKMVBlTV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNAQUR3UHcuLpoCiQEhU1E5WEc6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFkNVU1RPTQ0WQExFQUZfTkFNRRIA8gIeChpDMh0ACEFTVAEo8JBJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEy-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTDaBAIIAeAEAPAEYZAgiAUBmAUAoAX_EQEUAcAFAMkFacEU8D_SBQkJCQx4AADYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgklKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=a24f331ff55f96c5afe5134762f8d8e230b6287c" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "4399290132982250349", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEO32psKU4tqGPRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCITN6dDlwd2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFRWlZqbkpncmV3XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0BBRHdQdy4umgKJASFTUTlYRzo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56VXdRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0UwFlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCADgBADwBNLsn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAaakRAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=0d9b0a06992ff427d281fae8d8b18d4e6838b944", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABFtu0lIEWsNPRk3yQleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=52a446d84f5e9a2baa068760c4406f4a567a911a&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEO32psKU4tqGPRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiEzenQ5cHdpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBUVpWam5KZ3Jld18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNAQUR3UHcuLpoCiQEhU1E5WEc6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFkNVU1RPTQ0WQExFQUZfTkFNRRIA8gIeChpDMh0ACEFTVAEo8JBJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEy-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTDaBAIIAeAEAPAEYZAgiAUBmAUAoAX_EQEUAcAFAMkFacEU8D_SBQkJCQx4AADYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgklKPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=e6ae592b5fc3de0053b5acd46294b83549aa00c8" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "8677372908685012092", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEPzYku74lY62eBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIVVEeGp5d2lua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFVN29abmh2c09RXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASE5dzR0X2c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ASL4Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAF2tYC-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAGmpEQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=522b764f2276ce75053a55a0198b79fb8d1d39d5", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABF8rMSNrzhseBk3yQleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=02b75d0ecc71c7897b9590be5e50b094158c8097&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEPzYku74lY62eBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFVRHhqeXdpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVTdvWm5odnNPUV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhOXc0dF9nNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBGGQIIgFAZgFAKAF_xEBFAHABQDJBWnBFPA_0gUJCQkMeAAA2AUB4AUB8AXa1gL6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=0ae5558a7b0cc87eaa0c6811522629963b41bac5" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "617065604518434488", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFELitu4PevJDICBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIUFqMVNSZ2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFaUzdZYTg5Ny13XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASFTUTlYR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9HYBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ATS7J9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAAAAAAAAAAAAAAAAAAAAEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=f59bdb7b0f7020f75c6019f23686915b72d61779", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABG41m7g5UGQCBk3yQleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=912a721db8e85d4d64a8869d7cf9b56cd0c24907&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFELitu4PevJDICBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFBajFTUmdpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWlM3WWE4OTctd18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhU1E5WEd3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBGGQIIgFAZgFAKAF_xEBGAHABQDJBQAFARTwP9IFCQkFC3wAAADYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgEhMAAA8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=9b4372ae0674305d9cd7152959910d1fa7d4daec" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "8957511111704921777", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKfCKAfBAAAAwDWAAUBCLeSp_AFELGNpebanN6nfBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIXVqdHppQWlta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFUM1dNOVpkQU9JXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0BBRHdQdy4umgKJASFIZzhRRDo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56VXdRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0UwFlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCADgBADwBK_ln0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXgWPoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPC_0Ab1L9oGFgoQAAAAaakNAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=a8817d9dc3326ba1b59fe308f126ddaca5710a9c", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQk3yQleAAAAABGxRsms5XhPfBk3yQleAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=a0ac94e902cbc609881fa9f39537bbc67ba046e7&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL7COh7BAAAAwDWAAUBCLeSp_AFELGNpebanN6nfBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiF1anR6aUFpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVDNXTTlaZEFPSV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNAQUR3UHcuLpoCiQEhSGc4UUQ6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFkNVU1RPTQ0WQExFQUZfTkFNRRIA8gIeChpDMh0ACEFTVAEo8JBJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEy-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NTDaBAIIAeAEAPAEYZAgiAUBmAUAoAX_EQEUAcAFAMkFacEU8D_SBQkJCQx0AADYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGCSQo8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=75b3ad3e867323196da165cd655b3ae51c8b44d0" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "2680240375770812721", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKfCKAfBAAAAwDWAAUBCLeSp_AFELGi6rO9jYiZJRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIWd6eTMtZ2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFYcFdVTk9rai1jXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASFOUTg3RkE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ATE559HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAAGmpDQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=f8458c6a48fed591c269169a9744aba11cb90285", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQk3yQleAAAAABExkXrWayAyJRk3yQleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=e2dbd082b353a37db9f632efc2532913a0c48166&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL7COh7BAAAAwDWAAUBCLeSp_AFELGi6rO9jYiZJRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiFnenkzLWdpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWHBXVU5Pa2otY18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhTlE4N0ZBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPDtSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBMTnn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AWZPfoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPA_0Ab1L9oGFgoQAIkDFQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=5475ac640321ae51a06b5c05ac62519e97e90114" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "8439286287321689724", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKfCKAfBAAAAwDWAAUBCLeSp_AFEPzciY2kxZePdRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIU1UeWZ6d2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFWalR1QThjek9FXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASFOUTg3RkE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ATE559HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAAGmpDQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=73e760427d2aec3d47824994cf35c35f1eeddcf8", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQk3yQleAAAAABF8bqJBKl4edRk3yQleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=dac1e836a6f2c4dc036c50d0b3128dfefb34915d&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL7COh7BAAAAwDWAAUBCLeSp_AFEPzciY2kxZePdRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiFNVHlmendpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVmpUdUE4Y3pPRV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhTlE4N0ZBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPDtSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBMTnn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AWZPfoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPA_0Ab1L9oGFgoQAIkDFQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=6c1fb35564d2ffd08fb4c5b290aadd6e1e3d83ec" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "5519278898732145414", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEIa29Pmn3ZrMTBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCITFEc0hwUWlta184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFkQUZ3YVliRWVNXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASFTQThFR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ATf359HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAFrLwU-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAGmpEQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=d01f411e7cc9126e912daca85136b2ca6f99a347", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABEGGz1_6mqYTBk3yQleAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=81115280cee86ae0bc259627410fa5dbbc178646&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEIa29Pmn3ZrMTBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiExRHNIcFFpbWtfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBZEFGd2FZYkVlTV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhU0E4RUd3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBGGQIIgFAZgFAKAF_xEBFAHABQDJBWnBFPA_0gUJCQkMeAAA2AUB4AUB8AWsvBT6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=90f21feeb2c798cd77b3cadbc961240238825f0d" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "6754624236211478320", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKfCKAfBAAAAwDWAAUBCLeSp_AFELC-hPXI3s_eXRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIVJqc0hVUWlta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFaRjJPd05MVnVvXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMxTU9BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZNGxxUVUJE0RBRHdQdy4umgKJASFOUTg3RkE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelV3UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc1MNoEAggA4AQA8ATE559HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAAGmpDQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=34811f7e5c917550619274f5b75a0cd214119812", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQk3yQleAAAAABEwH6GO9D69XRk3yQleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=d811faa48963b18d33c0a1f9d1e64ff97640a415&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL7COh7BAAAAwDWAAUBCLeSp_AFELC-hPXI3s_eXRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4ybgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiFSanNIVVFpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWkYyT3dOTFZ1b18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjMU1PQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTRscVFVCRNEQUR3UHcuLpoCiQEhTlE4N0ZBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpVd1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPDtSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzUw2gQCCAHgBADwBMTnn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AWZPfoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPA_0Ab1L9oGFgoQAIkDFQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=c0a0a2d65dd091bd2ed81f95da1a9f7ff16ad70e" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/description.md b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/description.md new file mode 100644 index 00000000000..159ebbcc30b --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/description.md @@ -0,0 +1,40 @@ +Test Page - 'integrationExamples/longform/basic_wo_brandCategoryExclusion.html' +Test Spec File - 'test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: false + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: false + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/request.json b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/request.json new file mode 100644 index 00000000000..5a5ddce7d54 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/request.json @@ -0,0 +1,136 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/response.json b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/response.json new file mode 100644 index 00000000000..9273f8e0c7b --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_brandCategoryExclusion_2/response.json @@ -0,0 +1,224 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "4631210661889362034", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEPKQq-_0sdeiQBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIXF6eU1HUWlua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFVQk5fYTFxbHU0XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0RBRHdQdy4umgKJASFTd19PR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0M9oEAggA4AQA8ATS7J9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAGmpEQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=0dae5294ed5bb48b7d3a156e5e587a26da27c7e1", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABFyyOpNj11FQBk3yQleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=9085e4dbb4849b0e6d3714d84a2754bbab578e16&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEPKQq-_0sdeiQBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4z7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFxenlNR1FpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVUJOX2ExcWx1NF8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNEQUR3UHcuLpoCiQEhU3dfT0d3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQz2gQCCAHgBADwBGGQIIgFAZgFAKAF_xEBFAHABQDJBWnBFPA_0gUJCQkMeAAA2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=aa5533d04e16ee398f8028ab3af03a48d7d8cc17" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "714778825826946473", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEKmbi7Ph8dn1CRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIU56dmJOZ2lua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFRbjlXUzRmY09jXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0BBRHdQdy4umgKJASEtUTZrXzo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56UXpRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0UwFlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQz2gQCCADgBADwBIvhn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXa1gL6BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG9S_aBhYKEAAAaakRAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=8917b1722eed5b6d85c6d5a01eea7862089bee32", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABGpzWIWjmfrCRk3yQleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=9ec7e7de16b948b60d74b47f1917faf5d3b6dbf0&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEKmbi7Ph8dn1CRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4z7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFOenZiTmdpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBUW45V1M0ZmNPY18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNAQUR3UHcuLpoCiQEhLVE2a186PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFkNVU1RPTQ0WQExFQUZfTkFNRRIA8gIeChpDMh0ACEFTVAEo8N5JRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEy-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDPaBAIIAeAEAPAEi-GfR4gFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAAAAAAADYBQHgBQHwBdrWAvoFBAgAEACQBgGYBgC4BgDBBgAAYfQo8D_QBvUv2gYWChABDy4BAFAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=8198652a5ee16abf4595ab1bfc6b051f81f0579d" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "231404788116786844", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFEJydmpjcrYebAxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIUVqMFlVZ2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFhLXFPTExmZ2VrXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0RBRHdQdy4umgKJASFTd19PR3c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0M9oEAggA4AQA8ATS7J9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAGmpEQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=775dfc49086467337dee9a5e8d78b361931c6aaa", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABGcjgbDbR02Axk3yQleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=914256a92514df787ccb1eae7dea7578d23c8fba&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFEJydmpjcrYebAxiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4z7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFFajBZVWdpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBYS1xT0xMZmdla18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNEQUR3UHcuLpoCiQEhU3dfT0d3Nj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPCQSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQz2gQCCAHgBADwBGGQIIgFAZgFAKAF_xEBFAHABQDJBWnBFPA_0gUJCQkMeAAA2AUB4AUB8AXZugb6BQQIABAAkAYBmAYAuAYAwQYJJSjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=f624619431372f9a248d0fa0dd8d09c4977bb544" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "7557072342526904599", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKfCKAfBAAAAwDWAAUBCLeSp_AFEJeS-7GaqIfwaBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIUFqdmVKQWlta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFSY2lLblVqeU9VXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0BBRHdQdy4umgKJASFJQS1IRDo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56UXpRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0dQFlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQz2gQCCADgBADwBK_ln0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXgWPoFBAgAEACQBgGYBgC4BgDBBgAAAAAAAPC_0Ab1L9oGFgoQAAAAAAAAAAAAAAAAAAAAABAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=552663ed1246fd2a3cc5824164f57d32f18078ee", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQk3yQleAAAAABEXyT6mQR3gaBk3yQleAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=e1c80e622aa60bf7683413f4371b0055d0d16f47&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL7COh7BAAAAwDWAAUBCLeSp_AFEJeS-7GaqIfwaBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV4z7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiFBanZlSkFpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBUmNpS25VanlPVV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNAQUR3UHcuLpoCiQEhSUEtSEQ6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlxodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX2JyYW5kQ2F0ZWdvcnlFeGNsdXNpb24uaHRtbPICEwoPQ1VTVE9NX01PREVMX0lEEgDyAhoKFkNVU1RPTQ0WQExFQUZfTkFNRRIA8gIeChpDMh0ACEFTVAEo8JBJRklFRBIAgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gEy-YEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDPaBAIIAeAEAPAEYZAgiAUBmAUAoAX_EQEYAcAFAMkFAAUBFPA_0gUJCQULeAAAANgFAeAFAfAF4Fj6BQQIABAAkAYBmAYAuAYAwQYBIDAAAPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=ccf266d1b02551a2ad0af0871d4af43e4aee4bba" + } + } + } + ] + }, + { + "uuid": "2c52d7d1f2f703", + "tag_id": 15394006, + "auction_id": "5093465143102876632", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QKgCKAgBAAAAwDWAAUBCLeSp_AFENjX7JP78efXRhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIUJUeXRwUWlua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCcktjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFYamVBMVdNcXUwXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMwTS1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZY2xxUVUJE0RBRHdQdy4umgKJASEtUTZrX2c2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelF6UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9FMBZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATL5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0M9oEAggA4AQA8ASL4Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAAAAAAAAANgFAeAFAfAF2tYC-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAGmpEQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=6651c1436b699842aabb3ae53d96d07caf5b4938", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQk3yQleAAAAABHYK3uyj5-vRhk3yQleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=f62098bf5037b22ca4e5583289a1527fdfa87e43&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_brandCategoryExclusion.html&e=wqT_3QL8COh8BAAAAwDWAAUBCLeSp_AFENjX7JP78efXRhiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4z7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3Njk5NjM5KTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFCVHl0cFFpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQnJLY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWGplQTFXTXF1MF8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjME0tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWWNscVFVCRNEQUR3UHcuLpoCiQEhLVE2a19nNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRelFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJcaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19icmFuZENhdGVnb3J5RXhjbHVzaW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT00NFkBMRUFGX05BTUUSAPICHgoaQzIdAAhBU1QBKPDeSUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBMvmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQz2gQCCAHgBADwBIvhn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AXa1gL6BQQIABAAkAYBmAYAuAYAwQYAAGH0KPA_0Ab1L9oGFgoQAQ8uAQBQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=4b43aaab766ee7d2e4c900ec32cb3c8b7ccef1d0" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/description.md b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/description.md new file mode 100644 index 00000000000..c1781561af5 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/description.md @@ -0,0 +1,40 @@ +Test Page - 'integrationExamples/longform/basic_wo_requireExactDuration.html' +Test Spec File - 'test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: false + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/request.json b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/request.json new file mode 100644 index 00000000000..aba76398093 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/request.json @@ -0,0 +1,387 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + } + ], + "user": {}, + "brand_category_uniqueness": true + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/response.json b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/response.json new file mode 100644 index 00000000000..c35a47781f7 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_1/response.json @@ -0,0 +1,366 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "8905202273088829598", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCLWXp_AFEJ7x1-ORyejKexiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIWlEeE84Z2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFhV09TRWpDY09NXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0BBRHdQdy4umgKJASFQZzlaRjo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56YzNRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0DgFlQUEu2AIA4AKtmEjqAlpodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX3JlcXVpcmVFeGFjdER1cmF0aW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc3N9oEAggA4AQA8ATE559HiAUBmAUAoAX___________8BwAUAyQUAZWQU8D_SBQkJBQt4AAAA2AUB4AUB8AWZPfoFBAgAEACQBgGYBgC4BgDBBgEgMAAA8L_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=21195aa3e27f8eb88ba43d5da32e6b78c2aa03f8", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQm1ywleAAAAABGe-HUcSaKVexm1ywleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=3c05b8509dd5c7c4459886f4c69fdd9cd07caa66&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL5COh5BAAAAwDWAAUBCLWXp_AFEJ7x1-ORyejKexiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV41rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiFpRHhPOGdpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBYVdPU0VqQ2NPTV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjM04tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCYWtscVFVCRNAQUR3UHcuLpoCiQEhUGc5WkY6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlpodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX3JlcXVpcmVFeGFjdER1cmF0aW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTQUWPExFQUZfTkFNRRIA8gIeChoyMwDwwkxBU1RfTU9ESUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBNXmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0Nzc32gQCCAHgBADwBMTnn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAGXObNgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYFISwA8D_QBvUv2gYWChAJERkBUBAAGADgBgTyBgIIAIAHAYgHAKAHQA..&s=203ae79160a460b18f20165e5de53bb1f45e4933" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "2428831247136753876", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKeCKAeBAAAAwDWAAUBCLWXp_AFENSR0sfppLzaIRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCIXRUeV9FQWlta184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFTek5nNXJvQi0wXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0RBRHdQdy4umgKJASFVUThpSFE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NzfaBAIIAOAEAPAE39-fR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULfAAAANgFAeAFAfAFrLwU-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=af2d5096aa4f934e84b59ad16cd18dfe5b9bbc77", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQm1ywleAAAAABHUiPSYJvG0IRm1ywleAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=440a389c7f556dac70c650bb1e593a10cbcdf2af&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL6COh6BAAAAwDWAAUBCLWXp_AFENSR0sfppLzaIRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV41rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiF0VHlfRUFpbWtfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBU3pOZzVyb0ItMF8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjM04tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCYWtscVFVCRNEQUR3UHcuLpoCiQEhVVE4aUhRNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpjM1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX00FFjxMRUFGX05BTUUSAPICHgoaMjMA8MJMQVNUX01PRElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc3N9oEAggB4AQA8ATf359HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABlznDYBQHgBQHwBay8FPoFBAgAEACQBgGYBgC4BgDBBgUiLADwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=9b00ed17c420f328d63b72519d2d578c5921d0d7" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "1625697369389546128", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKeCKAeBAAAAwDWAAUBCLWXp_AFEJD1gLbO5OjHFhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIXVqeHMtUWlua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFhcDVwSkxuSXVVXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0RBRHdQdy4umgKJASFBQTlhQUE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NzfaBAIIAOAEAPAEi-GfR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULfAAAANgFAeAFAfAF2tYC-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=0e70f535a95c82238d685147f41e0bd2f86631c0", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQm1ywleAAAAABGQOsDmJKOPFhm1ywleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=144214d77cc4ced0893c9fe64132ad01c429c43c&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL6COh6BAAAAwDWAAUBCLWXp_AFEJD1gLbO5OjHFhiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV41rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiF1anhzLVFpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBYXA1cEpMbkl1VV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjM04tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCYWtscVFVCRNEQUR3UHcuLpoCiQEhQUE5YUFBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpjM1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX00FFjxMRUFGX05BTUUSAPICHgoaMjMA8MJMQVNUX01PRElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc3N9oEAggB4AQA8ASL4Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABlznDYBQHgBQHwBdrWAvoFBAgAEACQBgGYBgC4BgDBBgUiLADwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=f6bc475b66c167036dcb9f10c7c5f176124829a6" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "5434800203981031918", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCLWXp_AFEO6TivzZv5K2Sxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCIW5Ud3I5QWlta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFXVVcwV1Y1LU9JXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0RBRHdQdy4umgKJASFKdzh1RGc2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NzfaBAIIAOAEAPAEr-WfR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULeAAAANgFAeAFAfAF4Fj6BQQIABAAkAYBmAYAuAYAwQYBIDAAAPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=c3ba85f0eb0293896e02066385f82b4450af2cfb", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQm1ywleAAAAABHuiYKf_UlsSxm1ywleAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=9eb2d880fa9b524a6a0807eef0c65b7b1393f48a&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL5COh5BAAAAwDWAAUBCLWXp_AFEO6TivzZv5K2Sxiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV41rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiFuVHdyOUFpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBV1VXMFdWNS1PSV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjM04tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCYWtscVFVCRNEQUR3UHcuLpoCiQEhSnc4dURnNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpjM1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX00FFjxMRUFGX05BTUUSAPICHgoaMjMA8MJMQVNUX01PRElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc3N9oEAggB4AQA8ASv5Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABlzmzYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGBSEsAPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=17038c2671b58d2fd6ea98dd3f3e1166ee664dd0" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "8071104954749355970", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKeCKAeBAAAAwDWAAUBCLWXp_AFEMKP7OWZ5pSBcBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIXBUeEJEQWlsa184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFRbWtta1pXNGVZXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0RBRHdQdy4umgKJASFSZ19sR1E2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NzfaBAIIAOAEAPAE0uyfR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULfAAAANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=005579c0ff91586a887354409f637a63a1d69031", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQm1ywleAAAAABHCB7ucMVMCcBm1ywleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=edbfae73be0f41d672298d5c3ec9dd28a5307233&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL6CKB6BAAAAwDWAAUBCLWXp_AFEMKP7OWZ5pSBcBiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZCQkI4D8hCQkIJEApEQkAMQkJsOA_MNbJqwc47UhA7UhIAlDS7J9HWJzxW2AAaM26dXjWuAWAAQGKAQNVU0SSAQEG8FWYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjx1ZignYScsIDI1Mjk4ODUsIDE1Nzc3MDAyNzcpO3VmKCdyJywgMTQ5NDE5NjAyLCAxNRkf8P2SArkCIXBUeEJEQWlsa184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFRbWtta1pXNGVZXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0RBRHdQdy4umgKJASFSZ19sR1E2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlpodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX3JlcXVpcmVFeGFjdER1cmF0aW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTQUWPExFQUZfTkFNRRIA8gIeChoyMwDwwkxBU1RfTU9ESUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBNXmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0Nzc32gQCCAHgBADwBNLsn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAGXOcNgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGBSIsAPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=0ac18b64a6c0d58a114085d8b565b4c98de56448" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "6893555531330982923", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKeCKAeBAAAAwDWAAUBCLWXp_AFEIuopuO2h7XVXxiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE0MTg4Nh8A8P2SArkCIVFqd1R0Z2lHa184UEVLekNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFFajRyM1ZBQUFxUU1FQkktSzkxUUFBS2tESkFZS1J6NEZQV2V3XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRGhwUF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0RBRHdQdy4umgKJASEzUTZnOHc2PQEkblBGYklBUW9BRBVIVHFRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NzfaBAIIAOAEAPAErMKfR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULfAAAANgFAeAFAfAF8owB-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=f9ddb1066825dfc556d108168ffc0d16cf567ae8", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQm1ywleAAAAABELlGlsO9SqXxm1ywleAAAAACCswp9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jyjAFiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAazCn0ewAQE.&s=db3a0d52f97edd94aa7e4213c437d8e4a5bd16ce&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149414188, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 13.00001, + "cpm_publisher_currency": 13.00001, + "publisher_currency_code": "$", + "brand_category_id": 32, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 29000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL6COh6BAAAAwDWAAUBCLWXp_AFEIuopuO2h7XVXxiq5MnUovf28WEqNgmOWItPAQAqQBGOWItPAQAqQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQrMKfR1ic8VtgAGjNunV41rgFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxNDE4OCwgMTUZH_D9kgK5AiFRandUdGdpR2tfOFBFS3pDbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRRWo0cjNWQUFBcVFNRUJJLUs5MVFBQUtrREpBWUtSejRGUFdld18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RocFBfRDdvRENWTkpUak02TkRjM04tQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCYWtscVFVCRNEQUR3UHcuLpoCiQEhM1E2Zzh3Nj0BJG5QRmJJQVFvQUQVSFRxUURvSlUwbE9Nem8wTnpjM1FNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX00FFjxMRUFGX05BTUUSAPICHgoaMjMA8MJMQVNUX01PRElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc3N9oEAggB4AQA8ASswp9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABlznDYBQHgBQHwBfKMAfoFBAgAEACQBgGYBgC4BgDBBgUiLADwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=917c8192c7dc7e382c0bb7296ab0df261e69f572" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "5615186251901272031", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "6647218197537074925", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "4707051182303115567", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "4831890668873532085", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "7151522995196673389", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "4077353832159380438", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKeCKAeBAAAAwDWAAUBCLWXp_AFENaHwKvS9urKOBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE3NjY5Nh8A8P2SArkCIWJqeHo1UWlsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFZd1VQNVZMNi1VXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0RBRHdQdy4umgKJASFLZzhBRUE2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NzfaBAIIAOAEAPAExd2fR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULfAAAANgFAeAFAfAFwvIX-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=e96d121a0a7d49e05c1d2b4fab2da60d0b544287", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQm1ywleAAAAABHWA3AltauVOBm1ywleAAAAACDF3Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jC8hdiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAcXdn0ewAQE.&s=8d90e3ce42fe47da19cb85f8fb2d78822c590464&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417669, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 4, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL6CKB6BAAAAwDWAAUBCLWXp_AFENaHwKvS9urKOBiq5MnUovf28WEqNgkAAAECCCRAEQEHEAAAJEAZCQkI4D8hCQkIJEApEQkAMQkJsOA_MNbJqwc47UhA7UhIAlDF3Z9HWJzxW2AAaM26dXjWuAWAAQGKAQNVU0SSAQEG8FWYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAjx1ZignYScsIDI1Mjk4ODUsIDE1Nzc3MDAyNzcpO3VmKCdyJywgMTQ5NDE3NjY5LCAxNRkf8P2SArkCIWJqeHo1UWlsa184UEVNWGRuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFZd1VQNVZMNi1VXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBaUF9EN29EQ1ZOSlRqTTZORGMzTi1BRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJha2xxUVUJE0RBRHdQdy4umgKJASFLZzhBRUE2PQEkblBGYklBUW9BRBVIVGtRRG9KVTBsT016bzBOemMzUU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlpodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX3JlcXVpcmVFeGFjdER1cmF0aW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTQUWPExFQUZfTkFNRRIA8gIeChoyMwDwwkxBU1RfTU9ESUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBNXmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0Nzc32gQCCAHgBADwBMXdn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAGXOcNgFAeAFAfAFwvIX-gUECAAQAJAGAZgGALgGAMEGBSIsAPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=db30796cc71c3bee3aa8fc2890c75ad7186f9d73" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "2099457773367093540", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "7214207858308840891", + "nobid": true, + "ad_profile_id": 1182765 + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "4564285281969145467", + "nobid": true, + "ad_profile_id": 1182765 + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/description.md b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/description.md new file mode 100644 index 00000000000..c1781561af5 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/description.md @@ -0,0 +1,40 @@ +Test Page - 'integrationExamples/longform/basic_wo_requireExactDuration.html' +Test Spec File - 'test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'sample-code', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: false + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 15394006 + } + } + ] +}]; +``` + +SetConfig to use with AdUnit: +``` +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + }, + adpod: { + brandCategoryExclusion: true + } +}); +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/request.json b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/request.json new file mode 100644 index 00000000000..f2f20700ffe --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/request.json @@ -0,0 +1,137 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + }, + { + "sizes": [ + { + "width": 640, + "height": 480 + } + ], + "primary_size": { + "width": 640, + "height": 480 + }, + "ad_types": [ + "video" + ], + "id": 15394006, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 7, + "require_asset_url": true, + "video": { + "maxduration": 30 + } + } + ], + "user": {}, + "brand_category_uniqueness": true + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/response.json b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/response.json new file mode 100644 index 00000000000..5f2118095d4 --- /dev/null +++ b/test/fake-server/fixtures/longform/longform_wo_requireExactDuration_2/response.json @@ -0,0 +1,224 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "2704229116537156015", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKeCKAeBAAAAwDWAAUBCLWXp_AFEK_j3NPcwdbDJRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE3OTUxNh8A8P2SArkCIXpUczJvQWlta184UEVOX2ZuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFURVBwVy1oVE9ZXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMwT2VBRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZMGxxUVUJE0BBRHdQdy4umgKJASFVQV9qSDo9ASRuUEZiSUFRb0FEFUhUdVFEb0pVMGxPTXpvME56UTVRTUlZUxF4DFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0DgFlQUEu2AIA4AKtmEjqAlpodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX3JlcXVpcmVFeGFjdER1cmF0aW9uLmh0bWyAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0OdoEAggA4AQA8ATf359HiAUBmAUAoAX___________8BwAUAyQUAZWQU8D_SBQkJBQt8AAAA2AUB4AUB8AWsvBT6BQQIABAAkAYBmAYAuAYAwQYBITAAAPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=64565aadf65d370e9730e9ce82c93c9bd2fcfc14", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQm1ywleAAAAABGvMXfKDVqHJRm1ywleAAAAACDf359HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1isvBRiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAd_fn0ewAQE.&s=3b1f10f67b3253e38770fff694edbe6052795602&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149417951, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 33, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL6COh6BAAAAwDWAAUBCLWXp_AFEK_j3NPcwdbDJRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ39-fR1ic8VtgAGjNunV4t7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxNzk1MSwgMTUZH_D9kgK5AiF6VHMyb0FpbWtfOFBFTl9mbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBVEVQcFctaFRPWV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjME9lQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTBscVFVCRNAQUR3UHcuLpoCiQEhVUFfakg6PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelE1UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8ItlQUEu2AIA4AKtmEjqAlpodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2xvbmdmb3JtL2Jhc2ljX3dvX3JlcXVpcmVFeGFjdER1cmF0aW9uLmh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChZDVVNUT01fTQUWPExFQUZfTkFNRRIA8gIeChoyMwDwwkxBU1RfTU9ESUZJRUQSAIADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIEDTIwMi41OS4yMzEuNDeoBNXmBLIEEggBEAIYgAUg4AMoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzQ52gQCCAHgBADwBN_fn0eIBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAGXOcNgFAeAFAfAFrLwU-gUECAAQAJAGAZgGALgGAMEGBSIsAPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=5316c3262f36e4d89735b1ba252c64651a84f479" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "7987581685263122854", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKeCKAeBAAAAwDWAAUBCLWXp_AFEKaDmaSQ5-Xsbhiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE5NjAyNh8A8P2SArkCIU16MXpZd2lua184UEVOTHNuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFYLVkxZU5tY3VRXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMwT2VBRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZMGxxUVUJE0RBRHdQdy4umgKJASFVUTgySFE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelE1UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDnaBAIIAOAEAPAE0uyfR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULfAAAANgFAeAFAfAF2boG-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=17f2a3f5e78c188cc6ca23e677ced305198a8a05", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQm1ywleAAAAABGmQYYEOZfZbhm1ywleAAAAACDS7J9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jZugZiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAdLsn0ewAQE.&s=28e8f96efdfb9bc1e33e4d087ff5ed992e4692b1&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149419602, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 24, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL6COh6BAAAAwDWAAUBCLWXp_AFEKaDmaSQ5-Xsbhiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQ0uyfR1ic8VtgAGjNunV4t7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxOTYwMiwgMTUZH_D9kgK5AiFNejF6WXdpbmtfOFBFTkxzbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWC1ZMWVObWN1UV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjME9lQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTBscVFVCRNEQUR3UHcuLpoCiQEhVVE4MkhRNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRNVFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX00FFjxMRUFGX05BTUUSAPICHgoaMjMA8MJMQVNUX01PRElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0OdoEAggB4AQA8ATS7J9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABlznDYBQHgBQHwBdm6BvoFBAgAEACQBgGYBgC4BgDBBgUiLADwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=89d4586d9597cd2f9a4a918d1e6985aee45ade01" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "653115326806257319", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCLWXp_AFEKfdmd3enZWICRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE4NjcxNh8A8P2SArkCITlEd0hNZ2lta184UEVLX2xuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFjTnlESmJxeS13XzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMwT2VBRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZMGxxUVUJE0RBRHdQdy4umgKJASFKZ192RFE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelE1UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDnaBAIIAOAEAPAEr-WfR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULeAAAANgFAeAFAfAF4Fj6BQQIABAAkAYBmAYAuAYAwQYBIDAAAPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=1928a9daadbd431792adace7620880dda961eefb", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQm1ywleAAAAABGnbqbr7VQQCRm1ywleAAAAACCv5Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1jgWGICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBr-WfR7ABAQ..&s=7617d08e0c16fe1dea8ec80cd6bf73ec0a736b41&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418671, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 30, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL5COh5BAAAAwDWAAUBCLWXp_AFEKfdmd3enZWICRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQr-WfR1ic8VtgAGjNunV4t7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxODY3MSwgMTUZH_D9kgK5AiE5RHdITWdpbWtfOFBFS19sbjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBY055REpicXktd18yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjME9lQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTBscVFVCRNEQUR3UHcuLpoCiQEhSmdfdkRRNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRNVFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX00FFjxMRUFGX05BTUUSAPICHgoaMjMA8MJMQVNUX01PRElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0OdoEAggB4AQA8ASv5Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABlzmzYBQHgBQHwBeBY-gUECAAQAJAGAZgGALgGAMEGBSEsAPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=0d1d3f42fa225995a2f57ab84877dce3d24e9901" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "866435845408148233", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKeCKAeBAAAAwDWAAUBCLWXp_AFEImW35H52YyDDBiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE4MTIzNh8A8P2SArkCIXJEenNfZ2lua184UEVJdmhuMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFjMmZTZ1BpMi1BXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHA1UF9EN29EQ1ZOSlRqTTZORGMwT2VBRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZMGxxUVUJE0RBRHdQdy4umgKJASFfdzRiQUE2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelE1UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDnaBAIIAOAEAPAEi-GfR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULfAAAANgFAeAFAfAF2tYC-gUECAAQAJAGAZgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=83f65f38b4fd56344b3aceb70df7bac1b9b5f229", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=aAAAAAMArgAFAQm1ywleAAAAABEJyzeSzzIGDBm1ywleAAAAACCL4Z9HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1ja1gJiAklOaAFwAXgAgAEBiAEBkAGABZgB4AOgAQCoAYvhn0ewAQE.&s=9130c13cca7a1d3eb05c2b96585ccfdc2faa6844&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418123, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 12, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 15000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL6COh6BAAAAwDWAAUBCLWXp_AFEImW35H52YyDDBiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQi-GfR1ic8VtgAGjNunV4t7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxODEyMywgMTUZH_D9kgK5AiFyRHpzX2dpbmtfOFBFSXZobjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBYzJmU2dQaTItQV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwNVBfRDdvRENWTkpUak02TkRjME9lQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTBscVFVCRNEQUR3UHcuLpoCiQEhX3c0YkFBNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRNVFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX00FFjxMRUFGX05BTUUSAPICHgoaMjMA8MJMQVNUX01PRElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0OdoEAggB4AQA8ASL4Z9HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABlznDYBQHgBQHwBdrWAvoFBAgAEACQBgGYBgC4BgDBBgUiLADwP9AG9S_aBhYKEAkRGQFQEAAYAOAGBPIGAggAgAcBiAcAoAdA&s=1f88d8b0a467d528291f90a54fd810b8fdac4488" + } + } + } + ] + }, + { + "uuid": "2022b6b1fcf477", + "tag_id": 15394006, + "auction_id": "1540903203561034860", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QKdCKAdBAAAAwDWAAUBCLWXp_AFEOyokYzL6ZixFRiq5MnUovf28WEqNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwaeA_MNbJqwc47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEDwAEAyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTsBHTByJywgMTQ5NDE4OTQ4Nh8A8P2SArkCIXdUekVId2lta184UEVNVG5uMGNZQUNDYzhWc3dBRGdBUUFSSTdVaFExc21yQjFnQVlJSUNhQUJ3QUhnQWdBSElBb2dCd3FjRGtBRUFtQUVBb0FFQnFBRURzQUVBdVFIdEJLRDJBQUF1UU1FQjdRU2c5Z0FBTGtESkFaeE00NUxjRXVNXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFPQUNBT2dDQVBnQ0FJQURBWmdEQWFnRHBwUF9EN29EQ1ZOSlRqTTZORGMwT2VBRHdoaUlCQUNRQkFDWUJBSEJCQUFBQQ1yCHlRUQ0KJEFBQU5nRUFQRUUBCwkBMEQ0QkFDSUJZMGxxUVUJE0RBRHdQdy4umgKJASFQUThhRmc2PQEkblBGYklBUW9BRBVIVHVRRG9KVTBsT016bzBOelE1UU1JWVMReAxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M9A4BZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1sgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQNMjAyLjU5LjIzMS40N6gE1eYEsgQSCAEQAhiABSDgAygBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3NDnaBAIIAOAEAPAExOefR4gFAZgFAKAF____________AcAFAMkFAGVkFPA_0gUJCQULeAAAANgFAeAFAfAFmT36BQQIABAAkAYBmAYAuAYAwQYBIDAAAPC_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=a4de0e4084ce04a5cb2d347c07fde867aa9ff5c1", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "video", + "notify_url": "https://sin3-ib.adnxs.com/vast_track/v2?info=ZwAAAAMArgAFAQm1ywleAAAAABFsVISxTGNiFRm1ywleAAAAACDE559HKAAw7Ug47UhA0-hISLuv1AFQ1smrB1iZPWICSU5oAXABeACAAQGIAQGQAYAFmAHgA6ABAKgBxOefR7ABAQ..&s=cf600d825cec85f83c06119e5e383f8548b469a2&event_type=1", + "usersync_url": "https%3A%2F%2Facdn.adnxs.com%2Fdmp%2Fasync_usersync.html", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 149418948, + "media_type_id": 4, + "media_subtype_id": 64, + "cpm": 15.00001, + "cpm_publisher_currency": 15.00001, + "publisher_currency_code": "$", + "brand_category_id": 1, + "client_initiated_ad_counting": true, + "rtb": { + "video": { + "player_width": 640, + "player_height": 480, + "duration_ms": 30000, + "playback_methods": [ + "auto_play_sound_on" + ], + "frameworks": [ + "vpaid_1_0", + "vpaid_2_0" + ], + "asset_url": "https://sin3-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Flongform%2Fbasic_wo_requireExactDuration.html&e=wqT_3QL5COh5BAAAAwDWAAUBCLWXp_AFEOyokYzL6ZixFRiq5MnUovf28WEqNgmOWItPAQAuQBGOWItPAQAuQBkAAAECCOA_IREbACkRCQAxARm4AADgPzDWyasHOO1IQO1ISAJQxOefR1ic8VtgAGjNunV4t7gFgAEBigEDVVNEkgEBBvBVmAEBoAEBqAEBsAEAuAEDwAEEyAEC0AEA2AEA4AEA8AEAigI8dWYoJ2EnLCAyNTI5ODg1LCAxNTc3NzAwMjc3KTt1ZigncicsIDE0OTQxODk0OCwgMTUZH_D9kgK5AiF3VHpFSHdpbWtfOFBFTVRubjBjWUFDQ2M4VnN3QURnQVFBUkk3VWhRMXNtckIxZ0FZSUlDYUFCd0FIZ0FnQUhJQW9nQndxY0RrQUVBbUFFQW9BRUJxQUVEc0FFQXVRSHRCS0QyQUFBdVFNRUI3UVNnOWdBQUxrREpBWnhNNDVMY0V1TV8yUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBSmdDQUtBQ0FMVUNBQUFBQUwwQ0FBQUFBT0FDQU9nQ0FQZ0NBSUFEQVpnREFhZ0RwcFBfRDdvRENWTkpUak02TkRjME9lQUR3aGlJQkFDUUJBQ1lCQUhCQkFBQUENcgh5UVENCiRBQUFOZ0VBUEVFAQsJATBENEJBQ0lCWTBscVFVCRNEQUR3UHcuLpoCiQEhUFE4YUZnNj0BJG5QRmJJQVFvQUQVSFR1UURvSlUwbE9Nem8wTnpRNVFNSVlTEXgMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPCLZUFBLtgCAOACrZhI6gJaaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9sb25nZm9ybS9iYXNpY193b19yZXF1aXJlRXhhY3REdXJhdGlvbi5odG1s8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWQ1VTVE9NX00FFjxMRUFGX05BTUUSAPICHgoaMjMA8MJMQVNUX01PRElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA0yMDIuNTkuMjMxLjQ3qATV5gSyBBIIARACGIAFIOADKAEoAjAAOAO4BADABADIBADSBA45MzI1I1NJTjM6NDc0OdoEAggB4AQA8ATE559HiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAAABlzmzYBQHgBQHwBZk9-gUECAAQAJAGAZgGALgGAMEGBSEsAPA_0Ab1L9oGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.&s=17c466ea45d5d4beff02aa2b0eb87bc6c4d5aff3" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/modules/description.md b/test/fake-server/fixtures/modules/description.md new file mode 100644 index 00000000000..db13453b8aa --- /dev/null +++ b/test/fake-server/fixtures/modules/description.md @@ -0,0 +1,68 @@ +Test Pages: + - 'test/pages/bidderSettings.html' + - 'test/pages/consent_mgt_gdpr.html' + - 'test/pages/currency.html' + - 'test/pages/priceGranularity.html' + - 'test/pages/sizeConfig.html' + - 'test/pages/userSync.html' +Test Spec Files: + - 'test/spec/e2e/modules/e2e_bidderSettings.spec.js' + - 'test/spec/e2e/modules/e2e_consent_mgt_gdpr.spec.js' + - 'test/spec/e2e/modules/e2e_currency.spec.js' + - 'test/spec/e2e/modules/e2e_priceGranularity.spec.js' + - 'test/spec/e2e/modules/e2e_sizeConfig.spec.js' + - 'test/spec/e2e/modules/e2e_userSync.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] + +}, { + code: '/19968336/prebid_native_example_2', + sizes: [ + [1, 1] + ], + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + }, + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13232354, + allowSmallerSizes: true + } + }] +}]; +``` + +Refer to individual test pages to see the proper setConfigs for each test. diff --git a/test/fake-server/fixtures/modules/request.json b/test/fake-server/fixtures/modules/request.json new file mode 100644 index 00000000000..35dd6e2d499 --- /dev/null +++ b/test/fake-server/fixtures/modules/request.json @@ -0,0 +1,74 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 300, + "height": 250 + }, + { + "width": 300, + "height": 600 + } + ], + "primary_size": { + "width": 300, + "height": 250 + }, + "ad_types": [ + "banner" + ], + "id": 13144370, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 1 + }, + { + "sizes": [ + { + "width": 1, + "height": 1 + } + ], + "ad_types": [ + "native" + ], + "id": 13232354, + "allow_smaller_sizes": true, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "native": { + "layouts": [ + { + "title": { + "required": true + }, + "description": { + "required": true + }, + "main_image": { + "required": true + }, + "sponsored_by": { + "required": true + }, + "icon": { + "required": false + } + } + ] + }, + "hb_source": 1 + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/modules/response.json b/test/fake-server/fixtures/modules/response.json new file mode 100644 index 00000000000..9b708fa1534 --- /dev/null +++ b/test/fake-server/fixtures/modules/response.json @@ -0,0 +1,106 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "tag_id": 13144370, + "auction_id": "4842409943576641356", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fmodules%2Fcurrency.html%3Fpbjs_debug%3Dtrue&e=wqT_3QK7CKA7BAAAAwDWAAUBCNvV-_AFEMz-0f3_xeyZQxjCs_b6q5D9_0oqNgkAAAkCABEJBywAABkAAACA61HgPyEREgApEQkAMREb8GkwsqKiBjjtSEDtSEgAUABYnPFbYABotc95eACAAQGKAQCSAQNVU0SYAawCoAHYBKgBAbABALgBAcABAMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3OTA4NDUwNyk7AR0scicsIDk4NDkzNTgxNh4A8NCSArUCIVpqeldtZ2l1c0s0S0VJM0oteTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlNSUdhQUJ3RG5qd0U0QUJUSWdCOEJPUUFRQ1lBUUNnQVFHb0FRT3dBUUM1QVNtTGlJTUFBT0Ffd1FFcGk0aURBQURnUDhrQmxiMDhGYV9INERfWkFRQUFBQUFBQVBBXzRBRUE5UUVBQUFBQW1BSUFvQUlBdFFJQUFBQUF2UUlBQUFBQTRBSUE2QUlBLUFJQWdBTUJtQU1CcUFPdQHEiHVnTUpVMGxPTXpvME56TTE0QU80R1lnRUFKQUVBSmdFQWNFCV0FAQhESkIFCAkBGDJBUUE4UVEJDQEBLFBnRUFJZ0ZfeVNwQhEXNFBBX5oCiQEhblE4cUxBNjkBJG5QRmJJQVFvQUQRZBBEZ1B6bzKRABBRTGdaUx1NAFURDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJLDTpIdGVzdC5sb2NhbGhvc3Q6OTk5OQUUWC9wYWdlcy9tb2R1bGVzL2N1cnJlbmN5BUbwQD9wYmpzX2RlYnVnPXRydWWAAwCIAwGQAwCYAxegAwGqAwDAA6wCyAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzDbDwXpgEAKIECzEwLjc1Ljc0LjY5qATikAGyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggA4AQB8ASNyfsuiAUBmAUAoAX_____BQMYAcAFAMkFAAUBFPA_0gUJCQULfAAAANgFAeAFAfAFmfQh-gUECAAQAJAGAJgGALgGAMEGASEwAADwv9AG9S_aBhYKEAkRGQFQEAAYAOAGAfIGAggAgAcBiAcAoAcB&s=16a5ea7c8d5eb050a368495961803753dd6086c2", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "banner", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 98493581, + "media_type_id": 1, + "media_subtype_id": 1, + "cpm": 0.5, + "cpm_publisher_currency": 0.5, + "publisher_currency_code": "$", + "brand_category_id": 53, + "client_initiated_ad_counting": true, + "rtb": { + "banner": { + "content": "
", + "width": 300, + "height": 600 + }, + "trackers": [ + { + "impression_urls": [ + "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fmodules%2Fcurrency.html%3Fpbjs_debug%3Dtrue&e=wqT_3QLDCKBDBAAAAwDWAAUBCNvV-_AFEMz-0f3_xeyZQxjCs_b6q5D9_0oqNgkAAAECCOA_EQEHNAAA4D8ZAAAAgOtR4D8hERIAKREJADERG6gwsqKiBjjtSEDtSEgCUI3J-y5YnPFbYABotc95eJ64BYABAYoBA1VTRJIBAQbwUpgBrAKgAdgEqAEBsAEAuAEBwAEEyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTc5MDg0NTA3KTt1ZigncicsIDk4NDkzNTgxNh4A8NCSArUCIVpqeldtZ2l1c0s0S0VJM0oteTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlNSUdhQUJ3RG5qd0U0QUJUSWdCOEJPUUFRQ1lBUUNnQVFHb0FRT3dBUUM1QVNtTGlJTUFBT0Ffd1FFcGk0aURBQURnUDhrQmxiMDhGYV9INERfWkFRQUFBQUFBQVBBXzRBRUE5UUVBQUFBQW1BSUFvQUlBdFFJQUFBQUF2UUlBQUFBQTRBSUE2QUlBLUFJQWdBTUJtQU1CcUFPdQHEiHVnTUpVMGxPTXpvME56TTE0QU80R1lnRUFKQUVBSmdFQWNFCV0FAQhESkIFCAkBGDJBUUE4UVEJDQEBLFBnRUFJZ0ZfeVNwQhEXNFBBX5oCiQEhblE4cUxBNjkBJG5QRmJJQVFvQUQRZBBEZ1B6bzKRABBRTGdaUx1NAFURDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJLDTpIdGVzdC5sb2NhbGhvc3Q6OTk5OQUUWC9wYWdlcy9tb2R1bGVzL2N1cnJlbmN5BUbwQD9wYmpzX2RlYnVnPXRydWWAAwCIAwGQAwCYAxegAwGqAwDAA6wCyAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzDbDwXpgEAKIECzEwLjc1Ljc0LjY5qATikAGyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggB4AQB8ASNyfsuiAUBmAUAoAX_____BQMYAcAFAMkFAAUBFPA_0gUJCQULfAAAANgFAeAFAfAFmfQh-gUECAAQAJAGAJgGALgGAMEGASEwAADwP9AG9S_aBhYKEAkRGQFQEAAYAOAGAfIGAggAgAcBiAcAoAcB&s=9c378bb4ca21a7509f69955fb4e6fe72035190c6" + ], + "video_events": {} + } + ] + } + } + ] + }, + { + "tag_id": 13232354, + "auction_id": "8100967561168057198", + "nobid": false, + "no_ad_url": "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fmodules%2Fcurrency.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKECKAEBAAAAwDWAAUBCNvV-_AFEO7mieO34pq2cBjCs_b6q5D9_0oqNgkAAAkCABEJBwgAABkJCQgkQCEJCQgAACkRCQAxCQnwaSRAMOLRpwY47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEBwAEAyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTc5MDg0NTA3KTsBHSxyJywgOTc0OTQyMDQ2HgDwmpICtQIhMVR6c3lRajgtTHdLRUx6SnZpNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUTR0R25CbGdBWU1JR2FBQndBSGdBZ0FGTWlBSHdFNUFCQUpnQkFLQUJBYWdCQTdBQkFMa0I4NjFxcEFBQUpFREJBZk90YXFRQUFDUkF5UUc5QzZTMEtFampQOWtCQUFBQUFBQUE4RF9nQVFEMUFRAQ8sQ1lBZ0NnQWdDMUFnBRAAOQkI8EBEZ0FnRG9BZ0Q0QWdDQUF3R1lBd0dvQV96NHZBcTZBd2xUU1U0ek9qUTNNelhnQTdnWmlBUUFrQVFBbUFRQndRUQFNCQEITWtFCQkBARhEWUJBRHhCAQsNASwtQVFBaUFYX0pLa0YNEzxBOEQ4LpoCiQEhZUE4dE1BNjkBJG5QRmJJQVFvQUQVWFhrUURvSlUwbE9Nem8wTnpNMVFMZ1pTUQ1PDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQygZUFBLtgCAOACrZhI6gJLaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkFFPDXL3BhZ2VzL21vZHVsZXMvY3VycmVuY3kuaHRtbD9wYmpzX2RlYnVnPXRydWWAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBAsxMC43NS43NC42OagE4pABsgQOCAAQARgAIAAoADAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggA4AQB8AS8yb4uiAUBmAUAoAX___________8BwAUAyQUAAAAAAADwP9IFCQkAaVZ0ANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSQo8L_QBvUv2gYWChAJERkBUBAAGADgBgzyBgIIAIAHAYgHAKAHQQ..&s=428486554947609aac96ed5569d9bb2dd2be5502", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "native", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 97494204, + "media_type_id": 12, + "media_subtype_id": 65, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 53, + "client_initiated_ad_counting": true, + "viewability": { + "config": "" + }, + "rtb": { + "native": { + "title": "This is a Prebid Native Creative", + "desc": "This is a Prebid Native Creative. There are many like it, but this one is mine.", + "sponsored": "Prebid.org", + "icon": { + "url": "http://vcdn.adnxs.com/p/creative-image/1a/3e/e9/5b/1a3ee95b-06cd-4260-98c7-0258627c9197.png", + "width": 127, + "height": 83, + "prevent_crop": false + }, + "main_img": { + "url": "http://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg", + "width": 989, + "height": 742, + "prevent_crop": false + }, + "link": { + "url": "http://prebid.org/dev-docs/show-native-ads.html", + "click_trackers": [ + "http://sin3-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQG5zYnwTa2xwwpldv4L0_0rb6h5eAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAQQCAAAAALoAYBc26wAAAAA./bcr=AAAAAAAA8D8=/cnd=%21eA8tMAj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJU0lOMzo0NzM1QLgZSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeAA./cca=OTMyNSNTSU4zOjQ3MzU=/bn=89118/" + ] + }, + "impression_trackers": [ + "http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fmodules%2Fcurrency.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKMCKAMBAAAAwDWAAUBCNvV-_AFEO7mieO34pq2cBjCs_b6q5D9_0oqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXieuAWAAQGKAQNVU0SSAQEG8FKYAQGgAQGoAQGwAQC4AQHAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NzkwODQ1MDcpO3VmKCdyJywgOTc0OTQyMDQsIC4eAPCakgK1AiExVHpzeVFqOC1Md0tFTHpKdmk0WUFDQ2M4VnN3QURnQVFBUkk3VWhRNHRHbkJsZ0FZTUlHYUFCd0FIZ0FnQUZNaUFId0U1QUJBSmdCQUtBQkFhZ0JBN0FCQUxrQjg2MXFwQUFBSkVEQkFmT3RhcVFBQUNSQXlRRzlDNlMwS0VqalA5a0JBQUFBQUFBQThEX2dBUUQxQVEBDyxDWUFnQ2dBZ0MxQWcFEAA5CQjwQERnQWdEb0FnRDRBZ0NBQXdHWUF3R29BX3o0dkFxNkF3bFRTVTR6T2pRM016WGdBN2daaUFRQWtBUUFtQVFCd1FRAU0JAQhNa0UJCQEBGERZQkFEeEIBCw0BLC1BUUFpQVhfSktrRg0TPEE4RDgumgKJASFlQTh0TUE2OQEkblBGYklBUW9BRBVYWGtRRG9KVTBsT016bzBOek0xUUxnWlNRDU8MUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDKBlQUEu2AIA4AKtmEjqAktodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OQUU8NcvcGFnZXMvbW9kdWxlcy9jdXJyZW5jeS5odG1sP3BianNfZGVidWc9dHJ1ZYADAIgDAZADAJgDF6ADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECzEwLjc1Ljc0LjY5qATikAGyBA4IABABGAAgACgAMAA4ArgEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzM12gQCCAHgBAHwBLzJvi6IBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQBpXnQA2AUB4AUB8AWZ9CH6BQQIABAAkAYBmAYAuAYAwQYJJCjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGDPIGAggAgAcBiAcAoAdB&s=d6c58ebc137658c5dd258579c2575ad499e7a7b4" + ], + "id": 97494204 + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/description.md b/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/description.md new file mode 100644 index 00000000000..c5b114e8bb3 --- /dev/null +++ b/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/description.md @@ -0,0 +1,43 @@ +Test Page - 'test/pages/multiple_bidders.html' +Test Spec File - 'test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'div-banner-native-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13232392, + } + }] +}, +{ + code: 'div-banner-native-2', + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'adasta', + params: { + placementId: 13232392, + } + }] +}]; +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/request.json b/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/request.json new file mode 100644 index 00000000000..c4c862adc20 --- /dev/null +++ b/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/request.json @@ -0,0 +1,43 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 1, + "height": 1 + } + ], + "ad_types": [ + "native" + ], + "id": 13232392, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "native": { + "layouts": [ + { + "title": { + "required": true + }, + "main_image": { + "required": true + }, + "sponsored_by": { + "required": true + } + } + ] + }, + "hb_source": 1 + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/response.json b/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/response.json new file mode 100644 index 00000000000..56894e97e05 --- /dev/null +++ b/test/fake-server/fixtures/multi-bidder/multi-bidder-adasta/response.json @@ -0,0 +1,59 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "tag_id": 13232392, + "auction_id": "6287559286677633407", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fmultiple_bidders.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKICKAIBAAAAwDWAAUBCM3FpfEFEP_yg9W7u_mgVxi7_-bKzp3kuD8qNgkAAAkCABEJBwgAABkJCQgkQCEJCQgAACkRCQAxCQnwaSRAMIjSpwY47UhA7UhIAFAAWJzxW2AAaM26dXgAgAEBigEAkgEDVVNEmAEBoAEBqAEBsAEAuAEBwAEAyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTc5NzcwNTczKTsBHSxyJywgOTc1MjA0MzQ2HgDw0JICtQIhQWp4NUh3aUgtYndLRUxLV3dDNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUWlOS25CbGdBWUtzR2FBQndBbmlLQVlBQkhvZ0JpZ0dRQVFDWUFRQ2dBUUdvQVFPd0FRQzVBZk90YXFRQUFDUkF3UUh6cldxa0FBQWtRTWtCb2FZc1BwVDM0VF9aQVFBQUFBQUFBUEFfNEFFQTlRRUFBQUFBbUFJQW9BSUF0UUlBQUFBQXZRSUFBQUFBNEFJQTZBSUEtQUlBZ0FNQm1BTUJxQU9IAcSIdWdNSlUwbE9Nem8wT0RRMDRBUDRHWWdFQUpBRUFKZ0VBY0UJXQUBCERKQgUICQEYMkFRQThRUQkNAQEsUGdFQUlnRjdDV3BCERc0UEFfmgKJASFDZy1TX2c2OQEkblBGYklBUW9BRBVkDGtRRG8ykQAQUVBnWlMdTQBVEQwMQUFBVx0MAFkdDABhHQwAYx0MoGVBQS7YAgDgAq2YSOoCS2h0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5BRTw3i9wYWdlcy9tdWx0aXBsZV9iaWRkZXJzLmh0bWw_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMvcHJlYmlkmAQAogQOMTAzLjc5LjEwMC4xODCoBOZCsgQQCAQQARgAIAAoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0ODQ02gQCCADgBAHwBLKWwC6IBQGYBQCgBf___________wHABQDJBQAAAAAAAPA_0gUJCQAAAAABDnDYBQHgBQHwBZn0IfoFBAgAEACQBgGYBgC4BgDBBgEhMAAA8L_QBvUv2gYWChAJERkBUBAAGADgBgzyBgIIAIAHAYgHAKAHQQ..&s=8b14b952b73092945ef66436be991786b53f7a68", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "native", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 97520434, + "media_type_id": 12, + "media_subtype_id": 65, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 53, + "client_initiated_ad_counting": true, + "viewability": { + "config": "" + }, + "rtb": { + "native": { + "title": "This is a Prebid Native Creative", + "sponsored": "Prebid.org", + "main_img": { + "url": "https://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg", + "width": 989, + "height": 742, + "prevent_crop": false + }, + "link": { + "url": "http://prebid.org/dev-docs/show-multi-format-ads.html", + "click_trackers": [ + "https://sin3-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQH_5oLrb5UFXu79Z6eyQcT_NYileAAAAAAjpyQBtJAAAbSQAAAIAAAAyC9AFnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAQQCAAAAALoAnRbC8wAAAAA./bcr=AAAAAAAA8D8=/cnd=%21Cg-S_giH-bwKELKWwC4YnPFbIAQoADEAAAAAAAAkQDoJU0lOMzo0ODQ0QPgZSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeAA./cca=OTMyNSNTSU4zOjQ4NDQ=/bn=89112/" + ] + }, + "impression_trackers": [ + "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fmultiple_bidders.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKQCKAQBAAAAwDWAAUBCM3FpfEFEP_yg9W7u_mgVxi7_-bKzp3kuD8qNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMIjSpwY47UhA7UhIAlCylsAuWJzxW2AAaM26dXiYuAWAAQGKAQNVU0SSAQEG8FKYAQGgAQGoAQGwAQC4AQHAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1Nzk3NzA1NzMpO3VmKCdyJywgOTc1MjA0MzQsIC4eAPDQkgK1AiFBang1SHdpSC1id0tFTEtXd0M0WUFDQ2M4VnN3QURnQVFBUkk3VWhRaU5LbkJsZ0FZS3NHYUFCd0FuaUtBWUFCSG9nQmlnR1FBUUNZQVFDZ0FRR29BUU93QVFDNUFmT3RhcVFBQUNSQXdRSHpyV3FrQUFBa1FNa0JvYVlzUHBUMzRUX1pBUUFBQUFBQUFQQV80QUVBOVFFQUFBQUFtQUlBb0FJQXRRSUFBQUFBdlFJQUFBQUE0QUlBNkFJQS1BSUFnQU1CbUFNQnFBT0gBxIh1Z01KVTBsT016bzBPRFEwNEFQNEdZZ0VBSkFFQUpnRUFjRQldBQEIREpCBQgJARgyQVFBOFFRCQ0BASxQZ0VBSWdGN0NXcEIRFzRQQV-aAokBIUNnLVNfZzY5ASRuUEZiSUFRb0FEFWQMa1FEbzKRABBRUGdaUx1NAFURDAxBQUFXHQwAWR0MAGEdDABjHQygZUFBLtgCAOACrZhI6gJLaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkFFPDeL3BhZ2VzL211bHRpcGxlX2JpZGRlcnMuaHRtbD9wYmpzX2RlYnVnPXRydWWAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92My9wcmViaWSYBACiBA4xMDMuNzkuMTAwLjE4MKgE5kKyBBAIBBABGAAgACgBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ4NDTaBAIIAeAEAfAEspbALogFAZgFAKAF____________AcAFAMkFAAAAAAAA8D_SBQkJAAAAAAEOcNgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGASEwAADwP9AG9S_aBhYKEAkRGQFQEAAYAOAGDPIGAggAgAcBiAcAoAdB&s=2061bd85ce022a41bc16ebb20c193aabbbc07809" + ], + "id": 97520434 + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/description.md b/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/description.md new file mode 100644 index 00000000000..c5b114e8bb3 --- /dev/null +++ b/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/description.md @@ -0,0 +1,43 @@ +Test Page - 'test/pages/multiple_bidders.html' +Test Spec File - 'test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js' + +Ad Unit that generates given 'Request' - 'Response' pairs. + +```(javascript) +[{ + code: 'div-banner-native-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13232392, + } + }] +}, +{ + code: 'div-banner-native-2', + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'adasta', + params: { + placementId: 13232392, + } + }] +}]; +``` \ No newline at end of file diff --git a/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/request.json b/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/request.json new file mode 100644 index 00000000000..8399bed631c --- /dev/null +++ b/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/request.json @@ -0,0 +1,36 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/", + "body": { + "tags": [ + { + "sizes": [ + { + "width": 300, + "height": 250 + }, + { + "width": 300, + "height": 600 + } + ], + "primary_size": { + "width": 300, + "height": 250 + }, + "ad_types": [ + "banner" + ], + "id": 13232392, + "allow_smaller_sizes": false, + "use_pmt_rule": false, + "prebid": true, + "disable_psa": true, + "hb_source": 1 + } + ], + "user": {} + } + } +} \ No newline at end of file diff --git a/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/response.json b/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/response.json new file mode 100644 index 00000000000..54ad643b82d --- /dev/null +++ b/test/fake-server/fixtures/multi-bidder/multi-bidder-appnexus/response.json @@ -0,0 +1,49 @@ +{ + "httpResponse": { + "body": { + "version": "3.0.0", + "tags": [ + { + "tag_id": 13232392, + "auction_id": "6917498423334366136", + "nobid": false, + "no_ad_url": "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fmultiple_bidders.html%3Fpbjs_debug%3Dtrue&e=wqT_3QLBCKBBBAAAAwDWAAUBCNGcpvEFELjXrYmmhfn_Xxi7_-bKzp3kuD8qNgkAAAkCABEJBwgAABkJCQgkQCEJCQgAACkRCQAxCQn0gQEkQDCI0qcGOO1IQO1ISABQAFic8VtgAGjNunV4AIABAYoBAJIBA1VTRJgBrAKgAfoBqAEBsAEAuAEBwAEAyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTc5NzgxNzEzKTt1ZigncicsIDk2ODQ2MDM1LCAxNTc5NzgxNzEzKTuSArUCITZEb2NyZ2lILWJ3S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFpTktuQmxnQVlLc0dhQUJ3R25oaWdBR1lBWWdCWXBBQkFKZ0JBS0FCQWFnQkE3QUJBTGtCODYxcXBBQUFKRURCQWZPdGFxUUFBQ1JBeVFHQ1VBT2x6Q0xkUDlrQkFBQUFBQUFBOERfZ0FRRDFBUUFBQUFDWUFnQ2dBZ0MxQWdBQUFBQzlBZ0FBQUFEZ0FnRG9BZ0Q0QWdDQUF3R1lBd0dvQTRmNXZBcTZBd2xUU1U0ek9qUTNNem5nQV9nWmlBUUFrQVFBbUFRQndRUQFNCQEITWtFCQkBARhEWUJBRHhCAQsNASwtQVFBaUFXREpha0YNE0BBOEQ4LpoCiQEhOEE1YjlRaTI5ASRuUEZiSUFRb0FEFVhYa1FEb0pVMGxPTXpvME56TTVRUGdaU1ENTwxQQV9VEQwMQUFBVx0MAFkdDABhHQwAYx0M8EZlQUEuwgI1aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc2hvdy1tdWx0aS1mb3JtYXQtYWRzLmh0bWzYAgDgAq2YSOoCSw1ASHRlc3QubG9jYWxob3N0Ojk5OTkFFBgvcGFnZXMvBUYocGxlX2JpZGRlcnMFRvBAP3BianNfZGVidWc9dHJ1ZYADAIgDAZADAJgDF6ADAaoDAMADrALIAwDYAwDgAwDoAwD4AwGABACSBA0vdXQvdjMNtvBhmAQAogQOMTAzLjc5LjEwMC4xODCoBJ9EsgQSCAQQARisAiD6ASgBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNTSU4zOjQ3MznaBAIIAOAEAfAE04GXLogFAZgFAKAF______8BAxQBwAUAyQVpiBTwP9IFCQkJDHAAANgFAeAFAfAFAfoFBAgAEACQBgCYBgC4BgDBBgkjKPC_0Ab1L9oGFgoQCREZAVAQABgA4AYB8gYCCACABwGIBwCgBwE.&s=8d42ac30ca2d2a8a63215f5aba2a43bd23af0023", + "timeout_ms": 0, + "ad_profile_id": 1182765, + "rtb_video_fallback": false, + "ads": [ + { + "content_source": "rtb", + "ad_type": "banner", + "buyer_member_id": 9325, + "advertiser_id": 2529885, + "creative_id": 96846035, + "media_type_id": 1, + "media_subtype_id": 1, + "cpm": 10, + "cpm_publisher_currency": 10, + "publisher_currency_code": "$", + "brand_category_id": 0, + "client_initiated_ad_counting": true, + "rtb": { + "banner": { + "content": "
", + "width": 300, + "height": 250 + }, + "trackers": [ + { + "impression_urls": [ + "https://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2Ftest%2Fpages%2Fmultiple_bidders.html%3Fpbjs_debug%3Dtrue&e=wqT_3QLJCKBJBAAAAwDWAAUBCNGcpvEFELjXrYmmhfn_Xxi7_-bKzp3kuD8qNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMIjSpwY47UhA7UhIAlDTgZcuWJzxW2AAaM26dXicuAWAAQGKAQNVU0SSAQEG8FKYAawCoAH6AagBAbABALgBAcABBMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3OTc4MTcxMyk7dWYoJ3InLCA5Njg0NjAzNTYeAPCakgK1AiE2RG9jcmdpSC1id0tFTk9CbHk0WUFDQ2M4VnN3QURnQVFBUkk3VWhRaU5LbkJsZ0FZS3NHYUFCd0duaGlnQUdZQVlnQllwQUJBSmdCQUtBQkFhZ0JBN0FCQUxrQjg2MXFwQUFBSkVEQkFmT3RhcVFBQUNSQXlRR0NVQU9sekNMZFA5a0JBQUFBQUFBQThEX2dBUUQxQVEBDyxDWUFnQ2dBZ0MxQWcFEAA5CQjwQERnQWdEb0FnRDRBZ0NBQXdHWUF3R29BNGY1dkFxNkF3bFRTVTR6T2pRM016bmdBX2daaUFRQWtBUUFtQVFCd1FRAU0JAQhNa0UJCQEBGERZQkFEeEIBCw0BLC1BUUFpQVdESmFrRg0TPEE4RDgumgKJASE4QTViOVE2OQEkblBGYklBUW9BRBVYWGtRRG9KVTBsT016bzBOek01UVBnWlNRDU8MUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDPBGZUFBLsICNWh0dHA6Ly9wcmViaWQub3JnL2Rldi1kb2NzL3Nob3ctbXVsdGktZm9ybWF0LWFkcy5odG1s2AIA4AKtmEjqAksNQEh0ZXN0LmxvY2FsaG9zdDo5OTk5BRQYL3BhZ2VzLwVGKHBsZV9iaWRkZXJzBUbwQD9wYmpzX2RlYnVnPXRydWWAAwCIAwGQAwCYAxegAwGqAwDAA6wCyAMA2AMA4AMA6AMA-AMBgAQAkgQNL3V0L3YzDbbwYZgEAKIEDjEwMy43OS4xMDAuMTgwqASfRLIEEggEEAEYrAIg-gEoASgCMAA4A7gEAMAEAMgEANIEDjkzMjUjU0lOMzo0NzM52gQCCAHgBAHwBNOBly6IBQGYBQCgBf______AQMUAcAFAMkFaZAU8D_SBQkJCQxwAADYBQHgBQHwBQH6BQQIABAAkAYAmAYAuAYAwQYJIyjwP9AG9S_aBhYKEAkRGQFQEAAYAOAGAfIGAggAgAcBiAcAoAcB&s=6c6fb8a005b60bc5535b528c16ed74cd66c08dd0" + ], + "video_events": {} + } + ] + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/test/fake-server/index.js b/test/fake-server/index.js new file mode 100644 index 00000000000..752648c6746 --- /dev/null +++ b/test/fake-server/index.js @@ -0,0 +1,37 @@ +/* eslint-disable no-console */ + +const express = require('express'); +const morgan = require('morgan'); +const bodyParser = require('body-parser'); +const argv = require('yargs').argv; +const fakeResponder = require('./fake-responder.js'); + +const PORT = argv.port || '3000'; + +// Initialize express app +const app = express(); + +// Middlewares +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); +app.use(bodyParser.text({ type: 'text/plain' })); +app.use(morgan('dev')); // used to log incoming requests + +// Allow Cross Origin request from 'test.localhost:9999' +app.use(function(req, res, next) { + res.header('Access-Control-Allow-Origin', req.headers.origin); + res.header('Access-Control-Allow-Credentials', true); + next(); +}); + +app.post('/', fakeResponder, (req, res) => { + res.send(); +}); + +app.use((req, res) => { + res.status(404).send('Not Found'); +}); + +app.listen(PORT, () => { + console.log(`fake-server listening on http://localhost:${PORT}`); +}); diff --git a/test/fixtures/video/adUnit.json b/test/fixtures/video/adUnit.json index df55eb25d79..0773d7f3a62 100644 --- a/test/fixtures/video/adUnit.json +++ b/test/fixtures/video/adUnit.json @@ -1,7 +1,11 @@ { "code": "video1", - "sizes": [640,480], - "mediaType": "video", + "mediaTypes": { + "video": { + "context": "instream", + "playerSize": [640, 480] + } + }, "bids": [ { "bidder": "appnexus", diff --git a/test/helpers/testing-utils.js b/test/helpers/testing-utils.js index 21d5992873e..76e2b652a79 100644 --- a/test/helpers/testing-utils.js +++ b/test/helpers/testing-utils.js @@ -1,4 +1,13 @@ +/* eslint-disable no-console */ module.exports = { host: (process.env.TEST_SERVER_HOST) ? process.env.TEST_SERVER_HOST : 'localhost', - protocol: (process.env.TEST_SERVER_PROTOCOL) ? 'https' : 'http' + protocol: (process.env.TEST_SERVER_PROTOCOL) ? 'https' : 'http', + waitForElement: function(elementRef, time = 2000) { + let element = $(elementRef); + element.waitForExist({timeout: time}); + }, + switchFrame: function(frameRef, frameName) { + let iframe = $(frameRef); + browser.switchToFrame(iframe); + } } diff --git a/test/mock-server/expectations/request-response-pairs/banner/index.js b/test/mock-server/expectations/request-response-pairs/banner/index.js deleted file mode 100644 index 17538c8b33d..00000000000 --- a/test/mock-server/expectations/request-response-pairs/banner/index.js +++ /dev/null @@ -1,96 +0,0 @@ -var app = require('../../../index'); - -/** - * This file will have the fixtures for request and response. Each one has to export two functions getRequest and getResponse. - * expectation directory will hold all the request reponse pairs of different types. middlewares added to the server will parse - * these files and return the response when expecation is met - * - * The expecation created here is replicating trafficSourceCode example in Prebid. - */ - -/** - * This function will return the request object with all the entities method, path, body, header etc. - * - * @return {object} Request object - */ -exports.getRequest = function() { - return { - 'httpRequest': { - 'method': 'POST', - 'path': '/', - 'body': { - 'tags': [{ - 'sizes': [{ - 'width': 300, - 'height': 250 - }, { - 'width': 300, - 'height': 600 - }], - 'primary_size': { - 'width': 300, - 'height': 250 - }, - 'ad_types': ['banner'], - 'id': 13144370, - 'allow_smaller_sizes': false, - 'use_pmt_rule': false, - 'prebid': true, - 'disable_psa': true - }], - 'user': {} - } - } - } -} - -/** - * This function will return the response object with all the entities method, path, body, header etc. - * - * @return {object} Response object - */ -exports.getResponse = function() { - return { - 'httpResponse': { - 'body': { - 'version': '3.0.0', - 'tags': [{ - 'uuid': '2c8c83a1deaf1a', - 'tag_id': 13144370, - 'auction_id': '8147841645883553832', - 'nobid': false, - 'no_ad_url': 'http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKzCKAzBAAAAwDWAAUBCJKPpe4FEKjwl-js2byJcRiq5MnUovf28WEqNgkAAAkCABEJBywAABkAAACA61HgPyEREgApEQkAMREb8GkwsqKiBjjtSEDtSEgAUABYnPFbYABotc95eACAAQGKAQCSAQNVU0SYAawCoAH6AagBAbABALgBAcABAMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3MzQ3MjE0Nik7AR0scicsIDk2ODQ2MDM1Nh4A8PWSAqUCIXp6ZmhVQWl1c0s0S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlJSUNhQUJ3Q0hncWdBRWtpQUVxa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQVozRkl5WjA1Tm9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcnJDdUNyb0RDVk5KVGpNNk5EY3pOZUFEcUJXSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkEg0QkFDSUJmOGuaAokBIUl3OTBCOikBJG5QRmJJQVFvQUQROFhEZ1B6b0pVMGxPTXpvME56TTFRS2dWUxFoDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJTDTrYdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9oZWxsb193b3JsZAVO8EA_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAOsAsgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw248F6YBACiBAsxMC43NS43NC42OagEkECyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggA4AQB8ATTgZcuiAUBmAUAoAX______wEDFAHABQDJBWmAFPA_0gUJCQkMcAAA2AUB4AUB8AUB-gUECAAQAJAGAJgGALgGAMEGCSM08L_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgHyBgIIAIAHAYgHAKAHAQ..&s=68cfb6ed042ea47f5d3fc2c32cc068500e542066', - 'timeout_ms': 0, - 'ad_profile_id': 1182765, - 'rtb_video_fallback': false, - 'ads': [{ - 'content_source': 'rtb', - 'ad_type': 'banner', - 'buyer_member_id': 9325, - 'advertiser_id': 2529885, - 'creative_id': 96846035, - 'media_type_id': 1, - 'media_subtype_id': 1, - 'cpm': 0.500000, - 'cpm_publisher_currency': 0.500000, - 'is_bin_price_applied': false, - 'publisher_currency_code': '$', - 'brand_category_id': 0, - 'client_initiated_ad_counting': true, - 'rtb': { - 'banner': { - 'content': "
", - 'width': 300, - 'height': 250 - }, - 'trackers': [{ - 'impression_urls': ['http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue&e=wqT_3QK7CKA7BAAAAwDWAAUBCJKPpe4FEKjwl-js2byJcRiq5MnUovf28WEqNgkAAAECCOA_EQEHNAAA4D8ZAAAAgOtR4D8hERIAKREJADERG6gwsqKiBjjtSEDtSEgCUNOBly5YnPFbYABotc95eJK4BYABAYoBA1VTRJIBAQbwUpgBrAKgAfoBqAEBsAEAuAEBwAEEyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTczNDcyMTQ2KTt1ZigncicsIDk2ODQ2MDM1Nh4A8PWSAqUCIXp6ZmhVQWl1c0s0S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlJSUNhQUJ3Q0hncWdBRWtpQUVxa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQVozRkl5WjA1Tm9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcnJDdUNyb0RDVk5KVGpNNk5EY3pOZUFEcUJXSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkEg0QkFDSUJmOGuaAokBIUl3OTBCOikBJG5QRmJJQVFvQUQROFhEZ1B6b0pVMGxPTXpvME56TTFRS2dWUxFoDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJTDTrYdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9oZWxsb193b3JsZAVO8EA_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAOsAsgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw248F6YBACiBAsxMC43NS43NC42OagEkECyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggB4AQB8ATTgZcuiAUBmAUAoAX______wEDFAHABQDJBWmIFPA_0gUJCQkMcAAA2AUB4AUB8AUB-gUECAAQAJAGAJgGALgGAMEGCSM08D_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgHyBgIIAIAHAYgHAKAHAQ..&s=951a029669a69e3f0c527c937c2d852be92802e1'], - 'video_events': {} - }] - } - }] - }] - }, - } - } -} diff --git a/test/mock-server/index.js b/test/mock-server/index.js deleted file mode 100644 index 30ead952fcc..00000000000 --- a/test/mock-server/index.js +++ /dev/null @@ -1,36 +0,0 @@ -const express = require('express'); -const argv = require('yargs').argv; -const app = module.exports = express(); -const port = (argv.port) ? argv.port : 3000; -const bodyParser = require('body-parser'); -const renderCreative = require('./request-middlewares/prebid-request.js'); - -app.use(express.static(__dirname + '/content')); -app.use(bodyParser.text({type: 'text/plain'})); - -app.locals = { - 'port': port, - 'host': 'localhost' -}; - -// get type will be used to test prebid jsonp requests -app.get('/', renderCreative, (request, response) => { - response.send(); -}); - -// prebid make POST type request to ut endpoint so here we will match ut endpoint request. -app.post('/', renderCreative, (request, response) => { - response.send(); -}); - -app.listen(port, (err) => { - if (err) { - return console.log('something bad happened', err); - } - - console.log(`server is listening on ${port}`); -}); - -process.on('SIGTERM', function() { console.log('halt mock-server'); process.exit(0) }); - -process.on('SIGINT', function() { console.log('shutdown mock-server'); process.exit(0) }); diff --git a/test/mock-server/request-middlewares/prebid-request.js b/test/mock-server/request-middlewares/prebid-request.js deleted file mode 100644 index 6e2d03487cf..00000000000 --- a/test/mock-server/request-middlewares/prebid-request.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * This middleware will be used to find matching request hitting the ut endpoint by prebid. - * As of now it only uses the request payload to compare with httpRequest.body defined in expectations dir. - * Matching headers or cookies can also be the use case. - */ - -const glob = require('glob'); -const path = require('path'); -const deepEqual = require('deep-equal'); - -module.exports = function (req, res, next) { - let reqBody; - try { - if (req.method === 'GET') { - reqBody = JSON.parse(req.query.q); - } else { - reqBody = JSON.parse(req.body); - } - } catch (e) { - // error - } - - // prebid uses uuid to match request response pairs. - // On each request new uuid is generated, so here i am grabbing the uuid from incoming request and adding it to matched response. - let uuidObj = {}; - if (reqBody && reqBody.uuid) { - uuidObj.response = reqBody.uuid; - delete reqBody.uuid; - } - - if (reqBody && reqBody.tags) { - uuidObj.tags = reqBody.tags.map((tag) => { - let uuid = tag.uuid; - delete tag.uuid; - return uuid; - }); - } - - // values within these request props are dynamically generated and aren't - // vital to check in these tests, so they are deleted rather than updating - // the request-response pairs continuously - ['sdk', 'referrer_detection'].forEach(prop => { - if (reqBody && reqBody[prop]) { - delete reqBody[prop]; - } - }); - - // Parse all the expectation to find response for this request - glob.sync('./test/mock-server/expectations/**/*.js').some((file) => { - file = require(path.resolve(file)); - let expectedReqBody = JSON.parse(JSON.stringify(file.getRequest().httpRequest.body)); - // respond to all requests - // TODO send a 404 if resource not found - res.set({ - 'Access-Control-Allow-Credentials': 'true', - 'Access-Control-Allow-Origin': req.headers.origin - }); - - // As of now only body is compared. We can also add other request properties like headers, cookies if required - if (deepEqual(reqBody, expectedReqBody)) { - let response = file.getResponse().httpResponse.body; - if (Object.keys(uuidObj).length > 0) { - response.tags.forEach((tag, index) => { - tag.uuid = uuidObj.tags[index]; - }); - } - res.type('json'); - response = JSON.stringify(response); - res.write(response); - return true; - } - }); - - next(); -}; diff --git a/test/mocks/adloaderStub.js b/test/mocks/adloaderStub.js index 55d3815ead8..b52ca5e9280 100644 --- a/test/mocks/adloaderStub.js +++ b/test/mocks/adloaderStub.js @@ -1,21 +1,23 @@ -import * as adloader from 'src/adloader'; +import * as adloader from 'src/adloader.js'; -let sandbox; +// this export is for adloader's tests against actual implementation +export let loadExternalScript = adloader.loadExternalScript; -export let loadExternalScript; -export let loadExternalScriptStub; +let stub = createStub(); -beforeEach(function() { - sandbox = sinon.sandbox.create(); - loadExternalScript = adloader.loadExternalScript; - loadExternalScriptStub = sandbox.stub(adloader, 'loadExternalScript').callsFake((...args) => { +function createStub() { + return sinon.stub(adloader, 'loadExternalScript').callsFake((...args) => { if (typeof args[2] === 'function') { args[2](); + } else if (typeof args[3] === 'function') { + args[3](); } + return document.createElement('script'); }); -}); +} -afterEach(function() { - sandbox.restore(); +beforeEach(function() { + stub.restore(); + stub = createStub(); }); diff --git a/test/mocks/videoCacheStub.js b/test/mocks/videoCacheStub.js index 39f5d67b6a9..7ce899cae35 100644 --- a/test/mocks/videoCacheStub.js +++ b/test/mocks/videoCacheStub.js @@ -1,4 +1,4 @@ -import * as videoCache from 'src/videoCache'; +import * as videoCache from 'src/videoCache.js'; /** * Function which can be called from unit tests to stub out the video cache. diff --git a/test/mocks/xhr.js b/test/mocks/xhr.js new file mode 100644 index 00000000000..9fb8fe87fa0 --- /dev/null +++ b/test/mocks/xhr.js @@ -0,0 +1,9 @@ + +export let server = sinon.createFakeServer(); +export let xhr = global.XMLHttpRequest; + +beforeEach(function() { + server.restore(); + server = sinon.createFakeServer(); + xhr = global.XMLHttpRequest; +}); diff --git a/test/pages/banner.html b/test/pages/banner.html index e1859abdd85..75993cefb39 100644 --- a/test/pages/banner.html +++ b/test/pages/banner.html @@ -31,22 +31,20 @@ } }] } - //, { - // code: 'div-gpt-ad-1460505748561-1', - // mediaTypes: { - // banner: { - // sizes: [[300, 250], [300, 600]], - // } - // }, - // bids: [{ - // bidder: "appnexus", - // params: { - // accountId: 14062, - // siteId: 70608, - // zoneId: 498816 - // } - // }] - // } + ,{ + code: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: "appnexus", + params: { + placementId: 13144370 + } + }] + } ]; @@ -54,18 +52,18 @@ var googletag = googletag || {}; googletag.cmd = googletag.cmd || []; - googletag.cmd.push(() => { + googletag.cmd.push(function() { googletag.pubads().disableInitialLoad(); }); - pbjs.que.push(() => { + pbjs.que.push(function () { pbjs.addAdUnits(adUnits); pbjs.requestBids({ bidsBackHandler: sendAdServerRequest }); }); function sendAdServerRequest() { - googletag.cmd.push(() => { - pbjs.que.push(() => { + googletag.cmd.push(function () { + pbjs.que.push(function () { pbjs.setTargetingForGPTAsync('div-gpt-ad-1460505748561-0'); googletag.pubads().refresh(); }); @@ -74,7 +72,7 @@
diff --git a/test/pages/bidderSettings.html b/test/pages/bidderSettings.html new file mode 100644 index 00000000000..015ad3ca45f --- /dev/null +++ b/test/pages/bidderSettings.html @@ -0,0 +1,127 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/test/pages/consent_mgt_gdpr.html b/test/pages/consent_mgt_gdpr.html new file mode 100644 index 00000000000..02b367b3c7c --- /dev/null +++ b/test/pages/consent_mgt_gdpr.html @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/test/pages/currency.html b/test/pages/currency.html new file mode 100644 index 00000000000..7f196e60d5e --- /dev/null +++ b/test/pages/currency.html @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + diff --git a/test/pages/video.html b/test/pages/instream.html similarity index 76% rename from test/pages/video.html rename to test/pages/instream.html index 3fabeb14b94..c1e2358b754 100644 --- a/test/pages/video.html +++ b/test/pages/instream.html @@ -38,7 +38,7 @@ { bidder: 'appnexus', params: { - placementId: '9333431', + placementId: '13232361', video: { skipppable: false, playback_methods: ['auto_play_sound_off'] @@ -49,9 +49,22 @@ }; pbjs.que.push(function(){ + pbjs.onEvent('auctionEnd', function() { + let pEl = document.createElement('p'); + pEl.innerText = 'PREBID HAS FINISHED'; + pEl.id = 'statusText'; + let parDiv = document.getElementById('event-window'); + parDiv.appendChild(pEl); + }); + pbjs.addAdUnits(videoAdUnit); + pbjs.setConfig({ + debug: true, + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); pbjs.requestBids({ - timeout : 700, bidsBackHandler : function(bids) { var videoUrl = pbjs.adServers.dfp.buildVideoUrl({ adUnit: videoAdUnit, @@ -64,36 +77,13 @@ } }); }); - - pbjs.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) { - return "10.00"; - } - }, { - key: "hb_size", - val: function (bidResponse) { - return bidResponse.size; - - } - } - ] + pbjs.bidderSettings = { + appnexus: { + bidCpmAdjustment: function() { + return 10.00; } - }; + } + }; @@ -101,6 +91,7 @@

Prebid Video -- video.js

+
diff --git a/test/pages/outstream.html b/test/pages/outstream.html index 2a0543095cd..262c15ed02a 100644 --- a/test/pages/outstream.html +++ b/test/pages/outstream.html @@ -62,7 +62,14 @@ pbjs.que.push(function () { pbjs.setConfig({ debug: true }); - pbjs.addAdUnits(outstreamVideoAdUnit); + pbjs.addAdUnits(outstreamVideoAdUnit); + pbjs.bidderSettings = { + appnexus: { + bidCpmAdjustment: function () { + return 10; + } + } + }; pbjs.requestBids({ bidsBackHandler: initAdServer }); }); diff --git a/test/pages/priceGranularity.html b/test/pages/priceGranularity.html new file mode 100644 index 00000000000..7eae83f673a --- /dev/null +++ b/test/pages/priceGranularity.html @@ -0,0 +1,133 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/test/pages/sizeConfig.html b/test/pages/sizeConfig.html new file mode 100644 index 00000000000..a4aef89e44a --- /dev/null +++ b/test/pages/sizeConfig.html @@ -0,0 +1,144 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/test/pages/userSync.html b/test/pages/userSync.html new file mode 100644 index 00000000000..3d848906ae3 --- /dev/null +++ b/test/pages/userSync.html @@ -0,0 +1,123 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index 2e992d3298c..4afa430f81e 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import events from 'src/events'; +import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; +import { server } from 'test/mocks/xhr.js'; const REQUEST_BIDS = CONSTANTS.EVENTS.REQUEST_BIDS; const BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; @@ -20,19 +21,13 @@ describe(` FEATURE: Analytics Adapters API SCENARIO: A publisher enables analytics AND an \`example\` instance of \`AnalyticsAdapter\`\n`, () => { - let xhr; - let requests; let adapter; beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = (request) => requests.push(request); adapter = new AnalyticsAdapter(config); }); afterEach(function () { - xhr.restore(); adapter.disableAnalytics(); }); @@ -42,7 +37,7 @@ FEATURE: Analytics Adapters API adapter.track({ eventType, args }); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {some: 'data'}, eventType: 'bidRequested'}); }); @@ -53,7 +48,7 @@ FEATURE: Analytics Adapters API events.emit(eventType, args); adapter.enableAnalytics(); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {wat: 'wot'}, eventType: 'bidResponse'}); }); @@ -73,7 +68,7 @@ FEATURE: Analytics Adapters API adapter.enableAnalytics(); events.emit(eventType, args); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {more: 'info'}, eventType: 'bidWon'}); }); @@ -84,7 +79,7 @@ FEATURE: Analytics Adapters API adapter.enableAnalytics(); events.emit(eventType, args); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {call: 'adRenderFailed'}, eventType: 'adRenderFailed'}); }); @@ -95,7 +90,7 @@ FEATURE: Analytics Adapters API adapter.enableAnalytics(); events.emit(eventType, args); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {call: 'addAdUnits'}, eventType: 'addAdUnits'}); }); @@ -106,7 +101,7 @@ FEATURE: Analytics Adapters API adapter.enableAnalytics(); events.emit(eventType, args); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {call: 'request'}, eventType: 'requestBids'}); }); @@ -117,7 +112,7 @@ FEATURE: Analytics Adapters API adapter.enableAnalytics(); events.emit(eventType, args); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {call: 'request'}, eventType: 'bidRequested'}); }); @@ -128,7 +123,7 @@ FEATURE: Analytics Adapters API adapter.enableAnalytics(); events.emit(eventType, args); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {call: 'response'}, eventType: 'bidResponse'}); }); @@ -139,7 +134,7 @@ FEATURE: Analytics Adapters API adapter.enableAnalytics(); events.emit(eventType, args); - let result = JSON.parse(requests[0].requestBody); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {call: 'timeout'}, eventType: 'bidTimeout'}); }); @@ -151,7 +146,7 @@ FEATURE: Analytics Adapters API adapter.enableAnalytics(); events.emit(eventType, args); - expect(requests.length).to.equal(1); + expect(server.requests.length).to.equal(1); }); describe(`AND sampling is enabled\n`, function () { @@ -174,8 +169,8 @@ FEATURE: Analytics Adapters API }); events.emit(eventType, args); - expect(requests.length).to.equal(1); - let result = JSON.parse(requests[0].requestBody); + expect(server.requests.length).to.equal(1); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({args: {more: 'info'}, eventType: 'bidWon'}); }); @@ -187,7 +182,7 @@ FEATURE: Analytics Adapters API }); events.emit(eventType, args); - expect(requests.length).to.equal(0); + expect(server.requests.length).to.equal(0); }); }); }); diff --git a/test/spec/adapters/adbutler_spec.js b/test/spec/adapters/adbutler_spec.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js index 31ebeb23900..0c46cd2f171 100644 --- a/test/spec/adloader_spec.js +++ b/test/spec/adloader_spec.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import * as adLoader from 'test/mocks/adloaderStub'; +import * as utils from 'src/utils.js'; +import * as adLoader from 'test/mocks/adloaderStub.js'; describe('adLoader', function () { let utilsinsertElementStub; diff --git a/test/spec/aliasBidder_spec.js b/test/spec/aliasBidder_spec.js index 61a654dcb90..771d7fcdd46 100644 --- a/test/spec/aliasBidder_spec.js +++ b/test/spec/aliasBidder_spec.js @@ -1,4 +1,4 @@ -import { pbjsTestOnly } from 'test/helpers/pbjs-test-only'; +import { pbjsTestOnly } from 'test/helpers/pbjs-test-only.js'; describe('Publisher API _ Alias Bidder', function () { var assert = require('chai').assert; diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 970bc61cc61..35a29727614 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -1,13 +1,19 @@ -import { getKeyValueTargetingPairs, auctionCallbacks, AUCTION_COMPLETED } from 'src/auction'; +import { + getKeyValueTargetingPairs, + auctionCallbacks, + AUCTION_COMPLETED, + adjustBids, + getMediaTypeGranularity, +} from 'src/auction.js'; import CONSTANTS from 'src/constants.json'; -import { adjustBids, getMediaTypeGranularity } from 'src/auction'; -import * as auctionModule from 'src/auction'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { createBid } from 'src/bidfactory'; -import { config } from 'src/config'; -import * as store from 'src/videoCache'; -import * as ajaxLib from 'src/ajax'; -import find from 'core-js/library/fn/array/find'; +import * as auctionModule from 'src/auction.js'; +import { registerBidder } from 'src/adapters/bidderFactory.js'; +import { createBid } from 'src/bidfactory.js'; +import { config } from 'src/config.js'; +import * as store from 'src/videoCache.js'; +import * as ajaxLib from 'src/ajax.js'; +import find from 'core-js-pure/features/array/find.js'; +import { server } from 'test/mocks/xhr.js'; var assert = require('assert'); @@ -766,7 +772,6 @@ describe('auctionmanager.js', function () { describe('when auction timeout is 20', function () { let eventsEmitSpy; - let server; before(function () { bids = [mockBid(), mockBid({ bidderCode: BIDDER_CODE1 })]; @@ -776,8 +781,6 @@ describe('auctionmanager.js', function () { }); beforeEach(function () { - server = sinon.createFakeServer(); - adUnits = [{ code: ADUNIT_CODE, bids: [ @@ -789,7 +792,6 @@ describe('auctionmanager.js', function () { eventsEmitSpy = sinon.spy(events, 'emit'); }); afterEach(function () { - server.restore(); events.emit.restore(); }); @@ -985,6 +987,103 @@ describe('auctionmanager.js', function () { }); }); + describe('addBidRequests', function () { + let createAuctionStub; + let adUnits; + let adUnitCodes; + let spec; + let spec1; + let auction; + let ajaxStub; + let logMessageStub; + let logInfoStub; + let logWarnStub; + let logErrorStub; + + let bids = TEST_BIDS; + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; + + before(function () { + logMessageStub = sinon.stub(utils, 'logMessage'); + logInfoStub = sinon.stub(utils, 'logInfo'); + logWarnStub = sinon.stub(utils, 'logWarn'); + logErrorStub = sinon.stub(utils, 'logError'); + 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(function () { + logMessageStub.restore(); + logInfoStub.restore(); + logWarnStub.restore(); + logErrorStub.restore(); + ajaxStub.restore(); + adapterManager.makeBidRequests.restore(); + }); + + beforeEach(function () { + config.setConfig({ + debugging: { + enabled: true, + bidRequests: [{ + bidderCode: BIDDER_CODE, + adUnitCode: ADUNIT_CODE, + storedAuctionResponse: '11111' + }] + } + }); + + 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(function () { + logMessageStub.resetHistory(); + logInfoStub.resetHistory(); + logWarnStub.resetHistory(); + logErrorStub.resetHistory(); + auctionModule.newAuction.restore(); + config.resetConfig(); + }); + + it('should override bidRequest properties when config debugging has a matching bidRequest defined', function () { + auction.callBids(); + const auctionBidRequests = auction.getBidRequests(); + assert.equal(auctionBidRequests.length > 0, true); + assert.equal(Array.isArray(auctionBidRequests[0].bids), true); + + const bid = find(auctionBidRequests[0].bids, bid => bid.adUnitCode === ADUNIT_CODE); + assert.equal(typeof bid !== 'undefined', true); + assert.equal(bid.hasOwnProperty('storedAuctionResponse'), true); + assert.equal(bid.storedAuctionResponse, '11111'); + }); + }); + describe('getMediaTypeGranularity', function () { it('video', function () { let bidReq = { @@ -1058,8 +1157,6 @@ describe('auctionmanager.js', function () { describe('auctionCallbacks', function() { let bids = TEST_BIDS; let bidRequests; - let xhr; - let requests; let doneSpy; let auction = { getBidRequests: () => bidRequests, @@ -1070,9 +1167,6 @@ describe('auctionmanager.js', function () { beforeEach(() => { doneSpy = sinon.spy(); - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = (request) => requests.push(request); config.setConfig({ cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache' @@ -1082,7 +1176,6 @@ describe('auctionmanager.js', function () { afterEach(() => { doneSpy.resetHistory(); - xhr.restore(); config.resetConfig(); }); @@ -1132,7 +1225,7 @@ describe('auctionmanager.js', function () { assert.equal(doneSpy.callCount, 0); const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; - requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); + server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); assert.equal(doneSpy.callCount, 1); }) }); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 990add9b134..be5b4bbb78b 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { assert } from 'chai'; -import { newConfig } from 'src/config'; +import { newConfig } from 'src/config.js'; const utils = require('src/utils'); @@ -33,7 +33,7 @@ describe('config API', function () { expect(getConfig()).to.be.a('object'); }); - it('sets and gets arbitrary configuarion properties', function () { + it('sets and gets arbitrary configuration properties', function () { setConfig({ baz: 'qux' }); expect(getConfig('baz')).to.equal('qux'); }); @@ -178,6 +178,23 @@ describe('config API', function () { expect(getConfig('customPriceBucket')).to.equal(goodConfig); }); + it('sets deviceAccess', function () { + // When the deviceAccess flag config option is not set, cookies may be read and set + expect(getConfig('deviceAccess')).to.be.equal(true); + + // When the deviceAccess flag config option is set to false, no cookies are read or set + setConfig({ + 'deviceAccess': false + }); + expect(getConfig('deviceAccess')).to.be.equal(false); + + // When the deviceAccess flag config option is set to true, cookies may be read and set + setConfig({ + 'deviceAccess': true + }); + expect(getConfig('deviceAccess')).to.be.equal(true); + }); + it('should log error for invalid priceGranularity', function () { setConfig({ priceGranularity: '' }); const error = 'Prebid Error: no value passed to `setPriceGranularity()`'; diff --git a/test/spec/cpmBucketManager_spec.js b/test/spec/cpmBucketManager_spec.js index 0d83687bf2e..0b8635a4e3b 100644 --- a/test/spec/cpmBucketManager_spec.js +++ b/test/spec/cpmBucketManager_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import {getPriceBucketString, isValidPriceConfig} from 'src/cpmBucketManager'; +import {getPriceBucketString, isValidPriceConfig} from 'src/cpmBucketManager.js'; let cpmFixtures = require('test/fixtures/cpmInputsOutputs.json'); describe('cpmBucketManager', function () { diff --git a/test/spec/debugging_spec.js b/test/spec/debugging_spec.js index ba9702b0324..bf49a579cbd 100644 --- a/test/spec/debugging_spec.js +++ b/test/spec/debugging_spec.js @@ -1,8 +1,8 @@ import { expect } from 'chai'; -import { sessionLoader, addBidResponseHook, getConfig, disableOverrides, boundHook } from 'src/debugging'; -import { addBidResponse } from 'src/auction'; -import { config } from 'src/config'; +import { sessionLoader, addBidResponseHook, addBidderRequestsHook, getConfig, disableOverrides, addBidResponseBound, addBidderRequestsBound } from 'src/debugging.js'; +import { addBidResponse, addBidderRequests } from 'src/auction.js'; +import { config } from 'src/config.js'; describe('bid overrides', function () { let sandbox; @@ -31,14 +31,16 @@ describe('bid overrides', function () { enabled: true }); - expect(addBidResponse.getHooks().some(hook => hook.hook === boundHook)).to.equal(true); + expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); + expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); }); it('should happen when configuration found in sessionStorage', function () { sessionLoader({ getItem: () => ('{"enabled": true}') }); - expect(addBidResponse.getHooks().some(hook => hook.hook === boundHook)).to.equal(true); + expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); + expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); }); it('should not throw if sessionStorage is inaccessible', function () { @@ -52,7 +54,7 @@ describe('bid overrides', function () { }); }); - describe('hook', function () { + describe('bidResponse hook', function () { let mockBids; let bids; @@ -84,7 +86,7 @@ describe('bid overrides', function () { let next = (adUnitCode, bid) => { bids.push(bid); }; - addBidResponseHook.bind(overrides)(next, bid.adUnitCode, bid) + addBidResponseHook.bind(overrides)(next, bid.adUnitCode, bid); }); } @@ -141,4 +143,51 @@ describe('bid overrides', function () { expect(bids[1].cpm).to.equal(2); }); }); + + describe('bidRequests hook', function () { + let mockBidRequests; + let bidderRequests; + + beforeEach(function () { + let baseBidderRequest = { + 'bidderCode': 'rubicon', + 'bids': [{ + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'mediaType': 'banner', + 'source': 'client', + 'currency': 'USD', + 'cpm': 0.5, + 'ttl': 300, + 'netRevenue': false, + 'adUnitCode': '/19968336/header-bid-tag-0' + }] + }; + mockBidRequests = []; + mockBidRequests.push(baseBidderRequest); + mockBidRequests.push(Object.assign({}, baseBidderRequest, { + bidderCode: 'appnexus' + })); + + bidderRequests = []; + }); + + function run(overrides) { + let next = (b) => { + bidderRequests = b; + }; + addBidderRequestsHook.bind(overrides)(next, mockBidRequests); + } + + it('should allow us to exclude bidders', function () { + run({ + enabled: true, + bidders: ['appnexus'] + }); + + expect(bidderRequests.length).to.equal(1); + expect(bidderRequests[0].bidderCode).to.equal('appnexus'); + }); + }); }); diff --git a/test/spec/e2e/banner/basic_banner_ad.spec.js b/test/spec/e2e/banner/basic_banner_ad.spec.js index fa2d5af142f..7088bd3eade 100644 --- a/test/spec/e2e/banner/basic_banner_ad.spec.js +++ b/test/spec/e2e/banner/basic_banner_ad.spec.js @@ -1,27 +1,28 @@ const expect = require('chai').expect; -const { host, protocol } = require('../../../helpers/testing-utils'); +const { host, protocol, waitForElement, switchFrame } = require('../../../helpers/testing-utils'); const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/banner.html?pbjs_debug=true`; -const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/header-bid-tag-0_0"]'; +const CREATIVE_IFRAME_ID = 'google_ads_iframe_/19968336/header-bid-tag-0_0'; +const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="' + CREATIVE_IFRAME_ID + '"]'; const EXPECTED_TARGETING_KEYS = { 'hb_format': 'banner', 'hb_source': 'client', - 'hb_pb': '0.60', - 'hb_bidder': 'rubicon', - 'hb_format_rubicon': 'banner', - 'hb_source_rubicon': 'client', - 'hb_pb_rubicon': '0.60', - 'hb_bidder_rubicon': 'rubicon' + 'hb_pb': '0.50', + 'hb_bidder': 'appnexus', + 'hb_format_appnexus': 'banner', + 'hb_source_appnexus': 'client', + 'hb_pb_appnexus': '0.50', + 'hb_bidder_appnexus': 'appnexus' }; describe('Prebid.js Banner Ad Unit Test', function () { + this.retries(3); before(function loadTestPage() { - browser.url(TEST_PAGE_URL).pause(3000); + browser.url(TEST_PAGE_URL); + browser.pause(3000); try { - browser.waitForExist(CREATIVE_IFRAME_CSS_SELECTOR, 2000); - const creativeIframe = $(CREATIVE_IFRAME_CSS_SELECTOR).value; - browser.frame(creativeIframe); + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 3000); } catch (e) { // If creative Iframe didn't load, repeat the steps again! // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js @@ -29,21 +30,21 @@ describe('Prebid.js Banner Ad Unit Test', function () { } }); - // TODO: Add below test again. Removed the test since we are testing only for appnexus endpoint now and appnexus adapter does not set AdserverTargetting. + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('div-gpt-ad-1460505748561-1'); + }); + const targetingKeys = result['div-gpt-ad-1460505748561-1']; - // it('should load the targeting keys with correct values', function () { - // const result = browser.execute(function () { - // return window.top.pbjs.getAdserverTargeting('div-gpt-ad-1460505748561-1'); - // }); - // const targetingKeys = result.value['div-gpt-ad-1460505748561-1']; - - // expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); - // expect(targetingKeys.hb_adid).to.be.a('string'); - // expect(targetingKeys.hb_adid_rubicon).to.be.a('string'); - // expect(targetingKeys.hb_size).to.satisfy((size) => size === '300x250' || '300x600'); - // }); + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + expect(targetingKeys.hb_size).to.satisfy((size) => size === '300x250' || '300x600'); + }); it('should render the Banner Ad on the page', function () { - expect(browser.isVisible('body > div[class="GoogleActiveViewElement"] > a > img')).to.be.true; + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR, CREATIVE_IFRAME_ID); + const ele = $('body > div[class="GoogleActiveViewElement"] > a > img'); + expect(ele.isExisting()).to.be.true; }); }); diff --git a/test/spec/e2e/instream/basic_instream_video_ad.spec.js b/test/spec/e2e/instream/basic_instream_video_ad.spec.js new file mode 100644 index 00000000000..b712f90bf63 --- /dev/null +++ b/test/spec/e2e/instream/basic_instream_video_ad.spec.js @@ -0,0 +1,48 @@ +const expect = require('chai').expect; +const { host, protocol, waitForElement } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/instream.html?pbjs_debug=true`; +const ALERT_BOX_CSS_SELECTOR = 'div[id="event-window"] > p[id="statusText"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_format: 'video', + hb_source: 'client', + hb_size: '640x480', + hb_pb: '10.00', + hb_bidder: 'appnexus', + hb_format_appnexus: 'video', + hb_source_appnexus: 'client', + hb_size_appnexus: '640x480', + hb_pb_appnexus: '10.00', + hb_bidder_appnexus: 'appnexus' +}; + +describe('Prebid.js Instream Video Ad Test', function () { + this.retries(3); + before(function loadTestPage() { + browser.url(TEST_PAGE_URL); + browser.pause(5000); + try { + waitForElement(ALERT_BOX_CSS_SELECTOR, 3000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.top.pbjs.getAdserverTargeting('video1'); + }); + + const targetingKeys = result['video1']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + expect(targetingKeys.hb_uuid).to.be.a('string'); + expect(targetingKeys.hb_cache_id).to.be.a('string'); + expect(targetingKeys.hb_uuid_appnexus).to.be.a('string'); + expect(targetingKeys.hb_cache_id_appnexus).to.be.a('string'); + }); +}); diff --git a/test/spec/e2e/longform/basic_w_bidderSettings.spec.js b/test/spec/e2e/longform/basic_w_bidderSettings.spec.js index 06413fb809a..d3443558316 100644 --- a/test/spec/e2e/longform/basic_w_bidderSettings.spec.js +++ b/test/spec/e2e/longform/basic_w_bidderSettings.spec.js @@ -1,9 +1,6 @@ -const includes = require('core-js/library/fn/array/includes'); +const includes = require('core-js-pure/features/array/includes.js'); const expect = require('chai').expect; -const testServer = require('../../../helpers/testing-utils'); - -const host = testServer.host; -const protocol = testServer.protocol; +const { host, protocol, waitForElement } = require('../../../helpers/testing-utils'); const validDurations = ['15s', '30s']; const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; @@ -14,20 +11,20 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads not using requireExactDuration field', function() { this.retries(3); it('process the bids successfully', function() { - browser - .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_bidderSettings.html?pbjs_debug=true') - .pause(10000); + browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_bidderSettings.html?pbjs_debug=true'); + browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - browser.waitForExist(loadPrebidBtnXpath); - $(loadPrebidBtnXpath).click(); - browser.pause(3000); + waitForElement(loadPrebidBtnXpath, 3000); + const prebidBtn = $(loadPrebidBtnXpath); + prebidBtn.click(); + browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - browser.waitForExist(listOfCpmsXpath); + waitForElement(listOfCpmsXpath, 3000); let listOfCpms = $$(listOfCpmsXpath); let listOfCats = $$(listOfCategoriesXpath); @@ -47,8 +44,8 @@ describe('longform ads not using requireExactDuration field', function() { it('formats the targeting keys properly', function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - browser.waitForExist(listOfKeyElementsXpath); - browser.waitForExist(listOfKeyValuesXpath); + waitForElement(listOfKeyElementsXpath); + waitForElement(listOfKeyValuesXpath); let listOfKeyElements = $$(listOfKeyElementsXpath); let listOfKeyValues = $$(listOfKeyValuesXpath); diff --git a/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js b/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js index 82310738246..9abe7295027 100644 --- a/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js +++ b/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js @@ -1,9 +1,6 @@ -const includes = require('core-js/library/fn/array/includes'); +const includes = require('core-js-pure/features/array/includes.js'); const expect = require('chai').expect; -const testServer = require('../../../helpers/testing-utils'); - -const host = testServer.host; -const protocol = testServer.protocol; +const { host, protocol, waitForElement } = require('../../../helpers/testing-utils'); const validDurations = ['15s', '30s']; const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; @@ -14,20 +11,20 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads using custom adserver translation file', function() { this.retries(3); it('process the bids successfully', function() { - browser - .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_custom_adserver_translation.html?pbjs_debug=true') - .pause(10000); + browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_custom_adserver_translation.html?pbjs_debug=true'); + browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - browser.waitForExist(loadPrebidBtnXpath); - $(loadPrebidBtnXpath).click(); - browser.pause(3000); + waitForElement(loadPrebidBtnXpath, 3000); + const prebidBtn = $(loadPrebidBtnXpath); + prebidBtn.click(); + browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - browser.waitForExist(listOfCpmsXpath); + waitForElement(listOfCpmsXpath); let listOfCpms = $$(listOfCpmsXpath); let listOfCats = $$(listOfCategoriesXpath); @@ -47,8 +44,8 @@ describe('longform ads using custom adserver translation file', function() { it('formats the targeting keys properly', function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - browser.waitForExist(listOfKeyElementsXpath); - browser.waitForExist(listOfKeyValuesXpath); + waitForElement(listOfKeyElementsXpath); + waitForElement(listOfKeyValuesXpath); let listOfKeyElements = $$(listOfKeyElementsXpath); let listOfKeyValues = $$(listOfKeyValuesXpath); diff --git a/test/spec/e2e/longform/basic_w_priceGran.spec.js b/test/spec/e2e/longform/basic_w_priceGran.spec.js index 696b7fa3359..5658595eef7 100644 --- a/test/spec/e2e/longform/basic_w_priceGran.spec.js +++ b/test/spec/e2e/longform/basic_w_priceGran.spec.js @@ -1,9 +1,6 @@ -const includes = require('core-js/library/fn/array/includes'); +const includes = require('core-js-pure/features/array/includes.js'); const expect = require('chai').expect; -const testServer = require('../../../helpers/testing-utils'); - -const host = testServer.host; -const protocol = testServer.protocol; +const { host, protocol, waitForElement } = require('../../../helpers/testing-utils'); const validDurations = ['15s', '30s']; const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; @@ -14,20 +11,20 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads not using requireExactDuration field', function() { this.retries(3); it('process the bids successfully', function() { - browser - .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_priceGran.html?pbjs_debug=true') - .pause(10000); + browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_priceGran.html?pbjs_debug=true'); + browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - browser.waitForExist(loadPrebidBtnXpath); - $(loadPrebidBtnXpath).click(); - browser.pause(3000); + waitForElement(loadPrebidBtnXpath); + const prebidBtn = $(loadPrebidBtnXpath); + prebidBtn.click(); + browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - browser.waitForExist(listOfCpmsXpath); + waitForElement(listOfCpmsXpath); let listOfCpms = $$(listOfCpmsXpath); let listOfCats = $$(listOfCategoriesXpath); @@ -47,8 +44,8 @@ describe('longform ads not using requireExactDuration field', function() { it('formats the targeting keys properly', function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - browser.waitForExist(listOfKeyElementsXpath); - browser.waitForExist(listOfKeyValuesXpath); + waitForElement(listOfKeyElementsXpath); + waitForElement(listOfKeyValuesXpath); let listOfKeyElements = $$(listOfKeyElementsXpath); let listOfKeyValues = $$(listOfKeyValuesXpath); diff --git a/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js b/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js index 224ff1cbc34..886daa3e320 100644 --- a/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js +++ b/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js @@ -1,9 +1,6 @@ -const includes = require('core-js/library/fn/array/includes'); +const includes = require('core-js-pure/features/array/includes.js'); const expect = require('chai').expect; -const testServer = require('../../../helpers/testing-utils'); - -const host = testServer.host; -const protocol = testServer.protocol; +const { host, protocol, waitForElement } = require('../../../helpers/testing-utils'); const validDurations = ['15s', '30s']; const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; @@ -14,20 +11,20 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads using requireExactDuration field', function() { this.retries(3); it('process the bids successfully', function() { - browser - .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_requireExactDuration.html?pbjs_debug=true') - .pause(10000); + browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_requireExactDuration.html?pbjs_debug=true'); + browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - browser.waitForExist(loadPrebidBtnXpath); - $(loadPrebidBtnXpath).click(); - browser.pause(3000); + waitForElement(loadPrebidBtnXpath); + const prebidBtn = $(loadPrebidBtnXpath); + prebidBtn.click(); + browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - browser.waitForExist(listOfCpmsXpath); + waitForElement(listOfCpmsXpath); let listOfCpms = $$(listOfCpmsXpath); let listOfCats = $$(listOfCategoriesXpath); @@ -47,8 +44,8 @@ describe('longform ads using requireExactDuration field', function() { it('formats the targeting keys properly', function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - browser.waitForExist(listOfKeyElementsXpath); - browser.waitForExist(listOfKeyValuesXpath); + waitForElement(listOfKeyElementsXpath); + waitForElement(listOfKeyValuesXpath); let listOfKeyElements = $$(listOfKeyElementsXpath); let listOfKeyValues = $$(listOfKeyValuesXpath); diff --git a/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js b/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js index 95237366d0e..e19f90b8c39 100644 --- a/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js +++ b/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js @@ -1,9 +1,6 @@ -const includes = require('core-js/library/fn/array/includes'); +const includes = require('core-js-pure/features/array/includes.js'); const expect = require('chai').expect; -const testServer = require('../../../helpers/testing-utils'); - -const host = testServer.host; -const protocol = testServer.protocol; +const { host, protocol, waitForElement } = require('../../../helpers/testing-utils'); const validDurations = ['15s', '30s']; const validCpms = ['15.00', '14.00', '13.00', '10.00']; @@ -13,19 +10,19 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads without using brandCategoryExclusion', function() { this.retries(3); it('process the bids successfully', function() { - browser - .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_brandCategoryExclusion.html?pbjs_debug=true') - .pause(10000); + browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_brandCategoryExclusion.html?pbjs_debug=true'); + browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - browser.waitForExist(loadPrebidBtnXpath); - $(loadPrebidBtnXpath).click(); - browser.pause(3000); + waitForElement(loadPrebidBtnXpath); + const prebidBtn = $(loadPrebidBtnXpath); + prebidBtn.click(); + browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - browser.waitForExist(listOfCpmsXpath); + waitForElement(listOfCpmsXpath); let listOfCpms = $$(listOfCpmsXpath); let listOfDuras = $$(listOfDurationsXpath); @@ -42,8 +39,8 @@ describe('longform ads without using brandCategoryExclusion', function() { it('formats the targeting keys properly', function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - browser.waitForExist(listOfKeyElementsXpath); - browser.waitForExist(listOfKeyValuesXpath); + waitForElement(listOfKeyElementsXpath); + waitForElement(listOfKeyValuesXpath); let listOfKeyElements = $$(listOfKeyElementsXpath); let listOfKeyValues = $$(listOfKeyValuesXpath); diff --git a/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js b/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js index 6b628067138..cb1bcda93ff 100644 --- a/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js +++ b/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js @@ -1,9 +1,6 @@ -const includes = require('core-js/library/fn/array/includes'); +const includes = require('core-js-pure/features/array/includes.js'); const expect = require('chai').expect; -const testServer = require('../../../helpers/testing-utils'); - -const host = testServer.host; -const protocol = testServer.protocol; +const { host, protocol, waitForElement } = require('../../../helpers/testing-utils'); const validDurations = ['15s', '30s']; const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; @@ -14,20 +11,20 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads not using requireExactDuration field', function() { this.retries(3); it('process the bids successfully', function() { - browser - .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_requireExactDuration.html?pbjs_debug=true') - .pause(10000); + browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_requireExactDuration.html?pbjs_debug=true'); + browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - browser.waitForExist(loadPrebidBtnXpath); - $(loadPrebidBtnXpath).click(); - browser.pause(3000); + waitForElement(loadPrebidBtnXpath); + const prebidBtn = $(loadPrebidBtnXpath); + prebidBtn.click(); + browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - browser.waitForExist(listOfCpmsXpath); + waitForElement(listOfCpmsXpath); let listOfCpms = $$(listOfCpmsXpath); let listOfCats = $$(listOfCategoriesXpath); @@ -47,8 +44,8 @@ describe('longform ads not using requireExactDuration field', function() { it('formats the targeting keys properly', function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - browser.waitForExist(listOfKeyElementsXpath); - browser.waitForExist(listOfKeyValuesXpath); + waitForElement(listOfKeyElementsXpath); + waitForElement(listOfKeyValuesXpath); let listOfKeyElements = $$(listOfKeyElementsXpath); let listOfKeyValues = $$(listOfKeyValuesXpath); diff --git a/test/spec/e2e/modules/e2e_bidderSettings.spec.js b/test/spec/e2e/modules/e2e_bidderSettings.spec.js new file mode 100644 index 00000000000..2c0ba484654 --- /dev/null +++ b/test/spec/e2e/modules/e2e_bidderSettings.spec.js @@ -0,0 +1,61 @@ +const expect = require('chai').expect; +const { host, protocol, waitForElement, switchFrame } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/bidderSettings.html?pbjs_debug=true`; +const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/header-bid-tag-0_0"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_source: 'client', + hb_source_appnexus: 'client', + hb_pb_appnexus: '10.00', + hb_native_title_appn: 'This is a Prebid Native Creative', + hb_native_linkurl: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_format: 'native', + hb_native_brand: 'Prebid.org', + hb_size: '0x0', + hb_bidder_appnexus: 'appnexus', + hb_native_linkurl_ap: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_native_title: 'This is a Prebid Native Creative', + hb_pb: '10.00', + hb_native_brand_appn: 'Prebid.org', + hb_bidder: 'appnexus', + hb_format_appnexus: 'native', + hb_size_appnexus: '0x0' +} + +describe('Prebid.js Bidder Settings Ad Unit Test', function () { + this.retries(3); + before(function loadTestPage() { + browser.url(TEST_PAGE_URL); + browser.pause(3000); + try { + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 2000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); + }); + + const targetingKeys = result['/19968336/prebid_native_example_2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_native_body).to.be.a('string'); + expect(targetingKeys.hb_native_body_appne).to.be.a('string'); + expect(targetingKeys.hb_native_icon).to.be.a('string'); + expect(targetingKeys.hb_native_icon_appne).to.be.a('string'); + expect(targetingKeys.hb_native_image).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + }); + + it('should render the Banner Ad on the page', function () { + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const ele = $('body > div[class="GoogleActiveViewElement"] > a > img'); + expect(ele.isExisting()).to.be.true; + }); +}); diff --git a/test/spec/e2e/modules/e2e_consent_mgt_gdpr.spec.js b/test/spec/e2e/modules/e2e_consent_mgt_gdpr.spec.js new file mode 100644 index 00000000000..4e0e7da49ea --- /dev/null +++ b/test/spec/e2e/modules/e2e_consent_mgt_gdpr.spec.js @@ -0,0 +1,61 @@ +const expect = require('chai').expect; +const { host, protocol, switchFrame, waitForElement } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/consent_mgt_gdpr.html?pbjs_debug=true`; +const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/header-bid-tag-0_0"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_source: 'client', + hb_source_appnexus: 'client', + hb_pb_appnexus: '10.00', + hb_native_title_appn: 'This is a Prebid Native Creative', + hb_native_linkurl: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_format: 'native', + hb_native_brand: 'Prebid.org', + hb_size: '0x0', + hb_bidder_appnexus: 'appnexus', + hb_native_linkurl_ap: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_native_title: 'This is a Prebid Native Creative', + hb_pb: '10.00', + hb_native_brand_appn: 'Prebid.org', + hb_bidder: 'appnexus', + hb_format_appnexus: 'native', + hb_size_appnexus: '0x0' +}; + +describe('Prebid.js GDPR Ad Unit Test', function () { + this.retries(3); + before(function loadTestPage() { + browser.url(TEST_PAGE_URL); + browser.pause(3000); + try { + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 2000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); + }); + + const targetingKeys = result['/19968336/prebid_native_example_2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_native_body).to.be.a('string'); + expect(targetingKeys.hb_native_body_appne).to.be.a('string'); + expect(targetingKeys.hb_native_icon).to.be.a('string'); + expect(targetingKeys.hb_native_icon_appne).to.be.a('string'); + expect(targetingKeys.hb_native_image).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + }); + + it('should render the Banner Ad on the page', function () { + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const ele = $('body > div[class="GoogleActiveViewElement"] > a > img'); + expect(ele.isExisting()).to.be.true; + }); +}); diff --git a/test/spec/e2e/modules/e2e_currency.spec.js b/test/spec/e2e/modules/e2e_currency.spec.js new file mode 100644 index 00000000000..8d8da5c5d45 --- /dev/null +++ b/test/spec/e2e/modules/e2e_currency.spec.js @@ -0,0 +1,61 @@ +const expect = require('chai').expect; +const { host, protocol, waitForElement, switchFrame } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/currency.html?pbjs_debug=true`; +const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/header-bid-tag-0_0"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_source: 'client', + hb_source_appnexus: 'client', + hb_native_title_appn: 'This is a Prebid Native Creative', + hb_native_linkurl: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_format: 'native', + hb_native_brand: 'Prebid.org', + hb_size: '0x0', + hb_bidder_appnexus: 'appnexus', + hb_native_linkurl_ap: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_native_title: 'This is a Prebid Native Creative', + hb_native_brand_appn: 'Prebid.org', + hb_bidder: 'appnexus', + hb_format_appnexus: 'native', + hb_size_appnexus: '0x0' +} + +describe('Prebid.js Currency Ad Unit Test', function () { + this.retries(3); + before(function loadTestPage() { + browser.url(TEST_PAGE_URL); + browser.pause(5000); + try { + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 2000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); + }); + + const targetingKeys = result['/19968336/prebid_native_example_2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_pb).to.be.a('string'); + expect(targetingKeys.hb_pb_appnexus).to.be.a('string'); + expect(targetingKeys.hb_native_body).to.be.a('string'); + expect(targetingKeys.hb_native_body_appne).to.be.a('string'); + expect(targetingKeys.hb_native_icon).to.be.a('string'); + expect(targetingKeys.hb_native_icon_appne).to.be.a('string'); + expect(targetingKeys.hb_native_image).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + }); + + it('should render the Banner Ad on the page', function () { + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const ele = $('body > div[class="GoogleActiveViewElement"] > a > img'); + expect(ele.isExisting()).to.be.true; + }); +}); diff --git a/test/spec/e2e/modules/e2e_priceGranularity.spec.js b/test/spec/e2e/modules/e2e_priceGranularity.spec.js new file mode 100644 index 00000000000..157961d69ea --- /dev/null +++ b/test/spec/e2e/modules/e2e_priceGranularity.spec.js @@ -0,0 +1,61 @@ +const expect = require('chai').expect; +const { host, protocol, switchFrame, waitForElement } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/priceGranularity.html?pbjs_debug=true`; +const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/header-bid-tag-0_0"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_source: 'client', + hb_source_appnexus: 'client', + hb_pb_appnexus: '10.00', + hb_native_title_appn: 'This is a Prebid Native Creative', + hb_native_linkurl: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_format: 'native', + hb_native_brand: 'Prebid.org', + hb_size: '0x0', + hb_bidder_appnexus: 'appnexus', + hb_native_linkurl_ap: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_native_title: 'This is a Prebid Native Creative', + hb_pb: '10.00', + hb_native_brand_appn: 'Prebid.org', + hb_bidder: 'appnexus', + hb_format_appnexus: 'native', + hb_size_appnexus: '0x0' +} + +describe('Prebid.js Price Granularity Ad Unit Test', function () { + this.retries(3); + before(function loadTestPage() { + browser.url(TEST_PAGE_URL); + browser.pause(3000); + try { + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 2000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); + }); + + const targetingKeys = result['/19968336/prebid_native_example_2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_native_body).to.be.a('string'); + expect(targetingKeys.hb_native_body_appne).to.be.a('string'); + expect(targetingKeys.hb_native_icon).to.be.a('string'); + expect(targetingKeys.hb_native_icon_appne).to.be.a('string'); + expect(targetingKeys.hb_native_image).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + }); + + it('should render the Banner Ad on the page', function () { + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const ele = $('body > div[class="GoogleActiveViewElement"] > a > img'); + expect(ele.isExisting()).to.be.true; + }); +}); diff --git a/test/spec/e2e/modules/e2e_sizeConfig.spec.js b/test/spec/e2e/modules/e2e_sizeConfig.spec.js new file mode 100644 index 00000000000..a37e6d49122 --- /dev/null +++ b/test/spec/e2e/modules/e2e_sizeConfig.spec.js @@ -0,0 +1,61 @@ +const expect = require('chai').expect; +const { host, protocol, switchFrame, waitForElement } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/sizeConfig.html?pbjs_debug=true`; +const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/header-bid-tag-0_0"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_source: 'client', + hb_source_appnexus: 'client', + hb_pb_appnexus: '10.00', + hb_native_title_appn: 'This is a Prebid Native Creative', + hb_native_linkurl: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_format: 'native', + hb_native_brand: 'Prebid.org', + hb_size: '0x0', + hb_bidder_appnexus: 'appnexus', + hb_native_linkurl_ap: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_native_title: 'This is a Prebid Native Creative', + hb_pb: '10.00', + hb_native_brand_appn: 'Prebid.org', + hb_bidder: 'appnexus', + hb_format_appnexus: 'native', + hb_size_appnexus: '0x0' +} + +describe('Prebid.js Size Config Ad Unit Test', function () { + this.retries(3); + before(function loadTestPage() { + browser.url(TEST_PAGE_URL); + browser.pause(3000); + try { + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 2000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); + }); + + const targetingKeys = result['/19968336/prebid_native_example_2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_native_body).to.be.a('string'); + expect(targetingKeys.hb_native_body_appne).to.be.a('string'); + expect(targetingKeys.hb_native_icon).to.be.a('string'); + expect(targetingKeys.hb_native_icon_appne).to.be.a('string'); + expect(targetingKeys.hb_native_image).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + }); + + it('should render the Banner Ad on the page', function () { + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const ele = $('body > div[class="GoogleActiveViewElement"] > a > img'); + expect(ele.isExisting()).to.be.true; + }); +}); diff --git a/test/spec/e2e/modules/e2e_userSync.spec.js b/test/spec/e2e/modules/e2e_userSync.spec.js new file mode 100644 index 00000000000..d945bfd3278 --- /dev/null +++ b/test/spec/e2e/modules/e2e_userSync.spec.js @@ -0,0 +1,61 @@ +const expect = require('chai').expect; +const { host, protocol, switchFrame, waitForElement } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/userSync.html?pbjs_debug=true`; +const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/header-bid-tag-0_0"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_source: 'client', + hb_source_appnexus: 'client', + hb_pb_appnexus: '10.00', + hb_native_title_appn: 'This is a Prebid Native Creative', + hb_native_linkurl: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_format: 'native', + hb_native_brand: 'Prebid.org', + hb_size: '0x0', + hb_bidder_appnexus: 'appnexus', + hb_native_linkurl_ap: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_native_title: 'This is a Prebid Native Creative', + hb_pb: '10.00', + hb_native_brand_appn: 'Prebid.org', + hb_bidder: 'appnexus', + hb_format_appnexus: 'native', + hb_size_appnexus: '0x0' +} + +describe('Prebid.js User Sync Ad Unit Test', function () { + this.retries(3); + before(function loadTestPage() { + browser.url(TEST_PAGE_URL); + browser.pause(3000); + try { + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 2000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); + }); + + const targetingKeys = result['/19968336/prebid_native_example_2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_native_body).to.be.a('string'); + expect(targetingKeys.hb_native_body_appne).to.be.a('string'); + expect(targetingKeys.hb_native_icon).to.be.a('string'); + expect(targetingKeys.hb_native_icon_appne).to.be.a('string'); + expect(targetingKeys.hb_native_image).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + }); + + it('should render the Banner Ad on the page', function () { + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const ele = $('body > div[class="GoogleActiveViewElement"] > a > img'); + expect(ele.isExisting()).to.be.true; + }); +}); diff --git a/test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js b/test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js new file mode 100644 index 00000000000..a67e2bd6db5 --- /dev/null +++ b/test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js @@ -0,0 +1,67 @@ +const expect = require('chai').expect; +const { host, protocol, waitForElement, switchFrame } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/multiple_bidders.html?pbjs_debug=true`; +const CREATIVE_BANNER_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/prebid_multiformat_test_0"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_source: 'client', + hb_source_adasta: 'client', + hb_pb_adasta: '10.00', + hb_native_title_adas: 'This is a Prebid Native Creative', + hb_native_linkurl: 'http://prebid.org/dev-docs/show-multi-format-ads.html', + hb_format: 'native', + hb_native_brand: 'Prebid.org', + hb_size: '0x0', + hb_bidder_adasta: 'adasta', + hb_native_linkurl_ad: 'http://prebid.org/dev-docs/show-multi-format-ads.html', + hb_native_title: 'This is a Prebid Native Creative', + hb_pb: '10.00', + hb_native_brand_adas: 'Prebid.org', + hb_bidder: 'adasta', + hb_format_adasta: 'native', + hb_size_adasta: '0x0' +}; + +describe('Prebid.js Multiple Bidder Ad Unit Test', function () { + this.retries(3); + before(function loadTestPage() { + browser.url(TEST_PAGE_URL); + browser.pause(5000); + try { + waitForElement(CREATIVE_BANNER_CSS_SELECTOR, 3000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('div-banner-native-2'); + }); + + const targetingKeys = result['div-banner-native-2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_native_image).to.be.a('string'); + expect(targetingKeys.hb_native_image_adas).to.be.a('string'); + expect(targetingKeys.hb_adid_adasta).to.be.a('string'); + }); + + it('should render the Banner Ad on the page', function () { + switchFrame(CREATIVE_BANNER_CSS_SELECTOR); + let ele = $('body > div[class="GoogleActiveViewElement"] > a > img'); + expect(ele.isExisting()).to.be.true; + }); + + // it('should render the native ad on the page', function () { + // browser.switchToParentFrame(); + // waitForElement(CREATIVE_NATIVE_CSS_SELECTOR, 3000); + // switchFrame(CREATIVE_NATIVE_CSS_SELECTOR); + + // let ele = $('body > div[class="GoogleActiveViewElement"] > div[class="card"]'); + // expect(ele.isExisting()).to.be.true; + // }); +}); diff --git a/test/spec/e2e/native/basic_native_ad.spec.js b/test/spec/e2e/native/basic_native_ad.spec.js index ed09228b532..418bcf271a3 100644 --- a/test/spec/e2e/native/basic_native_ad.spec.js +++ b/test/spec/e2e/native/basic_native_ad.spec.js @@ -1,5 +1,5 @@ const expect = require('chai').expect; -const { host, protocol } = require('../../../helpers/testing-utils'); +const { host, protocol, switchFrame, waitForElement } = require('../../../helpers/testing-utils'); const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/native.html`; const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/prebid_native_example_1_0"]'; @@ -24,10 +24,12 @@ const EXPECTED_TARGETING_KEYS = { } describe('Prebid.js Native Ad Unit Test', function () { + this.retries(3); before(function loadTestPage() { - browser.url(TEST_PAGE_URL).pause(3000); + browser.url(TEST_PAGE_URL); + browser.pause(3000); try { - browser.waitForExist(CREATIVE_IFRAME_CSS_SELECTOR, 2000); + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 2000); } catch (e) { // If creative Iframe didn't load, repeat the steps again! // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js @@ -40,7 +42,7 @@ describe('Prebid.js Native Ad Unit Test', function () { return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); }); - const targetingKeys = result.value['/19968336/prebid_native_example_2']; + const targetingKeys = result['/19968336/prebid_native_example_2']; expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); expect(targetingKeys.hb_adid).to.be.a('string'); expect(targetingKeys.hb_native_body).to.be.a('string'); @@ -52,8 +54,8 @@ describe('Prebid.js Native Ad Unit Test', function () { }); it('should render the native ad on the page', function () { - const creativeIframe = $(CREATIVE_IFRAME_CSS_SELECTOR).value; - browser.frame(creativeIframe); - expect(browser.isVisible('body > div[class="GoogleActiveViewElement"] > div[class="card"]')).to.be.true; + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const ele = $('body > div[class="GoogleActiveViewElement"] > div[class="card"]'); + expect(ele.isExisting()).to.be.true; }); }); diff --git a/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js b/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js index 15b0bb29309..0240b094f5e 100644 --- a/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js +++ b/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js @@ -1,5 +1,5 @@ const expect = require('chai').expect; -const { host, protocol } = require('../../../helpers/testing-utils'); +const { host, protocol, waitForElement, switchFrame } = require('../../../helpers/testing-utils'); const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/outstream.html`; const CREATIVE_IFRAME_CSS_SELECTOR = 'div[id="video_ad_unit_1"] > div:nth-child(2) > iframe:nth-child(1)'; @@ -20,13 +20,15 @@ const EXPECTED_TARGETING_KEYS = { }; describe('Prebid.js Outstream Video Ad Test', function () { + this.retries(3); before(function loadTestPage() { - browser - .url(TEST_PAGE_URL) - .scroll(0, 300) - .pause(3000); + browser.url(TEST_PAGE_URL); + browser.execute(function () { + return window.scrollBy(0, 300); + }); + browser.pause(3000); try { - browser.waitForExist(CREATIVE_IFRAME_CSS_SELECTOR, 5000); + waitForElement(CREATIVE_IFRAME_CSS_SELECTOR, 5000); } catch (e) { // If creative Iframe didn't load, repeat the steps again! // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js @@ -39,15 +41,19 @@ describe('Prebid.js Outstream Video Ad Test', function () { return window.pbjs.getAdserverTargeting('video_ad_unit_2'); }); - const targetingKeys = result.value['video_ad_unit_2']; + const targetingKeys = result['video_ad_unit_2']; expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); expect(targetingKeys.hb_adid).to.be.a('string'); expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); }); it('should render the native ad on the page', function() { - const creativeIframe = $(CREATIVE_IFRAME_CSS_SELECTOR).value; - browser.frame(creativeIframe); - expect(browser.isVisible('body > div[class="video-js"] > video')); + // skipping test in Edge due to wdio bug: https://github.com/webdriverio/webdriverio/issues/3880 + // the iframe for the video does not have a name property and id is generated automatically... + if (browser.capabilities.browserName !== 'edge') { + switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const ele = $('body > div[id*="an_video_ad_player"] > video'); + expect(ele.isExisting()).to.be.true; + } }); }); diff --git a/test/spec/integration/faker/fixtures.js b/test/spec/integration/faker/fixtures.js index 643b531ad42..a11bd126d61 100644 --- a/test/spec/integration/faker/fixtures.js +++ b/test/spec/integration/faker/fixtures.js @@ -1,5 +1,5 @@ import faker from 'faker'; -import { makeSlot } from './googletag'; +import { makeSlot } from './googletag.js'; export function makeAdSlot(overrides = {}) { return Object.assign(makeSlot( diff --git a/test/spec/integration/faker/googletag.js b/test/spec/integration/faker/googletag.js index b043b5321cb..a0ce04402f7 100644 --- a/test/spec/integration/faker/googletag.js +++ b/test/spec/integration/faker/googletag.js @@ -1,5 +1,5 @@ import faker from 'faker'; -import { randomFive } from './fixtures'; +import { randomFive } from './fixtures.js'; var Slot = function Slot({ code, divId }) { code = code || `ad-slot-code-${randomFive()}`; diff --git a/test/spec/modules/1ad4goodBidAdapter_spec.js b/test/spec/modules/1ad4goodBidAdapter_spec.js index 316a5c13233..b9cd86a4cf7 100644 --- a/test/spec/modules/1ad4goodBidAdapter_spec.js +++ b/test/spec/modules/1ad4goodBidAdapter_spec.js @@ -1,9 +1,9 @@ import { expect } from 'chai'; -import { spec } from 'modules/1ad4goodBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import * as bidderFactory from 'src/adapters/bidderFactory'; -import { deepClone } from 'src/utils'; -import { config } from 'src/config'; +import { spec } from 'modules/1ad4goodBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; const ENDPOINT = 'https://hb.1ad4good.org/prebid'; diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index b18181eae40..d30659791ea 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -1,9 +1,9 @@ import { expect } from 'chai'; -import * as utils from 'src/utils'; -import { config } from 'src/config'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; -import { spec } from 'modules/33acrossBidAdapter'; +import { spec } from 'modules/33acrossBidAdapter.js'; describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; @@ -22,13 +22,11 @@ describe('33acrossBidAdapter:', function () { format: [ { w: 300, - h: 250, - ext: {} + h: 250 }, { w: 728, - h: 90, - ext: {} + h: 90 } ], ext: { @@ -56,7 +54,8 @@ describe('33acrossBidAdapter:', function () { }, regs: { ext: { - gdpr: 0 + gdpr: 0, + us_privacy: null } }, ext: { @@ -75,7 +74,7 @@ describe('33acrossBidAdapter:', function () { return this; }; - this.withViewabiliuty = viewability => { + this.withViewability = viewability => { Object.assign(ttxRequest.imp[0].banner, { ext: { ttx: { viewability } @@ -92,24 +91,78 @@ describe('33acrossBidAdapter:', function () { }); Object.assign(ttxRequest, { regs: { - ext: { gdpr } + ext: Object.assign( + {}, + ttxRequest.regs.ext, + { gdpr } + ) } }); return this; }; + this.withUspConsent = (consent) => { + Object.assign(ttxRequest, { + regs: { + ext: Object.assign( + {}, + ttxRequest.regs.ext, + { us_privacy: consent } + ) + } + }); + + return this; + }; + this.withSite = site => { Object.assign(ttxRequest, { site }); return this; }; + this.withPageUrl = pageUrl => { + Object.assign(ttxRequest.site, { + page: pageUrl + }); + + return this; + }; + + this.withSchain = schain => { + Object.assign(ttxRequest, { + source: { + ext: { + schain + } + } + }); + + return this; + }; + + this.withFormatFloors = floors => { + const format = ttxRequest.imp[0].banner.format.map((fm, i) => { + return Object.assign(fm, { + ext: { + ttx: { + bidfloors: [ floors[i] ] + } + } + }) + }); + + ttxRequest.imp[0].banner.format = format; + + return this; + }; + this.build = () => ttxRequest; } function ServerRequestBuilder() { const serverRequest = { 'method': 'POST', - 'url': END_POINT, + 'url': `${END_POINT}?guid=${SITE_ID}`, 'data': null, 'options': { 'contentType': 'text/plain', @@ -267,7 +320,7 @@ describe('33acrossBidAdapter:', function () { context('when element is fully in view', function() { it('returns 100', function() { const ttxRequest = new TtxRequestBuilder() - .withViewabiliuty({amount: 100}) + .withViewability({amount: 100}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -282,7 +335,7 @@ describe('33acrossBidAdapter:', function () { context('when element is out of view', function() { it('returns 0', function() { const ttxRequest = new TtxRequestBuilder() - .withViewabiliuty({amount: 0}) + .withViewability({amount: 0}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -297,7 +350,7 @@ describe('33acrossBidAdapter:', function () { context('when element is partially in view', function() { it('returns percentage', function() { const ttxRequest = new TtxRequestBuilder() - .withViewabiliuty({amount: 75}) + .withViewability({amount: 75}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -312,8 +365,8 @@ describe('33acrossBidAdapter:', function () { context('when width or height of the element is zero', function() { it('try to use alternative values', function() { const ttxRequest = new TtxRequestBuilder() - .withSizes([{ w: 800, h: 2400, ext: {} }]) - .withViewabiliuty({amount: 25}) + .withSizes([{ w: 800, h: 2400 }]) + .withViewability({amount: 25}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -329,7 +382,7 @@ describe('33acrossBidAdapter:', function () { context('when nested iframes', function() { it('returns \'nm\'', function() { const ttxRequest = new TtxRequestBuilder() - .withViewabiliuty({amount: spec.NON_MEASURABLE}) + .withViewability({amount: spec.NON_MEASURABLE}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -349,7 +402,7 @@ describe('33acrossBidAdapter:', function () { context('when tab is inactive', function() { it('returns 0', function() { const ttxRequest = new TtxRequestBuilder() - .withViewabiliuty({amount: 0}) + .withViewability({amount: 0}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -445,6 +498,242 @@ describe('33acrossBidAdapter:', function () { expect(builtServerRequests).to.deep.equal([serverRequest]); }); }); + + context('when us_privacy consent data exists', function() { + let bidderRequest; + + beforeEach(function() { + bidderRequest = { + uspConsent: 'foo' + } + }); + + it('returns corresponding server requests with us_privacy consent data', function() { + const ttxRequest = new TtxRequestBuilder() + .withUspConsent('foo') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + + it('returns corresponding test server requests with us_privacy consent data', function() { + sandbox.stub(config, 'getConfig').callsFake(() => { + return { + 'url': 'https://foo.com/hb/' + } + }); + + const ttxRequest = new TtxRequestBuilder() + .withUspConsent('foo') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .withUrl('https://foo.com/hb/') + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when us_privacy consent data does not exist', function() { + let bidderRequest; + + beforeEach(function() { + bidderRequest = {}; + }); + + it('returns corresponding server requests with default us_privacy data', function() { + const ttxRequest = new TtxRequestBuilder() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + + it('returns corresponding test server requests with default us_privacy consent data', function() { + sandbox.stub(config, 'getConfig').callsFake(() => { + return { + 'url': 'https://foo.com/hb/' + } + }); + + const ttxRequest = new TtxRequestBuilder() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .withUrl('https://foo.com/hb/') + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when referer value is available', function() { + it('returns corresponding server requests with site.page set', function() { + const bidderRequest = { + refererInfo: { + referer: 'http://foo.com/bar' + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withPageUrl('http://foo.com/bar') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when referer value is not available', function() { + it('returns corresponding server requests without site.page set', function() { + const bidderRequest = { + refererInfo: {} + }; + + const ttxRequest = new TtxRequestBuilder() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when there is schain object in the bidRequest', function() { + it('builds request with schain info in source', function() { + const schainValues = [ + { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'bidderA.com', + 'sid': '00001', + 'hp': 1 + } + ] + }, + { + 'ver': '1.0', + 'complete': 1, + }, + { + 'ver': '1.0', + 'complete': 1, + 'nodes': [] + }, + { + 'ver': '1.0', + 'complete': '1', + 'nodes': [ + { + 'asi': 'bidderA.com', + 'sid': '00001', + 'hp': 1 + } + ] + } + ]; + + schainValues.forEach((schain) => { + bidRequests[0].schain = schain; + + const ttxRequest = new TtxRequestBuilder() + .withSchain(schain) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + }); + + context('when there no schain object is passed', function() { + it('does not set source field', function() { + const ttxRequest = new TtxRequestBuilder() + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when price floor module is not enabled in bidRequest', function() { + it('does not set any bidfloors in ttxRequest', function() { + const ttxRequest = new TtxRequestBuilder() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when price floor module is enabled in bidRequest', function() { + it('does not set any bidfloors in ttxRequest if there is no floor', function() { + bidRequests[0].getFloor = () => ({}); + + const ttxRequest = new TtxRequestBuilder() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + + it('sets bidfloors in ttxRequest if there is a floor', function() { + bidRequests[0].getFloor = ({size, currency, mediaType}) => { + const floor = (size[0] === 300 && size[1] === 250) ? 1.0 : 0.10 + return ( + { + floor, + currency: 'USD' + } + ); + }; + + const ttxRequest = new TtxRequestBuilder() + .withFormatFloors([ 1.0, 0.10 ]) + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); }); describe('interpretResponse', function() { @@ -646,11 +935,11 @@ describe('33acrossBidAdapter:', function () { const expectedSyncs = [ { type: 'iframe', - url: `${syncs[0].url}&gdpr_consent=undefined` + url: `${syncs[0].url}&gdpr_consent=undefined&us_privacy=undefined` }, { type: 'iframe', - url: `${syncs[1].url}&gdpr_consent=undefined` + url: `${syncs[1].url}&gdpr_consent=undefined&us_privacy=undefined` } ] @@ -666,11 +955,11 @@ describe('33acrossBidAdapter:', function () { const expectedSyncs = [ { type: 'iframe', - url: `${syncs[0].url}&gdpr_consent=undefined&gdpr=1` + url: `${syncs[0].url}&gdpr_consent=undefined&us_privacy=undefined&gdpr=1` }, { type: 'iframe', - url: `${syncs[1].url}&gdpr_consent=undefined&gdpr=1` + url: `${syncs[1].url}&gdpr_consent=undefined&us_privacy=undefined&gdpr=1` } ]; @@ -686,11 +975,11 @@ describe('33acrossBidAdapter:', function () { const expectedSyncs = [ { type: 'iframe', - url: `${syncs[0].url}&gdpr_consent=consent123A&gdpr=1` + url: `${syncs[0].url}&gdpr_consent=consent123A&us_privacy=undefined&gdpr=1` }, { type: 'iframe', - url: `${syncs[1].url}&gdpr_consent=consent123A&gdpr=1` + url: `${syncs[1].url}&gdpr_consent=consent123A&us_privacy=undefined&gdpr=1` } ]; @@ -706,11 +995,11 @@ describe('33acrossBidAdapter:', function () { const expectedSyncs = [ { type: 'iframe', - url: `${syncs[0].url}&gdpr_consent=undefined&gdpr=0` + url: `${syncs[0].url}&gdpr_consent=undefined&us_privacy=undefined&gdpr=0` }, { type: 'iframe', - url: `${syncs[1].url}&gdpr_consent=undefined&gdpr=0` + url: `${syncs[1].url}&gdpr_consent=undefined&us_privacy=undefined&gdpr=0` } ]; expect(syncResults).to.deep.equal(expectedSyncs); @@ -725,11 +1014,11 @@ describe('33acrossBidAdapter:', function () { const expectedSyncs = [ { type: 'iframe', - url: `${syncs[0].url}&gdpr_consent=consent123A` + url: `${syncs[0].url}&gdpr_consent=consent123A&us_privacy=undefined` }, { type: 'iframe', - url: `${syncs[1].url}&gdpr_consent=consent123A` + url: `${syncs[1].url}&gdpr_consent=consent123A&us_privacy=undefined` } ]; expect(syncResults).to.deep.equal(expectedSyncs); @@ -744,16 +1033,64 @@ describe('33acrossBidAdapter:', function () { const expectedSyncs = [ { type: 'iframe', - url: `${syncs[0].url}&gdpr_consent=consent123A&gdpr=0` + url: `${syncs[0].url}&gdpr_consent=consent123A&us_privacy=undefined&gdpr=0` }, { type: 'iframe', - url: `${syncs[1].url}&gdpr_consent=consent123A&gdpr=0` + url: `${syncs[1].url}&gdpr_consent=consent123A&us_privacy=undefined&gdpr=0` } ]; expect(syncResults).to.deep.equal(expectedSyncs); }); }); + + context('when there is no usPrivacy data', function() { + it('returns sync urls with undefined consent string as param', function() { + spec.buildRequests(bidRequests); + + const syncResults = spec.getUserSyncs(syncOptions, {}); + const expectedSyncs = [ + { + type: 'iframe', + url: `${syncs[0].url}&gdpr_consent=undefined&us_privacy=undefined` + }, + { + type: 'iframe', + url: `${syncs[1].url}&gdpr_consent=undefined&us_privacy=undefined` + } + ] + + expect(syncResults).to.deep.equal(expectedSyncs); + }) + }); + + context('when there is usPrivacy data', function() { + it('returns sync urls with consent string as param', function() { + spec.buildRequests(bidRequests); + + const syncResults = spec.getUserSyncs(syncOptions, {}, {}, 'foo'); + const expectedSyncs = [ + { + type: 'iframe', + url: `${syncs[0].url}&gdpr_consent=undefined&us_privacy=foo` + }, + { + type: 'iframe', + url: `${syncs[1].url}&gdpr_consent=undefined&us_privacy=foo` + } + ]; + + expect(syncResults).to.deep.equal(expectedSyncs); + }); + }); + + context('when user sync is invoked without a bid request phase', function() { + it('results in an empty syncs array', function() { + const syncResults = spec.getUserSyncs(syncOptions, {}, {}, 'foo'); + + expect(syncResults).to.deep.equal([]); + }); + }); }); }); }); diff --git a/test/spec/modules/7xbidBidAdapter_spec.js b/test/spec/modules/7xbidBidAdapter_spec.js new file mode 100644 index 00000000000..bed2c604349 --- /dev/null +++ b/test/spec/modules/7xbidBidAdapter_spec.js @@ -0,0 +1,160 @@ +import {expect} from 'chai'; +import {spec, _getUrlVars} from 'modules/7xbidBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const BASE_URI = '//bidder.7xbid.com/api/v1/prebid/banner' +const NATIVE_BASE_URI = '//bidder.7xbid.com/api/v1/prebid/native' + +describe('7xbid adapter', function() { + let bidRequests; + let nativeBidRequests; + + beforeEach(function() { + bidRequests = [ + { + bidder: '7xbid', + params: { + placementId: 1425292, + currency: 'USD' + } + } + ] + + nativeBidRequests = [ + { + bidder: '7xbid', + params: { + placementId: 1429695, + currency: 'USD' + }, + nativeParams: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + sponsoredBy: { + required: true + } + } + } + ] + }) + describe('isBidRequestValid', function () { + it('valid bid case', function () { + let validBid = { + bidder: '7xbid', + params: { + placementId: 1425292, + currency: 'USD' + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('invalid bid case: placementId is not passed', function() { + let validBid = { + bidder: '7xbid', + params: { + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }) + + it('invalid bid case: currency is not support', function() { + let validBid = { + bidder: '7xbid', + params: { + placementId: 1108295, + currency: 'AUD' + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }) + }) + + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(BASE_URI); + expect(request.method).to.equal('GET'); + }); + + it('sends native bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(nativeBidRequests)[0]; + expect(request.url).to.equal(NATIVE_BASE_URI); + expect(request.method).to.equal('GET'); + }); + + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + + it('buildRequests function should not modify original nativeBidRequests object', function () { + let originalBidRequests = utils.deepClone(nativeBidRequests); + let request = spec.buildRequests(nativeBidRequests); + expect(nativeBidRequests).to.deep.equal(originalBidRequests); + }); + + it('Request params check', function() { + let request = spec.buildRequests(bidRequests)[0]; + const data = _getUrlVars(request.data) + expect(parseInt(data.placementid)).to.exist.and.to.equal(bidRequests[0].params.placementId); + expect(data.cur).to.exist.and.to.equal(bidRequests[0].params.currency); + }) + + it('Native request params check', function() { + let request = spec.buildRequests(nativeBidRequests)[0]; + const data = _getUrlVars(request.data) + expect(parseInt(data.placementid)).to.exist.and.to.equal(nativeBidRequests[0].params.placementId); + expect(data.cur).to.exist.and.to.equal(nativeBidRequests[0].params.currency); + }) + }) + + describe('interpretResponse', function () { + let response = { + 1425292: + { + 'creativeId': '', + 'cur': 'USD', + 'price': 0.0920, + 'width': 300, + 'height': 250, + 'requestid': '2e42361a6172bf', + 'adm': '' + } + } + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '2e42361a6172bf', + 'cpm': 0.0920, + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'currency': 'USD', + 'creativeId': '', + 'ttl': 700, + 'ad': '' + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({body: response}, request); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].cpm).to.not.equal(null); + expect(result[0].creativeId).to.not.equal(null); + expect(result[0].ad).to.not.equal(null); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + }); + }) +}) diff --git a/test/spec/modules/aardvarkBidAdapter_spec.js b/test/spec/modules/aardvarkBidAdapter_spec.js index 35902ebbe76..9671f961407 100644 --- a/test/spec/modules/aardvarkBidAdapter_spec.js +++ b/test/spec/modules/aardvarkBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import * as utils from 'src/utils'; -import { spec, resetUserSync } from 'modules/aardvarkBidAdapter'; +import * as utils from 'src/utils.js'; +import { spec, resetUserSync } from 'modules/aardvarkBidAdapter.js'; describe('aardvarkAdapterTest', function () { describe('forming valid bidRequests', function () { @@ -229,6 +229,54 @@ describe('aardvarkAdapterTest', function () { }); }); + describe('CCPA conformity', function () { + const bidRequests = [{ + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'TdAx', + }, + adUnitCode: 'aaa', + transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + sizes: [300, 250], + bidId: '1abgs362e0x48a8', + bidderRequestId: '70deaff71c281d', + auctionId: '5c66da22-426a-4bac-b153-77360bef5337' + }]; + + it('should transmit us_privacy data', function () { + const usp = '1NY-'; + const bidderRequest = { + gdprConsent: { + consentString: 'awefasdfwefasdfasd', + gdprApplies: true + }, + refererInfo: { + referer: 'http://example.com' + }, + uspConsent: usp + }; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests.length).to.equal(1); + expect(requests[0].data.gdpr).to.equal(true); + expect(requests[0].data.consent).to.equal('awefasdfwefasdfasd'); + expect(requests[0].data.us_privacy).to.equal(usp); + }); + + it('should not send us_privacy', function () { + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests.length).to.equal(1); + expect(requests[0].data.gdpr).to.be.undefined; + expect(requests[0].data.consent).to.be.undefined; + expect(requests[0].data.us_privacy).to.be.undefined; + }); + }); + describe('interpretResponse', function () { it('should handle bid responses', function () { const serverResponse = { @@ -343,6 +391,15 @@ describe('aardvarkAdapterTest', function () { expect(syncs[0].type).to.equal('iframe'); expect(syncs[0].url).to.equal('https://sync.rtk.io/cs?g=1&c=BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA'); }); + + it('should produce sync url with ccpa params', function () { + resetUserSync(); + + const syncs = spec.getUserSyncs(syncOptions, null, {}, '1YYN'); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://sync.rtk.io/cs?us_privacy=1YYN'); + }); }); describe('reading window.top properties', function () { @@ -419,4 +476,94 @@ describe('aardvarkAdapterTest', function () { }); }); }); + + describe('schain support', function() { + const nodePropsOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + let schainConfig = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'rtk.io', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'first pub', + domain: 'first.com' + }, + { + asi: 'rtk.io', + sid: '5678', + hp: 1, + rid: 'bid-request-2', + name: 'second pub', + domain: 'second.com' + } + ] + }; + + const bidRequests = [{ + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'TdAx', + }, + adUnitCode: 'aaa', + transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + sizes: [300, 250], + bidId: '1abgs362e0x48a8', + bidderRequestId: '70deaff71c281d', + auctionId: '5c66da22-426a-4bac-b153-77360bef5337', + schain: schainConfig, + }]; + + const bidderRequest = { + gdprConsent: undefined, + refererInfo: { + referer: 'https://example.com' + } + }; + + it('should properly serialize schain object with correct delimiters', () => { + const results = spec.buildRequests(bidRequests, bidderRequest); + const numNodes = schainConfig.nodes.length; + + const schain = results[0].data.schain; + + // each node serialization should start with an ! + expect(schain.match(/!/g).length).to.equal(numNodes); + + // 5 commas per node plus 1 for version + expect(schain.match(/,/g).length).to.equal(numNodes * 5 + 1); + }); + + it('should send the proper version for the schain', () => { + const results = spec.buildRequests(bidRequests, bidderRequest); + const schain = decodeURIComponent(results[0].data.schain).split('!'); + const version = schain.shift().split(',')[0]; + expect(version).to.equal(bidRequests[0].schain.ver); + }); + + it('should send the correct value for complete in schain', () => { + const results = spec.buildRequests(bidRequests, bidderRequest); + const schain = decodeURIComponent(results[0].data.schain).split('!'); + const complete = schain.shift().split(',')[1]; + expect(complete).to.equal(String(bidRequests[0].schain.complete)); + }); + + it('should send available params in the right order', () => { + const results = spec.buildRequests(bidRequests, bidderRequest); + const schain = decodeURIComponent(results[0].data.schain).split('!'); + schain.shift(); + + schain.forEach((serializeNode, nodeIndex) => { + const nodeProps = serializeNode.split(','); + nodeProps.forEach((nodeProp, propIndex) => { + const node = schainConfig.nodes[nodeIndex]; + const key = nodePropsOrder[propIndex]; + expect(nodeProp).to.equal(node[key] ? String(node[key]) : ''); + }); + }); + }); + }); }); diff --git a/test/spec/modules/ablidaBidAdapter_spec.js b/test/spec/modules/ablidaBidAdapter_spec.js index 8e0424aee23..0238c14fe5c 100644 --- a/test/spec/modules/ablidaBidAdapter_spec.js +++ b/test/spec/modules/ablidaBidAdapter_spec.js @@ -1,6 +1,6 @@ import {assert, expect} from 'chai'; -import {spec} from 'modules/ablidaBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; +import {spec} from 'modules/ablidaBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; @@ -67,7 +67,8 @@ describe('ablidaBidAdapter', function () { bidId: '2b8c4de0116e54', jaySupported: true, device: 'desktop', - referer: 'www.example.com' + referer: 'www.example.com', + adapterVersion: 2 } }; let serverResponse = { @@ -80,7 +81,8 @@ describe('ablidaBidAdapter', function () { currency: 'EUR', netRevenue: true, ttl: 3000, - ad: '' + ad: '', + nurl: 'https://example.com/some-tracker' }] }; it('should get the correct bid response', function () { @@ -93,10 +95,25 @@ describe('ablidaBidAdapter', function () { currency: 'EUR', netRevenue: true, ttl: 3000, - ad: '' + ad: '', + nurl: 'https://example.com/some-tracker' }]; let result = spec.interpretResponse(serverResponse, bidRequest[0]); expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); }); }); + + describe('onBidWon', function() { + it('Should not ajax call if bid does not contain nurl', function() { + const result = spec.onBidWon({}); + expect(result).to.equal(false) + }) + + it('Should ajax call if bid nurl', function() { + const result = spec.onBidWon({ + nurl: 'https://example.com/some-tracker' + }); + expect(result).to.equal(true) + }) + }) }); diff --git a/test/spec/modules/adWMGAnalyticsAdapter_spec.js b/test/spec/modules/adWMGAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..ab8336c7126 --- /dev/null +++ b/test/spec/modules/adWMGAnalyticsAdapter_spec.js @@ -0,0 +1,176 @@ +import adWMGAnalyticsAdapter from 'modules/adWMGAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('adWMG Analytics', function () { + let timestamp = new Date() - 256; + let auctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + let timeout = 1500; + + let bidTimeoutArgs = [ + { + bidId: '2baa51527bd015', + bidder: 'bidderA', + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' + }, + { + bidId: '6fe3b4c2c23092', + bidder: 'bidderB', + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' + } + ]; + + const bidResponse = { + bidderCode: 'bidderA', + adId: '208750227436c1', + mediaTypes: ['banner'], + cpm: 0.015, + auctionId: auctionId, + responseTimestamp: 1509369418832, + requestTimestamp: 1509369418389, + bidder: 'bidderA', + timeToRespond: 443, + size: '300x250', + width: 300, + height: 250, + }; + + let wonRequest = { + 'adId': '4587fec4900b81', + 'mediaType': 'banner', + 'requestId': '4587fec4900b81', + 'cpm': 1.962, + 'creativeId': 2126, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 302, + 'auctionId': '914bedad-b145-4e46-ba58-51365faea6cb', + 'statusMessage': 'Bid available', + 'responseTimestamp': 1530628534437, + 'requestTimestamp': 1530628534219, + 'bidder': 'bidderB', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'size': [300, 250], + }; + + let expectedBidWonData = { + publisher_id: '5abd0543ba45723db49d97ea', + site: 'test.com', + ad_unit_size: ['300,250'], + ad_unit_type: ['banner'], + c_timeout: 1500, + events: [ + { + status: 'bidWon', + bids: [ + { + bidder: 'bidderB', + auction_id: '914bedad-b145-4e46-ba58-51365faea6cb', + ad_unit_code: 'div-gpt-ad-1438287399331-0', + transaction_id: '', + bid_size: ['300,250'], + bid_type: ['banner'], + time_ms: 256, + cur: 'USD', + price: '1.96', + cur_native: '', + price_native: '' + } + ] + } + ] + } + + let adUnits = [{ + code: 'ad-slot-1', + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'bidderA', + params: { + placement: '1000' + } + }, + { + bidder: 'bidderB', + params: { + placement: '56656' + } + } + ] + }]; + + after(function () { + adWMGAnalyticsAdapter.disableAnalytics(); + }); + + describe('main test flow', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + it('should catch all events', function () { + sinon.spy(adWMGAnalyticsAdapter, 'track'); + + adapterManager.registerAnalyticsAdapter({ + code: 'adWMG', + adapter: adWMGAnalyticsAdapter + }); + + adapterManager.enableAnalytics({ + provider: 'adWMG', + options: { + site: 'test.com', + publisher_id: '5abd0543ba45723db49d97ea' + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {timestamp, auctionId, timeout, adUnits}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(constants.EVENTS.NO_BID, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgs); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_WON, wonRequest); + sinon.assert.callCount(adWMGAnalyticsAdapter.track, 7); + }); + + it('should be two xhr requests', function () { + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_WON, wonRequest); + expect(server.requests.length).to.equal(2); + }); + + it('second request should be bidWon', function () { + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_WON, wonRequest); + expect(JSON.parse(server.requests[1].requestBody).events[0].status).to.equal(expectedBidWonData.events[0].status); + }); + + it('check bidWon data', function () { + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_WON, wonRequest); + let realBidWonData = JSON.parse(server.requests[1].requestBody); + expect(realBidWonData.publisher_id).to.equal(expectedBidWonData.publisher_id); + expect(realBidWonData.site).to.equal(expectedBidWonData.site); + expect(realBidWonData.ad_unit_type[0]).to.equal(expectedBidWonData.ad_unit_type[0]); + expect(realBidWonData.ad_unit_size[0]).to.equal(expectedBidWonData.ad_unit_size[0]); + expect(realBidWonData.events[0].bids[0].bidder).to.equal(expectedBidWonData.events[0].bids[0].bidder); + }); + }); +}); diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 75b476f67af..aee85412104 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -1,23 +1,18 @@ -import adagioAnalyticsAdapter from 'modules/adagioAnalyticsAdapter'; +import adagioAnalyticsAdapter from 'modules/adagioAnalyticsAdapter.js'; import { expect } from 'chai'; -import * as utils from 'src/utils'; +import * as utils from 'src/utils.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); let constants = require('src/constants.json'); describe('adagio analytics adapter', () => { - let xhr; - let requests; - let sandbox + let sandbox; let adagioQueuePushSpy; beforeEach(() => { sandbox = sinon.createSandbox(); - xhr = sandbox.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); sandbox.stub(events, 'getEvents').returns([]); const w = utils.getWindowTop(); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 6c804418d03..52b99f274d5 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,562 +1,586 @@ +import find from 'core-js-pure/features/array/find.js'; import { expect } from 'chai'; -import { getAdagioScript, spec } from 'modules/adagioBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import * as utils from 'src/utils'; - -describe('adagioAdapter', () => { - let utilsMock; - const adapter = newBidder(spec); - const ENDPOINT = 'https://mp.4dex.io/prebid'; - const VERSION = '2.0.0'; +import { _features, internal as adagio, adagioScriptFromLocalStorageCb, getAdagioScript, storage, spec, ENDPOINT, VERSION } from '../../../modules/adagioBidAdapter.js'; +import { loadExternalScript } from '../../../src/adloader.js'; +import * as utils from '../../../src/utils.js'; + +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'adagio' + }, + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code' + }, + sizes: [[300, 250], [300, 600]], + }; + + const request = { + ...defaults.request, + ...options + }; + + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; + }; + + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'adagio', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' + } + }; - beforeEach(function() { - localStorage.removeItem('adagioScript'); - utilsMock = sinon.mock(utils); - }); + const request = { + ...defaults, + ...options + }; - afterEach(function() { - utilsMock.restore(); - }); + this.build = () => request; +}; - describe('inherited functions', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); +describe('Adagio bid adapter', () => { + let adagioMock; + let utilsMock; + let sandbox; + + const fixtures = { + getElementById(width, height, x, y) { + const obj = { + x: x || 800, + y: y || 300, + width: width || 300, + height: height || 250, + }; - describe('isBidRequestValid', () => { - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - let element = { - x: 0, - y: 0, - width: 200, - height: 300, + return { + ...obj, getBoundingClientRect: () => { return { - width: element.width, - height: element.height, - left: element.x, - top: element.y, - right: element.x + element.width, - bottom: element.y + element.height + width: obj.width, + height: obj.height, + left: obj.x, + top: obj.y, + right: obj.x + obj.width, + bottom: obj.y + obj.height }; } }; - sandbox.stub(document, 'getElementById').withArgs('banner-atf').returns(element); - }); + } + }; - afterEach(function () { - sandbox.restore(); - }); + // safeFrame implementation + const $sf = { + ext: { + geom: function() {} + } + }; + + beforeEach(() => { + window.ADAGIO = {}; + window.ADAGIO.adUnits = {}; + window.ADAGIO.pbjsAdUnits = []; + window.ADAGIO.queue = []; + window.ADAGIO.versions = {}; + window.ADAGIO.versions.adagioBidderAdapter = VERSION; + window.ADAGIO.pageviewId = 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a'; + + adagioMock = sinon.mock(adagio); + utilsMock = sinon.mock(utils); - let bid = { - 'bidder': 'adagio', - 'params': { - organizationId: '0', - placement: 'PAVE_ATF', - site: 'SITE-NAME', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }; + sandbox = sinon.sandbox.create(); + }); - let bidWithMediaTypes = { - 'bidder': 'adagio', - 'params': { - organizationId: '0', - placement: 'PAVE_ATF', - site: 'SITE-NAME', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf' - }, - 'adUnitCode': 'adunit-code-2', - 'mediaTypes': { - banner: { - sizes: [[300, 250]], - } - }, - sizes: [[300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } + afterEach(() => { + window.ADAGIO = undefined; - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(bid)).to.equal(true); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); - }) + adagioMock.restore(); + utilsMock.restore(); - it('should compute a printNumber for the new bid request on same adUnitCode and same pageviewId', () => { - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits).ok; - expect(window.top.ADAGIO.adUnits['adunit-code']).ok; - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(2); + sandbox.restore(); + }); - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(3); + describe('isBidRequestValid()', function() { + it('should return true when required params have been found', function() { + const bid = new BidRequestBuilder().withParams().build(); - window.top.ADAGIO.pageviewId = 123; - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when organization params is not passed', () => { - let bidTest = Object.assign({}, bid); - delete bidTest.params.organizationId; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); - }); + it('should return false if bid.params is missing', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder().build(); - it('should return false when site params is not passed', () => { - let bidTest = Object.assign({}, bid); - delete bidTest.params.site; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); + expect(spec.isBidRequestValid(bid01)).to.equal(false); + sinon.assert.callCount(utils.logWarn, 1); }); - it('should return false when placement params is not passed', () => { - let bidTest = Object.assign({}, bid); - delete bidTest.params.placement; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); - }); + it('should return false when a required param is missing', function() { + const bid01 = new BidRequestBuilder({ params: { + organizationId: '1000', + placement: 'PAVE_ATF' + }}).build(); + + const bid02 = new BidRequestBuilder({ params: { + organizationId: '1000', + site: 'SITE-NAME' + }}).build(); - it('should return false when adUnit element id params is not passed', () => { - let bidTest = Object.assign({}, bid); - delete bidTest.params.adUnitElementId; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); + const bid03 = new BidRequestBuilder({ params: { + placement: 'PAVE_ATF', + site: 'SITE-NAME' + }}).build(); + + expect(spec.isBidRequestValid(bid01)).to.equal(false); + expect(spec.isBidRequestValid(bid02)).to.equal(false); + expect(spec.isBidRequestValid(bid03)).to.equal(false); }); - it('should return false if not in the window.top', () => { - sandbox.stub(utils, 'getWindowTop').throws(); + it('should return false when refererInfo.reachedTop is false', function() { + sandbox.spy(utils, 'logWarn'); + sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: false }); + const bid = new BidRequestBuilder().withParams().build(); + expect(spec.isBidRequestValid(bid)).to.equal(false); + sinon.assert.callCount(utils.logWarn, 1); + sinon.assert.calledWith(utils.logWarn, 'Adagio: the main page url is unreachabled.'); }); - it('should expose ADAGIO.pbjsAdUnits in window', () => { - spec.isBidRequestValid(bidWithMediaTypes); + it('should log warning and enqueue the bid object in ADAGIO.queue when isBidRequestValid is false', function() { + sandbox.stub(Date, 'now').returns(12345); + sandbox.spy(utils, 'logWarn'); + sandbox.spy(adagio, 'enqueue'); + + const bid = new BidRequestBuilder({'params': { + organizationId: '1000', + placement: 'PAVE_ATF' + }}).build(); + + const expectedEnqueued = { + action: 'pb-dbg', + ts: 12345, + data: { bid } + }; + spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.pbjsAdUnits).ok; - expect(window.top.ADAGIO.pbjsAdUnits).to.have.lengthOf(2); - const adUnitWithMediaTypeSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code-2')[0]; - const adUnitWithSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code')[0]; - expect(adUnitWithMediaTypeSizes.sizes).to.eql([[300, 250]]); - expect(adUnitWithSizes.sizes).to.eql([[300, 250], [300, 600]]); + + sinon.assert.calledWith(adagio.enqueue, expectedEnqueued); + sinon.assert.callCount(utils.logWarn, 1); }); - }); - describe('buildRequests', () => { - const sandbox = sinon.createSandbox(); + describe('Store ADAGIO global in window.top or window.self depending on context', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + sizes: [[300, 250], [300, 600]] + }).withParams().build(); - const banner300x250 = { - x: 0, - y: 0, - width: 300, - height: 250, - getBoundingClientRect: () => { - return { - width: banner300x250.width, - height: banner300x250.height, - left: banner300x250.x, - top: banner300x250.y, - right: banner300x250.x + banner300x250.width, - bottom: banner300x250.y + banner300x250.height - }; - }, - }; + const bid02 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-02', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + }).withParams().build(); - const banner300x600 = { - x: 0, - y: 0, - width: 300, - height: 600, - getBoundingClientRect: () => { - return { - width: banner300x600.width, - height: banner300x600.height, - left: banner300x600.x, - top: banner300x600.y, - right: banner300x600.x + banner300x600.width, - bottom: banner300x600.y + banner300x600.height - }; - }, - }; + const bid03 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-02', + mediaTypes: { + banner: { sizes: [[300, 600]] } + }, + }).withParams().build(); + + const expected = [ + { + code: 'adunit-code-01', + sizes: [[300, 250], [300, 600]], + mediaTypes: {}, + bids: [{ + bidder: 'adagio', + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code', + environment: 'desktop' + } + }], + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + pageviewId: 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a', + printNumber: 1, + }, + { + code: 'adunit-code-02', + sizes: [[300, 600]], + mediaTypes: { + banner: { sizes: [[300, 600]] } + }, + bids: [{ + bidder: 'adagio', + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code', + environment: 'desktop' + } + }], + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + pageviewId: 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a', + printNumber: 2, + } + ]; - const computedStyleBlock = { - display: 'block' - }; + it('should store bids config once by bid in window.top if it accessible', function() { + sandbox.stub(adagio, 'getCurrentWindow').returns(window.top); - const computedStyleNone = { - display: 'none' - }; + // replace by the values defined in beforeEach + window.top.ADAGIO = { + ...window.ADAGIO + } - const stubs = { - topGetElementById: undefined, - topGetComputedStyle: undefined - } + spec.isBidRequestValid(bid01); + spec.isBidRequestValid(bid02); + spec.isBidRequestValid(bid03); - top.ADAGIO = top.ADAGIO || {}; - top.ADAGIO.adUnits = top.ADAGIO.adUnits || {}; - top.ADAGIO.pbjsAdUnits = top.ADAGIO.pbjsAdUnits || []; + expect(find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-01')).to.deep.eql(expected[0]); + expect(find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-02')).to.deep.eql(expected[1]); + }); - beforeEach(function () { - stubs.topGetElementById = sandbox.stub(top.document, 'getElementById'); - stubs.topGetComputedStyle = sandbox.stub(top, 'getComputedStyle'); + it('should store bids config once by bid in current window', function() { + sandbox.stub(adagio, 'getCurrentWindow').returns(window.self); - stubs.topGetElementById.withArgs('banner-atf-123').returns(banner300x250); - stubs.topGetElementById.withArgs('banner-atf-456').returns(banner300x600); - stubs.topGetElementById.withArgs('does-not-exist').returns(null); - stubs.topGetComputedStyle.returns(computedStyleBlock); - }); + spec.isBidRequestValid(bid01); + spec.isBidRequestValid(bid02); + spec.isBidRequestValid(bid03); - afterEach(function () { - sandbox.restore(); + expect(find(window.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-01')).to.deep.eql(expected[0]); + expect(find(window.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-02')).to.deep.eql(expected[1]); + }); }); + }); - after(function() { - sandbox.reset(); - }) - - let bidRequests = [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '123', - site: 'ADAGIO-123', - placement: 'PAVE_ATF-123', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-123' - }, - 'adUnitCode': 'adunit-code1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }, - { - 'bidder': 'adagio', - 'params': { - organizationId: '123', - site: 'ADAGIO-123', - placement: 'PAVE_ATF-123', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-123' - }, - 'adUnitCode': 'adunit-code2', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }, - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-456' - }, - 'adUnitCode': 'adunit-code3', - 'mediaTypes': { - banner: { - sizes: [[300, 250]] - } - }, - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } + describe('buildRequests()', function() { + const expectedDataKeys = [ + 'id', + 'organizationId', + 'secure', + 'device', + 'site', + 'pageviewId', + 'adUnits', + 'gdpr', + 'schain', + 'prebidVersion', + 'adapterVersion', + 'featuresVersion' ]; - const bidRequestsWithPostBid = [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-456', - postBid: true - }, - 'adUnitCode': 'adunit-code3', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } - ]; + it('groups requests by organizationId', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bid02 = new BidRequestBuilder().withParams().build(); + const bid03 = new BidRequestBuilder().withParams({ + organizationId: '1002' + }).build(); - let consentString = 'theConsentString'; - let bidderRequest = { - 'bidderCode': 'adagio', - 'auctionId': '12jejebn', - 'bidderRequestId': 'hehehehbeheh', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - allowAuctionWithoutConsent: true - }, - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'http://test.io/index.html?pbjs_debug=true' - } - }; + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01, bid02, bid03], bidderRequest); - it('groups requests by siteId', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests).to.have.lengthOf(2); - expect(requests[0].data.organizationId).to.equal('123'); + expect(requests[0].data.organizationId).to.equal('1000'); expect(requests[0].data.adUnits).to.have.lengthOf(2); - expect(requests[1].data.organizationId).to.equal('456'); + expect(requests[1].data.organizationId).to.equal('1002'); expect(requests[1].data.adUnits).to.have.lengthOf(1); }); - it('sends bid request to ENDPOINT_PB via POST', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENDPOINT); - expect(request.data.prebidVersion).to.equal('$prebid.version$'); - }); + it('should send bid request to ENDPOINT_PB via POST', function() { + sandbox.stub(adagio, 'getDevice').returns({ a: 'a' }); + sandbox.stub(adagio, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); + sandbox.stub(adagio, 'getPageviewId').returns('1234-567'); + sandbox.stub(adagio, 'getFeatures').returns({}); - it('features params must be empty if param adUnitElementId is not found', () => { - const requests = spec.buildRequests([Object.assign({}, bidRequests[0], {params: {adUnitElementId: 'does-not-exist'}})], bidderRequest); - const request = requests[0]; - const expected = {} - expect(request.data.adUnits[0].features).to.deep.equal(expected); - }); + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); - it('features params "adunit_position" should be computed even if DOM element is display:none', () => { - stubs.topGetComputedStyle.returns(computedStyleNone); - const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].features.adunit_position).to.equal('0x0'); - }); + const requests = spec.buildRequests([bid01], bidderRequest); - it('features params "viewport" should be computed even if window.innerWidth is not supported', () => { - sandbox.stub(top, 'innerWidth').value(undefined); - const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].features.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].options.contentType).to.eq('text/plain'); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); }); - it('AdUnit requested should have the correct sizes array depending on the config', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[1].data.adUnits[0]).to.have.property('mediaTypes'); - }); + it('should enqueue computed features for collect usage', function() { + sandbox.stub(Date, 'now').returns(12345); + + for (const prop in _features) { + sandbox.stub(_features, prop).returns(''); + } + + adagioMock.expects('enqueue').withExactArgs({ + action: 'features', + ts: 12345, + data: { + 'gpt-adunit-code': { + features: {}, + version: '1' + } + } + }).atLeast(1); + + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data).to.have.all.keys(expectedDataKeys); - it('features params must be an object if featurejs is loaded', () => { - let requests = spec.buildRequests(bidRequests, bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; + adagioMock.verify(); }); - it('outerAdUnitElementId must be added when PostBid param has been set', () => { - top.ADAGIO = top.ADAGIO || {}; - top.ADAGIO.pbjsAdUnits = []; + it('should filter some props in case refererDetection.reachedTop is false', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder({ + refererInfo: { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + null, + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + } + }).build(); - top.ADAGIO.pbjsAdUnits.push({ - code: bidRequestsWithPostBid[0].adUnitCode, - sizes: bidRequestsWithPostBid[0].sizes, - bids: [{ - bidder: bidRequestsWithPostBid[0].bidder, - params: bidRequestsWithPostBid[0].params + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].features).to.exist; + expect(requests[0].data.adUnits[0].features.url).to.not.exist; + }); + + describe('with sChain', function() { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'ssp.test', + sid: '00001', + hp: 1 }] + }; + + it('should add the schain if available at bidder level', function() { + const bid01 = new BidRequestBuilder({ schain }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.schain).to.deep.equal(schain); }); - let requests = spec.buildRequests(bidRequestsWithPostBid, bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].params.outerAdUnitElementId).to.exist; - }); - it('generates a pageviewId if missing', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - delete window.top.ADAGIO.pageviewId; + it('Schain should not be added to the request', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); + const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.pageviewId).to.exist.and.to.not.equal('_').and.to.not.equal(''); - expect(requests[0].data.pageviewId).to.equal(requests[1].data.pageviewId); + expect(requests[0].data.schain).to.not.exist; + }); }); - it('uses an existing pageviewId if present', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - window.top.ADAGIO.pageviewId = 'abc'; + describe('with GDPR', function() { + const bid01 = new BidRequestBuilder().withParams().build(); - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); + const consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; - expect(requests[0].data.pageviewId).to.equal('abc'); - expect(requests[1].data.pageviewId).to.equal('abc'); - }); + const gdprConsentBuilderTCF1 = function gdprConsentBuilderTCF1(applies, allows) { + return { + consentString, + gdprApplies: applies, + allowAuctionWithoutConsent: allows, + apiVersion: 1 + }; + }; - it('should send the printNumber in features object', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - window.top.ADAGIO.pageviewId = 'abc'; - window.top.ADAGIO.adUnits['adunit-code1'] = { - pageviewId: 'abc', - printNumber: 2 + const gdprConsentBuilderTCF2 = function gdprConsentBuilderTCF2(applies) { + return { + consentString, + gdprApplies: applies, + apiVersion: 2 + }; }; - const requests = spec.buildRequests([bidRequests[0]], bidderRequest); - const request = requests[0]; - expect(request.data.adUnits[0].features.print_number).to.equal('2'); - }) - it('GDPR consent is applied', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(1); - }); + context('When GDPR applies', function() { + it('send data.gdpr object to the server from TCF v.1.1 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF1(true, true) + }).build(); + + const expected = { + consentString, + allowAuctionWithoutConsent: 1, + consentRequired: 1, + apiVersion: 1 + }; - it('GDPR consent is not applied', () => { - bidderRequest.gdprConsent.gdprApplies = false; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(0); - }); + const requests = spec.buildRequests([bid01], bidderRequest); - it('GDPR consent is undefined', () => { - delete bidderRequest.gdprConsent.consentString; - delete bidderRequest.gdprConsent.gdprApplies; - delete bidderRequest.gdprConsent.allowAuctionWithoutConsent; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr).to.not.have.property('consentString'); - expect(request.data.gdpr).to.not.have.property('gdprApplies'); - expect(request.data.gdpr).to.not.have.property('allowAuctionWithoutConsent'); - }); + expect(requests[0].data.gdpr).to.deep.equal(expected); + }); - it('GDPR consent bidderRequest does not have gdprConsent', () => { - delete bidderRequest.gdprConsent; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr).to.be.empty; - }); + it('send data.gdpr object to the server from TCF v.2 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF2(true) + }).build(); - it('should expose version in window', () => { - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.versions).ok; - expect(window.top.ADAGIO.versions.adagioBidderAdapter).to.eq(VERSION); - }); + const expected = { + consentString, + consentRequired: 1, + apiVersion: 2 + }; - it('should returns an empty array if the bidder cannot access to window top (based on refererInfo.reachedTop)', () => { - const requests = spec.buildRequests(bidRequests, { - ...bidderRequest, - refererInfo: { reachedTop: false } + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.gdpr).to.deep.equal(expected); + }); }); - expect(requests).to.be.empty; - }); - }); - describe('interpretResponse', () => { - const sandbox = sinon.createSandbox(); + context('When GDPR does not applies', function() { + it('send data.gdpr object to the server from TCF v.1.1 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF1(false, true) + }).build(); + + const expected = { + consentString, + allowAuctionWithoutConsent: 1, + consentRequired: 0, + apiVersion: 1 + }; - let serverResponse = { - body: { - data: { - pred: 1 - }, - bids: [ - { - ad: '
', - cpm: 1, - creativeId: 'creativeId', - currency: 'EUR', - height: 250, - netRevenue: true, - requestId: 'c180kg4267tyqz', - ttl: 360, - width: 300 - } - ] - } - }; + const requests = spec.buildRequests([bid01], bidderRequest); - let emptyBodyServerResponse = { - body: null - }; + expect(requests[0].data.gdpr).to.deep.equal(expected); + }); - let withoutBidsArrayServerResponse = { - body: { - bids: [] - } - }; + it('send data.gdpr object to the server from TCF v.2 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF2(false) + }).build(); + + const expected = { + consentString, + consentRequired: 0, + apiVersion: 2 + }; + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.gdpr).to.deep.equal(expected); + }); + }); + + context('When GDPR is undefined in bidderRequest', function() { + it('send an empty data.gdpr to the server', function() { + const bidderRequest = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest); - let serverResponseWhichThrowsException = { + expect(requests[0].data.gdpr).to.be.empty; + }); + }); + }); + }); + + describe('interpretResponse()', function() { + let serverResponse = { body: { data: { pred: 1 }, - bids: { - foo: 'bar' - } + bids: [{ + ad: '
', + cpm: 1, + creativeId: 'creativeId', + currency: 'EUR', + height: 250, + netRevenue: true, + requestId: 'c180kg4267tyqz', + ttl: 360, + width: 300 + }] } }; let bidRequest = { - 'data': { - 'adUnits': [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - adUnitElementId: 'banner-atf-456', - pagetype: 'ARTICLE', - category: 'NEWS', - subcategory: 'SPORT', - environment: 'SITE-MOBILE' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - 'pageviewId': 'd8c4fl2k39i0wn', - } - ] + data: { + adUnits: [{ + bidder: 'adagio', + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'desktop' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { sizes: [[300, 250], [300, 600]] } + }, + bidId: 'c180kg4267tyqz', + bidderRequestId: '8vfscuixrovn8i', + auctionId: 'lel4fhp239i9km', + pageviewId: 'd8c4fl2k39i0wn', + }] } }; - afterEach(function() { - sandbox.restore(); - }); + it('should return an empty response array if body is empty', function() { + expect(spec.interpretResponse({ + body: null + }, bidRequest)).to.be.an('array').length(0); - it('Should returns empty response if body is empty', () => { - expect(spec.interpretResponse(emptyBodyServerResponse, bidRequest)).to.be.an('array').length(0); - expect(spec.interpretResponse({body: {}}, bidRequest)).to.be.an('array').length(0); + expect(spec.interpretResponse({ + body: {} + }, bidRequest)).to.be.an('array').length(0); }); - it('Should returns empty response if bids array is empty', () => { - expect(spec.interpretResponse({withoutBidsArrayServerResponse}, bidRequest)).to.be.an('array').length(0); + it('should return an empty response array if bids array is empty', function() { + expect(spec.interpretResponse({ + body: { + bids: [] + } + }, bidRequest)).to.be.an('array').length(0); }); - it('should get correct bid response', () => { + it('should handle properly a correct bid response', function() { let expectedResponse = [{ ad: '
', cpm: 1, @@ -567,89 +591,514 @@ describe('adagioAdapter', () => { requestId: 'c180kg4267tyqz', ttl: 360, width: 300, - placement: 'PAVE_ATF-456', - site: 'ADAGIO-456', + placement: 'PAVE_ATF', + site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', - environment: 'SITE-MOBILE' + environment: 'desktop' }]; + expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.deep.equal(expectedResponse); }); - it('Should populate ADAGIO queue with ssp-data', () => { - spec.interpretResponse(serverResponse, bidRequest); - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.queue).to.be.an('array'); - }); + it('should populate ADAGIO queue with ssp-data', function() { + sandbox.stub(Date, 'now').returns(12345); + + adagioMock.expects('enqueue').withExactArgs({ + action: 'ssp-data', + ts: 12345, + data: serverResponse.body.data + }).once(); - it('Should not populate ADAGIO queue with ssp-data if not in top window', () => { - utils.getWindowTop().ADAGIO.queue = []; - sandbox.stub(utils, 'getWindowTop').throws(); spec.interpretResponse(serverResponse, bidRequest); - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.queue).to.be.an('array'); - expect(window.top.ADAGIO.queue).empty; + + adagioMock.verify(); }); - it('should return an empty response even if an exception is ', () => { - expect(spec.interpretResponse(serverResponseWhichThrowsException, bidRequest)).to.be.an('array').length(0); + it('should properly try-catch an exception and return an empty array', function() { + sandbox.stub(adagio, 'enqueue').throws(); + utilsMock.expects('logError').once(); + + expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array').length(0); + + utilsMock.verify(); }); }); - describe('getUserSyncs', () => { + describe('getUserSyncs()', function() { const syncOptions = { - 'iframeEnabled': 'true' - } - const serverResponses = [ - { + syncEnabled: false + }; + + it('should handle user syncs if data is in the server response ', function() { + const serverResponses = [{ body: { userSyncs: [ - { - t: 'i', - u: 'https://test.url.com/setuid' - }, - { - t: 'p', - u: 'https://test.url.com/setuid' - } + { t: 'i', u: 'https://test.url.com/setuid' }, + { t: 'p', u: 'https://test.url.com/setuid' } ] } - } - ]; - - const emptyServerResponses = [ - { - body: '' - } - ]; + }]; - it('should handle correctly user syncs', () => { let result = spec.getUserSyncs(syncOptions, serverResponses); - let emptyResult = spec.getUserSyncs(syncOptions, emptyServerResponses); + expect(result[0].type).to.equal('iframe'); expect(result[0].url).contain('setuid'); + expect(result[1].type).to.equal('image'); - expect(emptyResult).to.equal(false); + expect(result[1].url).contain('setuid'); + }); + + it('should return false if data is not in server response', function() { + const serverResponse = [{ body: '' }]; + const result = spec.getUserSyncs(syncOptions, serverResponse); + expect(result).to.equal(false); + }); + }); + + describe('Adagio features', function() { + it('should return all expected features when all expected bidder params are available', function() { + sandbox.stub(window.top.document, 'getElementById').returns( + fixtures.getElementById() + ); + sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); + + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const result = adagio.getFeatures(bidRequest, bidderRequest); + + expect(result.adunit_position).to.match(/^[\d]+x[\d]+$/); + expect(result.page_dimensions).to.match(/^[\d]+x[\d]+$/); + expect(result.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); + expect(result.print_number).to.be.a('String'); + expect(result.dom_loading).to.be.a('String'); + expect(result.user_timestamp).to.be.a('String'); + expect(result.url).to.be.a('String'); + expect(result.device).to.be.a('String'); + expect(result.os).to.be.a('String'); + expect(result.browser).to.be.a('String'); + }); + + it('should return all expected features when `adUnitElementId` param is not available', function() { + const bidRequest = new BidRequestBuilder({ + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME' + }, + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const result = adagio.getFeatures(bidRequest, bidderRequest); + + expect(result.adunit_position).to.not.exist; + expect(result.page_dimensions).to.be.a('String'); + expect(result.viewport_dimensions).to.be.a('String'); + expect(result.print_number).to.be.a('String'); + expect(result.dom_loading).to.be.a('String'); + expect(result.user_timestamp).to.be.a('String'); + expect(result.url).to.be.a('String'); + expect(result.device).to.be.a('String'); + expect(result.os).to.be.a('String'); + expect(result.browser).to.be.a('String'); + }); + + it('should not return feature with an empty value', function() { + sandbox.stub(_features, 'getDomLoadingDuration').returns(''); + sandbox.stub(_features, 'getUrl').returns(''); + sandbox.stub(_features, 'getBrowser').returns(''); + + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const result = adagio.getFeatures(bidRequest, bidderRequest); + + expect(result.adunit_position).to.not.exist; + expect(result.page_dimensions).to.exist; + expect(result.viewport_dimensions).to.exist; + expect(result.print_number).to.exist; + expect(result.dom_loading).to.not.exist; + expect(result.user_timestamp).to.exist; + expect(result.url).to.not.exist; + expect(result.device).to.exist; + expect(result.os).to.exist; + expect(result.browser).to.not.exist; + }); + + describe('getPageDimensions feature', function() { + afterEach(() => { + delete window.$sf; + }); + + it('should not compute the page dimensions in cross-origin iframe', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + const result = _features.getPageDimensions(); + expect(result).to.eq(''); + }); + + it('should not compute the page dimensions even with safeFrame api', function() { + window.$sf = $sf; + const result = _features.getPageDimensions(); + expect(result).to.eq(''); + }); + + it('should not compute the page dimensions if is not in the DOM', function() { + sandbox.stub(window.top.document, 'querySelector').withArgs('body').returns(null); + const result = _features.getPageDimensions(); + expect(result).to.eq(''); + }); + + it('should compute the page dimensions based on body and viewport dimensions', function() { + sandbox.stub(window.top.document, 'querySelector').withArgs('body').returns({ scrollWidth: 1360, offsetWidth: 1280, scrollHeight: 2000, offsetHeight: 1000 }); + const result = _features.getPageDimensions(); + expect(result).to.eq('1360x2000'); + }); + }); + + describe('getViewPortDimensions feature', function() { + afterEach(() => { + delete window.$sf; + }); + + it('should not compute the viewport dimensions in cross-origin iframe', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + const result = _features.getViewPortDimensions(); + expect(result).to.eq(''); + }); + + it('should compute the viewport dimensions in cross-origin iframe w/ safeFrame api', function() { + window.$sf = $sf; + sandbox.stub(window.$sf.ext, 'geom').returns({ + win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, + self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, + }); + const result = _features.getViewPortDimensions(); + expect(result).to.eq('1920x1177'); + }); + + it('should not compute the viewport dimensions if safeFrame api is misimplemented', function() { + window.$sf = { + ext: { geom: 'nothing' } + }; + const result = _features.getViewPortDimensions(); + expect(result).to.eq(''); + }); + + it('should not compute the viewport dimensions if is not in the DOM', function() { + const querySelectorSpy = sandbox.spy(() => null); + sandbox.stub(utils, 'getWindowTop').returns({ + location: { href: 'https://mytest.io' }, + document: { querySelector: querySelectorSpy } + }); + const result = _features.getViewPortDimensions(); + expect(result).to.eq(''); + }); + + it('should compute the viewport dimensions based on window', function() { + sandbox.stub(utils, 'getWindowTop').returns({ + location: { href: 'https://mytest.io' }, + innerWidth: 960, + innerHeight: 3000 + }); + const result = _features.getViewPortDimensions(); + expect(result).to.eq('960x3000'); + }); + + it('should compute the viewport dimensions based on body', function() { + const querySelectorSpy = sandbox.spy(() => ({ clientWidth: 1024, clientHeight: 2000 })); + sandbox.stub(utils, 'getWindowTop').returns({ + location: { href: 'https://mytest.io' }, + document: { querySelector: querySelectorSpy } + }); + const result = _features.getViewPortDimensions(); + expect(result).to.eq('1024x2000'); + }); + }); + + describe('getSlotPosition feature', function() { + let getElementByIdStub; + let getComputedStyleStub; + + beforeEach(() => { + getElementByIdStub = sandbox.stub(window.top.document, 'getElementById'); + getElementByIdStub.returns(fixtures.getElementById()); + getComputedStyleStub = sandbox.stub(window.top, 'getComputedStyle'); + getComputedStyleStub.returns({ display: 'block' }); + }); + + afterEach(() => { + delete window.$sf; + getElementByIdStub.restore(); + getComputedStyleStub.restore(); + }); + + it('should not compute the slot position in cross-origin iframe', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq(''); + }); + + it('should compute the slot position in cross-origin iframe w/ safeFrame api', function() { + window.$sf = $sf; + sandbox.stub(window.$sf.ext, 'geom').returns({ + win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, + self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, + }); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq('210x859'); + }); + + it('should not compute the slot position if safeFrame api is misimplemented', function() { + window.$sf = { + ext: { geom: 'nothing' } + }; + utilsMock.expects('logWarn').once(); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq(''); + utilsMock.verify(); + }); + + it('should not compute the slot position due to unreachable adUnitElementId', function() { + getElementByIdStub.returns(null); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq(''); + }); + + it('should use a quick switch to display slot and compute position', function() { + getComputedStyleStub.returns({ display: 'none' }); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq('800x300'); + }); + + it('should compute the slot position based on window.top w/o postBid param', function() { + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq('800x300'); + }); + + it.skip('should compute the slot position inside the parent window (window.top) when safeFrame is not available and postBid params is `true`', function() { + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: true }); + // expect(result).to.eq('800x300'); + }); + }); + }); + + describe('optional params auto detection', function() { + it('should auto detect environment', function() { + const getDeviceStub = sandbox.stub(_features, 'getDevice'); + + getDeviceStub.returns(5); + expect(adagio.autoDetectEnvironment()).to.eq('tablet'); + + getDeviceStub.returns(4); + expect(adagio.autoDetectEnvironment()).to.eq('mobile'); + + getDeviceStub.returns(2); + expect(adagio.autoDetectEnvironment()).to.eq('desktop'); + }); + + it('should auto detect adUnitElementId when GPT is used', function() { + sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); + expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); + }); + }); + + describe('print number handling', function() { + it('should return 1 if no adunit-code found. This means it is the first auction', function() { + sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); + expect(adagio.computePrintNumber('adunit-code')).to.eql(1); + }); + + it('should increment the adunit print number when the adunit-code has already been used for an other auction', function() { + sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); + + window.top.ADAGIO.adUnits['adunit-code'] = { + pageviewId: 'abc-def', + printNumber: 1, + }; + + expect(adagio.computePrintNumber('adunit-code')).to.eql(2); }); }); - describe('getAdagioScript', () => { + describe('site information using refererDetection or window.top', function() { + it('should returns domain, page and window.referrer in a window.top context', function() { + sandbox.stub(utils, 'getWindowTop').returns({ + location: { + hostname: 'test.io', + href: 'https://test.io/article/a.html' + }, + document: { + referrer: 'https://google.com' + } + }); + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' + } + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'test.io', + page: 'https://test.io/article/a.html', + referrer: 'https://google.com' + }); + }); + + it('should returns domain and page in a cross-domain w/ top domain reached context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 0, + reachedTop: true, + referer: 'http://level.io/', + stack: [ + 'http://level.io/', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'level.io', + page: 'http://level.io/', + referrer: '' + }); + }); + + it('should not return anything in a cross-domain w/o top domain reached and w/o ancestor context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + null, + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: '', + page: '', + referrer: '' + }); + }); + + it('should return domain only in a cross-domain w/o top domain reached and w/ ancestors context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + 'http://mytest.com/', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'mytest.com', + page: '', + referrer: '' + }); + }); + }); + + describe('adagioScriptFromLocalStorageCb()', function() { const VALID_HASH = 'Lddcw3AADdQDrPtbRJkKxvA+o1CtScGDIMNRpHB3NnlC/FYmy/9RKXelKrYj/sjuWusl5YcOpo+lbGSkk655i8EKuDiOvK6ae/imxSrmdziIp+S/TA6hTFJXcB8k1Q9OIp4CMCT52jjXgHwX6G0rp+uYoCR25B1jHaHnpH26A6I='; const INVALID_HASH = 'invalid'; const VALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){};(_ADAGIO)();\n'; const INVALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){//corrupted};(_ADAGIO)();\n'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; + beforeEach(function() { + localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + }); + + describe('getAdagioScript', function() { + it('should run storage.getDataFromLocalStorage callback and call adagioScriptFromLocalStorageCb() ', function() { + sandbox.spy(adagio, 'adagioScriptFromLocalStorageCb'); + const getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsArg(1); + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + + getAdagioScript(); + + sinon.assert.callCount(getDataFromLocalStorageStub, 1); + sinon.assert.callCount(adagio.adagioScriptFromLocalStorageCb, 1); + }); + + it('should load external script if the user consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true); + getAdagioScript(); + + expect(loadExternalScript.called).to.be.true; + }); + + it('should not load external script if the user does not consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); + getAdagioScript(); + + expect(loadExternalScript.called).to.be.false; + }); + + it('should remove the localStorage key if exists and the user does not consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, 'the script'); + + getAdagioScript(); + + expect(loadExternalScript.called).to.be.false; + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + }) + }); + it('should verify valid hash with valid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').once(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script.').once(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never(); - getAdagioScript(); + adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.equals('// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); utilsMock.verify(); @@ -658,11 +1107,11 @@ describe('adagioAdapter', () => { it('should verify valid hash with invalid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + INVALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once(); - getAdagioScript(); + adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; utilsMock.verify(); @@ -671,11 +1120,11 @@ describe('adagioAdapter', () => { it('should verify invalid hash with valid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + INVALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once(); - getAdagioScript(); + adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; utilsMock.verify(); @@ -684,14 +1133,21 @@ describe('adagioAdapter', () => { it('should verify missing hash', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').once(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').once(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never(); - getAdagioScript(); + adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; utilsMock.verify(); }); + + it('should return false if content script does not exist in localStorage', function() { + sandbox.spy(utils, 'logWarn'); + expect(adagioScriptFromLocalStorageCb(null)).to.be.undefined; + sinon.assert.callCount(utils.logWarn, 1); + sinon.assert.calledWith(utils.logWarn, 'Adagio: script not found.'); + }); }); }); diff --git a/test/spec/modules/adbutlerBidAdapter_spec.js b/test/spec/modules/adbutlerBidAdapter_spec.js new file mode 100644 index 00000000000..6dedce321d8 --- /dev/null +++ b/test/spec/modules/adbutlerBidAdapter_spec.js @@ -0,0 +1,231 @@ +import {expect} from 'chai'; +import {spec} from 'modules/adbutlerBidAdapter.js'; + +describe('AdButler adapter', function () { + let bidRequests; + + beforeEach(function () { + bidRequests = [ + { + bidder: 'adbutler', + params: { + accountID: '167283', + zoneID: '210093', + keyword: 'red', + minCPM: '1.00', + maxCPM: '5.00', + extra: { + foo: 'bar', + } + }, + placementCode: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + }); + + describe('implementation', function () { + describe('for requests', function () { + it('should accept valid bid', function () { + let validBid = { + bidder: 'adbutler', + params: { + accountID: '167283', + zoneID: '210093' + } + }, + isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('should reject invalid bid', function () { + let invalidBid = { + bidder: 'adbutler', + params: { + accountID: '167283', + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should use custom domain string', function () { + 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', function () { + let requests = spec.buildRequests(bidRequests), + request = requests[0]; + + let [domain] = request.url.split('/adserve/'); + + expect(domain).to.equal('https://servedbyadbutler.com'); + }); + + it('should set the keyword parameter', function () { + let requests = spec.buildRequests(bidRequests), + requestURL = requests[0].url; + + expect(requestURL).to.have.string(';kw=red;'); + }); + + it('should set the extra parameter', () => { + let requests = spec.buildRequests(bidRequests); + let requestURL = requests[0].url; + + expect(requestURL).to.have.string(';foo=bar;'); + }); + + it('should increment the count for the same zone', function () { + let bidRequests = [ + { + sizes: [[300, 250]], + bidder: 'adbutler', + params: { + accountID: '107878', + zoneID: '86133', + } + }, { + sizes: [[300, 250]], + bidder: 'adbutler', + 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', function () { + it('should return complete bid response', function () { + 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', function () { + let serverResponse = { + body: { + 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', function () { + let serverResponse = { + body: { + 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', function () { + let serverResponse = { + body: { + 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', function () { + let serverResponse = { + body: { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 7, + 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/adfinityBidAdapter_spec.js b/test/spec/modules/adfinityBidAdapter_spec.js new file mode 100644 index 00000000000..479a2303dd5 --- /dev/null +++ b/test/spec/modules/adfinityBidAdapter_spec.js @@ -0,0 +1,151 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/adfinityBidAdapter.js'; + +describe('AdfinityAdapter', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'adfinity', + bidderRequestId: '145e1d6a7837c9', + params: { + placement_id: 0 + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + domain: 'example.com' + } + ] + } + }; + let bidderRequest = { + bidderCode: 'adfinity', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + uspConsent: '1YN-', + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + bids: [bid] + } + + describe('isBidRequestValid', function () { + it('Should return true when placement_id can be cast to a number', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when placement_id is not a number', function () { + bid.params.placement_id = 'aaa'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://stat.adfinity.pro/?c=o&m=multi'); + }); + + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + 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', 'schain'); + expect(placement.schain).to.be.an('object') + expect(placement.placementId).to.be.a('number'); + expect(placement.bidId).to.be.a('string'); + expect(placement.traffic).to.be.a('string'); + expect(placement.sizes).to.be.an('array'); + } + }); + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + let resObject = { + body: [ { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://stat.adfinity.pro/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index bb3e99173f7..360979659de 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -1,8 +1,8 @@ import {assert, expect} from 'chai'; -import * as url from 'src/url'; -import {spec} from 'modules/adformBidAdapter'; -import { BANNER, VIDEO } from 'src/mediaTypes'; -import { config } from 'src/config'; +import {spec} from 'modules/adformBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; +import { createEidsArray } from 'modules/userId/eids.js'; describe('Adform adapter', function () { let serverResponse, bidRequest, bidResponses; @@ -130,25 +130,43 @@ describe('Adform adapter', function () { assert.equal(parsedUrl.query.pt, 'gross'); }); - describe('gdpr', function () { + it('should pass extended ids', function () { + bids[0].userIdAsEids = createEidsArray({ + tdid: 'TTD_ID_FROM_USER_ID_MODULE', + pubcid: 'pubCommonId_FROM_USER_ID_MODULE' + }); + let request = spec.buildRequests(bids); + let eids = parseUrl(request.url).query.eids; + + assert.equal(eids, 'eyJhZHNlcnZlci5vcmciOnsiVFREX0lEX0ZST01fVVNFUl9JRF9NT0RVTEUiOlsxXX0sInB1YmNpZC5vcmciOnsicHViQ29tbW9uSWRfRlJPTV9VU0VSX0lEX01PRFVMRSI6WzFdfX0%3D'); + assert.deepEqual(JSON.parse(atob(decodeURIComponent(eids))), { + 'adserver.org': { + 'TTD_ID_FROM_USER_ID_MODULE': [1] + }, + 'pubcid.org': { + 'pubCommonId_FROM_USER_ID_MODULE': [1] + } + }); + }); + + describe('user privacy', function () { it('should send GDPR Consent data to adform if gdprApplies', function () { - let resultBids = JSON.parse(JSON.stringify(bids[0])); let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: true, consentString: 'concentDataString'}}); let parsedUrl = parseUrl(request.url).query; - assert.equal(parsedUrl.gdpr, 'true'); + assert.equal(parsedUrl.gdpr, '1'); assert.equal(parsedUrl.gdpr_consent, 'concentDataString'); }); - it('should not send GDPR Consent data to adform if gdprApplies is false or undefined', function () { - let resultBids = JSON.parse(JSON.stringify(bids[0])); + it('should not send GDPR Consent data to adform if gdprApplies is undefined', function () { let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: false, consentString: 'concentDataString'}}); let parsedUrl = parseUrl(request.url).query; - assert.ok(!parsedUrl.gdpr); - assert.ok(!parsedUrl.gdpr_consent); + assert.equal(parsedUrl.gdpr, '0'); + assert.equal(parsedUrl.gdpr_consent, 'concentDataString'); request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: undefined, consentString: 'concentDataString'}}); + parsedUrl = parseUrl(request.url).query; assert.ok(!parsedUrl.gdpr); assert.ok(!parsedUrl.gdpr_consent); }); @@ -164,6 +182,13 @@ describe('Adform adapter', function () { request = spec.buildRequests([bids[0]]); assert.ok(!request.gdpr); }); + + it('should send CCPA Consent data to adform', function () { + const request = spec.buildRequests([bids[0]], {uspConsent: '1YA-'}); + const parsedUrl = parseUrl(request.url).query; + + assert.equal(parsedUrl.us_privacy, '1YA-'); + }); }); }); @@ -248,14 +273,14 @@ describe('Adform adapter', function () { for (let i = 0; i < result.length; i++) { assert.equal(result[i].gdpr, true); assert.equal(result[i].gdpr_consent, 'ERW342EIOWT34234KMGds'); - }; + } bidRequest.gdpr = undefined; result = spec.interpretResponse(serverResponse, bidRequest); for (let i = 0; i < result.length; i++) { assert.ok(!result[i].gdpr); assert.ok(!result[i].gdpr_consent); - }; + } }); it('should set a renderer only for an outstream context', function () { @@ -303,13 +328,13 @@ describe('Adform adapter', function () { serverResponse.body = [serverResponse.body[0]]; bidRequest.bids = [bidRequest.bids[0]]; - bidRequest.bids[0].sizes = [['300', '250'], ['250', '300'], ['300', '600'], ['600', '300']] + bidRequest.bids[0].sizes = [['300', '250'], ['250', '300'], ['300', '600'], ['600', '300']]; let result = spec.interpretResponse(serverResponse, bidRequest); assert.equal(result[0].width, 300); assert.equal(result[0].height, 600); }); - }) + }); }); beforeEach(function () { diff --git a/test/spec/modules/adformOpenRTBBidAdapter_spec.js b/test/spec/modules/adformOpenRTBBidAdapter_spec.js index b0ce0f59a88..05788183e29 100644 --- a/test/spec/modules/adformOpenRTBBidAdapter_spec.js +++ b/test/spec/modules/adformOpenRTBBidAdapter_spec.js @@ -1,9 +1,9 @@ // jshint esversion: 6, es3: false, node: true import {assert, expect} from 'chai'; -import * as url from 'src/url'; -import {spec} from 'modules/adformOpenRTBBidAdapter'; -import { NATIVE } from 'src/mediaTypes'; -import { config } from 'src/config'; +import {spec} from 'modules/adformOpenRTBBidAdapter.js'; +import { NATIVE } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; +import { createEidsArray } from 'modules/userId/eids.js'; describe('AdformOpenRTB adapter', function () { let serverResponse, bidRequest, bidResponses; @@ -44,7 +44,7 @@ describe('AdformOpenRTB adapter', function () { assert.ok(request.data); }); - describe('gdpr', function () { + describe('user privacy', function () { it('should send GDPR Consent data to adform if gdprApplies', function () { let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', test: 1 } }]; let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; @@ -61,9 +61,25 @@ describe('AdformOpenRTB adapter', function () { let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(typeof request.regs.ext.gdpr, 'number'); + assert.equal(request.regs.ext.gdpr, 1); }); - it('should not send GDPR Consent data to adform if gdprApplies is false or undefined', function () { + it('should send CCPA Consent data to adform', function () { + let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', test: 1 } }]; + let bidderRequest = { uspConsent: '1YA-', refererInfo: { referer: 'page' } }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.regs.ext.us_privacy, '1YA-'); + + bidderRequest = { uspConsent: '1YA-', gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; + request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.regs.ext.us_privacy, '1YA-'); + assert.equal(request.user.ext.consent, 'consentDataString'); + assert.equal(request.regs.ext.gdpr, 1); + }); + + it('should not send GDPR Consent data to adform if gdprApplies is undefined', function () { let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId' } @@ -71,6 +87,12 @@ describe('AdformOpenRTB adapter', function () { let bidderRequest = {gdprConsent: {gdprApplies: false, consentString: 'consentDataString'}, refererInfo: { referer: 'page' }}; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + assert.equal(request.user.ext.consent, 'consentDataString'); + assert.equal(request.regs.ext.gdpr, 0); + + bidderRequest = {gdprConsent: {consentString: 'consentDataString'}, refererInfo: { referer: 'page' }}; + request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + assert.equal(request.user, undefined); assert.equal(request.regs, undefined); }); @@ -145,6 +167,23 @@ describe('AdformOpenRTB adapter', function () { }); }); + it('should pass extended ids', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + userIdAsEids: createEidsArray({ + tdid: 'TTD_ID_FROM_USER_ID_MODULE', + pubcid: 'pubCommonId_FROM_USER_ID_MODULE' + }) + }]; + + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + assert.deepEqual(request.user.ext.eids, [ + { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, + { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } + ]); + }); + it('should send currency if defined', function () { config.setConfig({ currency: { adServerCurrency: 'EUR' } }); let validBidRequests = [{ params: {} }]; diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index 6483272df21..927e7910723 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -1,8 +1,8 @@ import {expect} from 'chai'; -import {spec} from 'modules/adgenerationBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; -import {NATIVE} from 'src/mediaTypes'; -import {config} from 'src/config'; +import {spec} from 'modules/adgenerationBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {NATIVE} from 'src/mediaTypes.js'; +import {config} from 'src/config.js'; import prebid from '../../../package.json'; describe('AdgenerationAdapter', function () { diff --git a/test/spec/modules/adglareBidAdapter_spec.js b/test/spec/modules/adglareBidAdapter_spec.js new file mode 100644 index 00000000000..d0dbe891f9d --- /dev/null +++ b/test/spec/modules/adglareBidAdapter_spec.js @@ -0,0 +1,138 @@ +import {expect} from 'chai'; +import {spec} from 'modules/adglareBidAdapter.js'; + +describe('AdGlare Adapter Tests', function () { + let bidRequests; + + beforeEach(function () { + bidRequests = [ + { + bidder: 'adglare', + params: { + domain: 'try.engine.adglare.net', + zID: '475579334', + type: 'banner' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + }); + + describe('implementation', function () { + describe('for requests', function () { + it('should accept valid bid', function () { + let validBid = { + bidder: 'adglare', + params: { + domain: 'try.engine.adglare.net', + zID: '475579334', + type: 'banner' + } + }, + isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('should reject invalid bid', function () { + let invalidBid = { + bidder: 'adglare', + params: { + domain: 'somedomain.com', + zID: 'not an integer', + type: 'unsupported' + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should build a valid endpoint URL', function () { + let bidRequests = [ + { + bidder: 'adglare', + params: { + domain: 'try.engine.adglare.net', + zID: '475579334', + type: 'banner' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ], + bidderRequest = { + bidderCode: 'adglare', + auctionID: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + auctionStart: 1581497568252, + timeout: 5000, + refererInfo: { + referer: 'https://www.somedomain.com', + reachedTop: true, + numFrames: 0 + }, + start: 1581497568254 + }, + requests = spec.buildRequests(bidRequests, bidderRequest), + requestURL = requests[0].url; + + expect(requestURL).to.have.string('https://try.engine.adglare.net/?475579334'); + }); + }); + + describe('bid responses', function () { + it('should return complete bid response', function () { + let serverResponse = { + body: { + status: 'OK', + zID: 475579334, + cID: 501658124, + crID: 442123173, + cpm: 1.5, + ttl: 3600, + currency: 'USD', + width: 300, + height: 250, + adhtml: 'I am an ad.' + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].bidderCode).to.equal('adglare'); + 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); + }); + + it('should return empty bid response', function () { + let serverResponse = { + body: { + status: 'NOADS' + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + }); + }); +}); diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 32658e2bb27..aa4872641b4 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/adheseBidAdapter'; +import {spec} from 'modules/adheseBidAdapter.js'; const BID_ID = 456; const TTL = 360; @@ -17,9 +17,10 @@ let minimalBid = function() { } }; -let bidWithParams = function(data) { +let bidWithParams = function(data, userId) { let bid = minimalBid(); bid.params.data = data; + bid.userId = userId; return bid; }; @@ -70,34 +71,54 @@ describe('AdheseAdapter', function () { } }; + it('should include requested slots', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).slots).to.deep.include({ 'slotname': '_main_page_-leaderboard' }); + }); + it('should include all extra bid params', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/ag25'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'ag': [ '25' ] }); }); it('should include duplicate bid params once', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }), bidWithParams({ 'ag': '25', 'ci': 'gent' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/ag25/cigent'); + expect(JSON.parse(req.data).parameters).to.deep.include({'ag': ['25']}).and.to.deep.include({ 'ci': [ 'gent' ] }); }); it('should split multiple target values', function () { let req = spec.buildRequests([ bidWithParams({ 'ci': 'london' }), bidWithParams({ 'ci': 'gent' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/cilondon;gent'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'ci': [ 'london', 'gent' ] }); + }); + + it('should filter out empty params', function () { + let req = spec.buildRequests([ bidWithParams({ 'aa': [], 'bb': null, 'cc': '', 'dd': [ '', '' ], 'ee': [ 0, 1, null ], 'ff': 0, 'gg': [ 'x', 'y', '' ] }) ], bidderRequest); + + let params = JSON.parse(req.data).parameters; + expect(params).to.not.have.any.keys('aa', 'bb', 'cc', 'dd'); + expect(params).to.deep.include({ 'ee': [ 0, 1 ], 'ff': [ 0 ], 'gg': [ 'x', 'y' ] }); }); it('should include gdpr consent param', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(req.url).to.contain('/xtCONSENT_STRING'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'xt': [ 'CONSENT_STRING' ] }); }); it('should include referer param in base64url format', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(req.url).to.contain('/xfaHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'xf': [ 'aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ' ] }); + }); + + it('should include id5 id as /x5 param', function () { + let req = spec.buildRequests([ bidWithParams({}, { 'id5id': 'ID5-1234567890' }) ], bidderRequest); + + expect(JSON.parse(req.data).parameters).to.deep.include({ 'x5': [ 'ID5-1234567890' ] }); }); it('should include bids', function () { @@ -106,6 +127,18 @@ describe('AdheseAdapter', function () { expect(req.bids).to.deep.equal([ bid ]); }); + + it('should make a POST request', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(req.method).to.equal('POST'); + }); + + it('should request the json endpoint', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(req.url).to.equal('https://ads-demo.adhese.com/json'); + }); }); describe('interpretResponse', () => { @@ -120,6 +153,7 @@ describe('AdheseAdapter', function () { origin: 'APPNEXUS', originInstance: '', ext: 'js', + slotID: '10', slotName: '_main_page_-leaderboard', adType: 'leaderboard', originData: { @@ -154,13 +188,18 @@ describe('AdheseAdapter', function () { netRevenue: NET_REVENUE, ttl: TTL, adhese: { + origin: 'APPNEXUS', + originInstance: '', originData: { + adType: 'leaderboard', seatbid: [ { bid: [ { crid: '60613369', dealid: null } ], seat: '958' } - ] + ], + slotId: '10', + slotName: '_main_page_-leaderboard' } } }]; @@ -195,7 +234,11 @@ describe('AdheseAdapter', function () { mediaType: 'video', netRevenue: NET_REVENUE, ttl: TTL, - adhese: { originData: {} } + adhese: { + origin: 'RUBICON', + originInstance: '', + originData: {} + } }]; expect(spec.interpretResponse(sspVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -247,14 +290,20 @@ describe('AdheseAdapter', function () { requestId: BID_ID, ad: '', adhese: { + origin: '', + originInstance: '', originData: { adFormat: 'largeleaderboard', + adId: '742898', adType: 'largeleaderboard', adspaceId: '162363', libId: '90511', orderProperty: undefined, priority: undefined, - viewableImpressionCounter: undefined + viewableImpressionCounter: undefined, + slotId: '29306', + slotName: '_main_page_-leaderboard', + advertiserId: '2081' } }, cpm: 5.96, @@ -302,14 +351,20 @@ describe('AdheseAdapter', function () { requestId: BID_ID, vastXml: '', adhese: { + origin: '', + originInstance: '', originData: { adFormat: '', + adId: '742470', adType: 'preroll', adspaceId: '164196', libId: '89860', orderProperty: undefined, priority: undefined, - viewableImpressionCounter: undefined + viewableImpressionCounter: undefined, + slotId: '41711', + slotName: '_main_page_-leaderboard', + advertiserId: '2263', } }, cpm: 0, diff --git a/test/spec/modules/adkernelAdnAnalytics_spec.js b/test/spec/modules/adkernelAdnAnalytics_spec.js new file mode 100644 index 00000000000..e7ef831080c --- /dev/null +++ b/test/spec/modules/adkernelAdnAnalytics_spec.js @@ -0,0 +1,268 @@ +import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter.js'; +import {expect} from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import CONSTANTS from 'src/constants.json'; + +const events = require('../../../src/events'); + +const DIRECT = { + source: '(direct)', + medium: '(direct)', + campaign: '(direct)' +}; +const REFERRER = { + source: 'lander.com', + medium: '(referral)', + campaign: '(referral)', + content: '/lander.html' +}; +const GOOGLE_ORGANIC = { + source: 'google', + medium: '(organic)', + campaign: '(organic)' +}; +const CAMPAIGN = { + source: 'adkernel', + medium: 'email', + campaign: 'new_campaign', + c1: '1', + c2: '2', + c3: '3', + c4: '4', + c5: '5' + +}; +describe('', function () { + let sandbox; + + before(function () { + sandbox = sinon.sandbox.create(); + }); + + after(function () { + sandbox.restore(); + analyticsAdapter.disableAnalytics(); + }); + + describe('UTM source parser', function () { + let stubSetItem; + let stubGetItem; + + before(function () { + stubSetItem = sandbox.stub(storage, 'setItem'); + stubGetItem = sandbox.stub(storage, 'getItem'); + }); + + afterEach(function () { + sandbox.reset(); + }); + + it('should parse first direct visit as (direct)', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com'); + expect(source).to.be.eql(DIRECT); + }); + + it('should respect past campaign visits before direct', function () { + 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', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com', 'https://www.google.com/search?q=pikachu'); + expect(source).to.be.eql(GOOGLE_ORGANIC); + }); + + it('should respect previous campaign visit before organic', function () { + 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', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com', 'http://lander.com/lander.html'); + expect(source).to.be.eql(REFERRER); + }); + + it('should respect previous campaign visit before referral', function () { + 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', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://lander.com/news.html', 'http://lander.com/lander.html'); + expect(source).to.be.eql(DIRECT); + }); + + it('should parse campaign visit', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://lander.com/index.html?utm_campaign=new_campaign&utm_source=adkernel&utm_medium=email&utm_c1=1&utm_c2=2&utm_c3=3&utm_c4=4&utm_c5=5'); + expect(source).to.be.eql(CAMPAIGN); + }); + }); + + describe('ExpiringQueue', function () { + let timer; + before(function () { + timer = sandbox.useFakeTimers(0); + }); + after(function () { + timer.restore(); + }); + + it('should notify after timeout period', (done) => { + let queue = new ExpiringQueue(() => { + let elements = queue.popAll(); + expect(elements).to.be.eql([1, 2, 3, 4]); + elements = queue.popAll(); + expect(elements).to.have.lengthOf(0); + expect(Date.now()).to.be.equal(200); + done(); + }, 100); + + queue.push(1); + setTimeout(() => { + queue.push([2, 3]); + timer.tick(50); + }, 50); + setTimeout(() => { + queue.push([4]); + timer.tick(100); + }, 100); + timer.tick(50); + }); + }); + + const REQUEST = { + bidderCode: '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', function () { + let ajaxStub; + let timer; + + before(function () { + ajaxStub = sandbox.stub(analyticsAdapter, 'ajaxCall'); + timer = sandbox.useFakeTimers(0); + }); + + beforeEach(function () { + sandbox.stub(events, 'getEvents').callsFake(() => { + return [] + }); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + it('should be configurable', function () { + adapterManager.registerAnalyticsAdapter({ + code: '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', function () { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, timeout: 3000}); + const ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(1); + expect(ev[0]).to.be.eql({event: 'auctionInit'}); + }); + + it('should handle bid request event', function () { + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); + const ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(2); + expect(ev[1]).to.be.eql({event: 'bidRequested', adapter: 'adapter', tagid: 'container-1'}); + }); + + it('should handle bid response event', function () { + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, RESPONSE); + const ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(3); + expect(ev[2]).to.be.eql({ + event: 'bidResponse', + adapter: 'adapter', + tagid: 'container-1', + val: 0.015, + time: 0.443 + }); + }); + + it('should handle auction end event', function () { + timer.tick(447); + events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); + let ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(0); + expect(ajaxStub.calledOnce).to.be.equal(true); + ev = JSON.parse(ajaxStub.firstCall.args[0]).hb_ev; + expect(ev[3]).to.be.eql({event: 'auctionEnd', time: 0.447}); + }); + + it('should handle winning bid', function () { + events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); + timer.tick(4500); + expect(ajaxStub.calledTwice).to.be.equal(true); + let ev = JSON.parse(ajaxStub.secondCall.args[0]).hb_ev; + expect(ev[0]).to.be.eql({event: 'bidWon', adapter: 'adapter', tagid: 'container-1', val: 0.015}); + }); + }); +}); diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index 98e3f6b3408..3d3e64aeec9 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/adkernelAdnBidAdapter'; +import {spec} from 'modules/adkernelAdnBidAdapter.js'; describe('AdkernelAdn adapter', function () { const bid1_pub1 = { @@ -243,17 +243,18 @@ describe('AdkernelAdn adapter', function () { expect(tagRequest).to.not.have.property('user'); }); - it('shouldn\'t contain gdpr-related information for default request', function () { + it('shouldn\'t contain gdpr nor ccpa information for default request', function () { let [_, tagRequests] = buildRequest([bid1_pub1]); expect(tagRequests[0]).to.not.have.property('user'); }); - it('should contain gdpr-related information if consent is configured', function () { + it('should contain gdpr and ccpa information if consent is configured', function () { let [_, bidRequests] = buildRequest([bid1_pub1], - {gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}); + {gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}, uspConsent: '1YNN'}); expect(bidRequests[0]).to.have.property('user'); expect(bidRequests[0].user).to.have.property('gdpr', 1); expect(bidRequests[0].user).to.have.property('consent', 'test-consent-string'); + expect(bidRequests[0].user).to.have.property('us_privacy', '1YNN'); }); it('should\'t contain consent string if gdpr isn\'t applied', function () { diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 294aba72a6b..87504aa46af 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -1,6 +1,8 @@ import {expect} from 'chai'; -import {spec} from 'modules/adkernelBidAdapter'; -import * as utils from 'src/utils'; +import {spec} from 'modules/adkernelBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {NATIVE, BANNER, VIDEO} from 'src/mediaTypes'; +import {config} from 'src/config.js'; describe('Adkernel adapter', function () { const bid1_zone1 = { @@ -105,6 +107,57 @@ describe('Adkernel adapter', function () { bidId: 'Bid_01', bidderRequestId: 'req-001', auctionId: 'auc-001' + }, bid_native = { + bidder: 'adkernel', + params: {zoneId: 1, host: 'rtb.adkernel.com'}, + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + body: { + required: true + }, + body2: { + required: true + }, + icon: { + required: true, + aspect_ratios: [{min_width: 50, min_height: 50}] + }, + image: { + required: true, + sizes: [300, 200] + }, + clickUrl: { + required: true + }, + rating: { + required: false + }, + price: { + required: false + }, + privacyLink: { + required: false + }, + cta: { + required: false + }, + sponsoredBy: { + required: false + }, + displayUrl: { + required: false + } + } + }, + adUnitCode: 'ad-unit-1', + transactionId: 'f82c64b8-c602-42a4-9791-4a268f6559ed', + bidId: 'Bid_01', + bidderRequestId: 'req-001', + auctionId: 'auc-001' }; const bidResponse1 = { @@ -118,27 +171,14 @@ describe('Adkernel adapter', function () { nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', adm: '', w: 300, - h: 250 + h: 250, + dealid: 'deal' }] }], cur: 'USD', ext: { - adk_usersync: ['https://adk.sync.com/sync'] + adk_usersync: [{type: 1, url: 'https://adk.sync.com/sync'}] } - }, bidResponse2 = { - id: 'bid2', - seatbid: [{ - bid: [{ - id: '2', - impid: 'Bid_02', - crid: '100_002', - price: 1.31, - adm: '', - w: 300, - h: 250 - }] - }], - cur: 'USD' }, videoBidResponse = { id: '47ce4badcf7482', seatbid: [{ @@ -156,17 +196,56 @@ describe('Adkernel adapter', function () { }, usersyncOnlyResponse = { id: 'nobid1', ext: { - adk_usersync: ['https://adk.sync.com/sync'] + adk_usersync: [{type: 2, url: 'https://adk.sync.com/sync'}] } + }, nativeResponse = { + id: '56fbc713-b737-4651-9050-13376aed9818', + seatbid: [{ + bid: [{ + id: 'someid_01', + impid: 'Bid_01', + price: 2.25, + adid: '4', + adm: JSON.stringify({ + native: { + assets: [ + {id: 0, title: {text: 'Title'}}, + {id: 3, data: {value: 'Description'}}, + {id: 4, data: {value: 'Additional description'}}, + {id: 1, img: {url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0&imgt=icon', w: 50, h: 50}}, + {id: 2, img: {url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0', w: 300, h: 200}}, + {id: 5, data: {value: 'Sponsor.com'}}, + {id: 14, data: {value: 'displayurl.com'}} + ], + link: {url: 'http://rtb.com/click?i=pTuOlf5KHUo_0'}, + imptrackers: ['http://rtb.com/win?i=pTuOlf5KHUo_0&f=imp'] + } + }), + adomain: ['displayurl.com'], + cid: '1', + crid: '4' + }] + }], + bidid: 'pTuOlf5KHUo', + cur: 'USD' }; + var sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { - return Object.assign({}, params, {refererInfo: {referer: url, reachedTop: true}, timeout: 3000}); + return Object.assign({}, params, {refererInfo: {referer: url, reachedTop: true}, timeout: 3000, bidderCode: 'adkernel'}); } const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST, dnt = true) { - let dntmock = sinon.stub(utils, 'getDNT').callsFake(() => dnt); + let dntmock = sandbox.stub(utils, 'getDNT').callsFake(() => dnt); let pbRequests = spec.buildRequests(bidRequests, bidderRequest); dntmock.restore(); let rtbRequests = pbRequests.map(r => JSON.parse(r.data)); @@ -191,6 +270,10 @@ describe('Adkernel adapter', function () { it('empty request shouldn\'t generate exception', function () { expect(spec.isBidRequestValid(bid_with_wrong_zoneId)).to.be.equal(false); }); + + it('valid native requests should pass', () => { + expect(spec.isBidRequestValid(bid_native)).to.be.equal(true); + }) }); describe('banner request building', function () { @@ -234,11 +317,12 @@ describe('Adkernel adapter', function () { it('should fill device with caller macro', function () { expect(bidRequest).to.have.property('device'); expect(bidRequest.device).to.have.property('ip', 'caller'); + expect(bidRequest.device).to.have.property('ipv6', 'caller'); expect(bidRequest.device).to.have.property('ua', 'caller'); expect(bidRequest.device).to.have.property('dnt', 1); }); - it('shouldn\'t contain gdpr-related information for default request', function () { + it('shouldn\'t contain gdpr nor ccpa information for default request', function () { let [_, bidRequests] = buildRequest([bid1_zone1]); expect(bidRequests[0]).to.not.have.property('regs'); expect(bidRequests[0]).to.not.have.property('user'); @@ -247,10 +331,10 @@ describe('Adkernel adapter', function () { it('should contain gdpr-related information if consent is configured', function () { let [_, bidRequests] = buildRequest([bid1_zone1], buildBidderRequest('https://example.com/index.html', - {gdprConsent: {gdprApplies: true, consentString: 'test-consent-string', vendorData: {}}})); + {gdprConsent: {gdprApplies: true, consentString: 'test-consent-string', vendorData: {}}, uspConsent: '1YNN'})); let bidRequest = bidRequests[0]; expect(bidRequest).to.have.property('regs'); - expect(bidRequest.regs.ext).to.be.eql({'gdpr': 1}); + expect(bidRequest.regs.ext).to.be.eql({'gdpr': 1, 'us_privacy': '1YNN'}); expect(bidRequest).to.have.property('user'); expect(bidRequest.user.ext).to.be.eql({'consent': 'test-consent-string'}); }); @@ -270,7 +354,6 @@ describe('Adkernel adapter', function () { it('should forward default bidder timeout', function() { let [_, bidRequests] = buildRequest([bid1_zone1], DEFAULT_BIDDER_REQUEST); - let bidRequest = bidRequests[0]; expect(bidRequests[0]).to.have.property('tmax', 3000); }); }); @@ -332,6 +415,84 @@ describe('Adkernel adapter', function () { }); }); + describe('User sync request signals', function() { + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + let [pbRequests, bidRequests] = buildRequest([bid1_zone1]); + expect(bidRequests).to.have.length(1); + expect(bidRequests[0]).to.not.have.property('ext'); + }); + + it('should respect all config node', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + let [pbRequests, bidRequests] = buildRequest([bid1_zone1]); + expect(bidRequests).to.have.length(1); + expect(bidRequests[0].ext).to.have.property('adk_usersync', 1); + }); + + it('should respect exclude filter', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: '*', + filter: 'include' + }, + iframe: { + bidders: ['adkernel'], + filter: 'exclude' + } + } + } + }); + let [pbRequests, bidRequests] = buildRequest([bid1_zone1]); + expect(bidRequests).to.have.length(1); + expect(bidRequests[0].ext).to.have.property('adk_usersync', 2); + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: ['adkernel'], + filter: 'exclude' + }, + iframe: { + bidders: ['adkernel'], + filter: 'exclude' + } + } + } + }); + let [pbRequests, bidRequests] = buildRequest([bid1_zone1]); + expect(bidRequests).to.have.length(1); + expect(bidRequests[0]).to.not.have.property('ext'); + }); + }); + describe('responses processing', function () { it('should return fully-initialized banner bid-response', function () { let [pbRequests, _] = buildRequest([bid1_zone1]); @@ -343,8 +504,9 @@ describe('Adkernel adapter', function () { 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('mediaType', BANNER); expect(resp).to.have.property('ad'); + expect(resp).to.have.property('dealId', 'deal'); expect(resp.ad).to.have.string(''); }); @@ -352,7 +514,7 @@ describe('Adkernel adapter', function () { let [pbRequests, _] = buildRequest([bid_video]); let resp = spec.interpretResponse({body: videoBidResponse}, pbRequests[0])[0]; expect(resp).to.have.property('requestId', 'Bid_Video'); - expect(resp.mediaType).to.equal('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); @@ -373,12 +535,18 @@ describe('Adkernel adapter', function () { }); it('should perform usersync', function () { - let syncs = spec.getUserSyncs({iframeEnabled: false}, [{body: bidResponse1}]); + let syncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, []); + expect(syncs).to.have.length(0); + syncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, [{body: bidResponse1}]); expect(syncs).to.have.length(0); - syncs = spec.getUserSyncs({iframeEnabled: true}, [{body: bidResponse1}]); + syncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [{body: bidResponse1}]); expect(syncs).to.have.length(1); expect(syncs[0]).to.have.property('type', 'iframe'); expect(syncs[0]).to.have.property('url', 'https://adk.sync.com/sync'); + syncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{body: usersyncOnlyResponse}]); + expect(syncs).to.have.length(1); + expect(syncs[0]).to.have.property('type', 'image'); + expect(syncs[0]).to.have.property('url', 'https://adk.sync.com/sync'); }); }); @@ -388,4 +556,49 @@ describe('Adkernel adapter', function () { expect(spec.aliases).to.include.members(['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon']); }); }); + + describe('native support', () => { + let _, bidRequests; + before(function () { + [_, bidRequests] = buildRequest([bid_native]); + }); + + it('native request building', () => { + expect(bidRequests[0].imp).to.have.length(1); + expect(bidRequests[0].imp[0]).to.have.property('native'); + expect(bidRequests[0].imp[0].native).to.have.property('request'); + let request = JSON.parse(bidRequests[0].imp[0].native.request); + expect(request).to.have.property('ver', '1.1'); + expect(request.assets).to.have.length(10); + expect(request.assets[0]).to.be.eql({id: 0, required: 1, title: {len: 80}}); + expect(request.assets[1]).to.be.eql({id: 3, required: 1, data: {type: 2}}); + expect(request.assets[2]).to.be.eql({id: 4, required: 1, data: {type: 10}}); + expect(request.assets[3]).to.be.eql({id: 1, required: 1, img: {wmin: 50, hmin: 50, type: 1}}); + expect(request.assets[4]).to.be.eql({id: 2, required: 1, img: {w: 300, h: 200, type: 3}}); + expect(request.assets[5]).to.be.eql({id: 11, required: 0, data: {type: 3}}); + expect(request.assets[6]).to.be.eql({id: 8, required: 0, data: {type: 6}}); + expect(request.assets[7]).to.be.eql({id: 10, required: 0, data: {type: 12}}); + expect(request.assets[8]).to.be.eql({id: 5, required: 0, data: {type: 1}}); + expect(request.assets[9]).to.be.eql({id: 14, required: 0, data: {type: 11}}); + }); + + it('native response processing', () => { + let [pbRequests, _] = buildRequest([bid_native]); + let resp = spec.interpretResponse({body: nativeResponse}, pbRequests[0])[0]; + expect(resp).to.have.property('requestId', 'Bid_01'); + expect(resp).to.have.property('cpm', 2.25); + expect(resp).to.have.property('currency', 'USD'); + expect(resp).to.have.property('mediaType', NATIVE); + expect(resp).to.have.property('native'); + expect(resp.native).to.have.property('clickUrl', 'http://rtb.com/click?i=pTuOlf5KHUo_0'); + expect(resp.native.impressionTrackers).to.be.eql(['http://rtb.com/win?i=pTuOlf5KHUo_0&f=imp']); + expect(resp.native).to.have.property('title', 'Title'); + expect(resp.native).to.have.property('body', 'Description'); + expect(resp.native).to.have.property('body2', 'Additional description'); + expect(resp.native.icon).to.be.eql({url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0&imgt=icon', width: 50, height: 50}); + expect(resp.native.image).to.be.eql({url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0', width: 300, height: 200}); + expect(resp.native).to.have.property('sponsoredBy', 'Sponsor.com'); + expect(resp.native).to.have.property('displayUrl', 'displayurl.com'); + }); + }); }); diff --git a/test/spec/modules/adliveBidAdapter_spec.js b/test/spec/modules/adliveBidAdapter_spec.js index 0048fc028b8..ddf8f82f20f 100644 --- a/test/spec/modules/adliveBidAdapter_spec.js +++ b/test/spec/modules/adliveBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/adliveBidAdapter'; +import { spec } from 'modules/adliveBidAdapter.js'; describe('adliveBidAdapterTests', function() { let bidRequestData = { diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js new file mode 100644 index 00000000000..f3212dec2f5 --- /dev/null +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -0,0 +1,231 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/admanBidAdapter.js'; + +describe('AdmanMediaBidAdapter', function () { + let bid = { + bidId: '23fhj33i987f', + bidder: 'adman', + params: { + placementId: 0, + traffic: 'banner' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://pub.admanmedia.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', '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', 'sizes'); + 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', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://pub.admanmedia.com/?c=o&m=sync'); + }); + }); +}); diff --git a/test/spec/modules/admediaBidAdapter_spec.js b/test/spec/modules/admediaBidAdapter_spec.js index 78228b85bff..5dc7b9a02a8 100644 --- a/test/spec/modules/admediaBidAdapter_spec.js +++ b/test/spec/modules/admediaBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/admediaBidAdapter'; +import { spec } from 'modules/admediaBidAdapter.js'; describe('admediaAdapterTests', function () { describe('bidRequestValidity', function () { diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js new file mode 100644 index 00000000000..6d2e3059dc8 --- /dev/null +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -0,0 +1,122 @@ +import {expect} from 'chai'; +import {spec} from 'modules/admixerBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'admixer'; +const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.0.aspx'; +const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; + +describe('AdmixerAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.be.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': BIDDER_CODE, + 'params': { + 'zone': ZONE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let validRequest = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'zone': ZONE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + let bidderRequest = { + refererInfo: { + referer: 'https://example.com' + } + }; + + it('should add referrer and imp to be equal bidRequest', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + const payload = JSON.parse(request.data.substr(5)); + expect(payload.referrer).to.not.be.undefined; + expect(payload.imps[0]).to.deep.equal(validRequest[0]); + }); + + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.method).to.equal('GET'); + }); + }); + + describe('interpretResponse', function () { + let response = { + body: [{ + 'currency': 'USD', + 'cpm': 6.210000, + 'ad': '
ad
', + 'width': 300, + 'height': 600, + 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', + 'ttl': 360, + 'netRevenue': false, + 'bidId': '5e4e763b6bc60b' + }] + }; + + it('should get correct bid response', function () { + const body = response.body; + let expectedResponse = [ + { + 'requestId': body[0].bidId, + 'cpm': body[0].cpm, + 'creativeId': body[0].creativeId, + 'width': body[0].width, + 'height': body[0].height, + 'ad': body[0].ad, + 'vastUrl': undefined, + 'currency': body[0].currency, + 'netRevenue': body[0].netRevenue, + 'ttl': body[0].ttl, + } + ]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles nobid responses', function () { + let response = []; + + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js new file mode 100644 index 00000000000..54ff038c083 --- /dev/null +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -0,0 +1,119 @@ +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from 'modules/adnuntiusBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('adnuntiusBidAdapter', function () { + const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo=-60&format=json'; + const adapter = newBidder(spec); + const bidRequests = [ + { + bidder: 'adnuntius', + params: { + auId: '8b6bc', + network: 'adnuntius', + }, + bidId: '123' + } + ]; + + const serverResponse = { + body: { + 'adUnits': [ + { + 'auId': '000000000008b6bc', + 'targetId': '', + 'html': '

hi!

', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp-1460129238', + 'ads': [ + { + 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', + 'assets': { + 'image': { + 'cdnId': 'https://assets.adnuntius.com/oEmZa5uYjxENfA1R692FVn6qIveFpO8wUbpyF2xSOCc.jpg', + 'width': '980', + 'height': '120' + } + }, + 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'urls': { + 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' + }, + 'urlsEsc': { + 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' + }, + 'destinationUrls': { + 'destination': 'http://google.com' + }, + 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, + 'bid': { 'amount': 0.005, 'currency': 'NOK' }, + 'cost': { 'amount': 0.005, 'currency': 'NOK' }, + 'impressionTrackingUrls': [], + 'impressionTrackingUrlsEsc': [], + 'adId': 'adn-id-1347343135', + 'selectedColumn': '0', + 'selectedColumnPosition': '0', + 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'creativeWidth': '980', + 'creativeHeight': '120', + 'creativeId': 'wgkq587vgtpchsx1', + 'lineItemId': 'scyjdyv3mzgdsnpf', + 'layoutId': 'sw6gtws2rdj1kwby', + 'layoutName': 'Responsive image' + } + ] + } + ] + } + } + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bidRequests[0])).to.equal(true); + }); + }); + + describe('buildRequests', function () { + it('Test requests', function () { + const request = spec.buildRequests(bidRequests); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('bid'); + const bid = request[0].bid[0] + expect(bid).to.have.property('bidId'); + expect(request[0]).to.have.property('url'); + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[0]).to.have.property('data'); + expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\"}]}'); + }); + }); + + describe('interpretResponse', function () { + it('should return valid response when passed valid server response', function () { + const request = spec.buildRequests(bidRequests); + const interpretedResponse = spec.interpretResponse(serverResponse, request[0]); + const ad = serverResponse.body.adUnits[0].ads[0] + expect(interpretedResponse).to.have.lengthOf(1); + expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); + expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[0].currency).to.equal(ad.cpm.currency); + expect(interpretedResponse[0].netRevenue).to.equal(false); + expect(interpretedResponse[0].ad).to.equal(serverResponse.body.adUnits[0].html); + expect(interpretedResponse[0].ttl).to.equal(360); + }); + }); +}); diff --git a/test/spec/modules/adoceanBidAdapter_spec.js b/test/spec/modules/adoceanBidAdapter_spec.js index 1fd009ce526..2b4b7d711e1 100644 --- a/test/spec/modules/adoceanBidAdapter_spec.js +++ b/test/spec/modules/adoceanBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { spec } from 'modules/adoceanBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/adoceanBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { deepClone } from 'src/utils.js'; describe('AdoceanAdapter', function () { const adapter = newBidder(spec); @@ -20,7 +21,11 @@ describe('AdoceanAdapter', function () { 'emiter': 'myao.adocean.pl' }, 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -51,7 +56,11 @@ describe('AdoceanAdapter', function () { 'emiter': 'myao.adocean.pl' }, 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -64,7 +73,11 @@ describe('AdoceanAdapter', function () { 'emiter': 'myao.adocean.pl' }, 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 200], [600, 250]] + } + }, 'bidId': '30b31c1838de1f', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -78,12 +91,12 @@ describe('AdoceanAdapter', function () { } }; - it('should send two requests if slave is duplicated', () => { + it('should send two requests if slave is duplicated', function () { const nrOfRequests = spec.buildRequests(bidRequests, bidderRequest).length; expect(nrOfRequests).to.equal(2); }); - it('should add bidIdMap with correct slaveId => bidId mapping', () => { + it('should add bidIdMap with correct slaveId => bidId mapping', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (let i = 0; i < bidRequests.length; i++) { expect(requests[i]).to.exist; @@ -95,7 +108,7 @@ describe('AdoceanAdapter', function () { it('sends bid request to url via GET', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.method).to.equal('GET'); - expect(request.url).to.match(new RegExp(`^https://${bidRequests[0].params.emiter}/ad.json`)); + expect(request.url).to.match(new RegExp(`^https://${bidRequests[0].params.emiter}/_[0-9]*/ad.json`)); }); it('should attach id to url', function () { @@ -108,6 +121,19 @@ describe('AdoceanAdapter', function () { expect(request.url).to.include('gdpr=1'); expect(request.url).to.include('gdpr_consent=' + bidderRequest.gdprConsent.consentString); }); + + it('should attach sizes information to url', function () { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.include('aosspsizes=myaozpniqismex~300x250_300x600'); + expect(requests[1].url).to.include('aosspsizes=myaozpniqismex~300x200_600x250'); + + const differentSlavesBids = deepClone(bidRequests); + differentSlavesBids[1].params.slaveId = 'adoceanmyaowafpdwlrks'; + requests = spec.buildRequests(differentSlavesBids, bidderRequest); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.include('aosspsizes=myaozpniqismex~300x250_300x600-myaowafpdwlrks~300x200_600x250'); + expect((requests[0].url.match(/aosspsizes=/g) || []).length).to.equal(1); + }); }) describe('interpretResponse', function () { diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js index 16480d746b7..1414b2402b9 100644 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ b/test/spec/modules/adomikAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import adomikAnalytics from 'modules/adomikAnalyticsAdapter'; +import adomikAnalytics from 'modules/adomikAnalyticsAdapter.js'; import {expect} from 'chai'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; @@ -7,6 +7,13 @@ let constants = require('src/constants.json'); describe('Adomik Prebid Analytic', function () { let sendEventStub; let sendWonEventStub; + let clock; + before(function () { + clock = sinon.useFakeTimers(); + }); + after(function () { + clock.restore(); + }); describe('enableAnalytics', function () { beforeEach(function () { @@ -120,7 +127,6 @@ describe('Adomik Prebid Analytic', function () { expect(adomikAnalytics.currentContext.timeouted).to.equal(true); // Step 7: Send auction end event - var clock = sinon.useFakeTimers(); events.emit(constants.EVENTS.AUCTION_END, {}); setTimeout(function() { @@ -130,7 +136,6 @@ describe('Adomik Prebid Analytic', function () { }, 3000); clock.tick(5000); - clock.restore(); sinon.assert.callCount(adomikAnalytics.track, 6); }); diff --git a/test/spec/modules/adotBidAdapter_spec.js b/test/spec/modules/adotBidAdapter_spec.js new file mode 100644 index 00000000000..594fc4ac7b7 --- /dev/null +++ b/test/spec/modules/adotBidAdapter_spec.js @@ -0,0 +1,3118 @@ +import { expect } from 'chai'; +import { executeRenderer } from 'src/Renderer.js'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/adotBidAdapter.js'; + +const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding/bidrequest'; + +describe('Adot Adapter', function () { + const examples = { + adUnit_banner: { + adUnitCode: 'ad_unit_banner', + bidder: 'adot', + bidderRequestId: 'bid_request_id', + bidId: 'bid_id', + params: {}, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }, + + adUnit_video_outstream: { + adUnitCode: 'ad_unit_video_outstream', + bidder: 'adot', + bidderRequestId: 'bid_request_id', + bidId: 'bid_id', + params: { + video: { + mimes: ['video/mp4'], + minDuration: 5, + maxDuration: 30, + protocols: [2, 3] + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[300, 250]] + } + } + }, + + adUnit_video_instream: { + adUnitCode: 'ad_unit_video_instream', + bidder: 'adot', + bidderRequestId: 'bid_request_id', + bidId: 'bid_id', + params: { + video: { + instreamContext: 'pre-roll', + mimes: ['video/mp4'], + minDuration: 5, + maxDuration: 30, + protocols: [2, 3] + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[300, 250]] + } + } + }, + + adUnitContext: { + refererInfo: { + referer: 'https://we-are-adot.com/test', + }, + gdprConsent: { + consentString: 'consent_string', + gdprApplies: true + } + }, + + adUnit_position: { + adUnitCode: 'ad_unit_position', + bidder: 'adot', + bidderRequestId: 'bid_request_id', + bidId: 'bid_id', + params: { + position: 1 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }, + + adUnit_native: { + adUnitCode: 'ad_unit_native', + bidder: 'adot', + bidderRequestId: 'bid_request_id', + bidId: 'bid_id', + params: {}, + mediaTypes: { + native: { + title: {required: true, len: 140}, + icon: {required: true, sizes: [50, 50]}, + image: {required: false, sizes: [320, 200]}, + sponsoredBy: {required: false}, + body: {required: false}, + cta: {required: true} + } + } + }, + + serverRequest_banner: { + method: 'POST', + url: 'https://we-are-adot.com/bidrequest', + data: { + id: 'bid_request_id', + imp: [ + { + id: 'imp_id_banner_0_0', + banner: { + format: [{ + w: 300, + h: 200 + }] + }, + video: null + } + ], + site: { + page: 'https://we-are-adot.com/test', + domain: 'we-are-adot.com', + name: 'we-are-adot.com' + }, + device: { + ua: '', + language: 'en' + }, + user: null, + regs: null, + at: 1, + ext: { + adot: { + 'adapter_version': 'v1.0.0' + } + } + }, + _adot_internal: { + impressions: [ + { + impressionId: 'imp_id_banner_0_0', + adUnitCode: 'ad_unit_banner', + bidId: 'imp_id_banner' + } + ] + } + }, + + serverRequest_banner_twoImps: { + method: 'POST', + url: 'https://we-are-adot.com/bidrequest', + data: { + id: 'bid_request_id', + imp: [ + { + id: 'imp_id_banner_0_0', + banner: { + format: [{ + w: 300, + h: 200 + }] + }, + video: null + }, + { + id: 'imp_id_banner_2_0_0', + banner: { + format: [{ + w: 300, + h: 200 + }] + }, + video: null + } + ], + site: { + page: 'https://we-are-adot.com/test', + domain: 'we-are-adot.com', + name: 'we-are-adot.com' + }, + device: { + ua: '', + language: 'en' + }, + user: null, + regs: null, + at: 1, + ext: { + adot: { + 'adapter_version': 'v1.0.0' + } + } + }, + _adot_internal: { + impressions: [ + { + impressionId: 'imp_id_banner_0_0', + adUnitCode: 'ad_unit_banner', + bidId: 'imp_id_banner' + }, + { + impressionId: 'imp_id_banner_2_0_0', + adUnitCode: 'ad_unit_banner_2', + bidId: 'imp_id_banner_2' + } + ] + } + }, + + serverRequest_video_instream: { + method: 'POST', + url: 'https://we-are-adot.com/bidrequest', + data: { + id: 'bid_request_id', + imp: [ + { + id: 'imp_id_video_instream_0', + banner: null, + video: { + mimes: ['video/mp4'], + w: 300, + h: 200, + startdelay: 0, + minduration: 5, + maxduration: 35, + protocols: [2, 3] + } + } + ], + site: { + page: 'https://we-are-adot.com/test', + domain: 'we-are-adot.com', + name: 'we-are-adot.com' + }, + device: { + ua: '', + language: 'en' + }, + user: null, + regs: null, + at: 1, + ext: { + adot: { + 'adapter_version': 'v1.0.0' + } + } + }, + _adot_internal: { + impressions: [ + { + impressionId: 'imp_id_video_instream_0', + adUnitCode: 'ad_unit_video_instream', + bidId: 'imp_id_video_instream' + } + ] + } + }, + + serverRequest_video_outstream: { + method: 'POST', + url: 'https://we-are-adot.com/bidrequest', + data: { + id: 'bid_request_id', + imp: [ + { + id: 'imp_id_video_outstream_0', + banner: null, + video: { + mimes: ['video/mp4'], + w: 300, + h: 200, + startdelay: null, + minduration: 5, + maxduration: 35, + protocols: [2, 3] + } + } + ], + site: { + page: 'https://we-are-adot.com/test', + domain: 'we-are-adot.com', + name: 'we-are-adot.com' + }, + device: { + ua: '', + language: 'en' + }, + user: null, + regs: null, + at: 1, + ext: { + adot: { + 'adapter_version': 'v1.0.0' + } + } + }, + _adot_internal: { + impressions: [ + { + impressionId: 'imp_id_video_outstream_0', + adUnitCode: 'ad_unit_video_outstream', + bidId: 'imp_id_video_outstream' + } + ] + } + }, + + serverRequest_video_instream_outstream: { + method: 'POST', + url: 'https://we-are-adot.com/bidrequest', + data: { + id: 'bid_request_id', + imp: [ + { + id: 'imp_id_video_instream_0', + banner: null, + video: { + mimes: ['video/mp4'], + w: 300, + h: 200, + startdelay: 0, + minduration: 5, + maxduration: 35, + protocols: [2, 3] + } + }, + { + id: 'imp_id_video_outstream_0', + banner: null, + video: { + mimes: ['video/mp4'], + w: 300, + h: 200, + startdelay: null, + minduration: 5, + maxduration: 35, + protocols: [2, 3] + } + } + ], + site: { + page: 'https://we-are-adot.com/test', + domain: 'we-are-adot.com', + name: 'we-are-adot.com' + }, + device: { + ua: '', + language: 'en' + }, + user: null, + regs: null, + at: 1, + ext: { + adot: { + 'adapter_version': 'v1.0.0' + } + } + }, + _adot_internal: { + impressions: [ + { + impressionId: 'imp_id_video_instream_0', + adUnitCode: 'ad_unit_video_instream', + bidId: 'imp_id_video_instream' + }, + { + impressionId: 'imp_id_video_outstream_0', + adUnitCode: 'ad_unit_video_outstream', + bidId: 'imp_id_video_outstream' + } + ] + } + }, + + serverRequest_position: { + method: 'POST', + url: 'https://we-are-adot.com/bidrequest', + data: { + id: 'bid_request_id', + imp: [ + { + id: 'imp_id_banner', + banner: { + format: [{ + w: 300, + h: 200 + }], + position: 1 + }, + video: null + } + ], + site: { + page: 'https://we-are-adot.com/test', + domain: 'we-are-adot.com', + name: 'we-are-adot.com' + }, + device: { + ua: '', + language: 'en' + }, + user: null, + regs: null, + at: 1, + ext: { + adot: { + 'adapter_version': 'v1.0.0' + } + } + }, + _adot_internal: { + impressions: [ + { + impressionId: 'imp_id_banner', + adUnitCode: 'ad_unit_position' + } + ] + } + }, + + serverRequest_native: { + method: 'POST', + url: 'https://we-are-adot.com/bidrequest', + data: { + id: 'bid_request_id', + imp: [ + { + id: 'imp_id_native_0', + native: { + request: { + assets: [ + { + id: 1, + required: true, + title: { + len: 140 + } + }, + { + id: 2, + required: true, + img: { + type: 1, + wmin: 50, + hmin: 50 + } + }, + { + id: 3, + required: false, + img: { + type: 3, + wmin: 320, + hmin: 200 + } + }, + { + id: 4, + required: false, + data: { + type: 1 + } + }, + { + id: 5, + required: false, + data: { + type: 2 + } + }, + { + id: 6, + required: true, + data: { + type: 12 + } + } + ] + } + }, + video: null, + banner: null + } + ], + site: { + page: 'https://we-are-adot.com/test', + domain: 'we-are-adot.com', + name: 'we-are-adot.com' + }, + device: { + ua: '', + language: 'en' + }, + user: null, + regs: null, + at: 1, + ext: { + adot: { + 'adapter_version': 'v1.0.0' + } + } + }, + _adot_internal: { + impressions: [ + { + impressionId: 'imp_id_native_0', + adUnitCode: 'ad_unit_native', + bidId: 'imp_id_native' + } + ] + } + }, + + serverResponse_banner: { + body: { + cur: 'EUR', + seatbid: [ + { + bid: [ + { + impid: 'imp_id_banner_0_0', + crid: 'creative_id', + adm: 'creative_data_${AUCTION_PRICE}', + nurl: 'win_notice_url_${AUCTION_PRICE}', + price: 1.5, + h: 350, + w: 300, + ext: { + adot: { + media_type: 'banner' + } + } + } + ] + } + ] + } + }, + + serverResponse_banner_twoBids: { + body: { + cur: 'EUR', + seatbid: [ + { + bid: [ + { + impid: 'imp_id_banner_0_0', + crid: 'creative_id', + adm: 'creative_data_${AUCTION_PRICE}', + nurl: 'win_notice_url_${AUCTION_PRICE}', + price: 1.5, + h: 350, + w: 300, + ext: { + adot: { + media_type: 'banner' + } + } + }, + { + impid: 'imp_id_banner_2_0_0', + crid: 'creative_id_2', + adm: 'creative_data_2_${AUCTION_PRICE}', + nurl: 'win_notice_url_2_${AUCTION_PRICE}', + price: 2.5, + h: 400, + w: 350, + ext: { + adot: { + media_type: 'banner' + } + } + } + ] + } + ] + } + }, + + serverResponse_video_instream: { + body: { + cur: 'EUR', + seatbid: [ + { + bid: [ + { + impid: 'imp_id_video_instream_0', + crid: 'creative_id', + adm: 'creative_data_${AUCTION_PRICE}', + nurl: 'win_notice_url_${AUCTION_PRICE}', + price: 1.5, + ext: { + adot: { + media_type: 'video' + } + } + } + ] + } + ] + } + }, + + serverResponse_video_outstream: { + body: { + cur: 'EUR', + seatbid: [ + { + bid: [ + { + impid: 'imp_id_video_outstream_0', + crid: 'creative_id', + adm: 'creative_data_${AUCTION_PRICE}', + nurl: 'win_notice_url_${AUCTION_PRICE}', + price: 1.5, + ext: { + adot: { + media_type: 'video' + } + } + } + ] + } + ] + } + }, + + serverResponse_video_instream_outstream: { + body: { + cur: 'EUR', + seatbid: [ + { + bid: [ + { + impid: 'imp_id_video_instream_0', + crid: 'creative_id', + adm: 'creative_data_${AUCTION_PRICE}', + nurl: 'win_notice_url_${AUCTION_PRICE}', + price: 1.5, + ext: { + adot: { + media_type: 'video' + } + } + }, + { + impid: 'imp_id_video_outstream_0', + crid: 'creative_id', + adm: 'creative_data_${AUCTION_PRICE}', + nurl: 'win_notice_url_${AUCTION_PRICE}', + price: 1.5, + ext: { + adot: { + media_type: 'video' + } + } + } + ] + } + ] + } + }, + + serverResponse_native: { + body: { + cur: 'EUR', + seatbid: [ + { + bid: [ + { + impid: 'imp_id_native_0', + crid: 'creative_id', + adm: '{"native":{"assets":[{"id":1,"title":{"len":140,"text":"Hi everyone"}},{"id":2,"img":{"url":"https://adotmob.com","type":1,"w":50,"h":50}},{"id":3,"img":{"url":"https://adotmob.com","type":3,"w":320,"h":200}},{"id":4,"data":{"type":1,"value":"adotmob"}},{"id":5,"data":{"type":2,"value":"This is a test ad"}},{"id":6,"data":{"type":12,"value":"Click to buy"}}],"link":{"url":"https://adotmob.com?auction=${AUCTION_PRICE}"}}}', + nurl: 'win_notice_url_${AUCTION_PRICE}', + price: 1.5, + ext: { + adot: { + media_type: 'native' + } + } + } + ] + } + ] + } + } + }; + + describe('isBidRequestValid', function () { + describe('General', function () { + it('should return false when not given an ad unit', function () { + const adUnit = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an invalid ad unit', function () { + const adUnit = 'bad_bid'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without bidder code', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidder = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a bad bidder code', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidder = 'unknownBidder'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without ad unit code', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.adUnitCode = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid ad unit code', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.adUnitCode = {}; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without bid request identifier', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidderRequestId = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid bid request identifier', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidderRequestId = {}; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without impression identifier', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidId = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid impression identifier', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidId = {}; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without media types', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with empty media types', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes = {}; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with invalid media types', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes = 'bad_media_types'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + }); + + describe('Banner', function () { + it('should return true when given a valid ad unit', function () { + const adUnit = examples.adUnit_banner; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given a valid ad unit without bidder parameters', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.params = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return false when given an ad unit without size', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid size', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = 'bad_banner_size'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an empty size', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = []; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid size value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = ['bad_banner_size_value']; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a size value with less than 2 dimensions', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[300]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a size value with more than 2 dimensions', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[300, 250, 30]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a negative width value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[-300, 250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a negative height value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[300, -250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid width value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[false, 250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid height value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[300, {}]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + }); + + describe('Video', function () { + it('should return true when given a valid outstream ad unit', function () { + const adUnit = examples.adUnit_video_outstream; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given a valid pre-roll instream ad unit', function () { + const adUnit = utils.deepClone(examples.adUnit_video_instream); + adUnit.params.video.instreamContext = 'pre-roll'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given a valid mid-roll instream ad unit', function () { + const adUnit = utils.deepClone(examples.adUnit_video_instream); + adUnit.params.video.instreamContext = 'mid-roll'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given a valid post-roll instream ad unit', function () { + const adUnit = utils.deepClone(examples.adUnit_video_instream); + adUnit.params.video.instreamContext = 'post-roll'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given an ad unit without size', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given an ad unit with an empty size', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = []; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given an ad unit without minimum duration parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.minDuration = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given an ad unit without maximum duration parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.maxDuration = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return false when given an ad unit without bidder parameters', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with invalid bidder parameters', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params = 'bad_bidder_parameters'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without video parameters', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with invalid video parameters', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video = 'bad_bidder_parameters'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without mime types parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.mimes = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid mime types parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.mimes = 'bad_mime_types'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an empty mime types parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.mimes = []; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid mime types parameter value', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.mimes = [200]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid minimum duration parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.minDuration = 'bad_min_duration'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid maximum duration parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.maxDuration = 'bad_max_duration'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without protocols parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.protocols = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid protocols parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.protocols = 'bad_protocols'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an empty protocols parameter', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.protocols = []; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid protocols parameter value', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.protocols = ['bad_protocols_value']; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an instream ad unit without instream context', function () { + const adUnit = utils.deepClone(examples.adUnit_video_instream); + adUnit.params.video.instreamContext = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an instream ad unit with an invalid instream context', function () { + const adUnit = utils.deepClone(examples.adUnit_video_instream); + adUnit.params.video.instreamContext = 'bad_instream_context'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without context', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.context = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid context', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.context = []; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an adpod ad unit', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.context = 'adpod'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an unknown context', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.context = 'invalid_context'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid size', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = 'bad_video_size'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid size value', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = ['bad_video_size_value']; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a size value with less than 2 dimensions', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = [[300]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a size value with more than 2 dimensions', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = [[300, 250, 30]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a negative width value', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = [[-300, 250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a negative height value', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = [[300, -250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid width value', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = [[false, 250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid height value', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = [[300, {}]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + }); + }); + + describe('buildRequests', function () { + describe('ServerRequest', function () { + it('should return a server request when given a valid ad unit and a valid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.length(1); + expect(serverRequests[0].method).to.exist.and.to.be.a('string').and.to.equal('POST'); + expect(serverRequests[0].url).to.exist.and.to.be.a('string').and.to.equal(BIDDER_URL); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal.impressions).to.exist.and.to.be.an('array').and.to.have.length(1); + expect(serverRequests[0]._adot_internal.impressions[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal.impressions[0].impressionId).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); + expect(serverRequests[0]._adot_internal.impressions[0].adUnitCode).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].adUnitCode); + }); + + it('should return a server request containing a position when given a valid ad unit and a valid ad unit context and a position in the bidder params', function () { + const adUnits = [examples.adUnit_position]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.length(1); + expect(serverRequests[0].method).to.exist.and.to.be.a('string').and.to.equal('POST'); + expect(serverRequests[0].url).to.exist.and.to.be.a('string').and.to.equal(BIDDER_URL); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal.impressions).to.exist.and.to.be.an('array').and.to.have.length(1); + expect(serverRequests[0]._adot_internal.impressions[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal.impressions[0].impressionId).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); + expect(serverRequests[0]._adot_internal.impressions[0].adUnitCode).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].adUnitCode); + expect(serverRequests[0].data.imp[0].banner.pos).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.position); + }); + + it('should return a server request when given two valid ad units and a valid ad unit context', function () { + const adUnits_1 = utils.deepClone(examples.adUnit_banner); + adUnits_1.bidId = 'bid_id_1'; + adUnits_1.adUnitCode = 'ad_unit_banner_1'; + + const adUnits_2 = utils.deepClone(examples.adUnit_banner); + adUnits_2.bidId = 'bid_id_2'; + adUnits_2.adUnitCode = 'ad_unit_banner_2'; + + const adUnits = [adUnits_1, adUnits_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.length(1); + expect(serverRequests[0].method).to.exist.and.to.be.a('string').and.to.equal('POST'); + expect(serverRequests[0].url).to.exist.and.to.be.a('string').and.to.equal(BIDDER_URL); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal.impressions).to.exist.and.to.be.an('array').and.to.have.length(2); + expect(serverRequests[0]._adot_internal.impressions[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal.impressions[0].impressionId).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); + expect(serverRequests[0]._adot_internal.impressions[0].adUnitCode).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].adUnitCode); + expect(serverRequests[0]._adot_internal.impressions[1]).to.exist.and.to.be.an('object'); + expect(serverRequests[0]._adot_internal.impressions[1].impressionId).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0_0`); + expect(serverRequests[0]._adot_internal.impressions[1].adUnitCode).to.exist.and.to.be.a('string').and.to.equal(adUnits[1].adUnitCode); + }); + + it('should return an empty server request list when given an empty ad unit list and a valid ad unit context', function () { + const adUnits = []; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.length(0); + }); + + it('should not return a server request when given no ad unit and a valid ad unit context', function () { + const serverRequests = spec.buildRequests(null, examples.adUnitContext); + + expect(serverRequests).to.equal(null); + }); + + it('should not return a server request when given a valid ad unit and no ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, null); + + expect(serverRequests).to.be.an('array').and.to.have.length(1); + }); + + it('should not return a server request when given a valid ad unit and an invalid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, {}); + + expect(serverRequests).to.be.an('array').and.to.have.length(1); + }); + }); + + describe('BidRequest', function () { + it('should return a valid server request when given a valid ad unit', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.at).to.exist.and.to.be.a('number').and.to.equal(1); + expect(serverRequests[0].data.ext).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.ext.adot).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.ext.adot.adapter_version).to.exist.and.to.be.a('string').and.to.equal('v1.0.0'); + }); + + it('should return one server request when given one valid ad unit', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); + }); + + it('should return one server request when given two valid ad units with different impression identifiers', function () { + const adUnit_1 = utils.deepClone(examples.adUnit_banner); + adUnit_1.bidId = 'bid_id_1'; + + const adUnit_2 = utils.deepClone(examples.adUnit_banner); + adUnit_2.bidId = 'bid_id_2'; + + const adUnits = [adUnit_1, adUnit_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[1].bidderRequestId); + }); + + it('should return two server requests when given two valid ad units with different bid request identifiers', function () { + const adUnit_1 = utils.deepClone(examples.adUnit_banner); + adUnit_1.bidderRequestId = 'bidder_request_id_1'; + + const adUnit_2 = utils.deepClone(examples.adUnit_banner); + adUnit_2.bidderRequestId = 'bidder_request_id_2'; + + const adUnits = [adUnit_1, adUnit_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(2); + expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[1].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[1].bidderRequestId); + }); + }); + + describe('Impression', function () { + describe('Banner', function () { + it('should return a server request with one impression when given a valid ad unit', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); + expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][0]); + expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][1]); + expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[0].mediaTypes.banner.sizes[0][0], + h: adUnits[0].mediaTypes.banner.sizes[0][1] + }); + }); + + it('should return a server request with two impressions containing one banner formats when given a valid ad unit with two banner sizes', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [ + [300, 250], + [350, 300] + ]; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(2); + + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); + expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][0]); + expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][1]); + expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[0].mediaTypes.banner.sizes[0][0], + h: adUnits[0].mediaTypes.banner.sizes[0][1] + }); + + expect(serverRequests[0].data.imp[1]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[1].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_1`); + expect(serverRequests[0].data.imp[1].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[1].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[1][0]); + expect(serverRequests[0].data.imp[1].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[1][1]); + expect(serverRequests[0].data.imp[1].banner.format).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[1].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[0].mediaTypes.banner.sizes[1][0], + h: adUnits[0].mediaTypes.banner.sizes[1][1] + }); + }); + + it('should return a server request with two impressions when given two valid ad units with different impression identifiers', function () { + const adUnit_1 = utils.deepClone(examples.adUnit_banner); + adUnit_1.bidId = 'bid_id_1'; + + const adUnit_2 = utils.deepClone(examples.adUnit_banner); + adUnit_2.bidId = 'bid_id_2'; + + const adUnits = [adUnit_1, adUnit_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(2); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); + expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][0]); + expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][1]); + expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array'); + expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[0].mediaTypes.banner.sizes[0][0], + h: adUnits[0].mediaTypes.banner.sizes[0][1] + }); + expect(serverRequests[0].data.imp[1]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[1].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0_0`); + expect(serverRequests[0].data.imp[1].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[1].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][0]); + expect(serverRequests[0].data.imp[1].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][1]); + expect(serverRequests[0].data.imp[1].banner.format).to.exist.and.to.be.an('array'); + expect(serverRequests[0].data.imp[1].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[1].mediaTypes.banner.sizes[0][0], + h: adUnits[1].mediaTypes.banner.sizes[0][1] + }); + }); + + it('should return a server request with one overriden impression when given two valid ad units with identical identifiers', function () { + const adUnit_1 = utils.deepClone(examples.adUnit_banner); + adUnit_1.mediaTypes.banner.sizes = [[300, 250]]; + + const adUnit_2 = utils.deepClone(examples.adUnit_banner); + adUnit_2.mediaTypes.banner.sizes = [[350, 300]]; + + const adUnits = [adUnit_1, adUnit_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0_0`); + expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][0]); + expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][1]); + expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array'); + expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[1].mediaTypes.banner.sizes[0][0], + h: adUnits[1].mediaTypes.banner.sizes[0][1] + }); + }); + + it('should return two server requests with one impression when given two valid ad units with different bid request identifiers', function () { + const adUnit_1 = utils.deepClone(examples.adUnit_banner); + adUnit_1.bidderRequestId = 'bidder_request_id_1'; + + const adUnit_2 = utils.deepClone(examples.adUnit_banner); + adUnit_2.bidderRequestId = 'bidder_request_id_2'; + + const adUnits = [adUnit_1, adUnit_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(2); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); + expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][0]); + expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][1]); + expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array'); + expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[0].mediaTypes.banner.sizes[0][0], + h: adUnits[0].mediaTypes.banner.sizes[0][1] + }); + expect(serverRequests[1].data).to.exist.and.to.be.an('object'); + expect(serverRequests[1].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[1].bidderRequestId); + expect(serverRequests[1].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[1].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[1].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0_0`); + expect(serverRequests[1].data.imp[0].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[1].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][0]); + expect(serverRequests[1].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][1]); + expect(serverRequests[1].data.imp[0].banner.format).to.exist.and.to.be.an('array'); + expect(serverRequests[1].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[1].mediaTypes.banner.sizes[0][0], + h: adUnits[1].mediaTypes.banner.sizes[0][1] + }); + }); + }); + + describe('Video', function () { + it('should return a server request with one impression when given a valid outstream ad unit', function () { + const adUnit = examples.adUnit_video_outstream; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with one impression when given a valid pre-roll instream ad unit', function () { + const adUnit = utils.deepClone(examples.adUnit_video_instream); + adUnit.params.video.instreamContext = 'pre-roll'; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.exist.and.to.be.a('number').and.to.equal(0); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with one impression when given a valid mid-roll instream ad unit', function () { + const adUnit = utils.deepClone(examples.adUnit_video_instream); + adUnit.params.video.instreamContext = 'mid-roll'; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.exist.and.to.be.a('number').and.to.equal(-1); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with one impression when given a valid post-roll instream ad unit', function () { + const adUnit = utils.deepClone(examples.adUnit_video_instream); + adUnit.params.video.instreamContext = 'post-roll'; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.exist.and.to.be.a('number').and.to.equal(-2); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with one impression when given a valid ad unit without player size', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = undefined; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.equal(null); + expect(serverRequests[0].data.imp[0].video.h).to.equal(null); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with one impression when given a valid ad unit with an empty player size', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = []; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.equal(null); + expect(serverRequests[0].data.imp[0].video.h).to.equal(null); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with one impression when given a valid ad unit with multiple player sizes', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.mediaTypes.video.playerSize = [[350, 300], [400, 350]]; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with one impression when given a valid ad unit without minimum duration', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.minDuration = undefined; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.equal(null); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with one impression when given a valid ad unit without maximum duration', function () { + const adUnit = utils.deepClone(examples.adUnit_video_outstream); + adUnit.params.video.maxDuration = undefined; + + const adUnits = [adUnit]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.equal(null); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + }); + + it('should return a server request with two impressions when given two valid ad units with different impression identifiers', function () { + const adUnit_1 = utils.deepClone(examples.adUnit_video_outstream); + adUnit_1.bidId = 'bid_id_1'; + + const adUnit_2 = utils.deepClone(examples.adUnit_video_outstream); + adUnit_2.bidId = 'bid_id_2'; + + const adUnits = [adUnit_1, adUnit_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(2); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + expect(serverRequests[0].data.imp[1]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[1].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0`); + expect(serverRequests[0].data.imp[1].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[1].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[1].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[1].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[1].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[1].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.minDuration); + expect(serverRequests[0].data.imp[1].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.maxDuration); + expect(serverRequests[0].data.imp[1].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].params.video.protocols); + }); + + it('should return a server request with one overridden impression when given two valid ad units with identical identifiers', function () { + const adUnit_1 = utils.deepClone(examples.adUnit_video_outstream); + adUnit_1.params.video.minDuration = 10; + + const adUnit_2 = utils.deepClone(examples.adUnit_video_outstream); + adUnit_2.params.video.minDuration = 15; + + const adUnits = [adUnit_1, adUnit_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].params.video.protocols); + }); + + it('should return two server requests with one impression when given two valid ad units with different bid request identifiers', function () { + const adUnit_1 = utils.deepClone(examples.adUnit_video_outstream); + adUnit_1.bidderRequestId = 'bidder_request_id_1'; + + const adUnit_2 = utils.deepClone(examples.adUnit_video_outstream); + adUnit_2.bidderRequestId = 'bidder_request_id_2'; + + const adUnits = [adUnit_1, adUnit_2]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(2); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); + expect(serverRequests[1].data).to.exist.and.to.be.an('object'); + expect(serverRequests[1].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[1].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[1].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0`); + expect(serverRequests[1].data.imp[0].video).to.exist.and.to.be.an('object'); + expect(serverRequests[1].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); + expect(serverRequests[1].data.imp[0].video.startdelay).to.equal(null); + expect(serverRequests[1].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][0]); + expect(serverRequests[1].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][1]); + expect(serverRequests[1].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.minDuration); + expect(serverRequests[1].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.maxDuration); + expect(serverRequests[1].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].params.video.protocols); + }); + }); + + describe('Native', function () { + it('should return a server request with one impression when given a valid ad unit', function () { + const adUnits = [examples.adUnit_native]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnit_native); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); + expect(serverRequests[0].data.imp[0].native).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].native.request).to.exist.and.to.be.a('string').and.to.equal(JSON.stringify(examples.serverRequest_native.data.imp[0].native.request)) + }); + }); + }); + + describe('Site', function () { + it('should return a server request with site information when given a valid ad unit and a valid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = examples.adUnitContext; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.site).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.site.page).to.exist.and.to.be.an('string').and.to.equal(adUnitContext.refererInfo.referer); + expect(serverRequests[0].data.site.id).to.equal(undefined); + expect(serverRequests[0].data.site.domain).to.exist.and.to.be.an('string').and.to.equal('we-are-adot.com'); + expect(serverRequests[0].data.site.name).to.exist.and.to.be.an('string').and.to.equal('we-are-adot.com'); + }); + + it('should return a server request without site information when not given an ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.site).to.equal(null); + }); + + it('should return a server request without site information when given an ad unit context without referer information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.site).to.equal(null); + }); + + it('should return a server request without site information when given an ad unit context with invalid referer information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo = 'bad_referer_information'; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.site).to.equal(null); + }); + + it('should return a server request without site information when given an ad unit context without referer', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo.referer = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.site).to.equal(null); + }); + + it('should return a server request without site information when given an ad unit context with an invalid referer', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo.referer = {}; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.site).to.equal(null); + }); + + it('should return a server request without site information when given an ad unit context with a misformatted referer', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo.referer = 'we-are-adot'; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.site).to.equal(null); + }); + }); + + describe('Device', function () { + it('should return a server request with device information when given a valid ad unit and a valid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.device).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.device.ua).to.exist.and.to.be.a('string'); + expect(serverRequests[0].data.device.language).to.exist.and.to.be.a('string'); + }); + }); + + describe('Regs', function () { + it('should return a server request with regulations information when given a valid ad unit and a valid ad unit context with GDPR applying', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = examples.adUnitContext; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.regs).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.regs.ext).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.regs.ext.gdpr).to.exist.and.to.be.a('boolean').and.to.equal(adUnitContext.gdprConsent.gdprApplies); + }); + + it('should return a server request with regulations information when given a valid ad unit and a valid ad unit context with GDPR not applying', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.gdprConsent.gdprApplies = false; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.regs).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.regs.ext).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.regs.ext.gdpr).to.exist.and.to.be.a('boolean').and.to.equal(adUnitContext.gdprConsent.gdprApplies); + }); + + it('should return a server request without regulations information when not given an ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.regs).to.equal(null); + }); + + it('should return a server request without regulations information when given an ad unit context without GDPR information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.gdprConsent = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.regs).to.equal(null); + }); + + it('should return a server request without regulations information when given an ad unit context with invalid GDPR information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.gdprConsent = 'bad_gdpr_consent'; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.regs).to.equal(null); + }); + + it('should return a server request without regulations information when given an ad unit context with invalid GDPR application information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.gdprConsent.gdprApplies = 'bad_gdpr_applies'; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.regs).to.equal(null); + }); + }); + + describe('User', function () { + it('should return a server request with user information when given a valid ad unit and a valid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = examples.adUnitContext; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.user).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.user.ext).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.user.ext.consent).to.exist.and.to.be.a('string').and.to.equal(adUnitContext.gdprConsent.consentString); + }); + + it('should return a server request without user information when not given an ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.user).to.equal(null); + }); + + it('should return a server request without user information when given an ad unit context without GDPR information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.gdprConsent = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.user).to.equal(null); + }); + + it('should return a server request without user information when given an ad unit context with invalid GDPR information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.gdprConsent = 'bad_gdpr_consent'; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.user).to.equal(null); + }); + + it('should return a server request without user information when given an ad unit context with an invalid consent string', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.gdprConsent.consentString = true; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.user).to.equal(null); + }); + }); + }); + + describe('interpretResponse', function () { + describe('General', function () { + it('should return an ad when given a valid server response with one bid with USD currency', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.cur = 'USD'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(null); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return two ads when given a valid server response with two bids', function () { + const serverRequest = examples.serverRequest_banner_twoImps; + + const serverResponse = examples.serverResponse_banner_twoBids; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(2); + + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(null); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); + expect(ads[0].renderer).to.equal(null); + expect(ads[1].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[1].bidId); + expect(ads[1].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[1].adm); + expect(ads[1].adUrl).to.equal(null); + expect(ads[1].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[1].crid); + expect(ads[1].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].price); + expect(ads[1].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[1].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[1].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[1].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].h); + expect(ads[1].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].w); + expect(ads[1].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); + expect(ads[1].renderer).to.equal(null); + }); + + it('should return no ad when not given a server response', function () { + const ads = spec.interpretResponse(null); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when not given a server response body', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given an invalid server response body', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body = 'invalid_body'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response without seat bids', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with invalid seat bids', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid = 'invalid_seat_bids'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with an empty seat bids array', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid = []; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with an invalid seat bid', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid = 'invalid_bids'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with an empty bids array', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid = []; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with an invalid bid', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid = ['invalid_bid']; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without currency', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.cur = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid currency', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.cur = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without impression identifier', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].impid = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid impression identifier', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].impid = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without creative identifier', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].crid = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid creative identifier', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].crid = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without ad markup and ad serving URL', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].adm = undefined; + serverResponse.body.seatbid[0].bid[0].nurl = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid ad markup', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].adm = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an ad markup without auction price macro', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].adm = 'creative_data'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid ad serving URL', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].nurl = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an ad serving URL without auction price macro', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].nurl = 'win_notice_url'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without bid price', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].price = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid bid price', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].price = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without extension', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].ext = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid extension', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].ext = 'bad_ext'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without adot extension', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].ext.adot = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid adot extension', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].ext.adot = 'bad_adot_ext'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without media type', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].ext.adot.media_type = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid media type', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].ext.adot.media_type = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an unknown media type', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].ext.adot.media_type = 'unknown_media_type'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and no server request', function () { + const serverRequest = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and an invalid server request', function () { + const serverRequest = 'bad_server_request'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without bid request', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request with an invalid bid request', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data = 'bad_bid_request'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without impression', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data.imp = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request with an invalid impression field', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data.imp = 'invalid_impressions'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without matching impression', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data.imp[0].id = 'unknown_imp_id'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without internal data', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest._adot_internal = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request with invalid internal data', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest._adot_internal = 'bad_internal_data'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without internal impression data', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest._adot_internal.impressions = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request with invalid internal impression data', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest._adot_internal.impressions = 'bad_internal_impression_data'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without matching internal impression', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest._adot_internal.impressions[0].impressionId = 'unknown_imp_id'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without internal impression ad unit code', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest._adot_internal.impressions[0].adUnitCode = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request with an invalid internal impression ad unit code', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest._adot_internal.impressions[0].adUnitCode = {}; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + }); + + describe('Banner', function () { + it('should return an ad when given a valid server response with one bid', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(null); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response with one bid without a win notice URL', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].nurl = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(null); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response with one bid using an ad serving URL', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].adm = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.equal(null); + expect(ads[0].adUrl).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].nurl); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return no ad when given a server response with a bid without height', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].h = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid height', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].h = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without width', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].w = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid width', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].w = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without banner impression', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data.imp[0].banner = undefined; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + }); + + describe('Video', function () { + it('should return an ad when given a valid server response with one bid on an instream impression', function () { + const serverRequest = examples.serverRequest_video_instream; + + const serverResponse = examples.serverResponse_video_instream; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); + expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response with one bid on an outstream impression', function () { + const serverRequest = examples.serverRequest_video_outstream; + + const serverResponse = examples.serverResponse_video_outstream; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); + expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.be.an('object'); + }); + + it('should return two ads when given a valid server response with two bids on both instream and outstream impressions', function () { + const serverRequest = examples.serverRequest_video_instream_outstream; + + const serverResponse = examples.serverResponse_video_instream_outstream; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(2); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); + expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + expect(ads[1].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[1].bidId); + expect(ads[1].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[1].adm); + expect(ads[1].adUrl).to.equal(null); + expect(ads[1].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[1].vastUrl).to.equal(null); + expect(ads[1].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[1].crid); + expect(ads[1].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].price); + expect(ads[1].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[1].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[1].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverRequest.data.imp[1].video.h); + expect(ads[0].width).to.equal(serverRequest.data.imp[1].video.w); + expect(ads[1].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[1].renderer).to.be.an('object'); + }); + + it('should return an ad when given a valid server response with one bid without a win notice URL', function () { + const serverRequest = examples.serverRequest_video_instream; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + serverResponse.body.seatbid[0].bid[0].nurl = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); + expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response with one bid using an ad serving URL', function () { + const serverRequest = examples.serverRequest_video_instream; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + serverResponse.body.seatbid[0].bid[0].adm = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.equal(null); + expect(ads[0].adUrl).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].nurl); + expect(ads[0].vastXml).to.equal(null); + expect(ads[0].vastUrl).to.equal(serverResponse.body.seatbid[0].bid[0].nurl); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); + expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response with a bid with a video height', function () { + const serverRequest = examples.serverRequest_video_instream; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + serverResponse.body.seatbid[0].bid[0].h = 500; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); + expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response with a bid with a video width', function () { + const serverRequest = examples.serverRequest_video_instream; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + serverResponse.body.seatbid[0].bid[0].w = 500; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); + expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response with a bid with a video width and height', function () { + const serverRequest = examples.serverRequest_video_instream; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + serverResponse.body.seatbid[0].bid[0].w = 500; + serverResponse.body.seatbid[0].bid[0].h = 400; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(ads[0].width).to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response and server request with a video impression without width', function () { + const serverRequest = utils.deepClone(examples.serverRequest_video_instream); + serverRequest.data.imp[0].video.w = null; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(null); + expect(ads[0].width).to.equal(null); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return an ad when given a valid server response and server request with a video impression without height', function () { + const serverRequest = utils.deepClone(examples.serverRequest_video_instream); + serverRequest.data.imp[0].video.h = null; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.equal(null); + expect(ads[0].width).to.equal(null); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); + expect(ads[0].renderer).to.equal(null); + }); + + it('should return no ad when given a server response with a bid with an invalid height', function () { + const serverRequest = examples.serverRequest_video_instream; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + serverResponse.body.seatbid[0].bid[0].h = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid width', function () { + const serverRequest = examples.serverRequest_video_instream; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + serverResponse.body.seatbid[0].bid[0].w = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without video impression', function () { + const serverRequest = utils.deepClone(examples.serverRequest_video_instream); + serverRequest.data.imp[0].video = undefined; + + const serverResponse = utils.deepClone(examples.serverResponse_video_instream); + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + describe('Outstream renderer', function () { + function spyAdRenderingQueue(ad) { + const spy = sinon.spy(ad.renderer, 'push'); + + this.sinonSpies.push(spy); + } + + function executeAdRenderer(ad, onRendererExecution, done) { + executeRenderer(ad.renderer, ad); + + setTimeout(() => { + try { + onRendererExecution(); + } catch (err) { + done(err); + } + + done() + }, 100); + } + + before('Bind helper functions to the Mocha context', function () { + this.spyAdRenderingQueue = spyAdRenderingQueue.bind(this); + + window.VASTPlayer = function VASTPlayer() {}; + window.VASTPlayer.prototype.loadXml = function loadXml() { + return new Promise((resolve, reject) => resolve()) + }; + window.VASTPlayer.prototype.load = function load() { + return new Promise((resolve, reject) => resolve()) + }; + window.VASTPlayer.prototype.on = function on(event, callback) {}; + window.VASTPlayer.prototype.startAd = function startAd() {}; + }); + + beforeEach('Initialize the Sinon spies list', function () { + this.sinonSpies = []; + }); + + afterEach('Clear the registered Sinon spies', function () { + this.sinonSpies.forEach(spy => spy.restore()); + }); + + after('clear data', () => { + window.VASTPlayer = null; + }); + + it('should return an ad with valid renderer', function () { + const serverRequest = examples.serverRequest_video_outstream; + const serverResponse = examples.serverResponse_video_outstream; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].renderer).to.be.an('object'); + }); + + it('should append a command to the ad rendering queue when executing the renderer', function (done) { + const serverRequest = examples.serverRequest_video_outstream; + const serverResponse = examples.serverResponse_video_outstream; + + const [ad] = spec.interpretResponse(serverResponse, serverRequest); + + this.spyAdRenderingQueue(ad); + + executeAdRenderer(ad, () => { + expect(ad.renderer.push.calledOnce).to.equal(true); + expect(ad.renderer.push.firstCall.args[0]).to.exist.and.to.be.a('function'); + }, done); + }); + }); + }); + + describe('Native', function () { + it('should return an ad when given a valid server response with one bid', function () { + const serverRequest = examples.serverRequest_native; + const serverResponse = examples.serverResponse_native; + const native = JSON.parse(serverResponse.body.seatbid[0].bid[0].adm).native; + const {link, assets} = native; + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('native'); + expect(ads[0].native).to.exist.and.to.be.an('object'); + expect(Object.keys(ads[0].native)).to.have.length(10); + expect(ads[0].native.title).to.equal(assets[0].title.text); + expect(ads[0].native.icon.url).to.equal(assets[1].img.url); + expect(ads[0].native.icon.width).to.equal(assets[1].img.w); + expect(ads[0].native.icon.height).to.equal(assets[1].img.h); + expect(ads[0].native.image.url).to.equal(assets[2].img.url); + expect(ads[0].native.image.width).to.equal(assets[2].img.w); + expect(ads[0].native.image.height).to.equal(assets[2].img.h); + expect(ads[0].native.sponsoredBy).to.equal(assets[3].data.value); + expect(ads[0].native.body).to.equal(assets[4].data.value); + expect(ads[0].native.cta).to.equal(assets[5].data.value); + expect(ads[0].native.clickUrl).to.equal(link.url); + }); + }); + }); +}); diff --git a/test/spec/modules/adpartnerBidAdapter_spec.js b/test/spec/modules/adpartnerBidAdapter_spec.js new file mode 100644 index 00000000000..d30ef7ebf71 --- /dev/null +++ b/test/spec/modules/adpartnerBidAdapter_spec.js @@ -0,0 +1,234 @@ +import {expect} from 'chai'; +import {spec, ENDPOINT_PROTOCOL, ENDPOINT_DOMAIN, ENDPOINT_PATH} from 'modules/adpartnerBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'adpartner'; + +describe('AdpartnerAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.be.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + let validRequest = { + 'params': { + 'unitId': 123 + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(true); + }); + + it('should return true when required params is srting', function () { + let validRequest = { + 'params': { + 'unitId': '456' + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let validRequest = { + 'params': { + 'unknownId': 123 + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + + it('should return false when required params is 0', function () { + let validRequest = { + 'params': { + 'unitId': 0 + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let validEndpoint = ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + ENDPOINT_PATH + '?tag=123,456&sizes=300x250|300x600,728x90&referer=https%3A%2F%2Ftest.domain'; + + let validRequest = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'unitId': 123 + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e' + }, + { + 'bidder': BIDDER_CODE, + 'params': { + 'unitId': '456' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '22aidtbx5eabd9' + } + ]; + + let bidderRequest = { + refererInfo: { + referer: 'https://test.domain' + } + }; + + it('bidRequest HTTP method', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.method).to.equal('POST'); + }); + + it('bidRequest url', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(validEndpoint); + }); + + it('bidRequest data', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload[0].unitId).to.equal(123); + expect(payload[0].sizes).to.deep.equal([[300, 250], [300, 600]]); + expect(payload[0].bidId).to.equal('30b31c1838de1e'); + expect(payload[1].unitId).to.equal(456); + expect(payload[1].sizes).to.deep.equal([[728, 90]]); + expect(payload[1].bidId).to.equal('22aidtbx5eabd9'); + }); + }); + + describe('joinSizesToString', function () { + it('success convert sizes list to string', function () { + const sizesStr = spec.joinSizesToString([[300, 250], [300, 600]]); + expect(sizesStr).to.equal('300x250|300x600'); + }); + }); + + describe('interpretResponse', function () { + const bidRequest = { + 'method': 'POST', + 'url': ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + ENDPOINT_PATH + '?tag=123,456&code=adunit-code-1,adunit-code-2&bid=30b31c1838de1e,22aidtbx5eabd9&sizes=300x250|300x600,728x90&referer=https%3A%2F%2Ftest.domain', + 'data': '[{"unitId": 13144370,"adUnitCode": "div-gpt-ad-1460505748561-0","sizes": [[300, 250], [300, 600]],"bidId": "2bdcb0b203c17d","referer": "https://test.domain/index.html"},{"unitId": 13144370,"adUnitCode":"div-gpt-ad-1460505748561-1","sizes": [[768, 90]],"bidId": "3dc6b8084f91a8","referer": "https://test.domain/index.html"}]' + }; + + const bidResponse = { + body: { + 'div-gpt-ad-1460505748561-0': + { + 'ad': '
ad
', + 'width': 300, + 'height': 250, + 'creativeId': '8:123456', + 'syncs': [ + {'type': 'image', 'url': 'https://test.domain/tracker_1.gif'}, + {'type': 'image', 'url': 'https://test.domain/tracker_2.gif'}, + {'type': 'image', 'url': 'https://test.domain/tracker_3.gif'} + ], + 'winNotification': [ + { + 'method': 'POST', + 'path': '/hb/bid_won?test=1', + 'data': { + 'ad': [ + {'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'} + ], + 'unit_id': 1234, + 'site_id': 123 + } + } + ], + 'cpm': 0.01, + 'currency': 'USD', + 'netRevenue': true + } + }, + headers: {} + }; + + it('result is correct', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0].requestId).to.equal('2bdcb0b203c17d'); + expect(result[0].cpm).to.equal(0.01); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal('8:123456'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].ttl).to.equal(60); + expect(result[0].winNotification[0]).to.deep.equal({'method': 'POST', 'path': '/hb/bid_won?test=1', 'data': {'ad': [{'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'}], 'unit_id': 1234, 'site_id': 123}}); + }); + }); + + describe('adResponse', function () { + const bid = { + 'unitId': 13144370, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2bdcb0b203c17d', + 'referer': 'https://test.domain/index.html' + }; + const ad = { + 'ad': '
ad
', + 'width': 300, + 'height': 250, + 'creativeId': '8:123456', + 'syncs': [], + 'winNotification': [], + 'cpm': 0.01, + 'currency': 'USD', + 'netRevenue': true + }; + + it('fill ad for response', function () { + const result = spec.adResponse(bid, ad); + expect(result.requestId).to.equal('2bdcb0b203c17d'); + expect(result.cpm).to.equal(0.01); + expect(result.width).to.equal(300); + expect(result.height).to.equal(250); + expect(result.creativeId).to.equal('8:123456'); + expect(result.currency).to.equal('USD'); + expect(result.ttl).to.equal(60); + }); + }); + + describe('onBidWon', function () { + const bid = { + winNotification: [ + { + 'method': 'POST', + 'path': '/hb/bid_won?test=1', + 'data': { + 'ad': [ + {'dsp': 8, 'id': 800008, 'cost': 0.01, 'nurl': 'http://test.domain/'} + ], + 'unit_id': 1234, + 'site_id': 123 + } + } + ] + }; + + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'postRequest') + }) + + afterEach(() => { + ajaxStub.restore() + }) + + it('calls adpartner\'s callback endpoint', () => { + const result = spec.onBidWon(bid); + expect(result).to.equal(true); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal(ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + '/hb/bid_won?test=1'); + expect(ajaxStub.firstCall.args[1]).to.deep.equal(JSON.stringify(bid.winNotification[0].data)); + }); + }); +}); diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js index 6137d641cdf..5e4bcce1fe6 100644 --- a/test/spec/modules/adpod_spec.js +++ b/test/spec/modules/adpod_spec.js @@ -1,10 +1,10 @@ -import * as utils from 'src/utils'; -import { config } from 'src/config'; -import * as videoCache from 'src/videoCache'; -import * as auction from 'src/auction'; -import { ADPOD } from 'src/mediaTypes'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import * as videoCache from 'src/videoCache.js'; +import * as auction from 'src/auction.js'; +import { ADPOD } from 'src/mediaTypes.js'; -import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig, sortByPricePerSecond } from 'modules/adpod'; +import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig, sortByPricePerSecond } from 'modules/adpod.js'; let expect = require('chai').expect; @@ -138,7 +138,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 300, durationRangeSec: [15, 30, 45], requireExactDuration: false @@ -220,7 +220,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 120, durationRangeSec: [15, 30], requireExactDuration: false @@ -282,7 +282,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 120, durationRangeSec: [15, 30], requireExactDuration: true @@ -376,7 +376,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 45, durationRangeSec: [15, 30], requireExactDuration: false @@ -465,7 +465,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 45, durationRangeSec: [15, 30], requireExactDuration: false @@ -525,7 +525,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 45, durationRangeSec: [15, 30], requireExactDuration: false @@ -602,7 +602,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 120, durationRangeSec: [15, 30, 45], requireExactDuration: false @@ -675,7 +675,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 120, durationRangeSec: [15, 30, 45], requireExactDuration: false @@ -748,7 +748,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 45, durationRangeSec: [15, 30], requireExactDuration: false @@ -826,7 +826,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 300, durationRangeSec: [15, 30, 45], requireExactDuration: false @@ -922,7 +922,7 @@ describe('adpod.js', function () { }, video: { context: 'adpod', - playerSize: [300, 250], + playerSize: [[300, 250]], durationRangeSec: [15, 30, 45], adPodDurationSec: 300 } @@ -941,7 +941,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 300], + playerSize: [[300, 300]], adPodDurationSec: 360, durationRangeSec: [15, 30, 45], requireExactDuration: true @@ -981,7 +981,7 @@ describe('adpod.js', function () { durationBucket: 15 }, meta: { - iabSubCatId: 'testCategory_123' + primaryCatId: 'testCategory_123' }, vastXml: 'test XML here' }; @@ -989,7 +989,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 400], + playerSize: [[300, 400]], durationRangeSec: [15, 45], requireExactDuration: false, adPodDurationSec: 300 @@ -1000,7 +1000,7 @@ describe('adpod.js', function () { mediaTypes: { video: { context: ADPOD, - playerSize: [300, 400], + playerSize: [[300, 400]], durationRangeSec: [15, 30, 45, 60], requireExactDuration: true, adPodDurationSec: 300 @@ -1050,7 +1050,7 @@ describe('adpod.js', function () { }); let goodBid = utils.deepClone(adpodTestBid); - goodBid.meta.iabSubCatId = undefined; + goodBid.meta.primaryCatId = undefined; checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestNoExact, {}, ADPOD); expect(callbackResult).to.be.null; expect(bailResult).to.equal(true); @@ -1074,7 +1074,7 @@ describe('adpod.js', function () { } let noCatBid = utils.deepClone(adpodTestBid); - noCatBid.meta.iabSubCatId = undefined; + noCatBid.meta.primaryCatId = undefined; testInvalidAdpodBid(noCatBid, false); let noContextBid = utils.deepClone(adpodTestBid); @@ -1101,7 +1101,7 @@ describe('adpod.js', function () { durationSeconds: 30 }, meta: { - iabSubCatId: 'testCategory_123' + primaryCatId: 'testCategory_123' }, vastXml: '' }; diff --git a/test/spec/modules/adponeBidAdapter_spec.js b/test/spec/modules/adponeBidAdapter_spec.js index e1535f8596b..737f1c284e1 100644 --- a/test/spec/modules/adponeBidAdapter_spec.js +++ b/test/spec/modules/adponeBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { spec } from 'modules/adponeBidAdapter'; -import {newBidder} from '../../../src/adapters/bidderFactory'; +import { spec } from 'modules/adponeBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; const EMPTY_ARRAY = []; describe('adponeBidAdapter', function () { @@ -213,11 +214,18 @@ describe('getUserSyncs', function () { }); describe('test onBidWon function', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); it('exists and is a function', () => { expect(spec.onBidWon).to.exist.and.to.be.a('function'); }); it('should return nothing', function () { var response = spec.onBidWon({}); expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); }); }); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js new file mode 100644 index 00000000000..fe05634baae --- /dev/null +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -0,0 +1,287 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/adprimeBidAdapter.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; + +describe('AdprimebBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'adprime', + params: { + placementId: 0, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://delta.adprime.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'identeties', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'keywords'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('buildRequests with user ids', function () { + bid.userId = {} + bid.userId.idl_env = 'idl_env123'; + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Return bids with user identeties', function () { + let data = serverRequest.data; + let placements = data['placements']; + expect(data).to.be.an('object'); + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.property('identeties') + expect(placement.identeties).to.be.an('object') + expect(placement.identeties).to.have.property('identityLink') + expect(placement.identeties.identityLink).to.be.equal('idl_env123') + } + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/adtargetBidAdapter_spec.js b/test/spec/modules/adtargetBidAdapter_spec.js new file mode 100644 index 00000000000..5a867e7dd52 --- /dev/null +++ b/test/spec/modules/adtargetBidAdapter_spec.js @@ -0,0 +1,338 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adtargetBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; + +const DISPLAY_REQUEST = { + 'bidder': 'adtarget', + 'params': { + 'aid': 12345 + }, + 'schain': { ver: 1 }, + 'userId': { criteo: 2 }, + 'mediaTypes': { 'banner': { 'sizes': [300, 250] } }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', +}; + +const VIDEO_REQUEST = { + 'bidder': 'adtarget', + 'mediaTypes': { + 'video': { + 'playerSize': [[480, 360], [640, 480]] + } + }, + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d' +}; + +const SERVER_VIDEO_RESPONSE = { + 'source': { 'aid': 12345, 'pubId': 54321 }, + 'bids': [{ + 'vastUrl': 'https://rtb.adtarget.com/vast/?adid=44F2AEB9BFC881B3', + 'requestId': '2e41f65424c87c', + 'url': '44F2AEB9BFC881B3', + 'creative_id': 342516, + 'durationSeconds': 30, + '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 + }], + 'cookieURLs': ['link1', 'link2'] +}; +const SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS = { + 'source': { 'aid': 12345, 'pubId': 54321 }, + 'bids': [{ + 'ad': '', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }], + 'cookieURLs': ['link3', 'link4'], + 'cookieURLSTypes': ['image', 'iframe'] +}; +const videoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{ mediaTypes: { video: {} }, bidId: '2e41f65424c87c' }] +}; + +const displayBidderRequest = { + bidderCode: 'bidderCode', + bids: [{ bidId: '2e41f65424c87c' }] +}; + +const displayBidderRequestWithConsents = { + bidderCode: 'bidderCode', + bids: [{ bidId: '2e41f65424c87c' }], + gdprConsent: { + gdprApplies: true, + consentString: 'test' + }, + uspConsent: 'iHaveIt' +}; + +const videoEqResponse = [{ + vastUrl: 'https://rtb.adtarget.com/vast/?adid=44F2AEB9BFC881B3', + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'video', + netRevenue: true, + currency: 'USD', + height: 480, + width: 640, + ttl: 300, + cpm: 0.9 +}]; + +const displayEqResponse = [{ + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'banner', + netRevenue: true, + currency: 'USD', + ad: '', + height: 250, + width: 300, + ttl: 300, + cpm: 0.9 +}]; + +describe('adtargetBidAdapter', () => { + const adapter = newBidder(spec); + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('user syncs', () => { + describe('as image', () => { + it('should be returned if pixel enabled', () => { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[0]]); + expect(syncs.map(s => s.type)).to.deep.equal(['image']); + }) + }) + + describe('as iframe', () => { + it('should be returned if iframe enabled', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[1]]); + expect(syncs.map(s => s.type)).to.deep.equal(['iframe']); + }) + }) + + describe('user sync', () => { + it('should not be returned if passed syncs where already used', () => { + const syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs).to.deep.equal([]); + }) + + it('should not be returned if pixel not set', () => { + const syncs = spec.getUserSyncs({}, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs).to.be.empty; + }); + }); + describe('user syncs with both types', () => { + it('should be returned if pixel and iframe enabled', () => { + const mockedServerResponse = Object.assign({}, SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS, { 'cookieURLs': ['link5', 'link6'] }); + const syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{ body: mockedServerResponse }]); + + expect(syncs.map(s => s.url)).to.deep.equal(mockedServerResponse.cookieURLs); + expect(syncs.map(s => s.type)).to.deep.equal(mockedServerResponse.cookieURLSTypes); + }); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(VIDEO_REQUEST)).to.equal(true); + }); + + 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(false); + }); + }); + + 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('building requests as arrays', () => { + expect(videoRequest).to.be.a('array'); + expect(displayRequest).to.be.a('array'); + expect(videoAndDisplayRequests).to.be.a('array'); + }) + + it('sending as POST', () => { + const postActionMethod = 'POST' + const comparator = br => br.method === postActionMethod; + expect(videoRequest.every(comparator)).to.be.true; + expect(displayRequest.every(comparator)).to.be.true; + expect(videoAndDisplayRequests.every(comparator)).to.be.true; + }); + + it('sends correct video bid parameters', () => { + const data = videoRequest[0].data; + + const eq = { + CallbackId: '84ab500420319d', + AdType: 'video', + Aid: 12345, + Sizes: '480x360,640x480' + }; + expect(data.BidRequests[0]).to.deep.equal(eq); + }); + + it('sends correct display bid parameters', () => { + const data = displayRequest[0].data; + + const eq = { + CallbackId: '84ab500420319d', + AdType: 'display', + Aid: 12345, + Sizes: '300x250' + }; + + expect(data.BidRequests[0]).to.deep.equal(eq); + }); + + it('sends correct video and display bid parameters', () => { + const bidRequests = videoAndDisplayRequests[0].data; + const expectedBidReqs = [{ + CallbackId: '84ab500420319d', + AdType: 'display', + Aid: 12345, + Sizes: '300x250' + }, { + CallbackId: '84ab500420319d', + AdType: 'video', + Aid: 12345, + Sizes: '480x360,640x480' + }] + + expect(bidRequests.BidRequests).to.deep.equal(expectedBidReqs); + }); + + describe('publisher environment', () => { + const sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake((key) => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const bidRequestWithPubSettingsData = spec.buildRequests([DISPLAY_REQUEST], displayBidderRequestWithConsents)[0].data; + sandbox.restore(); + it('sets GDPR', () => { + expect(bidRequestWithPubSettingsData.GDPR).to.be.equal(1); + expect(bidRequestWithPubSettingsData.GDPRConsent).to.be.equal(displayBidderRequestWithConsents.gdprConsent.consentString); + }); + it('sets USP', () => { + expect(bidRequestWithPubSettingsData.USP).to.be.equal(displayBidderRequestWithConsents.uspConsent); + }) + it('sets Coppa', () => { + expect(bidRequestWithPubSettingsData.Coppa).to.be.equal(1); + }) + it('sets Schain', () => { + expect(bidRequestWithPubSettingsData.Schain).to.be.deep.equal(DISPLAY_REQUEST.schain); + }) + it('sets UserId\'s', () => { + expect(bidRequestWithPubSettingsData.UserIds).to.be.deep.equal(DISPLAY_REQUEST.userId); + }) + }) + }); + + describe('interpretResponse', () => { + let serverResponse; + let adapterRequest; + let eqResponse; + + afterEach(() => { + serverResponse = null; + adapterRequest = null; + eqResponse = null; + }); + + it('should get correct video bid response', () => { + serverResponse = SERVER_VIDEO_RESPONSE; + adapterRequest = videoBidderRequest; + eqResponse = videoEqResponse; + + bidServerResponseCheck(); + }); + + it('should get correct display bid response', () => { + serverResponse = SERVER_DISPLAY_RESPONSE; + adapterRequest = displayBidderRequest; + eqResponse = displayEqResponse; + + bidServerResponseCheck(); + }); + + function bidServerResponseCheck() { + const result = spec.interpretResponse({ body: serverResponse }, { adapterRequest }); + + expect(result).to.deep.equal(eqResponse); + } + + function nobidServerResponseCheck() { + const noBidServerResponse = { bids: [] }; + const noBidResult = spec.interpretResponse({ body: noBidServerResponse }, { adapterRequest }); + + expect(noBidResult.length).to.equal(0); + } + + it('handles video nobid responses', () => { + adapterRequest = videoBidderRequest; + nobidServerResponseCheck(); + }); + + it('handles display nobid responses', () => { + adapterRequest = displayBidderRequest; + nobidServerResponseCheck(); + }); + }); +}); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js new file mode 100644 index 00000000000..9c694668703 --- /dev/null +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -0,0 +1,409 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adtelligentBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; + +const EXPECTED_ENDPOINTS = [ + 'https://ghb.adtelligent.com/v2/auction/', + 'https://ghb1.adtelligent.com/v2/auction/', + 'https://ghb2.adtelligent.com/v2/auction/', + 'https://ghb.adtelligent.com/v2/auction/' +]; + +const DISPLAY_REQUEST = { + 'bidder': 'adtelligent', + 'params': { + 'aid': 12345 + }, + 'schain': { ver: 1 }, + 'userId': { criteo: 2 }, + 'mediaTypes': { 'banner': { 'sizes': [300, 250] } }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', +}; + +const VIDEO_REQUEST = { + 'bidder': 'adtelligent', + 'mediaTypes': { + 'video': { + 'playerSize': [[480, 360], [640, 480]] + } + }, + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d' +}; + +const ADPOD_REQUEST = { + 'bidder': 'adtelligent', + 'mediaTypes': { + 'video': { + 'context': 'adpod', + 'playerSize': [[640, 480]], + 'anyField': 10 + } + }, + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '2e41f65424c87c' +}; + +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, + 'durationSeconds': 30, + 'cmpId': 342516, + 'height': 480, + 'cur': 'USD', + 'width': 640, + 'cpm': 0.9 + } + ] +}; +const SERVER_OUSTREAM_VIDEO_RESPONSE = SERVER_VIDEO_RESPONSE; +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 + }], + 'cookieURLs': ['link1', 'link2'] +}; +const SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS = { + 'source': { 'aid': 12345, 'pubId': 54321 }, + 'bids': [{ + 'ad': '', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }], + 'cookieURLs': ['link3', 'link4'], + 'cookieURLSTypes': ['image', 'iframe'] +}; +const outstreamVideoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{ + 'params': { + 'aid': 12345, + 'outstream': { + 'video_controls': 'show' + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + bidId: '2e41f65424c87c' + }] +}; +const videoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{ mediaTypes: { video: {} }, bidId: '2e41f65424c87c' }] +}; + +const displayBidderRequest = { + bidderCode: 'bidderCode', + bids: [{ bidId: '2e41f65424c87c' }] +}; + +const displayBidderRequestWithConsents = { + bidderCode: 'bidderCode', + bids: [{ bidId: '2e41f65424c87c' }], + gdprConsent: { + gdprApplies: true, + consentString: 'test' + }, + uspConsent: 'iHaveIt' +}; + +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: 300, + cpm: 0.9 +}]; + +const displayEqResponse = [{ + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'banner', + netRevenue: true, + currency: 'USD', + ad: '', + height: 250, + width: 300, + ttl: 300, + 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('user syncs', () => { + describe('as image', () => { + it('should be returned if pixel enabled', () => { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[0]]); + expect(syncs.map(s => s.type)).to.deep.equal(['image']); + }) + }) + + describe('as iframe', () => { + it('should be returned if iframe enabled', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[1]]); + expect(syncs.map(s => s.type)).to.deep.equal(['iframe']); + }) + }) + + describe('user sync', () => { + it('should not be returned if passed syncs where already used', () => { + const syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs).to.deep.equal([]); + }) + + it('should not be returned if pixel not set', () => { + const syncs = spec.getUserSyncs({}, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs).to.be.empty; + }); + }); + describe('user syncs with both types', () => { + it('should be returned if pixel and iframe enabled', () => { + const mockedServerResponse = Object.assign({}, SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS, { 'cookieURLs': ['link5', 'link6'] }); + const syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{ body: mockedServerResponse }]); + + expect(syncs.map(s => s.url)).to.deep.equal(mockedServerResponse.cookieURLs); + expect(syncs.map(s => s.type)).to.deep.equal(mockedServerResponse.cookieURLSTypes); + }); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(VIDEO_REQUEST)).to.equal(true); + }); + + 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(false); + }); + }); + + 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, {}); + const rotatingRequest = spec.buildRequests(displayBidRequests, {}); + it('rotates endpoints', () => { + const bidReqUrls = [displayRequest[0], videoRequest[0], videoAndDisplayRequests[0], rotatingRequest[0]].map(br => br.url); + expect(bidReqUrls).to.deep.equal(EXPECTED_ENDPOINTS); + }) + + it('building requests as arrays', () => { + expect(videoRequest).to.be.a('array'); + expect(displayRequest).to.be.a('array'); + expect(videoAndDisplayRequests).to.be.a('array'); + }) + + it('sending as POST', () => { + const postActionMethod = 'POST' + const comparator = br => br.method === postActionMethod; + expect(videoRequest.every(comparator)).to.be.true; + expect(displayRequest.every(comparator)).to.be.true; + expect(videoAndDisplayRequests.every(comparator)).to.be.true; + }); + it('forms correct ADPOD request', () => { + const pbBidReqData = spec.buildRequests([ADPOD_REQUEST], {})[0].data; + const impRequest = pbBidReqData.BidRequests[0] + expect(impRequest.AdType).to.be.equal('video'); + expect(impRequest.Adpod).to.be.a('object'); + expect(impRequest.Adpod.anyField).to.be.equal(10); + }) + it('sends correct video bid parameters', () => { + const data = videoRequest[0].data; + + const eq = { + CallbackId: '84ab500420319d', + AdType: 'video', + Aid: 12345, + Sizes: '480x360,640x480' + }; + expect(data.BidRequests[0]).to.deep.equal(eq); + }); + + it('sends correct display bid parameters', () => { + const data = displayRequest[0].data; + + const eq = { + CallbackId: '84ab500420319d', + AdType: 'display', + Aid: 12345, + Sizes: '300x250' + }; + + expect(data.BidRequests[0]).to.deep.equal(eq); + }); + + it('sends correct video and display bid parameters', () => { + const bidRequests = videoAndDisplayRequests[0].data; + const expectedBidReqs = [{ + CallbackId: '84ab500420319d', + AdType: 'display', + Aid: 12345, + Sizes: '300x250' + }, { + CallbackId: '84ab500420319d', + AdType: 'video', + Aid: 12345, + Sizes: '480x360,640x480' + }] + + expect(bidRequests.BidRequests).to.deep.equal(expectedBidReqs); + }); + + describe('publisher environment', () => { + const sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake((key) => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const bidRequestWithPubSettingsData = spec.buildRequests([DISPLAY_REQUEST], displayBidderRequestWithConsents)[0].data; + sandbox.restore(); + it('sets GDPR', () => { + expect(bidRequestWithPubSettingsData.GDPR).to.be.equal(1); + expect(bidRequestWithPubSettingsData.GDPRConsent).to.be.equal(displayBidderRequestWithConsents.gdprConsent.consentString); + }); + it('sets USP', () => { + expect(bidRequestWithPubSettingsData.USP).to.be.equal(displayBidderRequestWithConsents.uspConsent); + }) + it('sets Coppa', () => { + expect(bidRequestWithPubSettingsData.Coppa).to.be.equal(1); + }) + it('sets Schain', () => { + expect(bidRequestWithPubSettingsData.Schain).to.be.deep.equal(DISPLAY_REQUEST.schain); + }) + it('sets UserId\'s', () => { + expect(bidRequestWithPubSettingsData.UserIds).to.be.deep.equal(DISPLAY_REQUEST.userId); + }) + }) + }); + + describe('interpretResponse', () => { + let serverResponse; + let adapterRequest; + let eqResponse; + + afterEach(() => { + serverResponse = null; + adapterRequest = null; + eqResponse = null; + }); + + it('should get correct video bid response', () => { + serverResponse = SERVER_VIDEO_RESPONSE; + adapterRequest = videoBidderRequest; + eqResponse = videoEqResponse; + + bidServerResponseCheck(); + }); + + it('should get correct display bid response', () => { + serverResponse = SERVER_DISPLAY_RESPONSE; + adapterRequest = displayBidderRequest; + eqResponse = displayEqResponse; + + bidServerResponseCheck(); + }); + + function bidServerResponseCheck() { + const result = spec.interpretResponse({ body: serverResponse }, { adapterRequest }); + + expect(result).to.deep.equal(eqResponse); + } + + function nobidServerResponseCheck() { + const noBidServerResponse = { bids: [] }; + const noBidResult = spec.interpretResponse({ body: noBidServerResponse }, { adapterRequest }); + + expect(noBidResult.length).to.equal(0); + } + + it('handles video nobid responses', () => { + adapterRequest = videoBidderRequest; + + nobidServerResponseCheck(); + }); + + it('handles display nobid responses', () => { + adapterRequest = displayBidderRequest; + + nobidServerResponseCheck(); + }); + + it('forms correct ADPOD response', () => { + const videoBids = spec.interpretResponse({ body: SERVER_VIDEO_RESPONSE }, { adapterRequest: { bids: [ADPOD_REQUEST] } }); + expect(videoBids[0].video.durationSeconds).to.be.equal(30); + expect(videoBids[0].video.context).to.be.equal('adpod'); + }) + describe('outstream setup', () => { + const videoBids = spec.interpretResponse({ body: SERVER_OUSTREAM_VIDEO_RESPONSE }, { adapterRequest: outstreamVideoBidderRequest }); + it('should return renderer with expected outstream params config', () => { + expect(!!videoBids[0].renderer).to.be.true; + expect(videoBids[0].renderer.getConfig().video_controls).to.equal('show'); + }) + }) + }); +}); diff --git a/test/spec/modules/aduptechBidAdapter_spec.js b/test/spec/modules/aduptechBidAdapter_spec.js new file mode 100644 index 00000000000..1e39e0cfc8b --- /dev/null +++ b/test/spec/modules/aduptechBidAdapter_spec.js @@ -0,0 +1,502 @@ +import { expect } from 'chai'; +import { + BIDDER_CODE, + PUBLISHER_PLACEHOLDER, + ENDPOINT_URL, + ENDPOINT_METHOD, + spec, + extractGdprFromBidderRequest, + extractParamsFromBidRequest, + extractSizesFromBidRequest, + extractTopWindowReferrerFromBidRequest, + extractTopWindowUrlFromBidRequest +} from '../../../modules/aduptechBidAdapter.js'; +import { newBidder } from '../../../src/adapters/bidderFactory.js'; + +describe('AduptechBidAdapter', () => { + describe('extractGdprFromBidderRequest', () => { + it('should handle empty bidder request', () => { + const bidderRequest = null; + expect(extractGdprFromBidderRequest(bidderRequest)).to.be.null; + }); + + it('should handle missing gdprConsent in bidder request', () => { + const bidderRequest = {}; + expect(extractGdprFromBidderRequest(bidderRequest)).to.be.null; + }); + + it('should handle gdprConsent in bidder request', () => { + const bidderRequest = { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true + } + }; + + expect(extractGdprFromBidderRequest(bidderRequest)).to.deep.equal({ + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: true + }); + }); + }); + + describe('extractParamsFromBidRequest', () => { + it('should handle empty bid request', () => { + const bidRequest = null; + expect(extractParamsFromBidRequest(bidRequest)).to.be.null; + }); + + it('should handle missing params in bid request', () => { + const bidRequest = {}; + expect(extractParamsFromBidRequest(bidRequest)).to.be.null; + }); + + it('should handle params in bid request', () => { + const bidRequest = { + params: { + foo: '123', + bar: 456 + } + }; + expect(extractParamsFromBidRequest(bidRequest)).to.deep.equal(bidRequest.params); + }); + }); + + describe('extractSizesFromBidRequest', () => { + it('should handle empty bid request', () => { + const bidRequest = null; + expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal([]); + }); + + it('should handle missing sizes in bid request', () => { + const bidRequest = {}; + expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal([]); + }); + + it('should handle sizes in bid request', () => { + const bidRequest = { + mediaTypes: { + banner: { + sizes: [[12, 34], [56, 78]] + } + } + }; + expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal(bidRequest.mediaTypes.banner.sizes); + }); + + it('should handle sizes in bid request (backward compatibility)', () => { + const bidRequest = { + sizes: [[12, 34], [56, 78]] + }; + expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal(bidRequest.sizes); + }); + + it('should prefer sizes in mediaTypes.banner', () => { + const bidRequest = { + sizes: [[12, 34]], + mediaTypes: { + banner: { + sizes: [[56, 78]] + } + } + }; + expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal(bidRequest.mediaTypes.banner.sizes); + }); + }); + + describe('extractTopWindowReferrerFromBidRequest', () => { + it('should use fallback if bid request is empty', () => { + const bidRequest = null; + expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(window.top.document.referrer); + }); + + it('should use fallback if refererInfo in bid request is missing', () => { + const bidRequest = {}; + expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(window.top.document.referrer); + }); + + it('should use fallback if refererInfo.referer in bid request is missing', () => { + const bidRequest = { + refererInfo: {} + }; + expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(window.top.document.referrer); + }); + + it('should use fallback if refererInfo.referer in bid request is empty', () => { + const bidRequest = { + refererInfo: { + referer: '' + } + }; + expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(window.top.document.referrer); + }); + + it('should use refererInfo.referer from bid request ', () => { + const bidRequest = { + refererInfo: { + referer: 'foobar' + } + }; + expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(bidRequest.refererInfo.referer); + }); + }); + + describe('extractTopWindowUrlFromBidRequest', () => { + it('should use fallback if bid request is empty', () => { + const bidRequest = null; + expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(window.top.location.href); + }); + + it('should use fallback if refererInfo in bid request is missing', () => { + const bidRequest = {}; + expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(window.top.location.href); + }); + + it('should use fallback if refererInfo.canonicalUrl in bid request is missing', () => { + const bidRequest = { + refererInfo: {} + }; + expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(window.top.location.href); + }); + + it('should use fallback if refererInfo.canonicalUrl in bid request is empty', () => { + const bidRequest = { + refererInfo: { + canonicalUrl: '' + } + }; + expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(window.top.location.href); + }); + + it('should use refererInfo.canonicalUrl from bid request ', () => { + const bidRequest = { + refererInfo: { + canonicalUrl: 'foobar' + } + }; + expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(bidRequest.refererInfo.canonicalUrl); + }); + }); + + describe('spec', () => { + let adapter; + + beforeEach(() => { + 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 necessary information is given', () => { + expect(spec.isBidRequestValid({ + mediaTypes: { + banner: { + sizes: [[100, 200]] + } + }, + params: { + publisher: 'test', + placement: '1234' + } + })).to.be.true; + }); + + it('should return true when necessary information is given (backward compatibility)', () => { + expect(spec.isBidRequestValid({ + sizes: [[100, 200]], + params: { + publisher: 'test', + placement: '1234' + } + })).to.be.true; + }); + + it('should return false on empty bid', () => { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('should return false on missing sizes', () => { + expect(spec.isBidRequestValid({ + params: { + publisher: 'test', + placement: '1234' + } + })).to.be.false; + }); + + it('should return false on empty sizes', () => { + expect(spec.isBidRequestValid({ + mediaTypes: { + banner: { + sizes: [] + } + }, + params: { + publisher: 'test', + placement: '1234' + } + })).to.be.false; + }); + + it('should return false on empty sizes (backward compatibility)', () => { + expect(spec.isBidRequestValid({ + sizes: [], + params: { + publisher: 'test', + placement: '1234' + } + })).to.be.false; + }); + + it('should return false on missing params', () => { + expect(spec.isBidRequestValid({ + mediaTypes: { + banner: { + sizes: [[100, 200]] + } + }, + })).to.be.false; + }); + + it('should return false on invalid params', () => { + expect(spec.isBidRequestValid({ + mediaTypes: { + banner: { + sizes: [[100, 200]] + } + }, + params: 'bar' + })).to.be.false; + }); + + it('should return false on empty params', () => { + expect(spec.isBidRequestValid({ + mediaTypes: { + banner: { + sizes: [[100, 200]] + } + }, + params: {} + })).to.be.false; + }); + + it('should return false on missing publisher', () => { + expect(spec.isBidRequestValid({ + mediaTypes: { + banner: { + sizes: [[100, 200]] + } + }, + params: { + placement: '1234' + } + })).to.be.false; + }); + + it('should return false on missing placement', () => { + expect(spec.isBidRequestValid({ + mediaTypes: { + banner: { + sizes: [[100, 200]] + } + }, + params: { + publisher: 'test' + } + })).to.be.false; + }); + }); + + describe('buildRequests', () => { + it('should send one bid request per ad unit to the endpoint via POST', () => { + const bidRequests = [ + { + bidder: BIDDER_CODE, + bidId: 'bidId1', + adUnitCode: 'adUnitCode1', + transactionId: 'transactionId1', + auctionId: 'auctionId1', + mediaTypes: { + banner: { + sizes: [[100, 200], [300, 400]] + } + }, + params: { + publisher: 'publisher1', + placement: 'placement1' + } + }, + { + bidder: BIDDER_CODE, + bidId: 'bidId2', + adUnitCode: 'adUnitCode2', + transactionId: 'transactionId2', + auctionId: 'auctionId2', + mediaTypes: { + banner: { + sizes: [[500, 600]] + } + }, + params: { + publisher: 'publisher2', + placement: 'placement2' + } + } + ]; + + const result = spec.buildRequests(bidRequests); + expect(result.length).to.equal(2); + + expect(result[0].url).to.equal(ENDPOINT_URL.replace(PUBLISHER_PLACEHOLDER, bidRequests[0].params.publisher)); + expect(result[0].method).to.equal(ENDPOINT_METHOD); + expect(result[0].data).to.deep.equal({ + bidId: bidRequests[0].bidId, + auctionId: bidRequests[0].auctionId, + transactionId: bidRequests[0].transactionId, + adUnitCode: bidRequests[0].adUnitCode, + pageUrl: extractTopWindowUrlFromBidRequest(bidRequests[0]), + referrer: extractTopWindowReferrerFromBidRequest(bidRequests[0]), + sizes: extractSizesFromBidRequest(bidRequests[0]), + params: extractParamsFromBidRequest(bidRequests[0]), + gdpr: null + }); + + expect(result[1].url).to.equal(ENDPOINT_URL.replace(PUBLISHER_PLACEHOLDER, bidRequests[1].params.publisher)); + expect(result[1].method).to.equal(ENDPOINT_METHOD); + expect(result[1].data).to.deep.equal({ + bidId: bidRequests[1].bidId, + auctionId: bidRequests[1].auctionId, + transactionId: bidRequests[1].transactionId, + adUnitCode: bidRequests[1].adUnitCode, + pageUrl: extractTopWindowUrlFromBidRequest(bidRequests[1]), + referrer: extractTopWindowReferrerFromBidRequest(bidRequests[1]), + sizes: extractSizesFromBidRequest(bidRequests[1]), + params: extractParamsFromBidRequest(bidRequests[1]), + gdpr: null + }); + }); + + it('should pass gdpr informations', () => { + const bidderRequest = { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true + } + }; + + const bidRequests = [ + { + bidder: BIDDER_CODE, + bidId: 'bidId3', + adUnitCode: 'adUnitCode3', + transactionId: 'transactionId3', + auctionId: 'auctionId3', + mediaTypes: { + banner: { + sizes: [[100, 200], [300, 400]] + } + }, + params: { + publisher: 'publisher3', + placement: 'placement3' + } + } + ]; + + const result = spec.buildRequests(bidRequests, bidderRequest); + expect(result.length).to.equal(1); + expect(result[0].data.gdpr).to.deep.equal(extractGdprFromBidderRequest(bidderRequest)); + }); + + it('should encode publisher param in endpoint url', () => { + const bidRequests = [ + { + bidder: BIDDER_CODE, + bidId: 'bidId1', + adUnitCode: 'adUnitCode1', + transactionId: 'transactionId1', + auctionId: 'auctionId1', + mediaTypes: { + banner: { + sizes: [[100, 200]] + } + }, + params: { + publisher: 'crazy publisher key äÖÜ', + placement: 'placement1' + } + }, + ]; + + const result = spec.buildRequests(bidRequests); + expect(result[0].url).to.equal(ENDPOINT_URL.replace(PUBLISHER_PLACEHOLDER, encodeURIComponent(bidRequests[0].params.publisher))); + }); + + it('should handle empty bidRequests', () => { + expect(spec.buildRequests([])).to.deep.equal([]); + }); + }); + + describe('interpretResponse', () => { + it('should correctly interpret the server response', () => { + const serverResponse = { + body: { + bid: { + bidId: 'bidId1', + price: 0.12, + net: true, + currency: 'EUR', + ttl: 123 + }, + creative: { + id: 'creativeId1', + width: 100, + height: 200, + html: '
Hello World
' + } + } + }; + + const result = spec.interpretResponse(serverResponse); + expect(result).to.deep.equal([ + { + requestId: serverResponse.body.bid.bidId, + cpm: serverResponse.body.bid.price, + netRevenue: serverResponse.body.bid.net, + currency: serverResponse.body.bid.currency, + ttl: serverResponse.body.bid.ttl, + creativeId: serverResponse.body.creative.id, + width: serverResponse.body.creative.width, + height: serverResponse.body.creative.height, + ad: serverResponse.body.creative.html + } + ]); + }); + + it('should handle empty serverResponse', () => { + expect(spec.interpretResponse({})).to.deep.equal([]); + }); + + it('should handle missing bid', () => { + expect(spec.interpretResponse({ + body: { + creative: {} + } + })).to.deep.equal([]); + }); + + it('should handle missing creative', () => { + expect(spec.interpretResponse({ + body: { + bid: {} + } + })).to.deep.equal([]); + }); + }); + }); +}); diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js index fbdfc9f30ee..2b9615fb572 100755 --- a/test/spec/modules/advangelistsBidAdapter_spec.js +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/advangelistsBidAdapter'; -import { BANNER, VIDEO } from 'src/mediaTypes'; +import { spec } from 'modules/advangelistsBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; describe('advangelistsBidAdapter', function () { let bidRequests; diff --git a/test/spec/modules/advenueBidAdapter_spec.js b/test/spec/modules/advenueBidAdapter_spec.js index f00b9a9f680..2d7739361b4 100644 --- a/test/spec/modules/advenueBidAdapter_spec.js +++ b/test/spec/modules/advenueBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from '../../../modules/advenueBidAdapter'; +import {spec} from '../../../modules/advenueBidAdapter.js'; describe('AdvenueAdapter', function () { let bid = { diff --git a/test/spec/modules/advertlyBidAdapter_spec.js b/test/spec/modules/advertlyBidAdapter_spec.js new file mode 100755 index 00000000000..7825f11948a --- /dev/null +++ b/test/spec/modules/advertlyBidAdapter_spec.js @@ -0,0 +1,159 @@ +import { expect } from 'chai'; +import { spec } from 'modules/advertlyBidAdapter.js'; + +const ENDPOINT = 'https://api.advertly.com/www/admin/plugins/Prebid/getAd.php'; + +describe('The Advertly bidding adapter', function () { + describe('isBidRequestValid', function () { + it('should return false when given an invalid bid', function () { + const bid = { + bidder: 'advertly', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return true when given a publisherId in bid', function () { + const bid = { + bidder: 'advertly', + params: { + publisherId: 2 + }, + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + 'bidder': 'advertly', + 'params': { + 'publisherId': 2 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ] + }]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint url', function () { + expect(request.url).to.equal(ENDPOINT) + }); + + it('sets the proper banner object', function () { + expect(bidRequests[0].params.publisherId).to.equal(2); + }) + }); + const response = { + body: [ + { + 'requestId': '2ee937f15015c6', + 'cpm': '0.2000', + 'width': 300, + 'height': 600, + 'creativeId': '4', + 'currency': 'USD', + 'netRevenue': true, + 'ad': 'ads.html', + 'mediaType': 'banner' + }, + { + 'requestId': '3e1af92622bdc', + 'cpm': '0.2000', + 'creativeId': '4', + 'context': 'outstream', + 'currency': 'USD', + 'netRevenue': true, + 'vastUrl': 'tezt.xml', + 'width': 640, + 'height': 480, + 'mediaType': 'video' + }] + }; + + const request = [ + { + 'bidder': 'advertly', + 'params': { + 'publisherId': 2 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 600] + ] + } + }, + 'bidId': '2ee937f15015c6', + 'src': 'client', + }, + { + 'bidder': 'advertly', + 'params': { + 'publisherId': 2 + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [ + [640, 480] + ] + } + }, + 'bidId': '3e1af92622bdc', + 'src': 'client', + } + ]; + + describe('interpretResponse', function () { + it('return empty array when no ad found', function () { + const response = {}; + const request = { bidRequests: [] }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('check response for banner and video', function () { + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(2); + expect(bids[0].requestId).to.equal('2ee937f15015c6'); + expect(bids[0].cpm).to.equal('0.2000'); + expect(bids[1].cpm).to.equal('0.2000'); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(600); + expect(bids[1].vastUrl).to.not.equal(''); + expect(bids[0].ad).to.not.equal(''); + expect(bids[1].adResponse).to.not.equal(''); + expect(bids[1].renderer).not.to.be.an('undefined'); + }); + }); + + describe('On winning bid', function () { + const bids = spec.interpretResponse(response, request); + spec.onBidWon(bids); + }); + + describe('On bid Time out', function () { + const bids = spec.interpretResponse(response, request); + spec.onTimeout(bids); + }); + + describe('user sync', function () { + it('to check the user sync iframe', function () { + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + }); + }); +}); diff --git a/test/spec/modules/adxcgAnalyticsAdapter_spec.js b/test/spec/modules/adxcgAnalyticsAdapter_spec.js index 605da3bd6bc..a796e7e966d 100644 --- a/test/spec/modules/adxcgAnalyticsAdapter_spec.js +++ b/test/spec/modules/adxcgAnalyticsAdapter_spec.js @@ -1,23 +1,17 @@ -import adxcgAnalyticsAdapter from 'modules/adxcgAnalyticsAdapter'; +import adxcgAnalyticsAdapter from 'modules/adxcgAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; +import { server } from 'test/mocks/xhr.js'; let events = require('src/events'); let constants = require('src/constants.json'); describe('adxcg analytics adapter', function () { - let xhr; - let requests; - beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); sinon.stub(events, 'getEvents').returns([]); }); afterEach(function () { - xhr.restore(); events.getEvents.restore(); }); @@ -193,9 +187,9 @@ describe('adxcg analytics adapter', function () { // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, {}); - expect(requests.length).to.equal(1); + expect(server.requests.length).to.equal(1); - let realAfterBid = JSON.parse(requests[0].requestBody); + let realAfterBid = JSON.parse(server.requests[0].requestBody); expect(realAfterBid).to.deep.equal(expectedAfterBid); @@ -204,8 +198,8 @@ describe('adxcg analytics adapter', function () { // Step 6: Send auction bid won event events.emit(constants.EVENTS.BID_WON, wonRequest); - expect(requests.length).to.equal(2); - let winEventData = JSON.parse(requests[1].requestBody); + expect(server.requests.length).to.equal(2); + let winEventData = JSON.parse(server.requests[1].requestBody); expect(winEventData).to.deep.equal(wonExpect); }); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 3e73479259c..306914960c3 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,7 +1,6 @@ import {expect} from 'chai'; -import * as url from 'src/url'; -import {spec} from 'modules/adxcgBidAdapter'; -import {deepClone} from '../../../src/utils'; +import {spec} from 'modules/adxcgBidAdapter.js'; +import {deepClone, parseUrl} from 'src/utils.js'; describe('AdxcgAdapter', function () { let bidBanner = { @@ -119,7 +118,7 @@ describe('AdxcgAdapter', function () { let request = spec.buildRequests([bidBanner]); expect(request).to.exist; expect(request.method).to.equal('GET'); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); expect(parsedRequestUrl.pathname).to.equal('/get/adi'); @@ -152,7 +151,7 @@ describe('AdxcgAdapter', function () { let request = spec.buildRequests([bidVideo]); expect(request).to.exist; expect(request.method).to.equal('GET'); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); expect(parsedRequestUrl.pathname).to.equal('/get/adi'); @@ -191,7 +190,7 @@ describe('AdxcgAdapter', function () { let request = spec.buildRequests([bidNative]); expect(request).to.exist; expect(request.method).to.equal('GET'); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); expect(parsedRequestUrl.pathname).to.equal('/get/adi'); @@ -229,7 +228,7 @@ describe('AdxcgAdapter', function () { consentString: 'consentDataString' } }); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); let query = parsedRequestUrl.search; expect(query.gdpr).to.equal('1'); @@ -243,7 +242,7 @@ describe('AdxcgAdapter', function () { consentString: 'consentDataString' } }); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); let query = parsedRequestUrl.search; expect(query.gdpr).to.be.undefined; @@ -258,7 +257,7 @@ describe('AdxcgAdapter', function () { it('should send pubcid if available', function () { let request = spec.buildRequests(bid, bidderRequests); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); let query = parsedRequestUrl.search; expect(query.pubcid).to.equal('pubcidabcd'); }); @@ -272,7 +271,7 @@ describe('AdxcgAdapter', function () { it('should send pubcid if available', function () { let request = spec.buildRequests(bid, bidderRequests); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); let query = parsedRequestUrl.search; expect(query.tdid).to.equal('tdidabcd'); }); @@ -286,7 +285,7 @@ describe('AdxcgAdapter', function () { it('should send pubcid if available', function () { let request = spec.buildRequests(bid, bidderRequests); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); let query = parsedRequestUrl.search; expect(query.id5id).to.equal('id5idsample'); }); @@ -300,7 +299,7 @@ describe('AdxcgAdapter', function () { it('should send pubcid if available', function () { let request = spec.buildRequests(bid, bidderRequests); - let parsedRequestUrl = url.parse(request.url); + let parsedRequestUrl = parseUrl(request.url); let query = parsedRequestUrl.search; expect(query.idl_env).to.equal('idl_envsample'); }); diff --git a/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js b/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..e4a8fa9dccd --- /dev/null +++ b/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js @@ -0,0 +1,415 @@ +import adxpremiumAnalyticsAdapter from 'modules/adxpremiumAnalyticsAdapter.js'; +import { testSend } from 'modules/adxpremiumAnalyticsAdapter.js'; +import { expect } from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import { server } from 'test/mocks/xhr.js'; + +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('AdxPremium analytics adapter', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + describe('track', function () { + let initOptions = { + pubId: 123, + sid: 's2' + }; + + let auctionInit = { + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b', + 'timestamp': 1589707613899, + 'auctionStatus': 'inProgress', + 'adUnits': [ + { + 'code': 'div-gpt-ad-1533155193780-2', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': 'aebbdfa9-3e0f-49b6-ad87-437aaa88db2d' + } + } + ], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'transactionId': 'f68c54c2-0814-4ae5-95f5-09f6dd9dc1ef' + } + ], + 'adUnitCodes': [ + 'div-gpt-ad-1533155193780-2' + ], + 'labels': [ + 'BA' + ], + 'bidderRequests': [ + { + 'bidderCode': 'luponmedia', + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b', + 'bidderRequestId': '18c49b05a23645', + 'bids': [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': 'aebbdfa9-3e0f-49b6-ad87-437aaa88db2d' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'transactionId': 'f68c54c2-0814-4ae5-95f5-09f6dd9dc1ef', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '284f8e1469246a', + 'bidderRequestId': '18c49b05a23645', + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + } + } + ], + 'auctionStart': 1589707613899, + 'timeout': 2000, + 'refererInfo': { + 'referer': 'https://test.com/article/176067', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://test.com/article/176067' + ] + }, + 'gdprConsent': {} + } + ], + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 2000, + 'config': { + 'pubId': 4444, + 'sid': 's2' + } + }; + + // requests & responses + let bidRequest = { + 'bidderCode': 'luponmedia', + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b', + 'bidderRequestId': '18c49b05a23645', + 'bids': [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': 'aebbdfa9-3e0f-49b6-ad87-437aaa88db2d' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'transactionId': 'f68c54c2-0814-4ae5-95f5-09f6dd9dc1ef', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '284f8e1469246a', + 'bidderRequestId': '18c49b05a23645', + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + }, + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c5' + }, + 'crumbs': { + 'pubcid': 'aebbdfa9-3e0f-49b6-ad87-437aaa88db2d' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-3', + 'transactionId': 'f68c54c2-0814-4ae5-95f5-09f6dd9dc1ef', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '284f8e1469246b', + 'bidderRequestId': '18c49b05a23645', + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + } + ], + 'auctionStart': 1589707613899, + 'timeout': 2000, + 'refererInfo': { + 'referer': 'https://test.com/article/176067', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://test.com/article/176067' + ] + }, + 'start': 1589707613908 + }; + + let bidResponse = { + 'bidderCode': 'luponmedia', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '3b40e0da8968f5', + 'requestId': '284f8e1469246a', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.43, + 'creativeId': '443801010', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': " ", + 'originalCpm': '0.43', + 'originalCurrency': 'USD', + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b', + 'responseTimestamp': 1589707615188, + 'requestTimestamp': 1589707613908, + 'bidder': 'luponmedia', + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'timeToRespond': 1280, + 'pbLg': '0.00', + 'pbMg': '0.40', + 'pbHg': '0.43', + 'pbAg': '0.40', + 'pbDg': '0.43', + 'pbCg': '0.43', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'luponmedia', + 'hb_adid': '3b40e0da8968f5', + 'hb_pb': '0.43', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + } + }; + + // what we expect after general auction + + let expectedAfterBidData = JSON.parse(atob('eyJwdWJsaXNoZXJfaWQiOjEyMywiYXVjdGlvbl9pZCI6ImM0ZjBjY2UwLTI2NGMtNDgzYS1iMmY0LThhYzIyNDhhODk2YiIsInJlZmVyZXIiOiJodHRwczovL3Rlc3QuY29tL2FydGljbGUvMTc2MDY3Iiwic2NyZWVuX3Jlc29sdXRpb24iOiIxNDQweDkwMCIsImRldmljZV90eXBlIjoiZGVza3RvcCIsImdlbyI6bnVsbCwiZXZlbnRzIjpbeyJ0eXBlIjoiVElNRU9VVCIsImJpZGRlcl9jb2RlIjoibHVwb25tZWRpYSIsImV2ZW50X3RpbWVzdGFtcCI6MTU4OTcwNzYxMzkwOCwiYmlkX2dwdF9jb2RlcyI6eyJkaXYtZ3B0LWFkLTE1MzMxNTUxOTM3ODAtMiI6W1szMDAsMjUwXV0sImRpdi1ncHQtYWQtMTUzMzE1NTE5Mzc4MC0zIjpbWzMwMCwyNTBdXX19XX0=')); + expectedAfterBidData['screen_resolution'] = window.screen.width + 'x' + window.screen.height; + expectedAfterBidData = btoa(JSON.stringify(expectedAfterBidData)); + + let expectedAfterBid = { + 'query': 'mutation {createEvent(input: {event: {eventData: "' + expectedAfterBidData + '"}}) {event {createTime } } }' + }; + + // what we expect after timeout + + let expectedAfterTimeoutData = JSON.parse(atob('eyJwdWJsaXNoZXJfaWQiOjEyMywiYXVjdGlvbl9pZCI6ImM0ZjBjY2UwLTI2NGMtNDgzYS1iMmY0LThhYzIyNDhhODk2YiIsInJlZmVyZXIiOiJodHRwczovL3Rlc3QuY29tL2FydGljbGUvMTc2MDY3Iiwic2NyZWVuX3Jlc29sdXRpb24iOiIxNDQweDkwMCIsImRldmljZV90eXBlIjoiZGVza3RvcCIsImdlbyI6bnVsbCwiZXZlbnRzIjpbeyJ0eXBlIjoiVElNRU9VVCIsImJpZGRlcl9jb2RlIjoibHVwb25tZWRpYSIsImV2ZW50X3RpbWVzdGFtcCI6MTU4OTcwNzYxMzkwOCwiYmlkX2dwdF9jb2RlcyI6eyJkaXYtZ3B0LWFkLTE1MzMxNTUxOTM3ODAtMiI6W1szMDAsMjUwXV0sImRpdi1ncHQtYWQtMTUzMzE1NTE5Mzc4MC0zIjpbWzMwMCwyNTBdXX19XX0=')); + expectedAfterTimeoutData['screen_resolution'] = window.screen.width + 'x' + window.screen.height; + expectedAfterTimeoutData = btoa(JSON.stringify(expectedAfterTimeoutData)); + + let expectedAfterTimeout = { + 'query': 'mutation {createEvent(input: {event: {eventData: "' + expectedAfterTimeoutData + '"}}) {event {createTime } } }' + }; + + // lets simulate that some bidders timeout + let bidTimeoutArgsV1 = [ + { + 'bidId': '284f8e1469246b', + 'bidder': 'luponmedia', + 'adUnitCode': 'div-gpt-ad-1533155193780-3', + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b' + } + ]; + + // now simulate some WIN and RENDERING + let wonRequest = { + 'bidderCode': 'luponmedia', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '3b40e0da8968f5', + 'requestId': '284f8e1469246a', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.43, + 'creativeId': '443801010', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': " ", + 'originalCpm': '0.43', + 'originalCurrency': 'USD', + 'auctionId': 'c4f0cce0-264c-483a-b2f4-8ac2248a896b', + 'responseTimestamp': 1589707615188, + 'requestTimestamp': 1589707613908, + 'bidder': 'luponmedia', + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'timeToRespond': 1280, + 'pbLg': '0.00', + 'pbMg': '0.40', + 'pbHg': '0.43', + 'pbAg': '0.40', + 'pbDg': '0.43', + 'pbCg': '0.43', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'luponmedia', + 'hb_adid': '3b40e0da8968f5', + 'hb_pb': '0.43', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered', + 'params': [ + { + 'siteId': 303522, + 'keyId': '4o2c4' + } + ] + }; + + let wonExpectData = JSON.parse(atob('eyJwdWJsaXNoZXJfaWQiOjEyMywiYXVjdGlvbl9pZCI6ImM0ZjBjY2UwLTI2NGMtNDgzYS1iMmY0LThhYzIyNDhhODk2YiIsInJlZmVyZXIiOiJodHRwczovL3Rlc3QuY29tL2FydGljbGUvMTc2MDY3Iiwic2NyZWVuX3Jlc29sdXRpb24iOiIxNDQweDkwMCIsImRldmljZV90eXBlIjoiZGVza3RvcCIsImdlbyI6bnVsbCwiZXZlbnRzIjpbeyJ0eXBlIjoiVElNRU9VVCIsImJpZGRlcl9jb2RlIjoibHVwb25tZWRpYSIsImV2ZW50X3RpbWVzdGFtcCI6MTU4OTcwNzYxMzkwOCwiYmlkX2dwdF9jb2RlcyI6eyJkaXYtZ3B0LWFkLTE1MzMxNTUxOTM3ODAtMiI6W1szMDAsMjUwXV0sImRpdi1ncHQtYWQtMTUzMzE1NTE5Mzc4MC0zIjpbWzMwMCwyNTBdXX19LHsidHlwZSI6IlJFU1BPTlNFIiwiYmlkZGVyX2NvZGUiOiJsdXBvbm1lZGlhIiwiZXZlbnRfdGltZXN0YW1wIjoxNTg5NzA3NjE1MTg4LCJzaXplIjoiMzAweDI1MCIsImdwdF9jb2RlIjoiZGl2LWdwdC1hZC0xNTMzMTU1MTkzNzgwLTIiLCJjdXJyZW5jeSI6IlVTRCIsImNyZWF0aXZlX2lkIjoiNDQzODAxMDEwIiwidGltZV90b19yZXNwb25kIjoxMjgwLCJjcG0iOjAuNDMsImlzX3dpbm5pbmciOmZhbHNlfV19')); + wonExpectData['screen_resolution'] = window.screen.width + 'x' + window.screen.height; + wonExpectData = btoa(JSON.stringify(wonExpectData)); + + let wonExpect = { + 'query': 'mutation {createEvent(input: {event: {eventData: "' + wonExpectData + '"}}) {event {createTime } } }' + }; + + adapterManager.registerAnalyticsAdapter({ + code: 'adxpremium', + adapter: adxpremiumAnalyticsAdapter + }); + + beforeEach(function () { + adapterManager.enableAnalytics({ + provider: 'adxpremium', + options: initOptions + }); + }); + + afterEach(function () { + adxpremiumAnalyticsAdapter.disableAnalytics(); + }); + + it('builds and sends auction data', function () { + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); + + // 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, {}); + + testSend(); + + expect(server.requests.length).to.equal(2); + + let realAfterBid = JSON.parse(server.requests[0].requestBody); + + expect(realAfterBid).to.deep.equal(expectedAfterBid); + + // expect after timeout + expect(realAfterBid).to.deep.equal(expectedAfterTimeout); + + // Step 6: Send auction bid won event + events.emit(constants.EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.equal(3); + let winEventData = JSON.parse(server.requests[1].requestBody); + + expect(winEventData).to.deep.equal(wonExpect); + }); + }); +}); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index a76468671c8..d2d4e10c17f 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -1,19 +1,35 @@ import { expect } from 'chai'; -import { parse } from '../../../src/url'; -import { spec } from 'modules/adyoulikeBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/adyoulikeBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('Adyoulike Adapter', function () { const canonicalUrl = 'https://canonical.url/?t=%26'; + const referrerUrl = 'http://referrer.url/?param=value'; const defaultDC = 'hb-api'; + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + }, + refererInfo: {referer: referrerUrl} + }; const bidRequestWithEmptyPlacement = [ { 'bidId': 'bid_id_0', 'bidder': 'adyoulike', 'placementCode': 'adunit/hb-0', 'params': {}, - 'sizes': '300x250' + 'sizes': '300x250', + 'mediaTypes': + { 'banner': + {'sizes': ['300x250', '300x600'] + } + } } ]; const bidRequestWithEmptySizes = { @@ -39,6 +55,11 @@ describe('Adyoulike Adapter', function () { 'placement': 'placement_0' }, 'sizes': '300x250', + 'mediaTypes': + { 'banner': + {'sizes': ['300x250'] + } + }, 'transactionId': 'bid_id_0_transaction_id' } ]; @@ -53,6 +74,11 @@ describe('Adyoulike Adapter', function () { 'DC': 'fra01' }, 'sizes': '300x250', + 'mediaTypes': + { 'banner': + {'sizes': ['300x250'] + } + }, 'transactionId': 'bid_id_0_transaction_id' } ]; @@ -66,6 +92,11 @@ describe('Adyoulike Adapter', function () { 'placement': 'placement_0' }, 'sizes': '300x250', + 'mediaTypes': + { 'banner': + {'sizes': ['300x250'] + } + }, 'transactionId': 'bid_id_0_transaction_id' }, { @@ -76,6 +107,11 @@ describe('Adyoulike Adapter', function () { 'placement': 'placement_1' }, 'sizes': [[300, 600]], + 'mediaTypes': + { 'banner': + {'sizes': ['300x600'] + } + }, 'transactionId': 'bid_id_1_transaction_id' }, { @@ -97,6 +133,33 @@ describe('Adyoulike Adapter', function () { } ]; + const requestDataOnePlacement = { + 'bid_id_0': + { 'PlacementID': 'e622af275681965d3095808561a1e510', + 'TransactionID': '1bca18cc-c0fe-439b-88c2-8247d3448f22', + 'Width': 300, + 'Height': 600, + 'AvailableSizes': '300x600' + } + } + + const requestDataMultiPlacement = { + 'bid_id_0': + { 'PlacementID': 'e622af275681965d3095808561a1e510', + 'TransactionID': '1bca18cc-c0fe-439b-88c2-8247d3448f22', + 'Width': 300, + 'Height': 600, + 'AvailableSizes': '300x600' + }, + 'bid_id_1': + { 'PlacementID': 'e622af275681965d3095808561a1e510', + 'TransactionID': 'e63b2d86-ca60-4167-9cf1-497607079634', + 'Width': 400, + 'Height': 250, + 'AvailableSizes': '300x250' + } + } + const responseWithEmptyPlacement = [ { 'Placement': 'placement_0' @@ -108,8 +171,7 @@ describe('Adyoulike Adapter', function () { 'Placement': 'placement_0', 'Ad': 'placement_0', 'Price': 0.5, - 'Height': 300, - 'Width': 300, + 'Height': 600, } ]; const responseWithMultiplePlacements = [ @@ -118,16 +180,16 @@ describe('Adyoulike Adapter', function () { 'Placement': 'placement_0', 'Ad': 'placement_0', 'Price': 0.5, - 'Height': 300, - 'Width': 300, + 'Height': 0, // test with wrong value + 'Width': 300 }, { 'BidID': 'bid_id_1', 'Placement': 'placement_1', 'Ad': 'placement_1', 'Price': 0.6, - 'Height': 300, - 'Width': 300, + 'Height': 250 + // 'Width' test with missing value } ]; const adapter = newBidder(spec); @@ -188,8 +250,9 @@ describe('Adyoulike Adapter', function () { canonicalQuery.restore(); }); - it('should add gdpr consent information to the request', function () { + it('should add gdpr/usp consent information to the request', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentData = '1YCC'; let bidderRequest = { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', @@ -197,8 +260,10 @@ describe('Adyoulike Adapter', function () { 'gdprConsent': { consentString: consentString, gdprApplies: true - } + }, + 'uspConsent': uspConsentData }; + bidderRequest.bids = bidRequestWithSinglePlacement; const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); @@ -207,15 +272,40 @@ describe('Adyoulike Adapter', function () { expect(payload.gdprConsent).to.exist; expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; + expect(payload.uspConsent).to.exist.and.to.equal(uspConsentData); + }); + + it('should not set a default value for gdpr consentRequired', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentData = '1YCC'; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString + }, + 'uspConsent': uspConsentData + }; + + bidderRequest.bids = bidRequestWithSinglePlacement; + + const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdprConsent).to.exist; + expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); + expect(payload.gdprConsent.consentRequired).to.be.null; }); it('sends bid request to endpoint with single placement', function () { - const request = spec.buildRequests(bidRequestWithSinglePlacement); + const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); const payload = JSON.parse(request.data); expect(request.url).to.contain(getEndpoint()); expect(request.method).to.equal('POST'); expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); @@ -225,7 +315,7 @@ describe('Adyoulike Adapter', function () { it('sends bid request to endpoint with single placement without canonical', function () { canonicalQuery.restore(); - const request = spec.buildRequests(bidRequestWithSinglePlacement); + const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); const payload = JSON.parse(request.data); expect(request.url).to.contain(getEndpoint()); @@ -239,12 +329,13 @@ describe('Adyoulike Adapter', function () { }); it('sends bid request to endpoint with multiple placements', function () { - const request = spec.buildRequests(bidRequestMultiPlacements); + const request = spec.buildRequests(bidRequestMultiPlacements, bidderRequest); const payload = JSON.parse(request.data); expect(request.url).to.contain(getEndpoint()); expect(request.method).to.equal('POST'); expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); expect(payload.Version).to.equal('1.0'); @@ -259,7 +350,7 @@ describe('Adyoulike Adapter', function () { }); it('sends bid request to endpoint setted by parameters', function () { - const request = spec.buildRequests(bidRequestWithDCPlacement); + const request = spec.buildRequests(bidRequestWithDCPlacement, bidderRequest); const payload = JSON.parse(request.data); expect(request.url).to.contain(getEndpoint(`${defaultDC}-fra01`)); @@ -288,30 +379,30 @@ describe('Adyoulike Adapter', function () { it('receive reponse with single placement', function () { serverResponse.body = responseWithSinglePlacement; - let result = spec.interpretResponse(serverResponse, bidRequestWithSinglePlacement); + let result = spec.interpretResponse(serverResponse, {data: '{"Bids":' + JSON.stringify(requestDataOnePlacement) + '}'}); 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); + expect(result[0].height).to.equal(600); }); it('receive reponse with multiple placement', function () { serverResponse.body = responseWithMultiplePlacements; - let result = spec.interpretResponse(serverResponse, bidRequestMultiPlacements); + let result = spec.interpretResponse(serverResponse, {data: '{"Bids":' + JSON.stringify(requestDataMultiPlacement) + '}'}); expect(result.length).to.equal(2); 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[0].height).to.equal(600); 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); + expect(result[1].width).to.equal(400); + expect(result[1].height).to.equal(250); }); }); }); diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index 4b78711f46f..80ecab764e8 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -1,5 +1,5 @@ -import { spec } from 'modules/ajaBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/ajaBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const ENDPOINT = 'https://ad.as.amanad.adtdp.com/v2/prebid'; diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js new file mode 100644 index 00000000000..790f3bf2581 --- /dev/null +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -0,0 +1,384 @@ +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; +import { spec } from 'modules/amxBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; + +const sampleRequestId = '82c91e127a9b93e'; +const sampleDisplayAd = (additionalImpressions) => `${additionalImpressions}`; +const sampleDisplayCRID = '78827819'; +// minimal example vast +const sampleVideoAd = (addlImpression) => ` +00:00:15${addlImpression} +`.replace(/\n+/g, '') + +const embeddedTrackingPixel = `https://1x1.a-mo.net/hbx/g_impression?A=sample&B=20903`; +const sampleNurl = 'https://example.exchange/nurl'; + +const sampleBidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: utils.getUniqueIdentifierStr(), + vendorData: {} + }, + auctionId: utils.getUniqueIdentifierStr(), + uspConsent: '1YYY', + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + } +}; + +const sampleBidRequestBase = { + bidder: spec.code, + params: { + endpoint: 'https://httpbin.org/post', + }, + sizes: [[320, 50]], + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'div-gpt-ad-example', + transactionId: utils.getUniqueIdentifierStr(), + bidId: sampleRequestId, + auctionId: utils.getUniqueIdentifierStr(), +}; + +const sampleBidRequestVideo = { + ...sampleBidRequestBase, + bidId: sampleRequestId + '_video', + sizes: [[300, 150]], + mediaTypes: { + [VIDEO]: { + sizes: [[360, 250]] + } + } +}; + +const sampleServerResponse = { + 'p': { + 'hreq': ['https://1x1.a-mo.net/hbx/g_sync?partner=test', 'https://1x1.a-mo.net/hbx/g_syncf?__st=iframe'] + }, + 'r': { + [sampleRequestId]: [ + { + 'b': [ + { + 'adid': '78827819', + 'adm': sampleDisplayAd(''), + 'adomain': [ + 'example.com' + ], + 'crid': sampleDisplayCRID, + 'ext': { + 'himp': [ + embeddedTrackingPixel + ], + }, + 'nurl': sampleNurl, + 'h': 600, + 'id': '2014691335735134254', + 'impid': '1', + 'price': 0.25, + 'w': 300 + }, + { + 'adid': '222976952', + 'adm': sampleVideoAd(''), + 'adomain': [ + 'example.com' + ], + 'crid': sampleDisplayCRID, + 'ext': { + 'himp': [ + embeddedTrackingPixel + ], + }, + 'nurl': sampleNurl, + 'h': 1, + 'id': '7735706981389902829', + 'impid': '1', + 'price': 0.25, + 'w': 1 + }, + ], + } + ] + }, +} + +describe('AmxBidAdapter', () => { + describe('isBidRequestValid', () => { + it('endpoint must be an optional string', () => { + expect(spec.isBidRequestValid({params: { endpoint: 1 }})).to.equal(false) + expect(spec.isBidRequestValid({params: { endpoint: 'test' }})).to.equal(true) + }); + + it('tagId is an optional string', () => { + expect(spec.isBidRequestValid({params: { tagId: 1 }})).to.equal(false) + expect(spec.isBidRequestValid({params: { tagId: 'test' }})).to.equal(true) + }); + + it('testMode is an optional boolean', () => { + expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(false) + expect(spec.isBidRequestValid({params: { testMode: false }})).to.equal(true) + }); + + it('none of the params are required', () => { + expect(spec.isBidRequestValid({})).to.equal(true) + }); + }) + describe('getUserSync', () => { + it('will only sync from valid server responses', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + expect(syncs).to.eql([]); + }); + + it('will return valid syncs from a server response', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [{body: sampleServerResponse}]); + expect(syncs.length).to.equal(2); + expect(syncs[0].type).to.equal('image'); + expect(syncs[1].type).to.equal('iframe'); + }); + + it('will filter out iframe syncs based on options', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: false }, [{body: sampleServerResponse}, {body: sampleServerResponse}]); + expect(syncs.length).to.equal(2); + expect(syncs).to.satisfy((allSyncs) => allSyncs.every((sync) => sync.type === 'image')) + }); + }); + + describe('buildRequests', () => { + it('will default to prebid.a-mo.net endpoint', () => { + const { url } = spec.buildRequests([], sampleBidderRequest); + expect(url).to.equal('https://prebid.a-mo.net/a/c') + }); + + it('reads test mode from the first bid request', () => { + const { data } = spec.buildRequests([{ + ...sampleBidRequestBase, + params: { + testMode: true + } + }], sampleBidderRequest); + expect(data.tm).to.equal(true); + }); + + it('handles referer data and GDPR, USP Consent', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); + delete data.m; // don't deal with "m" in this test + expect(data.gs).to.equal(sampleBidderRequest.gdprConsent.gdprApplies) + expect(data.gc).to.equal(sampleBidderRequest.gdprConsent.consentString) + expect(data.usp).to.equal(sampleBidderRequest.uspConsent) + }); + + it('can build a banner request', () => { + const { method, url, data } = spec.buildRequests([sampleBidRequestBase, { + ...sampleBidRequestBase, + bidId: sampleRequestId + '_2', + params: { + ...sampleBidRequestBase.params, + tagId: 'example' + } + }], sampleBidderRequest) + + expect(url).to.equal(sampleBidRequestBase.params.endpoint) + expect(method).to.equal('POST'); + expect(Object.keys(data.m).length).to.equal(2); + expect(data.m[sampleRequestId]).to.deep.equal({ + av: true, + aw: 300, + ah: 250, + tf: 0, + vr: false + }); + expect(data.m[sampleRequestId + '_2']).to.deep.equal({ + av: true, + aw: 300, + i: 'example', + ah: 250, + tf: 0, + vr: false, + }); + }); + + it('can build a video request', () => { + const { data } = spec.buildRequests([sampleBidRequestVideo], sampleBidderRequest); + expect(Object.keys(data.m).length).to.equal(1); + expect(data.m[sampleRequestId + '_video']).to.deep.equal({ + av: true, + aw: 360, + ah: 250, + tf: 0, + vr: true + }); + }); + }); + + describe('interpretResponse', () => { + const baseBidResponse = { + requestId: sampleRequestId, + cpm: 0.25, + creativeId: sampleDisplayCRID, + currency: 'USD', + netRevenue: true, + meta: { + advertiserDomains: ['example.com'], + }, + }; + + const baseRequest = { + data: { + m: { + [sampleRequestId]: { + aw: 300, + ah: 250, + }, + } + } + }; + + it('will handle a nobid response', () => { + const parsed = spec.interpretResponse({ body: '' }, baseRequest) + expect(parsed).to.eql([]) + }); + + it('can parse a display ad', () => { + const parsed = spec.interpretResponse({ body: sampleServerResponse }, baseRequest) + expect(parsed.length).to.equal(2) + + // we should have display, video, display + expect(parsed[0]).to.deep.equal({ + ...baseBidResponse, + meta: { + ...baseBidResponse.meta, + mediaType: BANNER, + }, + width: 300, + height: 600, // from the bid itself + ttl: 70, + ad: sampleDisplayAd( + `` + + `` + ), + }); + }); + + it('can parse a video ad', () => { + const parsed = spec.interpretResponse({ body: sampleServerResponse }, baseRequest) + expect(parsed.length).to.equal(2) + + // we should have display, video, display + const xml = parsed[1].vastXml + delete parsed[1].vastXml + + expect(xml).to.have.string(``) + expect(xml).to.have.string(``) + + expect(parsed[1]).to.deep.equal({ + ...baseBidResponse, + meta: { + ...baseBidResponse.meta, + mediaType: VIDEO, + }, + width: 300, + height: 250, + ttl: 90, + }); + }); + }); + + describe('analytics methods', () => { + let firedPixels = []; + let _Image = window.Image; + before(() => { + _Image = window.Image; + window.Image = class FakeImage { + set src(value) { + firedPixels.push(value) + } + } + }); + + beforeEach(() => { + firedPixels = []; + }); + + after(() => { + window.Image = _Image; + }); + + it('will fire an event for onSetTargeting', () => { + spec.onSetTargeting({ + bidder: 'example', + width: 300, + height: 250, + adId: 'ad-id', + mediaType: BANNER, + cpm: 1.23, + requestId: utils.getUniqueIdentifierStr(), + adUnitCode: 'div-gpt-ad', + adserverTargeting: { + hb_pb: '1.23', + hb_adid: 'ad-id', + hb_bidder: 'example' + } + }); + expect(firedPixels.length).to.equal(1) + expect(firedPixels[0]).to.match(/\/hbx\/g_pbst/) + try { + const parsed = new URL(firedPixels[0]); + const nestedData = parsed.searchParams.get('c2'); + expect(nestedData).to.equal(utils.formatQS({ + hb_pb: '1.23', + hb_adid: 'ad-id', + hb_bidder: 'example' + })); + } catch (e) { + // unsupported browser; try testing for string + const pixel = firedPixels[0]; + expect(pixel).to.have.string(encodeURIComponent('hb_pb=1.23')) + expect(pixel).to.have.string(encodeURIComponent('hb_adid=ad-id')) + } + }); + + it('will log an event for timeout', () => { + spec.onTimeout({ + bidder: 'example', + bidId: 'test-bid-id', + adUnitCode: 'div-gpt-ad', + timeout: 300, + auctionId: utils.getUniqueIdentifierStr() + }); + expect(firedPixels.length).to.equal(1) + expect(firedPixels[0]).to.match(/\/hbx\/g_pbto/) + }); + + it('will log an event for prebid win', () => { + spec.onBidWon({ + bidder: 'example', + adId: 'test-ad-id', + width: 300, + height: 250, + mediaType: VIDEO, + cpm: 1.34, + adUnitCode: 'div-gpt-ad', + timeout: 300, + auctionId: utils.getUniqueIdentifierStr() + }); + expect(firedPixels.length).to.equal(1) + expect(firedPixels[0]).to.match(/\/hbx\/g_pbwin/) + + const pixel = firedPixels[0]; + try { + const url = new URL(pixel); + expect(url.searchParams.get('C')).to.equal('1') + expect(url.searchParams.get('np')).to.equal('1.34') + } catch (e) { + expect(pixel).to.have.string('C=1') + expect(pixel).to.have.string('np=1.34') + } + }); + }); +}); diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js new file mode 100644 index 00000000000..cca175c3388 --- /dev/null +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -0,0 +1,199 @@ +import { spec } from 'modules/aniviewBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +const { expect } = require('chai'); + +describe('ANIVIEW Bid Adapter Test', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'aniview', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'video1', + 'sizes': [[300, 250], [640, 480]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + something: 'is wrong' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bid2Requests = [ + { + 'bidder': 'aniview', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'test1', + 'sizes': [[300, 250], [640, 480]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + let bid1Request = [ + { + 'bidder': 'aniview', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'test1', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + + it('Test 2 requests', function () { + const requests = spec.buildRequests(bid2Requests); + expect(requests.length).to.equal(2); + const r1 = requests[0]; + const d1 = requests[0].data; + expect(d1).to.have.property('AV_PUBLISHERID'); + expect(d1.AV_PUBLISHERID).to.equal('123456'); + expect(d1).to.have.property('AV_CHANNELID'); + expect(d1.AV_CHANNELID).to.equal('123456'); + expect(d1).to.have.property('AV_WIDTH'); + expect(d1.AV_WIDTH).to.equal(300); + expect(d1).to.have.property('AV_HEIGHT'); + expect(d1.AV_HEIGHT).to.equal(250); + expect(d1).to.have.property('AV_URL'); + expect(d1).to.have.property('cb'); + expect(d1).to.have.property('s2s'); + expect(d1.s2s).to.equal('1'); + expect(d1).to.have.property('pbjs'); + expect(d1.pbjs).to.equal(1); + expect(r1).to.have.property('url'); + expect(r1.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); + const r2 = requests[1]; + const d2 = requests[1].data; + expect(d2).to.have.property('AV_PUBLISHERID'); + expect(d2.AV_PUBLISHERID).to.equal('123456'); + expect(d2).to.have.property('AV_CHANNELID'); + expect(d2.AV_CHANNELID).to.equal('123456'); + expect(d2).to.have.property('AV_WIDTH'); + expect(d2.AV_WIDTH).to.equal(640); + expect(d2).to.have.property('AV_HEIGHT'); + expect(d2.AV_HEIGHT).to.equal(480); + expect(d2).to.have.property('AV_URL'); + expect(d2).to.have.property('cb'); + expect(d2).to.have.property('s2s'); + expect(d2.s2s).to.equal('1'); + expect(d2).to.have.property('pbjs'); + expect(d2.pbjs).to.equal(1); + expect(r2).to.have.property('url'); + expect(r2.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); + }); + + it('Test 1 request', function () { + const requests = spec.buildRequests(bid1Request); + expect(requests.length).to.equal(1); + const r = requests[0]; + const d = requests[0].data; + expect(d).to.have.property('AV_PUBLISHERID'); + expect(d.AV_PUBLISHERID).to.equal('123456'); + expect(d).to.have.property('AV_CHANNELID'); + expect(d.AV_CHANNELID).to.equal('123456'); + expect(d).to.have.property('AV_WIDTH'); + expect(d.AV_WIDTH).to.equal(640); + expect(d).to.have.property('AV_HEIGHT'); + expect(d.AV_HEIGHT).to.equal(480); + expect(d).to.have.property('AV_URL'); + expect(d).to.have.property('cb'); + expect(d).to.have.property('s2s'); + expect(d.s2s).to.equal('1'); + expect(d).to.have.property('pbjs'); + expect(d.pbjs).to.equal(1); + expect(r).to.have.property('url'); + expect(r.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = { + 'url': 'https://gov.aniview.com/api/adserver/vast3/', + 'data': { + 'bidId': '253dcb69fb2577', + AV_PUBLISHERID: '55b78633181f4603178b4568', + AV_CHANNELID: '55b7904d181f46410f8b4568', + } + }; + let serverResponse = {}; + serverResponse.body = 'FORDFORD00:00:15'; + + it('Check bid interpretResponse', function () { + const BIDDER_CODE = 'aniview'; + let bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).to.equal(1); + let bidResponse = bidResponses[0]; + expect(bidResponse.requestId).to.equal(bidRequest.data.bidId); + expect(bidResponse.cpm).to.equal('2'); + expect(bidResponse.ttl).to.equal(600); + expect(bidResponse.currency).to.equal('USD'); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.mediaType).to.equal('video'); + }); + + it('safely handles XML parsing failure from invalid bid response', function () { + let invalidServerResponse = {}; + invalidServerResponse.body = ''; + + let result = spec.interpretResponse(invalidServerResponse, bidRequest); + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', function () { + let nobidResponse = {}; + nobidResponse.body = ''; + + let result = spec.interpretResponse(nobidResponse, bidRequest); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + it('Check get sync pixels from response', function () { + let pixelUrl = 'https://sync.pixel.url/sync'; + let pixelEvent = 'inventory'; + let pixelType = '3'; + let pixelStr = '{"url":"' + pixelUrl + '", "e":"' + pixelEvent + '", "t":' + pixelType + '}'; + let bidResponse = 'FORDFORD00:00:15'; + let serverResponse = [ + {body: bidResponse} + ]; + let syncPixels = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + expect(syncPixels.length).to.equal(1); + let pixel = syncPixels[0]; + expect(pixel.url).to.equal(pixelUrl); + expect(pixel.type).to.equal('iframe'); + }); + }); +}); diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index dd624222d69..dd10a57bbfe 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; -import * as utils from 'src/utils'; -import {spec} from 'modules/aolBidAdapter'; -import {config} from 'src/config'; +import * as utils from 'src/utils.js'; +import {spec} from 'modules/aolBidAdapter.js'; +import {config} from 'src/config.js'; const DEFAULT_AD_CONTENT = ''; diff --git a/test/spec/modules/appierAnalyticsAdapter_spec.js b/test/spec/modules/appierAnalyticsAdapter_spec.js index 07b9796a4cb..cd026f64d49 100644 --- a/test/spec/modules/appierAnalyticsAdapter_spec.js +++ b/test/spec/modules/appierAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import { appierAnalyticsAdapter, getCpmInUsd, parseBidderCode, parseAdUnitCode, ANALYTICS_VERSION, BIDDER_STATUS -} from 'modules/appierAnalyticsAdapter'; +} from 'modules/appierAnalyticsAdapter.js'; import {expect} from 'chai'; const events = require('src/events'); const constants = require('src/constants.json'); @@ -15,8 +15,6 @@ const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e'; describe('Appier Prebid AnalyticsAdapter Testing', function () { describe('event tracking and message cache manager', function () { - let xhr; - beforeEach(function () { const configOptions = { affiliateId: affiliateId, @@ -32,12 +30,10 @@ describe('Appier Prebid AnalyticsAdapter Testing', function () { provider: 'appierAnalytics', options: configOptions }); - xhr = sinon.useFakeXMLHttpRequest(); }); afterEach(function () { appierAnalyticsAdapter.disableAnalytics(); - xhr.restore(); }); describe('#getCpmInUsd()', function() { diff --git a/test/spec/modules/appierBidAdapter_spec.js b/test/spec/modules/appierBidAdapter_spec.js index c7fc5744d1c..5b6ccf14162 100644 --- a/test/spec/modules/appierBidAdapter_spec.js +++ b/test/spec/modules/appierBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import { spec, API_SERVERS_MAP, ADAPTER_VERSION } from 'modules/appierBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { config } from 'src/config'; +import { spec, API_SERVERS_MAP, ADAPTER_VERSION } from 'modules/appierBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; describe('AppierAdapter', function () { const adapter = newBidder(spec); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 5365b0ac529..7e985045c45 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1,10 +1,10 @@ import { expect } from 'chai'; -import { spec } from 'modules/appnexusBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import * as bidderFactory from 'src/adapters/bidderFactory'; -import { auctionManager } from 'src/auctionManager'; -import { deepClone } from 'src/utils'; -import { config } from 'src/config'; +import { spec } from 'modules/appnexusBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { auctionManager } from 'src/auctionManager.js'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -100,6 +100,24 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); }); + it('should add publisher_id in request', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + publisherId: '1231234' + } + }); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].publisher_id).to.exist; + expect(payload.tags[0].publisher_id).to.deep.equal(1231234); + expect(payload.publisher_id).to.exist; + expect(payload.publisher_id).to.deep.equal(1231234); + }) + it('should add source and verison to the tag', function () { const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -164,6 +182,7 @@ describe('AppNexusAdapter', function () { const payload = JSON.parse(request.data); expect(payload.tags[0].ad_types).to.deep.equal(['video']); + expect(payload.tags[0].hb_source).to.deep.equal(1); }); it('sends bid request to ENDPOINT via POST', function () { @@ -193,6 +212,7 @@ describe('AppNexusAdapter', function () { id: 123, minduration: 100 }); + expect(payload.tags[0].hb_source).to.deep.equal(1); }); it('should add video property when adUnit includes a renderer', function () { @@ -228,12 +248,12 @@ describe('AppNexusAdapter', function () { const payload = JSON.parse(request.data); expect(payload.tags[0].video).to.deep.equal({ skippable: true, - playback_method: ['auto_play_sound_off'], + playback_method: 2, custom_renderer_present: true }); expect(payload.tags[1].video).to.deep.equal({ skippable: true, - playback_method: ['auto_play_sound_off'] + playback_method: 2 }); }); @@ -256,7 +276,7 @@ describe('AppNexusAdapter', function () { expect(payload.user).to.exist; expect(payload.user).to.deep.equal({ - externalUid: '123', + external_uid: '123', }); }); @@ -417,6 +437,44 @@ describe('AppNexusAdapter', function () { expect(payload3.tags.length).to.equal(15); }); + it('should contain hb_source value for adpod', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + const request = spec.buildRequests([bidRequest])[0]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(7); + }); + + it('should contain hb_source value for other media', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'banner', + params: { + sizes: [[300, 250], [300, 600]], + placementId: 13144370 + } + } + ); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + it('adds brand_category_exclusion to request when set', function() { let bidRequest = Object.assign({}, bidRequests[0]); sinon @@ -480,6 +538,7 @@ describe('AppNexusAdapter', function () { saleprice: {required: true}, privacy_supported: true }); + expect(payload.tags[0].hb_source).to.equal(1); }); it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { @@ -583,6 +642,7 @@ describe('AppNexusAdapter', function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.options).to.be.empty; const payload = JSON.parse(request.data); expect(payload.gdpr_consent).to.exist; @@ -678,22 +738,6 @@ describe('AppNexusAdapter', function () { }); }); - it('should populate tpids array when userId is available', function () { - const bidRequest = Object.assign({}, bidRequests[0], { - userId: { - criteortus: { - appnexus: { - userid: 'sample-userid' - } - } - } - }); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tpuids).to.deep.equal([{provider: 'criteo', user_id: 'sample-userid'}]); - }); - it('should populate schain if available', function () { const bidRequest = Object.assign({}, bidRequests[0], { schain: { @@ -737,6 +781,54 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); + + it('should set withCredentials to false if purpose 1 consent is not given', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true, + apiVersion: 2, + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.options).to.deep.equal({withCredentials: false}); + }); + + it('should populate eids array when ttd id and criteo is available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + userId: { + tdid: 'sample-userid', + criteoId: 'sample-criteo-userid' + } + }); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.eids).to.deep.include({ + source: 'adserver.org', + id: 'sample-userid', + rti_partner: 'TDID' + }); + + expect(payload.eids).to.deep.include({ + source: 'criteo.com', + id: 'sample-criteo-userid', + }); + }); }) describe('interpretResponse', function () { @@ -1032,17 +1124,29 @@ describe('AppNexusAdapter', function () { it('should add deal_priority and deal_code', function() { let responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].deal_priority = 'high'; + responseWithDeal.tags[0].ads[0].ad_type = 'video'; + responseWithDeal.tags[0].ads[0].deal_priority = 5; responseWithDeal.tags[0].ads[0].deal_code = '123'; + responseWithDeal.tags[0].ads[0].rtb.video = { + duration_ms: 1500, + player_width: 640, + player_height: 340, + }; let bidderRequest = { bids: [{ bidId: '3db3773286ee59', - adUnitCode: 'code' + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } + } }] } let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + expect(result[0].video.dealTier).to.equal(5); }); it('should add advertiser id', function() { diff --git a/test/spec/modules/apstreamBidAdapter_spec.js b/test/spec/modules/apstreamBidAdapter_spec.js new file mode 100644 index 00000000000..c6546a3bd83 --- /dev/null +++ b/test/spec/modules/apstreamBidAdapter_spec.js @@ -0,0 +1,324 @@ +// jshint esversion: 6, es3: false, node: true +import {assert, expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/apstreamBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const validBidRequests = [{ + bidId: 'bidId', + adUnitCode: '/id/site1/header-ad', + sizes: [[980, 120], [980, 180]], + mediaTypes: { + banner: { + sizes: [[980, 120], [980, 180]] + } + }, + params: { + publisherId: '1234' + } +}]; + +describe('AP Stream adapter', function() { + describe('isBidRequestValid', function() { + const bid = { + bidder: 'apstream', + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + params: { + } + }; + + it('should return true when publisherId is configured and one media type', function() { + bid.params.publisherId = '1234'; + assert(spec.isBidRequestValid(bid)) + }); + + it('should return false when publisherId is configured and two media types', function() { + bid.mediaTypes.video = {sizes: [300, 250]}; + assert.isFalse(spec.isBidRequestValid(bid)) + }); + }); + + describe('buildRequests', function() { + it('should send request with correct structure', function() { + const request = spec.buildRequests(validBidRequests, { })[0]; + + assert.equal(request.method, 'GET'); + assert.deepEqual(request.options, {withCredentials: false}); + assert.ok(request.data); + }); + + it('should send request with different endpoints', function() { + const validTwoBidRequests = [ + ...validBidRequests, + ...[{ + bidId: 'bidId2', + adUnitCode: '/id/site1/header-ad', + sizes: [[980, 980], [980, 900]], + mediaTypes: { + banner: { + sizes: [[980, 980], [980, 900]] + } + }, + params: { + publisherId: '1234', + endpoint: 'site2.com' + } + }] + ]; + + const request = spec.buildRequests(validTwoBidRequests, {}); + assert.isArray(request); + assert.lengthOf(request, 2); + assert.equal(request[1].data.bids, 'bidId2:t=b,s=980x980_980x900,c=/id/site1/header-ad'); + }); + + it('should send request with adUnit code', function() { + const adunitCodeValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + params: { + code: 'Site1_Leaderboard' + } + } + } + ]; + + const request = spec.buildRequests(adunitCodeValidBidRequests, { })[0]; + assert.equal(request.data.bids, 'bidId:t=b,s=980x120_980x180,c=Site1_Leaderboard'); + }); + + it('should send request with adUnit id', function() { + const adunitIdValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + params: { + adunitId: '12345' + } + } + } + ]; + + const request = spec.buildRequests(adunitIdValidBidRequests, { })[0]; + assert.equal(request.data.bids, 'bidId:t=b,s=980x120_980x180,u=12345'); + }); + + it('should send request with different media type', function() { + const types = { + 'audio': 'a', + 'banner': 'b', + 'native': 'n', + 'video': 'v' + } + Object.keys(types).forEach(key => { + const adunitIdValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + mediaTypes: { + [key]: { + sizes: [300, 250] + } + } + } + } + ]; + + const request = spec.buildRequests(adunitIdValidBidRequests, { })[0]; + assert.equal(request.data.bids, `bidId:t=${types[key]},s=980x120_980x180,c=/id/site1/header-ad`); + }) + }); + + describe('gdpr', function() { + let mockConfig; + + beforeEach(function () { + mockConfig = { + consentManagement: { + cmpApi: 'iab' + } + }; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send GDPR Consent data', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + assert.equal(request.iab_consent, bidderRequest.gdprConsent.consentString); + }); + }); + + describe('dsu', function() { + it('should pass DSU from local storage if set', function() { + let dsu = 'some_dsu'; + localStorage.setItem('apr_dsu', dsu); + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + + assert.equal(request.dsu, dsu); + }); + + it('should generate new DSU if nothing in local storage', function() { + localStorage.removeItem('apr_dsu'); + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + let dsu = localStorage.getItem('apr_dsu'); + + assert.isNotEmpty(dsu); + assert.equal(request.dsu, dsu); + }); + }); + }); + + describe('dsu config', function() { + let mockConfig; + beforeEach(function () { + mockConfig = { + apstream: { + noDsu: true + } + }; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should not send DSU if it is disabled in config', function() { + const request = spec.buildRequests(validBidRequests, { })[0]; + + assert.equal(request.data.dsu, ''); + }); + }); + + describe('interpretResponse', function () { + it('should return empty array if no body in response', function () { + const serverResponse = {}; + const bidRequest = {}; + + assert.isEmpty(spec.interpretResponse(serverResponse, bidRequest)); + }); + + it('should map server response', function () { + const serverResponse = { + body: [ + { + bidId: 123, + bidDetails: { + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + netRevenue: 'true', + creativeId: '1234', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360 + } + } + ] + }; + const bidRequest = {}; + + const response = spec.interpretResponse(serverResponse, bidRequest); + + const expected = { + requestId: 123, + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + creativeId: '1234', + netRevenue: 'true', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360 + }; + + assert.deepEqual(response[0], expected); + }); + + it('should add pixels to ad', function () { + const serverResponse = { + body: [ + { + bidId: 123, + bidDetails: { + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + creativeId: '1234', + netRevenue: 'true', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360, + noticeUrls: [ + 'site1', + 'site2' + ], + impressionScripts: [ + 'url_to_script' + ] + } + } + ] + }; + const bidRequest = {}; + + const response = spec.interpretResponse(serverResponse, bidRequest); + + assert.match(response[0].ad, /site1/); + assert.match(response[0].ad, /site2/); + }); + }); +}); diff --git a/test/spec/modules/astraoneBidAdapter_spec.js b/test/spec/modules/astraoneBidAdapter_spec.js index cd80e742b7d..e422f64b570 100644 --- a/test/spec/modules/astraoneBidAdapter_spec.js +++ b/test/spec/modules/astraoneBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { spec } from 'modules/astraoneBidAdapter' +import { spec } from 'modules/astraoneBidAdapter.js' function getSlotConfigs(mediaTypes, params) { return { diff --git a/test/spec/modules/atomxBidAdapter_spec.js b/test/spec/modules/atomxBidAdapter_spec.js index 782619d04db..d798bd6308c 100644 --- a/test/spec/modules/atomxBidAdapter_spec.js +++ b/test/spec/modules/atomxBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/atomxBidAdapter'; +import { spec } from 'modules/atomxBidAdapter.js'; describe('atomxAdapterTest', function () { describe('bidRequestValidity', function () { diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..849ccf88c51 --- /dev/null +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -0,0 +1,208 @@ +import atsAnalyticsAdapter from '../../../modules/atsAnalyticsAdapter.js'; +import { expect } from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import {server} from '../../mocks/xhr.js'; +import {checkUserBrowser, browserIsChrome, browserIsEdge, browserIsSafari, browserIsFirefox} from '../../../modules/atsAnalyticsAdapter.js'; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('ats analytics adapter', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + atsAnalyticsAdapter.disableAnalytics(); + }); + + describe('track', function () { + it('builds and sends request and response data', function () { + sinon.spy(atsAnalyticsAdapter, 'track'); + + let initOptions = { + pid: '10433394', + host: 'https://example.com/dev', + }; + let auctionTimestamp = 1496510254326; + + // prepare general auction - request + let bidRequest = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394' + }, + 'userId': { + 'idl_env': 'AmThEbO1ssIWjrNdU4noT4ZFBILSVBBYHbipOYt_JP40e5nZdXns2g' + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', + 'sizes': '300x250,300x600', + 'bidId': '30c77d079cdf17' + }], + 'refererInfo': { + 'referer': 'https://example.com/dev' + }, + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + }; + // prepare general auction - response + let bidResponse = { + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '2eddfdc0c791dc', + 'mediaType': 'banner', + 'source': 'client', + 'requestId': '30c77d079cdf17', + 'cpm': 0.5, + 'creativeId': 29681110, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'responseTimestamp': 1580739791978, + 'requestTimestamp': 1580739265164, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'timeToRespond': 2510, + 'size': '300x250' + }; + + // what we expect after general auction + let expectedAfterBid = { + 'Data': [{ + 'has_envelope': true, + 'bidder': 'appnexus', + 'bid_id': '30c77d079cdf17', + 'auction_id': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'user_browser': checkUserBrowser(), + 'user_platform': navigator.platform, + 'auction_start': '2020-02-03T14:14:25.161Z', + 'domain': window.location.hostname, + 'pid': '10433394', + 'response_time_stamp': '2020-02-03T14:23:11.978Z', + 'currency': 'USD', + 'cpm': 0.5, + 'net_revenue': true + }] + }; + + // lets simulate that some bidders timeout + 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' + } + ]; + + adapterManager.registerAnalyticsAdapter({ + code: 'atsAnalytics', + adapter: atsAnalyticsAdapter + }); + + adapterManager.enableAnalytics({ + provider: 'atsAnalytics', + options: initOptions + }); + + // 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, {}); + + let requests = server.requests.filter(req => { + return req.url.indexOf(initOptions.host) > -1; + }); + + expect(requests.length).to.equal(1); + + let realAfterBid = JSON.parse(requests[0].requestBody); + + // Step 6: assert real data after bid and expected data + expect(realAfterBid['Data']).to.deep.equal(expectedAfterBid['Data']); + + // check that the host and publisher ID is configured via options + expect(atsAnalyticsAdapter.context.host).to.equal(initOptions.host); + expect(atsAnalyticsAdapter.context.pid).to.equal(initOptions.pid); + }) + it('check browser is not safari', function () { + window.safari = undefined; + let browser = browserIsSafari(); + expect(browser).to.equal(false); + }) + it('check browser is safari', function () { + window.safari = {}; + let browser = browserIsSafari(); + expect(browser).to.equal('Safari'); + }) + it('check browser is not chrome', function () { + window.chrome = { + app: undefined, + webstore: undefined, + runtime: undefined + }; + let browser = browserIsChrome(); + expect(browser).to.equal(false); + }) + it('check browser is chrome', function () { + window.chrome = { + app: {}, + webstore: {}, + runtime: {} + }; + let browser = browserIsChrome(); + expect(browser).to.equal('Chrome'); + }) + it('check browser is edge', function () { + Object.defineProperty(window, 'StyleMedia', { + value: {}, + writable: true + }); + Object.defineProperty(document, 'documentMode', { + value: undefined, + writable: true + }); + let browser = browserIsEdge(); + expect(browser).to.equal('Edge'); + }) + it('check browser is not edge', function () { + Object.defineProperty(document, 'documentMode', { + value: {}, + writable: true + }); + let browser = browserIsEdge(); + expect(browser).to.equal(false); + }) + it('check browser is firefox', function () { + global.InstallTrigger = {}; + let browser = browserIsFirefox(); + expect(browser).to.equal('Firefox'); + }) + it('check browser is not firefox', function () { + global.InstallTrigger = undefined; + let browser = browserIsFirefox(); + expect(browser).to.equal(false); + }) + }) +}) diff --git a/test/spec/modules/audiencerunBidAdapter_spec.js b/test/spec/modules/audiencerunBidAdapter_spec.js new file mode 100644 index 00000000000..826944abaf5 --- /dev/null +++ b/test/spec/modules/audiencerunBidAdapter_spec.js @@ -0,0 +1,204 @@ +import { expect } from 'chai'; +import { spec } from 'modules/audiencerunBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const ENDPOINT = 'https://d.audiencerun.com/prebid'; + +const BID_SERVER_RESPONSE = { + body: { + bid: [ + { + 'bidId': '51ef8751f9aead', + 'zoneId': '12345abcde', + 'adId': '1234', + 'crid': '5678', + 'cpm': 8.021951999999999999, + 'currency': 'USD', + 'w': 728, + 'h': 90, + 'isNet': false, + 'buying_type': 'rtb', + 'syncUrl': 'https://ac.audiencerun.com/f/sync.html', + 'adm': '' + } + ] + } +}; + +describe('AudienceRun bid adapter tests', function() { + const adapter = newBidder(spec); + + describe('inherited functions', function() { + it('exists and is a function', function() { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'audiencerun', + 'params': { + 'zoneId': '12345abcde' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee' + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when zoneId is valid', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'zoneId': '12345abcde' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function() { + let bid = Object.assign({}, bid); + delete bid.params; + + bid.params = {}; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + { + 'bidder': 'audiencerun', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': '12345abcde' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[320, 50], [300, 250], [300, 600]] + } + }, + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1 + } + ]; + + it('sends a valid bid request to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + canonicalUrl: 'https://example.com/canonical', + referer: 'https://example.com' + } + }); + + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.exist; + + expect(payload.bids).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(payload.referer).to.exist; + + const bid = payload.bids[0]; + expect(bid).to.exist; + expect(bid).to.have.property('bidId'); + expect(bid).to.have.property('zoneId'); + expect(bid).to.have.property('sizes'); + expect(bid.sizes[0].w).to.be.a('number'); + expect(bid.sizes[0].h).to.be.a('number'); + }); + + it('should send GDPR to endpoint and honor gdprApplies value', function() { + let consentString = 'bogusConsent'; + let bidderRequest = { + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consent).to.equal(consentString); + expect(payload.gdpr.applies).to.equal(true); + + let bidderRequest2 = { + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': false + } + }; + + const request2 = spec.buildRequests(bidRequests, bidderRequest2); + const payload2 = JSON.parse(request2.data); + + expect(payload2.gdpr).to.exist; + expect(payload2.gdpr.consent).to.equal(consentString); + expect(payload2.gdpr.applies).to.equal(false); + }); + }); + + describe('interpretResponse', function () { + const expectedResponse = [{ + 'requestId': '51ef8751f9aead', + 'adId': '12345abcde', + 'cpm': 8.021951999999999999, + 'width': '728', + 'height': '90', + 'creativeId': '5678', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 300, + 'ad': '', + 'mediaType': 'banner' + }]; + + it('should get the correct bid response by display ad', function () { + let result = spec.interpretResponse(BID_SERVER_RESPONSE); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', function () { + const response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + const serverResponses = [ BID_SERVER_RESPONSE ]; + const syncOptions = { iframeEnabled: true }; + + it('should return empty if no server responses', function() { + const syncs = spec.getUserSyncs(syncOptions, []); + expect(syncs).to.deep.equal([]) + }); + + it('should return user syncs', function () { + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://ac.audiencerun.com/f/sync.html'}]) + }); + }); +}); diff --git a/test/spec/modules/automatadBidAdapter_spec.js b/test/spec/modules/automatadBidAdapter_spec.js new file mode 100644 index 00000000000..a6de8810284 --- /dev/null +++ b/test/spec/modules/automatadBidAdapter_spec.js @@ -0,0 +1,149 @@ +import { expect } from 'chai' +import { spec } from 'modules/automatadBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' + +describe('automatadBidAdapter', function () { + const adapter = newBidder(spec) + + let bidRequest = { + bidder: 'automatad', + params: {siteId: '123ad', placementId: '123abc345'}, + mediaTypes: { + banner: { + sizes: [[300, 600]], + } + }, + adUnitCode: 'some-ad-unit-code', + transactionId: '1465569e-52cc-4c36-88a1-7174cfef4b44', + sizes: [[300, 600]], + bidId: '123abc', + bidderRequestId: '3213887463c059', + auctionId: 'abc-123', + src: 'client', + bidRequestsCount: 1 + } + + let expectedResponse = [{ + 'body': { + 'id': 'abc-123', + 'seatbid': [ + { + 'bid': [ + { + 'adm': '', + 'adomain': [ + 'someAdDomain' + ], + 'crid': 123, + 'h': 600, + 'id': 'bid1', + 'impid': '1', + 'nurl': 'https://example/win', + 'price': 0.5, + 'w': 300 + } + ] + } + ] + } + }] + + describe('codes', function () { + it('should return a bidder code of automatad', function () { + expect(spec.code).to.equal('automatad') + }) + it('should alias atd', function () { + expect(spec.aliases.length > 0 && spec.aliases[0] === 'atd').to.be.true + }) + }) + + describe('isBidRequestValid', function () { + let inValidBid = Object.assign({}, bidRequest) + delete inValidBid.params + it('should return true if all params present', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true) + }) + + it('should return false if any parameter missing', function () { + expect(spec.isBidRequestValid(inValidBid)).to.be.false + }) + }) + + describe('buildRequests', function () { + let req = spec.buildRequests([ bidRequest ], { refererInfo: { } }) + let rdata + + it('should return request object', function () { + expect(req).to.not.be.null + }) + + it('should build request data', function () { + expect(req.data).to.not.be.null + }) + + it('should include one request', function () { + rdata = JSON.parse(req.data) + expect(rdata.imp.length).to.equal(1) + }) + + it('should include media types', function () { + let r = rdata.imp[0] + expect(r.media_types !== null).to.be.true + }) + + it('should include all publisher params', function () { + let r = rdata.imp[0] + expect(r.siteID !== null && r.placementID !== null).to.be.true + }) + + it('should include adunit code', function () { + let r = rdata.imp[0] + expect(r.adUnitCode !== null).to.be.true + }) + }) + + describe('interpretResponse', function () { + it('should get the correct bid response', function () { + let result = spec.interpretResponse(expectedResponse[0]) + expect(result).to.be.an('array').that.is.not.empty + }) + + it('handles empty bid response', function () { + let response = { + body: '' + } + let result = spec.interpretResponse(response) + expect(result.length).to.equal(0) + }) + }) + + describe('getUserSyncs', function () { + it('should return iframe sync', function () { + let sync = spec.getUserSyncs() + expect(sync.length).to.equal(1) + expect(sync[0].type === 'iframe') + expect(typeof sync[0].url === 'string') + }) + }) + + describe('onBidWon', function () { + let serverResponses = spec.interpretResponse(expectedResponse[0]) + let wonbid = serverResponses[0] + let ajaxStub + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'ajaxCall') + }) + + afterEach(() => { + ajaxStub.restore() + }) + + it('Returns true is nurl is good/not blank', function () { + expect(wonbid.nurl).to.not.equal('') + expect(spec.onBidWon(wonbid)).to.be.true + expect(ajaxStub.calledOnce).to.equal(true) + expect(ajaxStub.firstCall.args[0].indexOf('https://')).to.equal(0) + }) + }) +}) diff --git a/test/spec/modules/avocetBidAdapter_spec.js b/test/spec/modules/avocetBidAdapter_spec.js new file mode 100644 index 00000000000..4cfd8ab89d4 --- /dev/null +++ b/test/spec/modules/avocetBidAdapter_spec.js @@ -0,0 +1,165 @@ +import { expect } from 'chai'; +import { spec } from 'modules/avocetBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; + +describe('Avocet adapter', function () { + beforeEach(function () { + config.setConfig({ + currency: { + adServerCurrency: 'USD', + }, + publisherDomain: 'test.com', + fpd: { + some: 'data', + }, + }); + }); + + afterEach(function () { + config.resetConfig(); + }); + + describe('inherited functions', function () { + it('exists and is a function', function () { + const adapter = newBidder(spec); + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return false for bid request missing params', () => { + const invalidBidRequest = { + bid: {}, + }; + expect(spec.isBidRequestValid(invalidBidRequest)).to.equal(false); + }); + it('should return false for an invalid type placement param', () => { + const invalidBidRequest = { + params: { + placement: 123, + }, + }; + expect(spec.isBidRequestValid(invalidBidRequest)).to.equal(false); + }); + it('should return false for an invalid length placement param', () => { + const invalidBidRequest = { + params: { + placement: '123', + }, + }; + expect(spec.isBidRequestValid(invalidBidRequest)).to.equal(false); + }); + it('should return true for a valid length placement param', () => { + const validBidRequest = { + params: { + placement: '012345678901234567890123', + }, + }; + expect(spec.isBidRequestValid(validBidRequest)).to.equal(true); + }); + }); + describe('buildRequests', function () { + it('constructs a valid POST request', function () { + const request = spec.buildRequests( + [ + { + bidder: 'avct', + params: { + placement: '012345678901234567890123', + }, + userId: { + id5id: 'test' + } + }, + { + bidder: 'avct', + params: { + placement: '012345678901234567890123', + }, + }, + ], + exampleBidderRequest + ); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://ads.avct.cloud/prebid'); + + const requestData = JSON.parse(request.data); + expect(requestData.ext).to.be.an('object'); + expect(requestData.ext.currency).to.equal('USD'); + expect(requestData.ext.publisherDomain).to.equal('test.com'); + expect(requestData.ext.fpd).to.deep.equal({ some: 'data' }); + expect(requestData.ext.schain).to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1, + }, + ], + }, + }); + expect(requestData.ext.id5id).to.equal('test'); + expect(requestData.bids).to.be.an('array'); + expect(requestData.bids.length).to.equal(2); + }); + }); + describe('interpretResponse', function () { + it('no response', function () { + const response = spec.interpretResponse(); + expect(response).to.be.an('array'); + expect(response.length).to.equal(0); + }); + it('no body', function () { + const response = spec.interpretResponse({}); + expect(response).to.be.an('array'); + expect(response.length).to.equal(0); + }); + it('null body', function () { + const response = spec.interpretResponse({ body: null }); + expect(response).to.be.an('array'); + expect(response.length).to.equal(0); + }); + it('empty body', function () { + const response = spec.interpretResponse({ body: {} }); + expect(response).to.be.an('array'); + expect(response.length).to.equal(0); + }); + it('null body.responses', function () { + const response = spec.interpretResponse({ body: { responses: null } }); + expect(response).to.be.an('array'); + expect(response.length).to.equal(0); + }); + it('array body', function () { + const response = spec.interpretResponse({ body: [{}] }); + expect(response).to.be.an('array'); + expect(response.length).to.equal(1); + }); + it('array body.responses', function () { + const response = spec.interpretResponse({ body: { responses: [{}] } }); + expect(response).to.be.an('array'); + expect(response.length).to.equal(1); + }); + }); +}); + +const exampleBidderRequest = { + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1, + }, + ], + }, + }, +}; diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 155da4fb309..5711e111e30 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; -import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT, OUTSTREAM_SRC, DEFAULT_MIMES } from 'modules/beachfrontBidAdapter'; -import { parse as parseUrl } from 'src/url'; +import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT, OUTSTREAM_SRC, DEFAULT_MIMES } from 'modules/beachfrontBidAdapter.js'; +import { parseUrl } from 'src/utils.js'; describe('BeachfrontAdapter', function () { let bidRequests; @@ -146,6 +146,7 @@ describe('BeachfrontAdapter', function () { const width = 640; const height = 480; const bidRequest = bidRequests[0]; + bidRequest.params.tagid = '7cd7a7b4-ef3f-4aeb-9565-3627f255fa10'; bidRequest.mediaTypes = { video: { playerSize: [ width, height ] @@ -165,6 +166,7 @@ describe('BeachfrontAdapter', function () { 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.imp[0].tagid).to.equal(bidRequest.params.tagid); 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']); @@ -234,6 +236,16 @@ describe('BeachfrontAdapter', function () { expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement }); }); + it('must add US privacy data to the request', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const uspConsent = '2112YYZ'; + const bidderRequest = { uspConsent }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.regs.ext.us_privacy).to.equal(uspConsent); + }); + it('must add GDPR consent data to the request', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; @@ -372,6 +384,16 @@ describe('BeachfrontAdapter', function () { expect(data.slots[0].sizes).to.deep.contain({ w: width, h: height }); }); + it('must add US privacy data to the request', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const uspConsent = '2112YYZ'; + const bidderRequest = { uspConsent }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.usPrivacy).to.equal(uspConsent); + }); + it('must add GDPR consent data to the request', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; diff --git a/test/spec/modules/betweenBidAdapter_spec.js b/test/spec/modules/betweenBidAdapter_spec.js index f2d770805c5..94db41ba014 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/betweenBidAdapter'; +import { spec } from 'modules/betweenBidAdapter.js'; describe('betweenBidAdapterTests', function () { it('validate_pub_params', function () { @@ -23,6 +23,122 @@ describe('betweenBidAdapterTests', function () { let req_data = request[0].data; expect(req_data.bidid).to.equal('bid1234'); }); + it('validate itu param', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112, + itu: 'https://something.url' + }, + sizes: [[240, 400]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + expect(req_data.itu).to.equal('https://something.url'); + }); + it('validate cur param', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112, + cur: 'THX' + }, + sizes: [[240, 400]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + expect(req_data.cur).to.equal('THX'); + }); + it('validate subid param', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112, + subid: 1138, + }, + sizes: [[240, 400]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + expect(req_data.subid).to.equal(1138); + }); + it('validate click3rd param', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112, + click3rd: 'https://something.url', + }, + sizes: [[240, 400]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + expect(req_data.click3rd).to.equal('https://something.url'); + }); + it('validate pubdata param', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112, + pubdata: { + param: '&test=tset' + }, + }, + sizes: [[240, 400]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + expect(req_data['pubside_macro[param]']).to.equal('%26test%3Dtset'); + }); + it('validate gdprConsent', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112, + }, + sizes: [[240, 400]] + }]; + let bidderRequest = { + gdprConsent: { + consentString: 'BOtGbjbOtGbjbBQABBENC3-AAAAtR7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Ew_v-_v-b7JCON_IA', + gdprApplies: true + } + } + + let request = spec.buildRequests(bidRequestData, bidderRequest); + let req_data = request[0].data; + + expect(req_data.gdprApplies).to.equal(bidderRequest.gdprConsent.gdprApplies); + expect(req_data.consentString).to.equal(bidderRequest.gdprConsent.consentString); + }); it('validate_response_params', function () { let serverResponse = { body: [{ @@ -66,4 +182,43 @@ describe('betweenBidAdapterTests', function () { expect(bid.requestId).to.equal('bid1234'); expect(bid.ad).to.equal('Ad html'); }); + it('validate response params without currency', function () { + let serverResponse = { + body: [{ + bidid: 'bid1234', + w: 240, + h: 400, + ad: 'Ad html' + }] + }; + let bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.currency).to.equal('RUB'); + }); + it('check getUserSyncs', function() { + const syncs = spec.getUserSyncs({}, {}); + expect(syncs).to.be.an('array').that.to.have.lengthOf(1); + expect(syncs[0]).to.deep.equal({type: 'iframe', url: 'https://ads.betweendigital.com/sspmatch-iframe'}); + }); + + it('check sizes', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + mediaTypes: { + banner: { + sizes: [[970, 250], [240, 400], [728, 90]] + } + }, + params: { + s: 1112, + }, + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + expect(req_data.sizes).to.deep.equal('970x250%2C240x400%2C728x90'); + }); }); diff --git a/test/spec/modules/bidfluenceBidAdapter_spec.js b/test/spec/modules/bidfluenceBidAdapter_spec.js index 9ce6e808c6b..6b3a0c2b044 100644 --- a/test/spec/modules/bidfluenceBidAdapter_spec.js +++ b/test/spec/modules/bidfluenceBidAdapter_spec.js @@ -1,114 +1,114 @@ -import { expect } from 'chai'; -import { spec } from 'modules/bidfluenceBidAdapter'; - -const BIDDER_CODE = 'bidfluence'; -const PLACEMENT_ID = '1000'; -const PUB_ID = '1000'; -const CONSENT_STRING = 'DUXDSDFSFWRRR8345F=='; - -const validBidRequests = [{ - 'bidder': BIDDER_CODE, - 'params': { - 'placementId': PLACEMENT_ID, - 'publisherId': PUB_ID, - 'reservePrice': 0 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], - 'bidId': '2b1f23307fb8ef', - 'bidderRequestId': '10edf38ec1a719', - 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304' -}]; - -const bidderRequest = { - 'bidderCode': 'bidfluence', - 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', - 'bidderRequestId': '10edf38ec1a719', - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'test', - 'stack': ['test'] - }, - 'timeout': 1000, - 'gdprConsent': { - 'gdprApplies': true, - 'consentString': CONSENT_STRING, - 'vendorData': '' - } -}; - -bidderRequest.bids = validBidRequests; - -describe('Bidfluence Adapter test', () => { - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(validBidRequests[0])).to.equal(true); - }); - it('should return the right bidder code', function () { - expect(spec.code).to.eql(BIDDER_CODE); - }); - }); - - describe('buildRequests', function () { - const request = spec.buildRequests(validBidRequests, bidderRequest); - - it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); - - const payload = JSON.parse(request.data); - - expect(payload.bids[0].bid).to.equal(validBidRequests[0].bidId); - expect(payload.azr).to.equal(true); - expect(payload.ck).to.not.be.undefined; - expect(payload.bids[0].tid).to.equal(PLACEMENT_ID); - expect(payload.bids[0].pid).to.equal(PUB_ID); - expect(payload.bids[0].rp).to.be.a('number'); - expect(payload.re).to.not.be.undefined; - expect(payload.st).to.not.be.undefined; - expect(payload.tz).to.not.be.undefined; - expect(payload.sr).to.not.be.undefined; - expect(payload.vp).to.not.be.undefined; - expect(payload.sdt).to.not.be.undefined; - expect(payload.bids[0].w).to.equal('300'); - expect(payload.bids[0].h).to.equal('250'); - - it('sends gdpr info if exists', function () { - expect(payload.gdpr).to.equal(true); - expect(payload.gdprc).to.equal(CONSENT_STRING); - }); - }); - - describe('interpretResponse', function () { - const response = { - body: { - Bids: - [{ - 'CreativeId': '1000', - 'Cpm': 0.50, - 'Ad': '
', - 'Height': 250, - 'Width': 300 - }] - } - }; - - it('should get correct bid response', function () { - const expectedResponse = [{ - requestId: response.body.Bids[0].BidId, - cpm: response.body.Bids[0].Cpm, - width: response.body.Bids[0].Width, - height: response.body.Bids[0].Height, - creativeId: response.body.Bids[0].CreativeId, - ad: response.body.Bids[0].Ad, - currency: 'USD', - netRevenue: true, - ttl: 360 - }]; - - let result = spec.interpretResponse(response, { 'bidderRequest': validBidRequests[0] }); - expect(result).to.deep.equal(expectedResponse); - }); - }); -}); +import { expect } from 'chai'; +import { spec } from 'modules/bidfluenceBidAdapter.js'; + +const BIDDER_CODE = 'bidfluence'; +const PLACEMENT_ID = '1000'; +const PUB_ID = '1000'; +const CONSENT_STRING = 'DUXDSDFSFWRRR8345F=='; + +const validBidRequests = [{ + 'bidder': BIDDER_CODE, + 'params': { + 'placementId': PLACEMENT_ID, + 'publisherId': PUB_ID, + 'reservePrice': 0 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '2b1f23307fb8ef', + 'bidderRequestId': '10edf38ec1a719', + 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304' +}]; + +const bidderRequest = { + 'bidderCode': 'bidfluence', + 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', + 'bidderRequestId': '10edf38ec1a719', + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'test', + 'stack': ['test'] + }, + 'timeout': 1000, + 'gdprConsent': { + 'gdprApplies': true, + 'consentString': CONSENT_STRING, + 'vendorData': '' + } +}; + +bidderRequest.bids = validBidRequests; + +describe('Bidfluence Adapter test', () => { + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBidRequests[0])).to.equal(true); + }); + it('should return the right bidder code', function () { + expect(spec.code).to.eql(BIDDER_CODE); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.method).to.equal('POST'); + const payload = JSON.parse(request.data); + + expect(payload.bids[0].bid).to.equal(validBidRequests[0].bidId); + expect(payload.azr).to.equal(true); + expect(payload.ck).to.not.be.undefined; + expect(payload.bids[0].tid).to.equal(PLACEMENT_ID); + expect(payload.bids[0].pid).to.equal(PUB_ID); + expect(payload.bids[0].rp).to.be.a('number'); + expect(payload.re).to.not.be.undefined; + expect(payload.st).to.not.be.undefined; + expect(payload.tz).to.not.be.undefined; + expect(payload.sr).to.not.be.undefined; + expect(payload.vp).to.not.be.undefined; + expect(payload.sdt).to.not.be.undefined; + expect(payload.bids[0].w).to.equal('300'); + expect(payload.bids[0].h).to.equal('250'); + }); + + it('sends gdpr info if exists', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.equal(true); + expect(payload.gdprc).to.equal(CONSENT_STRING); + }); + }); + + describe('interpretResponse', function () { + const response = { + body: { + Bids: + [{ + 'CreativeId': '1000', + 'Cpm': 0.50, + 'Ad': '
', + 'Height': 250, + 'Width': 300 + }] + } + }; + + it('should get correct bid response', function () { + const expectedResponse = [{ + requestId: response.body.Bids[0].BidId, + cpm: response.body.Bids[0].Cpm, + width: response.body.Bids[0].Width, + height: response.body.Bids[0].Height, + creativeId: response.body.Bids[0].CreativeId, + ad: response.body.Bids[0].Ad, + currency: 'USD', + netRevenue: true, + ttl: 360 + }]; + + let result = spec.interpretResponse(response, { 'bidderRequest': validBidRequests[0] }); + expect(result).to.deep.equal(expectedResponse); + }); + }); +}); diff --git a/test/spec/modules/bidglassAdapter_spec.js b/test/spec/modules/bidglassAdapter_spec.js index 00a47fc997a..d153430103d 100644 --- a/test/spec/modules/bidglassAdapter_spec.js +++ b/test/spec/modules/bidglassAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/bidglassBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/bidglassBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('Bid Glass Adapter', function () { const adapter = newBidder(spec); diff --git a/test/spec/modules/bidlabBidAdapter_spec.js b/test/spec/modules/bidlabBidAdapter_spec.js new file mode 100644 index 00000000000..cffd43ae6ca --- /dev/null +++ b/test/spec/modules/bidlabBidAdapter_spec.js @@ -0,0 +1,235 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/bidlabBidAdapter.js'; + +describe('BidlabBidAdapter', function () { + let bid = { + bidId: '23fhj33i987f', + bidder: 'bidlab', + params: { + placementId: 0, + traffic: 'banner' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://service.bidlab.ai/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes'); + 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', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + if (spec.noSync) { + expect(userSync).to.be.equal(false); + } else { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://service.bidlab.ai/?c=o&m=sync'); + } + }); + }); +}); diff --git a/test/spec/modules/bidphysicsBidAdapter_spec.js b/test/spec/modules/bidphysicsBidAdapter_spec.js index 8c46b8cefa6..fc15c39cf81 100644 --- a/test/spec/modules/bidphysicsBidAdapter_spec.js +++ b/test/spec/modules/bidphysicsBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/bidphysicsBidAdapter'; +import {spec} from 'modules/bidphysicsBidAdapter.js'; const REQUEST = { 'bidderCode': 'bidphysics', diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js new file mode 100644 index 00000000000..73bd4803358 --- /dev/null +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -0,0 +1,916 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bluebillywigBidAdapter.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { auctionManager } from 'src/auctionManager.js'; +import { deepClone, deepAccess } from 'src/utils.js'; +import { config } from 'src/config.js'; +import { VIDEO } from 'src/mediaTypes.js'; + +const BB_CONSTANTS = { + BIDDER_CODE: 'bluebillywig', + AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION', + SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION', + RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js', + DEFAULT_TIMEOUT: 5000, + DEFAULT_TTL: 300, + DEFAULT_WIDTH: 768, + DEFAULT_HEIGHT: 432, + DEFAULT_NET_REVENUE: true +}; + +describe('BlueBillywigAdapter', () => { + describe('isBidRequestValid', () => { + const baseValidBid = { + bidder: BB_CONSTANTS.BIDDER_CODE, + params: { + accountId: 123, + publicationName: 'bbprebid.dev', + rendererCode: 'glorious_renderer', + connections: [ BB_CONSTANTS.BIDDER_CODE ], + bluebillywig: {} + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(baseValidBid)).to.equal(true); + }); + + it('should return false when publicationName is missing', () => { + const bid = deepClone(baseValidBid); + delete bid.params.publicationName; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publicationName is not a string', () => { + const bid = deepClone(baseValidBid); + + bid.params.publicationName = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.publicationName = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.publicationName = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.publicationName = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publicationName is formatted poorly', () => { + const bid = deepClone(baseValidBid); + + bid.params.publicationName = 'bb.'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.publicationName = 'bb-test'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.publicationName = '?'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when renderer is not specified', () => { + const bid = deepClone(baseValidBid); + + delete bid.params.rendererCode; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when renderer is not a string', () => { + const bid = deepClone(baseValidBid); + + bid.params.rendererCode = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererCode = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererCode = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererCode = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when renderer is formatted poorly', () => { + const bid = deepClone(baseValidBid); + + bid.params.rendererCode = 'bb.'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererCode = 'bb-test'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererCode = '?'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when accountId is not specified', () => { + const bid = deepClone(baseValidBid); + + delete bid.params.accountId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when connections is not specified', () => { + const bid = deepClone(baseValidBid); + + delete bid.params.connections; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when connections is not an array', () => { + const bid = deepClone(baseValidBid); + + bid.params.connections = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.connections = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.connections = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.connections = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.connections = 'string'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when a connection is missing', () => { + const bid = deepClone(baseValidBid); + + bid.params.connections.push('potatoes'); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.connections.pop(); + + delete bid.params[BB_CONSTANTS.BIDDER_CODE]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail if bid has no mediaTypes', () => { + const bid = deepClone(baseValidBid); + + delete bid.mediaTypes; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail if bid has no mediaTypes.video', () => { + const bid = deepClone(baseValidBid); + + delete bid.mediaTypes[VIDEO]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail if bid has no mediaTypes.video.context', () => { + const bid = deepClone(baseValidBid); + + delete bid.mediaTypes[VIDEO].context; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail if mediaTypes.video.context is not "outstream"', () => { + const bid = deepClone(baseValidBid); + + bid.mediaTypes[VIDEO].context = 'instream'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const publicationName = 'bbprebid.dev'; + const rendererCode = 'glorious_renderer'; + + const baseValidBid = { + bidder: BB_CONSTANTS.BIDDER_CODE, + params: { + accountId: 123, + publicationName: publicationName, + rendererCode: rendererCode, + connections: [ BB_CONSTANTS.BIDDER_CODE ], + bluebillywig: {} + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }; + + const baseValidBidRequests = [baseValidBid]; + + const validBidderRequest = { + auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', + auctionStart: 1585918458868, + bidderCode: BB_CONSTANTS.BIDDER_CODE, + bidderRequestId: '1a2345b67c8d9e0', + bids: [{ + adUnitCode: 'ad-unit-test', + auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', + bidId: '1234ab567c89de0', + bidRequestsCount: 1, + bidder: BB_CONSTANTS.BIDDER_CODE, + bidderRequestId: '1a2345b67c8d9e0', + params: baseValidBid.params, + sizes: [[768, 432], [640, 480], [630, 360]], + transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' + }], + start: 11585918458869, + timeout: 3000 + }; + + it('sends bid request to AUCTION_URL via POST', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + expect(request.url).to.equal(`https://pbs.bluebillywig.com/openrtb2/auction?pub=${publicationName}`); + expect(request.method).to.equal('POST'); + }); + + it('sends data as a string', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + expect(request.data).to.be.a('string'); + }); + + it('sends all bid parameters', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('builds the base request properly', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.id).to.equal(validBidderRequest.auctionId); + expect(payload.source).to.be.an('object'); + expect(payload.source.tid).to.equal(validBidderRequest.auctionId); + expect(payload.tmax).to.equal(BB_CONSTANTS.DEFAULT_TIMEOUT); + expect(payload.imp).to.be.an('array'); + expect(payload.test).to.be.a('number'); + expect(payload).to.have.nested.property('ext.prebid.targeting'); + expect(payload.ext.prebid.targeting).to.be.an('object'); + expect(payload.ext.prebid.targeting.includewinners).to.equal(true); + expect(payload.ext.prebid.targeting.includebidderkeys).to.equal(false); + }); + + it('adds an impression to the payload', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.imp.length).to.equal(1); + }); + + it('adds connections to ext', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].ext).to.have.all.keys(['bluebillywig']); + }); + + it('adds gdpr when present', () => { + const newValidBidderRequest = deepClone(validBidderRequest); + newValidBidderRequest.gdprConsent = { + consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + gdprApplies: true + }; + + const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('regs.ext.gdpr'); + expect(payload.regs.ext.gdpr).to.be.a('number'); + expect(payload.regs.ext.gdpr).to.equal(1); + expect(payload).to.have.nested.property('user.ext.consent'); + expect(payload.user.ext.consent).to.equal(newValidBidderRequest.gdprConsent.consentString); + }); + + it('sets gdpr to 0 when explicitly gdprApplies: false', () => { + const newValidBidderRequest = deepClone(validBidderRequest); + newValidBidderRequest.gdprConsent = { + gdprApplies: false + }; + + const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('regs.ext.gdpr'); + expect(payload.regs.ext.gdpr).to.be.a('number'); + expect(payload.regs.ext.gdpr).to.equal(0); + }); + + it('adds usp_consent when present', () => { + const newValidBidderRequest = deepClone(validBidderRequest); + newValidBidderRequest.uspConsent = '1YYY'; + + const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('regs.ext.us_privacy'); + expect(payload.regs.ext.us_privacy).to.equal(newValidBidderRequest.uspConsent); + }); + + it('sets coppa to 1 when specified in config', () => { + config.setConfig({'coppa': true}); + + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('regs.coppa'); + expect(payload.regs.coppa).to.equal(1); + + config.resetConfig(); + }); + + it('does not set coppa when disabled in the config', () => { + config.setConfig({'coppa': false}); + + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(deepAccess(payload, 'regs.coppa')).to.be.undefined; + + config.resetConfig(); + }); + + it('does not set coppa when not specified in config', () => { + config.resetConfig(); + + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(deepAccess(payload, 'regs.coppa')).to.be.undefined; + }); + + it('should add window size to request by default', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('device.w'); + expect(payload).to.have.nested.property('device.h'); + expect(payload.device.w).to.be.a('number'); + expect(payload.device.h).to.be.a('number'); + }); + + it('should add site when specified in config', () => { + config.setConfig({ site: { name: 'Blue Billywig', domain: 'bluebillywig.com', page: 'https://bluebillywig.com/', publisher: { id: 'abc', name: 'Blue Billywig', domain: 'bluebillywig.com' } } }); + + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.property('site'); + expect(payload).to.have.nested.property('site.name'); + expect(payload).to.have.nested.property('site.domain'); + expect(payload).to.have.nested.property('site.page'); + expect(payload).to.have.nested.property('site.publisher'); + expect(payload).to.have.nested.property('site.publisher.id'); + expect(payload).to.have.nested.property('site.publisher.name'); + expect(payload).to.have.nested.property('site.publisher.domain'); + + config.resetConfig(); + }); + + it('should add app when specified in config', () => { + config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } }); + + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.property('app'); + expect(payload).to.have.nested.property('app.bundle'); + expect(payload).to.have.nested.property('app.domain'); + expect(payload.app.bundle).to.equal('org.prebid.mobile.demoapp'); + expect(payload.app.domain).to.equal('prebid.org'); + + config.resetConfig(); + }); + + it('should add referrerInfo as site when no app is set', () => { + const newValidBidderRequest = deepClone(validBidderRequest); + + newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' }; + + const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('site.page'); + expect(payload.site.page).to.equal('https://www.bluebillywig.com'); + }); + + it('should not add referrerInfo as site when app is set', () => { + config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } }); + + const newValidBidderRequest = deepClone(validBidderRequest); + newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' }; + + const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.site).to.be.undefined; + config.resetConfig(); + }); + + it('should add device size to request when specified in config', () => { + config.setConfig({ device: { w: 1, h: 1 } }); + + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('device.w'); + expect(payload).to.have.nested.property('device.h'); + expect(payload.device.w).to.be.a('number'); + expect(payload.device.h).to.be.a('number'); + expect(payload.device.w).to.equal(1); + expect(payload.device.h).to.equal(1); + + config.resetConfig(); + }); + + it('should set schain on the request when set on config', () => { + const schain = { + validation: 'lax', + config: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } + }; + + const newBaseValidBidRequests = deepClone(baseValidBidRequests); + newBaseValidBidRequests[0].schain = schain; + + const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('source.ext.schain'); + expect(payload.source.ext.schain).to.deep.equal(schain); + }); + + it('should add currency when specified on the config', () => { + config.setConfig({ currency: { adServerCurrency: 'USD' } }); + + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.property('cur'); + expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally + + config.resetConfig(); + }); + + it('should also take in array for currency on the config', () => { + config.setConfig({ currency: { adServerCurrency: ['USD', 'PHP'] } }); + + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.property('cur'); + expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally + + config.resetConfig(); + }); + + it('should not set cur when currency is not specified on the config', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.cur).to.be.undefined; + }); + + it('should set user ids when present', () => { + const userId = { tdid: 123 }; + + const newBaseValidBidRequests = deepClone(baseValidBidRequests); + newBaseValidBidRequests[0].userId = { criteoId: 'sample-userid' }; + + const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('user.ext.eids'); + expect(payload.user.ext.eids).to.be.an('array'); + expect(payload.user.ext.eids.length).to.equal(1); + }); + + it('should not set user ids when none present', () => { + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(deepAccess(payload, 'user.ext.eids')).to.be.undefined; + }); + + it('should set digitrust when present on bid', () => { + const digiTrust = {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}; + + const newBaseValidBidRequests = deepClone(baseValidBidRequests); + newBaseValidBidRequests[0].userId = { digitrustid: digiTrust }; + + const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload).to.have.nested.property('user.ext.digitrust'); + expect(payload.user.ext.digitrust.id).to.equal(digiTrust.data.id); + expect(payload.user.ext.digitrust.keyv).to.equal(digiTrust.data.keyv); + }); + + it('should not set digitrust when opted out', () => { + const digiTrust = {data: {id: 'DTID', keyv: 4, privacy: {optout: true}, producer: 'ABC', version: 2}}; + + const newBaseValidBidRequests = deepClone(baseValidBidRequests); + newBaseValidBidRequests[0].userId = { digitrustid: digiTrust }; + + const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(deepAccess(payload, 'user.ext.digitrust')).to.be.undefined; + }); + }); + describe('interpretResponse', () => { + const publicationName = 'bbprebid.dev'; + const rendererCode = 'glorious_renderer'; + + const baseValidBid = { + bidder: BB_CONSTANTS.BIDDER_CODE, + params: { + accountId: 123, + publicationName: publicationName, + rendererCode: rendererCode, + connections: [ BB_CONSTANTS.BIDDER_CODE ], + bluebillywig: {} + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }; + + const baseValidBidRequests = [baseValidBid]; + + const validBidderRequest = { + auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', + auctionStart: 1585918458868, + bidderCode: BB_CONSTANTS.BIDDER_CODE, + bidderRequestId: '1a2345b67c8d9e0', + bids: [{ + adUnitCode: 'ad-unit-test', + auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', + bidId: '1234ab567c89de0', + bidRequestsCount: 1, + bidder: BB_CONSTANTS.BIDDER_CODE, + bidderRequestId: '1a2345b67c8d9e0', + params: baseValidBid.params, + sizes: [[768, 432], [640, 480], [630, 360]], + transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' + }], + start: 11585918458869, + timeout: 3000 + }; + + const validResponse = { + id: 'a12abc345-67d8-9012-e345-6f78901a2b34', + seatbid: [ + { + bid: [ + { + id: '1', + impid: '1234ab567c89de0', + price: 1, + adm: '\r\nBB Adserver00:00:51', + adid: '67069817', + adomain: [ + 'bluebillywig.com' + ], + cid: '3535', + crid: '67069817', + w: 1, + h: 1, + publicationName: 'bbprebid', + accountId: 123, + ext: { + prebid: { + targeting: { + hb_bidder: 'bluebillywig', + hb_pb: '1.00', + hb_size: '1x1' + }, + type: 'video' + }, + bidder: { + prebid: { + targeting: { + hb_bidder: 'bluebillywig', + hb_pb: '10.00', + hb_size: '1x1' + }, + type: 'video', + video: { + duration: 51, + primary_category: '' + } + }, + bidder: { + bluebillywig: { + brand_id: 1, + auction_id: 1, + bid_ad_type: 1, + creative_info: { + video: { + duration: 51, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'bluebillywig' + } + ], + cur: 'USD', + ext: { + responsetimemillis: { + bluebillywig: 0 + }, + tmaxrequest: 5000 + } + }; + + const serverResponse = { body: validResponse }; + + it('should build bid array', () => { + const response = deepClone(serverResponse); + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', () => { + const response = deepClone(serverResponse); + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + const bid = result[0]; + + // BB_HELPERS.transformRTBToPrebidProps + expect(bid.cpm).to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(bid.bidId).to.equal(serverResponse.body.seatbid[0].bid[0].impid); + expect(bid.requestId).to.equal(serverResponse.body.seatbid[0].bid[0].impid); + expect(bid.width).to.equal(serverResponse.body.seatbid[0].bid[0].w || BB_CONSTANTS.DEFAULT_WIDTH); + expect(bid.height).to.equal(serverResponse.body.seatbid[0].bid[0].h || BB_CONSTANTS.DEFAULT_HEIGHT); + expect(bid.ad).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(bid.netRevenue).to.equal(BB_CONSTANTS.DEFAULT_NET_REVENUE); + expect(bid.creativeId).to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(bid.currency).to.equal(serverResponse.body.cur); + expect(bid.ttl).to.equal(BB_CONSTANTS.DEFAULT_TTL); + + expect(bid.publicationName).to.equal(validBidderRequest.bids[0].params.publicationName); + expect(bid.rendererCode).to.equal(validBidderRequest.bids[0].params.rendererCode); + expect(bid.accountId).to.equal(validBidderRequest.bids[0].params.accountId); + }); + + it('should not give anything when seatbid is an empty array', () => { + const seatbidEmptyArray = deepClone(serverResponse); + seatbidEmptyArray.body.seatbid = []; + + const response = seatbidEmptyArray; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(result.length).to.equal(0); + }); + + it('should not give anything when seatbid is missing', () => { + const seatbidMissing = deepClone(serverResponse); + delete seatbidMissing.body.seatbid; + + const response = seatbidMissing; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(result.length).to.equal(0); + }); + + const seatbidNotArrayResponse = deepClone(serverResponse); + it('should not give anything when seatbid is not an array', () => { + const invalidValues = [ false, null, {}, void (0), 123, 'string' ]; + + for (const invalidValue of invalidValues) { + seatbidNotArrayResponse.body.seatbid = invalidValue + const response = deepClone(seatbidNotArrayResponse); // interpretResponse is destructive + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(result.length).to.equal(0); + } + }); + + it('should not give anything when seatbid.bid is an empty array', () => { + const seatbidBidEmpty = deepClone(serverResponse); + seatbidBidEmpty.body.seatbid[0].bid = []; + + const response = seatbidBidEmpty; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(result.length).to.equal(0); + }); + + it('should not give anything when seatbid.bid is missing', () => { + const seatbidBidMissing = deepClone(serverResponse); + delete seatbidBidMissing.body.seatbid[0].bid; + + const response = seatbidBidMissing; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(result.length).to.equal(0); + }); + + it('should not give anything when seatbid.bid is not an array', () => { + const seatbidBidNotArray = deepClone(serverResponse); + + const invalidValues = [ false, null, {}, void (0), 123, 'string' ]; + + for (const invalidValue of invalidValues) { + seatbidBidNotArray.body.seatbid[0].bid = invalidValue; + + const response = deepClone(seatbidBidNotArray); // interpretResponse is destructive + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(result.length).to.equal(0); + } + }); + }); + describe('getUserSyncs', () => { + const publicationName = 'bbprebid.dev'; + const rendererCode = 'glorious_renderer'; + + const baseValidBid = { + bidder: BB_CONSTANTS.BIDDER_CODE, + params: { + accountId: 123, + publicationName: publicationName, + rendererCode: rendererCode, + connections: [ BB_CONSTANTS.BIDDER_CODE ], + bluebillywig: {} + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }; + + const validBidRequests = [baseValidBid]; + + const validBidderRequest = { + auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', + auctionStart: 1585918458868, + bidderCode: BB_CONSTANTS.BIDDER_CODE, + bidderRequestId: '1a2345b67c8d9e0', + bids: [{ + adUnitCode: 'ad-unit-test', + auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', + bidId: '1234ab567c89de0', + bidRequestsCount: 1, + bidder: BB_CONSTANTS.BIDDER_CODE, + bidderRequestId: '1a2345b67c8d9e0', + params: baseValidBid.params, + sizes: [[768, 432], [640, 480], [630, 360]], + transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' + }], + start: 11585918458869, + timeout: 3000 + }; + const validResponse = { + id: 'a12abc345-67d8-9012-e345-6f78901a2b34', + seatbid: [ + { + bid: [ + { + id: '1', + impid: '1234ab567c89de0', + price: 1, + adm: '\r\nBB Adserver00:00:51', + adid: '67069817', + adomain: [ + 'bluebillywig.com' + ], + cid: '3535', + crid: '67069817', + w: 1, + h: 1, + publicationName: 'bbprebid', + accountId: 123, + ext: { + prebid: { + targeting: { + hb_bidder: 'bluebillywig', + hb_pb: '1.00', + hb_size: '1x1' + }, + type: 'video' + }, + bidder: { + prebid: { + targeting: { + hb_bidder: 'bluebillywig', + hb_pb: '10.00', + hb_size: '1x1' + }, + type: 'video', + video: { + duration: 51, + primary_category: '' + } + }, + bidder: { + bluebillywig: { + brand_id: 1, + auction_id: 1, + bid_ad_type: 1, + creative_info: { + video: { + duration: 51, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'bluebillywig' + } + ], + cur: 'USD', + ext: { + responsetimemillis: { + bluebillywig: 0 + }, + tmaxrequest: 5000 + } + }; + + const serverResponse = { body: validResponse }; + + const gdpr = { + consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAA AAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + gdprApplies: true + }; + + it('should return empty if no server response', function () { + const result = spec.getUserSyncs({}, false, gdpr); + expect(result).to.be.empty; + }); + + it('should return empty if server response is empty', function () { + const result = spec.getUserSyncs({}, [], gdpr); + expect(result).to.be.empty; + }); + + it('should return empty if iframeEnabled is not true', () => { + const result = spec.getUserSyncs({iframeEnabled: false}, [serverResponse], gdpr); + expect(result).to.be.empty; + }); + + it('should append the various values if they exist', function() { + // push data to syncStore + spec.buildRequests(validBidRequests, validBidderRequest); + + const result = spec.getUserSyncs({iframeEnabled: true}, [serverResponse], gdpr); + + expect(result).to.not.be.empty; + + expect(result[0].url).to.include('gdpr=1'); + expect(result[0].url).to.include(gdpr.consentString); + expect(result[0].url).to.include('accountId=123'); + expect(result[0].url).to.include(`bidders=${btoa(JSON.stringify(validBidRequests[0].params.connections))}`); + expect(result[0].url).to.include('cb='); + }); + }); +}); diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js new file mode 100644 index 00000000000..a353665ec33 --- /dev/null +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -0,0 +1,281 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/boldwinBidAdapter.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; + +describe('BoldwinBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'boldwin', + params: { + placementId: 0, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://ssp.videowalldirect.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://cs.videowalldirect.com/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/bridgewellBidAdapter_spec.js b/test/spec/modules/bridgewellBidAdapter_spec.js new file mode 100644 index 00000000000..644f468abe8 --- /dev/null +++ b/test/spec/modules/bridgewellBidAdapter_spec.js @@ -0,0 +1,1219 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bridgewellBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('bridgewellBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + const validTag = { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CLJgEAYYvxUiBXBlbm55KgkIrAIQ-gEaATk' + }, + }; + expect(spec.isBidRequestValid(validTag)).to.equal(true); + }); + + it('should return false when required params not found', function () { + const invalidTag = { + 'bidder': 'bridgewell', + 'params': {}, + }; + expect(spec.isBidRequestValid(invalidTag)).to.equal(false); + }); + + it('should return false when required params are empty', function () { + const invalidTag = { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': '', + }, + }; + expect(spec.isBidRequestValid(invalidTag)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + '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', + } + ]; + + it('should attach valid params to the tag', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://www.bridgewell.com/' + } + } + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + + expect(payload).to.be.an('object'); + expect(payload.adUnits).to.be.an('array'); + expect(payload.url).to.exist.and.to.equal('https://www.bridgewell.com/'); + for (let i = 0, max_i = payload.adUnits.length; i < max_i; i++) { + expect(payload.adUnits[i]).to.have.property('ChannelID').that.is.a('string'); + expect(payload.adUnits[i]).to.have.property('adUnitCode').and.to.equal('adunit-code-2'); + } + }); + + it('should attach validBidRequests to the tag', function () { + const request = spec.buildRequests(bidRequests); + const validBidRequests = request.validBidRequests; + expect(validBidRequests).to.deep.equal(bidRequests); + }); + }); + + describe('interpretResponse', function () { + const nativeBidRequests = { + validBidRequests: [ + { + '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', + }, + ] + }; + const nativeServerResponses = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + const bannerBidRequests = { + validBidRequests: [ + { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + }, + 'bidId': '3150ccb55da321', + }, + ] + }; + const bannerServerResponses = [ + { + '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' + }, + ]; + + it('should return all required parameters', function () { + const result = spec.interpretResponse({ 'body': nativeServerResponses }, nativeBidRequests); + + expect(result[0].requestId).to.equal('3150ccb55da321'); + expect(result[0].cpm).to.equal(7.0); + expect(result[0].width).to.equal(1); + expect(result[0].height).to.equal(1); + expect(result[0].ttl).to.equal(400); + expect(result[0].creativeId).to.equal('0e4048d3-5c74-4380-a21a-00ba35629f7d'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].currency).to.equal('NTD'); + expect(result[0].mediaType).to.equal('native'); + expect(result[0].native.image.url).to.equal('https://img.scupio.com/test/test-image.jpg'); + }); + + it('should return all required parameters banner', function () { + const result = spec.interpretResponse({ 'body': bannerServerResponses }, bannerBidRequests); + + expect(result[0].requestId).to.equal('3150ccb55da321'); + expect(result[0].cpm).to.equal(5.0); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].ttl).to.equal(360); + expect(result[0].creativeId).to.equal('e5b10774-32bf-4931-85ee-05095e8cff21'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].currency).to.equal('NTD'); + expect(result[0].mediaType).to.equal('banner'); + expect(result[0].ad).to.equal('
test 300x250
'); + }); + + it('should give up bid if server response is undefiend', function () { + let result = spec.interpretResponse({ 'body': undefined }, bannerBidRequests); + + expect(result).to.deep.equal([]); + }); + + it('should give up bid if request sizes is missing', function () { + const request = { + validBidRequests: [ + { + 'mediaTypes': { + 'banner': {} + }, + 'bidId': '3150ccb55da321', + }, + ] + }; + const result = spec.interpretResponse({ 'body': bannerServerResponses }, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if response sizes is invalid', function () { + const request = { + validBidRequests: [ + { + 'mediaTypes': { + 'banner': { + 'sizes': [728, 90] + } + }, + 'bidId': '3150ccb55da321', + }, + ] + }; + const result = spec.interpretResponse({ 'body': bannerServerResponses }, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if cpm is missing', function () { + const response = [ + { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, bannerBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if width or height is missing', function () { + const response = [ + { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, bannerBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if ad is missing', function () { + const response = [ + { + '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': response }, bannerBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if revenue mode is missing', function () { + const response = [ + { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 360, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, bannerBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if currency is missing', function () { + const response = [ + { + '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, + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, bannerBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if mediaType is missing', function () { + const response = [ + { + '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': response }, bannerBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if mediaType is not support', function () { + const responses = [ + { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'mediaType': 'superNiceAd', + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': responses }, bannerBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if property native of mediaType native is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native title is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native title is too long', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native body is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native image url is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native image is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native image is empty', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native image sizes is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native sponsoredBy is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native icon is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native icon url is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native icon sizes is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native clickUrl is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native clickTrackers is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native clickTrackers is empty', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native impressionTrackers is missing', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native impressionTrackers is empty', function () { + const response = [ + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 7.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': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, + ]; + + const result = spec.interpretResponse({ 'body': response }, nativeBidRequests); + expect(result).to.deep.equal([]); + }); + + it('should contain every request bid id in responses', function () { + const request = { + validBidRequests: [ + { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + }, + 'bidId': '3150ccb55da321', + }, + { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + }, + 'bidId': '3150ccb55da322', + } + ], + }; + const response = [{ + 'id': '0cd250f4-f40e-4a78-90f5-5168eb0a97e9', + 'bidder_code': 'bridgewell', + 'cpm': 7.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, { + 'id': '8a740063-6820-45e4-b01f-34ce9b38e858', + 'bidder_code': 'bridgewell', + 'cpm': 7.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }]; + const result = spec.interpretResponse({ 'body': response }, request); + let actualBidId = result.map(obj => obj.requestId); + let expectedBidId = ['3150ccb55da321', '3150ccb55da322']; + + expect(actualBidId).to.include(expectedBidId[0]).and.to.include(expectedBidId[1]); + }); + + it('should have 2 consumed responses when two requests with same sizes are given', function () { + const request = { + validBidRequests: [ + { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + }, + 'bidId': '3150ccb55da321', + }, + { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + }, + 'bidId': '3150ccb55da322', + } + ], + }; + const response = [{ + 'id': '0cd250f4-f40e-4a78-90f5-5168eb0a97e9', + 'bidder_code': 'bridgewell', + 'cpm': 7.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, { + 'id': '8a740063-6820-45e4-b01f-34ce9b38e858', + 'bidder_code': 'bridgewell', + 'cpm': 7.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }]; + const reducer = function(accumulator, currentValue) { + if (currentValue.consumed) accumulator++; + return accumulator; + }; + + spec.interpretResponse({ 'body': response }, request); + expect(response.reduce(reducer, 0)).to.equal(2); + }); + + it('should use adUnitCode to build bidResponses', function () { + const request = { + validBidRequests: [ + { + 'adUnitCode': 'div-gpt-ad-1564632520056-0', + 'bidId': '3150ccb55da321', + }, + { + 'adUnitCode': 'div-gpt-ad-1564632520056-1', + 'bidId': '3150ccb55da322', + } + ], + }; + const response = [{ + 'id': '0cd250f4-f40e-4a78-90f5-5168eb0a97e9', + 'bidder_code': 'bridgewell', + 'adUnitCode': 'div-gpt-ad-1564632520056-0', + 'cpm': 7.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, { + 'id': '8a740063-6820-45e4-b01f-34ce9b38e858', + 'bidder_code': 'bridgewell', + 'adUnitCode': 'div-gpt-ad-1564632520056-1', + 'cpm': 7.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }]; + const result = spec.interpretResponse({ 'body': response }, request); + let actualBidId = result.map(obj => obj.requestId); + let expectedBidId = ['3150ccb55da321', '3150ccb55da322']; + + expect(actualBidId).to.include(expectedBidId[0]).and.to.include(expectedBidId[1]); + }); + + it('should use size to match when adUnitCode is empty string in server response', function () { + const request = { + validBidRequests: [ + { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + }, + 'adUnitCode': 'div-gpt-ad-1564632520056-0', + 'bidId': '3150ccb55da321', + }, + { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + }, + 'adUnitCode': 'div-gpt-ad-1564632520056-1', + 'bidId': '3150ccb55da322', + } + ], + }; + const response = [{ + 'id': '0cd250f4-f40e-4a78-90f5-5168eb0a97e9', + 'bidder_code': 'bridgewell', + 'adUnitCode': '', + 'cpm': 7.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }, { + 'id': '8a740063-6820-45e4-b01f-34ce9b38e858', + 'bidder_code': 'bridgewell', + 'adUnitCode': '', + 'cpm': 7.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 400, + 'netRevenue': true, + 'currency': 'NTD' + }]; + const result = spec.interpretResponse({ 'body': response }, request); + let actualBidId = result.map(obj => obj.requestId); + let expectedBidId = ['3150ccb55da321', '3150ccb55da322']; + + expect(actualBidId).to.include(expectedBidId[0]).and.to.include(expectedBidId[1]); + }); + }); +}); diff --git a/test/spec/modules/brightMountainMediaBidAdapter_spec.js b/test/spec/modules/brightMountainMediaBidAdapter_spec.js new file mode 100644 index 00000000000..3b7e46e55a7 --- /dev/null +++ b/test/spec/modules/brightMountainMediaBidAdapter_spec.js @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/brightMountainMediaBidAdapter.js'; + +describe('brightMountainMediaBidAdapter_spec', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'brightmountainmedia', + bidderRequestId: '145e1d6a7837c9', + params: { + placement_id: '123qwerty' + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + }; + let bidderRequest = { + bidderCode: 'brightmountainmedia', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + uspConsent: '1YN-', + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + bids: [bid] + } + + describe('isBidRequestValid', function () { + it('Should return true when when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when required params are not passed', function () { + bid.params = {} + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://console.brightmountainmedia.com/hb/bid'); + }); + + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + 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('string'); + expect(placement.bidId).to.be.a('string'); + expect(placement.traffic).to.be.a('string'); + expect(placement.sizes).to.be.an('array'); + } + }); + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + let resObject = { + body: [{ + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + }] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + + describe('getUserSyncs', function () { + let syncoptionsIframe = { + 'iframeEnabled': 'true' + } + it('should return iframe sync option', function () { + expect(spec.getUserSyncs(syncoptionsIframe)).to.be.an('array').with.lengthOf(1); + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.exist; + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.exist; + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe') + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('https://console.brightmountainmedia.com:4444/cookieSync') + }); + }); +}); diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js index 14ed4d3024d..a89391d681e 100644 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import * as utils from 'src/utils'; -import { spec } from 'modules/brightcomBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/brightcomBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const URL = 'https://brightcombid.marphezis.com/hb'; @@ -141,6 +141,31 @@ describe('brightcomBidAdapter', function() { expect(payload.site.publisher.id).to.equal(1234567); }); + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'brightcom', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + context('when element is fully in view', function() { it('returns 100', function() { Object.assign(element, { width: 600, height: 400 }); diff --git a/test/spec/modules/britepoolIdSystem_spec.js b/test/spec/modules/britepoolIdSystem_spec.js index d7250eeb941..2c6dd234a90 100644 --- a/test/spec/modules/britepoolIdSystem_spec.js +++ b/test/spec/modules/britepoolIdSystem_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import {britepoolIdSubmodule} from 'modules/britepoolIdSystem'; +import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; describe('BritePool Submodule', () => { const api_key = '1111'; diff --git a/test/spec/modules/bucksenseBidAdapter_spec.js b/test/spec/modules/bucksenseBidAdapter_spec.js index b9da6c077b2..f49a63d2003 100644 --- a/test/spec/modules/bucksenseBidAdapter_spec.js +++ b/test/spec/modules/bucksenseBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/bucksenseBidAdapter'; +import {spec} from 'modules/bucksenseBidAdapter.js'; describe('Bucksense Adapter', function() { const BIDDER_CODE = 'bucksense'; @@ -104,7 +104,7 @@ describe('Bucksense Adapter', function() { beforeEach(function() { serverRequest = { 'method': 'POST', - 'url': 'https://prebid.bksn.se:445/prebidjs/', + 'url': 'https://prebid.bksn.se/prebidjs/', 'data': { 'pub_id': 'prebid.org', 'pl_id': '1000', diff --git a/test/spec/modules/buzzoolaBidAdapter_spec.js b/test/spec/modules/buzzoolaBidAdapter_spec.js index e6f22d1da20..8a04999219d 100644 --- a/test/spec/modules/buzzoolaBidAdapter_spec.js +++ b/test/spec/modules/buzzoolaBidAdapter_spec.js @@ -1,8 +1,8 @@ import {expect} from 'chai'; -import {spec} from 'modules/buzzoolaBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; -import {executeRenderer, Renderer} from '../../../src/Renderer'; -import {deepClone} from '../../../src/utils'; +import {spec} from 'modules/buzzoolaBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {executeRenderer, Renderer} from '../../../src/Renderer.js'; +import {deepClone} from '../../../src/utils.js'; const ENDPOINT = 'https://exchange.buzzoola.com/ssp/prebidjs'; const RENDERER_SRC = 'https://tube.buzzoola.com/new/build/buzzlibrary.js'; diff --git a/test/spec/modules/byplayBidAdapter_spec.js b/test/spec/modules/byplayBidAdapter_spec.js new file mode 100644 index 00000000000..57aad403c4e --- /dev/null +++ b/test/spec/modules/byplayBidAdapter_spec.js @@ -0,0 +1,93 @@ +import { expect } from 'chai'; +import { spec } from 'modules/byplayBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; + +describe('byplayBidAdapter', () => { + describe('isBidRequestValid', () => { + describe('exist sectionId', () => { + const bid = { + 'bidder': 'byplay', + 'params': { + 'sectionId': '11111' + }, + }; + + it('should equal true', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('not exist sectionId', () => { + const bid = { + 'bidder': 'byplay', + 'params': { + }, + }; + + it('should equal false', () => { + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + }); + + describe('buildRequests', () => { + const bids = [ + { + 'bidder': 'byplay', + 'bidId': '1234', + 'params': { + 'sectionId': '1111' + }, + } + ]; + + const request = spec.buildRequests(bids); + + it('should return POST', () => { + expect(request[0].method).to.equal('POST'); + }); + + it('should return data', () => { + expect(request[0].data).to.equal('{"requestId":"1234","sectionId":"1111"}'); + }); + }); + + describe('interpretResponse', () => { + const serverResponse = { + body: { + 'cpm': 1500, + 'width': 320, + 'height': 180, + 'netRevenue': true, + 'creativeId': '1', + 'requestId': '209c1fb5ad88f5', + 'vastXml': '' + } + }; + + const bidderRequest = { + 'method': 'GET', + 'url': 'https://tasp0g98f2.execute-api.ap-northeast-1.amazonaws.com/v1/bidder', + 'data': '{"requestId":"209c1fb5ad88f5","sectionId":7986}' + }; + + const result = spec.interpretResponse(serverResponse, bidderRequest); + + it('should return Array', () => { + expect(Array.isArray(result)).to.equal(true); + }); + + it('should get the correct bid response', () => { + expect(result[0].cpm).to.equal(1500); + expect(result[0].creativeId).to.equal('1'); + expect(result[0].width).to.equal(320); + expect(result[0].height).to.equal(180); + expect(result[0].mediaType).to.equal('video'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].requestId).to.equal('209c1fb5ad88f5'); + expect(result[0].ttl).to.equal(3000); + expect(result[0].vastXml).to.equal(''); + }); + }); +}); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js index a728e52dbc4..00741abda7a 100644 --- a/test/spec/modules/c1xBidAdapter_spec.js +++ b/test/spec/modules/c1xBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { c1xAdapter } from 'modules/c1xBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { c1xAdapter } from 'modules/c1xBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const ENDPOINT = 'https://ht.c1exchange.com/ht'; const BIDDER_CODE = 'c1x'; diff --git a/test/spec/modules/categoryTranslation_spec.js b/test/spec/modules/categoryTranslation_spec.js index 17cc07269b0..2301d6aab1b 100644 --- a/test/spec/modules/categoryTranslation_spec.js +++ b/test/spec/modules/categoryTranslation_spec.js @@ -1,6 +1,6 @@ -import { getAdserverCategoryHook, initTranslation } from 'modules/categoryTranslation'; -import { config } from 'src/config'; -import * as utils from 'src/utils'; +import { getAdserverCategoryHook, initTranslation, storage } from 'modules/categoryTranslation.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; import { expect } from 'chai'; describe('category translation', function () { @@ -9,7 +9,7 @@ describe('category translation', function () { beforeEach(function () { fakeTranslationServer = sinon.fakeServer.create(); - getLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage'); + getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); }); afterEach(function() { @@ -34,7 +34,7 @@ describe('category translation', function () { })); let bid = { meta: { - iabSubCatId: 'iab-1' + primaryCatId: 'iab-1' } } getAdserverCategoryHook(sinon.spy(), 'code', bid); @@ -57,7 +57,7 @@ describe('category translation', function () { })); let bid = { meta: { - iabSubCatId: 'iab-2' + primaryCatId: 'iab-2' } } getAdserverCategoryHook(sinon.spy(), 'code', bid); @@ -77,6 +77,19 @@ describe('category translation', function () { clock.restore(); }); + it('should make ajax call to update mapping file if data found in localstorage is expired', function () { + let clock = sinon.useFakeTimers(utils.timestamp()); + getLocalStorageStub.returns(JSON.stringify({ + lastUpdated: utils.timestamp() - 2 * 24 * 60 * 60 * 1000, + mapping: { + 'iab-1': '1' + } + })); + initTranslation(); + expect(fakeTranslationServer.requests.length).to.equal(1); + clock.restore(); + }); + it('should use default mapping file if publisher has not defined in config', function () { getLocalStorageStub.returns(null); initTranslation('http://sample.com', 'somekey'); @@ -84,7 +97,7 @@ describe('category translation', function () { expect(fakeTranslationServer.requests[0].url).to.equal('http://sample.com'); }); - it('should use publisher defined defined mapping file', function () { + it('should use publisher defined mapping file', function () { config.setConfig({ 'brandCategoryTranslation': { 'translationFile': 'http://sample.com' diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js new file mode 100644 index 00000000000..f14612629b1 --- /dev/null +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -0,0 +1,428 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ccxBidAdapter.js'; +import * as utils from 'src/utils.js'; + +describe('ccxAdapter', function () { + let bids = [ + { + adUnitCode: 'banner', + auctionId: '0b9de793-8eda-481e-a548-c187d58b28d9', + bidId: '2e56e1af51a5d7', + bidder: 'ccx', + bidderRequestId: '17e7b9f58a607e', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 607 + }, + sizes: [[300, 250]], + transactionId: 'aefddd38-cfa0-48ab-8bdd-325de4bab5f9' + }, + { + adUnitCode: 'video', + auctionId: '0b9de793-8eda-481e-a548-c187d58b28d9', + bidId: '3u94t90ut39tt3t', + bidder: 'ccx', + bidderRequestId: '23ur20r239r2r', + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + }, + params: { + placementId: 608 + }, + sizes: [[640, 480]], + transactionId: 'aefddd38-cfa0-48ab-8bdd-325de4bab5f9' + } + ]; + describe('isBidRequestValid', function () { + it('Valid bid requests', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + expect(spec.isBidRequestValid(bids[1])).to.be.true; + }); + it('Invalid bid reqeusts - no placementId', function () { + let bidsClone = utils.deepClone(bids); + bidsClone[0].params = undefined; + expect(spec.isBidRequestValid(bidsClone[0])).to.be.false; + }); + it('Invalid bid reqeusts - invalid banner sizes', function () { + let bidsClone = utils.deepClone(bids); + bidsClone[0].mediaTypes.banner.sizes = [300, 250]; + expect(spec.isBidRequestValid(bidsClone[0])).to.be.false; + bidsClone[0].mediaTypes.banner.sizes = [[300, 250], [750]]; + expect(spec.isBidRequestValid(bidsClone[0])).to.be.false; + bidsClone[0].mediaTypes.banner.sizes = []; + expect(spec.isBidRequestValid(bidsClone[0])).to.be.false; + }); + it('Invalid bid reqeusts - invalid video sizes', function () { + let bidsClone = utils.deepClone(bids); + bidsClone[1].mediaTypes.video.playerSize = []; + expect(spec.isBidRequestValid(bidsClone[1])).to.be.false; + bidsClone[1].mediaTypes.video.sizes = [640, 480]; + expect(spec.isBidRequestValid(bidsClone[1])).to.be.false; + }); + it('Valid bid reqeust - old style sizes', function () { + let bidsClone = utils.deepClone(bids); + delete (bidsClone[0].mediaTypes); + delete (bidsClone[1].mediaTypes); + expect(spec.isBidRequestValid(bidsClone[0])).to.be.true; + expect(spec.isBidRequestValid(bidsClone[1])).to.be.true; + bidsClone[0].sizes = [300, 250]; + expect(spec.isBidRequestValid(bidsClone[0])).to.be.true; + }); + }); + describe('buildRequests', function () { + it('No valid bids', function () { + expect(spec.buildRequests([])).to.be.undefined; + }); + + it('Valid bid request - default', function () { + let response = spec.buildRequests(bids, {bids}); + expect(response).to.be.not.empty; + expect(response.data).to.be.not.empty; + + let data = JSON.parse(response.data); + + expect(data).to.be.an('object'); + expect(data).to.have.keys('site', 'imp', 'id', 'ext', 'device'); + + let imps = [ + { + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + }, + ext: { + pid: 607 + }, + id: '2e56e1af51a5d7', + secure: 1 + }, + { + video: { + w: 640, + h: 480, + protocols: [2, 3, 5, 6], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [1, 2, 3, 4], + skip: 0 + }, + id: '3u94t90ut39tt3t', + secure: 1, + ext: { + pid: 608 + } + } + ]; + expect(data.imp).to.deep.have.same.members(imps); + }); + + it('Valid bid request - custom', function () { + let bidsClone = utils.deepClone(bids); + let imps = [ + { + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + }, + ext: { + pid: 607 + }, + id: '2e56e1af51a5d7', + secure: 1 + }, + { + video: { + w: 640, + h: 480, + protocols: [5, 6], + mimes: ['video/mp4'], + playbackmethod: [3], + skip: 1, + skipafter: 5 + }, + id: '3u94t90ut39tt3t', + secure: 1, + ext: { + pid: 608 + } + } + ]; + + bidsClone[1].params.video = {}; + bidsClone[1].params.video.protocols = [5, 6]; + bidsClone[1].params.video.mimes = ['video/mp4']; + bidsClone[1].params.video.playbackmethod = [3]; + bidsClone[1].params.video.skip = 1; + bidsClone[1].params.video.skipafter = 5; + + let response = spec.buildRequests(bidsClone, {'bids': bidsClone}); + let data = JSON.parse(response.data); + + expect(data.imp).to.deep.have.same.members(imps); + }); + it('Valid bid request - sizes old style', function () { + let bidsClone = utils.deepClone(bids); + delete (bidsClone[0].mediaTypes); + delete (bidsClone[1].mediaTypes); + bidsClone[0].mediaType = 'banner'; + bidsClone[1].mediaType = 'video'; + + let imps = [ + { + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + }, + ext: { + pid: 607 + }, + id: '2e56e1af51a5d7', + secure: 1 + }, + { + video: { + w: 640, + h: 480, + protocols: [2, 3, 5, 6], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [1, 2, 3, 4], + skip: 0 + }, + id: '3u94t90ut39tt3t', + secure: 1, + ext: { + pid: 608 + } + } + ]; + + let response = spec.buildRequests(bidsClone, {'bids': bidsClone}); + let data = JSON.parse(response.data); + + expect(data.imp).to.deep.have.same.members(imps); + }); + it('Valid bid request - sizes old style - no media type', function () { + let bidsClone = utils.deepClone(bids); + delete (bidsClone[0].mediaTypes); + delete (bidsClone[1]); + + let imps = [ + { + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + }, + ext: { + pid: 607 + }, + id: '2e56e1af51a5d7', + secure: 1 + } + ]; + + let response = spec.buildRequests(bidsClone, {'bids': bidsClone}); + let data = JSON.parse(response.data); + + expect(data.imp).to.deep.have.same.members(imps); + }); + }); + + describe('GDPR conformity', function () { + it('should transmit correct data', function () { + let bidsClone = utils.deepClone(bids); + let gdprConsent = { + consentString: 'awefasdfwefasdfasd', + gdprApplies: true + }; + let response = spec.buildRequests(bidsClone, {'bids': bidsClone, 'gdprConsent': gdprConsent}); + let data = JSON.parse(response.data); + + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.equal('awefasdfwefasdfasd'); + }); + }); + + describe('GDPR absence conformity', function () { + it('should transmit correct data', function () { + let response = spec.buildRequests(bids, {bids}); + let data = JSON.parse(response.data); + + expect(data.regs).to.be.undefined; + expect(data.user).to.be.undefined; + }); + }); + + let response = { + id: '0b9de793-8eda-481e-a548-c187d58b28d9', + seatbid: [ + { + bid: [ + { + id: '2e56e1af51a5d7_221', + impid: '2e56e1af51a5d7', + price: 8.1, + adid: '221', + adm: '', + adomain: ['clickonometrics.com'], + crid: '221', + w: 300, + h: 250, + ext: { + type: 'standard' + } + }, + { + id: '2e56e1af51a5d8_222', + impid: '2e56e1af51a5d8', + price: 5.68, + adid: '222', + adm: '', + adomain: ['clickonometrics.com'], + crid: '222', + w: 640, + h: 480, + ext: { + type: 'video' + } + } + ] + } + ], + cur: 'PLN', + ext: { + ttl: 5, + usersync: [ + { + type: 'image', + url: 'http://foo.sync?param=1' + }, + { + type: 'iframe', + url: 'http://foo.sync?param=2' + } + ] + } + }; + + describe('interpretResponse', function () { + it('Valid bid response - multi', function () { + let bidResponses = [ + { + requestId: '2e56e1af51a5d7', + cpm: 8.1, + width: 300, + height: 250, + creativeId: '221', + netRevenue: false, + ttl: 5, + currency: 'PLN', + ad: '' + }, + { + requestId: '2e56e1af51a5d8', + cpm: 5.68, + width: 640, + height: 480, + creativeId: '222', + netRevenue: false, + ttl: 5, + currency: 'PLN', + vastXml: '' + } + ]; + expect(spec.interpretResponse({body: response})).to.deep.have.same.members(bidResponses); + }); + + it('Valid bid response - single', function () { + delete response.seatbid[0].bid[1]; + let bidResponses = [ + { + requestId: '2e56e1af51a5d7', + cpm: 8.1, + width: 300, + height: 250, + creativeId: '221', + netRevenue: false, + ttl: 5, + currency: 'PLN', + ad: '' + } + ]; + expect(spec.interpretResponse({body: response})).to.deep.have.same.members(bidResponses); + }); + + it('Empty bid response', function () { + expect(spec.interpretResponse({})).to.be.empty; + }); + }); + describe('getUserSyncs', function () { + it('Valid syncs - all', function () { + let syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + + let expectedSyncs = [ + { + type: 'image', + url: 'http://foo.sync?param=1' + }, + { + type: 'iframe', + url: 'http://foo.sync?param=2' + } + ]; + expect(spec.getUserSyncs(syncOptions, [{body: response}])).to.deep.have.same.members(expectedSyncs); + }); + + it('Valid syncs - only image', function () { + let syncOptions = { + iframeEnabled: false, + pixelEnabled: true + }; + let expectedSyncs = [ + { + type: 'image', url: 'http://foo.sync?param=1' + } + ]; + expect(spec.getUserSyncs(syncOptions, [{body: response}])).to.deep.have.same.members(expectedSyncs); + }); + + it('Valid syncs - only iframe', function () { + let syncOptions = {iframeEnabled: true, pixelEnabled: false}; + let expectedSyncs = [ + { + type: 'iframe', url: 'http://foo.sync?param=2' + } + ]; + expect(spec.getUserSyncs(syncOptions, [{body: response}])).to.deep.have.same.members(expectedSyncs); + }); + + it('Valid syncs - empty', function () { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + response.ext.usersync = {}; + expect(spec.getUserSyncs(syncOptions, [{body: response}])).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/cedatoBidAdapter_spec.js b/test/spec/modules/cedatoBidAdapter_spec.js index d05f3e7abbf..a7f4875afff 100644 --- a/test/spec/modules/cedatoBidAdapter_spec.js +++ b/test/spec/modules/cedatoBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/cedatoBidAdapter'; +import {spec} from 'modules/cedatoBidAdapter.js'; describe('the cedato adapter', function () { function getValidBidObject() { @@ -33,18 +33,35 @@ describe('the cedato adapter', function () { expect(result).to.equal(false); }); }); + describe('buildRequests', function() { var bid, bidRequestObj; beforeEach(function() { bid = getValidBidObject(); - bidRequestObj = {refererInfo: {referer: 'prebid.js'}}; + bidRequestObj = { + refererInfo: {referer: 'prebid.js'}, + gdprConsent: { + consentString: 'test-string', + gdprApplies: true + }, + uspConsent: '1NYN' + }; }); it('should build a very basic request', function() { - var request = spec.buildRequests([bid], bidRequestObj); + var [request] = spec.buildRequests([bid], bidRequestObj); expect(request.method).to.equal('POST'); }); + + it('should pass gdpr and usp strings to server', function() { + var [request] = spec.buildRequests([bid], bidRequestObj); + var payload = JSON.parse(request.data); + expect(payload.gdpr_consent).to.not.be.undefined; + expect(payload.gdpr_consent.consent_string).to.equal(bidRequestObj.gdprConsent.consentString); + expect(payload.gdpr_consent.consent_required).to.equal(bidRequestObj.gdprConsent.gdprApplies); + expect(payload.us_privacy).to.equal(bidRequestObj.uspConsent); + }); }); describe('interpretResponse', function() { @@ -88,4 +105,29 @@ describe('the cedato adapter', function () { expect(responses).to.be.an('array').with.length(1); }); }); + + describe('getUserSyncs', function() { + var bid; + + beforeEach(function() { + bid = getValidBidObject(); + }); + + it('should sync with iframe', function() { + var syncs = spec.getUserSyncs({ iframeEnabled: true }, null, { + consentString: '', + gdprApplies: true + }); + + expect(syncs).to.be.an('array').with.length(1); + expect(syncs[0].type).to.equal('iframe'); + }); + + it('should sync with image', function() { + var syncs = spec.getUserSyncs({ pixelEnabled: true }); + + expect(syncs).to.be.an('array').with.length(1); + expect(syncs[0].type).to.equal('image'); + }); + }); }); diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js new file mode 100644 index 00000000000..5438f6c8701 --- /dev/null +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -0,0 +1,597 @@ +import {expect} from 'chai'; +import {spec, helper} from 'modules/cleanmedianetBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {newBidder} from '../../../src/adapters/bidderFactory.js'; + +const supplyPartnerId = '123'; +const adapter = newBidder(spec); +describe('CleanmedianetAdapter', function () { + describe('Is String start with search ', function () { + it('check if a string started with', function () { + expect(helper.startsWith('cleanmediaads.com', 'cleanmediaads')).to.equal( + true + ); + }); + }); + + describe('inherited functions', function() { + it('exists and is a function', function() { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + it('should validate supply-partner ID', function() { + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: 123 } }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) + ).to.equal(true); + }); + + it('should validate bid floor', function() { + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) + ).to.equal(true); // bidfloor has a default + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', bidfloor: '123' } + }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', bidfloor: 0.1 } + }) + ).to.equal(true); + }); + + it('should validate adpos', function() { + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) + ).to.equal(true); // adpos has a default + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', adpos: '123' } + }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', adpos: 0.1 } + }) + ).to.equal(true); + }); + + it('should validate instl', function() { + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) + ).to.equal(true); // adpos has a default + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', instl: '123' } + }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', instl: -1 } + }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 0 } }) + ).to.equal(true); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 1 } }) + ).to.equal(true); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 2 } }) + ).to.equal(false); + }); + }); + + describe('buildRequests', function() { + const bidRequest = { + adUnitCode: 'adunit-code', + auctionId: '1d1a030790a475', + mediaTypes: { + banner: {} + }, + params: { + supplyPartnerId: supplyPartnerId + }, + sizes: [[300, 250], [300, 600]], + transactionId: 'a123456789', + refererInfo: { referer: 'https://examplereferer.com' }, + gdprConsent: { + consentString: 'some string', + gdprApplies: true + } + }; + it('returns an array', function() { + let response; + response = spec.buildRequests([]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + response = spec.buildRequests([bidRequest], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), { + auctionId: '1', + adUnitCode: 'a' + }); + const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), { + auctionId: '1', + adUnitCode: 'b' + }); + response = spec.buildRequests([adUnit1, adUnit2], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(2); + }); + + it('builds request correctly', function() { + let bidRequest2 = utils.deepClone(bidRequest); + bidRequest2.refererInfo.referer = 'https://www.test.com/page.html'; + let response = spec.buildRequests([bidRequest], bidRequest2)[0]; + expect(response.data.site.domain).to.equal('www.test.com'); + expect(response.data.site.page).to.equal('https://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('https://www.test.com/page.html'); + expect(response.data.imp.length).to.equal(1); + expect(response.data.imp[0].id).to.equal(bidRequest.transactionId); + expect(response.data.imp[0].instl).to.equal(0); + expect(response.data.imp[0].tagid).to.equal(bidRequest.adUnitCode); + expect(response.data.imp[0].bidfloor).to.equal(0); + expect(response.data.imp[0].bidfloorcur).to.equal('USD'); + const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals1.params.instl = 1; + response = spec.buildRequests( + [bidRequestWithInstlEquals1], + bidRequest2 + )[0]; + expect(response.data.imp[0].instl).to.equal( + bidRequestWithInstlEquals1.params.instl + ); + const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals0.params.instl = 1; + response = spec.buildRequests( + [bidRequestWithInstlEquals0], + bidRequest2 + )[0]; + expect(response.data.imp[0].instl).to.equal( + bidRequestWithInstlEquals0.params.instl + ); + const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); + bidRequestWithBidfloorEquals1.params.bidfloor = 1; + response = spec.buildRequests( + [bidRequestWithBidfloorEquals1], + bidRequest2 + )[0]; + expect(response.data.imp[0].bidfloor).to.equal( + bidRequestWithBidfloorEquals1.params.bidfloor + ); + }); + + it('builds request banner object correctly', function() { + let response; + const bidRequestWithBanner = utils.deepClone(bidRequest); + bidRequestWithBanner.mediaTypes = { + banner: { + sizes: [[300, 250], [120, 600]] + } + }; + response = spec.buildRequests([bidRequestWithBanner], bidRequest)[0]; + expect(response.data.imp[0].banner.w).to.equal( + bidRequestWithBanner.mediaTypes.banner.sizes[0][0] + ); + expect(response.data.imp[0].banner.h).to.equal( + bidRequestWithBanner.mediaTypes.banner.sizes[0][1] + ); + expect(response.data.imp[0].banner.pos).to.equal(0); + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].banner.pos).to.equal( + bidRequestWithPosEquals1.params.pos + ); + }); + + it('builds request video object correctly', function() { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + sizes: [[300, 250], [120, 600]] + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.w).to.equal( + bidRequestWithVideo.mediaTypes.video.sizes[0][0] + ); + expect(response.data.imp[0].video.h).to.equal( + bidRequestWithVideo.mediaTypes.video.sizes[0][1] + ); + expect(response.data.imp[0].video.pos).to.equal(0); + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.pos).to.equal( + bidRequestWithPosEquals1.params.pos + ); + }); + + it('builds request video object correctly with context', function() { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + context: 'instream' + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('outstream'); + + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal(null); + }); + it('builds request video object correctly with multi-dimensions size array', function () { + let bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes.video = { + playerSize: [[304, 254], [305, 255]], + context: 'instream' + }; + + let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.w).to.equal(304); + expect(response.data.imp[1].video.h).to.equal(254); + + bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes.video = { + playerSize: [304, 254] + }; + + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.w).to.equal(304); + expect(response.data.imp[1].video.h).to.equal(254); + }); + + it('builds request with gdpr consent', function() { + let response = spec.buildRequests([bidRequest], bidRequest)[0]; + expect(response.data.ext).to.have.property('gdpr_consent'); + expect(response.data.ext.gdpr_consent.consent_string).to.equal( + 'some string' + ); + expect(response.data.ext.gdpr_consent.consent_required).to.equal(true); + }); + }); + + describe('interpretResponse', function() { + const bannerBidRequest = { + adUnitCode: 'adunit-code', + auctionId: '1d1a030790a475', + mediaTypes: { + banner: {} + }, + params: { + supplyPartnerId: supplyPartnerId + }, + sizes: [[300, 250], [300, 600]], + transactionId: 'a123456789', + bidId: '111', + refererInfo: { referer: 'https://examplereferer.com' } + }; + + const videoBidRequest = { + adUnitCode: 'adunit-code', + auctionId: '1d1a030790a475', + mediaTypes: { + video: {} + }, + params: { + supplyPartnerId: supplyPartnerId + }, + sizes: [[300, 250], [300, 600]], + transactionId: 'a123456789', + bidId: '111', + refererInfo: { referer: 'https://examplereferer.com' } + }; + + const rtbResponse = { + id: 'imp_5b05b9fde4b09084267a556f', + bidid: 'imp_5b05b9fde4b09084267a556f', + cur: 'USD', + ext: { + utrk: [ + { type: 'iframe', url: '//bidder.cleanmediaads.com/user/sync/1' }, + { type: 'image', url: '//bidder.cleanmediaads.com/user/sync/2' } + ] + }, + seatbid: [ + { + seat: 'seat1', + group: 0, + bid: [ + { + id: '0', + impid: '1', + price: 2.016, + adid: '579ef31bfa788b9d2000d562', + nurl: + 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + adm: + '↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵', + adomain: ['aaa.com'], + cid: '579ef268fa788b9d2000d55c', + crid: '579ef31bfa788b9d2000d562', + attr: [], + h: 600, + w: 120, + ext: { + vast_url: 'https://my.vast.com', + utrk: [{ type: 'iframe', url: '//p.partner1.io/user/sync/1' }] + } + } + ] + }, + { + seat: 'seat2', + group: 0, + bid: [ + { + id: '1', + impid: '1', + price: 3, + adid: '542jlhdfd2112jnjf3x', + nurl: + 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + adm: + ' ', + adomain: ['bbb.com'], + cid: 'fgdlwjh2498ydjhg1', + crid: 'kjh34297ydh2133d', + attr: [], + h: 250, + w: 300, + ext: { + utrk: [{ type: 'image', url: '//p.partner2.io/user/sync/1' }] + } + } + ] + } + ] + }; + + it('returns an empty array on missing response', function() { + let response; + + response = spec.interpretResponse(undefined, { + bidRequest: bannerBidRequest + }); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + + response = spec.interpretResponse({}, { bidRequest: bannerBidRequest }); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + }); + + it('aggregates banner bids from all seat bids', function() { + const response = spec.interpretResponse( + { body: rtbResponse }, + { bidRequest: bannerBidRequest } + ); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + + const ad0 = response[0]; + expect(ad0.requestId).to.equal(bannerBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[1].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[1].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[1].bid[0].h); + expect(ad0.ttl).to.equal(360); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[1].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal( + rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD' + ); + expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm); + expect(ad0.vastXml).to.be.an('undefined'); + expect(ad0.vastUrl).to.be.an('undefined'); + }); + + it('aggregates video bids from all seat bids', function() { + const response = spec.interpretResponse( + { body: rtbResponse }, + { bidRequest: videoBidRequest } + ); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + + const ad0 = response[0]; + expect(ad0.requestId).to.equal(videoBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[0].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[0].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[0].bid[0].h); + expect(ad0.ttl).to.equal(360); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[0].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal( + rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD' + ); + expect(ad0.ad).to.be.an('undefined'); + expect(ad0.vastXml).to.equal(rtbResponse.seatbid[0].bid[0].adm); + expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[0].bid[0].ext.vast_url); + }); + + it('aggregates user-sync pixels', function() { + const response = spec.getUserSyncs({}, [{ body: rtbResponse }]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(4); + expect(response[0].type).to.equal(rtbResponse.ext.utrk[0].type); + expect(response[0].url).to.equal( + rtbResponse.ext.utrk[0].url + '?gc=missing' + ); + expect(response[1].type).to.equal(rtbResponse.ext.utrk[1].type); + expect(response[1].url).to.equal( + rtbResponse.ext.utrk[1].url + '?gc=missing' + ); + expect(response[2].type).to.equal( + rtbResponse.seatbid[0].bid[0].ext.utrk[0].type + ); + expect(response[2].url).to.equal( + rtbResponse.seatbid[0].bid[0].ext.utrk[0].url + '?gc=missing' + ); + expect(response[3].type).to.equal( + rtbResponse.seatbid[1].bid[0].ext.utrk[0].type + ); + expect(response[3].url).to.equal( + rtbResponse.seatbid[1].bid[0].ext.utrk[0].url + '?gc=missing' + ); + }); + + it('supports configuring outstream renderers', function() { + const videoResponse = { + id: '64f32497-b2f7-48ec-9205-35fc39894d44', + bidid: 'imp_5c24924de4b0d106447af333', + cur: 'USD', + seatbid: [ + { + seat: '3668', + group: 0, + bid: [ + { + id: 'gb_1', + impid: 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + price: 5.0, + adid: '1274', + nurl: + 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', + adomain: [], + adm: + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + cid: '3668', + crid: '1274', + cat: [], + attr: [], + h: 250, + w: 300, + ext: { + vast_url: + 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + imptrackers: [ + 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1' + ] + } + } + ] + } + ], + ext: { + utrk: [ + { + type: 'image', + url: + 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675' + } + ] + } + }; + const videoRequest = utils.deepClone(videoBidRequest); + videoRequest.mediaTypes.video.context = 'outstream'; + const result = spec.interpretResponse( + { body: videoResponse }, + { bidRequest: videoRequest } + ); + expect(result[0].renderer).to.not.equal(undefined); + }); + + it('validates in/existing of gdpr consent', function() { + let videoResponse = { + id: '64f32497-b2f7-48ec-9205-35fc39894d44', + bidid: 'imp_5c24924de4b0d106447af333', + cur: 'USD', + seatbid: [ + { + seat: '3668', + group: 0, + bid: [ + { + id: 'gb_1', + impid: 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + price: 5.0, + adid: '1274', + nurl: + 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', + adomain: [], + adm: + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + cid: '3668', + crid: '1274', + cat: [], + attr: [], + h: 250, + w: 300, + ext: { + vast_url: + 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + imptrackers: [ + 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1' + ] + } + } + ] + } + ], + ext: { + utrk: [ + { + type: 'image', + url: + 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675' + } + ] + } + }; + let gdprConsent = { + gdprApplies: true, + consentString: 'consent string' + }; + let result = spec.getUserSyncs( + {}, + [{ body: videoResponse }], + gdprConsent + ); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal( + 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gc=consent%20string' + ); + + gdprConsent.gdprApplies = false; + result = spec.getUserSyncs({}, [{ body: videoResponse }], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal( + 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gc=missing' + ); + + videoResponse.ext.utrk[0].url = + 'https://bidder.cleanmediaads.com/pix/1275/scm'; + result = spec.getUserSyncs({}, [{ body: videoResponse }], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal( + 'https://bidder.cleanmediaads.com/pix/1275/scm?gc=missing' + ); + }); + }); +}); diff --git a/test/spec/modules/clickforceBidAdapter_spec.js b/test/spec/modules/clickforceBidAdapter_spec.js index 3d4fc70c057..dad00f94641 100644 --- a/test/spec/modules/clickforceBidAdapter_spec.js +++ b/test/spec/modules/clickforceBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/clickforceBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/clickforceBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('ClickforceAdapter', function () { const adapter = newBidder(spec); @@ -175,7 +175,7 @@ describe('ClickforceAdapter', function () { } 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'); + expect(userSync[0].url).to.equal('https://cdn.holmesmind.com/js/capmapping.htm'); }); it('should register type is image', function () { @@ -184,7 +184,7 @@ describe('ClickforceAdapter', function () { } let userSync = spec.getUserSyncs(syncOptions); expect(userSync[0].type).to.equal('image'); - expect(userSync[0].url).to.equal('https://c.doublemax.net/cm'); + expect(userSync[0].url).to.equal('https://c.holmesmind.com/cm'); }); }); }); diff --git a/test/spec/modules/clicktripzBidAdapter_spec.js b/test/spec/modules/clicktripzBidAdapter_spec.js new file mode 100644 index 00000000000..fed94811c4e --- /dev/null +++ b/test/spec/modules/clicktripzBidAdapter_spec.js @@ -0,0 +1,152 @@ +import {expect} from 'chai'; +import {spec} from 'modules/clicktripzBidAdapter.js'; + +const ENDPOINT_URL = 'https://www.clicktripz.com/x/prebid/v1'; + +describe('clicktripzBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'clicktripz', + 'params': { + placementId: 'testPlacementId', + siteId: 'testSiteId' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '1234asdf1234', + 'bidderRequestId': '1234asdf1234asdf', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' + }; + + let bid2 = { + 'bidder': 'clicktripz', + 'params': { + placementId: 'testPlacementId' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '1234asdf1234', + 'bidderRequestId': '1234asdf1234asdf', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' + }; + + let bid3 = { + 'bidder': 'clicktripz', + 'params': { + siteId: 'testSiteId' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '1234asdf1234', + 'bidderRequestId': '1234asdf1234asdf', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are NOT found', function () { + expect(spec.isBidRequestValid(bid2)).to.equal(false); + expect(spec.isBidRequestValid(bid3)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let validBidRequests = [{ + 'bidder': 'clicktripz', + 'params': { + placementId: 'testPlacementId', + siteId: 'testSiteId' + }, + 'sizes': [ + [300, 250], + [300, 300] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, { + 'bidder': 'clicktripz', + 'params': { + placementId: 'testPlacementId2', + siteId: 'testSiteId2' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '25beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }]; + + const request = spec.buildRequests(validBidRequests); + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('sends bid request to our endpoint at the correct URL', function () { + expect(request.url).to.equal(ENDPOINT_URL); + }); + it('sends bid request to our endpoint at the correct URL', function () { + expect(request.url).to.equal(ENDPOINT_URL); + }); + + it('transforms sizes into an array of strings. Pairs of concatenated sizes joined with an x', function () { + expect(request.data[0].sizes.toString()).to.equal('300x250,300x300'); + }); + it('transforms sizes into an array of strings. Pairs of concatenated sizes joined with an x', function () { + expect(request.data[1].sizes.toString()).to.equal('300x250'); + }); + + it('includes bidId, siteId, and placementId in payload', function () { + expect(request.data[0].bidId).to.equal('23beaa6af6cdde'); + expect(request.data[0].siteId).to.equal('testSiteId'); + expect(request.data[0].placementId).to.equal('testPlacementId'); + }); + it('includes bidId, siteId, and placementId in payload', function () { + expect(request.data[1].bidId).to.equal('25beaa6af6cdde'); + expect(request.data[1].siteId).to.equal('testSiteId2'); + expect(request.data[1].placementId).to.equal('testPlacementId2'); + }); + }); + + describe('interpretResponse', function () { + let serverResponse = { + body: [{ + 'bidId': 'bid-request-id', + 'ttl': 120, + 'netRevenue': true, + 'size': '300x200', + 'currency': 'USD', + 'adUrl': 'https://www.clicktripz.com/n3/crane/v0/render?t=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoiaHR0cHM6XC9cL3d3dy5jbGlja3RyaXB6LmNvbVwvY2xpY2sucGhwP2NhbXBhaWduSUQ9MTkxNTYmcHJlQ2hlY2tlZD0xJnB1Ymxpc2hlcklEPTM2MCZzZWFyY2hLZXk9N2M5MzQ0NzhlM2M1NTc3Y2EyN2ZmN2Y1NTg5N2NkMzkmc2VhcmNoRGlzcGxheVR5cGU9MSZkaXNwbGF5VHlwZT00JmNyZWF0aXZlVHlwZT1zaW5nbGUmaXNQb3BVbmRlcj0wJnBvc2l0aW9uPTEmdHlwZT0xJmNpdHk9TWFkcmlkJTJDK1NwYWluJmNoZWNrSW5EYXRlPTAzJTJGMDElMkYyMDIwJmNoZWNrT3V0RGF0ZT0wMyUyRjA1JTJGMjAyMCZndWVzdHM9MiZyb29tcz0xIn0.WBDGYr1qfkSvOuK02VpMW3iAua1E02jjDGDViFc2kaE', + 'creativeId': '25ef9876abc5681f153', + 'cpm': 50 + }] + }; + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': 'bid-request-id', + 'cpm': 50, + 'netRevenue': true, + 'width': '300', + 'height': '200', + 'creativeId': '25ef9876abc5681f153', + 'currency': 'USD', + 'adUrl': 'https://www.clicktripz.com/n3/crane/v0/render?t=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoiaHR0cHM6XC9cL3d3dy5jbGlja3RyaXB6LmNvbVwvY2xpY2sucGhwP2NhbXBhaWduSUQ9MTkxNTYmcHJlQ2hlY2tlZD0xJnB1Ymxpc2hlcklEPTM2MCZzZWFyY2hLZXk9N2M5MzQ0NzhlM2M1NTc3Y2EyN2ZmN2Y1NTg5N2NkMzkmc2VhcmNoRGlzcGxheVR5cGU9MSZkaXNwbGF5VHlwZT00JmNyZWF0aXZlVHlwZT1zaW5nbGUmaXNQb3BVbmRlcj0wJnBvc2l0aW9uPTEmdHlwZT0xJmNpdHk9TWFkcmlkJTJDK1NwYWluJmNoZWNrSW5EYXRlPTAzJTJGMDElMkYyMDIwJmNoZWNrT3V0RGF0ZT0wMyUyRjA1JTJGMjAyMCZndWVzdHM9MiZyb29tcz0xIn0.WBDGYr1qfkSvOuK02VpMW3iAua1E02jjDGDViFc2kaE', + 'ttl': 120 + }]; + let result = spec.interpretResponse(serverResponse); + expect(result).to.deep.equal(expectedResponse); + }); + }); +}); diff --git a/test/spec/modules/coinzillaBidAdapter_spec.js b/test/spec/modules/coinzillaBidAdapter_spec.js index e9157e2a735..a3438b80126 100644 --- a/test/spec/modules/coinzillaBidAdapter_spec.js +++ b/test/spec/modules/coinzillaBidAdapter_spec.js @@ -1,6 +1,6 @@ import {assert, expect} from 'chai'; -import {spec} from 'modules/coinzillaBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; +import {spec} from 'modules/coinzillaBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; const ENDPOINT_URL = 'https://request.czilladx.com/serve/request.php'; diff --git a/test/spec/modules/collectcentBidAdapter_spec.js b/test/spec/modules/collectcentBidAdapter_spec.js index 7398c5c7dd9..0ab83a8024b 100644 --- a/test/spec/modules/collectcentBidAdapter_spec.js +++ b/test/spec/modules/collectcentBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from '../../../modules/collectcentBidAdapter'; +import {spec} from '../../../modules/collectcentBidAdapter.js'; describe('Collectcent', function () { let bid = { diff --git a/test/spec/modules/colombiaBidAdapter_spec.js b/test/spec/modules/colombiaBidAdapter_spec.js new file mode 100644 index 00000000000..4e80c6b1d9d --- /dev/null +++ b/test/spec/modules/colombiaBidAdapter_spec.js @@ -0,0 +1,152 @@ +import { expect } from 'chai'; +import { spec } from 'modules/colombiaBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const HOST_NAME = document.location.protocol + '//' + window.location.host; +const ENDPOINT = 'https://ade.clmbtech.com/cde/prebid.htm'; + +describe('colombiaBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId not passed correctly', function () { + bid.params.placementId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code1', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code2', + 'sizes': [ + [300, 250] + ], + 'bidId': '382091349b149f"', + 'bidderRequestId': '"1f9c98192de251"', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + expect(request[1].method).to.equal('POST'); + }); + + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = [ + { + 'method': 'POST', + 'url': 'https://ade.clmbtech.com/cde/prebid.htm', + 'data': { + 'v': 'hb1', + 'p': '307466', + 'w': '300', + 'h': '250', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i', + } + } + ]; + + let serverResponse = { + body: { + 'ad': '
This is test case for colombia adapter
', + 'cpm': 3.14, + 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', + 'currency': 'USD', + 'uid': '23beaa6af6cdde', + 'width': 728, + 'height': 90, + 'netRevenue': true, + 'ttl': 600, + 'dealid': '', + 'referrer': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836' + } + }; + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 3.14, + 'width': 728, + 'height': 90, + 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', + 'dealId': '', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'referrer': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'ad': '
This is test case for colombia adapter
' + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', function () { + let response = { + body: { + 'uid': '23beaa6af6cdde', + 'height': 0, + 'creativeId': '', + '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/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 9ed2dbe6e6b..df9bdcbd47b 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from '../../../modules/colossussspBidAdapter'; +import {spec} from '../../../modules/colossussspBidAdapter.js'; describe('ColossussspAdapter', function () { let bid = { @@ -88,7 +88,7 @@ describe('ColossussspAdapter', function () { let placements = data['placements']; for (let i = 0; i < placements.length; i++) { let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); + expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain'); expect(placement.schain).to.be.an('object') expect(placement.placementId).to.be.a('number'); expect(placement.bidId).to.be.a('string'); @@ -102,6 +102,36 @@ describe('ColossussspAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + + describe('buildRequests with user ids', function () { + bid.userId = {} + bid.userId.britepoolid = 'britepoolid123'; + bid.userId.idl_env = 'idl_env123'; + bid.userId.tdid = 'tdid123'; + bid.userId.id5id = 'id5id123' + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + let placements = data['placements']; + expect(data).to.be.an('object'); + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.property('eids') + expect(placement.eids).to.be.an('array') + expect(placement.eids.length).to.be.equal(4) + for (let index in placement.eids) { + let v = placement.eids[index]; + expect(v).to.have.all.keys('source', 'uids') + expect(v.source).to.be.oneOf(['britepool.com', 'identityLink', 'adserver.org', 'id5-sync.com']) + expect(v.uids).to.be.an('array'); + expect(v.uids.length).to.be.equal(1) + expect(v.uids[0]).to.have.property('id') + expect(v.uids[0].id).to.be.oneOf(['britepoolid123', 'idl_env123', 'tdid123', 'id5id123']) + } + } + }); + }); + describe('interpretResponse', function () { let resObject = { body: [ { diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js new file mode 100644 index 00000000000..aef4fb562a7 --- /dev/null +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -0,0 +1,469 @@ +import {expect} from 'chai'; +import {spec} from 'modules/connectadBidAdapter.js'; +import { config } from 'src/config.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('ConnectAd Adapter', function () { + let bidRequests; + let bidderRequest; + let bidRequestsUserIds; + + beforeEach(function () { + bidRequests = [ + { + bidder: 'conntectad', + params: { + siteId: 123456, + networkId: 123456, + bidfloor: 0.50 + }, + adUnitCode: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidId: '2f95c00074b931', + auctionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df' + } + ]; + + bidRequestsUserIds = [{ + bidder: 'conntectad', + params: { + siteId: 123456, + networkId: 123456 + }, + adUnitCode: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidId: '2f95c00074b931', + auctionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', + userId: { + tdid: '123456' + } + }]; + + bidderRequest = { + timeout: 3000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: {} + } + } + }); + + describe('inherited functions', function () { + it('should exists and is a function', function () { + const adapter = newBidder(spec); + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('implementation', function () { + describe('for requests', function () { + it('should accept bid', function () { + let validBid = { + bidder: 'connectad', + params: { + siteId: 123456, + networkId: 123456 + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } + }; + const isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('should reject if missing sizes', function () { + let invalidBid = { + bidder: 'connectad', + params: { + siteId: 123456, + } + }; + const isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }); + + it('should return true when optional bidFloor params found for an ad', function () { + let validBid = { + bidder: 'connectad', + params: { + siteId: 123456, + networkId: 123456, + bidfloor: 0.20 + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } + }; + const isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true) + }); + + it('should reject if missing siteId/networkId', function () { + let invalidBid = { + bidder: 'connectad', + params: {}, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + } + }; + const isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }); + + it('should reject if missing networkId', function () { + let invalidBid = { + bidder: 'connectad', + params: { + siteId: 123456 + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + } + }; + const isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }); + + it('should contain SiteId and NetworkId', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].siteId).to.equal(123456); + expect(requestparse.placements[0].networkId).to.equal(123456); + }); + + it('should process floors module if available', function() { + const floorInfo = { + currency: 'USD', + floor: 5.20 + }; + bidRequests[0].getFloor = () => floorInfo; + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].floor).to.equal(5.20); + }); + + it('should be 0 if no floormodule is available', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].floor).to.equal(0); + }); + + it('should contain gdpr info', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.user.ext.gdpr).to.equal(1); + expect(requestparse.user.ext.consent).to.equal('consentDataString'); + }); + + it('should build a request if Consent but no gdprApplies', function () { + let bidderRequest = { + timeout: 3000, + gdprConsent: { + gdprApplies: false, + consentString: 'consentDataString', + }, + } + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.placements[0].adTypes).to.be.an('array'); + expect(requestparse.placements[0].siteId).to.equal(123456); + expect(requestparse.user.ext.consent).to.equal('consentDataString'); + }); + + it('should build a request if gdprConsent empty', function () { + let bidderRequest = { + timeout: 3000, + gdprConsent: {} + } + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.placements[0].adTypes).to.be.an('array'); + expect(requestparse.placements[0].siteId).to.equal(123456); + }); + + it('should have CCPA Consent if defined', function () { + const uspConsent = '1YYN' + bidderRequest.uspConsent = uspConsent + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.user.ext.us_privacy).to.equal(uspConsent); + }); + + it('should not have CCPA Consent if not defined', function () { + bidderRequest.uspConsent = undefined + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.user.ext.us_privacy).to.be.undefined; + }); + + it('should not include schain when not provided', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.source).to.not.exist; + }); + + it('should submit coppa if set in config', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.user.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should send all UserData data', function () { + const request = spec.buildRequests(bidRequestsUserIds, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.user.ext.eids).to.be.an('array'); + expect(requestparse.user.ext.eids[0].uids[0].id).to.equal('123456'); + }); + + it('should add referer info', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequ = { + refererInfo: { + referer: 'https://connectad.io/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'https://connectad.io/page.html', + 'https://connectad.io/iframe1.html', + 'https://connectad.io/iframe2.html' + ] + } + } + const request = spec.buildRequests([bidRequest], bidderRequ); + const requestparse = JSON.parse(request.data); + + expect(requestparse.referrer_info).to.exist; + }); + + it('should populate schain', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'reseller1.com', + 'sid': 'absc1', + 'hp': 1 + } + ] + } + }); + + const request = spec.buildRequests([bidRequest], bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.source.ext.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'reseller1.com', + 'sid': 'absc1', + 'hp': 1 + } + ] + }); + }); + }); + + describe('bid responses', function () { + it('should return complete bid response', function () { + let serverResponse = { + body: { + decisions: { + '2f95c00074b931': { + adId: '0', + contents: [ + { + body: '<<<---- Creative --->>>' + } + ], + height: '250', + width: '300', + pricing: { + clearPrice: 11.899999999999999 + } + } + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].cpm).to.equal(11.899999999999999); + expect(bids[0].width).to.equal('300'); + expect(bids[0].height).to.equal('250'); + expect(bids[0].ad).to.have.length.above(1); + }); + + it('should return empty bid response', function () { + let serverResponse = { + body: { + decisions: [] + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on incorrect size', function () { + let serverResponse = { + body: { + decisions: { + '2f95c00074b931': { + adId: '0', + contents: [ + { + body: '<<<---- Creative --->>>' + } + ], + height: '160', + width: '600', + pricing: { + clearPrice: 0 + } + } + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on 0 cpm', function () { + let serverResponse = { + body: { + decisions: { + '2f95c00074b931': { + adId: '0', + contents: [ + { + body: '<<<---- Creative --->>>' + } + ], + height: '300', + width: '250', + pricing: { + clearPrice: 0 + } + } + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + expect(bids).to.be.lengthOf(0); + }); + + it('should process a deal id', function () { + let serverResponse = { + body: { + decisions: { + '2f95c00074b931': { + adId: '0', + dealid: 'ABC90210', + contents: [ + { + body: '<<<---- Creative --->>>' + } + ], + height: '300', + width: '250', + pricing: { + clearPrice: 11.899999999999999 + } + } + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + expect(bids).to.be.lengthOf(1); + expect(bids[0].dealid).to.equal('ABC90210'); + }); + }); + }); + + describe('getUserSyncs', () => { + let testParams = [ + { + name: 'iframe/no gdpr or ccpa', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null], + expect: { + type: 'iframe', + pixels: ['https://cdn.connectad.io/connectmyusers.php?'] + } + }, + { + name: 'iframe/gdpr', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}], + expect: { + type: 'iframe', + pixels: ['https://cdn.connectad.io/connectmyusers.php?gdpr=1&gdpr_consent=234234&'] + } + }, + { + name: 'iframe/ccpa', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null, 'YN12'], + expect: { + type: 'iframe', + pixels: ['https://cdn.connectad.io/connectmyusers.php?us_privacy=YN12&'] + } + }, + { + name: 'iframe/ccpa & gdpr', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}, 'YN12'], + expect: { + type: 'iframe', + pixels: ['https://cdn.connectad.io/connectmyusers.php?gdpr=1&gdpr_consent=234234&us_privacy=YN12&'] + } + } + ]; + + for (let i = 0; i < testParams.length; i++) { + let currParams = testParams[i]; + it(currParams.name, function () { + const result = spec.getUserSyncs.apply(this, currParams.arguments); + expect(result).to.have.lengthOf(currParams.expect.pixels.length); + for (let ix = 0; ix < currParams.expect.pixels.length; ix++) { + expect(result[ix].url).to.equal(currParams.expect.pixels[ix]); + expect(result[ix].type).to.equal(currParams.expect.type); + } + }); + } + }); +}); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index d6e0ef22f83..ee4140afa10 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -3,11 +3,12 @@ import { requestBidsHook, resetConsentData, consentAPI, - consentTimeout -} from 'modules/consentManagementUsp'; -import * as utils from 'src/utils'; -import { config } from 'src/config'; -import { uspDataHandler } from 'src/adapterManager'; + consentTimeout, + staticConsentData +} from 'modules/consentManagementUsp.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import { uspDataHandler } from 'src/adapterManager.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -84,6 +85,31 @@ describe('consentManagement', function () { expect(consentTimeout).to.be.equal(7500); }); }); + + describe('static consent string setConsentConfig value', () => { + afterEach(() => { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + it('results in user settings overriding system defaults', () => { + let staticConfig = { + usp: { + cmpApi: 'static', + timeout: 7500, + consentData: { + getUSPData: { + uspString: '1YYY' + } + } + } + }; + + setConsentConfig(staticConfig); + expect(consentAPI).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + expect(staticConsentData.usPrivacy).to.be.equal(staticConfig.usp.consentData.getUSPData.uspString); + }); + }); }); describe('requestBidsHook tests:', function () { @@ -159,7 +185,10 @@ describe('consentManagement', function () { resetConsentData(); }); - it('should bypass CMP and simply use previously stored consentData', function () { + // from prebid 4425 - "the USP (CCPA) api function __uspapi() always responds synchronously, whether or not privacy data is available, while the GDPR CMP may respond asynchronously + // Because the USP API does not wait for a user response, if it was not successfully obtained before the first auction, we should try again to retrieve privacy data before each subsequent auction. + + it('should not bypass CMP and simply use previously stored consentData', function () { let testConsentData = { uspString: '1YY' }; @@ -182,7 +211,7 @@ describe('consentManagement', function () { let consent = uspDataHandler.getConsentData(); expect(didHookReturn).to.be.true; expect(consent).to.equal(testConsentData.uspString); - sinon.assert.notCalled(uspStub); + sinon.assert.called(uspStub); }); }); @@ -245,6 +274,48 @@ describe('consentManagement', function () { } }); + describe('test without iframe locater', function() { + let uspapiStub = sinon.stub(); + + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + window.__uspapi = function() {}; + }); + + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + uspapiStub.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + delete window.__uspapi; + resetConsentData(); + }); + + it('Workflow for normal page withoout iframe locater', function() { + let testConsentData = { + uspString: '1NY' + }; + + uspapiStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfig); + requestBidsHook(() => { didHookReturn = true; }, {}); + + let consent = uspDataHandler.getConsentData(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + expect(didHookReturn).to.be.true; + expect(consent).to.equal(testConsentData.uspString); + }); + }); + describe('USPAPI workflow for normal pages:', function () { let uspapiStub = sinon.stub(); let ifr = null; diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 9731164c655..deaacbc5a28 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,9 +1,8 @@ -import {setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, allowAuction, staticConsentData} from 'modules/consentManagement'; -import {gdprDataHandler} from 'src/adapterManager'; -import * as utils from 'src/utils'; -import { config } from 'src/config'; +import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, allowAuction, staticConsentData, gdprScope } from 'modules/consentManagement.js'; +import { gdprDataHandler } from 'src/adapterManager.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; -let assert = require('chai').assert; let expect = require('chai').expect; describe('consentManagement', function () { @@ -25,17 +24,17 @@ describe('consentManagement', function () { setConsentConfig({}); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(10000); - expect(allowAuction).to.be.true; - sinon.assert.callCount(utils.logInfo, 4); + expect(gdprScope).to.be.equal(false); + sinon.assert.callCount(utils.logInfo, 3); }); - it('should exit consent manager if config is not an object', function() { + it('should exit consent manager if config is not an object', function () { setConsentConfig(''); expect(userCMP).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); - it('should exit consent manager if gdpr not set with new config structure', function() { + it('should exit consent manager if gdpr not set with new config structure', function () { setConsentConfig({ usp: { cmpApi: 'iab', timeout: 50 } }); expect(userCMP).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); @@ -45,23 +44,27 @@ describe('consentManagement', function () { describe('valid setConsentConfig value', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('results in all user settings overriding system defaults', function () { let allConfig = { cmpApi: 'iab', timeout: 7500, - allowAuctionWithoutConsent: false + allowAuctionWithoutConsent: false, + defaultGdprScope: true }; setConsentConfig(allConfig); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(7500); - expect(allowAuction).to.be.false; + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); + expect(gdprScope).to.be.true; }); - it('should use new consent manager config structure for gdpr', function() { + it('should use new consent manager config structure for gdpr', function () { setConsentConfig({ gdpr: { cmpApi: 'daa', timeout: 8700 } }); @@ -70,7 +73,7 @@ describe('consentManagement', function () { expect(consentTimeout).to.be.equal(8700); }); - it('should ignore config.usp and use config.gdpr, with default cmpApi', function() { + it('should ignore config.usp and use config.gdpr, with default cmpApi', function () { setConsentConfig({ gdpr: { timeout: 5000 }, usp: { cmpApi: 'daa', timeout: 50 } @@ -80,7 +83,7 @@ describe('consentManagement', function () { expect(consentTimeout).to.be.equal(5000); }); - it('should ignore config.usp and use config.gdpr, with default cmpAip and timeout', function() { + it('should ignore config.usp and use config.gdpr, with default cmpAip and timeout', function () { setConsentConfig({ gdpr: {}, usp: { cmpApi: 'daa', timeout: 50 } @@ -90,7 +93,7 @@ describe('consentManagement', function () { expect(consentTimeout).to.be.equal(10000); }); - it('should recognize config.gdpr, with default cmpAip and timeout', function() { + it('should recognize config.gdpr, with default cmpAip and timeout', function () { setConsentConfig({ gdpr: {} }); @@ -99,7 +102,7 @@ describe('consentManagement', function () { expect(consentTimeout).to.be.equal(10000); }); - it('should fallback to old consent manager config object if no config.gdpr', function() { + it('should fallback to old consent manager config object if no config.gdpr', function () { setConsentConfig({ cmpApi: 'iab', timeout: 3333, @@ -109,16 +112,19 @@ describe('consentManagement', function () { expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(3333); - expect(allowAuction).to.be.equal(false); + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); + expect(gdprScope).to.be.equal(false); }); }); describe('static consent string setConsentConfig value', () => { afterEach(() => { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); - it('results in user settings overriding system defaults', () => { + it('results in user settings overriding system defaults for v1 spec', () => { let staticConfig = { cmpApi: 'static', timeout: 7500, @@ -154,361 +160,7 @@ describe('consentManagement', function () { '2': true, '3': true, '4': true, - '5': false, - '6': true, - '7': true, - '8': true, - '9': true, - '10': true, - '11': true, - '12': true, - '13': true, - '14': true, - '15': true, - '16': true, - '17': true, - '18': true, - '19': true, - '20': true, - '21': true, - '22': true, - '23': true, - '24': true, - '25': true, - '26': true, - '27': true, - '28': true, - '29': true, - '30': true, - '31': true, - '32': true, - '33': true, - '34': true, - '35': true, - '36': true, - '37': true, - '38': true, - '39': true, - '40': true, - '41': true, - '42': true, - '43': true, - '44': true, - '45': true, - '46': true, - '47': true, - '48': true, - '49': true, - '50': true, - '51': true, - '52': true, - '53': true, - '54': false, - '55': true, - '56': true, - '57': true, - '58': true, - '59': true, - '60': true, - '61': true, - '62': true, - '63': true, - '64': true, - '65': true, - '66': true, - '67': true, - '68': true, - '69': true, - '70': true, - '71': true, - '72': true, - '73': true, - '74': true, - '75': true, - '76': true, - '77': true, - '78': true, - '79': true, - '80': true, - '81': true, - '82': true, - '83': false, - '84': true, - '85': true, - '86': true, - '87': true, - '88': true, - '89': true, - '90': true, - '91': true, - '92': true, - '93': true, - '94': true, - '95': true, - '96': false, - '97': true, - '98': true, - '99': false, - '100': true, - '101': true, - '102': true, - '103': false, - '104': true, - '105': true, - '106': false, - '107': false, - '108': true, - '109': true, - '110': true, - '111': true, - '112': true, - '113': true, - '114': true, - '115': true, - '116': false, - '117': false, - '118': false, - '119': true, - '120': true, - '121': false, - '122': true, - '123': false, - '124': true, - '125': true, - '126': true, - '127': true, - '128': true, - '129': true, - '130': true, - '131': true, - '132': true, - '133': true, - '134': true, - '135': false, - '136': true, - '137': false, - '138': true, - '139': true, - '140': true, - '141': true, - '142': true, - '143': true, - '144': true, - '145': true, - '146': false, - '147': true, - '148': true, - '149': true, - '150': true, - '151': true, - '152': false, - '153': true, - '154': true, - '155': true, - '156': true, - '157': true, - '158': true, - '159': true, - '160': true, - '161': true, - '162': true, - '163': true, - '164': true, - '165': true, - '166': false, - '167': true, - '168': true, - '169': true, - '170': true, - '171': false, - '172': false, - '173': true, - '174': true, - '175': true, - '176': false, - '177': true, - '178': false, - '179': true, - '180': true, - '181': false, - '182': true, - '183': true, - '184': false, - '185': true, - '186': false, - '187': false, - '188': true, - '189': true, - '190': true, - '191': false, - '192': true, - '193': true, - '194': true, - '195': true, - '196': false, - '197': true, - '198': true, - '199': true, - '200': true, - '201': true, - '202': true, - '203': true, - '204': false, - '205': true, - '206': false, - '207': false, - '208': true, - '209': true, - '210': true, - '211': true, - '212': true, - '213': true, - '214': false, - '215': true, - '216': false, - '217': true, - '218': false, - '219': false, - '220': false, - '221': false, - '222': false, - '223': false, - '224': true, - '225': true, - '226': true, - '227': true, - '228': true, - '229': true, - '230': true, - '231': false, - '232': true, - '233': false, - '234': true, - '235': true, - '236': true, - '237': true, - '238': true, - '239': true, - '240': true, - '241': true, - '242': false, - '243': false, - '244': true, - '245': true, - '246': true, - '247': false, - '248': true, - '249': true, - '250': false, - '251': false, - '252': true, - '253': true, - '254': true, - '255': true, - '256': true, - '257': true, - '258': true, - '259': true, - '260': true, - '261': false, - '262': true, - '263': false, - '264': true, - '265': true, - '266': true, - '267': false, - '268': false, - '269': true, - '270': true, - '271': false, - '272': true, - '273': true, - '274': true, - '275': true, - '276': true, - '277': true, - '278': true, - '279': true, - '280': true, - '281': true, - '282': true, - '283': false, - '284': true, - '285': true, - '286': false, - '287': false, - '288': true, - '289': true, - '290': true, - '291': true, - '292': false, - '293': false, - '294': true, - '295': true, - '296': false, - '297': true, - '298': false, - '299': true, - '300': false, - '301': true, - '302': true, - '303': true, - '304': true, - '305': false, - '306': false, - '307': false, - '308': true, - '309': true, - '310': true, - '311': false, - '312': false, - '313': false, - '314': true, - '315': true, - '316': true, - '317': true, - '318': true, - '319': true, - '320': true, - '321': false, - '322': false, - '323': true, - '324': false, - '325': true, - '326': true, - '327': false, - '328': true, - '329': false, - '330': false, - '331': true, - '332': false, - '333': true, - '334': false, - '335': false, - '336': false, - '337': false, - '338': false, - '339': true, - '340': false, - '341': false, - '342': false, - '343': false, - '344': false, - '345': true, - '346': false, - '347': false, - '348': false, - '349': true, - '350': false, - '351': false, - '352': false, - '353': false, - '354': true, - '355': false, - '356': false, - '357': false, - '358': false, - '359': true + '5': false } } } @@ -517,7 +169,94 @@ describe('consentManagement', function () { setConsentConfig(staticConfig); expect(userCMP).to.be.equal('static'); expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(allowAuction).to.be.false; + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); + expect(staticConsentData).to.be.equal(staticConfig.consentData); + }); + + it('results in user settings overriding system defaults for v2 spec', () => { + let staticConfig = { + cmpApi: 'static', + timeout: 7500, + allowAuctionWithoutConsent: false, + consentData: { + getTCData: { + 'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A', + 'cmpId': 92, + 'cmpVersion': 100, + 'tcfPolicyVersion': 2, + 'gdprApplies': true, + 'isServiceSpecific': true, + 'useNonStandardStacks': false, + 'purposeOneTreatment': false, + 'publisherCC': 'US', + 'cmpStatus': 'loaded', + 'eventStatus': 'tcloaded', + 'outOfBand': { + 'allowedVendors': {}, + 'discloseVendors': {} + }, + 'purpose': { + 'consents': { + '1': true, + '2': true, + '3': true + }, + 'legitimateInterests': { + '1': false, + '2': false, + '3': false + } + }, + 'vendor': { + 'consents': { + '1': false, + '2': true, + '3': false + }, + 'legitimateInterests': { + '1': false, + '2': true, + '3': false, + '4': false, + '5': false + } + }, + 'specialFeatureOptins': { + '1': false, + '2': false + }, + 'restrictions': {}, + 'publisher': { + 'consents': { + '1': false, + '2': false, + '3': false + }, + 'legitimateInterests': { + '1': false, + '2': false, + '3': false + }, + 'customPurpose': { + 'consents': {}, + 'legitimateInterests': {} + } + } + } + } + }; + + setConsentConfig(staticConfig); + expect(userCMP).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); + expect(gdprScope).to.be.equal(false); expect(staticConsentData).to.be.equal(staticConfig.consentData); }); }); @@ -554,7 +293,6 @@ describe('consentManagement', function () { utils.logWarn.restore(); utils.logError.restore(); config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); resetConsentData(); }); @@ -593,12 +331,11 @@ describe('consentManagement', function () { beforeEach(function () { didHookReturn = false; - window.__cmp = function() {}; + window.__cmp = function () { }; }); afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); cmpStub.restore(); delete window.__cmp; resetConsentData(); @@ -614,7 +351,7 @@ describe('consentManagement', function () { args[2](testConsentData); }); setConsentConfig(goodConfigWithAllowAuction); - requestBidsHook(() => {}, {}); + requestBidsHook(() => { }, {}); cmpStub.restore(); // reset the stub to ensure it wasn't called during the second round of calls @@ -632,220 +369,355 @@ describe('consentManagement', function () { expect(consent.gdprApplies).to.be.true; sinon.assert.notCalled(cmpStub); }); - }); - describe('CMP workflow for safeframe page', function () { - let registerStub = sinon.stub(); - - beforeEach(function () { - didHookReturn = false; - window.$sf = { - ext: { - register: function() {}, - cmp: function() {} - } + it('should not set consent.gdprApplies to true if defaultGdprScope is true', function () { + let testConsentData = { + gdprApplies: false, + consentData: 'xyz' }; - sinon.stub(utils, 'logError'); - sinon.stub(utils, 'logWarn'); - }); - afterEach(function () { - delete window.$sf; - config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); - registerStub.restore(); - utils.logError.restore(); - utils.logWarn.restore(); - resetConsentData(); - }); + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); - it('should return the consent data from a safeframe callback', function () { - var testConsentData = { - data: { - msgName: 'cmpReturn', - vendorConsents: { - metadata: 'abc123def', - gdprApplies: true - }, - vendorConsentData: { - consentData: 'abc123def', - gdprApplies: true - } - } - }; - registerStub = sinon.stub(window.$sf.ext, 'register').callsFake((...args) => { - args[2](testConsentData.data.msgName, testConsentData.data); + setConsentConfig({ + cmpApi: 'iab', + timeout: 7500, + defaultGdprScope: true }); - setConsentConfig(goodConfigWithAllowAuction); requestBidsHook(() => { didHookReturn = true; - }, {adUnits: [{ sizes: [[300, 250]] }]}); + }, {}); + let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logWarn); - sinon.assert.notCalled(utils.logError); - expect(didHookReturn).to.be.true; - expect(consent.consentString).to.equal('abc123def'); - expect(consent.gdprApplies).to.be.true; + expect(consent.gdprApplies).to.be.false; }); }); - describe('CMP workflow for iframed page', function () { - let ifr = null; - let stringifyResponse = false; + describe('iframe tests', function () { + let cmpPostMessageCb = () => { }; + let stringifyResponse; - beforeEach(function () { - sinon.stub(utils, 'logError'); - sinon.stub(utils, 'logWarn'); - ifr = createIFrameMarker(); - window.addEventListener('message', cmpMessageHandler, false); - }); - - afterEach(function () { - config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); - delete window.__cmp; - utils.logError.restore(); - utils.logWarn.restore(); - resetConsentData(); - document.body.removeChild(ifr); - window.removeEventListener('message', cmpMessageHandler); - }); - - function createIFrameMarker() { - var ifr = document.createElement('iframe'); + function createIFrameMarker(frameName) { + let ifr = document.createElement('iframe'); ifr.width = 0; ifr.height = 0; - ifr.name = '__cmpLocator'; + ifr.name = frameName; document.body.appendChild(ifr); return ifr; } - function cmpMessageHandler(event) { - if (event && event.data) { - var data = event.data; - if (data.__cmpCall) { - var callId = data.__cmpCall.callId; - var returnValue = null; - var response = { - __cmpReturn: { - callId, - returnValue: { - consentData: 'encoded_consent_data_via_post_message', - gdprApplies: true, - }, - success: true - } - }; - event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); + function creatCmpMessageHandler(prefix, returnValue) { + return function (event) { + if (event && event.data) { + let data = event.data; + if (data[`${prefix}Call`]) { + let callId = data[`${prefix}Call`].callId; + let response = { + [`${prefix}Return`]: { + callId, + returnValue, + success: true + } + }; + event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); + } } } } - // Run tests with JSON response and String response - // from CMP window postMessage listener. - testIFramedPage('with/JSON response', false); - testIFramedPage('with/String response', true); - - function testIFramedPage(testName, messageFormatString) { + function testIFramedPage(testName, messageFormatString, tarConsentString, ver) { it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { stringifyResponse = messageFormatString; setConsentConfig(goodConfigWithAllowAuction); requestBidsHook(() => { let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); - expect(consent.consentString).to.equal('encoded_consent_data_via_post_message'); + expect(consent.consentString).to.equal(tarConsentString); expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(ver); done(); }, {}); }); } + + beforeEach(function () { + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logError.restore(); + utils.logWarn.restore(); + config.resetConfig(); + resetConsentData(); + }); + + describe('v1 CMP workflow for safeframe page', function () { + let registerStub = sinon.stub(); + let ifrSf = null; + beforeEach(function () { + didHookReturn = false; + window.$sf = { + ext: { + register: function () { }, + cmp: function () { } + } + }; + ifrSf = createIFrameMarker('__cmpLocator'); + }); + + afterEach(function () { + delete window.$sf; + registerStub.restore(); + document.body.removeChild(ifrSf); + }); + + it('should return the consent data from a safeframe callback', function () { + let testConsentData = { + data: { + msgName: 'cmpReturn', + vendorConsents: { + metadata: 'abc123def', + gdprApplies: true + }, + vendorConsentData: { + consentData: 'abc123def', + gdprApplies: true + } + } + }; + registerStub = sinon.stub(window.$sf.ext, 'register').callsFake((...args) => { + args[2](testConsentData.data.msgName, testConsentData.data); + }); + + setConsentConfig(goodConfigWithAllowAuction); + requestBidsHook(() => { + didHookReturn = true; + }, { adUnits: [{ sizes: [[300, 250]] }] }); + let consent = gdprDataHandler.getConsentData(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal('abc123def'); + expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(1); + }); + }); + + describe('v1 CMP workflow for iframe pages', function () { + stringifyResponse = false; + let ifr1 = null; + + beforeEach(function () { + ifr1 = createIFrameMarker('__cmpLocator'); + cmpPostMessageCb = creatCmpMessageHandler('__cmp', { + consentData: 'encoded_consent_data_via_post_message', + gdprApplies: true, + }); + window.addEventListener('message', cmpPostMessageCb, false); + }); + + afterEach(function () { + delete window.__cmp; // deletes the local copy made by the postMessage CMP call function + document.body.removeChild(ifr1); + window.removeEventListener('message', cmpPostMessageCb); + }); + + // Run tests with JSON response and String response + // from CMP window postMessage listener. + testIFramedPage('with/JSON response', false, 'encoded_consent_data_via_post_message', 1); + testIFramedPage('with/String response', true, 'encoded_consent_data_via_post_message', 1); + }); + + describe('v2 CMP workflow for iframe pages:', function () { + stringifyResponse = false; + let ifr2 = null; + + beforeEach(function () { + ifr2 = createIFrameMarker('__tcfapiLocator'); + cmpPostMessageCb = creatCmpMessageHandler('__tcfapi', { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded' + }); + window.addEventListener('message', cmpPostMessageCb, false); + }); + + afterEach(function () { + delete window.__tcfapi; // deletes the local copy made by the postMessage CMP call function + document.body.removeChild(ifr2); + window.removeEventListener('message', cmpPostMessageCb); + }); + + testIFramedPage('with/JSON response', false, 'abc12345234', 2); + testIFramedPage('with/String response', true, 'abc12345234', 2); + }); }); - describe('CMP workflow for normal pages:', function () { + describe('direct calls to CMP API tests', function () { let cmpStub = sinon.stub(); beforeEach(function () { didHookReturn = false; sinon.stub(utils, 'logError'); sinon.stub(utils, 'logWarn'); - window.__cmp = function() {}; }); afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); cmpStub.restore(); utils.logError.restore(); utils.logWarn.restore(); - delete window.__cmp; resetConsentData(); }); - it('performs lookup check and stores consentData for a valid existing user', function () { - let testConsentData = { - gdprApplies: true, - consentData: 'BOJy+UqOJy+UqABAB+AAAAAZ+A==' - }; - cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { - args[2](testConsentData); + describe('v1 CMP workflow for normal pages:', function () { + beforeEach(function () { + window.__cmp = function () { }; }); - setConsentConfig(goodConfigWithAllowAuction); + afterEach(function () { + delete window.__cmp; + }); - requestBidsHook(() => { - didHookReturn = true; - }, {}); - let consent = gdprDataHandler.getConsentData(); + it('performs lookup check and stores consentData for a valid existing user', function () { + let testConsentData = { + gdprApplies: true, + consentData: 'BOJy+UqOJy+UqABAB+AAAAAZ+A==' + }; + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); - sinon.assert.notCalled(utils.logWarn); - sinon.assert.notCalled(utils.logError); - expect(didHookReturn).to.be.true; - expect(consent.consentString).to.equal(testConsentData.consentData); - expect(consent.gdprApplies).to.be.true; + setConsentConfig(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.consentData); + expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(1); + }); }); - it('throws an error when processCmpData check failed while config had allowAuction set to false', function () { - let testConsentData = {}; - let bidsBackHandlerReturn = false; + describe('v2 CMP workflow for normal pages:', function () { + beforeEach(function() { + window.__tcfapi = function () { }; + }); - cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { - args[2](testConsentData); + afterEach(function () { + delete window.__tcfapi; }); - setConsentConfig(goodConfigWithCancelAuction); + it('performs lookup check and stores consentData for a valid existing user', function () { + let testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded' + }; + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); - requestBidsHook(() => { - didHookReturn = true; - }, { bidsBackHandler: () => bidsBackHandlerReturn = true }); - let consent = gdprDataHandler.getConsentData(); + setConsentConfig(goodConfigWithAllowAuction); - sinon.assert.calledOnce(utils.logError); - expect(didHookReturn).to.be.false; - expect(bidsBackHandlerReturn).to.be.true; - expect(consent).to.be.null; - }); + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gdprDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal(testConsentData.tcString); + expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(2); + }); - it('throws a warning + stores consentData + calls callback when processCmpData check failed while config had allowAuction set to true', function () { - let testConsentData = {}; + it('performs lookup check and stores consentData for a valid existing user with additional consent', function () { + let testConsentData = { + tcString: 'abc12345234', + addtlConsent: 'superduperstring', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded' + }; + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); - cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { - args[2](testConsentData); + setConsentConfig(goodConfigWithAllowAuction); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gdprDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal(testConsentData.tcString); + expect(consent.addtlConsent).to.equal(testConsentData.addtlConsent); + expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(2); }); - setConsentConfig(goodConfigWithAllowAuction); + it('throws an error when processCmpData check fails + does not call requestBids callbcack even when allowAuction is true', function () { + let testConsentData = {}; + let bidsBackHandlerReturn = false; - requestBidsHook(() => { - didHookReturn = true; - }, {}); - let consent = gdprDataHandler.getConsentData(); + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData); + }); - sinon.assert.calledOnce(utils.logWarn); - expect(didHookReturn).to.be.true; - expect(consent.consentString).to.be.undefined; - expect(consent.gdprApplies).to.be.undefined; + setConsentConfig(goodConfigWithAllowAuction); + + requestBidsHook(() => { + didHookReturn = true; + }, { bidsBackHandler: () => bidsBackHandlerReturn = true }); + let consent = gdprDataHandler.getConsentData(); + + sinon.assert.calledOnce(utils.logError); + sinon.assert.notCalled(utils.logWarn); + expect(didHookReturn).to.be.false; + expect(bidsBackHandlerReturn).to.be.true; + expect(consent).to.be.null; + }); + + it('It still considers it a valid cmp response if gdprApplies is not a boolean', function () { + // gdprApplies is undefined, should just still store consent response but use whatever defaultGdprScope was + let testConsentData = { + tcString: 'abc12345234', + purposeOneTreatment: false, + eventStatus: 'tcloaded' + }; + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig({ + cmpApi: 'iab', + timeout: 7500, + defaultGdprScope: true + }); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gdprDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal(testConsentData.tcString); + expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(2); + }); }); }); }); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index 7785454f8e1..44076194885 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; -import {spec} from 'modules/consumableBidAdapter'; -import {createBid} from 'src/bidfactory'; +import {spec} from 'modules/consumableBidAdapter.js'; +import {createBid} from 'src/bidfactory.js'; const ENDPOINT = 'https://e.serverbid.com/api/v2'; const SMARTSYNC_CALLBACK = 'serverbidCallBids'; diff --git a/test/spec/modules/convergeBidAdapter_spec.js b/test/spec/modules/convergeBidAdapter_spec.js new file mode 100644 index 00000000000..e92ed475497 --- /dev/null +++ b/test/spec/modules/convergeBidAdapter_spec.js @@ -0,0 +1,899 @@ +import { expect } from 'chai'; +import { spec, resetUserSync, getSyncUrl } from 'modules/convergeBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('ConvergeAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'converge', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + function parseRequest(url) { + const res = {}; + url.split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + + const bidderRequest = { + refererInfo: { + referer: 'https://example.com' + } + }; + const referrer = bidderRequest.refererInfo.referer; + + let bidRequests = [ + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90], [300, 250]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '60' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', function () { + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '59'); + expect(payload).to.have.property('sizes', '300x250,300x600'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('wrapperType', 'Prebid_js'); + expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); + }); + + it('sizes must not be duplicated', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '59,59,60'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', function () { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'gross'); + expect(payload).to.have.property('auids', '59,59,60'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', function () { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '59,59,60'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if gdprApplies is false gdpr_applies must be 0', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: false}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '0'); + }); + + it('if gdprApplies is undefined gdpr_applies must be 1', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA'}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if usPrivacy is present payload must have us_privacy param', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('us_privacy', '1YNN'); + }); + + it('should convert keyword params to proper form and attaches to request', function () { + const bidRequestWithKeywords = [].concat(bidRequests); + bidRequestWithKeywords[1] = Object.assign({}, + bidRequests[1], + { + params: { + uid: '59', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [5], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + emptyStr: '', + emptyArr: [''], + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.keywords).to.be.an('string'); + payload.keywords = JSON.parse(payload.keywords); + + expect(payload.keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + }]); + }); + }); + + describe('interpretResponse', function () { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 59, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 60, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 59, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 61, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
test content 5
', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', function () { + const bidRequests = [ + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + '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': 59, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', function () { + const bidRequests = [ + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '60' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + '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': 59, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 60, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
test content 2
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 0.15, + 'creativeId': 59, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 3
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(0, 3)}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', function () { + const bidRequests = [ + { + 'bidder': 'converge', + 'params': { + 'uid': '61' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '65' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '70' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(3)}}, request); + expect(result.length).to.equal(0); + }); + + it('complicated case', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 59, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 60, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 59, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
test content 4
', 'auid': 59, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 5
', 'auid': 60, 'h': 600, 'w': 350}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2164be6358b9', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '326bde7fbf69', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '60' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4e111f1b66e4', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '26d6f897b516', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '60' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '1751cd90161', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '2164be6358b9', + 'cpm': 1.15, + 'creativeId': 59, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4e111f1b66e4', + 'cpm': 0.5, + 'creativeId': 60, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
test content 2
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '26d6f897b516', + 'cpm': 0.15, + 'creativeId': 59, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 3
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '326bde7fbf69', + 'cpm': 0.15, + 'creativeId': 59, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
test content 4
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('dublicate uids and sizes in one slot', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 59, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 59, 'h': 250, 'w': 300}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '5126e301f4be', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57b2ebe70e16', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '59' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '225fcd44b18c', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '5126e301f4be', + 'cpm': 1.15, + 'creativeId': 59, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '57b2ebe70e16', + 'cpm': 0.5, + 'creativeId': 59, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 2
', + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + }); + + it('should get correct video bid response', function () { + const bidRequests = [ + { + 'bidder': 'converge', + 'params': { + 'uid': '58' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57dfefb80eca', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '60' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e893c787c22dd', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 58, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 60, content_type: 'video'}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '57dfefb80eca', + 'cpm': 1.15, + 'creativeId': 58, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should have right renderer in the bid response', function () { + const spySetRenderer = sinon.spy(); + const stubRenderer = { + setRender: spySetRenderer + }; + const spyRendererInstall = sinon.spy(function() { return stubRenderer; }); + const stubRendererConst = { + install: spyRendererInstall + }; + const bidRequests = [ + { + 'bidder': 'converge', + 'params': { + 'uid': '58' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e6e65553fc8', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'mediaTypes': { + 'video': { + 'context': 'outstream' + } + } + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '60' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c8fdcb3f269f', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3' + }, + { + 'bidder': 'converge', + 'params': { + 'uid': '61' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '1de036c37685', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'renderer': {} + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 58, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 60, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, + {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 61, content_type: 'video', w: 300, h: 250}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': 'e6e65553fc8', + 'cpm': 1.15, + 'creativeId': 58, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': 'c8fdcb3f269f', + 'cpm': 1.00, + 'creativeId': 60, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': '1de036c37685', + 'cpm': 1.20, + 'creativeId': 61, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'converge', + 'currency': 'EUR', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request, stubRendererConst); + + expect(spySetRenderer.calledTwice).to.equal(true); + expect(spySetRenderer.getCall(0).args[0]).to.be.a('function'); + expect(spySetRenderer.getCall(1).args[0]).to.be.a('function'); + + expect(spyRendererInstall.calledTwice).to.equal(true); + expect(spyRendererInstall.getCall(0).args[0]).to.deep.equal({ + id: 'e6e65553fc8', + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + loaded: false + }); + expect(spyRendererInstall.getCall(1).args[0]).to.deep.equal({ + id: 'c8fdcb3f269f', + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + loaded: false + }); + + expect(result).to.deep.equal(expectedResponse); + }); + + describe('user sync', function () { + const syncUrl = getSyncUrl(); + + beforeEach(function () { + resetUserSync(); + }); + + it('should register sync image', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + + expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); + }); + + it('should not register sync image more than once', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); + + // when called again, should still have only been called once + syncs = spec.getUserSyncs(); + expect(syncs).to.equal(undefined); + }); + + it('should pass gdpr params if consent is true', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: true, consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr=1&gdpr_consent=foo` + }); + }); + + it('should pass gdpr params if consent is false', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: false, consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr=0&gdpr_consent=foo` + }); + }); + + it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr_consent=foo` + }); + }); + + it('should pass no params if gdpr consentString is not defined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {})).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is a number', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: 0 + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is null', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: null + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is a object', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: {} + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr is not defined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined)).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass usPrivacy param if it is available', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1YNN')).to.deep.equal({ + type: 'image', url: `${syncUrl}&us_privacy=1YNN` + }); + }); + }); +}); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 67dd721a059..d802cd288ef 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; -import {spec} from 'modules/conversantBidAdapter'; -import * as utils from 'src/utils'; +import {spec, storage} from 'modules/conversantBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { createEidsArray } from 'modules/userId/eids.js'; describe('Conversant adapter tests', function() { const siteId = '108060'; @@ -209,7 +210,7 @@ describe('Conversant adapter tests', function() { }; const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://web.hb.ad.cpe.dotomi.com/s2s/header/24'); + expect(request.url).to.equal('https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'); const payload = request.data; expect(payload).to.have.property('id', 'req000'); @@ -320,6 +321,12 @@ describe('Conversant adapter tests', function() { expect(payload).to.not.have.property('user'); // there should be no user by default }); + it('Verify override url', function() { + const testUrl = 'https://someurl?name=value'; + const request = spec.buildRequests([{params: {white_label_url: testUrl}}]); + expect(request.url).to.equal(testUrl); + }); + it('Verify interpretResponse', function() { const request = spec.buildRequests(bidRequests); const response = spec.interpretResponse(bidResponses, request); @@ -395,6 +402,7 @@ describe('Conversant adapter tests', function() { // add pubcid to every entry requests.forEach((unit) => { Object.assign(unit, {userId: {pubcid: 67890}}); + Object.assign(unit, {userIdAsEids: createEidsArray(unit.userId)}); }); // construct http post payload const payload = spec.buildRequests(requests).data; @@ -468,13 +476,14 @@ describe('Conversant adapter tests', function() { // add pubcid to every entry requests.forEach((unit) => { - Object.assign(unit, {userId: {pubcid: 112233, tdid: 223344, idl_env: 334455}}); + Object.assign(unit, {userId: {pubcid: '112233', tdid: '223344', idl_env: '334455'}}); + Object.assign(unit, {userIdAsEids: createEidsArray(unit.userId)}); }); // construct http post payload const payload = spec.buildRequests(requests).data; expect(payload).to.have.deep.nested.property('user.ext.eids', [ - {source: 'adserver.org', uids: [{id: 223344, atype: 1}]}, - {source: 'liveramp.com', uids: [{id: 334455, atype: 1}]} + {source: 'adserver.org', uids: [{id: '223344', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'liveramp.com', uids: [{id: '334455', atype: 1}]} ]); }); }); @@ -505,7 +514,7 @@ describe('Conversant adapter tests', function() { const requests = utils.deepClone(bidRequests); // add a pubcid cookie - utils.setCookie(ID_NAME, '12345', expStr(TIMEOUT)); + storage.setCookie(ID_NAME, '12345', expStr(TIMEOUT)); // construct http post payload const payload = spec.buildRequests(requests).data; @@ -518,7 +527,7 @@ describe('Conversant adapter tests', function() { requests[0].params.pubcid_name = CUSTOM_ID_NAME; // add a pubcid cookie - utils.setCookie(CUSTOM_ID_NAME, '12345', expStr(TIMEOUT)); + storage.setCookie(CUSTOM_ID_NAME, '12345', expStr(TIMEOUT)); // construct http post payload const payload = spec.buildRequests(requests).data; @@ -530,8 +539,8 @@ describe('Conversant adapter tests', function() { const requests = utils.deepClone(bidRequests); // add a pubcid in local storage - utils.setDataInLocalStorage(ID_NAME + EXP, ''); - utils.setDataInLocalStorage(ID_NAME, 'abcde'); + storage.setDataInLocalStorage(ID_NAME + EXP, ''); + storage.setDataInLocalStorage(ID_NAME, 'abcde'); // construct http post payload const payload = spec.buildRequests(requests).data; @@ -543,8 +552,8 @@ describe('Conversant adapter tests', function() { const requests = utils.deepClone(bidRequests); // add a pubcid in local storage - utils.setDataInLocalStorage(ID_NAME + EXP, expStr(TIMEOUT)); - utils.setDataInLocalStorage(ID_NAME, 'fghijk'); + storage.setDataInLocalStorage(ID_NAME + EXP, expStr(TIMEOUT)); + storage.setDataInLocalStorage(ID_NAME, 'fghijk'); // construct http post payload const payload = spec.buildRequests(requests).data; @@ -556,8 +565,8 @@ describe('Conversant adapter tests', function() { const requests = utils.deepClone(bidRequests); // add a pubcid in local storage - utils.setDataInLocalStorage(ID_NAME + EXP, expStr(-TIMEOUT)); - utils.setDataInLocalStorage(ID_NAME, 'lmnopq'); + storage.setDataInLocalStorage(ID_NAME + EXP, expStr(-TIMEOUT)); + storage.setDataInLocalStorage(ID_NAME, 'lmnopq'); // construct http post payload const payload = spec.buildRequests(requests).data; @@ -570,8 +579,8 @@ describe('Conversant adapter tests', function() { requests[0].params.pubcid_name = CUSTOM_ID_NAME; // add a pubcid in local storage - utils.setDataInLocalStorage(CUSTOM_ID_NAME + EXP, expStr(TIMEOUT)); - utils.setDataInLocalStorage(CUSTOM_ID_NAME, 'fghijk'); + storage.setDataInLocalStorage(CUSTOM_ID_NAME + EXP, expStr(TIMEOUT)); + storage.setDataInLocalStorage(CUSTOM_ID_NAME, 'fghijk'); // construct http post payload const payload = spec.buildRequests(requests).data; diff --git a/test/spec/modules/cosmosBidAdapter_spec.js b/test/spec/modules/cosmosBidAdapter_spec.js index 7eb51c596e4..b33f53221e2 100644 --- a/test/spec/modules/cosmosBidAdapter_spec.js +++ b/test/spec/modules/cosmosBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/cosmosBidAdapter'; -import * as utils from 'src/utils'; +import { spec } from 'modules/cosmosBidAdapter.js'; +import * as utils from 'src/utils.js'; const constants = require('src/constants.json'); describe('Cosmos adapter', function () { diff --git a/test/spec/modules/cpmstarBidAdapter_spec.js b/test/spec/modules/cpmstarBidAdapter_spec.js index d080a2570ab..1993f28e5d5 100755 --- a/test/spec/modules/cpmstarBidAdapter_spec.js +++ b/test/spec/modules/cpmstarBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { spec } from 'modules/cpmstarBidAdapter'; -import { deepClone } from 'src/utils'; +import { spec } from 'modules/cpmstarBidAdapter.js'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; describe('Cpmstar Bid Adapter', function () { describe('isBidRequestValid', function () { @@ -15,22 +16,26 @@ describe('Cpmstar Bid Adapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }) - it('should return a valid player size', function() { - var bid = { mediaTypes: { - video: { - playerSize: [[960, 540]] + it('should return a valid player size', function () { + var bid = { + mediaTypes: { + video: { + playerSize: [[960, 540]] + } } - }} + } expect(spec.getPlayerSize(bid)[0]).to.equal(960); expect(spec.getPlayerSize(bid)[1]).to.equal(540); }) - it('should return a default player size', function() { - var bid = { mediaTypes: { - video: { - playerSize: null + it('should return a default player size', function () { + var bid = { + mediaTypes: { + video: { + playerSize: null + } } - }} + } expect(spec.getPlayerSize(bid)[0]).to.equal(640); expect(spec.getPlayerSize(bid)[1]).to.equal(440); }) @@ -79,6 +84,31 @@ describe('Cpmstar Bid Adapter', function () { expect(requests[0]).to.have.property('bidRequest'); expect(requests[0].url).to.include('https://dev.server.cpmstar.com/view.aspx'); }); + it('should produce a request with support for GDPR', function () { + var gdpr_bidderRequest = deepClone(bidderRequest); + gdpr_bidderRequest.gdprConsent = { + consentString: 'consentString', + gdprApplies: true + }; + var requests = spec.buildRequests(valid_bid_requests, gdpr_bidderRequest); + expect(requests[0]).to.have.property('url'); + expect(requests[0].url).to.include('gdpr_consent=consentString'); + expect(requests[0].url).to.include('gdpr=1'); + }); + it('should produce a request with support for USP', function () { + var usp_bidderRequest = deepClone(bidderRequest); + usp_bidderRequest.uspConsent = '1YYY'; + var requests = spec.buildRequests(valid_bid_requests, usp_bidderRequest); + expect(requests[0]).to.have.property('url'); + expect(requests[0].url).to.include('us_privacy=1YYY'); + }); + it('should produce a request with support for COPPA', function () { + sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); + var requests = spec.buildRequests(valid_bid_requests, bidderRequest); + config.getConfig.restore(); + expect(requests[0]).to.have.property('url'); + expect(requests[0].url).to.include('tfcd=1'); + }); }) describe('interpretResponse', function () { diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js new file mode 100644 index 00000000000..ef7dd7c3232 --- /dev/null +++ b/test/spec/modules/craftBidAdapter_spec.js @@ -0,0 +1,152 @@ +import {expect} from 'chai'; +import {spec} from 'modules/craftBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from 'src/config.js'; + +describe('craftAdapter', function () { + let adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + before(function() { + this.windowContext = window.context; + window.context = null; + }); + + after(function() { + window.context = this.windowContext; + }); + let bid = { + bidder: 'craft', + params: { + sitekey: 'craft-prebid-example', + placementId: '1234abcd' + }, + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when params.sitekey not found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + placementId: '1234abcd' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when params.placementId not found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + sitekey: 'craft-prebid-example' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when AMP cotext found', function () { + window.context = { + pageViewId: 'xxx' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [{ + bidder: 'craft', + params: { + 'sitekey': 'craft-prebid-example', + 'placementId': '1234abcd' + }, + adUnitCode: '/21998384947/prebid-example', + sizes: [[300, 250]], + bidId: '0396fae4eb5f47', + bidderRequestId: '4a859978b5d4bd', + auctionId: '8720f980-4639-4150-923a-e96da2f1de36', + transactionId: 'e0c52da2-c008-491c-a910-c6765d948700', + }]; + let bidderRequest = { + refererInfo: { + referer: 'https://www.gacraft.jp/publish/craft-prebid-example.html' + } + }; + it('sends bid request to ENDPOINT via POST', function () { + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://gacraft.jp/prebid-v3'); + let data = JSON.parse(request.data); + expect(data.tags).to.deep.equals([{ + sitekey: 'craft-prebid-example', + placementId: '1234abcd', + uid: null, + sizes: [[300, 250]], + primary_size: [300, 250], + uuid: '0396fae4eb5f47' + }]); + expect(data.referrer_detection).to.deep.equals({ + rd_ref: 'https://www.gacraft.jp/publish/craft-prebid-example.html' + }); + }); + }); + + describe('interpretResponse', function() { + let serverResponse = { + body: { + tags: [{ + uuid: '0396fae4eb5f47', + bid_key: '72038482-c4c3-4055-9e7e-0579585bb421', + won_url: 'https://www.gacraft.jp/publish/won', + ads: [{ + content_source: 'rtb', + ad_type: 'banner', + creative_id: 9999, + cpm: 10.01, + deal_id: '8DEF60EFDFB5', + rtb: { + banner: { + content: '', + width: 300, + height: 250 + }, + } + }] + }], + } + }; + let bidderRequest = { + bids: [{ + bidId: '0396fae4eb5f47', + adUnitCode: 'craft-prebid-example' + }] + }; + it('should get correct bid response', function() { + let bids = spec.interpretResponse(serverResponse, {bidderRequest: bidderRequest}); + expect(bids).to.have.lengthOf(1); + expect(bids[0]).to.deep.equals({ + _adUnitCode: 'craft-prebid-example', + _bidKey: '72038482-c4c3-4055-9e7e-0579585bb421', + _prebidWon: 'https://www.gacraft.jp/publish/won', + ad: '', + cpm: 10.01, + creativeId: 9999, + currency: 'JPY', + dealId: '8DEF60EFDFB5', + height: 250, + mediaType: 'banner', + meta: null, + netRevenue: false, + requestId: '0396fae4eb5f47', + ttl: 360, + width: 300, + }); + }); + }); +}); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 9ba1c38ab73..c5068d11d31 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1,23 +1,26 @@ import { expect } from 'chai'; -import { tryGetCriteoFastBid, spec, PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, PUBLISHER_TAG_URL } from 'modules/criteoBidAdapter'; -import { createBid } from 'src/bidfactory'; +import { tryGetCriteoFastBid, spec, PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION } from 'modules/criteoBidAdapter.js'; +import { createBid } from 'src/bidfactory.js'; import CONSTANTS from 'src/constants.json'; -import * as utils from 'src/utils'; -import { config } from '../../../src/config'; -import { VIDEO } from '../../../src/mediaTypes'; +import * as utils from 'src/utils.js'; +import { config } from '../../../src/config.js'; +import { NATIVE, VIDEO } from '../../../src/mediaTypes.js'; describe('The Criteo bidding adapter', function () { - let utilsMock; + let utilsMock, sandbox; beforeEach(function () { // Remove FastBid to avoid side effects localStorage.removeItem('criteo_fast_bid'); utilsMock = sinon.mock(utils); + + sandbox = sinon.sandbox.create(); }); afterEach(function() { global.Criteo = undefined; utilsMock.restore(); + sandbox.restore(); }); describe('isBidRequestValid', function () { @@ -382,6 +385,7 @@ describe('The Criteo bidding adapter', function () { '91': 1 }, }, + apiVersion: 1, }, }; @@ -433,7 +437,63 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].zoneid).to.equal(123); expect(ortbRequest.gdprConsent.consentData).to.equal('concentDataString'); expect(ortbRequest.gdprConsent.gdprApplies).to.equal(true); - expect(ortbRequest.gdprConsent.consentGiven).to.equal(true); + expect(ortbRequest.gdprConsent.version).to.equal(1); + }); + + it('should keep undefined sizes for non native banner', function () { + const bidRequests = [ + { + sizes: [[undefined, undefined]], + params: {}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); + expect(ortbRequest.slots[0].sizes[0]).to.equal('undefinedxundefined'); + }); + + it('should keep undefined size for non native banner', function () { + const bidRequests = [ + { + sizes: [undefined, undefined], + params: {}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); + expect(ortbRequest.slots[0].sizes[0]).to.equal('undefinedxundefined'); + }); + + it('should properly detect and get sizes of native sizeless banner', function () { + const bidRequests = [ + { + sizes: [[undefined, undefined]], + params: { + nativeCallback: function() {} + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); + expect(ortbRequest.slots[0].sizes[0]).to.equal('2x2'); + }); + + it('should properly detect and get size of native sizeless banner', function () { + const bidRequests = [ + { + sizes: [undefined, undefined], + params: { + nativeCallback: function() {} + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); + expect(ortbRequest.slots[0].sizes[0]).to.equal('2x2'); }); it('should properly build a networkId request', function () { @@ -481,7 +541,6 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].sizes[1]).to.equal('728x90'); expect(ortbRequest.gdprConsent.consentData).to.equal(undefined); expect(ortbRequest.gdprConsent.gdprApplies).to.equal(false); - expect(ortbRequest.gdprConsent.consentGiven).to.equal(undefined); }); it('should properly build a mixed request', function () { @@ -530,7 +589,7 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.gdprConsent).to.equal(undefined); }); - it('should properly build request with undefined gdpr consent fields when they are not provided', function () { + it('should properly build a request with undefined gdpr consent fields when they are not provided', function () { const bidRequests = [ { bidder: 'criteo', @@ -550,7 +609,6 @@ describe('The Criteo bidding adapter', function () { const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; expect(ortbRequest.gdprConsent.consentData).to.equal(undefined); expect(ortbRequest.gdprConsent.gdprApplies).to.equal(undefined); - expect(ortbRequest.gdprConsent.consentGiven).to.equal(undefined); }); it('should properly build a request with ccpa consent field', function () { @@ -705,6 +763,115 @@ describe('The Criteo bidding adapter', function () { expect(request.data.user).to.not.be.null; expect(request.data.user.ceh).to.equal('hashedemail'); }); + + it('should properly build a request without first party data', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123 + } + }, + ]; + + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + }; + return utils.deepAccess(config, key); + }); + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.publisher.ext).to.equal(undefined); + expect(request.data.user.ext).to.equal(undefined); + expect(request.data.slots[0].ext).to.equal(undefined); + }); + + it('should properly build a request with criteo specific ad unit first party data', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + } + }, + ]; + + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + }; + return utils.deepAccess(config, key); + }); + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].ext).to.deep.equal({ + bidfloor: 0.75, + }); + }); + + it('should properly build a request with first party data', function () { + const contextData = { + keywords: ['power tools'], + data: { + pageType: 'article' + } + }; + const userData = { + gender: 'M', + data: { + registered: true + } + }; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + }, + fpd: { + context: { + data: { + someContextAttribute: 'abc' + } + } + } + }, + ]; + + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + fpd: { + context: contextData, + user: userData + } + }; + return utils.deepAccess(config, key); + }); + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.publisher.ext).to.deep.equal(contextData); + expect(request.data.user.ext).to.deep.equal(userData); + expect(request.data.slots[0].ext).to.deep.equal({ + bidfloor: 0.75, + data: { + someContextAttribute: 'abc' + } + }); + }); }); describe('interpretResponse', function () { @@ -747,7 +914,7 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].dealId).to.equal('myDealCode'); }); - it('should properly parse a bid responsewith with a zoneId', function () { + it('should properly parse a bid response with a zoneId', function () { const response = { body: { slots: [{ @@ -780,7 +947,7 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].height).to.equal(90); }); - it('should properly parse a bid responsewith with a video', function () { + it('should properly parse a bid response with a video', function () { const response = { body: { slots: [{ @@ -813,7 +980,120 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].mediaType).to.equal(VIDEO); }); - it('should properly parse a bid responsewith with a zoneId passed as a string', function () { + it('should properly parse a bid response with native', function () { + const response = { + body: { + slots: [{ + impid: 'test-requestId', + bidId: 'abc123', + cpm: 1.23, + width: 728, + height: 90, + zoneid: 123, + native: { + 'products': [{ + 'title': 'Product title', + 'description': 'Product desc', + 'price': '100', + 'click_url': 'https://product.click', + 'image': { + 'url': 'https://publisherdirect.criteo.com/publishertag/preprodtest/creative.png', + 'height': 300, + 'width': 300 + }, + 'call_to_action': 'Try it now!' + }], + 'advertiser': { + 'description': 'sponsor', + 'domain': 'criteo.com', + 'logo': {'url': 'https://www.criteo.com/images/criteo-logo.svg', 'height': 300, 'width': 300} + }, + 'privacy': { + 'optout_click_url': 'https://info.criteo.com/privacy/informations', + 'optout_image_url': 'https://static.criteo.net/flash/icon/nai_small.png', + }, + 'impression_pixels': [{'url': 'https://my-impression-pixel/test/impression'}, {'url': 'https://cas.com/lg.com'}] + } + }], + }, + }; + const request = { + bidRequests: [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + zoneId: 123, + }, + native: true, + }] + }; + config.setConfig({'enableSendAllBids': false}); + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].adId).to.equal('abc123'); + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].mediaType).to.equal(NATIVE); + }); + + it('should not parse bid response with native when enableSendAllBids is true', function () { + const response = { + body: { + slots: [{ + impid: 'test-requestId', + bidId: 'abc123', + cpm: 1.23, + width: 728, + height: 90, + zoneid: 123, + native: {} + }], + }, + }; + const request = { + bidRequests: [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + zoneId: 123, + }, + native: true, + }] + }; + config.setConfig({'enableSendAllBids': true}); + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should not parse bid response with native when enableSendAllBids is not set', function () { + const response = { + body: { + slots: [{ + impid: 'test-requestId', + bidId: 'abc123', + cpm: 1.23, + width: 728, + height: 90, + zoneid: 123, + native: {} + }], + }, + }; + const request = { + bidRequests: [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + zoneId: 123, + }, + native: true, + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a bid response with a zoneId passed as a string', function () { const response = { body: { slots: [{ @@ -893,7 +1173,6 @@ describe('The Criteo bidding adapter', function () { utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').once(); utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').never(); - utilsMock.expects('insertElement').once(); tryGetCriteoFastBid(); @@ -941,6 +1220,132 @@ describe('The Criteo bidding adapter', function () { }); }); + describe('when pubtag prebid adapter is not available', function () { + it('should not warn if sendId is provided on required fields for native bidRequest', () => { + const bidderRequest = { }; + const bidRequestsWithSendId = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + publisherSubId: '123', + nativeCallback: function() {} + }, + nativeParams: { + image: { + sendId: true + }, + icon: { + sendId: true + }, + clickUrl: { + sendId: true + }, + displayUrl: { + sendId: true + }, + privacyLink: { + sendId: true + }, + privacyIcon: { + sendId: true + } + } + } + ]; + + utilsMock.expects('logWarn').withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)').never(); + const request = spec.buildRequests(bidRequestsWithSendId, bidderRequest); + utilsMock.verify(); + }); + + it('should warn only once if sendId is not provided on required fields for native bidRequest', () => { + const bidderRequest = { }; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + publisherSubId: '123', + nativeCallback: function() {} + }, + }, + { + bidder: 'criteo', + adUnitCode: 'bid-456', + transactionId: 'transaction-456', + sizes: [[728, 90]], + params: { + zoneId: 456, + publisherSubId: '456', + nativeCallback: function() {} + }, + }, + ]; + + const nativeParamsWithoutSendId = [ + { + nativeParams: { + image: { + sendId: false + }, + } + }, + { + nativeParams: { + icon: { + sendId: false + }, + } + }, + { + nativeParams: { + clickUrl: { + sendId: false + }, + } + }, + { + nativeParams: { + displayUrl: { + sendId: false + }, + } + }, + { + nativeParams: { + privacyLink: { + sendId: false + }, + } + }, + { + nativeParams: { + privacyIcon: { + sendId: false + }, + } + } + ]; + + utilsMock.expects('logWarn') + .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') + .exactly(nativeParamsWithoutSendId.length * bidRequests.length); + nativeParamsWithoutSendId.forEach(nativeParams => { + let transformedBidRequests = {...bidRequests}; + transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; + spec.buildRequests(transformedBidRequests, bidderRequest); + }); + utilsMock.verify(); + }); + }); + describe('when pubtag prebid adapter is available', function () { it('should forward response to pubtag when calling interpretResponse', () => { const response = {}; @@ -1013,14 +1418,14 @@ describe('The Criteo bidding adapter', function () { }); it('should forward bid to pubtag when calling onTimeout', () => { - const timeoutData = { auctionId: 123 }; + const timeoutData = [{ auctionId: 123 }]; const adapter = { handleBidTimeout: function() {} }; const adapterMock = sinon.mock(adapter); adapterMock.expects('handleBidTimeout').once(); const prebidAdapter = { GetAdapter: function() {} }; const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(timeoutData.auctionId).once().returns(adapter); + prebidAdapterMock.expects('GetAdapter').withExactArgs(timeoutData[0].auctionId).once().returns(adapter); global.Criteo = { PubTag: { diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 44decfd56b6..aa5807da0da 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -1,7 +1,6 @@ -import { criteoIdSubmodule } from 'modules/criteoIdSystem'; -import * as utils from 'src/utils'; -import * as ajaxLib from 'src/ajax'; -import * as urlLib from 'src/url'; +import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; +import * as utils from 'src/utils.js'; +import * as ajaxLib from 'src/ajax.js'; const pastDateString = new Date(0).toString() @@ -27,14 +26,14 @@ describe('CriteoId module', function () { let triggerPixelStub; beforeEach(function (done) { - getCookieStub = sinon.stub(utils, 'getCookie'); - setCookieStub = sinon.stub(utils, 'setCookie'); - getLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage'); - setLocalStorageStub = sinon.stub(utils, 'setDataInLocalStorage'); - removeFromLocalStorageStub = sinon.stub(utils, 'removeDataFromLocalStorage'); + getCookieStub = sinon.stub(storage, 'getCookie'); + setCookieStub = sinon.stub(storage, 'setCookie'); + getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); + removeFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage'); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockResponse('{}')); - parseUrlStub = sinon.stub(urlLib, 'parse').returns({protocol: 'https', hostname: 'testdev.com'}) + parseUrlStub = sinon.stub(utils, 'parseUrl').returns({protocol: 'https', hostname: 'testdev.com'}) triggerPixelStub = sinon.stub(utils, 'triggerPixel'); done(); }); @@ -132,4 +131,26 @@ describe('CriteoId module', function () { } }); })); + + const gdprConsentTestCases = [ + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: 'expectedConsentString' }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: undefined }, + { consentData: { gdprApplies: true, consentString: undefined }, expected: undefined }, + { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expected: undefined }, + { consentData: undefined, expected: undefined } + ]; + + gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () { + const emptyObj = '{}'; + let ajaxStub = sinon.stub().callsFake((url, callback) => callback(emptyObj)); + ajaxBuilderStub.callsFake(mockResponse(undefined, ajaxStub)) + + criteoIdSubmodule.getId(undefined, testCase.consentData); + + if (testCase.expected) { + expect(ajaxStub.calledWithMatch(`gdprString=${testCase.expected}`)).to.be.true; + } else { + expect(ajaxStub.calledWithMatch('gdprString')).not.to.be.true; + } + })); }); diff --git a/test/spec/modules/criteortusIdSystem_spec.js b/test/spec/modules/criteortusIdSystem_spec.js deleted file mode 100644 index 217a2f86ba7..00000000000 --- a/test/spec/modules/criteortusIdSystem_spec.js +++ /dev/null @@ -1,90 +0,0 @@ -import { criteortusIdSubmodule } from 'modules/criteortusIdSystem'; -import * as utils from 'src/utils'; - -describe('Criteo RTUS', function() { - let xhr; - let requests; - let getCookieStub; - let logErrorStub; - - beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - getCookieStub = sinon.stub(utils, 'getCookie'); - logErrorStub = sinon.stub(utils, 'logError'); - }); - - afterEach(function () { - xhr.restore(); - getCookieStub.restore(); - logErrorStub.restore(); - }); - - it('should log error when configParams are not passed', function() { - criteortusIdSubmodule.getId(); - expect(logErrorStub.calledOnce).to.be.true; - }) - - it('should call criteo endpoint to get user id', function() { - getCookieStub.returns(null); - let configParams = { - clientIdentifier: { - 'sampleBidder': 1 - } - } - - let response = { 'status': 'ok', 'userid': 'sample-userid' } - let callBackSpy = sinon.spy(); - const idResp = criteortusIdSubmodule.getId(configParams); - const submoduleCallback = idResp.callback; - submoduleCallback(callBackSpy); - requests[0].respond( - 200, - { 'Content-Type': 'text/plain' }, - JSON.stringify(response) - ); - expect(callBackSpy.calledOnce).to.be.true; - expect(callBackSpy.calledWith({'sampleBidder': response})).to.be.true; - }) - - it('should get uid from cookie and not call endpoint', function() { - let response = {'appnexus': {'status': 'ok', 'userid': 'sample-userid'}} - getCookieStub.returns(JSON.stringify(response)); - let configParams = { - clientIdentifier: { - 'sampleBidder': 1 - } - } - let uid = criteortusIdSubmodule.getId(configParams); - expect(requests.length).to.equal(0); - }) - - it('should call criteo endpoint for multiple bidders', function() { - getCookieStub.returns(null); - let configParams = { - clientIdentifier: { - 'sampleBidder': 1, - 'sampleBidder2': 2 - } - } - - let response = { 'status': 'ok', 'userid': 'sample-userid' } - let callBackSpy = sinon.spy(); - const idResp = criteortusIdSubmodule.getId(configParams); - const submoduleCallback = idResp.callback; - submoduleCallback(callBackSpy); - requests[0].respond( - 200, - { 'Content-Type': 'text/plain' }, - JSON.stringify(response) - ); - expect(callBackSpy.calledOnce).to.be.false; - requests[1].respond( - 200, - { 'Content-Type': 'text/plain' }, - JSON.stringify(response) - ); - expect(callBackSpy.calledOnce).to.be.true; - }) -}); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index 98f51b72251..ccd205964a9 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -1,14 +1,16 @@ import { getCurrencyRates -} from 'test/fixtures/fixtures'; +} from 'test/fixtures/fixtures.js'; + +import { getGlobal } from 'src/prebidGlobal.js'; import { setConfig, addBidResponseHook, currencySupportEnabled, currencyRates -} from 'modules/currency'; +} from 'modules/currency.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -43,8 +45,12 @@ describe('currency', function () { it('results in currencySupportEnabled = false when currency not configured', function () { setConfig({}); expect(currencySupportEnabled).to.equal(false); + expect(getGlobal().convertCurrency).to.be.undefined; + }); + it('adds conversion function onto pbjs global var once initiated', function () { + setConfig({ 'adServerCurrency': 'JPY' }); + expect(getGlobal().convertCurrency).to.be.a('function'); }); - it('results in currencySupportEnabled = true and currencyRates being loaded when configured', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'JPY' }); @@ -124,6 +130,38 @@ describe('currency', function () { }); }); + describe('global currency function', function () { + it('still returns conversion if default rates present with fetch not returned yet', function () { + setConfig({ + 'adServerCurrency': 'USD', + 'defaultRates': { + 'USD': { EUR: 2, JPY: 100 } + } + }); + // first conversion should use default rates + expect(getGlobal().convertCurrency(1.0, 'USD', 'EUR')).to.equal(2); + expect(getGlobal().convertCurrency(1.0, 'USD', 'JPY')).to.equal(100); + fakeCurrencyFileServer.respond(); + }); + it('uses fetch rates if returned', function () { + fakeCurrencyFileServer.respondWith(JSON.stringify({ + 'dataAsOf': '2017-04-25', + 'conversions': { + 'USD': { EUR: 4, JPY: 200 } + } + })); + setConfig({ + 'adServerCurrency': 'USD', + 'defaultRates': { + 'USD': { EUR: 2, JPY: 100 } + } + }); + // now respond and should use updates rates + fakeCurrencyFileServer.respond(); + expect(getGlobal().convertCurrency(1.0, 'USD', 'EUR')).to.equal(4); + expect(getGlobal().convertCurrency(1.0, 'USD', 'JPY')).to.equal(200); + }); + }); describe('bidder override', function () { it('allows setConfig to set bidder currency', function () { setConfig({}); diff --git a/test/spec/modules/dailyhuntBidAdapter_spec.js b/test/spec/modules/dailyhuntBidAdapter_spec.js index 617c1be04bb..d571150dbee 100644 --- a/test/spec/modules/dailyhuntBidAdapter_spec.js +++ b/test/spec/modules/dailyhuntBidAdapter_spec.js @@ -1,9 +1,8 @@ import { expect } from 'chai'; -import { spec } from 'modules/dailyhuntBidAdapter'; +import { spec } from 'modules/dailyhuntBidAdapter.js'; -const PROD_PREBID_ENDPOINT_URL = 'https://money.dailyhunt.in/openrtb2/auction'; - -const PROD_ENDPOINT_URL = 'https://money.dailyhunt.in/openx/ads/index.php'; +const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner=dailyhunt'; +const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner=dailyhunt'; const _encodeURIComponent = function (a) { if (!a) { return } @@ -17,7 +16,9 @@ describe('DailyhuntAdapter', function () { let bid = { 'bidder': 'dailyhunt', 'params': { - partnerId: 'pb-partnerId' + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt' } }; @@ -32,20 +33,92 @@ describe('DailyhuntAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - - describe('buildRequests', function () { + describe('buildRequests', function() { let bidRequests = [ { - 'bidder': 'dailyhunt', - 'params': { - 'placementId': '10433394' + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + bidfloor: 0.1, + device: { + ip: '47.9.247.217' + }, + site: { + cat: ['1', '2', '3'] + } }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 50]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + let nativeBidRequests = [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + }, + nativeParams: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + }, + mediaTypes: { + native: { + title: { + required: true + }, + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + let videoBidRequests = [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt' + }, + nativeParams: { + video: { + context: 'instream' + } + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' } ]; let bidderRequest = { @@ -61,29 +134,6 @@ describe('DailyhuntAdapter', function () { 'referer': 'http://m.dailyhunt.in/' } }; - - let nativeBidRequests = [ - { - 'bidder': 'dailyhunt', - 'params': { - 'placementId': '10433394' - }, - nativeParams: { - image: { - required: true, - }, - title: { - required: true, - }, - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 50]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' - } - ]; let nativeBidderRequest = { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -97,6 +147,19 @@ describe('DailyhuntAdapter', function () { 'referer': 'http://m.dailyhunt.in/' } }; + let videoBidderRequest = { + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'bidderCode': 'dailyhunt', + 'bids': [ + { + ...videoBidRequests[0] + } + ], + 'refererInfo': { + 'referer': 'http://m.dailyhunt.in/' + } + }; it('sends display bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; @@ -104,186 +167,234 @@ describe('DailyhuntAdapter', function () { expect(request.method).to.equal('POST'); }); - it('sends native bid request to ENDPOINT via GET', function () { + it('sends native bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(nativeBidRequests, nativeBidderRequest)[0]; - expect(request.url).to.equal(PROD_ENDPOINT_URL); - expect(request.method).to.equal('GET'); + expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); + expect(request.method).to.equal('POST'); }); - }) + it('sends video bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(videoBidRequests, videoBidderRequest)[0]; + expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); + expect(request.method).to.equal('POST'); + }); + }); describe('interpretResponse', function () { - let nativeResponse = [ - [ + let bidResponses = { + id: 'da32def7-6779-403c-ada7-0b201dbc9744', + seatbid: [ { - 'ad': { - 'requestId': '12345', - 'type': 'native-banner', - 'aduid': 'notId=ad-123679', - 'srcUrlExpiry': '60', - 'position': 'web', - 'width': 300, - 'height': 250, - 'card-position': 6, - 'positionWithTicker': 6, - 'min-ad-distance': 9, - 'banner-fill': 'fill-width', - 'bannerid': '70459', - 'adTemplate': 'H', - 'showOnlyImage': 'false', - 'id': '4210005ddbed0f096078.05613283', - 'span': 60, - 'useInternalBrowser': 'false', - 'useWideViewPort': 'true', - 'adCacheGood': '1', - 'adCacheAverage': '1', - 'adCacheSlow': '1', - 'allowDelayedAdInsert': 'true', - 'adGroupId': '4210005ddbed0f0982f4.86287578', - 'adContext': null, - 'showPlayIcon': 'false', - 'showBorder': 'false', - 'adid': '123679', - 'typeId': '3', - 'subTypeId': '0', - 'orderId': '1', - 'data-subSlot': 'web-3', - 'data-partner': 'DH', - 'data-origin': 'pbs', - 'pbAdU': '0', - 'price': '1.4', - 'beaconUrl': 'beacon-url', - 'landingUrl': null, - 'content': { - 'bg-color': '#ffffff', - 'bg-color-night': '#000000', - 'language': 'en', - 'sourceAlphabet': 'A', - 'iconLink': 'icon-link', - 'itemTag': { - 'color': '#0889ac', - 'color-night': '#a5a5a5', - 'data': 'Promoted' - }, - 'itemTitle': { - 'color': '#000000', - 'color-night': '#ffffff', - 'data': 'PREBID TEST' - }, - 'itemSubtitle1': { - 'color': '#000000', - 'color-night': '#ffffff', - 'data': 'Lorem Ipsum lorem ipsum' - }, - 'itemSubtitle2': { - 'color': '#4caf79', - 'color-night': '#ffffff', - 'data': 'CLICK ME' + bid: [ + { + id: 'id1', + impid: 'banner-impid', + price: 1.4, + adm: 'adm', + adid: '66658', + crid: 'asd5ddbf014cac993.66466212', + dealid: 'asd5ddbf014cac993.66466212', + w: 300, + h: 250, + nurl: 'winUrl', + ext: { + prebid: { + type: 'banner' + } } }, - 'action': 'action-url', - 'shareability': null - } - } - ] - ]; - - let bannerResponse = { - 'id': 'da32def7-6779-403c-ada7-0b201dbc9744', - 'seatbid': [ - { - 'bid': [ { - 'id': '3db3773286ee59', - 'impid': '1', - 'price': 0.14, - 'adm': "\r\n\r\n\r\n\t\r\n\tWidgets Magazine\r\n\t\r\n\t\r\n\t\r\n\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n
\r\n\r\n
\r\n\r\n\r\n", - 'adid': '66658', - 'crid': 'asd5ddbf014cac993.66466212', - 'dealid': 'asd5ddbf014cac993.66466212', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' + id: '5caccc1f-94a6-4230-a1f9-6186ee65da99', + impid: 'video-impid', + price: 1.4, + nurl: 'winUrl', + adm: 'adm', + adid: '980', + crid: '2394', + w: 300, + h: 250, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + cacheKey: 'cache_key', + vastUrl: 'vastUrl' } } - } + }, + { + id: '74973faf-cce7-4eff-abd0-b59b8e91ca87', + impid: 'native-impid', + price: 50, + nurl: 'winUrl', + adm: '{"native":{"link":{"url":"url","clicktrackers":[]},"assets":[{"id":1,"required":1,"img":{},"video":{},"data":{},"title":{"text":"TITLE"},"link":{}},{"id":1,"required":1,"img":{},"video":{},"data":{"type":2,"value":"Lorem Ipsum Lorem Ipsum Lorem Ipsum."},"title":{},"link":{}},{"id":1,"required":1,"img":{},"video":{},"data":{"type":12,"value":"Install Here"},"title":{},"link":{}},{"id":1,"required":1,"img":{"type":3,"url":"urk","w":990,"h":505},"video":{},"data":{},"title":{},"link":{}}],"imptrackers":[]}}', + adid: '968', + crid: '2370', + w: 300, + h: 250, + ext: { + prebid: { + type: 'native' + }, + bidder: null + } + }, + { + id: '5caccc1f-94a6-4230-a1f9-6186ee65da99', + impid: 'video-outstream-impid', + price: 1.4, + nurl: 'winUrl', + adm: 'adm', + adid: '980', + crid: '2394', + w: 300, + h: 250, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + cacheKey: 'cache_key', + vastUrl: 'vastUrl' + } + } + }, ], - 'seat': 'dailyhunt' + seat: 'dailyhunt' } ], - 'ext': { - 'responsetimemillis': { - 'dailyhunt': 119 + ext: { + responsetimemillis: { + dailyhunt: 119 } } }; - it('should get correct native bid response', function () { + it('should get correct bid response', function () { let expectedResponse = [ { - requestId: '12345', - cpm: '10', - creativeId: '70459', + requestId: '1', + cpm: 1.4, + creativeId: 'asd5ddbf014cac993.66466212', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + ad: 'adm', + mediaType: 'banner', + winUrl: 'winUrl' + }, + { + requestId: '2', + cpm: 1.4, + creativeId: '2394', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, currency: 'USD', + mediaType: 'video', + winUrl: 'winUrl', + videoCacheKey: 'cache_key', + vastUrl: 'vastUrl', + }, + { + requestId: '3', + cpm: 1.4, + creativeId: '2370', + width: 300, + height: 250, ttl: 360, netRevenue: true, + currency: 'USD', mediaType: 'native', + winUrl: 'winUrl', native: { - title: 'PREBID TEST', - body: 'Lorem Ipsum lorem ipsum', - body2: 'Lorem Ipsum lorem ipsum', - cta: 'CLICK ME', - clickUrl: _encodeURIComponent('action-url'), - impressionTrackers: [], + clickUrl: 'https%3A%2F%2Fmontu1996.github.io%2F', clickTrackers: [], + impressionTrackers: [], + javascriptTrackers: [], + title: 'TITLE', + body: 'Lorem Ipsum Lorem Ipsum Lorem Ipsum.', + cta: 'Install Here', image: { - url: 'icon-link', - height: 250, - width: 300 - }, - icon: { - url: 'icon-link', - height: 300, - width: 250 + url: 'url', + height: 505, + width: 990 } } - } - ]; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - 'requestId': '12345' - }] - } - let result = spec.interpretResponse({ body: nativeResponse }, { bidderRequest }); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); - - it('should get correct banner bid response', function () { - let expectedResponse = [ + }, { - requestId: '12345', - cpm: 0.14, - creativeId: 'asd5ddbf014cac993.66466212', + requestId: '4', + cpm: 1.4, + creativeId: '2394', width: 300, height: 250, ttl: 360, netRevenue: true, currency: 'USD', - ad: "\r\n\r\n\r\n\t\r\n\tWidgets Magazine\r\n\t\r\n\t\r\n\t\r\n\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n
\r\n\r\n
\r\n\r\n\r\n", - } + mediaType: 'video', + winUrl: 'winUrl', + vastXml: 'adm', + }, ]; let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - 'requestId': '12345' - }] + bids: [ + { + bidId: 'banner-impid', + adUnitCode: 'code1', + requestId: '1' + }, + { + bidId: 'video-impid', + adUnitCode: 'code2', + requestId: '2', + mediaTypes: { + video: { + context: 'instream' + } + } + }, + { + bidId: 'native-impid', + adUnitCode: 'code3', + requestId: '3' + }, + { + bidId: 'video-outstream-impid', + adUnitCode: 'code4', + requestId: '4', + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + ] } - let result = spec.interpretResponse({ body: bannerResponse }, bidderRequest); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + let result = spec.interpretResponse({ body: bidResponses }, bidderRequest); + result.forEach((r, i) => { + expect(Object.keys(r)).to.have.members(Object.keys(expectedResponse[i])); + }); + }); + }) + describe('onBidWon', function () { + it('should hit win url when bid won', function () { + let bid = { + requestId: '1', + cpm: 1.4, + creativeId: 'asd5ddbf014cac993.66466212', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + ad: 'adm', + mediaType: 'banner', + winUrl: 'winUrl' + }; + expect(spec.onBidWon(bid)).to.equal(undefined); }); }) }) diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index af0fd8a3bc9..18b8aac7371 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/datablocksBidAdapter'; +import { spec } from '../../../modules/datablocksBidAdapter.js'; let bid = { bidId: '2dd581a2b6281d', diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index f4adad7faf2..6d9b883e2bb 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; -import {spec} from 'modules/deepintentBidAdapter'; -import * as utils from '../../../src/utils'; +import {spec} from 'modules/deepintentBidAdapter.js'; +import * as utils from '../../../src/utils.js'; describe('Deepintent adapter', function () { let request; @@ -19,6 +19,7 @@ describe('Deepintent adapter', function () { tagId: '100013', w: 728, h: 90, + pos: 1, user: { id: 'di_testuid', buyeruid: 'di_testbuyeruid', @@ -129,6 +130,11 @@ describe('Deepintent adapter', function () { expect(data.imp[0].ext).to.be.a('object'); expect(data.imp[0].ext.deepintent.position).to.equal('right-box'); }); + it('bid request check: position check', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].banner.pos).to.equal(1); + }); it('bid request check: displaymanager check', function() { let bRequest = spec.buildRequests(request); let data = JSON.parse(bRequest.data); @@ -143,7 +149,36 @@ describe('Deepintent adapter', function () { expect(data.user.buyeruid).to.equal('di_testbuyeruid'); expect(data.user.yob).to.equal(2002); expect(data.user.gender).to.equal('F'); - }) + }); + it('bid request check: CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let bRequest = spec.buildRequests(request, bidRequest); + let data = JSON.parse(bRequest.data); + expect(data.regs.ext.us_privacy).to.equal('1NYN'); + let bidRequest2 = {}; + let bRequest2 = spec.buildRequests(request, bidRequest2); + let data2 = JSON.parse(bRequest2.data); + expect(data2.regs).to.equal(undefined); + }); + it('bid Request check: GDPR Check', function () { + let bidRequest = { + gdprConsent: { + consentString: 'kjfdnidasd123sadsd', + gdprApplies: true + } + }; + let bRequest = spec.buildRequests(request, bidRequest); + let data = JSON.parse(bRequest.data); + expect(data.user.ext.consent).to.equal('kjfdnidasd123sadsd'); + expect(data.regs.ext.gdpr).to.equal(1); + let bidRequest2 = {}; + let bRequest2 = spec.buildRequests(request, bidRequest2); + let data2 = JSON.parse(bRequest2.data); + expect(data2.regs).to.equal(undefined); + expect(data2.user.ext).to.equal(undefined); + }); }); describe('user sync check', function () { it('user sync url check', function () { @@ -153,7 +188,7 @@ describe('Deepintent adapter', function () { let userSync = spec.getUserSyncs(syncOptions); expect(userSync).to.be.an('array').with.length.above(0); expect(userSync[0].type).to.equal('iframe'); - expect(userSync[0].url).to.equal('https://beacon.deepintent.com/usersync.html'); + expect(userSync[0].url).to.equal('https://cdn.deepintent.com/syncpixel.html'); }); }); describe('response check', function () { diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 6271c9b38f4..cd412530d2b 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -1,14 +1,14 @@ import { expect } from 'chai'; import parse from 'url-parse'; -import { buildDfpVideoUrl, buildAdpodVideoUrl } from 'modules/dfpAdServerVideo'; -import { parseQS } from 'src/url'; -import adUnit from 'test/fixtures/video/adUnit'; -import * as utils from 'src/utils'; -import { config } from 'src/config'; -import { targeting } from 'src/targeting'; -import { auctionManager } from 'src/auctionManager'; -import * as adpod from 'modules/adpod'; +import { buildDfpVideoUrl, buildAdpodVideoUrl } from 'modules/dfpAdServerVideo.js'; +import adUnit from 'test/fixtures/video/adUnit.json'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import { targeting } from 'src/targeting.js'; +import { auctionManager } from 'src/auctionManager.js'; +import * as adpod from 'modules/adpod.js'; +import { server } from 'test/mocks/xhr.js'; const bid = { videoCacheKey: 'abc', @@ -32,7 +32,7 @@ describe('The DFP video support module', function () { expect(url.protocol).to.equal('https:'); expect(url.host).to.equal('securepubads.g.doubleclick.net'); - const queryParams = parseQS(url.query); + const queryParams = utils.parseQS(url.query); expect(queryParams).to.have.property('correlator'); expect(queryParams).to.have.property('description_url', 'someUrl.com'); expect(queryParams).to.have.property('env', 'vp'); @@ -56,7 +56,7 @@ describe('The DFP video support module', function () { expect(url.host).to.equal('video.adserver.example'); - const queryObject = parseQS(url.query); + const queryObject = utils.parseQS(url.query); expect(queryObject.description_url).to.equal('vastUrl.example'); }); @@ -77,7 +77,7 @@ describe('The DFP video support module', function () { params: { iu: 'my/adUnit' } })); - const queryObject = parseQS(url.query); + const queryObject = utils.parseQS(url.query); expect(queryObject.iu).to.equal('my/adUnit'); }); @@ -91,7 +91,7 @@ describe('The DFP video support module', function () { } })); - expect(parseQS(url.query)).to.have.property('output', 'vast'); + expect(utils.parseQS(url.query)).to.have.property('output', 'vast'); }); it('should include the cache key and adserver targeting in cust_params', function () { @@ -107,8 +107,8 @@ describe('The DFP video support module', function () { 'iu': 'my/adUnit' } })); - const queryObject = parseQS(url.query); - const customParams = parseQS('?' + decodeURIComponent(queryObject.cust_params)); + const queryObject = utils.parseQS(url.query); + const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); @@ -177,8 +177,8 @@ describe('The DFP video support module', function () { 'iu': 'my/adUnit' } })); - const queryObject = parseQS(url.query); - const customParams = parseQS('?' + decodeURIComponent(queryObject.cust_params)); + const queryObject = utils.parseQS(url.query); + const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); @@ -204,8 +204,8 @@ describe('The DFP video support module', function () { }, }, })); - const queryObject = parseQS(url.query); - const customParams = parseQS('?' + decodeURIComponent(queryObject.cust_params)); + const queryObject = utils.parseQS(url.query); + const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('my_targeting', 'foo'); @@ -223,8 +223,8 @@ describe('The DFP video support module', function () { 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)); + const queryObject = utils.parseQS(url.query); + const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('section', 'blog'); @@ -246,7 +246,7 @@ describe('The DFP video support module', function () { } })); - const queryObject = parseQS(url.query); + const queryObject = utils.parseQS(url.query); expect(queryObject.description_url).to.equal('descriptionurl.example'); }); @@ -271,8 +271,8 @@ describe('The DFP video support module', function () { 'iu': 'my/adUnit' } })); - const queryObject = parseQS(url.query); - const customParams = parseQS('?' + decodeURIComponent(queryObject.cust_params)); + const queryObject = utils.parseQS(url.query); + const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); @@ -292,8 +292,8 @@ describe('The DFP video support module', function () { 'iu': 'my/adUnit' } })); - const queryObject = parseQS(url.query); - const customParams = parseQS('?' + decodeURIComponent(queryObject.cust_params)); + const queryObject = utils.parseQS(url.query); + const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_uuid', 'def'); expect(customParams).to.have.property('hb_cache_id', 'def'); @@ -302,8 +302,6 @@ describe('The DFP video support module', function () { describe('adpod unit tests', function () { let amStub; let amGetAdUnitsStub; - let xhr; - let requests; before(function () { let adUnits = [{ @@ -333,10 +331,6 @@ describe('The DFP video support module', function () { }); beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - config.setConfig({ adpod: { brandCategoryExclusion: true, @@ -347,7 +341,6 @@ describe('The DFP video support module', function () { afterEach(function() { config.resetConfig(); - xhr.restore(); }); after(function () { @@ -376,7 +369,7 @@ describe('The DFP video support module', function () { expect(url.protocol).to.equal('https:'); expect(url.host).to.equal('securepubads.g.doubleclick.net'); - const queryParams = parseQS(url.query); + const queryParams = utils.parseQS(url.query); expect(queryParams).to.have.property('correlator'); expect(queryParams).to.have.property('description_url', 'someUrl.com'); expect(queryParams).to.have.property('env', 'vp'); @@ -388,7 +381,7 @@ describe('The DFP video support module', function () { expect(queryParams).to.have.property('url'); expect(queryParams).to.have.property('cust_params'); - const custParams = parseQS(decodeURIComponent(queryParams.cust_params)); + const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); expect(custParams).to.have.property('hb_cache_id', '123'); expect(custParams).to.have.property('hb_pb_cat_dur', '15.00_395_15s,15.00_406_30s,10.00_395_15s'); } @@ -430,7 +423,7 @@ describe('The DFP video support module', function () { expect(url.protocol).to.equal('https:'); expect(url.host).to.equal('securepubads.g.doubleclick.net'); - const queryParams = parseQS(url.query); + const queryParams = utils.parseQS(url.query); expect(queryParams).to.have.property('correlator'); expect(queryParams).to.have.property('description_url', 'someUrl.com'); expect(queryParams).to.have.property('env', 'vp'); @@ -442,7 +435,7 @@ describe('The DFP video support module', function () { expect(queryParams).to.have.property('url'); expect(queryParams).to.have.property('cust_params'); - const custParams = parseQS(decodeURIComponent(queryParams.cust_params)); + const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); expect(custParams).to.have.property('hb_cache_id', '123'); expect(custParams).to.have.property('hb_pb_cat_dur', '10.00_15s,15.00_15s,15.00_30s'); } @@ -466,7 +459,7 @@ describe('The DFP video support module', function () { } })); - requests[0].respond(503, { + server.requests[0].respond(503, { 'Content-Type': 'plain/text', }, 'The server could not save anything at the moment.'); @@ -535,7 +528,7 @@ function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, }, 'customCacheKey': `${priceIndustryDuration}_${uuid}`, 'meta': { - 'iabSubCatId': 'iab-1', + 'primaryCatId': 'iab-1', 'adServerCatId': label }, 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' diff --git a/test/spec/modules/digitrustIdSystem_spec.js b/test/spec/modules/digitrustIdSystem_spec.js deleted file mode 100644 index 55035bc4b4e..00000000000 --- a/test/spec/modules/digitrustIdSystem_spec.js +++ /dev/null @@ -1,50 +0,0 @@ -import { - digiTrustIdSubmodule, - surfaceTestHook -} from 'modules/digiTrustIdSystem'; - -let assert = require('chai').assert; -let expect = require('chai').expect; - -var testHook = null; - -describe('DigiTrust Id System', function () { - it('Should create the test hook', function (done) { - testHook = surfaceTestHook(); - assert.isNotNull(testHook, 'The test hook failed to surface'); - var conf = { - init: { - member: 'unit_test', - site: 'foo' - }, - callback: function (result) { - } - }; - testHook.initDigitrustFacade(conf); - window.DigiTrust.getUser(conf); - expect(window.DigiTrust).to.exist; - expect(window.DigiTrust.isMock).to.be.true; - done(); - }); - - it('Should report as client', function (done) { - delete window.DigiTrust; - testHook = surfaceTestHook(); - - var conf = { - init: { - member: 'unit_test', - site: 'foo' - }, - callback: function (result) { - expect(window.DigiTrust).to.exist; - expect(result).to.exist; - expect(window.DigiTrust.isMock).to.be.true; - } - }; - testHook.initDigitrustFacade(conf); - expect(window.DigiTrust).to.exist; - expect(window.DigiTrust.isClient).to.be.true; - done(); - }); -}); diff --git a/test/spec/modules/districtmDmxBidAdapter_spec.js b/test/spec/modules/districtmDmxBidAdapter_spec.js index ac8698f63a9..d8f0beb9a36 100644 --- a/test/spec/modules/districtmDmxBidAdapter_spec.js +++ b/test/spec/modules/districtmDmxBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import * as _ from 'lodash'; -import {spec, matchRequest, checkDeepArray, defaultSize, upto5, cleanSizes, shuffle} from '../../../modules/districtmDMXBidAdapter'; +import {spec, matchRequest, checkDeepArray, defaultSize, upto5, cleanSizes, shuffle} from '../../../modules/districtmDMXBidAdapter.js'; const supportedSize = [ { @@ -40,7 +40,7 @@ const bidRequest = [{ 'bidder': 'districtmDMX', 'params': { 'dmxid': 100001, - 'memberid': 100003 + 'memberid': 100003, }, 'adUnitCode': 'div-gpt-ad-12345678-1', 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', @@ -53,6 +53,22 @@ const bidRequest = [{ 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' }]; +const bidRequestNoCoppa = [{ + 'bidder': 'districtmDMX', + 'params': { + 'dmxid': 100001, + 'memberid': 100003 + }, + 'adUnitCode': 'div-gpt-ad-12345678-1', + 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '29a28a1bbc8a8d', + 'bidderRequestId': '124b579a136515', + 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' +}]; const bidderRequest = { 'bidderCode': 'districtmDMX', 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf', @@ -61,7 +77,7 @@ const bidderRequest = { 'bidder': 'districtmDMX', 'params': { 'dmxid': 100001, - 'memberid': 100003 + 'memberid': 100003, }, 'adUnitCode': 'div-gpt-ad-12345678-1', 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', @@ -75,6 +91,7 @@ const bidderRequest = { }], 'auctionStart': 1529511035677, 'timeout': 700, + 'uspConsent': '1NY', 'gdprConsent': { 'consentString': 'BOPqNzUOPqNzUAHABBAAA5AAAAAAAA', 'vendorData': { @@ -419,6 +436,32 @@ const bidderRequest = { 'doneCbCallCount': 0 }; +const bidderRequestNoCoppa = { + 'bidderCode': 'districtmDMX', + 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf', + 'bidderRequestId': '124b579a136515', + 'bids': [{ + 'bidder': 'districtmDMX', + 'params': { + 'dmxid': 100001, + 'memberid': 100003, + }, + 'adUnitCode': 'div-gpt-ad-12345678-1', + 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '29a28a1bbc8a8d', + 'bidderRequestId': '124b579a136515', + 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' + }], + 'auctionStart': 1529511035677, + 'timeout': 700, + 'start': 1529511035686, + 'doneCbCallCount': 0 +}; + const responses = { 'body': { 'id': '1f45b37c-5298-4934-b517-4d911aadabfd', @@ -521,9 +564,26 @@ describe('DistrictM Adaptor', function () { describe(`buildRequests test usage`, function () { const buildRequestResults = districtm.buildRequests(bidRequest, bidderRequest); + const buildRequestResultsNoCoppa = districtm.buildRequests(bidRequestNoCoppa, bidderRequestNoCoppa); it(`the function should return an array`, function () { expect(buildRequestResults).to.be.an('object'); }); + it(`contain gdpr consent & ccpa`, function() { + const bidr = JSON.parse(buildRequestResults.data) + expect(bidr.regs.ext.gdpr).to.be.equal(1); + expect(bidr.regs.ext.us_privacy).to.be.equal('1NY'); + expect(bidr.user.ext.consent).to.be.an('string'); + }); + it(`test contain COPPA`, function() { + const bidr = JSON.parse(buildRequestResults.data) + bidr.regs = bidr.regs || {}; + bidr.regs.coppa = 1; + expect(bidr.regs.coppa).to.be.equal(1) + }) + it(`test should not contain COPPA`, function() { + const bidr = JSON.parse(buildRequestResultsNoCoppa.data) + expect(bidr.regs.coppa).to.be.equal(0) + }) it(`the function should return array length of 1`, function () { expect(buildRequestResults.data).to.be.a('string'); }); @@ -552,6 +612,32 @@ describe('DistrictM Adaptor', function () { }); }); + describe(`check validation for id sync gdpr ccpa`, () => { + let allin = spec.getUserSyncs({iframeEnabled: true}, {}, bidderRequest.gdprConsent, bidderRequest.uspConsent)[0] + let noCCPA = spec.getUserSyncs({iframeEnabled: true}, {}, bidderRequest.gdprConsent, null)[0] + let noGDPR = spec.getUserSyncs({iframeEnabled: true}, {}, null, bidderRequest.uspConsent)[0] + let nothing = spec.getUserSyncs({iframeEnabled: true}, {}, null, null)[0] + + /* + + 'uspConsent': '1NY', + 'gdprConsent': { + 'consentString': 'BOPqNzUOPqNzUAHABBAAA5AAAAAAAA', + */ + it(`gdpr & ccpa should be present`, () => { + expect(allin.url).to.be.equal('https://cdn.districtm.io/ids/index.html?gdpr=BOPqNzUOPqNzUAHABBAAA5AAAAAAAA&ccpa=1NY') + }) + it(`ccpa should be present`, () => { + expect(noGDPR.url).to.be.equal('https://cdn.districtm.io/ids/index.html?ccpa=1NY') + }) + it(`gdpr should be present`, () => { + expect(noCCPA.url).to.be.equal('https://cdn.districtm.io/ids/index.html?gdpr=BOPqNzUOPqNzUAHABBAAA5AAAAAAAA') + }) + it(`gdpr & ccpa shouldn't be present`, () => { + expect(nothing.url).to.be.equal('https://cdn.districtm.io/ids/index.html') + }) + }) + describe(`Helper function testing`, function () { const bid = matchRequest('29a28a1bbc8a8d', {bidderRequest}); const {width, height} = defaultSize(bid); diff --git a/test/spec/modules/djaxBidAdapter_spec.js b/test/spec/modules/djaxBidAdapter_spec.js index 82955ba43bc..bef2b1fddc5 100644 --- a/test/spec/modules/djaxBidAdapter_spec.js +++ b/test/spec/modules/djaxBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/djaxBidAdapter'; +import { spec } from 'modules/djaxBidAdapter.js'; const ENDPOINT = 'https://demo.reviveadservermod.com/headerbidding_adminshare/www/admin/plugins/Prebid/getAd.php'; diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js new file mode 100644 index 00000000000..cf36c3f62c4 --- /dev/null +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -0,0 +1,286 @@ +import { expect } from 'chai'; +import { spec } from 'modules/dspxBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; +const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; + +describe('dspxAdapter', function () { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'dspx', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000 + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [{ + 'bidder': 'dspx', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000, + 'private_auction': 0, + 'geo': { + 'country': 'DE' + } + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e1', + 'bidderRequestId': '22edbae2733bf61', + 'auctionId': '1d1a030790a475' + }, + { + 'bidder': 'dspx', + 'params': { + 'placement': '101', + 'devMode': true + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e2', + 'bidderRequestId': '22edbae2733bf62', + 'auctionId': '1d1a030790a476' + }, { + 'bidder': 'dspx', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000, + 'private_auction': 0, + 'geo': { + 'country': 'DE' + } + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e3', + 'bidderRequestId': '22edbae2733bf69', + 'auctionId': '1d1a030790a477' + }, + { + 'bidder': 'dspx', + 'params': { + 'placement': '101', + 'devMode': true + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e4', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478' + }, + { + 'bidder': 'dspx', + 'params': { + 'placement': '101', + 'devMode': true + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'bidId': '30b31c1838de1e41', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478' + } + + ]; + + // With gdprConsent + var bidderRequest = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {someData: 'value'}, + gdprApplies: true + } + }; + + var request1 = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + it('sends bid request to our endpoint via GET', function () { + expect(request1.method).to.equal('GET'); + expect(request1.url).to.equal(ENDPOINT_URL); + let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop'); + }); + + var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; + it('sends bid request to our DEV endpoint via GET', function () { + expect(request2.method).to.equal('GET'); + expect(request2.url).to.equal(ENDPOINT_URL_DEV); + let data = request2.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e2&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&prebidDevMode=1'); + }); + + // Without gdprConsent + var bidderRequestWithoutGdpr = { + refererInfo: { + referer: 'some_referrer.net' + } + }; + var request3 = spec.buildRequests([bidRequests[2]], bidderRequestWithoutGdpr)[0]; + it('sends bid request without gdprConsent to our endpoint via GET', function () { + expect(request3.method).to.equal('GET'); + expect(request3.url).to.equal(ENDPOINT_URL); + let data = request3.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e3&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop'); + }); + + var request4 = spec.buildRequests([bidRequests[3]], bidderRequestWithoutGdpr)[0]; + it('sends bid request without gdprConsent to our DEV endpoint via GET', function () { + expect(request4.method).to.equal('GET'); + expect(request4.url).to.equal(ENDPOINT_URL_DEV); + let data = request4.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&prebidDevMode=1'); + }); + + var request5 = spec.buildRequests([bidRequests[4]], bidderRequestWithoutGdpr)[0]; + it('sends bid video request to our rads endpoint via GET', function () { + expect(request5.method).to.equal('GET'); + let data = request5.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&inventory_item_id=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&prebidDevMode=1'); + }); + }); + + describe('interpretResponse', function () { + let serverResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'type': 'sspHTML', + 'tag': '', + 'requestId': '220ed41385952a', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + let serverVideoResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'vastXml': '{"reason":7001,"status":"accepted"}', + 'requestId': '220ed41385952a', + 'type': 'vast2', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + + let expectedResponse = [{ + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '' + }, { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'vast2', + vastXml: '{"reason":7001,"status":"accepted"}', + mediaType: 'video' + }]; + + it('should get the correct bid response by display ad', function () { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('should get the correct dspx video bid response by display ad', function () { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + }); + + it('handles empty bid response', function () { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js new file mode 100644 index 00000000000..447420616d1 --- /dev/null +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -0,0 +1,235 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/e_volutionBidAdapter.js'; + +describe('EvolutionTechBidAdapter', function () { + let bid = { + bidId: '23fhj33i987f', + bidder: 'e_volution', + params: { + placementId: 0, + traffic: 'banner' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://service.e-volution.ai/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes'); + 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', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + if (spec.noSync) { + expect(userSync).to.be.equal(false); + } else { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://service.e-volution.ai/?c=o&m=sync'); + } + }); + }); +}); diff --git a/test/spec/modules/ebdrBidAdapter_spec.js b/test/spec/modules/ebdrBidAdapter_spec.js index 3cac03024c0..ba1cad475da 100644 --- a/test/spec/modules/ebdrBidAdapter_spec.js +++ b/test/spec/modules/ebdrBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import { spec } from 'modules/ebdrBidAdapter'; -import { VIDEO, BANNER } from 'src/mediaTypes'; -import * as utils from 'src/utils'; +import { spec } from 'modules/ebdrBidAdapter.js'; +import { VIDEO, BANNER } from 'src/mediaTypes.js'; +import * as utils from 'src/utils.js'; describe('ebdrBidAdapter', function () { let bidRequests; diff --git a/test/spec/modules/edgequeryxBidAdapter_spec.js b/test/spec/modules/edgequeryxBidAdapter_spec.js new file mode 100644 index 00000000000..a66c546bd7c --- /dev/null +++ b/test/spec/modules/edgequeryxBidAdapter_spec.js @@ -0,0 +1,116 @@ +import { + expect +} from 'chai'; +import { + spec +} from 'modules/edgequeryxBidAdapter.js'; +import { + newBidder +} from 'src/adapters/bidderFactory.js'; +import { + config +} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { requestBidsHook } from 'modules/consentManagement.js'; + +// Default params with optional ones +describe('Edge Query X bid adapter tests', function () { + var DEFAULT_PARAMS = [{ + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [1, 1] + ] + } + }, + bidder: 'edgequeryx', + params: { + accountId: 'test', + widgetId: 'test' + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]; + var BID_RESPONSE = { + body: { + requestId: 'abcd1234', + cpm: 22, + width: 1, + height: 1, + creativeId: 'EQXTest', + currency: 'EUR', + netRevenue: true, + ttl: 360, + ad: '< --- awesome script --- >' + } + }; + + it('Verify build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('https://deep.edgequery.io/prebid/x'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('accountId').and.to.equal('test'); + expect(requestContent).to.have.property('widgetId').and.to.equal('test'); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(1); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(1); + }); + + it('Verify parse response', function () { + 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(22); + expect(bid.ad).to.equal('< --- awesome script --- >'); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('EQXTest'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(360); + expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); + + expect(function () { + spec.interpretResponse(BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('Verifies bidder code', function () { + expect(spec.code).to.equal('edgequeryx'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('eqx'); + }); + + it('Verifies if bid request valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + widgetyId: 'abcdef' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + widgetId: 'test', + accountId: 'test' + } + })).to.equal(true); + }); +}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js new file mode 100644 index 00000000000..434e1841a0f --- /dev/null +++ b/test/spec/modules/eids_spec.js @@ -0,0 +1,215 @@ +import {createEidsArray} from 'modules/userId/eids.js'; +import {expect} from 'chai'; + +// Note: In unit tets cases for bidders, call the createEidsArray function over userId object that is used for calling fetchBids +// this way the request will stay consistent and unit test cases will not need lots of changes. + +describe('eids array generation for known sub-modules', function() { + it('pubCommonId', function() { + const userId = { + pubcid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + + it('unifiedId: ext generation', function() { + const userId = { + tdid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adserver.org', + uids: [{id: 'some-random-id-value', atype: 1, ext: { rtiPartner: 'TDID' }}] + }); + }); + + it('id5Id', function() { + const userId = { + id5id: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'id5-sync.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + + it('parrableId', function() { + const userId = { + parrableId: { + eid: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'parrable.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + + it('identityLink', function() { + const userId = { + idl_env: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveramp.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + + it('liveIntentId; getValue call and ext', function() { + const userId = { + lipb: { + lipbid: 'some-random-id-value', + segments: ['s1', 's2'] + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'some-random-id-value', atype: 1}], + ext: {segments: ['s1', 's2']} + }); + }); + + it('liveIntentId; getValue call and NO ext', function() { + const userId = { + lipb: { + lipbid: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + + it('britepoolId', function() { + const userId = { + britepoolid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'britepool.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + + it('lotamePanoramaId', function () { + const userId = { + lotamePanoramaId: 'some-random-id-value', + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'crwdcntrl.net', + uids: [{ id: 'some-random-id-value', atype: 1 }], + }); + }); + + it('criteo', function() { + const userId = { + criteoId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'criteo.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + + it('NetId', function() { + const userId = { + netId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'netid.de', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + it('Sharedid', function() { + const userId = { + sharedid: { + id: 'test_sharedId', + third: 'test_sharedId' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [{ + id: 'test_sharedId', + atype: 1, + ext: { + third: 'test_sharedId' + } + }] + }); + }); + it('Sharedid: Not Synched', function() { + const userId = { + sharedid: { + id: 'test_sharedId' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [{ + id: 'test_sharedId', + atype: 1 + }] + }); + }); +}); + +describe('Negative case', function() { + it('eids array generation for UN-known sub-module', function() { + // UnknownCommonId + const userId = { + unknowncid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(0); + }); + + it('eids array generation for known sub-module with non-string value', function() { + // pubCommonId + let userId = { + pubcid: undefined + }; + let newEids = createEidsArray(userId); + expect(newEids.length).to.equal(0); + userId.pubcid = 123; + newEids = createEidsArray(userId); + expect(newEids.length).to.equal(0); + userId.pubcid = []; + newEids = createEidsArray(userId); + expect(newEids.length).to.equal(0); + userId.pubcid = {}; + newEids = createEidsArray(userId); + expect(newEids.length).to.equal(0); + userId.pubcid = null; + newEids = createEidsArray(userId); + expect(newEids.length).to.equal(0); + }); +}); diff --git a/test/spec/modules/emoteevBidAdapter_spec.js b/test/spec/modules/emoteevBidAdapter_spec.js index 410dea60b65..43ae62f1eb9 100644 --- a/test/spec/modules/emoteevBidAdapter_spec.js +++ b/test/spec/modules/emoteevBidAdapter_spec.js @@ -47,10 +47,10 @@ import { validateExternalId, VENDOR_ID, WALLPAPER, -} from 'modules/emoteevBidAdapter'; -import * as url from '../../../src/url'; -import * as utils from '../../../src/utils'; -import {config} from '../../../src/config'; + storage +} from 'modules/emoteevBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +import {config} from '../../../src/config.js'; const cannedValidBidRequests = [{ adUnitCode: '/19968336/header-bid-tag-1', @@ -329,108 +329,108 @@ describe('emoteevBidAdapter', function () { }); describe('eventsUrl', function () { - expect(eventsUrl(null)).to.deep.equal(url.format({ + expect(eventsUrl(null)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(DEFAULT_ENV), pathname: EVENTS_PATH })); - expect(eventsUrl('anything')).to.deep.equal(url.format({ + expect(eventsUrl('anything')).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(DEFAULT_ENV), pathname: EVENTS_PATH })); - expect(eventsUrl(PRODUCTION)).to.deep.equal(url.format({ + expect(eventsUrl(PRODUCTION)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(PRODUCTION), pathname: EVENTS_PATH })); - expect(eventsUrl(STAGING)).to.deep.equal(url.format({ + expect(eventsUrl(STAGING)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(STAGING), pathname: EVENTS_PATH })); - expect(eventsUrl(DEVELOPMENT)).to.deep.equal(url.format({ + expect(eventsUrl(DEVELOPMENT)).to.deep.equal(utils.buildUrl({ hostname: domain(DEVELOPMENT), pathname: EVENTS_PATH })); }); describe('bidderUrl', function () { - expect(bidderUrl(null)).to.deep.equal(url.format({ + expect(bidderUrl(null)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(DEFAULT_ENV), pathname: BIDDER_PATH })); - expect(bidderUrl('anything')).to.deep.equal(url.format({ + expect(bidderUrl('anything')).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(DEFAULT_ENV), pathname: BIDDER_PATH })); - expect(bidderUrl(PRODUCTION)).to.deep.equal(url.format({ + expect(bidderUrl(PRODUCTION)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(PRODUCTION), pathname: BIDDER_PATH })); - expect(bidderUrl(STAGING)).to.deep.equal(url.format({ + expect(bidderUrl(STAGING)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(STAGING), pathname: BIDDER_PATH })); - expect(bidderUrl(DEVELOPMENT)).to.deep.equal(url.format({ + expect(bidderUrl(DEVELOPMENT)).to.deep.equal(utils.buildUrl({ hostname: domain(DEVELOPMENT), pathname: BIDDER_PATH })); }); describe('userSyncIframeUrl', function () { - expect(userSyncIframeUrl(null)).to.deep.equal(url.format({ + expect(userSyncIframeUrl(null)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(DEFAULT_ENV), pathname: USER_SYNC_IFRAME_PATH })); - expect(userSyncIframeUrl('anything')).to.deep.equal(url.format({ + expect(userSyncIframeUrl('anything')).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(DEFAULT_ENV), pathname: USER_SYNC_IFRAME_PATH })); - expect(userSyncIframeUrl(PRODUCTION)).to.deep.equal(url.format({ + expect(userSyncIframeUrl(PRODUCTION)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(PRODUCTION), pathname: USER_SYNC_IFRAME_PATH })); - expect(userSyncIframeUrl(STAGING)).to.deep.equal(url.format({ + expect(userSyncIframeUrl(STAGING)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(STAGING), pathname: USER_SYNC_IFRAME_PATH })); - expect(userSyncIframeUrl(DEVELOPMENT)).to.deep.equal(url.format({ + expect(userSyncIframeUrl(DEVELOPMENT)).to.deep.equal(utils.buildUrl({ hostname: domain(DEVELOPMENT), pathname: USER_SYNC_IFRAME_PATH })); }); describe('userSyncImageUrl', function () { - expect(userSyncImageUrl(null)).to.deep.equal(url.format({ + expect(userSyncImageUrl(null)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(DEFAULT_ENV), pathname: USER_SYNC_IMAGE_PATH })); - expect(userSyncImageUrl('anything')).to.deep.equal(url.format({ + expect(userSyncImageUrl('anything')).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(DEFAULT_ENV), pathname: USER_SYNC_IMAGE_PATH })); - expect(userSyncImageUrl(PRODUCTION)).to.deep.equal(url.format({ + expect(userSyncImageUrl(PRODUCTION)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(PRODUCTION), pathname: USER_SYNC_IMAGE_PATH })); - expect(userSyncImageUrl(STAGING)).to.deep.equal(url.format({ + expect(userSyncImageUrl(STAGING)).to.deep.equal(utils.buildUrl({ protocol: 'https', hostname: domain(STAGING), pathname: USER_SYNC_IMAGE_PATH })); - expect(userSyncImageUrl(DEVELOPMENT)).to.deep.equal(url.format({ + expect(userSyncImageUrl(DEVELOPMENT)).to.deep.equal(utils.buildUrl({ hostname: domain(DEVELOPMENT), pathname: USER_SYNC_IMAGE_PATH })); @@ -736,7 +736,7 @@ describe('emoteevBidAdapter', function () { }); describe('side effects', function () { - let triggerPixelSpy; + let triggerPixelStub; let getCookieSpy; let getConfigSpy; let getParameterByNameSpy; @@ -748,13 +748,13 @@ describe('emoteevBidAdapter', function () { config.resetConfig(); }); beforeEach(function () { - triggerPixelSpy = sinon.spy(utils, 'triggerPixel'); - getCookieSpy = sinon.spy(utils, 'getCookie'); + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + getCookieSpy = sinon.spy(storage, 'getCookie'); getConfigSpy = sinon.spy(config, 'getConfig'); getParameterByNameSpy = sinon.spy(utils, 'getParameterByName'); }); afterEach(function () { - triggerPixelSpy.restore(); + triggerPixelStub.restore(); getCookieSpy.restore(); getConfigSpy.restore(); getParameterByNameSpy.restore(); @@ -776,7 +776,7 @@ describe('emoteevBidAdapter', function () { }; spec.isBidRequestValid(validBidRequest); sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(storage.getCookie); // sinon.assert.notCalled(config.getConfig); sinon.assert.notCalled(utils.getParameterByName); }); @@ -786,7 +786,7 @@ describe('emoteevBidAdapter', function () { const invalidBidRequest = {}; spec.isBidRequestValid(invalidBidRequest); sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(storage.getCookie); // disabling these getConfig tests as they have been flaky in browserstack testing // sinon.assert.notCalled(config.getConfig); sinon.assert.notCalled(utils.getParameterByName); @@ -796,7 +796,7 @@ describe('emoteevBidAdapter', function () { it('has intended side-effects', function () { spec.buildRequests(cannedValidBidRequests, cannedBidderRequest); sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(storage.getCookie); // sinon.assert.callCount(config.getConfig, 3); sinon.assert.callCount(utils.getParameterByName, 2); }); @@ -805,7 +805,7 @@ describe('emoteevBidAdapter', function () { it('has intended side-effects', function () { spec.interpretResponse(serverResponse); sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(storage.getCookie); // sinon.assert.notCalled(config.getConfig); sinon.assert.notCalled(utils.getParameterByName); }); @@ -815,7 +815,7 @@ describe('emoteevBidAdapter', function () { const bidObject = serverResponse.body[0]; spec.onBidWon(bidObject); sinon.assert.calledOnce(utils.triggerPixel); - sinon.assert.calledOnce(utils.getCookie); + sinon.assert.calledOnce(storage.getCookie); // sinon.assert.calledOnce(config.getConfig); sinon.assert.calledOnce(utils.getParameterByName); }); @@ -824,7 +824,7 @@ describe('emoteevBidAdapter', function () { it('has intended side-effects', function () { spec.onTimeout(cannedValidBidRequests[0]); sinon.assert.calledOnce(utils.triggerPixel); - sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(storage.getCookie); // sinon.assert.calledOnce(config.getConfig); sinon.assert.calledOnce(utils.getParameterByName); }); @@ -833,7 +833,7 @@ describe('emoteevBidAdapter', function () { it('has intended side-effects', function () { spec.getUserSyncs({}); sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(storage.getCookie); // sinon.assert.calledOnce(config.getConfig); sinon.assert.calledOnce(utils.getParameterByName); }); diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js new file mode 100644 index 00000000000..138786b9c74 --- /dev/null +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -0,0 +1,634 @@ +import { expect } from 'chai'; +import { spec } from 'modules/emx_digitalBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('emx_digital Adapter', function () { + describe('callBids', function () { + const adapter = newBidder(spec); + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + describe('banner request validity', function () { + let bid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + let badBid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'mediaTypes': { + 'banner': { + } + }, + 'adUnitCode': 'adunit-code', + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + let noBid = {}; + let otherBid = { + 'bidder': 'emxdigital', + 'params': { + 'tagid': '25251' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + let noMediaSizeBid = { + 'bidder': 'emxdigital', + 'params': { + 'tagid': '25251' + }, + 'mediaTypes': { + 'banner': {} + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(badBid)).to.equal(false); + expect(spec.isBidRequestValid(noBid)).to.equal(false); + expect(spec.isBidRequestValid(otherBid)).to.equal(false); + expect(spec.isBidRequestValid(noMediaSizeBid)).to.equal(false); + }); + }); + + describe('video request validity', function () { + let bid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + let noInstreamBid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': { + 'protocols': [1, 7] + } + }, + 'mediaTypes': { + 'video': { + 'context': 'something_random' + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + let outstreamBid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(noInstreamBid)).to.equal(false); + expect(spec.isBidRequestValid(outstreamBid)).to.equal(true); + }); + + it('should contain tagid param', function () { + expect(spec.isBidRequestValid({ + bidder: 'emx_digital', + params: {}, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'emx_digital', + params: { + tagid: '' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'emx_digital', + params: { + tagid: '123' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + })).to.equal(true); + }); + }); + }); + + describe('buildRequests', function () { + let bidderRequest = { + 'bidderCode': 'emx_digital', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'bidderRequestId': '22edbae3120bf6', + 'timeout': 1500, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com/index.html?pbjs_debug=true' + }, + 'bids': [{ + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] + ] + } + }, + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + }] + }; + let request = spec.buildRequests(bidderRequest.bids, bidderRequest); + + it('sends bid request to ENDPOINT via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('contains the correct options', function () { + expect(request.options.withCredentials).to.equal(true); + }); + + it('contains a properly formatted endpoint url', function () { + const url = request.url.split('?'); + const queryParams = url[1].split('&'); + expect(queryParams[0]).to.match(new RegExp('^t=\d*', 'g')); + expect(queryParams[1]).to.match(new RegExp('^ts=\d*', 'g')); + }); + + it('builds with bid floor', function () { + const bidRequestWithBidFloor = utils.deepClone(bidderRequest.bids); + bidRequestWithBidFloor[0].params.bidfloor = 1; + const requestWithFloor = spec.buildRequests(bidRequestWithBidFloor, bidderRequest); + const data = JSON.parse(requestWithFloor.data); + expect(data.imp[0].bidfloor).to.equal(bidRequestWithBidFloor[0].params.bidfloor); + }); + + it('builds request properly', function () { + const data = JSON.parse(request.data); + expect(Array.isArray(data.imp)).to.equal(true); + expect(data.id).to.equal(bidderRequest.auctionId); + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal('30b31c2501de1e'); + expect(data.imp[0].tid).to.equal('d7b773de-ceaa-484d-89ca-d9f51b8d61ec'); + expect(data.imp[0].tagid).to.equal('25251'); + expect(data.imp[0].secure).to.equal(0); + expect(data.imp[0].vastXml).to.equal(undefined); + }); + + it('properly sends site information and protocol', function () { + request = spec.buildRequests(bidderRequest.bids, bidderRequest); + request = JSON.parse(request.data); + expect(request.site).to.have.property('domain', 'example.com'); + expect(request.site).to.have.property('page', 'https://example.com/index.html?pbjs_debug=true'); + expect(request.site).to.have.property('ref', window.top.document.referrer); + }); + + it('builds correctly formatted request banner object', function () { + let bidRequestWithBanner = utils.deepClone(bidderRequest.bids); + let request = spec.buildRequests(bidRequestWithBanner, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].video).to.equal(undefined); + expect(data.imp[0].banner).to.exist.and.to.be.a('object'); + expect(data.imp[0].banner.w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][1]); + expect(data.imp[0].banner.format[0].w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.format[0].h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][1]); + expect(data.imp[0].banner.format[1].w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[1][0]); + expect(data.imp[0].banner.format[1].h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[1][1]); + }); + + it('builds correctly formatted request video object for instream', function () { + let bidRequestWithVideo = utils.deepClone(bidderRequest.bids); + bidRequestWithVideo[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [[640, 480]] + }, + }; + bidRequestWithVideo[0].params.video = {}; + let request = spec.buildRequests(bidRequestWithVideo, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist.and.to.be.a('object'); + expect(data.imp[0].video.w).to.equal(bidRequestWithVideo[0].mediaTypes.video.playerSize[0][0]); + expect(data.imp[0].video.h).to.equal(bidRequestWithVideo[0].mediaTypes.video.playerSize[0][1]); + }); + + it('builds correctly formatted request video object for outstream', function () { + let bidRequestWithOutstreamVideo = utils.deepClone(bidderRequest.bids); + bidRequestWithOutstreamVideo[0].mediaTypes = { + video: { + context: 'outstream', + playerSize: [[640, 480]] + }, + }; + bidRequestWithOutstreamVideo[0].params.video = {}; + let request = spec.buildRequests(bidRequestWithOutstreamVideo, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist.and.to.be.a('object'); + expect(data.imp[0].video.w).to.equal(bidRequestWithOutstreamVideo[0].mediaTypes.video.playerSize[0][0]); + expect(data.imp[0].video.h).to.equal(bidRequestWithOutstreamVideo[0].mediaTypes.video.playerSize[0][1]); + }); + + it('shouldn\'t contain a user obj without GDPR information', function () { + let request = spec.buildRequests(bidderRequest.bids, bidderRequest) + request = JSON.parse(request.data) + expect(request).to.not.have.property('user'); + }); + + it('should have the right gdpr info when enabled', function () { + let consentString = 'OIJSZsOAFsABAB8EMXZZZZZ+A=='; + const gdprBidderRequest = utils.deepClone(bidderRequest); + gdprBidderRequest.gdprConsent = { + 'consentString': consentString, + 'gdprApplies': true + }; + let request = spec.buildRequests(gdprBidderRequest.bids, gdprBidderRequest); + + request = JSON.parse(request.data) + expect(request.regs.ext).to.have.property('gdpr', 1); + expect(request.user.ext).to.have.property('consent', consentString); + }); + + it('should\'t contain consent string if gdpr isn\'t applied', function () { + const nonGdprBidderRequest = utils.deepClone(bidderRequest); + nonGdprBidderRequest.gdprConsent = { + 'gdprApplies': false + }; + let request = spec.buildRequests(nonGdprBidderRequest.bids, nonGdprBidderRequest); + request = JSON.parse(request.data) + expect(request.regs.ext).to.have.property('gdpr', 0); + expect(request).to.not.have.property('user'); + }); + + it('should add us privacy info to request', function() { + const uspBidderRequest = utils.deepClone(bidderRequest); + let consentString = '1YNN'; + uspBidderRequest.uspConsent = consentString; + let request = spec.buildRequests(uspBidderRequest.bids, uspBidderRequest); + request = JSON.parse(request.data); + expect(request.us_privacy).to.exist; + expect(request.us_privacy).to.exist.and.to.equal(consentString); + }); + }); + + describe('interpretResponse', function () { + let bid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + const bid_outstream = { + 'bidderRequest': { + 'bids': [{ + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '987654321cba', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }, { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25252', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '987654321dcb', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }] + } + }; + + const serverResponse = { + 'id': '12819a18-56e1-4256-b836-b69a10202668', + 'seatbid': [{ + 'bid': [{ + 'adid': '123456abcde', + 'adm': '', + 'crid': '3434abab34', + 'h': 250, + 'id': '987654321cba', + 'price': 0.5, + 'ttl': 300, + 'w': 300 + }], + 'seat': '1356' + }, { + 'bid': [{ + 'adid': '123456abcdf', + 'adm': '', + 'crid': '3434abab35', + 'h': 600, + 'id': '987654321dcb', + 'price': 0.5, + 'ttl': 300, + 'w': 300 + }] + }] + }; + + const expectedResponse = [{ + 'requestId': '12819a18-56e1-4256-b836-b69a10202668', + 'cpm': 0.5, + 'width': 300, + 'height': 250, + 'creativeId': '3434abab34', + 'dealId': null, + 'currency': 'USD', + 'netRevneue': true, + 'mediaType': 'banner', + 'ad': '', + 'ttl': 300 + }, { + 'requestId': '12819a18-56e1-4256-b836-b69a10202668', + 'cpm': 0.7, + 'width': 300, + 'height': 600, + 'creativeId': '3434abab35', + 'dealId': null, + 'currency': 'USD', + 'netRevneue': true, + 'mediaType': 'banner', + 'ad': '', + 'ttl': 300 + }]; + + it('should properly format bid response', function () { + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(Object.keys(result[0]).length).to.equal(Object.keys(expectedResponse[0]).length); + expect(Object.keys(result[0]).requestId).to.equal(Object.keys(expectedResponse[0]).requestId); + expect(Object.keys(result[0]).bidderCode).to.equal(Object.keys(expectedResponse[0]).bidderCode); + expect(Object.keys(result[0]).cpm).to.equal(Object.keys(expectedResponse[0]).cpm); + expect(Object.keys(result[0]).creativeId).to.equal(Object.keys(expectedResponse[0]).creativeId); + expect(Object.keys(result[0]).width).to.equal(Object.keys(expectedResponse[0]).width); + expect(Object.keys(result[0]).height).to.equal(Object.keys(expectedResponse[0]).height); + expect(Object.keys(result[0]).ttl).to.equal(Object.keys(expectedResponse[0]).ttl); + expect(Object.keys(result[0]).adId).to.equal(Object.keys(expectedResponse[0]).adId); + expect(Object.keys(result[0]).currency).to.equal(Object.keys(expectedResponse[0]).currency); + expect(Object.keys(result[0]).netRevenue).to.equal(Object.keys(expectedResponse[0]).netRevenue); + expect(Object.keys(result[0]).ad).to.equal(Object.keys(expectedResponse[0]).ad); + }); + + it('should return multiple bids', function () { + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(Array.isArray(result.seatbid)) + + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.ad).to.equal(serverResponse.seatbid[0].bid[0].adm); + expect(ad0.cpm).to.equal(serverResponse.seatbid[0].bid[0].price); + expect(ad0.creativeId).to.equal(serverResponse.seatbid[0].bid[0].crid); + expect(ad0.currency).to.equal('USD'); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.requestId).to.equal(serverResponse.seatbid[0].bid[0].id); + expect(ad0.ttl).to.equal(300); + + expect(ad1.ad).to.equal(serverResponse.seatbid[1].bid[0].adm); + expect(ad1.cpm).to.equal(serverResponse.seatbid[1].bid[0].price); + expect(ad1.creativeId).to.equal(serverResponse.seatbid[1].bid[0].crid); + expect(ad1.currency).to.equal('USD'); + expect(ad1.netRevenue).to.equal(true); + expect(ad1.requestId).to.equal(serverResponse.seatbid[1].bid[0].id); + expect(ad1.ttl).to.equal(300); + }); + + it('returns a banner bid for non-xml creatives', function () { + let result = spec.interpretResponse({ + body: serverResponse + }, { bidRequest: bid } + ); + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.mediaType).to.equal('banner'); + expect(ad0.ad.indexOf('
'; + vastServerResponse.seatbid[1].bid[0].adm = '
'; + + let result = spec.interpretResponse({ + body: vastServerResponse + }, { bidRequest: bid } + ); + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.mediaType).to.equal('video'); + expect(ad0.ad.indexOf(' -1).to.equal(true); + expect(ad0.vastXml).to.equal(vastServerResponse.seatbid[0].bid[0].adm); + expect(ad0.ad).to.exist.and.to.be.a('string'); + expect(ad1.mediaType).to.equal('video'); + expect(ad1.ad.indexOf(' -1).to.equal(true); + expect(ad1.vastXml).to.equal(vastServerResponse.seatbid[1].bid[0].adm); + expect(ad1.ad).to.exist.and.to.be.a('string'); + }); + + it('returns a renderer for outstream video creatives', function () { + const vastServerResponse = utils.deepClone(serverResponse); + vastServerResponse.seatbid[0].bid[0].adm = ''; + vastServerResponse.seatbid[1].bid[0].adm = ''; + let result = spec.interpretResponse({body: vastServerResponse}, bid_outstream); + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.renderer).to.exist.and.to.be.a('object'); + expect(ad0.renderer.url).to.equal('https://js.brealtime.com/outstream/1.30.0/bundle.js'); + expect(ad0.renderer.id).to.equal('987654321cba'); + expect(ad1.renderer).to.equal(undefined); + }); + + it('handles nobid responses', function () { + let serverResponse = { + 'bids': [] + }; + + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(result.length).to.equal(0); + }); + + it('should not throw an error when decoding an improperly encoded adm', function () { + const badAdmServerResponse = utils.deepClone(serverResponse); + badAdmServerResponse.seatbid[0].bid[0].adm = '\\<\\/script\\>'; + badAdmServerResponse.seatbid[1].bid[0].adm = '%3F%%3Demx%3C3prebid'; + + assert.doesNotThrow(() => spec.interpretResponse({ + body: badAdmServerResponse + })); + }); + }); + + describe('getUserSyncs', function () { + it('should register the iframe sync url', function () { + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + }); + + it('should pass gdpr params', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: false, consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=0'); + }); + }); +}); diff --git a/test/spec/modules/envivoBidAdapter_spec.js b/test/spec/modules/envivoBidAdapter_spec.js new file mode 100644 index 00000000000..7bd1dd1ccf1 --- /dev/null +++ b/test/spec/modules/envivoBidAdapter_spec.js @@ -0,0 +1,159 @@ +import { expect } from 'chai'; +import { spec } from 'modules/envivoBidAdapter.js'; + +const ENDPOINT = 'https://ad.nvivo.tv/ads/www/admin/plugins/Prebid/getAd.php'; + +describe('The Envivo bidding adapter', function () { + describe('isBidRequestValid', function () { + it('should return false when given an invalid bid', function () { + const bid = { + bidder: 'envivo', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return true when given a publisherId in bid', function () { + const bid = { + bidder: 'envivo', + params: { + publisherId: 14 + }, + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + 'bidder': 'envivo', + 'params': { + 'publisherId': 14 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ] + }]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint url', function () { + expect(request.url).to.equal(ENDPOINT) + }); + + it('sets the proper banner object', function () { + expect(bidRequests[0].params.publisherId).to.equal(14); + }) + }); + const response = { + body: [ + { + 'requestId': '2ee937f15015c6', + 'cpm': '0.2000', + 'width': 300, + 'height': 600, + 'creativeId': '4', + 'currency': 'USD', + 'netRevenue': true, + 'ad': 'ads.html', + 'mediaType': 'banner' + }, + { + 'requestId': '3e1af92622bdc', + 'cpm': '0.2000', + 'creativeId': '4', + 'context': 'outstream', + 'currency': 'USD', + 'netRevenue': true, + 'vastUrl': 'tezt.xml', + 'width': 640, + 'height': 480, + 'mediaType': 'video' + }] + }; + + const request = [ + { + 'bidder': 'envivo', + 'params': { + 'publisherId': 14 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 600] + ] + } + }, + 'bidId': '2ee937f15015c6', + 'src': 'client', + }, + { + 'bidder': 'envivo', + 'params': { + 'publisherId': 14 + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [ + [640, 480] + ] + } + }, + 'bidId': '3e1af92622bdc', + 'src': 'client', + } + ]; + + describe('interpretResponse', function () { + it('return empty array when no ad found', function () { + const response = {}; + const request = { bidRequests: [] }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('check response for banner and video', function () { + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(2); + expect(bids[0].requestId).to.equal('2ee937f15015c6'); + expect(bids[0].cpm).to.equal('0.2000'); + expect(bids[1].cpm).to.equal('0.2000'); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(600); + expect(bids[1].vastUrl).to.not.equal(''); + expect(bids[0].ad).to.not.equal(''); + expect(bids[1].adResponse).to.not.equal(''); + expect(bids[1].renderer).not.to.be.an('undefined'); + }); + }); + + describe('On winning bid', function () { + const bids = spec.interpretResponse(response, request); + spec.onBidWon(bids); + }); + + describe('On bid Time out', function () { + const bids = spec.interpretResponse(response, request); + spec.onTimeout(bids); + }); + + describe('user sync', function () { + it('to check the user sync iframe', function () { + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + }); + }); +}); diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index 8609024c7d9..255d116a0ff 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -1,24 +1,18 @@ -import eplAnalyticsAdapter from 'modules/eplanningAnalyticsAdapter'; -import includes from 'core-js/library/fn/array/includes'; +import eplAnalyticsAdapter from 'modules/eplanningAnalyticsAdapter.js'; +import includes from 'core-js-pure/features/array/includes.js'; import { expect } from 'chai'; -import {parse as parseURL} from 'src/url'; +import { parseUrl } from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); let constants = require('src/constants.json'); describe('eplanning analytics adapter', function () { - let xhr; - let requests; - beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => { requests.push(request) }; sinon.stub(events, 'getEvents').returns([]); }); afterEach(function () { - xhr.restore(); events.getEvents.restore(); eplAnalyticsAdapter.disableAnalytics(); }); @@ -115,7 +109,7 @@ describe('eplanning analytics adapter', function () { events.emit(constants.EVENTS.AUCTION_END, {auctionId: pauctionId}); // Step 7: Find the request data sent (filtering other hosts) - requests = requests.filter(req => { + let requests = server.requests.filter(req => { return req.url.indexOf(initOptions.host) > -1; }); expect(requests.length).to.equal(1); @@ -124,7 +118,7 @@ describe('eplanning analytics adapter', function () { 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 purl = parseUrl(info); let eplData = JSON.parse(decodeURIComponent(purl.search.d)); // Step 8 check that 6 events were sent diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js new file mode 100644 index 00000000000..f267680b46b --- /dev/null +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -0,0 +1,799 @@ +import { expect } from 'chai'; +import { spec, storage } from 'modules/eplanningBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('E-Planning Adapter', function () { + const adapter = newBidder('spec'); + const CI = '12345'; + const ADUNIT_CODE = 'adunit-co:de'; + const ADUNIT_CODE2 = 'adunit-code-dos'; + const ADUNIT_CODE_VIEW = 'adunit-code-view'; + const ADUNIT_CODE_VIEW2 = 'adunit-code-view2'; + const ADUNIT_CODE_VIEW3 = 'adunit-code-view3'; + const CLEAN_ADUNIT_CODE2 = '300x250_1'; + const CLEAN_ADUNIT_CODE = '300x250_0'; + const CLEAN_ADUNIT_CODE_ML = 'adunitco_de'; + const BID_ID = '123456789'; + const BID_ID2 = '987654321'; + const BID_ID3 = '998877665'; + 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]], + }; + const ML = '1'; + const validBidMappingLinear = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + 'ml': ML, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], + }; + const validBid2 = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + }; + const validBidView = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE_VIEW, + 'sizes': [[300, 250], [300, 600]], + }; + const validBidView2 = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE_VIEW2, + 'sizes': [[300, 250], [300, 600]], + }; + const validBidView3 = { + 'bidder': 'eplanning', + 'bidId': BID_ID3, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE_VIEW3, + 'sizes': [[300, 250], [300, 600]], + }; + 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 + } + ] + } + }; + const refererUrl = 'https://localhost'; + const bidderRequest = { + refererInfo: { + referer: refererUrl + }, + gdprConsent: { + gdprApplies: 1, + consentString: 'concentDataString', + vendorData: { + vendorConsents: { + '90': 1 + }, + }, + }, + uspConsent: 'consentCcpa' + }; + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when bid has ci parameter', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when bid does not have ci parameter and is not a test bid', function () { + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return true when bid does not have ci parameter but is a test bid', function () { + expect(spec.isBidRequestValid(testBid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [validBid]; + it('should create the url correctly', function () { + const url = spec.buildRequests(bidRequests, bidderRequest).url; + expect(url).to.equal('https://ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + }); + + it('should return GET method', function () { + const method = spec.buildRequests(bidRequests, bidderRequest).method; + expect(method).to.equal('GET'); + }); + + it('should return r parameter with value pbjs', function () { + const r = spec.buildRequests(bidRequests, bidderRequest).data.r; + expect(r).to.equal('pbjs'); + }); + + it('should return pbv parameter with value prebid version', function () { + const pbv = spec.buildRequests(bidRequests, bidderRequest).data.pbv; + expect(pbv).to.equal('$prebid.version$'); + }); + + it('should return e parameter with value according to the adunit sizes', function () { + const e = spec.buildRequests(bidRequests, bidderRequest).data.e; + expect(e).to.equal('300x250_0:300x250,300x600'); + }); + + it('should return e parameter with linear mapping attribute with value according to the adunit sizes', function () { + let bidRequestsML = [validBidMappingLinear]; + const e = spec.buildRequests(bidRequestsML, bidderRequest).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE_ML + ':300x250,300x600'); + }); + + it('should return correct e parameter with more than one adunit', function () { + const NEW_CODE = ADUNIT_CODE + '2'; + const CLEAN_NEW_CODE = CLEAN_ADUNIT_CODE + '2'; + const anotherBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + }, + 'adUnitCode': NEW_CODE, + 'sizes': [[100, 100]], + }; + bidRequests.push(anotherBid); + + const e = spec.buildRequests(bidRequests, bidderRequest).data.e; + expect(e).to.equal('300x250_0:300x250,300x600+100x100_0:100x100'); + }); + + it('should return correct e parameter with linear mapping attribute with more than one adunit', function () { + let bidRequestsML = [validBidMappingLinear]; + const NEW_CODE = ADUNIT_CODE + '2'; + const CLEAN_NEW_CODE = CLEAN_ADUNIT_CODE_ML + '2'; + const anotherBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + 'ml': ML, + }, + 'adUnitCode': NEW_CODE, + 'sizes': [[100, 100]], + }; + bidRequestsML.push(anotherBid); + + const e = spec.buildRequests(bidRequestsML, bidderRequest).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE_ML + ':300x250,300x600+' + CLEAN_NEW_CODE + ':100x100'); + }); + + it('should return correct e parameter when the adunit has no size', function () { + const noSizeBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + }; + + const e = spec.buildRequests([noSizeBid], bidderRequest).data.e; + expect(e).to.equal('1x1_0:1x1'); + }); + + it('should return correct e parameter with linear mapping attribute when the adunit has no size', function () { + const noSizeBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + 'ml': ML, + }, + 'adUnitCode': ADUNIT_CODE, + }; + + const e = spec.buildRequests([noSizeBid], bidderRequest).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE_ML + ':1x1'); + }); + + it('should return ur parameter with current window url', function () { + const ur = spec.buildRequests(bidRequests, bidderRequest).data.ur; + expect(ur).to.equal(bidderRequest.refererInfo.referer); + }); + + it('should return fr parameter when there is a referrer', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const dataRequest = request.data; + expect(dataRequest.fr).to.equal(refererUrl); + }); + + it('should return crs parameter with document charset', function () { + let expected; + try { + expected = window.top.document.characterSet; + } catch (e) { + expected = document.characterSet; + } + + const chset = spec.buildRequests(bidRequests, bidderRequest).data.crs; + + expect(chset).to.equal(expected); + }); + + it('should return the testing url when the request has the t parameter', function () { + const url = spec.buildRequests([testBid], bidderRequest).url; + const expectedUrl = 'https://' + TEST_ISV + '/layers/t_pbjs_2.json'; + expect(url).to.equal(expectedUrl); + }); + + it('should return the parameter ncb with value 1', function () { + const ncb = spec.buildRequests(bidRequests, bidderRequest).data.ncb; + expect(ncb).to.equal('1'); + }); + + it('should properly build a gdpr request', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const dataRequest = request.data; + expect(dataRequest.gdpr).to.equal('1'); + expect(dataRequest.gdprcs).to.equal('concentDataString'); + }); + + it('should properly build a uspConsent request', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const dataRequest = request.data; + expect(dataRequest.ccpa).to.equal('consentCcpa'); + }); + }); + + describe('interpretResponse', function () { + it('should return an empty array when there is no ads in the response', function () { + const bidResponses = spec.interpretResponse(responseWithNoAd); + expect(bidResponses).to.be.empty; + }); + + it('should return an empty array when there is no spaces in the response', function () { + const bidResponses = spec.interpretResponse(responseWithNoSpace); + expect(bidResponses).to.be.empty; + }); + + it('should correctly map the parameters in the response', function () { + 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', function () { + 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', function () { + 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', function () { + const syncs = spec.getUserSyncs(sOptionsAllDisabled, [response]); + expect(syncs).to.be.empty; + }); + + it('should only return pixels if iframe is not enabled', function () { + 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', function () { + const syncs = spec.getUserSyncs(sOptionsOnlyIframe, [response]); + syncs.forEach(sync => expect(sync.type).to.equal('iframe')); + }); + }); + + describe('adUnits mapping to bidId', function () { + it('should correctly map the bidId to the adunit', function () { + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const responses = spec.interpretResponse(responseWithTwoAdunits, requests); + expect(responses[0].requestId).to.equal(BID_ID); + expect(responses[1].requestId).to.equal(BID_ID2); + }); + }); + describe('viewability', function() { + let storageIdRender = 'pbsr_' + validBidView.adUnitCode; + let storageIdView = 'pbvi_' + validBidView.adUnitCode; + let bidRequests = [validBidView]; + let bidRequestMultiple = [validBidView, validBidView2, validBidView3]; + let getLocalStorageSpy; + let setDataInLocalStorageSpy; + let hasLocalStorageStub; + let clock; + let element; + let getBoundingClientRectStub; + let sandbox = sinon.sandbox.create(); + let focusStub; + function createElement(id) { + element = document.createElement('div'); + element.id = id || ADUNIT_CODE_VIEW; + element.style.width = '50px'; + element.style.height = '50px'; + if (frameElement) { + frameElement.style.width = '100px'; + frameElement.style.height = '100px'; + } + element.style.background = 'black'; + document.body.appendChild(element); + } + function createElementVisible(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 0, + y: 0, + width: 50, + height: 50, + top: 0, + right: 50, + bottom: 50, + left: 0, + }); + } + function createElementOutOfView(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 100, + y: 100, + width: 250, + height: 250, + top: 100, + right: 350, + bottom: 350, + left: 100, + }); + } + + function createPartiallyVisibleElement(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 0, + y: 0, + width: 50, + height: 150, + top: 0, + right: 50, + bottom: 150, + left: 0, + }); + } + function createPartiallyInvisibleElement(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 0, + y: 0, + width: 150, + height: 150, + top: 0, + right: 150, + bottom: 150, + left: 0, + }); + } + function createElementOutOfRange(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 200, + y: 200, + width: 350, + height: 350, + top: 200, + right: 350, + bottom: 350, + left: 200, + }); + } + beforeEach(function () { + getLocalStorageSpy = sandbox.spy(storage, 'getDataFromLocalStorage'); + setDataInLocalStorageSpy = sandbox.spy(storage, 'setDataInLocalStorage'); + + hasLocalStorageStub = sandbox.stub(storage, 'hasLocalStorage'); + hasLocalStorageStub.returns(true); + + clock = sandbox.useFakeTimers(); + + focusStub = sandbox.stub(window.top.document, 'hasFocus'); + focusStub.returns(true); + }); + afterEach(function () { + sandbox.restore(); + if (document.getElementById(ADUNIT_CODE_VIEW)) { + document.body.removeChild(element); + } + window.top.localStorage.removeItem(storageIdRender); + window.top.localStorage.removeItem(storageIdView); + }); + + it('should create the url correctly without LocalStorage', function() { + createElementVisible(); + hasLocalStorageStub.returns(false); + const response = spec.buildRequests(bidRequests, bidderRequest); + + expect(response.url).to.equal('https://ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + expect(response.data.vs).to.equal('F'); + + sinon.assert.notCalled(getLocalStorageSpy); + sinon.assert.notCalled(setDataInLocalStorageSpy); + }); + + it('should create the url correctly with LocalStorage', function() { + createElementVisible(); + const response = spec.buildRequests(bidRequests, bidderRequest); + expect(response.url).to.equal('https://ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + + expect(response.data.vs).to.equal('F'); + + sinon.assert.called(getLocalStorageSpy); + sinon.assert.called(setDataInLocalStorageSpy); + sinon.assert.calledWith(getLocalStorageSpy, storageIdRender); + sinon.assert.calledWith(setDataInLocalStorageSpy, storageIdRender); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + }); + + context('when element is fully in view', function() { + let respuesta; + beforeEach(function () { + createElementVisible(); + }); + it('when you have a render', function() { + respuesta = spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + + expect(respuesta.data.vs).to.equal('F'); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal('1'); + }); + it('when you have more than four render', function() { + storage.setDataInLocalStorage(storageIdRender, 4); + respuesta = spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + + expect(respuesta.data.vs).to.equal('0'); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('5'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal('1'); + }); + it('when you have more than four render and already record visibility', function() { + storage.setDataInLocalStorage(storageIdRender, 4); + storage.setDataInLocalStorage(storageIdView, 4); + respuesta = spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + + expect(respuesta.data.vs).to.equal('a'); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('5'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal('5'); + }); + }); + + context('when element is out of view', function() { + let respuesta; + beforeEach(function () { + createElementOutOfView(); + }); + + it('when you have a render', function() { + respuesta = spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + expect(respuesta.data.vs).to.equal('F'); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + it('when you have more than four render', function() { + storage.setDataInLocalStorage(storageIdRender, 4); + respuesta = spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + expect(respuesta.data.vs).to.equal('0'); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('5'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + + context('when element is partially in view', function() { + let respuesta; + it('should register visibility with more than 50%', function() { + createPartiallyVisibleElement(); + respuesta = spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal('1'); + }); + it('you should not register visibility with less than 50%', function() { + createPartiallyInvisibleElement(); + respuesta = spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + context('when width or height of the element is zero', function() { + beforeEach(function () { + createElementVisible(); + }); + it('if the width is zero but the height is within the range', function() { + element.style.width = '0px'; + spec.buildRequests(bidRequests, bidderRequest) + clock.tick(1005); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + it('if the height is zero but the width is within the range', function() { + element.style.height = '0px'; + spec.buildRequests(bidRequests, bidderRequest) + clock.tick(1005); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + it('if both are zero', function() { + element.style.height = '0px'; + element.style.width = '0px'; + spec.buildRequests(bidRequests, bidderRequest) + clock.tick(1005); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + context('when tab is inactive', function() { + it('I should not register if it is not in focus', function() { + createElementVisible(); + focusStub.returns(false); + spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + context('segmentBeginsBeforeTheVisibleRange', function() { + it('segmentBeginsBeforeTheVisibleRange', function() { + createElementOutOfRange(); + spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + context('when there are multiple adunit', function() { + let respuesta; + beforeEach(function () { + [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + storage.setDataInLocalStorage('pbsr_' + ac, 5); + storage.setDataInLocalStorage('pbvi_' + ac, 5); + }); + }); + afterEach(function () { + [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + if (document.getElementById(ac)) { + document.body.removeChild(document.getElementById(ac)); + } + window.top.localStorage.removeItem(ac); + window.top.localStorage.removeItem(ac); + }); + }); + it('all visibles', function() { + createElementVisible(ADUNIT_CODE_VIEW); + createElementVisible(ADUNIT_CODE_VIEW2); + createElementVisible(ADUNIT_CODE_VIEW3); + + respuesta = spec.buildRequests(bidRequestMultiple, bidderRequest); + clock.tick(1005); + [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + expect(storage.getDataFromLocalStorage('pbsr_' + ac)).to.equal('6'); + expect(storage.getDataFromLocalStorage('pbvi_' + ac)).to.equal('6'); + }); + expect('aaa').to.equal(respuesta.data.vs); + }); + it('none visible', function() { + createElementOutOfView(ADUNIT_CODE_VIEW); + createElementOutOfView(ADUNIT_CODE_VIEW2); + createElementOutOfView(ADUNIT_CODE_VIEW3); + + respuesta = spec.buildRequests(bidRequestMultiple, bidderRequest); + clock.tick(1005); + [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + expect(storage.getDataFromLocalStorage('pbsr_' + ac)).to.equal('6'); + expect(storage.getDataFromLocalStorage('pbvi_' + ac)).to.equal('5'); + }); + + expect('aaa').to.equal(respuesta.data.vs); + }); + it('some visible and others not visible', function() { + createElementVisible(ADUNIT_CODE_VIEW); + createElementOutOfView(ADUNIT_CODE_VIEW2); + createElementOutOfView(ADUNIT_CODE_VIEW3); + + respuesta = spec.buildRequests(bidRequestMultiple, bidderRequest); + clock.tick(1005); + expect(storage.getDataFromLocalStorage('pbsr_' + ADUNIT_CODE_VIEW)).to.equal('6'); + expect(storage.getDataFromLocalStorage('pbvi_' + ADUNIT_CODE_VIEW)).to.equal('6'); + [ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + expect(storage.getDataFromLocalStorage('pbsr_' + ac)).to.equal('6'); + expect(storage.getDataFromLocalStorage('pbvi_' + ac)).to.equal('5'); + }); + expect('aaa').to.equal(respuesta.data.vs); + }); + }); + }); +}); diff --git a/test/spec/modules/etargetBidAdapter_spec.js b/test/spec/modules/etargetBidAdapter_spec.js index ed512e55ea9..4f5e0c224ec 100644 --- a/test/spec/modules/etargetBidAdapter_spec.js +++ b/test/spec/modules/etargetBidAdapter_spec.js @@ -1,7 +1,6 @@ import {assert, expect} from 'chai'; -import * as url from 'src/url'; -import {spec} from 'modules/etargetBidAdapter'; -import { BANNER, VIDEO } from 'src/mediaTypes'; +import {spec} from 'modules/etargetBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; describe('etarget adapter', function () { let serverResponse, bidRequest, bidResponses; diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 3432a40eca4..066ab6b21f6 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; -import {spec} from 'modules/feedadBidAdapter'; -import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; -import * as sinon from 'sinon'; +import {spec} from 'modules/feedadBidAdapter.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; +import {server} from 'test/mocks/xhr.js'; const CODE = 'feedad'; @@ -382,28 +382,15 @@ describe('FeedAdAdapter', function () { cases.forEach(([name, data, eventKlass]) => { let subject = spec[name]; describe(name + ' handler', function () { - let xhr; - let requests; - - beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = xhr => requests.push(xhr); - }); - - afterEach(function () { - xhr.restore(); - }); - it('should do nothing on empty data', function () { subject(undefined); subject(null); - expect(requests.length).to.equal(0); + expect(server.requests.length).to.equal(0); }); it('should do nothing when bid metadata is not set', function () { subject(data); - expect(requests.length).to.equal(0); + expect(server.requests.length).to.equal(0); }); it('should send tracking params when correct metadata was set', function () { @@ -420,8 +407,8 @@ describe('FeedAdAdapter', function () { sdk_version: '1.0.0' }; subject(data); - expect(requests.length).to.equal(1); - let call = requests[0]; + expect(server.requests.length).to.equal(1); + let call = server.requests[0]; expect(call.url).to.equal('https://api.feedad.com/1/prebid/web/events'); expect(JSON.parse(call.requestBody)).to.deep.equal(expectedData); expect(call.method).to.equal('POST'); diff --git a/test/spec/modules/fidelityBidAdapter_spec.js b/test/spec/modules/fidelityBidAdapter_spec.js new file mode 100644 index 00000000000..304a98675b3 --- /dev/null +++ b/test/spec/modules/fidelityBidAdapter_spec.js @@ -0,0 +1,233 @@ +import { expect } from 'chai'; +import { spec } from 'modules/fidelityBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('FidelityAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'fidelity', + 'params': { + 'zoneid': '27248', + 'floor': '0.05', + 'server': 'x.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', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'zoneid': '27248', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'zoneid': 0, + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidderRequest = { + bidderCode: 'fidelity', + requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + bidderRequestId: '178e34bad3658f', + bids: [ + { + bidder: 'fidelity', + params: { + zoneid: '27248', + floor: '0.05', + server: 'x.fidelity-media.com', + }, + placementCode: '/19968336/header-bid-tag-0', + sizes: [[300, 250], [320, 50]], + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' + }] + } + } + ], + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + refererInfo: { + referer: 'http://test.com/index.html' + } + }; + + it('should add params to the request', function () { + let schainString = '1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com'; + 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.subid).to.exist; + expect(payload.flashver).to.exist; + expect(payload.tmax).to.exist; + expect(payload.defloc).to.exist; + expect(payload.schain).to.exist.and.to.be.a('string'); + expect(payload.schain).to.equal(schainString); + }); + + it('should add consent information to the request - TCF v1', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentString = '1YN-'; + bidderRequest.gdprConsent = { + gdprApplies: true, + allowAuctionWithoutConsent: true, + consentString: consentString, + vendorData: { + vendorConsents: { + '408': true + }, + }, + apiVersion: 1 + }; + bidderRequest.uspConsent = uspConsentString; + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const payload = request.data; + expect(payload.gdpr).to.exist.and.to.be.a('number'); + expect(payload.gdpr).to.equal(1); + expect(payload.consent_str).to.exist.and.to.be.a('string'); + expect(payload.consent_str).to.equal(consentString); + expect(payload.consent_given).to.exist.and.to.be.a('number'); + expect(payload.consent_given).to.equal(1); + expect(payload.us_privacy).to.exist.and.to.be.a('string'); + expect(payload.us_privacy).to.equal(uspConsentString); + }); + + it('should add consent information to the request - TCF v2', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentString = '1YN-'; + bidderRequest.gdprConsent = { + gdprApplies: true, + allowAuctionWithoutConsent: true, + consentString: consentString, + vendorData: { + vendor: { + consents: { + '408': true + } + }, + }, + apiVersion: 2 + }; + bidderRequest.uspConsent = uspConsentString; + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const payload = request.data; + expect(payload.gdpr).to.exist.and.to.be.a('number'); + expect(payload.gdpr).to.equal(1); + expect(payload.consent_str).to.exist.and.to.be.a('string'); + expect(payload.consent_str).to.equal(consentString); + expect(payload.consent_given).to.exist.and.to.be.a('number'); + expect(payload.consent_given).to.equal(1); + expect(payload.us_privacy).to.exist.and.to.be.a('string'); + expect(payload.us_privacy).to.equal(uspConsentString); + }); + + it('sends bid request to ENDPOINT via GET', function () { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.url).to.equal('https://x.fidelity-media.com/delivery/hb.php'); + expect(request.method).to.equal('GET'); + }); + }) + + describe('interpretResponse', function () { + 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', function () { + 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])); + }); + + it('handles nobid responses', function () { + let response = { + 'id': '543210', + 'seatbid': [ ] + }; + + let result = spec.interpretResponse({ body: response }); + expect(result.length).to.equal(0); + }); + }); + + describe('user sync', function () { + const syncUrl = 'https://x.fidelity-media.com/delivery/matches.php?type=iframe'; + + it('should register the sync iframe', function () { + 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/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index 2cc2f380016..29136c85241 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -1,7 +1,8 @@ -import fntzAnalyticsAdapter from 'modules/fintezaAnalyticsAdapter'; -import includes from 'core-js/library/fn/array/includes'; +import fntzAnalyticsAdapter from 'modules/fintezaAnalyticsAdapter.js'; +import includes from 'core-js-pure/features/array/includes.js'; import { expect } from 'chai'; -import { parse as parseURL } from 'src/url'; +import { parseUrl } from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); @@ -18,14 +19,8 @@ describe('finteza analytics adapter', function () { const clientId = 'fntz-client-32145'; const uniqCookie = '5045380421580287382'; - let xhr; - let requests; - beforeEach(function () { setCookie('_fz_uniq', uniqCookie); - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => { requests.push(request) }; sinon.stub(events, 'getEvents').returns([]); sinon.spy(fntzAnalyticsAdapter, 'track'); @@ -49,7 +44,6 @@ describe('finteza analytics adapter', function () { afterEach(function () { setCookie('_fz_uniq', '', new Date(0)); - xhr.restore(); events.getEvents.restore(); fntzAnalyticsAdapter.track.restore(); fntzAnalyticsAdapter.disableAnalytics(); @@ -82,12 +76,12 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); - expect(requests.length).to.equal(1); + expect(server.requests.length).to.equal(1); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].withCredentials).to.equal(true); + expect(server.requests[0].method).to.equal('GET'); + expect(server.requests[0].withCredentials).to.equal(true); - const url = parseURL(requests[0].url); + const url = parseUrl(server.requests[0].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); @@ -125,12 +119,12 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); - expect(requests.length).to.equal(2); + expect(server.requests.length).to.equal(2); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].withCredentials).to.equal(true); + expect(server.requests[0].method).to.equal('GET'); + expect(server.requests[0].withCredentials).to.equal(true); - let url = parseURL(requests[0].url); + let url = parseUrl(server.requests[0].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); @@ -141,10 +135,10 @@ describe('finteza analytics adapter', function () { expect(url.search.value).to.equal(String(cpm)); expect(url.search.unit).to.equal('usd'); - expect(requests[1].method).to.equal('GET'); - expect(requests[1].withCredentials).to.equal(true); + expect(server.requests[1].method).to.equal('GET'); + expect(server.requests[1].withCredentials).to.equal(true); - url = parseURL(requests[1].url); + url = parseUrl(server.requests[1].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); @@ -179,12 +173,12 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(constants.EVENTS.BID_WON, bidWon); - expect(requests.length).to.equal(1); + expect(server.requests.length).to.equal(1); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].withCredentials).to.equal(true); + expect(server.requests[0].method).to.equal('GET'); + expect(server.requests[0].withCredentials).to.equal(true); - const url = parseURL(requests[0].url); + const url = parseUrl(server.requests[0].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); @@ -218,12 +212,12 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - expect(requests.length).to.equal(1); + expect(server.requests.length).to.equal(1); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].withCredentials).to.equal(true); + expect(server.requests[0].method).to.equal('GET'); + expect(server.requests[0].withCredentials).to.equal(true); - const url = parseURL(requests[0].url); + const url = parseUrl(server.requests[0].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js new file mode 100644 index 00000000000..6530a3c36cf --- /dev/null +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -0,0 +1,201 @@ +import {expect} from 'chai'; +import {spec} from 'modules/fluctBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from 'src/config.js'; + +describe('fluctAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + bidder: 'fluct', + params: { + dfpUnitCode: '/1000/dfp_unit_code', + tagId: '10000:100000001', + groupId: '1000000002', + } + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when dfpUnitCode is not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + tagId: '10000:100000001', + groupId: '1000000002', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when groupId is not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + dfpUnitCode: '/1000/dfp_unit_code', + tagId: '10000:100000001', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + bidder: 'fluct', + params: { + dfpUnitCode: '/100000/unit_code', + tagId: '10000:100000001', + groupId: '1000000002', + }, + adUnitCode: '/10000/unit_code', + sizes: [[300, 250], [336, 280]], + bidId: '237f4d1a293f99', + bidderRequestId: '1a857fa34c1c96', + auctionId: 'a297d1aa-7900-4ce4-a0aa-caa8d46c4af7', + transactionId: '00b2896c-2731-4f01-83e4-7a3ad5da13b6', + }]; + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponse', function() { + const callBeaconSnippet = ''; + + it('should get correct bid response', function() { + const bidRequest = { + bidder: 'fluct', + params: { + dfpUnitCode: '/10000/unit_code', + tagid: '10000:100000001', + groupId: '1000000002', + }, + adUnitCode: '/10000/unit_code', + sizes: [[300, 250], [336, 280]], + bidId: '237f4d1a293f99', + bidderRequestId: '1a857fa34c1c96', + auctionId: 'a297d1aa-7900-4ce4-a0aa-caa8d46c4af7', + transactionId: '00b2896c-2731-4f01-83e4-7a3ad5da13b6', + }; + + const serverResponse = { + body: { + id: '237f4d1a293f99', + cur: 'JPY', + seatbid: [{ + bid: [{ + price: 100, + w: 300, + h: 250, + adm: '', + burl: 'https://i.adingo.jp/?test=1&et=hb&bidid=237f4d1a293f99', + crid: 'test_creative', + }] + }] + } + }; + + const expectedResponse = [ + { + bidderCode: 'fluct', + requestId: '237f4d1a293f99', + currency: 'JPY', + cpm: 100, + netRevenue: true, + width: 300, + height: 250, + creativeId: 'test_creative', + ttl: 300, + ad: '' + callBeaconSnippet, + } + ]; + + const result = spec.interpretResponse(serverResponse, bidRequest); + expect(result).to.have.lengthOf(1); + expect(result).to.deep.have.same.members(expectedResponse); + }); + + it('should get correct bid response with dealId', function() { + const bidRequest = { + bidder: 'fluct', + params: { + dfpUnitCode: '/10000/unit_code', + tagid: '10000:100000001', + groupId: '1000000002' + }, + adUnitCode: '/10000/unit_code', + sizes: [[300, 250], [336, 280]], + bidId: '237f4d1a293f99', + bidderRequestId: '1a857fa34c1c96', + auctionId: 'a297d1aa-7900-4ce4-a0aa-caa8d46c4af7', + transactionId: '00b2896c-2731-4f01-83e4-7a3ad5da13b6', + }; + + const serverResponse = { + body: { + id: '237f4d1a293f99', + cur: 'JPY', + seatbid: [{ + bid: [{ + price: 100, + w: 300, + h: 250, + adm: '', + burl: 'https://i.adingo.jp/?test=1&et=hb&bidid=237f4d1a293f99', + crid: 'test_creative', + dealid: 'test_deal', + }] + }] + } + }; + + const expectedResponse = [ + { + bidderCode: 'fluct', + requestId: '237f4d1a293f99', + currency: 'JPY', + cpm: 100, + netRevenue: true, + width: 300, + height: 250, + creativeId: 'test_creative', + ttl: 300, + ad: '' + callBeaconSnippet, + dealId: 'test_deal', + } + ]; + + const result = spec.interpretResponse(serverResponse, bidRequest); + expect(result).to.have.lengthOf(1); + expect(result).to.deep.have.same.members(expectedResponse); + }); + + it('should get empty response when bid server returns 204', function() { + expect(spec.interpretResponse({})).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/freeWheelAdserverVideo_spec.js b/test/spec/modules/freeWheelAdserverVideo_spec.js index c2afaf8d69f..0a215092e18 100644 --- a/test/spec/modules/freeWheelAdserverVideo_spec.js +++ b/test/spec/modules/freeWheelAdserverVideo_spec.js @@ -1,13 +1,12 @@ import { expect } from 'chai'; -import { adpodUtils } from 'modules/freeWheelAdserverVideo'; -import { auctionManager } from 'src/auctionManager'; -import { config } from 'src/config'; +import { adpodUtils } from 'modules/freeWheelAdserverVideo.js'; +import { auctionManager } from 'src/auctionManager.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; describe('freeWheel adserver module', function() { let amStub; let amGetAdUnitsStub; - let xhr; - let requests; before(function () { let adUnits = [{ @@ -56,10 +55,6 @@ describe('freeWheel adserver module', function() { }); beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - config.setConfig({ adpod: { brandCategoryExclusion: false, @@ -70,7 +65,6 @@ describe('freeWheel adserver module', function() { afterEach(function() { config.resetConfig(); - xhr.restore(); }); after(function () { @@ -188,7 +182,7 @@ describe('freeWheel adserver module', function() { } }); - requests[0].respond( + server.requests[0].respond( 200, { 'Content-Type': 'text/plain' }, JSON.stringify({'responses': getBidsReceived().slice(0, 4)}) @@ -225,7 +219,7 @@ describe('freeWheel adserver module', function() { } }); - requests[0].respond( + server.requests[0].respond( 200, { 'Content-Type': 'text/plain' }, JSON.stringify({'responses': bidsReceived.slice(1)}) @@ -275,7 +269,7 @@ describe('freeWheel adserver module', function() { } }); - requests[0].respond( + server.requests[0].respond( 200, { 'Content-Type': 'text/plain' }, JSON.stringify({'responses': [tier7Bid, bid]}) @@ -348,7 +342,7 @@ function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, }, 'customCacheKey': `${priceIndustryDuration}_${uuid}`, 'meta': { - 'iabSubCatId': 'iab-1', + 'primaryCatId': 'iab-1', 'adServerCatId': industry }, 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js new file mode 100644 index 00000000000..3047b635d13 --- /dev/null +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -0,0 +1,516 @@ +import { expect } from 'chai'; +import { spec } from 'modules/freewheel-sspBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +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('isBidRequestValidForBanner', () => { + let bid = { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], [300, 600] + ] + } + }, + '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('isBidRequestValidForVideo', () => { + let bid = { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'playerSize': [300, 250], + } + }, + '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 = { + wrong: 'missing zone id' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequestsForBanner', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], [300, 600] + ] + } + }, + '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[0].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('prebid'); + expect(payload.componentSubId).to.equal('mustang'); + expect(payload.playerSize).to.equal('300x600'); + }); + + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request[0].url).to.contain(ENDPOINT); + expect(request[0].method).to.equal('GET'); + }); + + it('should add usp consent to the request', () => { + let uspConsentString = '1FW-SSP-uspConsent-'; + let bidderRequest = {}; + bidderRequest.uspConsent = uspConsentString; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request[0].data; + expect(payload.reqType).to.equal('AdsSetup'); + expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.zoneId).to.equal('277225'); + expect(payload.componentId).to.equal('prebid'); + expect(payload.componentSubId).to.equal('mustang'); + expect(payload.playerSize).to.equal('300x600'); + expect(payload._fw_us_privacy).to.exist.and.to.be.a('string'); + expect(payload._fw_us_privacy).to.equal(uspConsentString); + }); + + it('should add gdpr consent to the request', () => { + let gdprConsentString = '1FW-SSP-gdprConsent-'; + let bidderRequest = { + 'gdprConsent': { + 'consentString': gdprConsentString + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request[0].data; + expect(payload.reqType).to.equal('AdsSetup'); + expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.zoneId).to.equal('277225'); + expect(payload.componentId).to.equal('prebid'); + expect(payload.componentSubId).to.equal('mustang'); + expect(payload.playerSize).to.equal('300x600'); + expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); + expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + }); + }) + + describe('buildRequestsForVideo', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'playerSize': [300, 600], + } + }, + '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[0].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('prebid'); + expect(payload.componentSubId).to.equal('mustang'); + expect(payload.playerSize).to.equal('300x600'); + }); + + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request[0].url).to.contain(ENDPOINT); + expect(request[0].method).to.equal('GET'); + }); + + it('should add usp consent to the request', () => { + let uspConsentString = '1FW-SSP-uspConsent-'; + let bidderRequest = {}; + bidderRequest.uspConsent = uspConsentString; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request[0].data; + expect(payload.reqType).to.equal('AdsSetup'); + expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.zoneId).to.equal('277225'); + expect(payload.componentId).to.equal('prebid'); + expect(payload.componentSubId).to.equal('mustang'); + expect(payload.playerSize).to.equal('300x600'); + expect(payload._fw_us_privacy).to.exist.and.to.be.a('string'); + expect(payload._fw_us_privacy).to.equal(uspConsentString); + }); + + it('should add gdpr consent to the request', () => { + let gdprConsentString = '1FW-SSP-gdprConsent-'; + let bidderRequest = { + 'gdprConsent': { + 'consentString': gdprConsentString + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request[0].data; + expect(payload.reqType).to.equal('AdsSetup'); + expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.zoneId).to.equal('277225'); + expect(payload.componentId).to.equal('prebid'); + expect(payload.componentSubId).to.equal('mustang'); + expect(payload.playerSize).to.equal('300x600'); + expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); + expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + }); + }) + + describe('interpretResponseForBanner', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], [300, 600] + ] + } + }, + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + let formattedBidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225', + 'format': 'floorad' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], [300, 600] + ] + } + }, + 'sizes': [[600, 250], [300, 600]], + 'bidId': '30b3other1c1838de1e', + 'bidderRequestId': '22edbae273other3bf6', + 'auctionId': '1d1a03079test0a475', + }, + { + 'bidder': 'stickyadstv', + 'params': { + 'zoneId': '277225', + 'format': 'test' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 600] + ] + } + }, + 'sizes': [[300, 600]], + 'bidId': '2', + 'bidderRequestId': '3', + 'auctionId': '4', + } + ]; + + let response = '' + + '' + + ' ' + + ' Adswizz' + + ' ' + + ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-00008' + + ' ' + + ' ' + + ' ' + + ' 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, + dealId: 'NRJ-PRO-00008', + ad: ad + } + ]; + + let result = spec.interpretResponse(response, request[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + }); + + 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, + dealId: 'NRJ-PRO-00008', + ad: formattedAd + } + ]; + + let result = spec.interpretResponse(response, request[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + }); + + it('handles nobid responses', () => { + var request = spec.buildRequests(formattedBidRequests); + let response = ''; + + let result = spec.interpretResponse(response, request[0]); + expect(result.length).to.equal(0); + }); + }); + describe('interpretResponseForVideo', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'playerSize': [300, 600], + } + }, + 'sizes': [[300, 400]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + let formattedBidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225', + 'format': 'floorad' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'playerSize': [300, 600], + } + }, + 'sizes': [[300, 400]], + 'bidId': '30b3other1c1838de1e', + 'bidderRequestId': '22edbae273other3bf6', + 'auctionId': '1d1a03079test0a475', + }, + { + 'bidder': 'stickyadstv', + 'params': { + 'zoneId': '277225', + 'format': 'test' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'playerSize': [300, 600], + } + }, + 'sizes': [[300, 400]], + 'bidId': '2', + 'bidderRequestId': '3', + 'auctionId': '4', + } + ]; + + let response = '' + + '' + + ' ' + + ' Adswizz' + + ' ' + + ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-00008' + + ' ' + + ' ' + + ' ' + + ' 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, + dealId: 'NRJ-PRO-00008', + vastXml: response, + mediaType: 'video', + ad: ad + } + ]; + + let result = spec.interpretResponse(response, request[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + }); + + 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, + dealId: 'NRJ-PRO-00008', + vastXml: response, + mediaType: 'video', + ad: formattedAd + } + ]; + + let result = spec.interpretResponse(response, request[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + }); + + it('handles nobid responses', () => { + var request = spec.buildRequests(formattedBidRequests); + let response = ''; + + let result = spec.interpretResponse(response, request[0]); + 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..cdaa1b5448a --- /dev/null +++ b/test/spec/modules/gammaBidAdapter_spec.js @@ -0,0 +1,104 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gammaBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('gammaBidAdapter', function() { + const adapter = newBidder(spec); + + 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', + }; + let bidArray = [bid]; + + describe('isBidRequestValid', () => { + 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', () => { + it('should attempt to send bid requests to the endpoint via GET', () => { + const requests = spec.buildRequests(bidArray); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + expect(requestItem.url).to.match(new RegExp(`hb.gammaplatform.com`)); + }); + }); + }); + + describe('interpretResponse', () => { + let serverResponse; + + beforeEach(() => { + 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/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js new file mode 100644 index 00000000000..79f58470cb3 --- /dev/null +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -0,0 +1,561 @@ +import {expect} from 'chai'; +import {spec, helper} from 'modules/gamoshiBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {newBidder} from '../../../src/adapters/bidderFactory.js'; + +const supplyPartnerId = '123'; +const adapter = newBidder(spec); +const TTL = 360; + +describe('GamoshiAdapter', () => { + let schainConfig, + bidRequest, + bannerBidRequest, + videoBidRequest, + rtbResponse, + videoResponse, + gdprConsent; + + beforeEach(() => { + schainConfig = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + + bidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + refererInfo: {referer: 'http://examplereferer.com'}, + gdprConsent: { + consentString: 'some string', + gdprApplies: true + }, + schain: schainConfig, + uspConsent: 'gamoshiCCPA' + }; + + bannerBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + videoBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + video: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + rtbResponse = { + 'id': 'imp_5b05b9fde4b09084267a556f', + 'bidid': 'imp_5b05b9fde4b09084267a556f', + 'cur': 'USD', + 'ext': { + 'utrk': [ + {'type': 'iframe', 'url': '//rtb.gamoshi.io/user/sync/1?gdpr=[GDPR]&consent=[CONSENT]&usp=[US_PRIVACY]'}, + {'type': 'image', 'url': '//rtb.gamoshi.io/user/sync/2'} + ] + }, + 'seatbid': [ + { + 'seat': 'seat1', + 'group': 0, + 'bid': [ + { + 'id': '0', + 'impid': '1', + 'price': 2.016, + 'adid': '579ef31bfa788b9d2000d562', + 'nurl': 'https://rtb.gamoshi.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + 'adm': '↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵', + 'adomain': ['aaa.com'], + 'cid': '579ef268fa788b9d2000d55c', + 'crid': '579ef31bfa788b9d2000d562', + 'attr': [], + 'h': 600, + 'w': 120, + 'ext': { + 'vast_url': 'http://my.vast.com', + 'utrk': [ + {'type': 'iframe', 'url': '//p.partner1.io/user/sync/1'} + ] + } + } + ] + }, + { + 'seat': 'seat2', + 'group': 0, + 'bid': [ + { + 'id': '1', + 'impid': '1', + 'price': 3, + 'adid': '542jlhdfd2112jnjf3x', + 'nurl': 'https://rtb.gamoshi.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + 'adm': ' ', + 'adomain': ['bbb.com'], + 'cid': 'fgdlwjh2498ydjhg1', + 'crid': 'kjh34297ydh2133d', + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'utrk': [ + {'type': 'image', 'url': '//p.partner2.io/user/sync/1'} + ] + } + } + ] + } + ] + }; + + videoResponse = { + 'id': '64f32497-b2f7-48ec-9205-35fc39894d44', + 'bidid': 'imp_5c24924de4b0d106447af333', + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '3668', + 'group': 0, + 'bid': [ + { + 'id': 'gb_1', + 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + 'price': 5.0, + 'adid': '1274', + 'nurl': 'https://rtb.gamoshi.io/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', + 'adomain': [], + 'adm': '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + 'cid': '3668', + 'crid': '1274', + 'cat': [], + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'vast_url': 'https://rtb.gamoshi.io/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + 'imptrackers': [ + 'https://rtb.gamoshi.io/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1'] + } + } + ] + } + ], + 'ext': { + 'utrk': [{ + 'type': 'image', + 'url': 'https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gdpr=[GDPR]&consent=[CONSENT]&us_privacy=[US_PRIVACY]' + }] + } + }; + + gdprConsent = { + gdprApplies: true, + consentString: 'consent string' + }; + }); + + describe('Get top Frame', () => { + it('check if you are in the top frame', () => { + expect(helper.getTopFrame()).to.equal(0); + }); + + it('verify domain parsing', () => { + expect(helper.getTopWindowDomain('http://www.domain.com')).to.equal('www.domain.com'); + }); + }); + + describe('Is String start with search', () => { + it('check if a string started with', () => { + expect(helper.startsWith('gamoshi.com', 'gamo')).to.equal(true); + }); + }); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should validate supply-partner ID', () => { + expect(spec.isBidRequestValid({params: {}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: 123}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); + }); + + it('should validate RTB endpoint', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // RTB endpoint has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', rtbEndpoint: 123}})).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyPartnerId: '123', + rtbEndpoint: 'https://some.url.com' + } + })).to.equal(true); + }); + + it('should validate bid floor', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // bidfloor has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: 0.1}})).to.equal(true); + }); + + it('should validate adpos', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: 0.1}})).to.equal(true); + }); + + it('should validate instl', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: -1}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 0}})).to.equal(true); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 1}})).to.equal(true); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 2}})).to.equal(false); + }); + }); + + describe('buildRequests', () => { + it('returns an array', () => { + let response; + response = spec.buildRequests([]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + response = spec.buildRequests([bidRequest], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'a'}); + const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'b'}); + response = spec.buildRequests([adUnit1, adUnit2], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(2); + }); + + it('targets correct endpoint', () => { + let response; + response = spec.buildRequests([bidRequest], bidRequest)[0]; + expect(response.method).to.equal('POST'); + expect(response.url).to.match(new RegExp(`^https://rtb\\.gamoshi\\.io/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + expect(response.data.id).to.equal(bidRequest.auctionId); + const bidRequestWithEndpoint = utils.deepClone(bidRequest); + bidRequestWithEndpoint.params.rtbEndpoint = 'https://rtb2.gamoshi.io/a12'; + response = spec.buildRequests([bidRequestWithEndpoint], bidRequest)[0]; + expect(response.url).to.match(new RegExp(`^https://rtb2\\.gamoshi\\.io/a12/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + }); + + it('builds request correctly', () => { + let bidRequest2 = utils.deepClone(bidRequest); + bidRequest2.refererInfo.referer = 'http://www.test.com/page.html'; + let response = spec.buildRequests([bidRequest], bidRequest2)[0]; + + expect(response.data.site.domain).to.equal('www.test.com'); + expect(response.data.site.page).to.equal('http://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('http://www.test.com/page.html'); + expect(response.data.imp.length).to.equal(1); + expect(response.data.imp[0].id).to.equal(bidRequest.transactionId); + expect(response.data.imp[0].instl).to.equal(0); + expect(response.data.imp[0].tagid).to.equal(bidRequest.adUnitCode); + expect(response.data.imp[0].bidfloor).to.equal(0); + expect(response.data.imp[0].bidfloorcur).to.equal('USD'); + expect(response.data.regs.ext.us_privacy).to.equal('gamoshiCCPA');// USP/CCPAs + expect(response.data.source.ext.schain).to.deep.equal(bidRequest2.schain); + + const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals1.params.instl = 1; + response = spec.buildRequests([bidRequestWithInstlEquals1], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals1.params.instl); + const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals0.params.instl = 1; + response = spec.buildRequests([bidRequestWithInstlEquals0], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals0.params.instl); + const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); + bidRequestWithBidfloorEquals1.params.bidfloor = 1; + response = spec.buildRequests([bidRequestWithBidfloorEquals1], bidRequest2)[0]; + expect(response.data.imp[0].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor); + }); + + it('builds request banner object correctly', () => { + let response; + const bidRequestWithBanner = utils.deepClone(bidRequest); + bidRequestWithBanner.mediaTypes = { + banner: { + sizes: [[300, 250], [120, 600]] + } + }; + response = spec.buildRequests([bidRequestWithBanner], bidRequest)[0]; + expect(response.data.imp[0].banner.w).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][0]); + expect(response.data.imp[0].banner.h).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][1]); + expect(response.data.imp[0].banner.pos).to.equal(0); + expect(response.data.imp[0].banner.topframe).to.equal(0); + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos); + }); + + it('builds request video object correctly', () => { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + playerSize: [[302, 252]] + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][1]); + expect(response.data.imp[0].video.pos).to.equal(0); + bidRequestWithVideo.mediaTypes = { + video: { + playerSize: [302, 252] + } + }; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[1]); + + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.pos).to.equal(bidRequestWithPosEquals1.params.pos); + }); + + it('builds request video object correctly with context', () => { + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + context: 'instream' + } + }; + let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('outstream'); + + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal(null); + }); + + it('builds request video object correctly with multi-dimensions size array', () => { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes.video = { + playerSize: [[304, 254], [305, 255]], + context: 'instream' + }; + + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('outstream'); + + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal(null); + }); + + it('builds request with gdpr consent', () => { + let response = spec.buildRequests([bidRequest], bidRequest)[0]; + + expect(response.data.ext.gdpr_consent).to.not.equal(null).and.not.equal(undefined); + expect(response.data.ext).to.have.property('gdpr_consent'); + expect(response.data.ext.gdpr_consent.consent_string).to.equal('some string'); + expect(response.data.ext.gdpr_consent.consent_required).to.equal(true); + + expect(response.data.regs.ext.gdpr).to.not.equal(null).and.not.equal(undefined); + expect(response.data.user.ext.consent).to.equal('some string'); + }); + + it('build request with ID5 Id', () => { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.id5id = 'id5-user-id'; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'id5-user-id', + 'ext': { + 'rtiPartner': 'ID5ID' + } + }] + }]); + }); + + it('build request with unified Id', () => { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.tdid = 'tdid-user-id'; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'tdid-user-id', + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + }); + + describe('interpretResponse', () => { + it('returns an empty array on missing response', () => { + let response = spec.interpretResponse(undefined, {bidRequest: bannerBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + + response = spec.interpretResponse({}, {bidRequest: bannerBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + }); + + it('aggregates banner bids from all seat bids', () => { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: bannerBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const ad0 = response[0]; + expect(ad0.requestId).to.equal(bannerBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[1].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[1].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[1].bid[0].h); + expect(ad0.ttl).to.equal(TTL); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[1].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal(rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD'); + expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm); + expect(ad0.vastXml).to.be.an('undefined'); + expect(ad0.vastUrl).to.be.an('undefined'); + }); + + it('aggregates video bids from all seat bids', () => { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: videoBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const ad0 = response[0]; + expect(ad0.requestId).to.equal(videoBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[0].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[0].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[0].bid[0].h); + expect(ad0.ttl).to.equal(TTL); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[0].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal(rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD'); + expect(ad0.ad).to.be.an('undefined'); + expect(ad0.vastXml).to.equal(rtbResponse.seatbid[0].bid[0].adm); + expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[0].bid[0].ext.vast_url); + }); + + it('aggregates user-sync pixels', () => { + const response = spec.getUserSyncs({}, [{body: rtbResponse}]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(4); + expect(response[0].type).to.equal(rtbResponse.ext.utrk[0].type); + expect(response[0].url).to.equal('//rtb.gamoshi.io/user/sync/1?gdpr=0&consent=&usp='); + expect(response[1].type).to.equal(rtbResponse.ext.utrk[1].type); + expect(response[1].url).to.equal('//rtb.gamoshi.io/user/sync/2'); + expect(response[2].type).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].type); + expect(response[2].url).to.equal('//p.partner1.io/user/sync/1'); + expect(response[3].type).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].type); + expect(response[3].url).to.equal('//p.partner2.io/user/sync/1'); + }); + + it('supports configuring outstream renderers', () => { + const videoRequest = utils.deepClone(videoBidRequest); + videoRequest.mediaTypes.video.context = 'outstream'; + const result = spec.interpretResponse({body: videoResponse}, {bidRequest: videoRequest}); + expect(result[0].renderer).to.not.equal(undefined); + }); + + it('validates in/existing of gdpr consent', () => { + let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'gamoshiCCPA'); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gdpr=1&consent=consent%20string&us_privacy=gamoshiCCPA'); + + gdprConsent.gdprApplies = false; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'gamoshiCCPA'); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gdpr=0&consent=&us_privacy=gamoshiCCPA'); + + videoResponse.ext.utrk[0].url = 'https://rtb.gamoshi.io/pix/1275/scm'; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm'); + }); + + it('validates existence of gdpr, gdpr consent and usp consent', () => { + let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'gamoshiCCPA'); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gdpr=1&consent=consent%20string&us_privacy=gamoshiCCPA'); + + gdprConsent.gdprApplies = false; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, ''); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gdpr=0&consent=&us_privacy='); + }); + }); +}); diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js new file mode 100644 index 00000000000..d5c8d7bfb88 --- /dev/null +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -0,0 +1,1070 @@ +import { + deviceAccessHook, + setEnforcementConfig, + userSyncHook, + userIdHook, + makeBidRequestsHook, + validateRules, + enforcementRules, + purpose1Rule, + purpose2Rule, + enableAnalyticsHook +} from 'modules/gdprEnforcement.js'; +import { config } from 'src/config.js'; +import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; +import * as utils from 'src/utils.js'; +import { validateStorageEnforcement } from 'src/storageManager.js'; +import events from 'src/events.js'; + +describe('gdpr enforcement', function () { + let nextFnSpy; + let logWarnSpy; + let gdprDataHandlerStub; + let staticConfig = { + cmpApi: 'static', + timeout: 7500, + allowAuctionWithoutConsent: false, + consentData: { + getTCData: { + 'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A', + 'cmpId': 92, + 'cmpVersion': 100, + 'tcfPolicyVersion': 2, + 'gdprApplies': true, + 'isServiceSpecific': true, + 'useNonStandardStacks': false, + 'purposeOneTreatment': false, + 'publisherCC': 'US', + 'cmpStatus': 'loaded', + 'eventStatus': 'tcloaded', + 'outOfBand': { + 'allowedVendors': {}, + 'discloseVendors': {} + }, + 'purpose': { + 'consents': { + '1': true, + '2': true, + '3': true, + '7': true + }, + 'legitimateInterests': { + '1': false, + '2': true, + '3': false + } + }, + 'vendor': { + 'consents': { + '1': true, + '2': true, + '3': false, + '4': true, + '5': false + }, + 'legitimateInterests': { + '1': false, + '2': true, + '3': false, + '4': false, + '5': false + } + }, + 'specialFeatureOptins': { + '1': false, + '2': false + }, + 'restrictions': {}, + 'publisher': { + 'consents': { + '1': false, + '2': false, + '3': false + }, + 'legitimateInterests': { + '1': false, + '2': false, + '3': false + }, + 'customPurpose': { + 'consents': {}, + 'legitimateInterests': {} + } + } + } + } + }; + + after(function () { + validateStorageEnforcement.getHooks({ hook: deviceAccessHook }).remove(); + $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); + adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); + }) + + describe('deviceAccessHook', function () { + let adapterManagerStub; + + function getBidderSpec(gvlid) { + return { + getSpec: () => { + return { + gvlid + } + } + } + } + + beforeEach(function () { + nextFnSpy = sinon.spy(); + gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + logWarnSpy = sinon.spy(utils, 'logWarn'); + adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); + }); + afterEach(function () { + config.resetConfig(); + gdprDataHandler.getConsentData.restore(); + logWarnSpy.restore(); + adapterManagerStub.restore(); + }); + it('should not allow device access when device access flag is set to false', function () { + config.setConfig({ + deviceAccess: false, + consentManagement: { + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: false, + vendorExceptions: ['appnexus', 'rubicon'] + }] + } + } + }); + + deviceAccessHook(nextFnSpy); + expect(nextFnSpy.calledOnce).to.equal(true); + let result = { + hasEnforcementHook: true, + valid: false + } + sinon.assert.calledWith(nextFnSpy, undefined, undefined, result); + }); + + it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(5)); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: false, + vendorExceptions: ['appnexus'] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, 5, 'rubicon'); + expect(logWarnSpy.callCount).to.equal(0); + }); + + it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(3)); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, 3, 'rubicon'); + expect(logWarnSpy.callCount).to.equal(1); + }); + + it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = false; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + expect(nextFnSpy.calledOnce).to.equal(true); + let result = { + hasEnforcementHook: true, + valid: true + } + sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); + }); + + it('should use gvlMapping set by publisher', function() { + config.setConfig({ + 'gvlMapping': { + 'appnexus': 4 + } + }); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + expect(nextFnSpy.calledOnce).to.equal(true); + let result = { + hasEnforcementHook: true, + valid: true + } + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + config.resetConfig(); + }); + + it('should use gvl id of alias and not of parent', function() { + let curBidderStub = sinon.stub(config, 'getCurrentBidder'); + curBidderStub.returns('appnexus-alias'); + adapterManager.aliasBidAdapter('appnexus', 'appnexus-alias'); + config.setConfig({ + 'gvlMapping': { + 'appnexus-alias': 4 + } + }); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + expect(nextFnSpy.calledOnce).to.equal(true); + let result = { + hasEnforcementHook: true, + valid: true + } + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + config.resetConfig(); + curBidderStub.restore(); + }); + }); + + describe('userSyncHook', function () { + let curBidderStub; + let adapterManagerStub; + + beforeEach(function () { + gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + logWarnSpy = sinon.spy(utils, 'logWarn'); + curBidderStub = sinon.stub(config, 'getCurrentBidder'); + adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); + nextFnSpy = sinon.spy(); + }); + + afterEach(function () { + config.getCurrentBidder.restore(); + config.resetConfig(); + gdprDataHandler.getConsentData.restore(); + adapterManager.getBidAdapter.restore(); + logWarnSpy.restore(); + }); + + it('should allow bidder to do user sync if consent is true', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: ['sampleBidder2'] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + curBidderStub.returns('sampleBidder1'); + adapterManagerStub.withArgs('sampleBidder1').returns({ + getSpec: function () { + return { + 'gvlid': 1 + } + } + }); + userSyncHook(nextFnSpy); + + curBidderStub.returns('sampleBidder2'); + adapterManagerStub.withArgs('sampleBidder2').returns({ + getSpec: function () { + return { + 'gvlid': 3 + } + } + }); + userSyncHook(nextFnSpy); + expect(nextFnSpy.calledTwice).to.equal(true); + }); + + it('should not allow bidder to do user sync if user has denied consent', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + gdprDataHandlerStub.returns(consentData); + + curBidderStub.returns('sampleBidder1'); + adapterManagerStub.withArgs('sampleBidder1').returns({ + getSpec: function () { + return { + 'gvlid': 1 + } + } + }); + userSyncHook(nextFnSpy); + + curBidderStub.returns('sampleBidder2'); + adapterManagerStub.withArgs('sampleBidder2').returns({ + getSpec: function () { + return { + 'gvlid': 3 + } + } + }); + userSyncHook(nextFnSpy); + expect(nextFnSpy.calledOnce).to.equal(true); + expect(logWarnSpy.callCount).to.equal(1); + }); + + it('should not check vendor consent when enforceVendor is false', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: false, + vendorExceptions: ['sampleBidder1'] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + gdprDataHandlerStub.returns(consentData); + + curBidderStub.returns('sampleBidder1'); + adapterManagerStub.withArgs('sampleBidder1').returns({ + getSpec: function () { + return { + 'gvlid': 1 + } + } + }); + userSyncHook(nextFnSpy); + + curBidderStub.returns('sampleBidder2'); + adapterManagerStub.withArgs('sampleBidder2').returns({ + getSpec: function () { + return { + 'gvlid': 3 + } + } + }); + userSyncHook(nextFnSpy); + expect(nextFnSpy.calledTwice).to.equal(true); + expect(logWarnSpy.callCount).to.equal(0); + }); + }); + + describe('userIdHook', function () { + beforeEach(function () { + logWarnSpy = sinon.spy(utils, 'logWarn'); + nextFnSpy = sinon.spy(); + }); + afterEach(function () { + config.resetConfig(); + logWarnSpy.restore(); + }); + it('should allow user id module if consent is given', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + let submodules = [{ + submodule: { + gvlid: 1, + name: 'sampleUserId' + } + }] + userIdHook(nextFnSpy, submodules, consentData); + // Should pass back hasValidated flag since version 2 + const args = nextFnSpy.getCalls()[0].args; + expect(args[1].hasValidated).to.be.true; + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, submodules, { ...consentData, hasValidated: true }); + }); + + it('should allow userId module if gdpr not in scope', function () { + let submodules = [{ + submodule: { + gvlid: 1, + name: 'sampleUserId' + } + }]; + let consentData = null; + userIdHook(nextFnSpy, submodules, consentData); + // Should not pass back hasValidated flag since version 2 + const args = nextFnSpy.getCalls()[0].args; + expect(args[1]).to.be.null; + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, submodules, consentData); + }); + + it('should not allow user id module if user denied consent', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + + let submodules = [{ + submodule: { + gvlid: 1, + name: 'sampleUserId' + } + }, { + submodule: { + gvlid: 3, + name: 'sampleUserId1' + } + }] + userIdHook(nextFnSpy, submodules, consentData); + expect(logWarnSpy.callCount).to.equal(1); + let expectedSubmodules = [{ + submodule: { + gvlid: 1, + name: 'sampleUserId' + } + }] + sinon.assert.calledWith(nextFnSpy, expectedSubmodules, { ...consentData, hasValidated: true }); + }); + }); + + describe('makeBidRequestsHook', function () { + let sandbox; + let adapterManagerStub; + let emitEventSpy; + + const MOCK_AD_UNITS = [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [{ + bidder: 'bidder_1' // has consent + }, { + bidder: 'bidder_2' // doesn't have consent, but liTransparency is true. Bidder remains active. + }] + }, { + code: 'ad-unit-2', + mediaTypes: {}, + bids: [{ + bidder: 'bidder_2' + }, { + bidder: 'bidder_3' + }] + }]; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + gdprDataHandlerStub = sandbox.stub(gdprDataHandler, 'getConsentData'); + adapterManagerStub = sandbox.stub(adapterManager, 'getBidAdapter'); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + nextFnSpy = sandbox.spy(); + emitEventSpy = sandbox.spy(events, 'emit'); + }); + afterEach(function () { + config.resetConfig(); + sandbox.restore(); + }); + + it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is established)', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + const consentData = {}; + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('bidder_1').returns({ + getSpec: function () { + return { 'gvlid': 4 } + } + }); + adapterManagerStub.withArgs('bidder_2').returns({ + getSpec: function () { + return { 'gvlid': 5 } + } + }); + adapterManagerStub.withArgs('bidder_3').returns({ + getSpec: function () { + return { 'gvlid': undefined } + } + }); + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_1' }), + sinon.match({ bidder: 'bidder_2' }) + ] + }, { + code: 'ad-unit-2', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_2' }), + sinon.match({ bidder: 'bidder_3' }) // should be allowed even though it's doesn't have a gvlId because liTransparency is established. + ] + }], []); + }); + + it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function() { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: ['bidder_3'] + }] + } + }); + const consentData = {}; + + // set li for purpose 2 to false + const newConsentData = utils.deepClone(staticConfig); + newConsentData.consentData.getTCData.purpose.legitimateInterests['2'] = false; + + consentData.vendorData = newConsentData.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('bidder_1').returns({ + getSpec: function () { + return { 'gvlid': 4 } + } + }); + adapterManagerStub.withArgs('bidder_2').returns({ + getSpec: function () { + return { 'gvlid': 5 } + } + }); + adapterManagerStub.withArgs('bidder_3').returns({ + getSpec: function () { + return { 'gvlid': undefined } + } + }); + + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_1' }), // 'bidder_2' is not present because it doesn't have vendorConsent + ] + }, { + code: 'ad-unit-2', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_3' }), // 'bidder_3' is allowed despite gvlId being undefined because it's part of vendorExceptions + ] + }], []); + + expect(logWarnSpy.calledOnce).to.equal(true); + }); + + it('should skip validation checks if GDPR version is not equal to "2"', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforePurpose: false, + enforceVendor: false, + vendorExceptions: [] + }] + } + }); + + const consentData = {}; + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 1; + consentData.gdprApplies = true; + gdprDataHandlerStub.returns(consentData); + + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, sinon.match.array.deepEquals(MOCK_AD_UNITS), []); + expect(emitEventSpy.notCalled).to.equal(true); + expect(logWarnSpy.notCalled).to.equal(true); + }); + }); + + describe('enableAnalyticsHook', function () { + let sandbox; + let adapterManagerStub; + + const MOCK_ANALYTICS_ADAPTER_CONFIG = [{ + provider: 'analyticsAdapter_A', + options: {} + }, { + provider: 'analyticsAdapter_B', + options: {} + }, { + provider: 'analyticsAdapter_C', + options: {} + }]; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + gdprDataHandlerStub = sandbox.stub(gdprDataHandler, 'getConsentData'); + adapterManagerStub = sandbox.stub(adapterManager, 'getAnalyticsAdapter'); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + nextFnSpy = sandbox.spy(); + }); + + afterEach(function() { + config.resetConfig(); + sandbox.restore(); + }); + + it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function() { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'measurement', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: ['analyticsAdapter_B'] + }] + } + }); + + const consentData = {}; + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); + adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); + adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); + + enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + provider: 'analyticsAdapter_B', + options: {} + }, { + provider: 'analyticsAdapter_C', + options: {} + }]); + expect(logWarnSpy.calledOnce).to.equal(true); + }); + }); + + describe('validateRules', function () { + const createGdprRule = (purposeName = 'storage', enforcePurpose = true, enforceVendor = true, vendorExceptions = []) => ({ + purpose: purposeName, + enforcePurpose: enforcePurpose, + enforceVendor: enforceVendor, + vendorExceptions: vendorExceptions + }); + + const consentData = { + vendorData: staticConfig.consentData.getTCData, + apiVersion: 2, + gdprApplies: true + }; + + // Bidder - 'bidderA' has vendorConsent + const vendorAllowedModule = 'bidderA'; + const vendorAllowedGvlId = 1; + + // Bidder = 'bidderB' doesn't have vendorConsent + const vendorBlockedModule = 'bidderB'; + const vendorBlockedGvlId = 3; + + const consentDataWithPurposeConsentFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalse.vendorData.purpose.consents['1'] = false; + + it('should return true when enforcePurpose=true AND purposeConsent[p]==true AND enforceVendor[p,v]==true AND vendorConsent[v]==true', function () { + // 'enforcePurpose' and 'enforceVendor' both are 'true' + const gdprRule = createGdprRule('storage', true, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + + // case 3 - Purpose consent is 'false' but vendor consent is 'true'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=true AND purposeConsent[p]==true AND enforceVendor[p,v]==false', function () { + // 'enforcePurpose' is 'true' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('storage', true, false, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'true' because vendorConsent doens't matter + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'false' but vendor consent is 'true'. validateRules must return 'false' because vendorConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' and vendorConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=false AND enforceVendor[p,v]==true AND vendorConsent[v]==true', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('storage', false, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'false' because purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + + // case 3 - urpose consent is 'false' but vendor consent is 'true'. validateRules must return 'true' because purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' and purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=false AND enforceVendor[p,v]==false', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('storage', false, false, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true', both the consents do not matter. + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - urpose consent is 'false' but vendor consent is 'true'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + }); + + it('should return true when "vendorExceptions" contains the name of the vendor under test', function () { + // 'vendorExceptions' contains 'bidderB' which doesn't have vendor consent. + const gdprRule = createGdprRule('storage', false, true, [vendorBlockedModule]); + + /* 'bidderB' gets a free pass since it's included in the 'vendorExceptions' array. validateRules must disregard + user's choice for purpose and vendor consent and return 'true' for this bidder(s) */ + const isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + }); + + describe('Purpose 2 special case', function () { + const consentDataWithLIFalse = utils.deepClone(consentData); + consentDataWithLIFalse.vendorData.purpose.legitimateInterests['2'] = false; + + const consentDataWithPurposeConsentFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalse.vendorData.purpose.consents['2'] = false; + + const consentDataWithPurposeConsentFalseAndLIFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalseAndLIFalse.vendorData.purpose.legitimateInterests['2'] = false; + consentDataWithPurposeConsentFalseAndLIFalse.vendorData.purpose.consents['2'] = false; + + it('should return true when (enforcePurpose=true AND purposeConsent[p]===true AND enforceVendor[p.v]===true AND vendorConsent[v]===true) OR (purposesLITransparency[p]===true)', function () { + // both 'enforcePurpose' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('basicAds', true, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true', but legitimateInterests for purpose 2 is 'false'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false', but legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'true' and vendor consent is 'true', as well as legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Purpose consent is 'true' and vendor consent is 'false', and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when (enforcePurpose=true AND purposeConsent[p]===true AND enforceVendor[p.v]===false) OR (purposesLITransparency[p]===true)', function () { + // 'enforcePurpose' is 'true' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('basicAds', true, false, []); + + // case 1 - Purpose consent is 'true', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'false', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'false', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalseAndLIFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when (enforcePurpose=false AND enforceVendor[p,v]===true AND vendorConsent[v]===true) OR (purposesLITransparency[p]===true)', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('basicAds', false, true, []); + + // case - 1 Vendor consent is 'true', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Vendor consent is 'false', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Vendor consent is 'false', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + }); + }) + + describe('setEnforcementConfig', function () { + let sandbox; + const DEFAULT_RULES = [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }, { + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }]; + beforeEach(function () { + sandbox = sinon.createSandbox(); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + }); + afterEach(function () { + config.resetConfig(); + sandbox.restore(); + }); + + it('should enforce TCF2 Purpose1 and Purpose 2 if no "rules" found in the config', function () { + setEnforcementConfig({ + gdpr: { + cmpApi: 'iab', + allowAuctionWithoutConsent: true, + timeout: 5000 + } + }); + + expect(logWarnSpy.calledOnce).to.equal(true); + expect(enforcementRules).to.deep.equal(DEFAULT_RULES); + }); + + it('should enforce TCF2 Purpose 2 also if only Purpose 1 is defined in "rules"', function () { + const purpose1RuleDefinedInConfig = { + purpose: 'storage', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: ['bidderA'] + } + setEnforcementConfig({ + gdpr: { + rules: [purpose1RuleDefinedInConfig] + } + }); + + expect(purpose1Rule).to.deep.equal(purpose1RuleDefinedInConfig); + expect(purpose2Rule).to.deep.equal(DEFAULT_RULES[1]); + }); + + it('should enforce TCF2 Purpose 1 also if only Purpose 2 is defined in "rules"', function () { + const purpose2RuleDefinedInConfig = { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: ['bidderA'] + } + setEnforcementConfig({ + gdpr: { + rules: [purpose2RuleDefinedInConfig] + } + }); + + expect(purpose1Rule).to.deep.equal(DEFAULT_RULES[0]); + expect(purpose2Rule).to.deep.equal(purpose2RuleDefinedInConfig); + }); + + it('should use the "rules" defined in config if a definition found', function() { + const rules = [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: false + }, { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: false + }] + setEnforcementConfig({gdpr: { rules }}); + + expect(enforcementRules).to.deep.equal(rules); + }); + }); + + describe('TCF2FinalResults', function() { + let sandbox; + beforeEach(function() { + sandbox = sinon.createSandbox(); + sandbox.spy(events, 'emit'); + }); + afterEach(function() { + config.resetConfig(); + sandbox.restore(); + }); + it('should emit TCF2 enforcement data on auction end', function() { + const rules = [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: false + }, { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: false + }] + setEnforcementConfig({gdpr: { rules }}); + + events.emit('auctionEnd', {}) + + // Assertions + sinon.assert.calledWith(events.emit.getCall(1), 'tcf2Enforcement', sinon.match.object); + }) + }); +}); diff --git a/test/spec/modules/getintentBidAdapter_spec.js b/test/spec/modules/getintentBidAdapter_spec.js index e743d85972c..1959bda5c39 100644 --- a/test/spec/modules/getintentBidAdapter_spec.js +++ b/test/spec/modules/getintentBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { spec } from 'modules/getintentBidAdapter' +import { spec } from 'modules/getintentBidAdapter.js' describe('GetIntent Adapter Tests:', function () { const bidRequests = [{ diff --git a/test/spec/modules/glimpseBidAdapter_spec.js b/test/spec/modules/glimpseBidAdapter_spec.js new file mode 100644 index 00000000000..cc11efcb2af --- /dev/null +++ b/test/spec/modules/glimpseBidAdapter_spec.js @@ -0,0 +1,179 @@ +import { expect } from 'chai'; +import { spec } from 'modules/glimpseBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +/** + * Test Helpers + */ + +const API = 'https://api.glimpseprotocol.io/cloud/v1/vault/prebid'; + +const templateBidRequest = { + bidder: 'glimpse', + params: { + placementId: 'glimpse-demo-300x250', + }, + adUnitCode: 'banner-div-a', + sizes: [[300, 250]], + bidId: '26a80b71cfd671', + bidderRequestId: '133baeded6ac94', + auctionId: '96692a73-307b-44b8-8e4f-ddfb40341570', +}; + +const templateBidderRequest = { + bidderCode: 'glimpse', + auctionId: '96692a73-307b-44b8-8e4f-ddfb40341570', + bidderRequestId: '133baeded6ac94', + timeout: 3000, + gdprConsent: { + apiVersion: 2, + consentString: + 'COzP517OzP517AcABBENAlCsAP_AAAAAAAwIF8NX-T5eL2vju2Zdt7JEaYwfZxyigOgThgQIsW8NwIeFbBoGP2EgHBG4JCQAGBAkkgCBAQMsHGBcCQAAgIgRiRKMYE2MjzNKBJJAigkbc0FACDVunsHS2ZCY70-8O__bPAviADAvUC-AAAAA.YAAAAAAAAAAA', + gdprApplies: true, + vendorData: {}, + }, + refererInfo: { + referer: 'https://demo.glimpseprotocol.io/prebid/desktop', + reachedTop: true, + numIframes: 0, + stack: ['https://demo.glimpseprotocol.io/prebid/desktop'], + }, +}; + +const templateBidResponse = { + ad: '
HelloWorld
', + adUnitCode: 'banner-div-a', + bidder: 'glimpse', + cpm: 1.04, + creativeId: 'glimpse-demo-300x250', + currency: 'GBP', + height: 250, + mediaType: 'banner', + netRevenue: true, + pbAg: '1.04', + pbDg: '1.04', + pbHg: '1.04', + pbLg: '1.00', + pbMg: '1.05', + requestId: '133baeded6ac94', + ttl: 60, + width: 300, +}; + +const copyBidResponse = () => ({ ...templateBidResponse }); +const copyBidderRequest = () => ({ ...templateBidderRequest, bids: copyBidRequests() }); +const copyBidRequest = () => ({ ...templateBidRequest }); + +const copyBidRequests = () => [copyBidRequest()]; +const copyBidResponses = () => ({ + body: [copyBidResponse()], +}); + +/** + * Tests + */ + +describe('GlimpseProtocolAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + expect(adapter.getSpec).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when params.placementId is valid', function () { + expect(spec.isBidRequestValid(templateBidRequest)).to.equal(true); + }); + + it('should return false when params.placementId is invalid', function () { + let bid = copyBidRequest(); + delete bid.params; + bid.params = { + placementId: 0, + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when params is not passed', function () { + let bid = copyBidRequest(); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when params.placementId is not passed', function () { + let bid = copyBidRequest(); + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequest = copyBidRequest(); + const bidRequests = [bidRequest]; + + it('should add version and source information', function () { + 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 send request to API via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(API); + expect(request.method).to.equal('POST'); + }); + + it('should add GDPR consent', function () { + const bidderRequest = copyBidderRequest(); + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdprConsent).to.exist; + const { gdprConsent } = payload; + expect(gdprConsent.gdprApplies).to.be.true; + expect(gdprConsent.consentString).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('should add referer info', function () { + const bidderRequest = copyBidderRequest(); + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.refererInfo.referer).to.equal(templateBidderRequest.refererInfo.referer); + }); + }); + + describe('interpretResponse', function () { + it('should handle valid bid responses', function () { + const response = copyBidResponses(); + + const bids = spec.interpretResponse(response); + expect(bids).to.have.length(1); + expect(bids[0].adUnitCode).to.equal(templateBidRequest.adUnitCode); + }); + + it('should handle no bid responses', function () { + const response = copyBidResponses(); + response.body = []; + + const bids = spec.interpretResponse(response); + expect(bids).to.have.length(0); + }); + + it('should return no bid on an invalid response', function () { + const response = copyBidResponses(); + delete response.body; + + const bids = spec.interpretResponse(response); + expect(bids).to.have.length(0); + }); + }); +}); diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js new file mode 100644 index 00000000000..5de3db623c5 --- /dev/null +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -0,0 +1,162 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gmosspBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad'; + +describe('GmosspAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + bidder: 'gmossp', + params: { + sid: '123456' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + bidder: 'gmossp', + params: { + sid: '123456' + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [320, 50], + [320, 100], + ], + bidId: '2b84475b5b636e', + bidderRequestId: '1f4001782ac16c', + auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', + transactionId: '791e9d84-af92-4903-94da-24c7426d9d0c' + } + ]; + + const bidderRequest = { + refererInfo: { + referer: 'https://hoge.com' + } + }; + + it('sends bid request to ENDPOINT via GET', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].method).to.equal('GET'); + expect(requests[0].data).to.equal('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&url=https%3A%2F%2Fhoge.com&cur=JPY&dnt=0&'); + }); + }); + + describe('interpretResponse', function () { + const bidderRequests = [ + { + bidder: 'gmossp', + params: { + sid: '123456' + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [320, 50], + [320, 100], + ], + bidId: '2b84475b5b636e', + bidderRequestId: '1f4001782ac16c', + auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', + transactionId: '791e9d84-af92-4903-94da-24c7426d9d0c' + } + ]; + + it('should get correct banner bid response', function() { + const response = { + bid: '2b84475b5b636e', + price: 20, + w: 300, + h: 250, + ad: '
', + creativeId: '985ec572b32be309.76973017', + cur: 'JPY', + dealId: '', + imps: [ + 'https://sp.gmossp-sp.jp/hb/prebid/imp.ad' + ], + syncs: [ + 'https://sync.dsp.reemo-ad.jp' + ], + ttl: 300 + }; + + const expectedResponse = [ + { + requestId: '2b84475b5b636e', + cpm: 20, + currency: 'JPY', + width: 300, + height: 250, + ad: '
', + creativeId: '985ec572b32be309.76973017', + netRevenue: true, + ttl: 300 + } + ]; + + const result = spec.interpretResponse({ body: response }, bidderRequests); + expect(result).to.have.lengthOf(1); + + response.imps.forEach(impTracker => { + const tracker = utils.createTrackPixelHtml(impTracker); + expectedResponse[0].ad += tracker; + }); + + expect(result).to.deep.have.same.members(expectedResponse); + }); + + it('handles nobid responses', function () { + const response = ''; + + const result = spec.interpretResponse({ body: response }, bidderRequests); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse = { + body: { + ad: {}, + syncs: [ + 'https://hoge.com' + ] + } + }; + it('should return returns pixel syncs', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://hoge.com' + } + ]) + }) + }); +}); diff --git a/test/spec/modules/googleAnalyticsAdapter_spec.js b/test/spec/modules/googleAnalyticsAdapter_spec.js index 6bc0d4e192d..b801b5fe696 100644 --- a/test/spec/modules/googleAnalyticsAdapter_spec.js +++ b/test/spec/modules/googleAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import ga from 'modules/googleAnalyticsAdapter'; +import ga from 'modules/googleAnalyticsAdapter.js'; var assert = require('assert'); diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js new file mode 100644 index 00000000000..16b84467af2 --- /dev/null +++ b/test/spec/modules/gptPreAuction_spec.js @@ -0,0 +1,191 @@ +import { + appendGptSlots, + appendPbAdSlot, + _currentConfig, + makeBidRequestsHook +} from 'modules/gptPreAuction.js'; +import { config } from 'src/config.js'; +import { makeSlot } from '../integration/faker/googletag.js'; + +describe('GPT pre-auction module', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + config.setConfig({ gptPreAuction: { enabled: false } }); + }); + + const testSlots = [ + makeSlot({ code: 'slotCode1', divId: 'div1' }), + makeSlot({ code: 'slotCode2', divId: 'div2' }), + makeSlot({ code: 'slotCode3', divId: 'div3' }) + ]; + + describe('appendPbAdSlot', () => { + // sets up our document body to test the pbAdSlot dom actions against + document.body.innerHTML = '
test1
' + + '
test2
' + + '
test2
'; + + it('should be unchanged if already defined on adUnit', () => { + const adUnit = { fpd: { context: { pbAdSlot: '12345' } } }; + appendPbAdSlot(adUnit); + expect(adUnit.fpd.context.pbAdSlot).to.equal('12345'); + }); + + it('should use adUnit.code if matching id exists', () => { + const adUnit = { code: 'foo1', fpd: { context: {} } }; + appendPbAdSlot(adUnit); + expect(adUnit.fpd.context.pbAdSlot).to.equal('bar1'); + }); + + it('should use the gptSlot.adUnitPath if the adUnit.code matches a div id but does not have a data-adslotid', () => { + const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, fpd: { context: { adServer: { name: 'gam', adSlot: '/baz' } } } }; + appendPbAdSlot(adUnit); + expect(adUnit.fpd.context.pbAdSlot).to.equal('/baz'); + }); + + it('should use the video adUnit.code (which *should* match the configured "adSlotName", but is not being tested) if there is no matching div with "data-adslotid" defined', () => { + const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, fpd: { context: {} } }; + adUnit.code = 'foo5'; + appendPbAdSlot(adUnit, undefined); + expect(adUnit.fpd.context.pbAdSlot).to.equal('foo5'); + }); + + it('should use the adUnit.code if all other sources failed', () => { + const adUnit = { code: 'foo4', fpd: { context: {} } }; + appendPbAdSlot(adUnit, undefined); + expect(adUnit.fpd.context.pbAdSlot).to.equal('foo4'); + }); + + it('should use the customPbAdSlot function if one is given', () => { + config.setConfig({ + gptPreAuction: { + customPbAdSlot: () => 'customPbAdSlotName' + } + }); + + const adUnit = { code: 'foo1', fpd: { context: {} } }; + appendPbAdSlot(adUnit); + expect(adUnit.fpd.context.pbAdSlot).to.equal('customPbAdSlotName'); + }); + }); + + describe('appendGptSlots', () => { + it('should not add adServer object to context if no slots defined', () => { + const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + appendGptSlots([adUnit]); + expect(adUnit.fpd.context.adServer).to.be.undefined; + }); + + it('should not add adServer object to context if no slot matches', () => { + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + appendGptSlots([adUnit]); + expect(adUnit.fpd.context.adServer).to.be.undefined; + }); + + it('should add adServer object to context if matching slot is found', () => { + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'slotCode2', fpd: { context: {} } }; + appendGptSlots([adUnit]); + expect(adUnit.fpd.context.adServer).to.be.an('object'); + expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode2' }); + }); + + it('should use the customGptSlotMatching function if one is given', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: slot => + adUnitCode => adUnitCode.toUpperCase() === slot.getAdUnitPath().toUpperCase() + } + }); + + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'SlOtCoDe1', fpd: { context: {} } }; + appendGptSlots([adUnit]); + expect(adUnit.fpd.context.adServer).to.be.an('object'); + expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode1' }); + }); + }); + + describe('handleSetGptConfig', () => { + it('should enable the module by default', () => { + config.setConfig({ gptPreAuction: {} }); + expect(_currentConfig.enabled).to.equal(true); + }); + + it('should disable the module if told to in set config', () => { + config.setConfig({ gptPreAuction: { enabled: false } }); + expect(_currentConfig).to.be.an('object').that.is.empty; + }); + + it('should accept custom functions in config', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: () => 'customGptSlot', + customPbAdSlot: () => 'customPbAdSlot' + } + }); + + expect(_currentConfig.enabled).to.equal(true); + expect(_currentConfig.customGptSlotMatching).to.a('function'); + expect(_currentConfig.customPbAdSlot).to.a('function'); + expect(_currentConfig.customGptSlotMatching()).to.equal('customGptSlot'); + expect(_currentConfig.customPbAdSlot()).to.equal('customPbAdSlot'); + }); + + it('should check that custom functions in config are type function', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: 12345, + customPbAdSlot: 'test' + } + }); + expect(_currentConfig).to.deep.equal({ + enabled: true, + customGptSlotMatching: false, + customPbAdSlot: false + }); + }); + }); + + describe('makeBidRequestsHook', () => { + let returnedAdUnits; + const runMakeBidRequests = adUnits => { + const next = adUnits => { + returnedAdUnits = adUnits; + }; + makeBidRequestsHook(next, adUnits); + }; + + it('should append PB Ad Slot and GPT Slot info to first-party data in each ad unit', () => { + const testAdUnits = [{ + code: 'adUnit1', + fpd: { context: { pbAdSlot: '12345' } } + }, { + code: 'slotCode1', + fpd: { context: { pbAdSlot: '67890' } } + }, { + code: 'slotCode3', + }]; + + const expectedAdUnits = [{ + code: 'adUnit1', + fpd: { context: { pbAdSlot: '12345' } } + }, { + code: 'slotCode1', + fpd: { context: { pbAdSlot: '67890', adServer: { name: 'gam', adSlot: 'slotCode1' } } } + }, { + code: 'slotCode3', + fpd: { context: { pbAdSlot: 'slotCode3', adServer: { name: 'gam', adSlot: 'slotCode3' } } } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + }); +}); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index e1411da6e5a..344f1764c05 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { spec, resetUserSync, getSyncUrl } from 'modules/gridBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec, resetUserSync, getSyncUrl } from 'modules/gridBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; describe('TheMediaGrid Adapter', function () { const adapter = newBidder(spec); @@ -68,6 +69,14 @@ describe('TheMediaGrid Adapter', function () { }, 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [400, 600] + }, + 'banner': { + 'sizes': [[728, 90]] + } + }, 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -79,6 +88,14 @@ describe('TheMediaGrid Adapter', function () { }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'video': { + 'playerSize': [400, 600] + }, + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '42dbe3a7168a6a', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -86,7 +103,7 @@ describe('TheMediaGrid Adapter', function () { ]; it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const [request] = spec.buildRequests([bidRequests[0]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -97,18 +114,28 @@ describe('TheMediaGrid Adapter', function () { expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); }); + it('sizes must be added from mediaTypes', function () { + const [request] = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('auids', '1,1'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90,400x600'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + it('sizes must not be duplicated', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); + const [request] = spec.buildRequests(bidRequests, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('auids', '1,1,2'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90,400x600'); expect(payload).to.have.property('r', '22edbae2733bf6'); }); it('if gdprConsent is present payload must have gdpr params', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); + const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -117,7 +144,7 @@ describe('TheMediaGrid Adapter', function () { }); it('if gdprApplies is false gdpr_applies must be 0', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); @@ -125,12 +152,432 @@ describe('TheMediaGrid Adapter', function () { }); it('if gdprApplies is undefined gdpr_applies must be 1', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); + const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); expect(payload).to.have.property('gdpr_applies', '1'); }); + + it('if usPrivacy is present payload must have us_privacy param', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const [request] = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('us_privacy', '1YNN'); + }); + + it('should convert keyword params to proper form and attaches to request', function () { + const bidRequestWithKeywords = [].concat(bidRequests); + bidRequestWithKeywords[1] = Object.assign({}, + bidRequests[1], + { + params: { + uid: '1', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [3], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + emptyStr: '', + emptyArr: [''], + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const [request] = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.keywords).to.be.an('string'); + payload.keywords = JSON.parse(payload.keywords); + + expect(payload.keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['3'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + }]); + }); + + it('should mix keyword param with keywords from config', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'fpd.user' ? {'keywords': ['a', 'b']} : arg === 'fpd.context' ? {'keywords': ['any words']} : null); + + const bidRequestWithKeywords = [].concat(bidRequests); + bidRequestWithKeywords[1] = Object.assign({}, + bidRequests[1], + { + params: { + uid: '1', + keywords: { + single: 'val', + singleArr: ['val'], + multiValMixed: ['value1', 2, 'value3'] + } + } + } + ); + + const [request] = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.keywords).to.be.an('string'); + payload.keywords = JSON.parse(payload.keywords); + + expect(payload.keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'user', + 'value': ['a', 'b'] + }, { + 'key': 'context', + 'value': ['any words'] + }]); + + getConfigStub.restore(); + }); + }); + + describe('buildRequests in new format', function () { + function parseRequest(data) { + return JSON.parse(data); + } + const bidderRequest = { + refererInfo: {referer: 'https://example.com'}, + bidderRequestId: '22edbae2733bf6', + auctionId: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + timeout: 3000 + }; + const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + let bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '11', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [[400, 600]], + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '3', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [[400, 600]] + }, + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + } + ]; + + it('should attach valid params to the tag', function () { + const [request] = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); + }); + + it('make possible to process request without mediaTypes', function () { + const [request] = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); + }); + + it('should attach valid params to the video tag', function () { + const [request] = spec.buildRequests(bidRequests.slice(0, 3), bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }] + }); + }); + + it('should support mixed mediaTypes', function () { + const [request] = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + } + }, { + 'id': bidRequests[3].bidId, + 'tagid': bidRequests[3].params.uid, + 'ext': {'divid': bidRequests[3].adUnitCode}, + 'banner': { + 'w': 728, + 'h': 90, + 'format': [{'w': 728, 'h': 90}] + }, + 'video': { + 'w': 400, + 'h': 600 + } + }] + }); + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); + const [request] = spec.buildRequests(bidRequests, gdprBidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext).to.have.property('consent', 'AAA'); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + + it('if usPrivacy is present payload must have us_privacy param', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const [request] = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); + }); + + it('if userId is present payload must have user.ext param with right keys', function () { + const bidRequestsWithUserIds = bidRequests.map((bid) => { + return Object.assign({ + userId: { + id5id: 'id5id_1', + tdid: 'tdid_1', + digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + lipb: {lipbid: 'lipb_1'} + } + }, bid); + }); + const [request] = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext).to.have.property('unifiedid', 'tdid_1'); + expect(payload.user.ext).to.have.property('id5id', 'id5id_1'); + expect(payload.user.ext).to.have.property('digitrustid', 'DTID'); + expect(payload.user.ext).to.have.property('liveintentid', 'lipb_1'); + }); + + it('if schain is present payload must have source.ext.schain param', function () { + const schain = { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + }; + const bidRequestsWithSChain = bidRequests.map((bid) => { + return Object.assign({ + schain: schain + }, bid); + }); + const [request] = spec.buildRequests(bidRequestsWithSChain, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('source'); + expect(payload.source).to.have.property('ext'); + expect(payload.source.ext).to.have.property('schain'); + expect(payload.source.ext.schain).to.deep.equal(schain); + }); }); describe('interpretResponse', function () { @@ -159,7 +606,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1cbd2feafe5e8b', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '659423fff799cb', @@ -217,7 +664,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1fa09aee5c8c99', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '300bfeb0d71a5b', @@ -306,7 +753,7 @@ describe('TheMediaGrid Adapter', function () { {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 12, content_type: 'video'}], 'seat': '2'} ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '659423fff799cb', @@ -367,7 +814,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1fa09aee5c84d34', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); expect(result.length).to.equal(0); }); @@ -437,7 +884,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '32a1f276cb87cb8', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '2164be6358b9', @@ -541,7 +988,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '35bcbc0f7e79c', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '5126e301f4be', @@ -663,5 +1110,11 @@ describe('TheMediaGrid Adapter', function () { type: 'image', url: syncUrl }); }); + + it('should pass usPrivacy param if it is available', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1YNN')).to.deep.equal({ + type: 'image', url: `${syncUrl}&us_privacy=1YNN` + }); + }); }); }); diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js new file mode 100644 index 00000000000..0dbaac0c526 --- /dev/null +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -0,0 +1,461 @@ +import { expect } from 'chai'; +import { spec, resetUserSync, getSyncUrl } from 'modules/gridNMBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('TheMediaGridNM Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + const paramsList = [ + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + } + }, + { + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + { + 'source': 'jwp', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + { + 'source': 'jwp', + 'secid': '11', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + } + ]; + paramsList.forEach((params) => { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + it('should return false when required params has invalid values', function () { + const paramsList = [ + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': '1,2,3,4,5' + } + }, + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': [1, 2], + 'protocols': [1, 2, 3, 4, 5] + } + }, + { + 'source': 'jwp', + 'secid': 11, + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5] + } + }, + { + 'source': 111, + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5] + } + } + ]; + + paramsList.forEach((params) => { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + }); + + describe('buildRequests', function () { + function parseRequestUrl(url) { + const res = {}; + url.replace(/^[^\?]+\?/, '').split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + const bidderRequest = {refererInfo: {referer: 'https://example.com'}}; + const referrer = bidderRequest.refererInfo.referer; + let bidRequests = [ + { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3], + 'skip': 1 + } + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + const requestsSizes = ['300x250,300x600', '728x90']; + requests.forEach((req, i) => { + expect(req.url).to.be.an('string'); + const payload = parseRequestUrl(req.url); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('wrapperType', 'Prebid_js'); + expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); + expect(payload).to.have.property('sizes', requestsSizes[i]); + expect(req.data).to.deep.equal(bidRequests[i].params); + }); + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const [request] = spec.buildRequests([bidRequests[0]], {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); + expect(request.url).to.be.an('string'); + const payload = parseRequestUrl(request.url); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if gdprApplies is false gdpr_applies must be 0', function () { + const [request] = spec.buildRequests([bidRequests[0]], {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + expect(request.url).to.be.an('string'); + const payload = parseRequestUrl(request.url); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '0'); + }); + + it('if gdprApplies is undefined gdpr_applies must be 1', function () { + const [request] = spec.buildRequests([bidRequests[0]], {gdprConsent: {consentString: 'AAA'}}); + expect(request.url).to.be.an('string'); + const payload = parseRequestUrl(request.url); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if usPrivacy is present payload must have us_privacy param', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const [request] = spec.buildRequests([bidRequests[0]], bidderRequestWithUSP); + expect(request.url).to.be.an('string'); + const payload = parseRequestUrl(request.url); + expect(payload).to.have.property('us_privacy', '1YNN'); + }); + }); + + describe('interpretResponse', function () { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '2'}, + {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 600, 'w': 300}], 'seat': '2'}, + {'bid': [{'price': 0, 'h': 250, 'w': 300}], 'seat': '2'}, + {'bid': [{'price': 0, 'adm': '\n<\/Ad>\n<\/VAST>', 'h': 250, 'w': 300}], 'seat': '2'}, + undefined, + {'bid': [], 'seat': '2'}, + {'seat': '2'}, + ]; + + it('should get correct video bid response', function () { + const bidRequests = [ + { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5], + 'skip': 1 + } + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2bc598e42b6a', + 'bidderRequestId': '1e8b5a465f404', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + } + ]; + const requests = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': '5f2009617a7c0a', + 'dealId': 11, + 'width': 300, + 'height': 250, + 'bidderCode': 'gridNM', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': false, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + }, + { + 'requestId': '2bc598e42b6a', + 'cpm': 0.5, + 'creativeId': '1e8b5a465f404', + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'gridNM', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': false, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + requests.forEach((req, i) => { + const result = spec.interpretResponse({'body': {'seatbid': [responses[i]]}}, req); + expect(result[0]).to.deep.equal(expectedResponse[i]); + }); + }); + + it('handles wrong and nobid responses', function () { + responses.slice(2).forEach((resp) => { + const request = spec.buildRequests([{ + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5], + 'skip': 1 + } + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2bc598e42b6a', + 'bidderRequestId': '39d74f5b71464', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }]); + const result = spec.interpretResponse({'body': {'seatbid': [resp]}}, request[0]); + expect(result.length).to.equal(0); + }); + }); + }); + + describe('user sync', function () { + const syncUrl = getSyncUrl(); + + beforeEach(function () { + resetUserSync(); + }); + + it('should register the Emily iframe', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + + expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); + }); + + it('should not register the Emily iframe more than once', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); + + // when called again, should still have only been called once + syncs = spec.getUserSyncs(); + expect(syncs).to.equal(undefined); + }); + + it('should pass gdpr params if consent is true', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: true, consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr=1&gdpr_consent=foo` + }); + }); + + it('should pass gdpr params if consent is false', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: false, consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr=0&gdpr_consent=foo` + }); + }); + + it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr_consent=foo` + }); + }); + + it('should pass no params if gdpr consentString is not defined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {})).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is a number', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: 0 + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is null', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: null + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is a object', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: {} + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr is not defined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined)).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass usPrivacy param if it is available', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1YNN')).to.deep.equal({ + type: 'image', url: `${syncUrl}&us_privacy=1YNN` + }); + }); + }); +}); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index cbd71cc82f0..af929f437da 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -1,8 +1,9 @@ import { expect } from 'chai'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { spec } from 'modules/gumgumBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { spec } from 'modules/gumgumBidAdapter.js'; const ENDPOINT = 'https://g2.gumgum.com/hbid/imp'; +const JCSI = { t: 0, rq: 8, pbv: '$prebid.version$' } describe('gumgumAdapter', function () { const adapter = newBidder(spec); @@ -63,6 +64,20 @@ describe('gumgumAdapter', function () { }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false if invalid request id is found', function () { + const bidRequest = { + id: 12345, + sizes: [[300, 250], [1, 1]], + url: ENDPOINT, + method: 'GET', + pi: 3, + data: { t: '10433394' } + }; + let body; + spec.interpretResponse({ body }, bidRequest); // empty response + expect(spec.isBidRequestValid(bid)).to.be.equal(false); + }); }); describe('buildRequests', function () { @@ -99,6 +114,53 @@ describe('gumgumAdapter', function () { } } ]; + const vidMediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + minduration: 1, + maxduration: 2, + linearity: 1, + startdelay: 1, + placement: 123456, + protocols: [1, 2] + } + }; + + it('should return a defined sizes field for video', function () { + const request = { ...bidRequests[0], mediaTypes: vidMediaTypes, params: { 'videoPubID': 123 } }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.sizes).to.equal(vidMediaTypes.video.playerSize); + }); + + describe('floorModule', function () { + const floorTestData = { + 'currency': 'USD', + 'floor': 1.50 + }; + bidRequests[0].getFloor = _ => { + return floorTestData; + }; + it('should return the value from getFloor if present', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.fp).to.equal(floorTestData.floor); + }); + it('should return the getFloor.floor value if it is greater than bidfloor', function () { + const bidfloor = 0.80; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.fp).to.equal(floorTestData.floor); + }); + it('should return the bidfloor value if it is greater than getFloor.floor', function () { + const bidfloor = 1.80; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.fp).to.equal(bidfloor); + }); + }); it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests)[0]; @@ -106,7 +168,7 @@ describe('gumgumAdapter', function () { expect(request.method).to.equal('GET'); expect(request.id).to.equal('30b31c1838de1e'); }); - it('should correctly set the request paramters depending on params field', function () { + it('should set t and fp parameters in bid request if inScreen request param is found', function () { const request = Object.assign({}, bidRequests[0]); delete request.params; request.params = { @@ -129,7 +191,14 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.pubId).to.equal(request.params.inScreenPubID); expect(bidRequest.data).to.not.include.any.keys('t'); }); - it('should correctly set the request paramters depending on params field', function () { + it('should send pubId if videoPubID param is specified', function () { + const request = { ...bidRequests[0], mediaTypes: vidMediaTypes, params: { 'videoPubID': 123 } }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.include.any.keys('pubId'); + expect(bidRequest.data.pubId).to.equal(request.params.videoPubID); + expect(bidRequest.data).to.not.include.any.keys('t'); + }); + it('should set a ni parameter in bid request if ICV request param is found', function () { const request = Object.assign({}, bidRequests[0]); delete request.params; request.params = { @@ -139,6 +208,68 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.pi).to.equal(5); expect(bidRequest.data).to.include.any.keys('ni'); }); + it('should add parameters associated with video if video request param is found', function () { + const videoVals = { + playerSize: [640, 480], + context: 'instream', + minduration: 1, + maxduration: 2, + linearity: 1, + startdelay: 1, + placement: 123456, + protocols: [1, 2] + }; + const request = Object.assign({}, bidRequests[0]); + delete request.params; + request.mediaTypes = { + video: videoVals + }; + request.params = { + 'video': '10433395' + }; + const bidRequest = spec.buildRequests([request])[0]; + // 7 is video product line + expect(bidRequest.data.pi).to.eq(7); + expect(bidRequest.data.mind).to.eq(videoVals.minduration); + expect(bidRequest.data.maxd).to.eq(videoVals.maxduration); + expect(bidRequest.data.li).to.eq(videoVals.linearity); + expect(bidRequest.data.sd).to.eq(videoVals.startdelay); + expect(bidRequest.data.pt).to.eq(videoVals.placement); + expect(bidRequest.data.pr).to.eq(videoVals.protocols.join(',')); + expect(bidRequest.data.viw).to.eq(videoVals.playerSize[0].toString()); + expect(bidRequest.data.vih).to.eq(videoVals.playerSize[1].toString()); + }); + it('should add parameters associated with invideo if invideo request param is found', function () { + const inVideoVals = { + playerSize: [640, 480], + context: 'instream', + minduration: 1, + maxduration: 2, + linearity: 1, + startdelay: 1, + placement: 123456, + protocols: [1, 2] + }; + const request = Object.assign({}, bidRequests[0]); + delete request.params; + request.mediaTypes = { + video: inVideoVals + }; + request.params = { + 'inVideo': '10433395' + }; + const bidRequest = spec.buildRequests([request])[0]; + // 6 is invideo product line + expect(bidRequest.data.pi).to.eq(6); + expect(bidRequest.data.mind).to.eq(inVideoVals.minduration); + expect(bidRequest.data.maxd).to.eq(inVideoVals.maxduration); + expect(bidRequest.data.li).to.eq(inVideoVals.linearity); + expect(bidRequest.data.sd).to.eq(inVideoVals.startdelay); + expect(bidRequest.data.pt).to.eq(inVideoVals.placement); + expect(bidRequest.data.pr).to.eq(inVideoVals.protocols.join(',')); + expect(bidRequest.data.viw).to.eq(inVideoVals.playerSize[0].toString()); + expect(bidRequest.data.vih).to.eq(inVideoVals.playerSize[1].toString()); + }); it('should not add additional parameters depending on params field', function () { const request = spec.buildRequests(bidRequests)[0]; expect(request.data).to.not.include.any.keys('ni'); @@ -146,7 +277,7 @@ describe('gumgumAdapter', function () { expect(request.data).to.not.include.any.keys('eAdBuyId'); expect(request.data).to.not.include.any.keys('adBuyId'); }); - it('should add consent parameters if gdprConsent is present', function () { + it('should add gdpr consent parameters if gdprConsent is present', function () { const gdprConsent = { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', gdprApplies: true }; const fakeBidRequest = { gdprConsent: gdprConsent }; const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; @@ -159,6 +290,14 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data).to.not.include.any.keys('gdprConsent') }); + it('should add uspConsent parameter if it is present in the bidderRequest', function () { + const noUspBidRequest = spec.buildRequests(bidRequests)[0]; + const uspConsentObj = { uspConsent: '1YYY' }; + const bidRequest = spec.buildRequests(bidRequests, uspConsentObj)[0]; + expect(noUspBidRequest.data).to.not.include.any.keys('uspConsent'); + expect(bidRequest.data).to.include.any.keys('uspConsent'); + expect(bidRequest.data.uspConsent).to.eq(uspConsentObj.uspConsent); + }); it('should add a tdid parameter if request contains unified id from TradeDesk', function () { const unifiedId = { 'userId': { @@ -190,6 +329,14 @@ describe('gumgumAdapter', function () { expect(bidRequest.data).to.not.include.any.keys('ns'); } }); + it('adds jcsi param with correct keys', function () { + const expectedKeys = Object.keys(JCSI).sort(); + const jcsi = JSON.stringify(JCSI); + const bidRequest = spec.buildRequests(bidRequests)[0]; + const actualKeys = Object.keys(JSON.parse(bidRequest.data.jcsi)).sort(); + expect(actualKeys).to.eq(actualKeys); + expect(bidRequest.data.jcsi).to.eq(jcsi); + }); }) describe('interpretResponse', function () { @@ -213,6 +360,7 @@ describe('gumgumAdapter', function () { 'css': 'html { overflow-y: auto }', 'js': 'console.log("environment", env);' }, + 'jcsi': { t: 0, rq: 8 }, 'thms': 10000 } let bidRequest = { @@ -222,24 +370,33 @@ describe('gumgumAdapter', function () { method: 'GET', pi: 3 } + 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 + }; it('should get correct bid response', function () { - 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 - }; expect(spec.interpretResponse({ body: serverResponse }, bidRequest)).to.deep.equal([expectedResponse]); }); + it('should pass correct currency if found in bid response', function () { + const cur = 'EURO'; + let response = Object.assign({}, serverResponse); + let expected = Object.assign({}, expectedResponse); + response.ad.cur = cur; + expected.currency = cur; + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); + }); + it('handles nobid responses', function () { let response = { 'ad': {}, @@ -255,6 +412,12 @@ describe('gumgumAdapter', function () { expect(result.length).to.equal(0); }); + it('handles empty response', function () { + let body; + let result = spec.interpretResponse({ body }, bidRequest); + expect(result.length).to.equal(0); + }); + it('returns 1x1 when eligible product and size available', function () { let inscreenBidRequest = { id: 12346, @@ -290,7 +453,14 @@ describe('gumgumAdapter', function () { let result = spec.interpretResponse({ body: inscreenServerResponse }, inscreenBidRequest); expect(result[0].width).to.equal('1'); expect(result[0].height).to.equal('1'); - }) + }); + + it('updates jcsi object when the server response jcsi prop is found', function () { + const response = Object.assign({ cw: 'AD_JSON' }, serverResponse); + const bidResponse = spec.interpretResponse({ body: response }, bidRequest)[0].ad; + const decodedResponse = JSON.parse(atob(bidResponse)); + expect(decodedResponse.jcsi).to.eql(JCSI); + }); }) describe('getUserSyncs', function () { const syncOptions = { diff --git a/test/spec/modules/h12mediaBidAdapter_spec.js b/test/spec/modules/h12mediaBidAdapter_spec.js new file mode 100644 index 00000000000..08a83ce981f --- /dev/null +++ b/test/spec/modules/h12mediaBidAdapter_spec.js @@ -0,0 +1,388 @@ +import {expect} from 'chai'; +import {spec} from 'modules/h12mediaBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('H12 Media Adapter', function () { + const DEFAULT_CURRENCY = 'USD'; + const DEFAULT_TTL = 360; + const DEFAULT_NET_REVENUE = false; + const adapter = newBidder('spec'); + + const validBid = { + adUnitCode: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidder: 'h12media', + bidId: '1c5e8a1a84522d', + bidderRequestId: '1d0c4017f02458', + auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f313', + params: { + pubid: 123321, + }, + }; + + const validBid2 = { + adUnitCode: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + banner: {} + }, + bidder: 'h12media', + bidId: '2c5e8a1a84522d', + bidderRequestId: '2d0c4017f02458', + auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314', + params: { + pubid: 123321, + size: '100x200' + }, + }; + + const invalidBid = { + adUnitCode: 'div-gpt-ad-1460505748561-2', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidder: 'h12media', + bidId: '3c5e8a1a84522d', + bidderRequestId: '3d0c4017f02458', + auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f315', + }; + + const bidderRequest = { + refererInfo: { + referer: 'https://localhost' + }, + gdprConsent: { + gdprApplies: 1, + consentString: 'concentDataString', + vendorData: { + vendorConsents: { + '90': 1 + }, + }, + }, + uspConsent: 'consentUspString' + }; + + const serverResponse = { + currency: 'EUR', + netRevenue: true, + ttl: 500, + bids: [{ + bidId: validBid.bidId, + cpm: 0.33, + width: 300, + height: 600, + creativeId: '335566', + ad: '
my ad
', + usersync: [ + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} + ], + meta: { + advertiserId: '54321', + advertiserName: 'My advertiser', + advertiserDomains: ['test.com'] + } + }] + }; + + const serverResponse2 = { + bids: [{ + bidId: validBid2.bidId, + cpm: 0.33, + width: 300, + height: 600, + creativeId: '335566', + ad: '
my ad 2
', + }] + }; + + function removeElement(id) { + if (document.getElementById(id)) { + document.body.removeChild(document.getElementById(id)); + } + } + + function createElement(id) { + const div = document.createElement('div'); + div.id = id; + div.style.width = '50px'; + div.style.height = '50px'; + if (frameElement) { + frameElement.style.width = '100px'; + frameElement.style.height = '100px'; + } + div.style.background = 'black'; + document.body.appendChild(div); + return div; + } + function createElementVisible(id) { + const element = createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 10, + y: 10, + }); + return element; + } + function createElementInvisible(id) { + const element = document.createElement('div'); + element.id = id; + document.body.appendChild(element); + element.style.display = 'none'; + return element; + } + + function createElementHidden(id) { + const element = createElement(id); + document.body.appendChild(element); + element.style.visibility = 'hidden'; + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 100, + y: 100, + }); + return element; + } + + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + removeElement(validBid.adUnitCode); + removeElement(validBid2.adUnitCode); + removeElement(invalidBid.adUnitCode); + sandbox.restore(); + }); + + after(function() { + sandbox.reset(); + }) + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when bid is valid', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when bid does not have pubid parameter', function () { + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('should return adUnit size', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests.data; + + expect(requestsData.bidrequests[0]).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); + }); + + it('should return empty bid size', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests.data; + + expect(requestsData.bidrequests[1]).to.deep.include({adunitSize: []}); + }); + + it('should return bid size from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests.data; + + expect(requestsData.bidrequests[1]).to.include({size: validBid2.params.size}); + }); + + it('should return GDPR info', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests.data; + + expect(requestsData).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); + }); + + it('should not have error on empty GDPR', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidderRequestWithoutGDRP = {...bidderRequest, gdprConsent: null}; + const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutGDRP); + const requestsData = requests.data; + + expect(requestsData).to.include({gdpr: false}); + }); + + it('should create single POST', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + + expect(requests.method).to.equal('POST'); + }); + }); + + describe('creative viewability', function () { + it('should return coords', function () { + createElementVisible(validBid.adUnitCode); + const requests = spec.buildRequests([validBid], bidderRequest); + const requestsData = requests.data; + + expect(requestsData.bidrequests[0]).to.deep.include({coords: {x: 10, y: 10}}); + }); + + it('should define not iframe', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests.data; + + expect(requestsData).to.include({isiframe: false}); + }); + + it('should define visible element', function () { + createElementVisible(validBid.adUnitCode); + const requests = spec.buildRequests([validBid], bidderRequest); + const requestsData = requests.data; + + expect(requestsData.bidrequests[0]).to.include({ishidden: false}); + }); + + it('should define invisible element', function () { + createElementInvisible(validBid.adUnitCode); + const requests = spec.buildRequests([validBid], bidderRequest); + const requestsData = requests.data; + + expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + }); + + it('should define hidden element', function () { + createElementHidden(validBid.adUnitCode); + const requests = spec.buildRequests([validBid], bidderRequest); + const requestsData = requests.data; + + expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + }); + }); + + describe('interpretResponse', function () { + it('should return no bids if the response is not valid', function () { + const bidResponse = spec.interpretResponse({ body: null }, validBid); + + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response is empty', function () { + const bidResponse = spec.interpretResponse({ body: [] }, { validBid }); + + expect(bidResponse.length).to.equal(0); + }); + + it('should return valid bid responses', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const request = spec.buildRequests([validBid, validBid2], bidderRequest); + const bidResponse = spec.interpretResponse({body: serverResponse}, request); + + expect(bidResponse[0]).to.deep.include({ + requestId: validBid.bidId, + ad: serverResponse.bids[0].ad, + mediaType: 'banner', + creativeId: serverResponse.bids[0].creativeId, + cpm: serverResponse.bids[0].cpm, + width: serverResponse.bids[0].width, + height: serverResponse.bids[0].height, + currency: 'EUR', + netRevenue: true, + ttl: 500, + meta: serverResponse.bids[0].meta, + pubid: validBid.params.pubid + }); + }); + + it('should return default bid params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const request = spec.buildRequests([validBid, validBid2], bidderRequest); + const bidResponse = spec.interpretResponse({body: serverResponse2}, request); + + expect(bidResponse[0]).to.deep.include({ + requestId: validBid2.bidId, + ad: serverResponse2.bids[0].ad, + mediaType: 'banner', + creativeId: serverResponse2.bids[0].creativeId, + cpm: serverResponse2.bids[0].cpm, + width: serverResponse2.bids[0].width, + height: serverResponse2.bids[0].height, + meta: serverResponse2.bids[0].meta, + pubid: validBid2.params.pubid, + currency: DEFAULT_CURRENCY, + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_TTL, + }); + }); + }); + + describe('getUserSyncs', function () { + let syncOptions + beforeEach(function () { + syncOptions = { + enabledBidders: ['h12media'], + pixelEnabled: true, + iframeEnabled: true + } + }); + + it('should success with usersync pixel url', function () { + const result = { + type: 'image', + url: `https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies=${bidderRequest.gdprConsent.gdprApplies}&gdpr_consent_string=${bidderRequest.gdprConsent.consentString}`, + }; + const syncs = spec.getUserSyncs(syncOptions, [{body: serverResponse}], bidderRequest.gdprConsent); + + expect(syncs).to.deep.include(result); + }); + + it('should success with usersync iframe url', function () { + const result = { + type: 'iframe', + url: `https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies=${bidderRequest.gdprConsent.gdprApplies}&gdpr_consent_string=${bidderRequest.gdprConsent.consentString}`, + }; + const syncs = spec.getUserSyncs(syncOptions, [{body: serverResponse}], bidderRequest.gdprConsent); + + expect(syncs).to.deep.include(result); + }); + + it('should success without GDRP', function () { + const result = { + type: 'iframe', + url: `https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies=false&gdpr_consent_string=`, + }; + + expect(spec.getUserSyncs(syncOptions, [{body: serverResponse}], null)).to.deep.include(result); + }); + + it('should success without usersync url', function () { + expect(spec.getUserSyncs(syncOptions, [{body: serverResponse2}], bidderRequest.gdprConsent)).to.deep.equal([]); + }); + + it('should return empty usersync on empty response', function () { + expect(spec.getUserSyncs(syncOptions, [{body: {}}])).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/hpmdnetworkBidAdapter_spec.js b/test/spec/modules/hpmdnetworkBidAdapter_spec.js index 8e400f20def..9023fb248e9 100644 --- a/test/spec/modules/hpmdnetworkBidAdapter_spec.js +++ b/test/spec/modules/hpmdnetworkBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/hpmdnetworkBidAdapter'; +import { spec } from 'modules/hpmdnetworkBidAdapter.js'; describe('HPMDNetwork Adapter', function() { describe('isBidRequestValid', function () { diff --git a/test/spec/modules/hybridBidAdapter_spec.js b/test/spec/modules/hybridBidAdapter_spec.js new file mode 100644 index 00000000000..e5ad878c9b1 --- /dev/null +++ b/test/spec/modules/hybridBidAdapter_spec.js @@ -0,0 +1,275 @@ +import { expect } from 'chai' +import { spec } from 'modules/hybridBidAdapter.js' + +function getSlotConfigs(mediaTypes, params) { + return { + params: params, + sizes: [], + bidId: '2df8c0733f284e', + bidder: 'hybrid', + mediaTypes: mediaTypes, + transactionId: '31a58515-3634-4e90-9c96-f86196db1459' + } +} + +describe('Hybrid.ai Adapter', function() { + const PLACE_ID = '5af45ad34d506ee7acad0c26'; + const bidderRequest = { + refererInfo: { referer: 'referer' } + } + const bannerMandatoryParams = { + placeId: PLACE_ID, + placement: 'banner' + } + const videoMandatoryParams = { + placeId: PLACE_ID, + placement: 'video' + } + const validBidRequests = [ + getSlotConfigs({ banner: {} }, bannerMandatoryParams), + getSlotConfigs({ video: {playerSize: [[640, 480]]} }, videoMandatoryParams) + ] + describe('isBidRequestValid method', function() { + describe('returns true', function() { + describe('when banner slot config has all mandatory params', () => { + describe('and placement has the correct value', function() { + const slotConfig = getSlotConfigs( + { banner: {} }, + { + placeId: PLACE_ID, + placement: 'banner' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) + describe('when video slot has all mandatory params.', function() { + it('should return true, when video mediatype object are correct.', function() { + const slotConfig = getSlotConfigs( + { + video: { + context: 'instream', + playerSize: [[640, 480]] + } + }, + { + placeId: PLACE_ID, + placement: 'video' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) + }) + }) + }) + describe('returns false', function() { + describe('when params are not correct', function() { + function createSlotconfig(params) { + return getSlotConfigs({ banner: {} }, params) + } + it('does not have the placeId.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placement: 'banner' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placeId: PLACE_ID + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have a the correct placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placeId: PLACE_ID, + placement: 'something' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + describe('when video mediaType object is not correct.', function() { + function createVideoSlotconfig(mediaType) { + return getSlotConfigs(mediaType, { + placeId: PLACE_ID, + placement: 'video' + }) + } + it('is a void object', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ video: {} }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have playerSize.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ video: { context: 'instream' } }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have context', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ + video: { + playerSize: [[640, 480]] + } + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + }) + }) + it('Url params should be correct ', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + expect(request.method).to.equal('POST') + expect(request.url).to.equal('https://hbe198.hybrid.ai/prebidhb') + }) + + describe('buildRequests method', function() { + it('Common data request should be correct', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(Array.isArray(data.bidRequests)).to.equal(true) + expect(data.url).to.equal('referer') + data.bidRequests.forEach(bid => { + expect(bid.bidId).to.equal('2df8c0733f284e') + expect(bid.placeId).to.equal(PLACE_ID) + expect(bid.transactionId).to.equal('31a58515-3634-4e90-9c96-f86196db1459') + }) + }) + + describe('GDPR params', function() { + describe('when there are not consent management platform', function() { + it('cmp should be false', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(false) + }) + }) + describe('when there are consent management platform', function() { + it('cmps should be true and ga should not sended, when gdprApplies is undefined', function() { + bidderRequest['gdprConsent'] = { + gdprApplies: undefined, + consentString: 'consentString' + } + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(true) + expect(Object.keys(data).indexOf('data')).to.equal(-1) + expect(data.cs).to.equal('consentString') + }) + it('cmps should be true and all gdpr parameters should be sended, when there are gdprApplies', function() { + bidderRequest['gdprConsent'] = { + gdprApplies: true, + consentString: 'consentString' + } + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(true) + expect(data.ga).to.equal(true) + expect(data.cs).to.equal('consentString') + }) + }) + }) + + describe('BidRequests params', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + const bidRequests = data.bidRequests + it('should request a Banner', function() { + const bannerBid = bidRequests[0] + expect(bannerBid.placement).to.equal(spec.placementTypes[bannerMandatoryParams.placement]) + }) + it('should request a Video', function() { + const bannerBid = bidRequests[1] + expect(bannerBid.placement).to.equal(spec.placementTypes[videoMandatoryParams.placement]) + }) + }) + }) + + describe('interpret response method', function() { + it('should return a void array, when the server response are not correct.', function() { + const request = { data: JSON.stringify({}) } + const serverResponse = { + body: {} + } + const bids = spec.interpretResponse(serverResponse, request) + expect(typeof bids).to.equal('object') + expect(bids.length).to.equal(0) + }) + it('should return a void array, when the server response have not got bids.', function() { + const request = { data: JSON.stringify({}) } + const serverResponse = { body: { bids: [] } } + const bids = spec.interpretResponse(serverResponse, request) + expect(typeof bids).to.equal('object') + expect(bids.length).to.equal(0) + }) + describe('when the server response return a bid', function() { + describe('the bid is a banner', function() { + it('should return a banner bid', function() { + const request = spec.buildRequests([validBidRequests[0]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: 'html', + width: 100, + height: 100 + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].mediaType).to.equal(spec.supportedMediaTypes[0]) + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].ad).to.equal('string') + }) + }) + describe('the bid is a video', function() { + it('should return a video bid', function() { + const request = spec.buildRequests([validBidRequests[1]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: 'html', + width: 100, + height: 100 + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].mediaType).to.equal(spec.supportedMediaTypes[1]) + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].vastXml).to.equal('string') + }) + }) + }) + }) +}) diff --git a/test/spec/modules/iasBidAdapter_spec.js b/test/spec/modules/iasBidAdapter_spec.js index 7ac9d1906f5..1743ac947e6 100644 --- a/test/spec/modules/iasBidAdapter_spec.js +++ b/test/spec/modules/iasBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/iasBidAdapter'; +import { spec } from 'modules/iasBidAdapter.js'; describe('iasBidAdapter is an adapter that', function () { it('has the correct bidder code', function () { diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js new file mode 100644 index 00000000000..05ecec8dc36 --- /dev/null +++ b/test/spec/modules/id5IdSystem_spec.js @@ -0,0 +1,383 @@ +import { init, requestBidsHook, setSubmoduleRegistry, coreStorage } from 'modules/userId/index.js'; +import { config } from 'src/config.js'; +import { id5IdSubmodule } from 'modules/id5IdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import events from 'src/events.js'; +import CONSTANTS from 'src/constants.json'; + +let expect = require('chai').expect; + +describe('ID5 ID System', function() { + const ID5_MODULE_NAME = 'id5Id'; + const ID5_EIDS_NAME = ID5_MODULE_NAME.toLowerCase(); + const ID5_SOURCE = 'id5-sync.com'; + const ID5_PARTNER = 173; + const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_PARTNER}.json`; + const ID5_COOKIE_NAME = 'id5idcookie'; + const ID5_NB_COOKIE_NAME = `id5id.1st_${ID5_PARTNER}_nb`; + const ID5_EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; + const ID5_STORED_ID = 'storedid5id'; + const ID5_STORED_SIGNATURE = '123456'; + const ID5_STORED_OBJ = { + 'universal_uid': ID5_STORED_ID, + 'signature': ID5_STORED_SIGNATURE + }; + const ID5_LEGACY_STORED_OBJ = { + 'ID5ID': ID5_STORED_ID + } + const ID5_RESPONSE_ID = 'newid5id'; + const ID5_RESPONSE_SIGNATURE = 'abcdef'; + const ID5_JSON_RESPONSE = { + 'universal_uid': ID5_RESPONSE_ID, + 'signature': ID5_RESPONSE_SIGNATURE, + 'link_type': 0 + }; + + function getId5FetchConfig(storageName = ID5_COOKIE_NAME, storageType = 'cookie') { + return { + name: ID5_MODULE_NAME, + params: { + partner: ID5_PARTNER + }, + storage: { + name: storageName, + type: storageType, + expires: 90 + } + } + } + function getId5ValueConfig(value) { + return { + name: ID5_MODULE_NAME, + value: { + id5id: value + } + } + } + function getUserSyncConfig(userIds) { + return { + userSync: { + userIds: userIds, + syncDelay: 0 + } + } + } + function getFetchCookieConfig() { + return getUserSyncConfig([getId5FetchConfig()]); + } + function getFetchLocalStorageConfig() { + return getUserSyncConfig([getId5FetchConfig(ID5_COOKIE_NAME, 'html5')]); + } + function getValueConfig(value) { + return getUserSyncConfig([getId5ValueConfig(value)]); + } + function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [[300, 200], [300, 600]], + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + }; + } + + describe('Xhr Requests from getId()', function() { + const responseHeader = { 'Content-Type': 'application/json' }; + let callbackSpy = sinon.spy(); + + beforeEach(function() { + callbackSpy.resetHistory(); + }); + afterEach(function () { + + }); + + it('should fail if no partner is provided in the config', function() { + expect(id5IdSubmodule.getId()).to.be.eq(undefined); + }); + + it('should call the ID5 server with 1puid field for legacy storedObj format', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_LEGACY_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(request.url).to.contain(ID5_ENDPOINT); + expect(request.withCredentials).to.be.true; + expect(requestBody.s).to.eq(''); + expect(requestBody.partner).to.eq(ID5_PARTNER); + expect(requestBody['1puid']).to.eq(ID5_STORED_ID); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); + }); + + it('should call the ID5 server with signature field for new storedObj format', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(request.url).to.contain(ID5_ENDPOINT); + expect(request.withCredentials).to.be.true; + expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); + expect(requestBody.partner).to.eq(ID5_PARTNER); + expect(requestBody['1puid']).to.eq(''); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); + }); + + it('should call the ID5 server with pd field when pd config is set', function () { + const pubData = 'b50ca08271795a8e7e4012813f23d505193d75c0f2e2bb99baa63aa822f66ed3'; + + let config = getId5FetchConfig().params; + config.pd = pubData; + + let submoduleCallback = id5IdSubmodule.getId(config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(request.url).to.contain(ID5_ENDPOINT); + expect(request.withCredentials).to.be.true; + expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); + expect(requestBody.pd).to.eq(pubData); + expect(requestBody['1puid']).to.eq(''); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); + }); + + it('should call the ID5 server with empty pd field when pd config is not set', function () { + let config = getId5FetchConfig().params; + config.pd = undefined; + + let submoduleCallback = id5IdSubmodule.getId(config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(request.url).to.contain(ID5_ENDPOINT); + expect(request.withCredentials).to.be.true; + expect(requestBody.pd).to.eq(''); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); + }); + + it('should call the ID5 server with nb=1 when no stored value exists', function () { + coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(request.url).to.contain(ID5_ENDPOINT); + expect(request.withCredentials).to.be.true; + expect(requestBody.nbPage).to.eq(1); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); + + expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + }); + + it('should call the ID5 server with incremented nb when stored value exists', function () { + let expStr = (new Date(Date.now() + 25000).toUTCString()); + coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(request.url).to.contain(ID5_ENDPOINT); + expect(request.withCredentials).to.be.true; + expect(requestBody.nbPage).to.eq(2); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); + + expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + }); + }); + + describe('Request Bids Hook', function() { + let adUnits; + + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + coreStorage.setCookie(ID5_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, '', ID5_EXPIRED_COOKIE_DATE); + coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + adUnits = [getAdUnitMock()]; + }); + afterEach(function() { + events.getEvents.restore(); + coreStorage.setCookie(ID5_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, '', ID5_EXPIRED_COOKIE_DATE); + coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + }); + + it('should add stored ID from cookie to bids', function (done) { + let expStr = (new Date(Date.now() + 25000).toUTCString()); + coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); + + setSubmoduleRegistry([id5IdSubmodule]); + init(config); + config.setConfig(getFetchCookieConfig()); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); + expect(bid.userId.id5id).to.equal(ID5_STORED_ID); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: ID5_SOURCE, + uids: [{ id: ID5_STORED_ID, atype: 1 }] + }); + }); + }); + done(); + }, { adUnits }); + }); + + it('should add config value ID to bids', function (done) { + setSubmoduleRegistry([id5IdSubmodule]); + init(config); + config.setConfig(getValueConfig(ID5_STORED_ID)); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); + expect(bid.userId.id5id).to.equal(ID5_STORED_ID); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: ID5_SOURCE, + uids: [{ id: ID5_STORED_ID, atype: 1 }] + }); + }); + }); + done(); + }, { adUnits }); + }); + + it('should set nb=1 in cookie when no stored value exists', function () { + let expStr = (new Date(Date.now() + 25000).toUTCString()); + coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); + coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + + setSubmoduleRegistry([id5IdSubmodule]); + init(config); + config.setConfig(getFetchCookieConfig()); + + let innerAdUnits; + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('1'); + }); + + it('should increment nb in cookie when stored value exists', function () { + let expStr = (new Date(Date.now() + 25000).toUTCString()); + coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); + coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + + setSubmoduleRegistry([id5IdSubmodule]); + init(config); + config.setConfig(getFetchCookieConfig()); + + let innerAdUnits; + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('2'); + }); + + it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { + let expStr = (new Date(Date.now() + 25000).toUTCString()); + coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); + coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, (new Date(Date.now() - 50000).toUTCString()), expStr); + coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + + let id5Config = getFetchCookieConfig(); + id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; + + setSubmoduleRegistry([id5IdSubmodule]); + init(config); + config.setConfig(id5Config); + + let innerAdUnits; + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('2'); + + expect(server.requests).to.be.empty; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(request.url).to.contain(ID5_ENDPOINT); + expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); + expect(requestBody.nbPage).to.eq(2); + + const responseHeader = { 'Content-Type': 'application/json' }; + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + + expect(coreStorage.getCookie(ID5_COOKIE_NAME)).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); + expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + }); + + it('should call ID5 servers with 1puid and nb=1 post auction if refresh needed for legacy stored object', function () { + let expStr = (new Date(Date.now() + 25000).toUTCString()); + coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_LEGACY_STORED_OBJ), expStr); + coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, (new Date(Date.now() - 50000).toUTCString()), expStr); + + let id5Config = getFetchCookieConfig(); + id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; + + setSubmoduleRegistry([id5IdSubmodule]); + init(config); + config.setConfig(id5Config); + + let innerAdUnits; + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('1'); + + expect(server.requests).to.be.empty; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(request.url).to.contain(ID5_ENDPOINT); + expect(requestBody['1puid']).to.eq(ID5_STORED_ID); + expect(requestBody.nbPage).to.eq(1); + + const responseHeader = { 'Content-Type': 'application/json' }; + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + + expect(coreStorage.getCookie(ID5_COOKIE_NAME)).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); + expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + }); + }); + + describe('Decode stored object', function() { + const decodedObject = { 'id5id': ID5_STORED_ID }; + + it('should properly decode from a stored object', function() { + expect(id5IdSubmodule.decode(ID5_STORED_OBJ)).to.deep.equal(decodedObject); + }); + it('should properly decode from a legacy stored object', function() { + expect(id5IdSubmodule.decode(ID5_LEGACY_STORED_OBJ)).to.deep.equal(decodedObject); + }); + it('should return undefined if passed a string', function() { + expect(id5IdSubmodule.decode('somestring')).to.eq(undefined); + }); + }); +}); diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js new file mode 100644 index 00000000000..0d539d5988c --- /dev/null +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -0,0 +1,91 @@ +import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; +import * as utils from 'src/utils.js'; +import {server} from 'test/mocks/xhr.js'; + +const pid = '14'; +const defaultConfigParams = {pid: pid}; +const responseHeader = {'Content-Type': 'application/json'} + +describe('IdentityLinkId tests', function () { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + }); + + it('should log an error if no configParams were passed when getId', function () { + identityLinkSubmodule.getId(); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should log an error if pid configParam was not passed when getId', function () { + identityLinkSubmodule.getId({}); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should call the LiveRamp envelope endpoint', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call the LiveRamp envelope endpoint with consent string', function () { + let callBackSpy = sinon.spy(); + let consentData = { + gdprApplies: true, + consentString: 'BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g' + }; + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14&ct=1&cv=BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should not throw Uncaught TypeError when envelope endpoint returns empty response', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14'); + request.respond( + 204, + responseHeader, + '' + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(request.response).to.equal(''); + expect(logErrorStub.calledOnce).to.not.be.true; + }); + + it('should log an error and continue to callback if ajax request errors', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14'); + request.respond( + 503, + responseHeader, + 'Unavailable' + ); + expect(callBackSpy.calledOnce).to.be.true; + }); +}); diff --git a/test/spec/modules/imonomyBidAdapter_spec.js b/test/spec/modules/imonomyBidAdapter_spec.js index 91b907186c2..45b3bed6e77 100644 --- a/test/spec/modules/imonomyBidAdapter_spec.js +++ b/test/spec/modules/imonomyBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/imonomyBidAdapter'; +import { spec } from 'modules/imonomyBidAdapter.js'; describe('Imonomy Adapter Tests', function () { const bidsRequest = [ diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 15b7e2ff3e8..5a20944a6ed 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,10 +1,10 @@ import { expect } from 'chai'; -import { ImproveDigitalAdServerJSClient, spec } from 'modules/improvedigitalBidAdapter'; -import { config } from 'src/config'; -import { userSync } from 'src/userSync'; +import { ImproveDigitalAdServerJSClient, spec } from 'modules/improvedigitalBidAdapter.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; describe('Improve Digital Adapter Tests', function () { - let idClient = new ImproveDigitalAdServerJSClient('hb'); + const idClient = new ImproveDigitalAdServerJSClient('hb'); const METHOD = 'GET'; const URL = 'https://ice.360yield.com/hb'; @@ -20,9 +20,41 @@ describe('Improve Digital Adapter Tests', function () { bidId: '33e9500b21129f', bidderRequestId: '2772c1e566670b', auctionId: '192721e36a0239', + mediaTypes: { + banner: { + sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] + } + }, sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] }; + const instreamBidRequest = utils.deepClone(simpleBidRequest); + instreamBidRequest.mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480] + } + }; + + const outstreamBidRequest = utils.deepClone(simpleBidRequest); + outstreamBidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }; + + const multiFormatBidRequest = utils.deepClone(simpleBidRequest); + multiFormatBidRequest.mediaTypes = { + banner: { + sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] + }, + video: { + context: 'outstream', + playerSize: [640, 480] + } + }; + const simpleSmartTagBidRequest = { bidder: 'improvedigital', bidId: '1a2b3c', @@ -33,7 +65,24 @@ describe('Improve Digital Adapter Tests', function () { } }; + const bidderRequest = { + bids: [simpleBidRequest] + }; + + const instreamBidderRequest = { + bids: [instreamBidRequest] + }; + + const outstreamBidderRequest = { + bids: [outstreamBidRequest] + }; + + const multiFormatBidderRequest = { + bids: [multiFormatBidRequest] + }; + const bidderRequestGdpr = { + bids: [simpleBidRequest], gdprConsent: { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', vendorData: {}, @@ -42,6 +91,7 @@ describe('Improve Digital Adapter Tests', function () { }; const bidderRequestReferrer = { + bids: [simpleBidRequest], refererInfo: { referer: 'https://blah.com/test.html', }, @@ -53,12 +103,12 @@ describe('Improve Digital Adapter Tests', function () { }); it('should return false when no bid.params', function () { - let bid = {}; + const bid = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when both placementId and placementKey + publisherId are missing', function () { - let bid = { 'params': {} }; + const bid = { 'params': {} }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -78,31 +128,59 @@ describe('Improve Digital Adapter Tests', function () { }); it('should return true when placementId is passed', function () { - let bid = { 'params': {} }; expect(spec.isBidRequestValid(simpleBidRequest)).to.equal(true); }); it('should return true when both placementKey and publisherId are passed', function () { - let bid = { 'params': {} }; expect(spec.isBidRequestValid(simpleSmartTagBidRequest)).to.equal(true); }); }); describe('buildRequests', function () { it('should make a well-formed request objects', function () { - const requests = spec.buildRequests([simpleBidRequest]); + const requests = spec.buildRequests([simpleBidRequest], bidderRequest); 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.bidderRequest).to.deep.equal(bidderRequest); expect(request.data.substring(0, PARAM_PREFIX.length)).to.equal(PARAM_PREFIX); const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request).to.be.an('object'); expect(params.bid_request.id).to.be.a('string'); expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); + expect(params.bid_request.gdpr).to.not.exist; + expect(params.bid_request.us_privacy).to.not.exist; + expect(params.bid_request.imp).to.deep.equal([ + { + id: '33e9500b21129f', + pid: 1053688, + tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', + banner: {} + } + ]); + }); + + it('should make a well-formed request object for multi-format ad unit', function () { + const requests = spec.buildRequests([multiFormatBidRequest], multiFormatBidderRequest); + 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.bidderRequest).to.deep.equal(multiFormatBidderRequest); + expect(request.data.substring(0, PARAM_PREFIX.length)).to.equal(PARAM_PREFIX); + + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request).to.be.an('object'); + expect(params.bid_request.id).to.be.a('string'); + expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); + expect(params.bid_request.gdpr).to.not.exist; + expect(params.bid_request.us_privacy).to.not.exist; expect(params.bid_request.imp).to.deep.equal([ { id: '33e9500b21129f', @@ -114,33 +192,33 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set placementKey and publisherId for smart tags', function () { - const requests = spec.buildRequests([simpleSmartTagBidRequest]); + const requests = spec.buildRequests([simpleSmartTagBidRequest], bidderRequest); const params = JSON.parse(decodeURIComponent(requests[0].data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].pubid).to.equal(1032); expect(params.bid_request.imp[0].pkey).to.equal('data_team_test_hb_smoke_test'); }); it('should add keyValues', function () { - let bidRequest = Object.assign({}, simpleBidRequest); + const bidRequest = Object.assign({}, simpleBidRequest); const keyValues = { testKey: [ 'testValue' ] }; bidRequest.params.keyValues = keyValues; - const request = spec.buildRequests([bidRequest])[0]; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].kvw).to.deep.equal(keyValues); }); it('should add single size filter', function () { - let bidRequest = Object.assign({}, simpleBidRequest); + const bidRequest = Object.assign({}, simpleBidRequest); const size = { w: 800, h: 600 }; bidRequest.params.size = size; - const request = spec.buildRequests([bidRequest])[0]; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].banner).to.deep.equal(size); // When single size filter is set, format shouldn't be populated. This @@ -151,7 +229,7 @@ describe('Improve Digital Adapter Tests', function () { it('should add currency', function () { const bidRequest = Object.assign({}, simpleBidRequest); const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); - const request = spec.buildRequests([bidRequest])[0]; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].currency).to.equal('JPY'); getConfigStub.restore(); @@ -159,14 +237,14 @@ describe('Improve Digital Adapter Tests', function () { it('should add bid floor', function () { const bidRequest = Object.assign({}, simpleBidRequest); - let request = spec.buildRequests([bidRequest])[0]; + let request = spec.buildRequests([bidRequest], bidderRequest)[0]; let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); // Floor price currency shouldn't be populated without a floor price expect(params.bid_request.imp[0].bidfloorcur).to.not.exist; // Default floor price currency bidRequest.params.bidFloor = 0.05; - request = spec.buildRequests([bidRequest])[0]; + request = spec.buildRequests([bidRequest], bidderRequest)[0]; params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); expect(params.bid_request.imp[0].bidfloorcur).to.equal('USD'); @@ -186,6 +264,13 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.gdpr).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); }); + it('should add CCPA consent string', function () { + const bidRequest = Object.assign({}, simpleBidRequest); + const request = spec.buildRequests([bidRequest], { uspConsent: '1YYY' })[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.us_privacy).to.equal('1YYY'); + }); + it('should add referrer', function () { const bidRequest = Object.assign({}, simpleBidRequest); const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; @@ -196,7 +281,7 @@ describe('Improve Digital Adapter Tests', function () { it('should add ad type for instream video', function () { let bidRequest = Object.assign({}, simpleBidRequest); bidRequest.mediaType = 'video'; - let request = spec.buildRequests([bidRequest])[0]; + let request = spec.buildRequests([bidRequest], bidderRequest)[0]; let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); @@ -207,38 +292,48 @@ describe('Improve Digital Adapter Tests', function () { playerSize: [640, 480] } }; - request = spec.buildRequests([bidRequest])[0]; + request = spec.buildRequests([bidRequest], bidderRequest)[0]; params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); }); + it('should not set ad type for outstream video', function() { + const request = spec.buildRequests([outstreamBidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].ad_types).to.not.exist; + }); + + it('should not set ad type for multi-format bids', function() { + const request = spec.buildRequests([multiFormatBidRequest], bidderRequest)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].ad_types).to.not.exist; + }); + it('should not set Prebid sizes in bid request for instream video', function () { const getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const bidRequest = Object.assign({}, simpleBidRequest); - bidRequest.mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480] - } - }; - const request = spec.buildRequests([bidRequest])[0]; + const request = spec.buildRequests([instreamBidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].banner.format).to.not.exist; getConfigStub.restore(); }); - it('should not set ad type for outstream video', function() { - const bidRequest = Object.assign({}, simpleBidRequest); - bidRequest.mediaTypes = { - video: { - context: 'outstream', - playerSize: [640, 480] - } - }; - const request = spec.buildRequests([bidRequest])[0]; + it('should not set Prebid sizes in bid request for outstream video', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const request = spec.buildRequests([outstreamBidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.not.exist; + expect(params.bid_request.imp[0].banner.format).to.not.exist; + getConfigStub.restore(); + }); + + it('should not set Prebid sizes in multi-format bid request', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const request = spec.buildRequests([multiFormatBidRequest], bidderRequest)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].banner.format).to.not.exist; + getConfigStub.restore(); }); it('should add schain', function () { @@ -254,9 +349,11 @@ describe('Improve Digital Adapter Tests', function () { const requests = spec.buildRequests([ simpleBidRequest, simpleSmartTagBidRequest - ]); + ], bidderRequest); expect(requests).to.be.an('array'); expect(requests.length).to.equal(2); + expect(requests[0].bidderRequest).to.deep.equal(bidderRequest); + expect(requests[1].bidderRequest).to.deep.equal(bidderRequest); }); it('should return one request in a single request mode', function () { @@ -265,7 +362,7 @@ describe('Improve Digital Adapter Tests', function () { const requests = spec.buildRequests([ simpleBidRequest, simpleSmartTagBidRequest - ]); + ], bidderRequest); expect(requests).to.be.an('array'); expect(requests.length).to.equal(1); getConfigStub.restore(); @@ -274,7 +371,7 @@ describe('Improve Digital Adapter Tests', function () { it('should set Prebid sizes in bid request', function () { const getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([simpleBidRequest])[0]; + const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].banner).to.deep.equal({ format: [ @@ -294,7 +391,7 @@ describe('Improve Digital Adapter Tests', function () { h: 600 }; bidRequest.params.size = size; - const request = spec.buildRequests([bidRequest])[0]; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].banner).to.deep.equal({ format: [ @@ -546,7 +643,7 @@ describe('Improve Digital Adapter Tests', function () { ]; describe('interpretResponse', function () { - let expectedBid = [ + const expectedBid = [ { 'ad': '', 'adId': '33e9500b21129f', @@ -562,7 +659,7 @@ describe('Improve Digital Adapter Tests', function () { } ]; - let expectedTwoBids = [ + const expectedTwoBids = [ expectedBid[0], { 'ad': '', @@ -579,7 +676,7 @@ describe('Improve Digital Adapter Tests', function () { } ]; - let expectedBidNative = [ + const expectedBidNative = [ { mediaType: 'native', adId: '33e9500b21129f', @@ -628,7 +725,7 @@ describe('Improve Digital Adapter Tests', function () { } ]; - let expectedBidVideo = [ + const expectedBidInstreamVideo = [ { 'vastXml': '', 'adId': '33e9500b21129f', @@ -644,60 +741,77 @@ describe('Improve Digital Adapter Tests', function () { } ]; - it('should return a well-formed bid', function () { - const bids = spec.interpretResponse(serverResponse); + const expectedBidOutstreamVideo = utils.deepClone(expectedBidInstreamVideo); + expectedBidOutstreamVideo[0].adResponse = { + content: expectedBidOutstreamVideo[0].vastXml, + height: expectedBidOutstreamVideo[0].height, + width: expectedBidOutstreamVideo[0].width + }; + + it('should return a well-formed display bid', function () { + const bids = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(bids).to.deep.equal(expectedBid); + }); + + it('should return a well-formed display bid for multi-format ad unit', function () { + const bids = spec.interpretResponse(serverResponse, {bidderRequest: multiFormatBidderRequest}); expect(bids).to.deep.equal(expectedBid); }); it('should return two bids', function () { - const bids = spec.interpretResponse(serverResponseTwoBids); + const bids = spec.interpretResponse(serverResponseTwoBids, {bidderRequest}); expect(bids).to.deep.equal(expectedTwoBids); }); it('should set dealId correctly', function () { - let response = JSON.parse(JSON.stringify(serverResponse)); + const response = JSON.parse(JSON.stringify(serverResponse)); let bids; delete response.body.bid[0].lid; response.body.bid[0].buying_type = 'deal_id'; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.not.exist; response.body.bid[0].lid = 268515; delete response.body.bid[0].buying_type; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.not.exist; response.body.bid[0].lid = 268515; - response.body.bid[0].buying_type = 'classic'; - bids = spec.interpretResponse(response); + response.body.bid[0].buying_type = 'rtb'; + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.not.exist; + response.body.bid[0].lid = 268515; + response.body.bid[0].buying_type = 'classic'; + bids = spec.interpretResponse(response, {bidderRequest}); + expect(bids[0].dealId).to.equal(268515); + response.body.bid[0].lid = 268515; response.body.bid[0].buying_type = 'deal_id'; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.equal(268515); response.body.bid[0].lid = [ 268515, 12456, 34567 ]; response.body.bid[0].buying_type = 'deal_id'; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.not.exist; response.body.bid[0].lid = [ 268515, 12456, 34567 ]; response.body.bid[0].buying_type = [ 'deal_id', 'classic' ]; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.not.exist; response.body.bid[0].lid = [ 268515, 12456, 34567 ]; - response.body.bid[0].buying_type = [ 'classic', 'deal_id', 'deal_id' ]; - bids = spec.interpretResponse(response); + response.body.bid[0].buying_type = [ 'rtb', 'deal_id', 'deal_id' ]; + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.equal(12456); }); it('should set currency', function () { - let response = JSON.parse(JSON.stringify(serverResponse)); + const response = JSON.parse(JSON.stringify(serverResponse)); response.body.bid[0].currency = 'eur'; - const bids = spec.interpretResponse(response); + const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].currency).to.equal('EUR'); }); @@ -707,41 +821,41 @@ describe('Improve Digital Adapter Tests', function () { // Price missing or 0 response.body.bid[0].price = 0; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); delete response.body.bid[0].price; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); response.body.bid[0].price = null; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); // errorCode present response = JSON.parse(JSON.stringify(serverResponse)); response.body.bid[0].errorCode = undefined; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); // adm and native missing response = JSON.parse(JSON.stringify(serverResponse)); delete response.body.bid[0].adm; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); response.body.bid[0].adm = null; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); }); it('should set netRevenue', function () { - let response = JSON.parse(JSON.stringify(serverResponse)); + const response = JSON.parse(JSON.stringify(serverResponse)); response.body.bid[0].isNet = true; - const bids = spec.interpretResponse(response); + const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].netRevenue).to.equal(true); }); // Native ads it('should return a well-formed native ad bid', function () { - let bids = spec.interpretResponse(serverResponseNative); + let bids = spec.interpretResponse(serverResponseNative, {bidderRequest}); expect(bids[0].ortbNative).to.deep.equal(serverResponseNative.body.bid[0].native); delete bids[0].ortbNative; expect(bids).to.deep.equal(expectedBidNative); @@ -755,15 +869,29 @@ describe('Improve Digital Adapter Tests', function () { 'https://www.mytracker.com/imptracker' ]; expectedBids[0].native.javascriptTrackers = ''; - bids = spec.interpretResponse(response); + bids = spec.interpretResponse(response, {bidderRequest}); delete bids[0].ortbNative; expect(bids).to.deep.equal(expectedBids); }); // Video - it('should return a well-formed video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo); - expect(bids).to.deep.equal(expectedBidVideo); + it('should return a well-formed instream video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo, {bidderRequest: instreamBidderRequest}); + expect(bids).to.deep.equal(expectedBidInstreamVideo); + }); + + it('should return a well-formed outstream video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo, {bidderRequest: outstreamBidderRequest}); + expect(bids[0].renderer).to.exist; + delete (bids[0].renderer); + expect(bids).to.deep.equal(expectedBidOutstreamVideo); + }); + + it('should return a well-formed outstream video bid for multi-format ad unit', function () { + const bids = spec.interpretResponse(serverResponseVideo, {bidderRequest: multiFormatBidderRequest}); + expect(bids[0].renderer).to.exist; + delete (bids[0].renderer); + expect(bids).to.deep.equal(expectedBidOutstreamVideo); }); }); diff --git a/test/spec/modules/innityBidAdapter_spec.js b/test/spec/modules/innityBidAdapter_spec.js new file mode 100644 index 00000000000..80c00252632 --- /dev/null +++ b/test/spec/modules/innityBidAdapter_spec.js @@ -0,0 +1,106 @@ +import { expect } from 'chai'; +import { spec } from 'modules/innityBidAdapter.js'; + +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); + }); + + it('bidRequest with no required params', () => { + expect(spec.isBidRequestValid({ + bidder: 'innity', + params: { + }, + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'innity', + 'params': { + 'pub': 267, + 'zone': 62546 + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [300, 250], + 'bidId': '51ef8751f9aead', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://example.com' + } + }; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].data.pub).to.equal(267); + expect(requests[0].data.zone).to.equal(62546); + expect(requests[0].data.width).to.equal('300'); + expect(requests[0].data.height).to.equal('250'); + expect(requests[0].data.callback_uid).to.equal('51ef8751f9aead'); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'GET', + 'url': 'https://as.innity.com/synd/?', + 'data': { + 'ver': 2, + 'hb': 1, + 'output': 'js', + 'pub': 267, + 'zone': 62546, + 'width': '300', + 'height': '250', + 'callback': 'json', + 'callback_uid': '51ef8751f9aead', + 'url': 'https://example.com', + 'cb': '', + } + }; + + const bidResponse = { + body: { + 'cpm': 100, + 'width': '300', + 'height': '250', + 'creative_id': '148186', + 'callback_uid': '51ef8751f9aead', + '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..e817b3e3b81 --- /dev/null +++ b/test/spec/modules/inskinBidAdapter_spec.js @@ -0,0 +1,376 @@ +import { expect } from 'chai'; +import { spec } from 'modules/inskinBidAdapter.js'; +import { createBid } from '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': { + 'customData': { + 'pubCPM': 1 + }, + '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} + } + } + } +}; + +const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; +const bidderRequest = { + bidderCode: 'inskin', + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + referer: 'https://www.inskinmedia.com' + } +}; + +describe('InSkin BidAdapter', function () { + let bidRequests; + let adapter = spec; + + beforeEach(function () { + 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', function () { + it('should accept valid bid requests', function () { + 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', function () { + 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', function () { + let bid = { + bidder: 'inskin', + params: { + networkId: '9874' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should reject bid requests without networkId', function () { + let bid = { + bidder: 'inskin', + params: { + siteId: '9874' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests validation', function () { + it('creates request data', function () { + let request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request).to.exist.and.to.be.a('object'); + }); + + it('request to inskin should contain a url', function () { + let request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.url).to.have.string('inskinad.com'); + }); + + it('requires valid bids to make request', function () { + let request = spec.buildRequests([], bidderRequest); + expect(request.bidRequest).to.be.empty; + }); + + it('sends bid request to ENDPOINT via POST', function () { + let request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.method).to.have.string('POST'); + }); + + it('should add gdpr consent information to the request', function () { + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.consent.gdprConsentString).to.exist; + expect(payload.consent.gdprConsentRequired).to.exist; + expect(payload.consent.gdprConsentString).to.exist.and.to.equal(consentString); + expect(payload.consent.gdprConsentRequired).to.exist.and.to.be.true; + }); + + it('should not add keywords if TCF v2 purposes are granted', function () { + const bidderRequest2 = Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + 150: true + } + }, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + }, + apiVersion: 2 + } + }); + + const request = spec.buildRequests(bidRequests, bidderRequest2); + const payload = JSON.parse(request.data); + + expect(payload.keywords).to.be.an('array').that.is.empty; + expect(payload.placements[0].properties).to.be.undefined; + }); + + it('should add keywords if TCF v2 purposes are not granted', function () { + const bidderRequest2 = Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + 150: false + } + }, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + }, + apiVersion: 2 + } + }); + + const request = spec.buildRequests(bidRequests, bidderRequest2); + const payload = JSON.parse(request.data); + + expect(payload.keywords).to.be.an('array').that.includes('cst-nocookies'); + expect(payload.keywords).to.be.an('array').that.includes('cst-nocontext'); + expect(payload.keywords).to.be.an('array').that.includes('cst-nodmp'); + expect(payload.keywords).to.be.an('array').that.includes('cst-nodata'); + expect(payload.keywords).to.be.an('array').that.includes('cst-noclicks'); + expect(payload.keywords).to.be.an('array').that.includes('cst-noresearch'); + + expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('nocookies'); + expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('nocontext'); + expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('nodmp'); + expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('nodata'); + expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('noclicks'); + expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('noresearch'); + }); + }); + describe('interpretResponse validation', function () { + it('response should have valid bidderCode', function () { + let bidRequest = spec.buildRequests(REQUEST.bidRequest, bidderRequest); + let bid = createBid(1, bidRequest.bidRequest[0]); + + expect(bid.bidderCode).to.equal('inskin'); + }); + + it('response should include objects for all bids', function () { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + + expect(bids.length).to.equal(2); + }); + + it('registers bids', function () { + 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); + }); + }); + + it('cpm is correctly set', function () { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + + expect(bids[0].cpm).to.equal(0.5); + expect(bids[1].cpm).to.equal(1); + }); + + it('handles nobid responses', function () { + let EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {'decisions': null}}) + let bids = spec.interpretResponse(EMPTY_RESP, REQUEST); + + expect(bids).to.be.empty; + }); + + it('handles no server response', function () { + let bids = spec.interpretResponse(null, REQUEST); + + expect(bids).to.be.empty; + }); + }); + describe('getUserSyncs', function () { + it('handles empty sync options', function () { + let opts = spec.getUserSyncs({}); + + expect(opts).to.be.empty; + }); + + it('should return two sync urls if pixel syncs are enabled', function () { + 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', function () { + 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 index 3ca31c0abb4..b75c6a4578f 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -1,5 +1,5 @@ -import { expect } from 'chai'; -import { spec, resetInvibes, stubDomainOptions } from 'modules/invibesBidAdapter'; +import {expect} from 'chai'; +import {spec, resetInvibes, stubDomainOptions, readGdprConsent} from 'modules/invibesBidAdapter.js'; describe('invibesBidAdapter:', function () { const BIDDER_CODE = 'invibes'; @@ -40,14 +40,15 @@ describe('invibesBidAdapter:', function () { } ]; - let StubbedPersistence = function(initialValue) { + let StubbedPersistence = function (initialValue) { var value = initialValue; return { load: function () { let str = value || ''; try { return JSON.parse(str); - } catch (e) { } + } catch (e) { + } }, save: function (obj) { value = JSON.stringify(obj); @@ -67,7 +68,7 @@ describe('invibesBidAdapter:', function () { describe('isBidRequestValid:', function () { context('valid bid request:', function () { - it('returns true when bidder params.placementId is set', function() { + it('returns true when bidder params.placementId is set', function () { const validBid = { bidder: BIDDER_CODE, params: { @@ -88,7 +89,7 @@ describe('invibesBidAdapter:', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); - it('returns false when placementId is not set', function() { + it('returns false when placementId is not set', function () { const invalidBid = { bidder: BIDDER_CODE, params: { @@ -99,7 +100,7 @@ describe('invibesBidAdapter:', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); - it('returns false when bid response was previously received', function() { + it('returns false when bid response was previously received', function () { const validBid = { bidder: BIDDER_CODE, params: { @@ -107,7 +108,7 @@ describe('invibesBidAdapter:', function () { } } - top.window.invibes.bidResponse = { prop: 'prop' }; + top.window.invibes.bidResponse = {prop: 'prop'}; expect(spec.isBidRequestValid(validBid)).to.be.false; }); }); @@ -126,7 +127,7 @@ describe('invibesBidAdapter:', function () { }); it('has location, html id, placement and width/height', function () { - const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); const parsedData = request.data; expect(parsedData.location).to.exist; expect(parsedData.videoAdHtmlId).to.exist; @@ -137,37 +138,37 @@ describe('invibesBidAdapter:', function () { it('has capped ids if local storage variable is correctly formatted', function () { localStorage.ivvcap = '{"9731":[1,1768600800000]}'; - const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); expect(request.data.capCounts).to.equal('9731=1'); }); it('does not have capped ids if local storage variable is incorrectly formatted', function () { localStorage.ivvcap = ':[1,1574334216992]}'; - const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); expect(request.data.capCounts).to.equal(''); }); it('does not have capped ids if local storage variable is expired', function () { localStorage.ivvcap = '{"9731":[1,1574330064104]}'; - const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); expect(request.data.capCounts).to.equal(''); }); it('sends query string params from localstorage 1', function () { - localStorage.ivbs = JSON.stringify({ bvci: 1 }); - const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + localStorage.ivbs = JSON.stringify({bvci: 1}); + const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); expect(request.data.bvci).to.equal(1); }); it('sends query string params from localstorage 2', function () { - localStorage.ivbs = JSON.stringify({ invibbvlog: true }); - const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + localStorage.ivbs = JSON.stringify({invibbvlog: true}); + const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); expect(request.data.invibbvlog).to.equal(true); }); it('does not send query string params from localstorage if unknwon', function () { - localStorage.ivbs = JSON.stringify({ someparam: true }); - const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + localStorage.ivbs = JSON.stringify({someparam: true}); + const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); expect(request.data.someparam).to.be.undefined; }); @@ -191,65 +192,401 @@ describe('invibesBidAdapter:', function () { it('try to graduate but not enough count - doesnt send the domain id', function () { global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":0}'; - let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; }); it('try to graduate but not old enough - doesnt send the domain id', function () { - let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":' + Date.now() + ',"hc":5}'; let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; }); it('graduate and send the domain id', function () { - let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; - stubDomainOptions(new StubbedPersistence('{"id":"dvdjkams6nkq","cr":1521818537626,"hc":7}')); + let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; + stubDomainOptions(new StubbedPersistence('{"id":"dvdjkams6nkq","cr":1521818537626,"hc":7}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; }); it('send the domain id if already graduated', function () { - let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; - stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi"}')); + let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; + stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; expect(top.window.invibes.dom.tempId).to.exist; }); it('send the domain id after replacing it with new format', function () { - let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; - stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); + let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; + stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; expect(top.window.invibes.dom.tempId).to.exist; }); - it('dont send the domain id if consent declined', function () { - let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: false } } } }; + it('dont send the domain id if consent declined on tcf v1', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendorConsents: {436: false} + } + } + }; + stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.lId).to.not.exist; + expect(top.window.invibes.dom.tempId).to.not.exist; + expect(request.data.oi).to.equal(0); + }); + + it('dont send the domain id if consent declined on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}} + } + } + }; stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; expect(top.window.invibes.dom.tempId).to.not.exist; + expect(request.data.oi).to.equal(0); }); it('dont send the domain id if no consent', function () { - let bidderRequest = { }; - stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); + let bidderRequest = {}; + stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; expect(top.window.invibes.dom.tempId).to.not.exist; }); it('try to init id but was already loaded on page - does not increment the id again', function () { - let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":0}'; let request = spec.buildRequests(bidRequests, bidderRequest); request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; expect(top.window.invibes.dom.tempId).to.exist; }); + + it('should send oi = 0 when vendorData is null', function () { + let bidderRequest = { + gdprConsent: { + vendorData: null + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + + it('should send oi = 2 when consent was approved on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(2); + }); + + it('should send oi = 0 when vendor consents for invibes are false on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + + it('should send oi = 0 when purpose consents weren\'t approved on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: false, + 3: false, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + + it('should send oi = 0 when purpose consents are less then 10 on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: false, + 3: false, + 4: true, + 5: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + + it('should send oi = 4 when vendor consents are null on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: null}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(4); + }); + + it('should send oi = 4 when vendor consents for invibes is null on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: null}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(4); + }); + + it('should send oi = 4 when vendor consents for invibes is null on tcf v1', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendorConsents: {436: null}, + purposeConsents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(4); + }); + + it('should send oi = 4 when vendor consents consents are null on tcf v1', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendorConsents: null, + purposeConsents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(4); + }); + + it('should send oi = 2 when gdpr doesn\'t apply or has global consent', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: false, + hasGlobalConsent: true, + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(2); + }); + + it('should send oi = 2 when consent was approved on tcf v1', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendorConsents: {436: true}, + purposeConsents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(2); + }); + + it('should send oi = 0 when purpose consents weren\'t approved on tcf v1', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendorConsents: {436: true}, + purposeConsents: { + 1: false, + 2: false, + 3: true, + 4: true, + 5: true + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + + it('should send oi = 0 when purpose consents are less then 5 on tcf v1', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendorConsents: {436: true}, + purposeConsents: { + 1: false, + 2: false, + 3: true + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + + it('should send oi = 0 when vendor consents for invibes are false on tcf v1', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendorConsents: {436: false}, + purposeConsents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); }); describe('interpretResponse', function () { @@ -285,37 +622,47 @@ describe('invibesBidAdapter:', function () { context('when the response is not valid', function () { it('handles response with no bids requested', function () { - let emptyResult = spec.interpretResponse({ body: response }); + let emptyResult = spec.interpretResponse({body: response}); expect(emptyResult).to.be.empty; }); it('handles empty response', function () { - let emptyResult = spec.interpretResponse(null, { bidRequests }); + let emptyResult = spec.interpretResponse(null, {bidRequests}); expect(emptyResult).to.be.empty; }); it('handles response with bidding is not configured', function () { - let emptyResult = spec.interpretResponse({ body: { Ads: [{ BidPrice: 1 }] } }, { bidRequests }); + let emptyResult = spec.interpretResponse({body: {Ads: [{BidPrice: 1}]}}, {bidRequests}); expect(emptyResult).to.be.empty; }); it('handles response with no ads are received', function () { - let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '12345' }, AdReason: 'No ads' } }, { bidRequests }); + 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', function () { - let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '12345' } } }, { bidRequests }); + let emptyResult = spec.interpretResponse({body: {BidModel: {PlacementId: '12345'}}}, {bidRequests}); expect(emptyResult).to.be.empty; }); it('handles response when no placement Id matches', function () { - let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '123456' }, Ads: [{ BidPrice: 1 }] } }, { bidRequests }); + 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', function () { - let emptyResult = spec.interpretResponse({ BidModel: { }, Ads: [{ BidPrice: 1 }] }, { bidRequests }); + let emptyResult = spec.interpretResponse({BidModel: {}, Ads: [{BidPrice: 1}]}, {bidRequests}); expect(emptyResult).to.be.empty; }); }); @@ -324,20 +671,20 @@ describe('invibesBidAdapter:', function () { it('responds with a valid bid', function () { top.window.invibes.setCookie('a', 'b', 370); top.window.invibes.setCookie('c', 'd', 0); - let result = spec.interpretResponse({ body: response }, { bidRequests }); + 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', function () { localStorage.InvibesDEBUG = true; - let result = spec.interpretResponse({ body: response }, { bidRequests }); + 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', function () { localStorage.InvibesDEBUG = false; - let result = spec.interpretResponse({ body: response }, { bidRequests }); - let secondResult = spec.interpretResponse({ body: response }, { bidRequests }); + let result = spec.interpretResponse({body: response}, {bidRequests}); + let secondResult = spec.interpretResponse({body: response}, {bidRequests}); expect(secondResult).to.be.empty; }); }); @@ -353,7 +700,7 @@ describe('invibesBidAdapter:', function () { it('returns an iframe with params if enabled', function () { top.window.invibes.optIn = 1; global.document.cookie = 'ivvbks=17639.0,1,2'; - let response = spec.getUserSyncs({ iframeEnabled: true }); + let response = spec.getUserSyncs({iframeEnabled: true}); expect(response.type).to.equal('iframe'); expect(response.url).to.include(SYNC_ENDPOINT); expect(response.url).to.include('optIn'); @@ -362,7 +709,7 @@ describe('invibesBidAdapter:', function () { }); it('returns undefined if iframe not enabled ', function () { - let response = spec.getUserSyncs({ iframeEnabled: false }); + let response = spec.getUserSyncs({iframeEnabled: false}); expect(response).to.equal(undefined); }); }); diff --git a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js index 6ee989907b5..e13b16661b0 100644 --- a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js +++ b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js @@ -1,9 +1,9 @@ -import invisiblyAdapter from 'modules/invisiblyAnalyticsAdapter'; +import invisiblyAdapter from 'modules/invisiblyAnalyticsAdapter.js'; import { expect } from 'chai'; let events = require('src/events'); let constants = require('src/constants.json'); -describe('Invisibly Analytics Adapter test suite', function() { +describe('Invisibly Analytics Adapter test suite', function () { let xhr; let requests = []; const BID1 = { @@ -21,11 +21,11 @@ describe('Invisibly Analytics Adapter test suite', function() { hb_adid: '2ecff0db240757', hb_pb: 1.2, hb_size: '640x480', - hb_source: 'client' + hb_source: 'client', }, getStatusCode() { return CONSTANTS.STATUS.GOOD; - } + }, }; const BID2 = Object.assign({}, BID1, { @@ -35,7 +35,7 @@ describe('Invisibly Analytics Adapter test suite', function() { timeToRespond: 300, bidId: '3ecff0db240757', requestId: '3ecff0db240757', - adId: '3ecff0db240757' + adId: '3ecff0db240757', }); const BID3 = { @@ -49,11 +49,11 @@ describe('Invisibly Analytics Adapter test suite', function() { hb_adid: '3bd4ebb1c900e2', hb_pb: '1.500', hb_size: '728x90', - hb_source: 'server' + hb_source: 'server', }, getStatusCode() { return CONSTANTS.STATUS.NO_BID; - } + }, }; const MOCK = { @@ -61,11 +61,11 @@ describe('Invisibly Analytics Adapter test suite', function() { provider: 'invisiblyAnalytics', options: { bundleId: '', - account: 'invisibly' - } + account: 'invisibly', + }, }, AUCTION_INIT: { - auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa' + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', }, BID_REQUESTED: { bidder: 'mockBidder', @@ -75,27 +75,27 @@ describe('Invisibly Analytics Adapter test suite', function() { { bidder: 'mockBidder', adUnitCode: 'panorama_d_1', - bidId: '2ecff0db240757' + bidId: '2ecff0db240757', }, { bidder: 'mockBidder', adUnitCode: 'box_d_1', - bidId: '3ecff0db240757' + bidId: '3ecff0db240757', }, { bidder: 'mockBidder', adUnitCode: 'box_d_2', - bidId: '4ecff0db240757' - } + bidId: '4ecff0db240757', + }, ], - start: 1519149562216 + start: 1519149562216, }, BID_RESPONSE: [BID1, BID2], AUCTION_END: { auctionId: 'test_timeout_auction_id', timestamp: 1234567890, timeout: 3000, - auctionEnd: 1234567990 + auctionEnd: 1234567990, }, BID_WON: { bidderCode: 'appnexus', @@ -120,17 +120,17 @@ describe('Invisibly Analytics Adapter test suite', function() { status: 'rendered', eventType: 'bidWon', ad: 'some ad', - adUrl: 'ad url' + adUrl: 'ad url', }, BIDDER_DONE: { bidderCode: 'mockBidder', - bids: [BID1, BID2, BID3] + bids: [BID1, BID2, BID3], }, BID_TIMEOUT: [ { bidId: '2ecff0db240757', - auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa' - } + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', + }, ], BID_ADJUSTMENT: { ad: 'html', @@ -147,350 +147,437 @@ describe('Invisibly Analytics Adapter test suite', function() { statusMessage: 'Bid available', timeToRespond: 421, ttl: 300, - width: 300 + width: 300, }, NO_BID: { - testKey: false + testKey: false, }, SET_TARGETING: { [BID1.adUnitCode]: BID1.adserverTargeting, - [BID3.adUnitCode]: BID3.adserverTargeting + [BID3.adUnitCode]: BID3.adserverTargeting, }, REQUEST_BIDS: { - call: 'request' + call: 'request', }, ADD_AD_UNITS: { call: 'addAdUnits' }, AD_RENDER_FAILED: { call: 'adRenderFailed' }, INVALID_EVENT: { - mockKey: 'this event should not emit' - } + mockKey: 'this event should not emit', + }, }; - describe('Invisibly Analytic tests specs', function() { - beforeEach(function() { + describe('Invisibly Analytic tests specs', function () { + beforeEach(function () { xhr = sinon.useFakeXMLHttpRequest(); requests = []; - xhr.onCreate = request => requests.push(request); + xhr.onCreate = (xhr) => { + requests.push(xhr); + }; sinon.stub(events, 'getEvents').returns([]); sinon.spy(invisiblyAdapter, 'track'); }); - afterEach(function() { + afterEach(function () { invisiblyAdapter.disableAnalytics(); events.getEvents.restore(); invisiblyAdapter.track.restore(); xhr.restore(); }); - // specs to test invisibly account input to enableAnaylitcs - describe('monitor enableAnalytics method', function() { - it('should catch all events triggered with invisibly account config', function() { - invisiblyAdapter.enableAnalytics({ - provider: 'invisiblyAnalytics', - options: { - account: 'invisibly' - } + describe('Send all events as & when they are captured', function () { + beforeEach(function () { + invisiblyAdapter.weightedFilter.filter = true; + }); + // specs to test invisibly account input to enableAnaylitcs + describe('monitor enableAnalytics method', function () { + it('should catch all events triggered with invisibly account config', function () { + invisiblyAdapter.enableAnalytics({ + provider: 'invisiblyAnalytics', + options: { + account: 'invisibly', + }, + }); + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + sinon.assert.callCount(invisiblyAdapter.track, 5); + }); + + it('should not catch events triggered without invisibly account config', function () { + invisiblyAdapter.enableAnalytics({ + provider: 'invisiblyAnalytics', + options: {}, + }); + + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + invisiblyAdapter.flush(); + sinon.assert.callCount(invisiblyAdapter.track, 0); + }); + // spec to test custom api endpoint + it('support custom endpoint', function () { + let custom_url = 'custom url'; + invisiblyAdapter.enableAnalytics({ + provider: 'invisiblyAnalytics', + options: { + url: custom_url, + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + account: 'invisibly', + }, + }); + expect(invisiblyAdapter.getOptions().url).to.equal(custom_url); }); + }); + // spec for auction init event + it('auction init event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); - events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); - sinon.assert.callCount(invisiblyAdapter.track, 5); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse( + requests[1].requestBody.substring(0) + ); + // pageView is default event initially hence expecting 2 requests + expect(requests.length).to.equal(2); + expect(requests[1].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.auctionId).to.equal( + MOCK.AUCTION_INIT.auctionId + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_type).to.equal('PREBID_auctionInit'); + expect(invisiblyEvents.event_data.ver).to.equal(1); }); - it('should not catch events triggered without invisibly account config', function() { - invisiblyAdapter.enableAnalytics({ - provider: 'invisiblyAnalytics', - options: {} - }); + // spec for bid adjustment event + it('bid adjustment event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); + invisiblyAdapter.flush(); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidAdjustment'); + expect(invisiblyEvents.event_data.bidders.cpm).to.equal( + MOCK.BID_ADJUSTMENT.cpm + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bid timeout event + it('bid timeout event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidTimeout'); + expect(invisiblyEvents.event_data.bidders.bidId).to.equal( + MOCK.BID_TIMEOUT.bidId + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bid requested event + it('bid requested event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidRequested'); + expect(invisiblyEvents.event_data.auctionId).to.equal( + MOCK.BID_REQUESTED.auctionId + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bid response event + it('bid response event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidResponse'); + expect(invisiblyEvents.event_data.cpm).to.equal(MOCK.BID_REQUESTED.cpm); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for no bid event + it('no bid event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.NO_BID, MOCK.NO_BID); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_noBid'); + expect(invisiblyEvents.event_data.noBid.testKey).to.equal( + MOCK.NO_BID.testKey + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bid won event + it('bid won event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); invisiblyAdapter.flush(); - sinon.assert.callCount(invisiblyAdapter.track, 0); + + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidWon'); + sinon.assert.callCount(invisiblyAdapter.track, 1); }); - // spec to test custom api endpoint - it('support custom endpoint', function() { - let custom_url = 'custom url'; - invisiblyAdapter.enableAnalytics({ - provider: 'invisiblyAnalytics', - options: { - url: custom_url, - bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - account: 'invisibly' - } - }); - expect(invisiblyAdapter.getOptions().url).to.equal(custom_url); + + // spec for bidder done event + it('bidder done event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidderDone'); + expect(invisiblyEvents.event_data.bidderCode).to.equal( + MOCK.BIDDER_DONE.bidderCode + ); + expect(invisiblyEvents.event_data.bids.length).to.equal( + MOCK.BIDDER_DONE.bids.length + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); }); - }); - // spec for auction init event - it('auction init event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[1].requestBody.substring(0)); - // pageView is default event initially hence expecting 2 requests - expect(requests.length).to.equal(2); - expect(requests[1].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.auctionId).to.equal( - MOCK.AUCTION_INIT.auctionId - ); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_type).to.equal('PREBID_auctionInit'); - expect(invisiblyEvents.event_data.ver).to.equal(1); - }); + // spec for set targeting event + it('set targeting event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.SET_TARGETING, MOCK.SET_TARGETING); + invisiblyAdapter.flush(); - // spec for bid adjustment event - it('bid adjustment event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_bidAdjustment'); - expect(invisiblyEvents.event_data.bidders.cpm).to.equal( - MOCK.BID_ADJUSTMENT.cpm - ); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_setTargeting'); + expect( + invisiblyEvents.event_data.targetings[BID1.adUnitCode] + ).to.deep.equal(BID1.adserverTargeting); + expect( + invisiblyEvents.event_data.targetings[BID3.adUnitCode] + ).to.deep.equal(BID3.adserverTargeting); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); - // spec for bid timeout event - it('bid timeout event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_bidTimeout'); - expect(invisiblyEvents.event_data.bidders.bidId).to.equal( - MOCK.BID_TIMEOUT.bidId - ); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + // spec for request bids event + it('request bids event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); + invisiblyAdapter.flush(); - // spec for bid requested event - it('bid requested event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_bidRequested'); - expect(invisiblyEvents.event_data.auctionId).to.equal( - MOCK.BID_REQUESTED.auctionId - ); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_requestBids'); + expect(invisiblyEvents.event_data.call).to.equal( + MOCK.REQUEST_BIDS.call + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); - // spec for bid response event - it('bid response event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_bidResponse'); - expect(invisiblyEvents.event_data.cpm).to.equal(MOCK.BID_REQUESTED.cpm); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + // spec for add ad units event + it('add ad units event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); + invisiblyAdapter.flush(); - // spec for no bid event - it('no bid event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.NO_BID, MOCK.NO_BID); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_noBid'); - expect(invisiblyEvents.event_data.noBid.testKey).to.equal( - MOCK.NO_BID.testKey - ); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_addAdUnits'); + expect(invisiblyEvents.event_data.call).to.equal( + MOCK.ADD_AD_UNITS.call + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); - // spec for bid won event - it('bid won event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_bidWon'); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + // spec for ad render failed event + it('ad render failed event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); + invisiblyAdapter.flush(); - // spec for bidder done event - it('bidder done event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_bidderDone'); - expect(invisiblyEvents.event_data.bidderCode).to.equal( - MOCK.BIDDER_DONE.bidderCode - ); - expect(invisiblyEvents.event_data.bids.length).to.equal( - MOCK.BIDDER_DONE.bids.length - ); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_adRenderFailed'); + expect(invisiblyEvents.event_data.call).to.equal( + MOCK.AD_RENDER_FAILED.call + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); - // spec for set targeting event - it('set targeting event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.SET_TARGETING, MOCK.SET_TARGETING); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_setTargeting'); - expect( - invisiblyEvents.event_data.targetings[BID1.adUnitCode] - ).to.deep.equal(BID1.adserverTargeting); - expect( - invisiblyEvents.event_data.targetings[BID3.adUnitCode] - ).to.deep.equal(BID3.adserverTargeting); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + // spec for auction end event + it('auction end event', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + invisiblyAdapter.flush(); - // spec for request bids event - it('request bids event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_requestBids'); - expect(invisiblyEvents.event_data.call).to.equal(MOCK.REQUEST_BIDS.call); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + const invisiblyEvents = JSON.parse( + requests[0].requestBody.substring(0) + ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal( + 'https://api.pymx5.com/v1/sites/events' + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_auctionEnd'); + expect(invisiblyEvents.event_data.auctionId).to.equal( + MOCK.AUCTION_END.auctionId + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); - // spec for add ad units event - it('add ad units event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_addAdUnits'); - expect(invisiblyEvents.event_data.call).to.equal(MOCK.ADD_AD_UNITS.call); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + // should not call sendEvent for events not supported by the adapter + it('it should not call sendEvent for this event emit', function () { + sinon.spy(invisiblyAdapter, 'sendEvent'); + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.INVALID_EVENT, MOCK.INVALID_EVENT); + invisiblyAdapter.flush(); - // spec for ad render failed event - it('ad render failed event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_adRenderFailed'); - expect(invisiblyEvents.event_data.call).to.equal( - MOCK.AD_RENDER_FAILED.call - ); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + expect(requests.length).to.equal(0); + sinon.assert.callCount(invisiblyAdapter.track, 0); + sinon.assert.callCount(invisiblyAdapter.sendEvent, 0); + }); - // spec for auction end event - it('auction end event', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_auctionEnd'); - expect(invisiblyEvents.event_data.auctionId).to.equal( - MOCK.AUCTION_END.auctionId - ); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); + // spec to emit all events + it('track all event without errors', function () { + invisiblyAdapter.enableAnalytics(MOCK.config); - // should not call sendEvent for events not supported by the adapter - it('it should not call sendEvent for this event emit', function() { - sinon.spy(invisiblyAdapter, 'sendEvent'); - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.INVALID_EVENT, MOCK.INVALID_EVENT); - invisiblyAdapter.flush(); + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(constants.EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); + events.emit(constants.EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(constants.EVENTS.NO_BID, MOCK.NO_BID); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + events.emit(constants.EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(constants.EVENTS.SET_TARGETING, MOCK.SET_TARGETING); + events.emit(constants.EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); + events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); + events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); - expect(requests.length).to.equal(0); - sinon.assert.callCount(invisiblyAdapter.track, 0); - sinon.assert.callCount(invisiblyAdapter.sendEvent, 0); + sinon.assert.callCount(invisiblyAdapter.track, 13); + }); }); - // spec to emit all events - it('track all event without errors', function() { - invisiblyAdapter.enableAnalytics(MOCK.config); - - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); - events.emit(constants.EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); - events.emit(constants.EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); - events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); - events.emit(constants.EVENTS.NO_BID, MOCK.NO_BID); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); - events.emit(constants.EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(constants.EVENTS.SET_TARGETING, MOCK.SET_TARGETING); - events.emit(constants.EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); - events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); - events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); - - sinon.assert.callCount(invisiblyAdapter.track, 13); + describe('Should not send any event even if they are caputured', function () { + beforeEach(function () { + invisiblyAdapter.weightedFilter.filter = false; + }); + it('it should not send any event', function () { + invisiblyAdapter.enableAnalytics({ + provider: 'invisiblyAnalytics', + options: { + account: 'invisibly', + }, + }); + + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + invisiblyAdapter.flush(); + + sinon.assert.callCount(invisiblyAdapter.sendEvent, 0); + sinon.assert.callCount(invisiblyAdapter.track, 0); + expect(requests.length).to.equal(0); + }); }); }); }); diff --git a/test/spec/modules/ironsourceBidAdapter_spec.js b/test/spec/modules/ironsourceBidAdapter_spec.js new file mode 100644 index 00000000000..0ddc12f235c --- /dev/null +++ b/test/spec/modules/ironsourceBidAdapter_spec.js @@ -0,0 +1,339 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ironsourceBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { VIDEO } from '../../../src/mediaTypes.js'; + +const ENDPOINT = 'https://hb.yellowblue.io/hb'; +const TTL = 360; + +describe('ironsourceAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'isOrg': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'isOrg': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'isOrg': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'ironSource', + } + + it('sends bid request to ENDPOINT via GET', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('should send the correct bid Id', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.bid_id).to.equal('299ffc8cca0b87'); + } + }); + + it('should send the correct width and height', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('width', 640); + expect(request.data).to.have.property('height', 480); + } + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.setConfig({ + userSync: { + syncEnabled: true + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'pixel'); + } + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('us_privacy', '1YNN'); + } + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('us_privacy'); + } + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('gdpr'); + expect(request.data).to.not.have.property('gdpr_consent'); + } + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('gdpr', true); + expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); + } + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,,,,'); + } + }); + }); + + describe('interpretResponse', function () { + const response = { + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + netRevenue: true, + currency: 'USD' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '21e12606d47ba7', + cpm: 12.5, + width: 640, + height: 480, + creativeId: '21e12606d47ba7', + currency: 'USD', + netRevenue: true, + ttl: TTL, + vastXml: '', + mediaType: VIDEO + } + ]; + const result = spec.interpretResponse({ body: [response] }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + }; + + const iframeSyncResponse = { + body: { + userSyncURL: 'https://iframe-sync-url.test' + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) +}); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index f3fafed7b9a..63b04077f4e 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -1,14 +1,32 @@ -import * as utils from 'src/utils'; -import { config } from 'src/config'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; import { expect } from 'chai'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { spec } from 'modules/ixBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { spec } from 'modules/ixBidAdapter.js'; describe('IndexexchangeAdapter', function () { - const IX_SECURE_ENDPOINT = 'https://as-sec.casalemedia.com/cygnus'; + const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; const VIDEO_ENDPOINT_VERSION = 8.1; const BANNER_ENDPOINT_VERSION = 7.2; + const SAMPLE_SCHAIN = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + const DEFAULT_BANNER_VALID_BID = [ { bidder: 'ix', @@ -26,7 +44,8 @@ describe('IndexexchangeAdapter', function () { transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', bidId: '1a2b3c4d', bidderRequestId: '11a22b33c44d', - auctionId: '1aa2bb3cc4dd' + auctionId: '1aa2bb3cc4dd', + schain: SAMPLE_SCHAIN } ]; @@ -56,7 +75,8 @@ describe('IndexexchangeAdapter', function () { transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', bidderRequestId: '11a22b33c44e', - auctionId: '1aa2bb3cc4de' + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN } ]; @@ -90,6 +110,35 @@ describe('IndexexchangeAdapter', function () { ] }; + const DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN = { + cur: 'USD', + id: '11a22b33c44d', + seatbid: [ + { + bid: [ + { + crid: '12345', + 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' + } + ] + }; + const DEFAULT_VIDEO_BID_RESPONSE = { cur: 'USD', id: '1aa2bb3cc4de', @@ -170,6 +219,22 @@ describe('IndexexchangeAdapter', function () { v: 8.1 }; + const DEFAULT_USERID_DATA = { + idl_env: '1234-5678-9012-3456', // Liveramp + }; + + const DEFAULT_USERID_PAYLOAD = [ + { + source: 'liveramp.com', + uids: [{ + id: DEFAULT_USERID_DATA.idl_env, + ext: { + rtiPartner: 'idl' + } + }] + } + ]; + describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -177,6 +242,26 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('getUserSync tests', function () { + it('UserSync test : check type = iframe, check usermatch URL', function () { + const syncOptions = { + 'iframeEnabled': true + } + let userSync = spec.getUserSyncs(syncOptions); + expect(userSync[0].type).to.equal('iframe'); + const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; + expect(userSync[0].url).to.equal(USER_SYNC_URL); + }); + + it('When iframeEnabled is false, no userSync should be returned', function () { + const syncOptions = { + 'iframeEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions); + expect(userSync).to.be.an('array').that.is.empty; + }); + }); + describe('isBidRequestValid', function () { it('should return true when required params found for a banner or video ad', function () { expect(spec.isBidRequestValid(DEFAULT_BANNER_VALID_BID[0])).to.equal(true); @@ -317,21 +402,24 @@ describe('IndexexchangeAdapter', function () { let query; let testCopy; - beforeEach(function() { + beforeEach(function () { window.headertag = {}; - window.headertag.getIdentityInfo = function() { + window.headertag.getIdentityInfo = function () { return testCopy; }; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; }); - afterEach(function() { + + afterEach(function () { delete window.headertag; }); - describe('buildRequestSingleRTI', function() { - before(function() { + + describe('buildRequestSingleRTI', function () { + before(function () { testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); }); + it('payload should have correct format and value (single identity partner)', function () { const payload = JSON.parse(query.r); @@ -348,16 +436,19 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('buildRequestMultipleIds', function() { - before(function() { + describe('buildRequestMultipleIds', function () { + before(function () { testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); - testCopy.IdentityIp.data.uids.push({ - id: '1234567' - }, - { - id: '2019-04-01TF2:34:41' - }); + testCopy.IdentityIp.data.uids.push( + { + id: '1234567' + }, + { + id: '2019-04-01TF2:34:41' + } + ); }); + it('payload should have correct format and value (single identity w/ multi ids)', function () { const payload = JSON.parse(query.r); @@ -375,8 +466,8 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('buildRequestMultipleRTI', function() { - before(function() { + describe('buildRequestMultipleRTI', function () { + before(function () { testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); testCopy.JackIp = { responsePending: false, @@ -401,6 +492,7 @@ describe('IndexexchangeAdapter', function () { } } }); + it('payload should have correct format and value (multiple identity partners)', function () { const payload = JSON.parse(query.r); @@ -424,13 +516,13 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('buildRequestNoData', function() { - beforeEach(function() { + describe('buildRequestNoData', function () { + beforeEach(function () { testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); }); it('payload should not have any user eids with an undefined identity data response', function () { - window.headertag.getIdentityInfo = function() { + window.headertag.getIdentityInfo = function () { return undefined; }; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; @@ -477,12 +569,188 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('buildRequestsUserId', function () { + let validIdentityResponse; + let validUserIdPayload; + + beforeEach(function () { + window.headertag = {}; + window.headertag.getIdentityInfo = function () { + return validIdentityResponse; + }; + }); + + afterEach(function () { + delete window.headertag; + }); + + it('IX adapter reads LiveRamp IDL envelope from Prebid and adds it to Video', function () { + const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(1); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + }); + + it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { + validIdentityResponse = { + AdserverOrgIp: { + responsePending: false, + data: { + source: 'adserver.org', + uids: [ + { + id: '1234-5678-9012-3456', + ext: { + rtiPartner: 'TDID' + } + }, + { + id: 'FALSE', + ext: { + rtiPartner: 'TDID_LOOKUP' + } + }, + { + id: '2020-06-24T14:43:48.860Z', + ext: { + rtiPartner: 'TDID_CREATED_AT' + } + } + ] + } + }, + MerkleIp: { + responsePending: false, + data: { + source: 'merkle.com', + uids: [{ + id: '1234-5678-9012-3456', + ext: { + keyID: '1234-5678', + enc: 1 + } + }] + } + }, + LiveRampIp: { + source: 'liveramp.com', + uids: [ + { + id: '0000-1234-4567-8901', + ext: { + rtiPartner: 'idl' + } + } + ] + } + }; + + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA) + + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + validUserIdPayload = utils.deepClone(DEFAULT_USERID_PAYLOAD); + validUserIdPayload.push({ + source: 'merkle.com', + uids: [{ + id: '1234-5678-9012-3456', + ext: { + keyID: '1234-5678', + enc: 1 + } + }] + }) + validUserIdPayload.push({ + source: 'adserver.org', + uids: [ + { + id: '1234-5678-9012-3456', + ext: { + rtiPartner: 'TDID' + } + }, + { + id: 'FALSE', + ext: { + rtiPartner: 'TDID_LOOKUP' + } + }, + { + id: '2020-06-24T14:43:48.860Z', + ext: { + rtiPartner: 'TDID_CREATED_AT' + } + } + ] + }) + + expect(payload.user).to.exist; + expect(payload.user.eids).to.have.lengthOf(3); + expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[2]); + }); + + it('IXL and Prebid are mutually exclusive', function () { + validIdentityResponse = { + LiveIntentIp: { + responsePending: false, + data: { + source: 'liveintent.com', + uids: [{ + id: '1234-5678-9012-3456', + ext: { + keyID: '1234-5678', + rtiPartner: 'LDID', + enc: 1 + } + }] + } + } + }; + + const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + + validUserIdPayload = utils.deepClone(DEFAULT_USERID_PAYLOAD); + validUserIdPayload.push({ + source: 'liveintent.com', + uids: [{ + id: '1234-5678-9012-3456', + ext: { + keyID: '1234-5678', + rtiPartner: 'LDID', + enc: 1 + } + }] + }); + + const payload = JSON.parse(request.data.r); + expect(payload.user.eids).to.have.lengthOf(2); + expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); + }); + }); + describe('buildRequests', function () { const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const requestUrl = request.url; const requestMethod = request.method; const query = request.data; + const bidWithoutSchain = utils.deepClone(DEFAULT_BANNER_VALID_BID); + delete bidWithoutSchain[0].schain; + const requestWithoutSchain = spec.buildRequests(bidWithoutSchain, DEFAULT_OPTION)[0]; + const queryWithoutSchain = requestWithoutSchain.data; + const bidWithoutMediaType = utils.deepClone(DEFAULT_BANNER_VALID_BID); delete bidWithoutMediaType[0].mediaTypes; bidWithoutMediaType[0].sizes = [[300, 250], [300, 600]]; @@ -505,16 +773,21 @@ describe('IndexexchangeAdapter', function () { it('payload should have correct format and value', function () { 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.equal(DEFAULT_OPTION.refererInfo.referer); expect(payload.site.ref).to.equal(document.referrer); expect(payload.ext).to.exist; expect(payload.ext.source).to.equal('prebid'); + expect(payload.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); expect(payload.imp).to.exist; expect(payload.imp).to.be.an('array'); - expect(payload.imp).to.have.lengthOf(1); + expect(payload.imp).to.have.lengthOf(2); + }); + + it('payload should not include schain when not provided', function () { + const payload = JSON.parse(queryWithoutSchain.r); + expect(payload.source).to.not.exist; // source object currently only written for schain }); it('impression should have correct format and value', function () { @@ -683,6 +956,7 @@ describe('IndexexchangeAdapter', function () { const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]]); const bannerImp = JSON.parse(request[0].data.r).imp[0]; + expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(2); expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); expect(bannerImp.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(bannerImp.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); @@ -697,6 +971,80 @@ describe('IndexexchangeAdapter', function () { expect(videoImp.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); expect(videoImp.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); }); + + it('request should contain the extra banner ad sizes that IX is not configured for using the first site id in the ad unit', function () { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.sizes.push([336, 280], [970, 90]); + bid.mediaTypes.banner.sizes.push([336, 280], [970, 90]); + const bid2 = utils.deepClone(bid); + bid2.params.siteId = '124'; + bid2.params.size = [300, 600]; + bid2.params.bidId = '2b3c4d5e'; + + const request = spec.buildRequests([bid, bid2], DEFAULT_OPTION)[0]; + const impressions = JSON.parse(request.data.r).imp; + + expect(impressions).to.be.an('array'); + expect(impressions).to.have.lengthOf(4); + + expect(impressions[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) + expect(impressions[1].ext.siteID).to.equal(bid2.params.siteId) + expect(impressions[2].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) + expect(impressions[3].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) + + expect(impressions[0].banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); + expect(impressions[0].banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); + expect(impressions[1].banner.w).to.equal(bid2.params.size[0]); + expect(impressions[1].banner.h).to.equal(bid2.params.size[1]); + expect(impressions[2].banner.w).to.equal(bid.mediaTypes.banner.sizes[2][0]); + expect(impressions[2].banner.h).to.equal(bid.mediaTypes.banner.sizes[2][1]); + expect(impressions[3].banner.w).to.equal(bid.mediaTypes.banner.sizes[3][0]); + expect(impressions[3].banner.h).to.equal(bid.mediaTypes.banner.sizes[3][1]); + }); + + it('request should contain the extra banner ad sizes and their corresponding site ids when there is multiple ad units', function () { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.siteId = '124'; + bid.adUnitCode = 'div-gpt-ad-156456451554-1' + bid.transactionId = '152e36d1-1241-4242-t35e-y1dv34d12315'; + bid.bidId = '2f6g5s5e'; + bid.params.size = [336, 280] + bid.sizes = [[336, 280], [970, 90]] + bid.mediaTypes.banner.sizes = [[336, 280], [970, 90]] + + const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], bid], DEFAULT_OPTION)[0]; + + const impressions = JSON.parse(request.data.r).imp; + expect(impressions).to.be.an('array'); + expect(impressions).to.have.lengthOf(4); + + expect(impressions[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) + expect(impressions[1].ext.siteID).to.equal(bid.params.siteId) + expect(impressions[2].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) + expect(impressions[3].ext.siteID).to.equal(bid.params.siteId) + + expect(impressions[0].banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); + expect(impressions[0].banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); + expect(impressions[1].banner.w).to.equal(bid.params.size[0]); + expect(impressions[1].banner.h).to.equal(bid.params.size[1]); + expect(impressions[2].banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][0]); + expect(impressions[2].banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][1]); + expect(impressions[3].banner.w).to.equal(bid.mediaTypes.banner.sizes[1][0]); + expect(impressions[3].banner.h).to.equal(bid.mediaTypes.banner.sizes[1][1]); + + expect(impressions[0].ext.sid).to.equal(`${DEFAULT_BANNER_VALID_BID[0].params.size[0].toString()}x${DEFAULT_BANNER_VALID_BID[0].params.size[1].toString()}`); + expect(impressions[1].ext.sid).to.equal(`${bid.params.size[0].toString()}x${bid.params.size[1].toString()}`); + expect(impressions[2].ext.sid).to.equal(`${DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][0].toString()}x${DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][1].toString()}`); + expect(impressions[3].ext.sid).to.equal(`${bid.mediaTypes.banner.sizes[1][0].toString()}x${bid.mediaTypes.banner.sizes[1][1].toString()}`); + }); + + it('request should not contain the extra video ad sizes that IX is not configured for', function () { + const request = spec.buildRequests(DEFAULT_VIDEO_VALID_BID, DEFAULT_OPTION); + const impressions = JSON.parse(request[0].data.r).imp; + + expect(impressions).to.be.an('array'); + expect(impressions).to.have.lengthOf(1); + }); }); describe('buildRequestVideo', function () { @@ -777,13 +1125,14 @@ describe('IndexexchangeAdapter', function () { mediaType: 'banner', ad: '', currency: 'USD', - ttl: 35, + ttl: 300, netRevenue: true, dealId: undefined, meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; @@ -791,6 +1140,31 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should get correct bid response for banner ad with missing adomain', function () { + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + currency: 'USD', + ttl: 300, + netRevenue: true, + dealId: undefined, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA' + } + } + ]; + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + it('should set creativeId to default value if not provided', function () { const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); delete bidResponse.seatbid[0].bid[0].crid; @@ -804,13 +1178,14 @@ describe('IndexexchangeAdapter', function () { mediaType: 'banner', ad: '', currency: 'USD', - ttl: 35, + ttl: 300, netRevenue: true, dealId: undefined, meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; @@ -830,13 +1205,14 @@ describe('IndexexchangeAdapter', function () { mediaType: 'banner', ad: '', currency: 'JPY', - ttl: 35, + ttl: 300, netRevenue: true, dealId: undefined, meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; @@ -857,13 +1233,14 @@ describe('IndexexchangeAdapter', function () { mediaType: 'banner', ad: '', currency: 'USD', - ttl: 35, + ttl: 300, netRevenue: true, dealId: 'deal', meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; @@ -888,7 +1265,8 @@ describe('IndexexchangeAdapter', function () { meta: { networkId: 51, brandId: 303326, - brandName: 'OECTB' + brandName: 'OECTB', + advertiserDomains: ['www.abcd.com'] } } ]; @@ -896,7 +1274,42 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); - it('bidrequest should have consent info if gdprApplies and consentString exist', function () { + it('bidrequest should not have page if options is undefined', function () { + const options = {}; + const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); + + expect(requestWithoutreferInfo.site.page).to.be.undefined; + expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + }); + + it('bidrequest should not have page if options.refererInfo is an empty object', function () { + const options = { + refererInfo: {} + }; + const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); + + expect(requestWithoutreferInfo.site.page).to.be.undefined; + expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + }); + + it('bidrequest should sent to secure endpoint if page url is secure', function () { + const options = { + refererInfo: { + referer: 'https://www.prebid.org' + } + }; + const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); + + expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.referer); + expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + }); + }); + + describe('bidrequest consent', function () { + it('should have consent info if gdprApplies and consentString exist', function () { const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); @@ -904,7 +1317,7 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); }); - it('bidrequest should not have consent field if consentString is undefined', function () { + it('should not have consent field if consentString is undefined', function () { const options = { gdprConsent: { gdprApplies: true, @@ -918,7 +1331,7 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.user).to.be.undefined; }); - it('bidrequest should not have gdpr field if gdprApplies is undefined', function () { + it('should not have gdpr field if gdprApplies is undefined', function () { const options = { gdprConsent: { consentString: '3huaa11=qu3198ae', @@ -932,7 +1345,7 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); }); - it('bidrequest should not have consent info if options.gdprConsent is undefined', function () { + it('should not have consent info if options.gdprConsent is undefined', function () { const options = {}; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); @@ -941,37 +1354,36 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.user).to.be.undefined; }); - it('bidrequest should not have page if options is undefined', function () { - const options = {}; - const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); + it('should have us_privacy if uspConsent is defined', function () { + const options = { + uspConsent: '1YYN' + }; + const validBidWithUspConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithUspConsent = JSON.parse(validBidWithUspConsent[0].data.r); - expect(requestWithoutreferInfo.site.page).to.be.undefined; - expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + expect(requestWithUspConsent.regs.ext.us_privacy).to.equal('1YYN'); }); - it('bidrequest should not have page if options.refererInfo is an empty object', function () { - const options = { - refererInfo: {} - }; - const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); + it('should not have us_privacy if uspConsent undefined', function () { + const options = {}; + const validBidWithUspConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithUspConsent = JSON.parse(validBidWithUspConsent[0].data.r); - expect(requestWithoutreferInfo.site.page).to.be.undefined; - expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + expect(requestWithUspConsent.regs).to.be.undefined; }); - it('bidrequest should sent to secure endpoint if page url is secure', function () { + it('should have both gdpr and us_privacy if both are defined', function () { const options = { - refererInfo: { - referer: 'https://www.prebid.org' - } + gdprConsent: { + gdprApplies: true, + vendorData: {} + }, + uspConsent: '1YYN' }; - const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); - - expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.referer); - expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(requestWithConsent.regs.ext.gdpr).to.equal(1); + expect(requestWithConsent.regs.ext.us_privacy).to.equal('1YYN'); }); }); }); diff --git a/test/spec/modules/jcmBidAdapter_spec.js b/test/spec/modules/jcmBidAdapter_spec.js index fa6791674bc..9d84bca5513 100644 --- a/test/spec/modules/jcmBidAdapter_spec.js +++ b/test/spec/modules/jcmBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/jcmBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/jcmBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const ENDPOINT = 'https://media.adfrontiers.com/'; diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index b16586d0e14..c162b785aed 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { spec } from 'modules/justpremiumBidAdapter' +import { spec } from 'modules/justpremiumBidAdapter.js' describe('justpremium adapter', function () { let sandbox; @@ -44,6 +44,7 @@ describe('justpremium adapter', function () { ] let bidderRequest = { + uspConsent: '1YYN', refererInfo: { referer: 'https://justpremium.com' } @@ -80,11 +81,12 @@ describe('justpremium adapter', function () { expect(jpxRequest.id).to.equal(adUnits[0].params.zone) expect(jpxRequest.mediaTypes && jpxRequest.mediaTypes.banner && jpxRequest.mediaTypes.banner.sizes).to.not.equal('undefined') expect(jpxRequest.version.prebid).to.equal('$prebid.version$') - expect(jpxRequest.version.jp_adapter).to.equal('1.6') + expect(jpxRequest.version.jp_adapter).to.equal('1.7') expect(jpxRequest.pubcid).to.equal('0000000') expect(jpxRequest.uids.tdid).to.equal('1111111') expect(jpxRequest.uids.id5id).to.equal('2222222') expect(jpxRequest.uids.digitrustid.data.id).to.equal('3333333') + expect(jpxRequest.us_privacy).to.equal('1YYN') }) }) @@ -155,10 +157,12 @@ describe('justpremium adapter', function () { describe('getUserSyncs', function () { it('Verifies sync options', function () { - const options = spec.getUserSyncs({iframeEnabled: true}) + const options = spec.getUserSyncs({iframeEnabled: true}, {}, {gdprApplies: true, consentString: 'BOOgjO9OOgjO9APABAENAi-AAAAWd'}, '1YYN') expect(options).to.not.be.undefined expect(options[0].type).to.equal('iframe') expect(options[0].url).to.match(/\/\/pre.ads.justpremium.com\/v\/1.0\/t\/sync/) + expect(options[0].url).to.match(/&consentString=BOOgjO9OOgjO9APABAENAi-AAAAWd/) + expect(options[0].url).to.match(/&usPrivacy=1YYN/) }) }) }) diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index e1631f2d0eb..be73e140839 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect, assert} from 'chai'; -import {spec} from 'modules/kargoBidAdapter'; -import {config} from 'src/config'; +import {spec} from 'modules/kargoBidAdapter.js'; +import {config} from 'src/config.js'; describe('kargo adapter tests', function () { var sandbox, clock, frozenNow = new Date(); @@ -50,6 +50,7 @@ describe('kargo adapter tests', function () { return {adServerCurrency: 'USD'}; } if (key === 'debug') return true; + if (key === 'deviceAccess') return true; throw new Error(`Config stub incomplete! Missing key "${key}"`) }); @@ -254,7 +255,8 @@ describe('kargo adapter tests', function () { '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' }, - optOut: false + optOut: false, + usp: '1---' }, krux: { userID: 'rsgr9pnij', @@ -299,7 +301,8 @@ describe('kargo adapter tests', function () { if (excludeUserIds === true) { base.userIDs = { - crbIDs: {} + crbIDs: {}, + usp: '1---' }; delete base.prebidRawBidRequests[0].userId.tdid; } @@ -319,7 +322,7 @@ describe('kargo adapter tests', function () { if (excludeTdid) { delete clonedBids[0].userId.tdid; } - var request = spec.buildRequests(clonedBids, {timeout: 200, foo: 'bar'}); + var request = spec.buildRequests(clonedBids, {timeout: 200, uspConsent: '1---', foo: 'bar'}); expected.sessionId = getSessionId(); sessionIds.push(expected.sessionId); var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); @@ -438,7 +441,8 @@ describe('kargo adapter tests', function () { cpm: 3, adm: '
', width: 320, - height: 50 + height: 50, + metadata: {} }, 2: { id: 'bar', @@ -446,7 +450,10 @@ describe('kargo adapter tests', function () { adm: '
', width: 300, height: 250, - targetingCustom: 'dmpmptest1234' + targetingCustom: 'dmpmptest1234', + metadata: { + landingPageDomain: 'https://foobar.com' + } }, 3: { id: 'bar', @@ -484,7 +491,8 @@ describe('kargo adapter tests', function () { creativeId: 'foo', dealId: undefined, netRevenue: true, - currency: 'USD' + currency: 'USD', + meta: undefined }, { requestId: '2', cpm: 2.5, @@ -495,7 +503,10 @@ describe('kargo adapter tests', function () { creativeId: 'bar', dealId: 'dmpmptest1234', netRevenue: true, - currency: 'USD' + currency: 'USD', + meta: { + clickUrl: 'https://foobar.com' + } }, { requestId: '3', cpm: 2.5, @@ -506,7 +517,8 @@ describe('kargo adapter tests', function () { creativeId: 'bar', dealId: undefined, netRevenue: true, - currency: 'USD' + currency: 'USD', + meta: undefined }]; expect(resp).to.deep.equal(expectation); }); diff --git a/test/spec/modules/komoonaBidAdapter_spec.js b/test/spec/modules/komoonaBidAdapter_spec.js index ed84155b28d..3d62f91cae6 100644 --- a/test/spec/modules/komoonaBidAdapter_spec.js +++ b/test/spec/modules/komoonaBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/komoonaBidAdapter'; +import { spec } from 'modules/komoonaBidAdapter.js'; describe('Komoona.com Adapter Tests', function () { const bidsRequest = [ diff --git a/test/spec/modules/konduitAnalyticsAdapter_spec.js b/test/spec/modules/konduitAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..ac557d27f90 --- /dev/null +++ b/test/spec/modules/konduitAnalyticsAdapter_spec.js @@ -0,0 +1,126 @@ +import konduitAnalyticsAdapter from 'modules/konduitAnalyticsAdapter'; +import { expect } from 'chai'; +import { config } from '../../../src/config.js'; +import { server } from 'test/mocks/xhr.js'; +let events = require('src/events'); +let adapterManager = require('src/adapterManager').default; +let CONSTANTS = require('src/constants.json'); + +const eventsData = { + [CONSTANTS.EVENTS.AUCTION_INIT]: { + 'auctionId': 'test_auction_id', + 'timestamp': Date.now(), + 'auctionStatus': 'inProgress', + 'adUnitCodes': ['video-test'], + 'timeout': 700 + }, + [CONSTANTS.EVENTS.BID_REQUESTED]: { + 'bidderCode': 'test_bidder_code', + 'time': Date.now(), + 'bids': [{ + 'transactionId': 'test_transaction_id', + 'adUnitCode': 'video-test', + 'bidId': 'test_bid_id', + 'sizes': '640x480', + 'params': { 'testParam': 'test_param' } + }] + }, + [CONSTANTS.EVENTS.NO_BID]: { + 'bidderCode': 'test_bidder_code2', + 'transactionId': 'test_transaction_id', + 'adUnitCode': 'video-test', + 'bidId': 'test_bid_id' + }, + [CONSTANTS.EVENTS.BID_RESPONSE]: { + 'bidderCode': 'test_bidder_code', + 'adUnitCode': 'video-test', + 'statusMessage': 'Bid available', + 'mediaType': 'video', + 'renderedSize': '640x480', + 'cpm': 0.5, + 'currency': 'USD', + 'netRevenue': true, + 'timeToRespond': 124, + 'requestId': 'test_request_id', + 'creativeId': 144876543 + }, + [CONSTANTS.EVENTS.AUCTION_END]: { + 'auctionId': 'test_auction_id', + 'timestamp': Date.now(), + 'auctionEnd': Date.now() + 400, + 'auctionStatus': 'completed', + 'adUnitCodes': ['video-test'], + 'timeout': 700 + }, + [CONSTANTS.EVENTS.BID_WON]: { + 'bidderCode': 'test_bidder_code', + 'adUnitCode': 'video-test', + 'statusMessage': 'Bid available', + 'mediaType': 'video', + 'renderedSize': '640x480', + 'cpm': 0.5, + 'currency': 'USD', + 'netRevenue': true, + 'timeToRespond': 124, + 'requestId': 'test_request_id', + 'creativeId': 144876543 + }, +}; + +describe(`Konduit Analytics Adapter`, () => { + const konduitId = 'test'; + + beforeEach(function () { + sinon.spy(konduitAnalyticsAdapter, 'track'); + sinon.stub(events, 'getEvents').returns([]); + config.setConfig({ konduit: { konduitId } }); + }); + + afterEach(function () { + events.getEvents.restore(); + konduitAnalyticsAdapter.track.restore(); + konduitAnalyticsAdapter.disableAnalytics(); + }); + + it(`should add all events to an aggregatedEvents queue + inside konduitAnalyticsAdapter.context and send a request with correct data`, function () { + server.respondWith(JSON.stringify({ key: 'test' })); + + adapterManager.registerAnalyticsAdapter({ + code: 'konduit', + adapter: konduitAnalyticsAdapter + }); + + adapterManager.enableAnalytics({ + provider: 'konduit', + }); + + expect(konduitAnalyticsAdapter.context).to.be.an('object'); + expect(konduitAnalyticsAdapter.context.aggregatedEvents).to.be.an('array'); + + const eventTypes = [ + CONSTANTS.EVENTS.AUCTION_INIT, + CONSTANTS.EVENTS.BID_REQUESTED, + CONSTANTS.EVENTS.NO_BID, + CONSTANTS.EVENTS.BID_RESPONSE, + CONSTANTS.EVENTS.BID_WON, + CONSTANTS.EVENTS.AUCTION_END, + ]; + const args = eventTypes.map(eventType => eventsData[eventType]); + + eventTypes.forEach((eventType, i) => { + events.emit(eventType, args[i]); + }); + + server.respond(); + + expect(konduitAnalyticsAdapter.context.aggregatedEvents.length).to.be.equal(6); + expect(server.requests[0].url).to.match(/http(s):\/\/\w*\.konduit\.me\/analytics-initial-event/); + + const requestBody = JSON.parse(server.requests[0].requestBody); + expect(requestBody.konduitId).to.be.equal(konduitId); + expect(requestBody.prebidVersion).to.be.equal('$prebid.version$'); + expect(requestBody.environment).to.be.an('object'); + sinon.assert.callCount(konduitAnalyticsAdapter.track, 6); + }); +}); diff --git a/test/spec/modules/konduitWrapper_spec.js b/test/spec/modules/konduitWrapper_spec.js index bcc65ddd683..c88287c7066 100644 --- a/test/spec/modules/konduitWrapper_spec.js +++ b/test/spec/modules/konduitWrapper_spec.js @@ -1,72 +1,237 @@ import { expect } from 'chai'; -import parse from 'url-parse'; -import { buildVastUrl } from 'modules/konduitWrapper'; -import { parseQS } from 'src/url'; -import { config } from 'src/config'; +import { processBids, errorMessages } from 'modules/konduitWrapper.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; describe('The Konduit vast wrapper module', function () { - it('should make a wrapped request url when `bid` passed', function () { - const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + const konduitId = 'test'; + beforeEach(function() { + config.setConfig({ konduit: { konduitId } }); + }); - const url = parse(buildVastUrl({ - bid, - params: { 'konduit_id': 'testId' }, - })); + describe('processBids function (send one bid)', () => { + beforeEach(function() { + config.setConfig({ enableSendAllBids: false }); + }); - expect(url.protocol).to.equal('https:'); - expect(url.host).to.equal('p.konduit.me'); + it(`should make a correct processBids request and add kCpm and konduitCacheKey + to the passed bids and to the adserverTargeting object`, function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); - const queryParams = parseQS(url.query); - expect(queryParams).to.have.property('konduit_url', encodeURIComponent('http://some-vast-url.com')); - expect(queryParams).to.have.property('konduit_header_bidding', '1'); - expect(queryParams).to.have.property('konduit_id', 'testId'); - }); + server.respondWith(JSON.stringify({ + kCpmData: { [`${bid.bidderCode}:${bid.creativeId}`]: bid.cpm }, + cacheData: { [`${bid.bidderCode}:${bid.creativeId}`]: 'test_cache_key' }, + })); - it('should return null when no `konduit_id` (required param) passed', function () { - const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + processBids({ bid }); + server.respond(); - const url = buildVastUrl({ bid }); + expect(server.requests.length).to.equal(1); - expect(url).to.equal(null); - }); + const requestBody = JSON.parse(server.requests[0].requestBody); - it('should return null when either bid or adUnit is not passed', function () { - const url = buildVastUrl({ params: { 'konduit_id': 'testId' } }); + expect(requestBody.clientId).to.equal(konduitId); - expect(url).to.equal(null); - }); + expect(bid.konduitCacheKey).to.equal('test_cache_key'); + expect(bid.kCpm).to.equal(bid.cpm); + + expect(bid.adserverTargeting).to.be.an('object'); + + expect(bid.adserverTargeting.k_cpm).to.equal(bid.pbCg || bid.pbAg); + expect(bid.adserverTargeting.k_cache_key).to.equal('test_cache_key'); + expect(bid.adserverTargeting.konduit_id).to.equal(konduitId); + }); + + it(`should call callback with error object in arguments if cacheData is empty in the response`, function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + + server.respondWith(JSON.stringify({ + kCpmData: { [`${bid.bidderCode}:${bid.creativeId}`]: bid.cpm }, + cacheData: {}, + })); + const callback = sinon.spy(); + processBids({ bid, callback }); + server.respond(); + + expect(server.requests.length).to.equal(1); + + const requestBody = JSON.parse(server.requests[0].requestBody); + + expect(requestBody.clientId).to.equal(konduitId); - it('should return null when bid does not contain vastUrl', function () { - const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + expect(bid.konduitCacheKey).to.be.undefined; + expect(bid.kCpm).to.equal(bid.cpm); - delete bid.vastUrl; + expect(bid.adserverTargeting.k_cpm).to.equal(bid.pbCg || bid.pbAg); + expect(bid.adserverTargeting.k_cache_key).to.be.undefined; + expect(bid.adserverTargeting.konduit_id).to.be.undefined; - const url = buildVastUrl({ - bid, - params: { 'konduit_id': 'testId' }, + expect(callback.firstCall.args[0]).to.be.an('error'); }); - expect(url).to.equal(null); + it('should call callback if processBids request is sent successfully', function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + server.respondWith(JSON.stringify({ key: 'test' })); + const callback = sinon.spy(); + processBids({ + bid, + callback + }); + server.respond(); + + expect(callback.calledOnce).to.be.true; + }); + + it('should call callback with error object in arguments if processBids request is failed', function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + const callback = sinon.spy(); + processBids({ + bid, + callback + }); + server.respond(); + + expect(callback.calledOnce).to.be.true; + expect(callback.firstCall.args[0]).to.be.an('error'); + }); + + it('should call callback with error object in arguments if no konduitId in configs', function () { + config.setConfig({ konduit: { konduitId: null } }); + + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + const callback = sinon.spy(); + processBids({ + bid, + callback + }); + + expect(callback.calledOnce).to.be.true; + expect(callback.firstCall.args[0]).to.be.an('error'); + expect(callback.firstCall.args[0].message).to.equal(errorMessages.NO_KONDUIT_ID); + }); + + it('should call callback with error object in arguments if no bids found', function () { + const callback = sinon.spy(); + processBids({ + bid: null, + bids: [], + callback + }); + + expect(callback.calledOnce).to.be.true; + expect(callback.firstCall.args[0]).to.be.an('error'); + expect(callback.firstCall.args[0].message).to.equal(errorMessages.NO_BIDS); + }); }); + describe('processBids function (send all bids)', () => { + beforeEach(function() { + config.setConfig({ enableSendAllBids: true }); + }); - it('should return wrapped vastUrl based on cached url in params', function () { - config.setConfig({ cache: { url: 'https://cached.url.com' } }); - const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + it(`should make a correct processBids request and add kCpm and konduitCacheKey + to the passed bids and to the adserverTargeting object`, function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); - delete bid.vastUrl; + server.respondWith(JSON.stringify({ + kCpmData: { [`${bid.bidderCode}:${bid.creativeId}`]: bid.cpm }, + cacheData: { [`${bid.bidderCode}:${bid.creativeId}`]: 'test_cache_key' }, + })); - const expectedUrl = encodeURIComponent(`https://cached.url.com?uuid=${bid.videoCacheKey}`); + processBids({ adUnitCode: 'video1', bids: [bid] }); + server.respond(); - const url = parse(buildVastUrl({ - bid, - params: { 'konduit_id': 'testId' }, - })); - const queryParams = parseQS(url.query); + expect(server.requests.length).to.equal(1); - expect(queryParams).to.have.property('konduit_url', expectedUrl); + const requestBody = JSON.parse(server.requests[0].requestBody); - config.resetConfig(); + expect(requestBody.clientId).to.equal(konduitId); + + expect(bid.konduitCacheKey).to.equal('test_cache_key'); + expect(bid.kCpm).to.equal(bid.cpm); + + expect(bid.adserverTargeting).to.be.an('object'); + + expect(bid.adserverTargeting.k_cpm).to.equal(bid.pbCg || bid.pbAg); + expect(bid.adserverTargeting[`k_cpm_${bid.bidderCode}`]).to.equal(bid.pbCg || bid.pbAg); + expect(bid.adserverTargeting.k_cache_key).to.equal('test_cache_key'); + expect(bid.adserverTargeting[`k_cache_key_${bid.bidderCode}`]).to.equal('test_cache_key'); + expect(bid.adserverTargeting.konduit_id).to.equal(konduitId); + }); + + it(`should call callback with error object in arguments if cacheData is empty in the response`, function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + + server.respondWith(JSON.stringify({ + kCpmData: { [`${bid.bidderCode}:${bid.creativeId}`]: bid.cpm }, + cacheData: {}, + })); + const callback = sinon.spy(); + processBids({ adUnitCode: 'video1', bids: [bid], callback }); + server.respond(); + + expect(server.requests.length).to.equal(1); + + const requestBody = JSON.parse(server.requests[0].requestBody); + + expect(requestBody.clientId).to.equal(konduitId); + + expect(bid.konduitCacheKey).to.be.undefined; + expect(bid.kCpm).to.equal(bid.cpm); + + expect(bid.adserverTargeting.k_cpm).to.equal(bid.pbCg || bid.pbAg); + expect(bid.adserverTargeting[`k_cpm_${bid.bidderCode}`]).to.equal(bid.pbCg || bid.pbAg); + expect(bid.adserverTargeting.k_cache_key).to.be.undefined; + expect(bid.adserverTargeting[`k_cache_key_${bid.bidderCode}`]).to.be.undefined; + expect(bid.adserverTargeting.konduit_id).to.be.undefined; + + expect(callback.firstCall.args[0]).to.be.an('error'); + }); + + it('should call callback if processBids request is sent successfully', function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + server.respondWith(JSON.stringify({ key: 'test' })); + const callback = sinon.spy(); + processBids({ adUnitCode: 'video1', bid: [bid], callback }); + server.respond(); + + expect(callback.calledOnce).to.be.true; + }); + + it('should call callback with error object in arguments if processBids request is failed', function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + const callback = sinon.spy(); + processBids({ adUnitCode: 'video1', bid: [bid], callback }); + server.respond(); + + expect(callback.calledOnce).to.be.true; + expect(callback.firstCall.args[0]).to.be.an('error'); + }); + + it('should call callback with error object in arguments if no konduitId in configs', function () { + config.setConfig({ konduit: { konduitId: null } }); + + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + const callback = sinon.spy(); + processBids({ adUnitCode: 'video1', bid: [bid], callback }); + + expect(callback.calledOnce).to.be.true; + expect(callback.firstCall.args[0]).to.be.an('error'); + expect(callback.firstCall.args[0].message).to.equal(errorMessages.NO_KONDUIT_ID); + }); + + it('should call callback with error object in arguments if no bids found', function () { + const callback = sinon.spy(); + processBids({ + bid: null, + bids: [], + callback + }); + + expect(callback.calledOnce).to.be.true; + expect(callback.firstCall.args[0]).to.be.an('error'); + expect(callback.firstCall.args[0].message).to.equal(errorMessages.NO_BIDS); + }); }); }); @@ -101,9 +266,9 @@ function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, 'bidder': 'appnexus', 'timeToRespond': 61, 'pbLg': '5.00', - 'pbMg': '5.00', + 'pbMg': `${cpm}.00`, 'pbHg': '5.00', - 'pbAg': '5.00', + 'pbAg': `${cpm}.00`, 'pbDg': '5.00', 'pbCg': '', 'size': '640x360', @@ -119,7 +284,7 @@ function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, }, 'customCacheKey': `${priceIndustryDuration}_${uuid}`, 'meta': { - 'iabSubCatId': 'iab-1', + 'primaryCatId': 'iab-1', 'adServerCatId': label }, 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' diff --git a/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js new file mode 100644 index 00000000000..a236ac17d71 --- /dev/null +++ b/test/spec/modules/lemmaBidAdapter_spec.js @@ -0,0 +1,335 @@ +import { expect } from 'chai'; +import { spec } from 'modules/lemmaBidAdapter.js'; +import * as utils from 'src/utils.js'; +const constants = require('src/constants.json'); + +describe('lemmaBidAdapter', function() { + var bidRequests; + var videoBidRequests; + var bidResponses; + beforeEach(function() { + bidRequests = [{ + bidder: 'lemma', + mediaType: 'banner', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD', + geo: { + lat: '12.3', + lon: '23.7', + } + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + videoBidRequests = [{ + code: 'video1', + mediaType: 'video', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'lemma', + params: { + pubId: 1001, + adunitId: 1, + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }]; + bidResponses = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '

lemma"Connecting Advertisers and Publishers directly"

', + 'adomain': ['amazon.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '22918', + 'crid': 'v55jutrh', + 'h': 250, + 'w': 300, + 'ext': {} + }] + }] + } + }; + }); + describe('implementation', function() { + describe('Bid validations', function() { + it('valid bid case', function() { + var validBid = { + bidder: 'lemma', + params: { + pubId: 1001, + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + it('invalid bid case', function() { + var isValid = spec.isBidRequestValid(); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId not passed', function() { + var validBid = { + bidder: 'lemma', + params: { + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId is not number', function() { + var validBid = { + bidder: 'lemma', + params: { + pubId: '301', + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: adunitId is not passed', function() { + var validBid = { + bidder: 'lemma', + params: { + pubId: 1001 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: video bid request mimes is not passed', function() { + var validBid = { + bidder: 'lemma', + params: { + pubId: 1001, + adunitId: 1, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + validBid.params.video.mimes = []; + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + describe('Request formation', function() { + it('buildRequests function should not modify original bidRequests object', function() { + var originalBidRequests = utils.deepClone(bidRequests); + var request = spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + it('Endpoint checking', function() { + var request = spec.buildRequests(bidRequests); + expect(request.url).to.equal('https://ads.lemmatechnologies.com/lemma/servad?pid=1001&aid=1'); + expect(request.method).to.equal('POST'); + }); + it('Request params check', function() { + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal('1'); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + }); + it('Request params check without mediaTypes object', function() { + var bidRequests = [{ + bidder: 'lemma', + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(600); // height + }); + it('Request params check: without tagId', function() { + delete bidRequests[0].params.adunitId; + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal(undefined); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + }); + it('Request params multi size format object check', function() { + var bidRequests = [{ + bidder: 'lemma', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - size passed in adslot */ + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + /* case 2 - size passed in adslot as well as in sizes array */ + bidRequests[0].sizes = [ + [300, 600], + [300, 250] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 600], + [300, 250] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(600); // height + /* case 3 - size passed in sizes but not in adslot */ + bidRequests[0].params.adunitId = 1; + bidRequests[0].sizes = [ + [300, 250], + [300, 600] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(250); // height + }); + it('Request params currency check', function() { + var bidRequest = [{ + bidder: 'lemma', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - + currency specified in adunits + output: imp[0] use currency specified in bidRequests[0].params.currency + */ + var request = spec.buildRequests(bidRequest); + var data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + /* case 2 - + currency specified in adunit + output: imp[0] use default currency - USD + */ + delete bidRequest[0].params.currency; + request = spec.buildRequests(bidRequest); + data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + it('Request params check for video ad', function() { + var request = spec.buildRequests(videoBidRequests); + var data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + describe('Response checking', function() { + it('should check for valid response values', function() { + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + var response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); + expect(response[0].currency).to.equal('USD'); + expect(response[0].netRevenue).to.equal(false); + expect(response[0].ttl).to.equal(300); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/lifestreetBidAdapter_spec.js b/test/spec/modules/lifestreetBidAdapter_spec.js new file mode 100644 index 00000000000..d66727da644 --- /dev/null +++ b/test/spec/modules/lifestreetBidAdapter_spec.js @@ -0,0 +1,232 @@ +import { expect } from 'chai'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { spec } from 'modules/lifestreetBidAdapter.js'; + +describe('lifestreetBidAdapter', function() { + let bidRequests; + let videoBidRequests; + let bidResponses; + let videoBidResponses; + beforeEach(function() { + bidRequests = [ + { + bidder: 'lifestreet', + params: { + slot: 'slot166704', + adkey: '78c', + ad_size: '160x600' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600], + [300, 600] + ] + } + }, + sizes: [ + [160, 600], + [300, 600] + ] + } + ]; + + bidResponses = { + body: { + cpm: 0.1, + netRevenue: true, + content_type: 'display_flash', + width: 160, + currency: 'USD', + ttl: 86400, + content: '', + 'adid': '56380110', + 'cid': '44724710', + 'crid': '443801010', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'targeting': { + 'hb_bidder': 'luponmedia', + 'hb_pb': '0.40', + 'hb_size': '300x250' + }, + 'type': 'banner' + } + } + } + ], + 'seat': 'luponmedia' + } + ], + 'cur': 'USD', + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'ok', + 'bidder_status': [] + } + } + }; + + let expectedResponse = [ + { + 'requestId': '2a122246ef72ea', + 'cpm': '0.43', + 'width': 300, + 'height': 250, + 'creativeId': '443801010', + 'currency': 'USD', + 'dealId': '23425', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': ' ' + } + ]; + + let bidderRequest = { + 'data': '{"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}' + }; + + let result = spec.interpretResponse({ body: response }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let noBidResponse = []; + + let noBidBidderRequest = { + 'data': '{"site":{"page":""}}' + } + let noBidResult = spec.interpretResponse({ body: noBidResponse }, noBidBidderRequest); + expect(noBidResult.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse1 = { + 'body': { + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'ok', + 'bidder_status': [ + { + 'bidder': 'luponmedia', + 'no_cookie': true, + 'usersync': { + 'url': 'https://adxpremium.services/api/usersync', + 'type': 'redirect' + } + }, + { + 'bidder': 'luponmedia', + 'no_cookie': true, + 'usersync': { + 'url': 'https://adxpremium.services/api/iframeusersync', + 'type': 'iframe' + } + } + ] + } + } + } + }; + + const bidResponse2 = { + 'body': { + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'no_cookie', + 'bidder_status': [] + } + } + } + }; + + it('should use a sync url from first response (pixel and iframe)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse1, bidResponse2]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://adxpremium.services/api/usersync' + }, + { + type: 'iframe', + url: 'https://adxpremium.services/api/iframeusersync' + } + ]); + }); + + it('handle empty response (e.g. timeout)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('returns empty syncs when not pixel enabled and not iframe enabled', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([]); + }); + + it('returns pixel syncs when pixel enabled and not iframe enabled', function() { + resetUserSync(); + + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://adxpremium.services/api/usersync' + } + ]); + }); + + it('returns iframe syncs when not pixel enabled and iframe enabled', function() { + resetUserSync(); + + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: true }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://adxpremium.services/api/iframeusersync' + } + ]); + }); + }); + + describe('hasValidSupplyChainParams', function () { + it('returns true if schain is valid', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + }; + + const checkSchain = hasValidSupplyChainParams(schain); + expect(checkSchain).to.equal(true); + }); + + it('returns false if schain is invalid', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'invalid': 'novi.ba' + } + ] + }; + + const checkSchain = hasValidSupplyChainParams(schain); + expect(checkSchain).to.equal(false); + }); + }); +}); diff --git a/test/spec/modules/madvertiseBidAdapter_spec.js b/test/spec/modules/madvertiseBidAdapter_spec.js index e10a192355a..041b49ef69e 100644 --- a/test/spec/modules/madvertiseBidAdapter_spec.js +++ b/test/spec/modules/madvertiseBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; -import {config} from 'src/config'; -import * as utils from 'src/utils'; -import {spec} from 'modules/madvertiseBidAdapter'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import {spec} from 'modules/madvertiseBidAdapter.js'; describe('madvertise adapater', function () { describe('Test validate req', function () { diff --git a/test/spec/modules/mantisBidAdapter_spec.js b/test/spec/modules/mantisBidAdapter_spec.js index 7caa9b29f6f..d9ab3c69a24 100644 --- a/test/spec/modules/mantisBidAdapter_spec.js +++ b/test/spec/modules/mantisBidAdapter_spec.js @@ -1,9 +1,21 @@ import {expect} from 'chai'; -import {spec} from 'modules/mantisBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; +import {spec} from 'modules/mantisBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {sfPostMessage, iframePostMessage} from 'modules/mantisBidAdapter'; describe('MantisAdapter', function () { const adapter = newBidder(spec); + let sandbox; + let clock; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(); + }); + + afterEach(function() { + sandbox.restore(); + }); describe('isBidRequestValid', function () { let bid = { @@ -31,6 +43,68 @@ describe('MantisAdapter', function () { }); }); + describe('viewability', function() { + it('iframe (viewed)', () => { + let viewed = false; + + sandbox.stub(document, 'getElementsByTagName').withArgs('iframe').returns([ + { + name: 'mantis', + getBoundingClientRect: () => ({ + top: 10, + bottom: 260, + left: 10, + right: 190, + width: 300, + height: 250 + }) + } + ]); + + iframePostMessage({innerHeight: 500, innerWidth: 500}, 'mantis', () => viewed = true); + + sandbox.clock.runAll(); + + expect(viewed).to.equal(true); + }); + + it('safeframe (viewed)', () => { + let viewed = false; + + sfPostMessage({ + ext: { + register: (width, height, callback) => { + expect(width).to.equal(100); + expect(height).to.equal(200); + + callback(); + }, + inViewPercentage: () => 60 + } + }, 100, 200, () => viewed = true); + + expect(viewed).to.equal(true); + }); + + it('safeframe (unviewed)', () => { + let viewed = false; + + sfPostMessage({ + ext: { + register: (width, height, callback) => { + expect(width).to.equal(100); + expect(height).to.equal(200); + + callback(); + }, + inViewPercentage: () => 30 + } + }, 100, 200, () => viewed = true); + + expect(viewed).to.equal(false); + }); + }); + describe('buildRequests', function () { let bidRequests = [ { @@ -47,6 +121,24 @@ describe('MantisAdapter', function () { } ]; + it('gdpr consent not required', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {gdprApplies: false}}); + + expect(request.url).not.to.include('consent=false'); + }); + + it('gdpr consent required', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {gdprApplies: true}}); + + expect(request.url).to.include('consent=false'); + }); + + it('usp consent', function () { + const request = spec.buildRequests(bidRequests, {uspConsent: 'foobar'}); + + expect(request.url).to.include('usp=foobar'); + }); + it('domain override', function () { window.mantis_domain = 'https://foo'; const request = spec.buildRequests(bidRequests); diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index a58857e0f3f..b4c2fe68f34 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -1,122 +1,661 @@ -import { expect } from 'chai' -import { spec, _getPlatform } from 'modules/marsmediaBidAdapter' -import { newBidder } from 'src/adapters/bidderFactory' - -describe('marsmediaBidAdapter', function () { - const adapter = newBidder(spec) - - let bidRequest = { - 'bidId': '123', - 'sizes': [[ 300, 250 ]], - 'params': { - 'publisherID': 9999, - 'floor': 0.1 - } - } - - describe('codes', function () { - it('should return a bidder code of marsmedia', function () { - expect(spec.code).to.equal('marsmedia') - }) - it('should alias mars', function () { - expect(spec.aliases.length > 0 && spec.aliases[0] === 'mars').to.be.true - }) - }) +import {spec} from '../../../modules/marsmediaBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +import * as sinon from 'sinon'; - describe('isBidRequestValid', function () { - it('should return true if all params present', function () { - expect(spec.isBidRequestValid(bidRequest)).to.be.true - }) - - it('should return false if any parameter missing', function () { - expect(spec.isBidRequestValid(Object.assign(bidRequest, { params: { publisherID: null } }))).to.be.false - }) - }) - - describe('buildRequests', function () { - let req = spec.buildRequests([ bidRequest ], { refererInfo: { } }) - let rdata - - it('should return request object', function () { - expect(req).to.not.be.null - }) - - it('should build request data', function () { - expect(req.data).to.not.be.null - }) - - it('should include one request', function () { - rdata = JSON.parse(req.data) - expect(rdata.imp.length).to.equal(1) - }) - - it('should include all publisher params', function () { - let r = rdata.imp[0] - expect(r.publisherID !== null).to.be.true - }) - }) - - describe('interpretResponse', function () { - let response; - beforeEach(function () { - response = { - body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ - 'id': '1', - 'impid': '1', - 'cid': '1', - 'price': 0.1, - 'nurl': '', - 'adm': '', - 'w': 320, - 'h': 250 - }] - }] +var r1adapter = spec; + +describe('marsmedia adapter tests', function () { + beforeEach(function() { + this.defaultBidderRequest = { + 'refererInfo': { + 'referer': 'Reference Page', + 'stack': [ + 'aodomain.dvl', + 'page.dvl' + ] + } + }; + }); + + describe('Verify 1.0 POST Banner Bid Request', function () { + it('buildRequests works', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); + expect(bidRequest.method).to.equal('POST'); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.site).to.not.equal(null); + expect(openrtbRequest.site.ref).to.equal('Reference Page'); + expect(openrtbRequest.device).to.not.equal(null); + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + expect(openrtbRequest.device.dnt).to.equal(0); + expect(openrtbRequest.imp[0].banner).to.not.equal(null); + expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(openrtbRequest.imp[0].ext.bidder.zoneId).to.equal(9999); + }); + + it('interpretResponse works', function() { + var bidList = { + 'body': [ + { + 'impid': 'div-gpt-ad-1438287399331-0', + 'w': 300, + 'h': 250, + 'adm': '
My Compelling Ad
', + 'price': 1, + 'crid': 'cr-cfy24', + 'nurl': '' + } + ] }; + + var bannerBids = r1adapter.interpretResponse(bidList); + + expect(bannerBids.length).to.equal(1); + const bid = bannerBids[0]; + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('cr-cfy24'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.cpm).to.equal(1.0); + expect(bid.ttl).to.equal(350); }); + }); - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': '37386aade21a71', - 'cpm': 0.1, - 'width': 320, - 'height': 250, - 'creativeId': '1', - 'currency': 'USD', - 'netRevenue': true, - 'ad': ``, - 'ttl': 60 - }]; + describe('Verify POST Video Bid Request', function() { + it('buildRequests works', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'sizes': [ + [300, 250] + ], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); + expect(bidRequest.method).to.equal('POST'); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.site).to.not.equal(null); + expect(openrtbRequest.device).to.not.equal(null); + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + expect(openrtbRequest.device).to.have.property('dnt'); + expect(openrtbRequest.imp[0].video).to.not.equal(null); + expect(openrtbRequest.imp[0].video.w).to.equal(640); + expect(openrtbRequest.imp[0].video.h).to.equal(480); + expect(openrtbRequest.imp[0].video.mimes[0]).to.equal('video/mp4'); + expect(openrtbRequest.imp[0].video.protocols).to.eql([2, 3, 5, 6]); + expect(openrtbRequest.imp[0].video.startdelay).to.equal(0); + expect(openrtbRequest.imp[0].video.skip).to.equal(0); + expect(openrtbRequest.imp[0].video.playbackmethod).to.eql([1, 2, 3, 4]); + expect(openrtbRequest.imp[0].video.delivery[0]).to.equal(1); + expect(openrtbRequest.imp[0].video.api).to.eql([1, 2, 5]); }); - it('handles empty bid response', function () { - let response = { - body: '' + it('interpretResponse with vast url works', function() { + var bidList = { + 'body': [ + { + 'impid': 'div-gpt-ad-1438287399331-1', + 'price': 1, + 'adm': 'https://example.com/', + 'adomain': [ + 'test.com' + ], + 'cid': '467415', + 'crid': 'cr-vid', + 'w': 800, + 'h': 600, + 'nurl': '' + } + ] }; - let result = spec.interpretResponse(response); - expect(result.length).to.equal(0); + + var videoBids = r1adapter.interpretResponse(bidList); + + expect(videoBids.length).to.equal(1); + const bid = videoBids[0]; + expect(bid.width).to.equal(800); + expect(bid.height).to.equal(600); + expect(bid.vastUrl).to.equal('https://example.com/'); + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('cr-vid'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.cpm).to.equal(1.0); + expect(bid.ttl).to.equal(600); + }); + + it('interpretResponse with xml works', function() { + var bidList = { + 'body': [ + { + 'impid': 'div-gpt-ad-1438287399331-1', + 'price': 1, + 'adm': '', + 'adomain': [ + 'test.com' + ], + 'cid': '467415', + 'crid': 'cr-vid', + 'w': 800, + 'h': 600, + 'nurl': '' + } + ] + }; + + var videoBids = r1adapter.interpretResponse(bidList); + + expect(videoBids.length).to.equal(1); + const bid = videoBids[0]; + expect(bid.width).to.equal(800); + expect(bid.height).to.equal(600); + expect(bid.vastXml).to.equal(''); + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('cr-vid'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.cpm).to.equal(1.0); + expect(bid.ttl).to.equal(600); + }); + }); + + describe('misc buildRequests', function() { + it('should send GDPR Consent data to Marsmedia tag', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-3', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var consentString = 'testConsentString'; + var gdprBidderRequest = this.defaultBidderRequest; + gdprBidderRequest.gdprConsent = { + 'gdprApplies': true, + 'consentString': consentString + }; + + var bidRequest = r1adapter.buildRequests(bidRequestList, gdprBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.user.ext.consent).to.equal(consentString); + expect(openrtbRequest.regs.ext.gdpr).to.equal(true); + }); + + it('prefer 2.0 sizes', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(600); + }); + + it('does not return request for invalid banner size configuration', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300]] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + expect(bidRequest.method).to.be.undefined; + }); + + it('does not return request for missing banner size configuration', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': {} + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + expect(bidRequest.method).to.be.undefined; + }); + + it('reject bad sizes', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': {'sizes': [['400', '500'], ['4n0', '5g0']]} + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); + }); + + it('dnt is correctly set to 1', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var dntStub = sinon.stub(utils, 'getDNT').returns(1); + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + dntStub.restore(); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.device.dnt).to.equal(1); + }); + + it('supports string video sizes', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': ['600', '300'] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.equal(600); + expect(openrtbRequest.imp[0].video.h).to.equal(300); + }); + + it('rejects bad video sizes', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': ['badWidth', 'badHeight'] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.be.undefined; + expect(openrtbRequest.imp[0].video.h).to.be.undefined; + }); + + it('supports missing video size', function () { + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.be.undefined; + expect(openrtbRequest.imp[0].video.h).to.be.undefined; + }); + + it('should return empty site data when refererInfo is missing', function() { + delete this.defaultBidderRequest.refererInfo; + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.site.domain).to.equal(''); + expect(openrtbRequest.site.page).to.equal(''); + expect(openrtbRequest.site.ref).to.equal(''); + }); + }); + + it('should return empty site.domain and site.page when refererInfo.stack is empty', function() { + this.defaultBidderRequest.refererInfo.stack = []; + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.site.domain).to.equal(''); + expect(openrtbRequest.site.page).to.equal(''); + expect(openrtbRequest.site.ref).to.equal('Reference Page'); + }); + + it('should secure correctly', function() { + this.defaultBidderRequest.refererInfo.stack[0] = ['https://securesite.dvl']; + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.imp[0].secure).to.equal(1); + }); + + it('should pass schain', function() { + var schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 1 + }] + }; + var bidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead', + 'schain': schain + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.source.ext.schain).to.deep.equal(schain); + }); + + describe('misc interpretResponse', function () { + it('No bid response', function() { + var noBidResponse = r1adapter.interpretResponse({ + 'body': '' + }); + expect(noBidResponse.length).to.equal(0); + }); + }); + + describe('isBidRequestValid', function () { + var bid = { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'bannerDiv' + }; + + it('should return true when required params found', function () { + expect(r1adapter.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId missing', function () { + delete bid.params.zoneId; + expect(r1adapter.isBidRequestValid(bid)).to.equal(false); }); }); describe('getUserSyncs', function () { - /* it('should return iframe sync', function () { - let sync = spec.getUserSyncs({ iframeEnabled: true }) - expect(sync.length).to.equal(1) - expect(sync[0].type === 'iframe') - expect(typeof sync[0].url === 'string') - }) - - it('should return pixel sync', function () { - let sync = spec.getUserSyncs({ pixelEnabled: true }) - expect(sync.length).to.equal(1) - expect(sync[0].type === 'image') - expect(typeof sync[0].url === 'string') - }) */ - }) -}) + it('returns an empty string', function () { + expect(r1adapter.getUserSyncs()).to.deep.equal([]); + }); + }); + + describe('on bidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); + }); + + describe('on Timeout', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onTimeout({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); + }); + + describe('on Set Targeting', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onSetTargeting).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onSetTargeting({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/meazyBidAdapter_spec.js b/test/spec/modules/meazyBidAdapter_spec.js index 123d53f2fa1..2c24791f515 100644 --- a/test/spec/modules/meazyBidAdapter_spec.js +++ b/test/spec/modules/meazyBidAdapter_spec.js @@ -1,7 +1,7 @@ -import * as utils from 'src/utils'; +import * as utils from 'src/utils.js'; import { expect } from 'chai'; -import { spec } from 'modules/meazyBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/meazyBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const MEAZY_PID = '6910b7344ae566a1' const VALID_ENDPOINT = `https://rtb-filter.meazy.co/pbjs?host=${utils.getOrigin()}&api_key=${MEAZY_PID}`; diff --git a/test/spec/modules/mediaforceBidAdapter_spec.js b/test/spec/modules/mediaforceBidAdapter_spec.js new file mode 100644 index 00000000000..ee478acbc83 --- /dev/null +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -0,0 +1,440 @@ +import {assert} from 'chai'; +import {spec} from 'modules/mediaforceBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +import {BANNER, NATIVE} from '../../../src/mediaTypes.js'; + +describe('mediaforce bid adapter', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + function getLanguage() { + let language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; + } + + const language = getLanguage(); + const baseUrl = 'https://rtb.mfadsrvr.com'; + + describe('isBidRequestValid()', function () { + const defaultBid = { + bidder: 'mediaforce', + params: { + property: '10433394', + bidfloor: 0.3, + }, + }; + + it('should not accept bid without required params', function () { + assert.equal(spec.isBidRequestValid(defaultBid), false); + }); + + it('should return false when params are not passed', function () { + let bid = utils.deepClone(defaultBid); + delete bid.params; + assert.equal(spec.isBidRequestValid(bid), false); + }); + + it('should return false when valid params are not passed', function () { + let bid = utils.deepClone(defaultBid); + bid.params = {placement_id: '', publisher_id: ''}; + assert.equal(spec.isBidRequestValid(bid), false); + }); + + it('should return true when valid params are passed', function () { + let bid = utils.deepClone(defaultBid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {publisher_id: 2, placement_id: '123'}; + assert.equal(spec.isBidRequestValid(bid), true); + }); + }); + + describe('buildRequests()', function () { + const defaultBid = { + bidder: 'mediaforce', + params: { + publisher_id: 'pub123', + placement_id: '202', + }, + nativeParams: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + sizes: [300, 250], + }, + sponsoredBy: { + required: true + } + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + sizes: [300, 250], + }, + sponsoredBy: { + required: true + } + } + }, + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + }; + + const refererInfo = { + referer: 'https://www.prebid.org', + reachedTop: true, + stack: [ + 'https://www.prebid.org/page.html', + 'https://www.prebid.org/iframe1.html', + ] + }; + + const requestUrl = `${baseUrl}/header_bid`; + const dnt = utils.getDNT() ? 1 : 0; + const secure = 1; + + it('should return undefined if no validBidRequests passed', function () { + assert.equal(spec.buildRequests([]), undefined); + }); + + it('should return proper request url: no refererInfo', function () { + let [request] = spec.buildRequests([defaultBid]); + assert.equal(request.url, requestUrl); + }); + + it('should return proper banner imp', function () { + let bid = utils.deepClone(defaultBid); + bid.params.bidfloor = 0.5; + + let bidRequests = [bid]; + let bidderRequest = {bids: bidRequests, refererInfo: refererInfo}; + + let [request] = spec.buildRequests(bidRequests, bidderRequest); + + let data = JSON.parse(request.data); + assert.deepEqual(data, { + id: bid.transactionId, + site: { + id: bid.params.publisher_id, + publisher: {id: bid.params.publisher_id}, + ref: encodeURIComponent(refererInfo.referer), + page: encodeURIComponent(refererInfo.referer), + }, + device: { + ua: navigator.userAgent, + dnt: dnt, + js: 1, + language: language, + }, + imp: [{ + tagid: bid.params.placement_id, + secure: secure, + bidfloor: bid.params.bidfloor, + banner: {w: 300, h: 250}, + native: { + ver: '1.2', + request: { + assets: [ + {id: 1, title: {len: 800}, required: 1}, + {id: 3, img: {w: 300, h: 250, type: 3}, required: 1}, + {id: 5, data: {type: 1}, required: 1} + ], + context: 1, + plcmttype: 1, + ver: '1.2' + } + }, + }], + }); + + assert.deepEqual(request, { + method: 'POST', + url: requestUrl, + data: '{"id":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b","site":{"page":"https%3A%2F%2Fwww.prebid.org","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"imp":[{"tagid":"202","secure":1,"bidfloor":0.5,"banner":{"w":300,"h":250},"native":{"ver":"1.2","request":{"assets":[{"required":1,"id":1,"title":{"len":800}},{"required":1,"id":3,"img":{"type":3,"w":300,"h":250}},{"required":1,"id":5,"data":{"type":1}}],"context":1,"plcmttype":1,"ver":"1.2"}}}]}', + }); + }); + + it('multiple sizes', function () { + let bid = utils.deepClone(defaultBid); + bid.mediaTypes = { + banner: { + sizes: [[300, 600], [300, 250]], + } + }; + + let [request] = spec.buildRequests([bid]); + let data = JSON.parse(request.data); + assert.deepEqual(data.imp[0].banner, {w: 300, h: 600, format: [{w: 300, h: 250}]}); + }); + }); + + describe('interpretResponse() banner', function () { + it('not successfull response', function () { + assert.deepEqual(spec.interpretResponse(), []); + }); + + it('successfull response', function () { + let bid = { + price: 3, + w: 100, + id: '65599d0a-42d2-446a-9d39-6086c1433ffe', + burl: `${baseUrl}/burl/\${AUCTION_PRICE}`, + cid: '2_ssl', + h: 100, + cat: ['IAB1-1'], + dealid: '3901521', + crid: '2_ssl', + impid: '2b3c9d103723a7', + adid: '2_ssl', + adm: `` + }; + + let response = { + body: { + seatbid: [{ + bid: [bid] + }], + cur: 'USD', + id: '620190c2-7eef-42fa-91e2-f5c7fbc2bdd3' + } + }; + + let bids = spec.interpretResponse(response); + assert.deepEqual(bids, ([{ + ad: bid.adm, + cpm: bid.price, + dealId: bid.dealid, + creativeId: bid.adid, + currency: response.body.cur, + height: bid.h, + netRevenue: true, + burl: bid.burl, + mediaType: BANNER, + requestId: bid.impid, + ttl: 300, + width: bid.w, + }])); + }); + }); + + describe('interpretResponse() native as object', function () { + it('successfull response', function () { + let titleText = 'Colorado Drivers With No DUI\'s Getting A Pay Day on Friday'; + let imgData = { + url: `${baseUrl}/image`, + w: 1200, + h: 627 + }; + let nativeLink = `${baseUrl}/click/`; + let nativeTracker = `${baseUrl}/imp-image`; + let sponsoredByValue = 'Comparisons.org'; + let bodyValue = 'Drivers With No Tickets In 3 Years Should Do This On June'; + let bid = { + price: 3, + id: '65599d0a-42d2-446a-9d39-6086c1433ffe', + burl: `${baseUrl}/burl/\${AUCTION_PRICE}`, + cid: '2_ssl', + cat: ['IAB1-1'], + crid: '2_ssl', + impid: '2b3c9d103723a7', + adid: '2_ssl', + ext: { + advertiser_name: 'MediaForce', + native: { + link: {url: nativeLink}, + assets: [{ + id: 1, + title: {text: titleText}, + required: 1 + }, { + id: 3, + img: imgData + }, { + id: 5, + data: {value: sponsoredByValue} + }, { + id: 4, + data: {value: bodyValue} + }], + imptrackers: [nativeTracker], + ver: '1' + }, + language: 'en', + agency_name: 'MediaForce DSP' + } + }; + + let response = { + body: { + seatbid: [{ + bid: [bid] + }], + cur: 'USD', + id: '620190c2-7eef-42fa-91e2-f5c7fbc2bdd3' + } + }; + + let bids = spec.interpretResponse(response); + assert.deepEqual(bids, ([{ + native: { + clickUrl: nativeLink, + clickTrackers: [], + impressionTrackers: [nativeTracker], + javascriptTrackers: [], + title: titleText, + image: { + url: imgData.url, + width: imgData.w, + height: imgData.h + }, + sponsoredBy: sponsoredByValue, + body: bodyValue + }, + cpm: bid.price, + creativeId: bid.adid, + currency: response.body.cur, + netRevenue: true, + burl: bid.burl, + mediaType: NATIVE, + requestId: bid.impid, + ttl: 300, + }])); + }); + }); + + describe('interpretResponse() native as string', function () { + it('successfull response', function () { + let titleText = 'Colorado Drivers With No DUI\'s Getting A Pay Day on Friday'; + let imgData = { + url: `${baseUrl}/image`, + w: 1200, + h: 627 + }; + let nativeLink = `${baseUrl}/click/`; + let nativeTracker = `${baseUrl}/imp-image`; + let sponsoredByValue = 'Comparisons.org'; + let bodyValue = 'Drivers With No Tickets In 3 Years Should Do This On June'; + let adm = JSON.stringify({ + native: { + link: {url: nativeLink}, + assets: [{ + id: 1, + title: {text: titleText}, + required: 1 + }, { + id: 3, + img: imgData + }, { + id: 5, + data: {value: sponsoredByValue} + }, { + id: 4, + data: {value: bodyValue} + }], + imptrackers: [nativeTracker], + ver: '1' + } + }); + let bid = { + price: 3, + id: '65599d0a-42d2-446a-9d39-6086c1433ffe', + burl: `${baseUrl}/burl/\${AUCTION_PRICE}`, + cid: '2_ssl', + cat: ['IAB1-1'], + crid: '2_ssl', + impid: '2b3c9d103723a7', + adid: '2_ssl', + adm: adm + }; + + let response = { + body: { + seatbid: [{ + bid: [bid] + }], + cur: 'USD', + id: '620190c2-7eef-42fa-91e2-f5c7fbc2bdd3' + } + }; + + let bids = spec.interpretResponse(response); + assert.deepEqual(bids, ([{ + native: { + clickUrl: nativeLink, + clickTrackers: [], + impressionTrackers: [nativeTracker], + javascriptTrackers: [], + title: titleText, + image: { + url: imgData.url, + width: imgData.w, + height: imgData.h + }, + sponsoredBy: sponsoredByValue, + body: bodyValue + }, + cpm: bid.price, + creativeId: bid.adid, + currency: response.body.cur, + netRevenue: true, + burl: bid.burl, + mediaType: NATIVE, + requestId: bid.impid, + ttl: 300, + }])); + }); + }); + + describe('onBidWon()', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('should expand price macros in burl', function () { + let burl = 'burl&s=${AUCTION_PRICE}'; + let bid = { + bidder: 'mediaforce', + width: 300, + height: 250, + adId: '330a22bdea4cac', + mediaType: 'banner', + cpm: 0.28, + ad: '...', + requestId: '418b37f85e772c', + adUnitCode: 'div-gpt-ad-1460505748561-0', + size: '350x250', + burl: burl, + adserverTargeting: { + hb_bidder: 'mediaforce', + hb_adid: '330a22bdea4cac', + hb_pb: '0.20', + hb_size: '350x250' + } + } + spec.onBidWon(bid); + assert.equal(bid.burl, 'burl&s=0.20'); + }); + }); +}); diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..97b45cef00c --- /dev/null +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -0,0 +1,232 @@ +import { expect } from 'chai'; +import medianetAnalytics from 'modules/medianetAnalyticsAdapter.js'; +import * as utils from 'src/utils.js'; +import CONSTANTS from 'src/constants.json'; +import events from 'src/events.js'; + +const { + EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, NO_BID, BID_TIMEOUT, AUCTION_END, SET_TARGETING, BID_WON } +} = CONSTANTS; + +const MOCK = { + AUCTION_INIT: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739}, + AUCTION_INIT_WITH_FLOOR: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'bidderRequests': [{'bids': [{ 'floorData': {'enforcements': {'enforceJS': true}} }]}]}, + BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, + MULTI_FORMAT_BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'video': {'playerSize': [640, 480], 'context': 'instream'}, 'native': {'image': {'required': true, 'sizes': [150, 50]}, 'title': {'required': true, 'len': 80}}, 'ext': ['asdads']}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, + BID_RESPONSE: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, + AUCTION_END: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'auctionEnd': 1584563605739}, + SET_TARGETING: {'div-gpt-ad-1460505748561-0': {'prebid_test': '1', 'hb_format': 'banner', 'hb_source': 'client', 'hb_size': '300x250', 'hb_pb': '2.00', 'hb_adid': '3e6e4bce5c8fb3', 'hb_bidder': 'medianet', 'hb_format_medianet': 'banner', 'hb_source_medianet': 'client', 'hb_size_medianet': '300x250', 'hb_pb_medianet': '2.00', 'hb_adid_medianet': '3e6e4bce5c8fb3', 'hb_bidder_medianet': 'medianet'}}, + BID_WON: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, + NO_BID: {'bidder': 'medianet', 'params': {'cid': 'test123', 'crid': '451466393', 'site': {}}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', 'sizes': [[300, 250], [300, 600]], 'bidId': '28248b0e6aece2', 'bidderRequestId': '13fccf3809fe43', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}, + BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}] +} + +function performAuctionWithFloorConfig() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT_WITH_FLOOR); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON); +} + +function performStandardAuctionWithWinner() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON); +} + +function performMultiFormatAuctionWithNoBid() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.MULTI_FORMAT_BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + +function performStandardAuctionWithNoBid() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(NO_BID, MOCK.NO_BID); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + +function performStandardAuctionWithTimeout() { + 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); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + +function getQueryData(url) { + const queryArgs = url.split('?')[1].split('&'); + return queryArgs.reduce((data, arg) => { + const [key, val] = arg.split('='); + if (data[key] !== undefined) { + if (!Array.isArray(data[key])) { + data[key] = [data[key]]; + } + data[key].push(val); + } else { + data[key] = val; + } + return data; + }, {}); +} + +describe('Media.net Analytics Adapter', function() { + let sandbox; + let CUSTOMER_ID = 'test123'; + let VALID_CONFIGURATION = { + options: { + cid: CUSTOMER_ID + } + } + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('Configuration', function() { + it('should log error if publisher id is not passed', function() { + sandbox.stub(utils, 'logError'); + + medianetAnalytics.enableAnalytics(); + expect( + utils.logError.calledWith( + 'Media.net Analytics adapter: cid is required.' + ) + ).to.be.true; + }); + + it('should not log error if valid config is passed', function() { + sandbox.stub(utils, 'logError'); + + medianetAnalytics.enableAnalytics(VALID_CONFIGURATION); + expect(utils.logError.called).to.equal(false); + medianetAnalytics.disableAnalytics(); + }); + }); + + describe('Events', function() { + beforeEach(function () { + medianetAnalytics.enableAnalytics({ + options: { + cid: 'test123' + } + }); + }); + afterEach(function () { + medianetAnalytics.disableAnalytics(); + }); + + it('should not log if only Auction Init', function() { + medianetAnalytics.clearlogsQueue(); + medianetAnalytics.track({ AUCTION_INIT }) + expect(medianetAnalytics.getlogsQueue().length).to.equal(0); + }); + + it('should have all applicable sizes in request', function() { + medianetAnalytics.clearlogsQueue(); + performMultiFormatAuctionWithNoBid(); + const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; + medianetAnalytics.clearlogsQueue(); + + expect(noBidLog.szs).to.equal(encodeURIComponent('300x250|1x1|640x480')); + expect(noBidLog.vplcmtt).to.equal('instream'); + expect(noBidLog.sz2).to.equal(encodeURIComponent('300x250')); + }); + + it('should have winner log in standard auction', function() { + medianetAnalytics.clearlogsQueue(); + performStandardAuctionWithWinner(); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); + medianetAnalytics.clearlogsQueue(); + + expect(winnerLog.length).to.equal(1); + }); + + it('should have correct values in winner log', function() { + medianetAnalytics.clearlogsQueue(); + performStandardAuctionWithWinner(); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); + medianetAnalytics.clearlogsQueue(); + + expect(winnerLog[0]).to.include({ + winner: '1', + pvnm: 'medianet', + curr: 'USD', + src: 'client', + size: '300x250', + mtype: 'banner', + cid: 'test123', + lper: '1', + ogbdp: '1.1495', + flt: '1', + supcrid: 'div-gpt-ad-1460505748561-0', + mpvid: '123', + bidflr: '1.1' + }); + }); + + it('should have correct bid floor data in winner log', function() { + medianetAnalytics.clearlogsQueue(); + performAuctionWithFloorConfig(); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); + medianetAnalytics.clearlogsQueue(); + + expect(winnerLog[0]).to.include({ + winner: '1', + curr: 'USD', + ogbdp: '1.1495', + bidflr: '1.1', + flrrule: 'banner', + flrdata: encodeURIComponent('ln=||skp=||enfj=true||enfd=||sr=||fs=') + }); + }); + + it('should have no bid status', function() { + medianetAnalytics.clearlogsQueue(); + performStandardAuctionWithNoBid(); + let noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); + noBidLog = noBidLog[0]; + + medianetAnalytics.clearlogsQueue(); + expect(noBidLog.pvnm).to.have.ordered.members(['-2', 'medianet']); + expect(noBidLog.iwb).to.have.ordered.members(['0', '0']); + expect(noBidLog.status).to.have.ordered.members(['1', '2']); + expect(noBidLog.src).to.have.ordered.members(['client', 'client']); + expect(noBidLog.curr).to.have.ordered.members(['', '']); + expect(noBidLog.mtype).to.have.ordered.members(['', '']); + expect(noBidLog.ogbdp).to.have.ordered.members(['', '']); + expect(noBidLog.mpvid).to.have.ordered.members(['', '']); + expect(noBidLog.crid).to.have.ordered.members(['', '451466393']); + }); + + it('should have timeout status', function() { + medianetAnalytics.clearlogsQueue(); + performStandardAuctionWithTimeout(); + let timeoutLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); + timeoutLog = timeoutLog[0]; + + medianetAnalytics.clearlogsQueue(); + expect(timeoutLog.pvnm).to.have.ordered.members(['-2', 'medianet']); + expect(timeoutLog.iwb).to.have.ordered.members(['0', '0']); + expect(timeoutLog.status).to.have.ordered.members(['1', '3']); + expect(timeoutLog.src).to.have.ordered.members(['client', 'client']); + expect(timeoutLog.curr).to.have.ordered.members(['', '']); + expect(timeoutLog.mtype).to.have.ordered.members(['', '']); + expect(timeoutLog.ogbdp).to.have.ordered.members(['', '']); + expect(timeoutLog.mpvid).to.have.ordered.members(['', '']); + expect(timeoutLog.crid).to.have.ordered.members(['', '451466393']); + }); + }); +}); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index afe91ce3166..649929056fa 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; -import {spec} from 'modules/medianetBidAdapter'; -import { config } from 'src/config'; +import {spec} from 'modules/medianetBidAdapter.js'; +import { config } from 'src/config.js'; let VALID_BID_REQUEST = [{ 'bidder': 'medianet', @@ -37,6 +37,11 @@ let VALID_BID_REQUEST = [{ }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'sizes': [[300, 251]], 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', @@ -81,6 +86,62 @@ let VALID_BID_REQUEST = [{ }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }], + VALID_BID_REQUEST_WITH_USERID = [{ + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + userId: { + britepoolid: '82efd5e1-816b-4f87-97f8-044f407e2911' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }, { + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'sizes': [[300, 251]], 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', @@ -263,6 +324,7 @@ let VALID_BID_REQUEST = [{ 'prebid_version': $$PREBID_GLOBAL$$.version, 'gdpr_applies': false, 'usp_applies': false, + 'coppa_applies': false, 'screen': { 'w': 1000, 'h': 1000 @@ -347,6 +409,7 @@ let VALID_BID_REQUEST = [{ 'prebid_version': $$PREBID_GLOBAL$$.version, 'gdpr_applies': false, 'usp_applies': false, + 'coppa_applies': false, 'screen': { 'w': 1000, 'h': 1000 @@ -432,6 +495,94 @@ let VALID_BID_REQUEST = [{ 'prebid_version': $$PREBID_GLOBAL$$.version, 'gdpr_applies': false, 'usp_applies': false, + 'coppa_applies': false, + 'screen': { + 'w': 1000, + 'h': 1000 + } + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0', + 'visibility': 1, + 'viewability': 1, + 'coordinates': { + 'top_left': { + x: 50, + y: 50 + }, + 'bottom_right': { + x: 100, + y: 100 + } + }, + 'display_count': 1 + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }], + 'all': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + } + }, { + 'id': '3f97ca71b1e5c2', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-123', + 'visibility': 1, + 'viewability': 1, + 'coordinates': { + 'top_left': { + x: 50, + y: 50 + }, + 'bottom_right': { + x: 100, + y: 100 + } + }, + 'display_count': 1 + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }], + 'all': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + } + }], + 'tmax': config.getConfig('bidderTimeout') + }, + VALID_PAYLOAD_WITH_USERID = { + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': $$PREBID_GLOBAL$$.version, + 'gdpr_applies': false, + 'user_id': { + britepoolid: '82efd5e1-816b-4f87-97f8-044f407e2911' + }, + 'usp_applies': false, + 'coppa_applies': false, 'screen': { 'w': 1000, 'h': 1000 @@ -440,6 +591,7 @@ let VALID_BID_REQUEST = [{ 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'imp': [{ 'id': '28f8f8130a583e', + 'tagid': 'crid', 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-0', 'visibility': 1, @@ -462,6 +614,7 @@ let VALID_BID_REQUEST = [{ }], 'all': { 'cid': 'customer_id', + 'crid': 'crid', 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', @@ -471,6 +624,7 @@ let VALID_BID_REQUEST = [{ } }, { 'id': '3f97ca71b1e5c2', + 'tagid': 'crid', 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-123', 'visibility': 1, @@ -493,6 +647,7 @@ let VALID_BID_REQUEST = [{ }], 'all': { 'cid': 'customer_id', + 'crid': 'crid', 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', @@ -515,6 +670,7 @@ let VALID_BID_REQUEST = [{ 'prebid_version': $$PREBID_GLOBAL$$.version, 'gdpr_applies': false, 'usp_applies': false, + 'coppa_applies': true, 'screen': { 'w': 1000, 'h': 1000 @@ -590,6 +746,28 @@ let VALID_BID_REQUEST = [{ }], 'tmax': config.getConfig('bidderTimeout') }, + + VALID_VIDEO_BID_REQUEST = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'video': { + 'skipppable': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'mediaTypes': { + 'video': { + 'context': 'instream', + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }], + VALID_PAYLOAD_PAGE_META = (() => { let PAGE_META; try { @@ -856,6 +1034,7 @@ let VALID_BID_REQUEST = [{ 'gdpr_consent_string': 'consentString', 'gdpr_applies': true, 'usp_applies': true, + 'coppa_applies': false, 'usp_consent_string': '1NYN', 'screen': { 'w': 1000, @@ -967,6 +1146,8 @@ describe('Media.net bid adapter', function () { describe('buildRequests', function () { beforeEach(function () { + $$PREBID_GLOBAL$$.medianetGlobals = {}; + let documentStub = sandbox.stub(document, 'getElementById'); let boundingRect = { top: 50, @@ -1012,11 +1193,27 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_NATIVE); }); + it('should parse params for video request', function () { + let bidReq = spec.buildRequests(VALID_VIDEO_BID_REQUEST, VALID_AUCTIONDATA); + expect(JSON.stringify(bidReq.data)).to.include('instream'); + }); + it('should have valid crid present in bid request', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + const config = { + 'coppa': true + }; + return config[key]; + }); let bidreq = spec.buildRequests(VALID_BID_REQUEST_WITH_CRID, VALID_AUCTIONDATA); expect(JSON.parse(bidreq.data)).to.deep.equal(VALID_PAYLOAD_WITH_CRID); }); + it('should have userid in bid request', function () { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_USERID, VALID_AUCTIONDATA); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_WITH_USERID); + }); + describe('build requests: when page meta-data is available', () => { beforeEach(() => { spec.clearMnData(); diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js new file mode 100644 index 00000000000..351fbb40228 --- /dev/null +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -0,0 +1,131 @@ +import {expect} from 'chai'; +import {spec} from 'modules/mediasquareBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { requestBidsHook } from 'modules/consentManagement.js'; + +describe('MediaSquare bid adapter tests', function () { + var DEFAULT_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bidder: 'mediasquare', + params: { + owner: 'test', + code: 'publishername_atf_desktop_rg_pave' + }, + }]; + + var BID_RESPONSE = {'body': { + 'responses': [{ + 'transaction_id': 'cccc1234', + 'cpm': 22.256608, + 'width': 300, + 'height': 250, + 'creative_id': '158534630', + 'currency': 'USD', + 'net_revenue': true, + 'ttl': 300, + 'ad': '< --- creative code --- >', + 'bidder': 'msqClassic', + 'code': 'test/publishername_atf_desktop_rg_pave', + 'bid_id': 'aaaa1234', + }], + }}; + + const DEFAULT_OPTIONS = { + gdprConsent: { + gdprApplies: true, + consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', + vendorData: {} + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + }, + uspConsent: '111222333', + userId: {'id5id': '1111'}, + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + }] + }, + }; + it('Verify build request', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + expect(request).to.have.property('url').and.to.equal('https://pbs-front.mediasquare.fr/msq_prebid'); + expect(request).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request.data); + expect(requestContent.codes[0]).to.have.property('owner').and.to.equal('test'); + expect(requestContent.codes[0]).to.have.property('code').and.to.equal('publishername_atf_desktop_rg_pave'); + expect(requestContent.codes[0]).to.have.property('adunit').and.to.equal('banner-div'); + expect(requestContent.codes[0]).to.have.property('bidId').and.to.equal('aaaa1234'); + expect(requestContent.codes[0]).to.have.property('auctionId').and.to.equal('bbbb1234'); + expect(requestContent.codes[0]).to.have.property('transactionId').and.to.equal('cccc1234'); + expect(requestContent.codes[0]).to.have.property('mediatypes').exist; + }); + + it('Verify parse response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + const response = spec.interpretResponse(BID_RESPONSE, request); + expect(response).to.have.lengthOf(1); + const bid = response[0]; + expect(bid.cpm).to.equal(22.256608); + expect(bid.ad).to.equal('< --- creative code --- >'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('158534630'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal('aaaa1234'); + expect(bid.mediasquare).to.exist; + expect(bid.mediasquare.bidder).to.equal('msqClassic'); + expect(bid.mediasquare.code).to.equal([DEFAULT_PARAMS[0].params.owner, DEFAULT_PARAMS[0].params.code].join('/')); + }); + + it('Verifies bidder code', function () { + expect(spec.code).to.equal('mediasquare'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('msq'); + }); + it('Verifies if bid request valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + }); + it('Verifies bid won', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + const response = spec.interpretResponse(BID_RESPONSE, request); + const won = spec.onBidWon(response[0]); + expect(won).to.equal(true); + }); + it('Verifies user sync without cookie in bid response', function () { + var syncs = spec.getUserSyncs({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.property('type').and.to.equal('iframe'); + }); + it('Verifies user sync with cookies in bid response', function () { + BID_RESPONSE.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; + var syncs = spec.getUserSyncs({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0]).to.have.property('type').and.to.equal('image'); + expect(syncs[0]).to.have.property('url').and.to.equal('http://www.cookie.sync.org/'); + }); +}); diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index 07db18650c2..16f4f0b4607 100644 --- a/test/spec/modules/mgidBidAdapter_spec.js +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -1,7 +1,6 @@ import {assert, expect} from 'chai'; -import {spec} from 'modules/mgidBidAdapter'; -import * as utils from '../../../src/utils'; -import * as urlUtils from '../../../src/url'; +import {spec} from 'modules/mgidBidAdapter.js'; +import * as utils from '../../../src/utils.js'; describe('Mgid bid adapter', function () { let sandbox; @@ -361,7 +360,7 @@ describe('Mgid bid adapter', function () { }; let bidRequests = [bid]; const page = top.location.href; - const domain = urlUtils.parse(page).hostname; + const domain = utils.parseUrl(page).hostname; const request = spec.buildRequests(bidRequests); expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); expect(request.method).deep.equal('POST'); @@ -409,7 +408,7 @@ describe('Mgid bid adapter', function () { let bidRequests = [bid]; const page = top.location.href; - const domain = urlUtils.parse(page).hostname; + const domain = utils.parseUrl(page).hostname; const request = spec.buildRequests(bidRequests); expect(request).to.be.a('object'); expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); @@ -446,7 +445,7 @@ describe('Mgid bid adapter', function () { let bidRequests = [bid]; const page = top.location.href; - const domain = urlUtils.parse(page).hostname; + const domain = utils.parseUrl(page).hostname; const request = spec.buildRequests(bidRequests); expect(request).to.be.a('object'); expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); @@ -482,7 +481,7 @@ describe('Mgid bid adapter', function () { let bidRequests = [bid]; const page = top.location.href; - const domain = urlUtils.parse(page).hostname; + const domain = utils.parseUrl(page).hostname; const request = spec.buildRequests(bidRequests); expect(request).to.be.a('object'); expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); @@ -516,7 +515,7 @@ describe('Mgid bid adapter', function () { const request = spec.buildRequests(bidRequests); const page = top.location.href; - const domain = urlUtils.parse(page).hostname; + const domain = utils.parseUrl(page).hostname; expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); expect(request.method).deep.equal('POST'); const data = JSON.parse(request.data); @@ -676,6 +675,12 @@ describe('Mgid bid adapter', function () { }); }); describe('on bidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); it('should replace nurl and burl for native', function () { const burl = 'burl&s=${' + 'AUCTION_PRICE}'; const nurl = 'nurl&s=${' + 'AUCTION_PRICE}'; diff --git a/test/spec/modules/microadBidAdapter_spec.js b/test/spec/modules/microadBidAdapter_spec.js index b80a6938425..8298e2bd559 100644 --- a/test/spec/modules/microadBidAdapter_spec.js +++ b/test/spec/modules/microadBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/microadBidAdapter'; -import * as utils from 'src/utils'; +import { spec } from 'modules/microadBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('microadBidAdapter', () => { const bidRequestTemplate = { diff --git a/test/spec/modules/mobsmartBidAdapter_spec.js b/test/spec/modules/mobsmartBidAdapter_spec.js index 7c1128276ab..b48878adff6 100644 --- a/test/spec/modules/mobsmartBidAdapter_spec.js +++ b/test/spec/modules/mobsmartBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/mobsmartBidAdapter'; +import { spec } from 'modules/mobsmartBidAdapter.js'; describe('mobsmartBidAdapter', function () { describe('isBidRequestValid', function () { diff --git a/test/spec/modules/my6senseBidAdapter_spec.js b/test/spec/modules/my6senseBidAdapter_spec.js index d2c875dad07..5e51280d70b 100644 --- a/test/spec/modules/my6senseBidAdapter_spec.js +++ b/test/spec/modules/my6senseBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/my6senseBidAdapter'; +import { spec } from 'modules/my6senseBidAdapter.js'; describe('My6sense Bid adapter test', function () { let bidRequests, serverResponses; diff --git a/test/spec/modules/mytargetBidAdapter_spec.js b/test/spec/modules/mytargetBidAdapter_spec.js index 01a94e76d60..ea998303fe3 100644 --- a/test/spec/modules/mytargetBidAdapter_spec.js +++ b/test/spec/modules/mytargetBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { config } from 'src/config'; -import { spec } from 'modules/mytargetBidAdapter'; +import { config } from 'src/config.js'; +import { spec } from 'modules/mytargetBidAdapter.js'; describe('MyTarget Adapter', function() { describe('isBidRequestValid', function () { diff --git a/test/spec/modules/nafdigitalBidAdapter_spec.js b/test/spec/modules/nafdigitalBidAdapter_spec.js index ca486c632c7..c8ffb9fbbaf 100644 --- a/test/spec/modules/nafdigitalBidAdapter_spec.js +++ b/test/spec/modules/nafdigitalBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import * as utils from 'src/utils'; -import { spec } from 'modules/nafdigitalBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/nafdigitalBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const URL = 'https://nafdigitalbidder.com/hb'; diff --git a/test/spec/modules/nanointeractiveBidAdapter_spec.js b/test/spec/modules/nanointeractiveBidAdapter_spec.js index f999c2a4656..715a26a4597 100644 --- a/test/spec/modules/nanointeractiveBidAdapter_spec.js +++ b/test/spec/modules/nanointeractiveBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import * as utils from 'src/utils'; +import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import { @@ -13,7 +13,7 @@ import { REF, spec, SUB_ID -} from '../../../modules/nanointeractiveBidAdapter'; +} from '../../../modules/nanointeractiveBidAdapter.js'; describe('nanointeractive adapter tests', function () { const SIZES_PARAM = 'sizes'; diff --git a/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js b/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js new file mode 100644 index 00000000000..4731b1a77d3 --- /dev/null +++ b/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js @@ -0,0 +1,143 @@ +import {expect} from 'chai'; +import {spec} from 'modules/nasmediaAdmixerBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('nasmediaAdmixerBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'media_key': 'media_key', + 'adunit_id': 'adunit_id', + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '3361d01e67dbd6', + 'bidderRequestId': '2b60dcd392628a', + 'auctionId': '124cb070528662', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + const bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'media_key': '', + 'adunit_id': '', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'media_key': '19038695', + 'adunit_id': '24190632', + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '3361d01e67dbd6', + 'bidderRequestId': '2b60dcd392628a', + 'auctionId': '124cb070528662', + } + ]; + const bidderRequest = {refererInfo: {referer: 'https://example.com'}}; + + it('sends bid request to url via GET', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.match(new RegExp(`https://adn.admixer.co.kr`)); + }); + }); + + describe('interpretResponse', function () { + const response = { + 'body': { + 'bidder': 'nasmediaAdmixer', + 'req_id': '861a8e7952c82c', + 'error_code': 0, + 'error_msg': 'OK', + 'body': [{ + 'ad_id': '20049', + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'cpm': 1.769221, + 'ad': '' + }] + }, + 'headers': { + 'get': function () { + } + } + }; + + const bidRequest = { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'media_key': '19038695', + 'adunit_id': '24190632', + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [320, 480]], + 'bidId': '31300c8b9697cd', + 'bidderRequestId': '2bf570adcf83fa', + 'auctionId': '169827a33f03cc', + }; + + it('should get correct bid response', function () { + const expectedResponse = [ + { + 'requestId': '861a8e7952c82c', + 'cpm': 1.769221, + 'currency': 'USD', + 'width': 300, + 'height': 250, + 'ad': '', + '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(/$/); + } else { + expect(result[0][k]).to.equal(expectedResponse[0][k]); + } + }); + }); + + it('handles nobid responses', function () { + response.body = { + 'bidder': 'nasmediaAdmixer', + '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/newborntownWebBidAdapter_spec.js b/test/spec/modules/newborntownWebBidAdapter_spec.js index d7cf4a7dd95..3d3285328fe 100644 --- a/test/spec/modules/newborntownWebBidAdapter_spec.js +++ b/test/spec/modules/newborntownWebBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import {spec} from 'modules/newborntownWebBidAdapter'; +import {spec} from 'modules/newborntownWebBidAdapter.js'; describe('NewborntownWebAdapter', function() { describe('isBidRequestValid', function () { let bid = { @@ -54,8 +54,8 @@ describe('NewborntownWebAdapter', function() { 'timeout': 9000, 'start': 1573123289383 } - const request = spec.buildRequests(bidderRequest['bids'], bidderRequest); it('Returns POST method', function () { + const request = spec.buildRequests(bidderRequest['bids'], bidderRequest); expect(request[0].method).to.equal('POST'); expect(request[0].url.indexOf('//us-west.solortb.com/adx/api/rtb?from=4') !== -1).to.equal(true); expect(request[0].data).to.exist; diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 087f06d7e8e..f4d929b439c 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/nextMillenniumBidAdapter'; +import { spec } from 'modules/nextMillenniumBidAdapter.js'; describe('nextMillenniumBidAdapterTests', function() { let bidRequestData = { diff --git a/test/spec/modules/nextrollBidAdapter_spec.js b/test/spec/modules/nextrollBidAdapter_spec.js new file mode 100644 index 00000000000..7722443e584 --- /dev/null +++ b/test/spec/modules/nextrollBidAdapter_spec.js @@ -0,0 +1,268 @@ +import { expect } from 'chai'; +import { spec, tryGetPubtag, hasCCPAConsent } from 'modules/nextrollBidAdapter.js'; +import * as utils from 'src/utils.js'; + +describe('nextrollBidAdapter', function() { + let utilsMock; + beforeEach(function () { + utilsMock = sinon.mock(utils); + }); + + afterEach(function() { + global.NextRoll = undefined; + utilsMock.restore(); + }); + + let validBid = { + bidder: 'nextroll', + adUnitCode: 'adunit-code', + bidId: 'bid_id', + sizes: [[300, 200]], + params: { + bidfloor: 1, + zoneId: 'zone1', + publisherId: 'publisher_id' + } + }; + let bidWithoutValidId = { id: '' }; + let bidWithoutId = { params: { zoneId: 'zone1' } }; + + describe('nativeBidRequest', () => { + it('validates native spec', () => { + let nativeAdUnit = [{ + bidder: 'nextroll', + adUnitCode: 'adunit-code', + bidId: 'bid_id', + mediaTypes: { + native: { + title: {required: true, len: 80}, + image: {required: true, sizes: [728, 90]}, + sponsoredBy: {required: false, len: 20}, + clickUrl: {required: true}, + body: {required: true, len: 25}, + icon: {required: true, sizes: [50, 50], aspect_ratios: [{ratio_height: 3, ratio_width: 4}]}, + someRandomAsset: {required: false, len: 100} // This should be ignored + } + }, + params: { + bidfloor: 1, + zoneId: 'zone1', + publisherId: 'publisher_id' + } + }]; + + let request = spec.buildRequests(nativeAdUnit) + let assets = request[0].data.imp.native.request.native.assets + + let excptedAssets = [ + {id: 1, required: 1, title: {len: 80}}, + {id: 2, required: 1, img: {w: 728, h: 90, wmin: 1, hmin: 1, type: 3}}, + {id: 3, required: 1, img: {w: 50, h: 50, wmin: 4, hmin: 3, type: 1}}, + {id: 5, required: 0, data: {len: 20, type: 1}}, + {id: 6, required: 1, data: {len: 25, type: 2}} + ] + expect(assets).to.be.deep.equal(excptedAssets) + }) + }) + + describe('isBidRequestValid', function() { + it('validates the bids correctly when the bid has an id', function() { + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('validates the bids correcly when the bid does not have an id', function() { + expect(spec.isBidRequestValid(bidWithoutValidId)).to.be.false; + expect(spec.isBidRequestValid(bidWithoutId)).to.be.false; + }); + }); + + describe('buildRequests', function() { + it('builds the same amount of requests as valid requests it takes', function() { + expect(spec.buildRequests([validBid, validBid], {})).to.be.lengthOf(2); + }); + + it('doest not build a request when there is no valid requests', function () { + expect(spec.buildRequests([], {})).to.be.lengthOf(0); + }); + + it('builds a request with POST method', function () { + expect(spec.buildRequests([validBid], {})[0].method).to.equal('POST'); + }); + + it('builds a request with cookies method', function () { + expect(spec.buildRequests([validBid], {})[0].options.withCredentials).to.be.true; + }); + + it('builds a request with id, url and imp object', function () { + const request = spec.buildRequests([validBid], {})[0]; + expect(request.data.id).to.be.an('string').that.is.not.empty; + expect(request.url).to.equal('https://d.adroll.com/bid/prebid/'); + expect(request.data.imp).to.exist.and.to.be.a('object'); + }); + + it('builds a request with site and device information', function () { + const request = spec.buildRequests([validBid], {})[0]; + + expect(request.data.site).to.exist.and.to.be.a('object'); + expect(request.data.device).to.exist.and.to.be.a('object'); + }); + + it('builds a request with a complete imp object', function () { + const request = spec.buildRequests([validBid], {})[0]; + + expect(request.data.imp.id).to.equal('bid_id'); + expect(request.data.imp.bidfloor).to.be.equal(1); + expect(request.data.imp.banner).to.exist.and.to.be.a('object'); + expect(request.data.imp.ext.zone.id).to.be.equal('zone1'); + }); + + it('includes the sizes into the request correctly', function () { + const bannerObject = spec.buildRequests([validBid], {})[0].data.imp.banner; + + expect(bannerObject.format).to.exist; + expect(bannerObject.format).to.be.lengthOf(1); + expect(bannerObject.format[0].w).to.be.equal(300); + expect(bannerObject.format[0].h).to.be.equal(200); + }); + + it('sets the CCPA consent string', function () { + const us_privacy = '1YYY'; + const request = spec.buildRequests([validBid], {'uspConsent': us_privacy})[0]; + + expect(request.data.regs.ext.us_privacy).to.be.equal(us_privacy); + }); + }); + + describe('interpretResponse', function () { + let responseBody = { + id: 'bidresponse_id', + dealId: 'deal_id', + seatbid: [ + { + bid: [ + { + price: 1.2, + w: 300, + h: 200, + crid: 'crid1', + adm: 'adm1' + } + ] + }, + { + bid: [ + { + price: 2.1, + w: 250, + h: 300, + crid: 'crid2', + adm: 'adm2' + } + ] + } + ] + }; + + it('returns an empty list when there is no response body', function () { + expect(spec.interpretResponse({}, {})).to.be.eql([]); + }); + + it('builds the same amount of responses as server responses it receives', function () { + expect(spec.interpretResponse({body: responseBody}, {})).to.be.lengthOf(2); + }); + + it('builds a response with the expected fields', function () { + const response = spec.interpretResponse({body: responseBody}, {})[0]; + + expect(response.requestId).to.be.equal('bidresponse_id'); + expect(response.cpm).to.be.equal(1.2); + expect(response.width).to.be.equal(300); + expect(response.height).to.be.equal(200); + expect(response.creativeId).to.be.equal('crid1'); + expect(response.dealId).to.be.equal('deal_id'); + expect(response.currency).to.be.equal('USD'); + expect(response.netRevenue).to.be.equal(true); + expect(response.ttl).to.be.equal(300); + expect(response.ad).to.be.equal('adm1'); + }); + }); + + describe('interpret native response', () => { + let clickUrl = 'https://clickurl.com/with/some/path' + let titleText = 'Some title' + let imgW = 300 + let imgH = 250 + let imgUrl = 'https://clickurl.com/img.png' + let brandText = 'Some Brand' + let impUrl = 'https://clickurl.com/imptracker' + + let responseBody = { + body: { + id: 'bidresponse_id', + seatbid: [{ + bid: [{ + price: 1.2, + crid: 'crid1', + adm: { + link: {url: clickUrl}, + assets: [ + {id: 1, title: {text: titleText}}, + {id: 2, img: {w: imgW, h: imgH, url: imgUrl}}, + {id: 5, data: {value: brandText}} + ], + imptrackers: [impUrl] + } + }] + }] + } + }; + + it('Should interpret response', () => { + let response = spec.interpretResponse(utils.deepClone(responseBody)) + let expectedResponse = { + clickUrl: clickUrl, + impressionTrackers: [impUrl], + privacyLink: 'https://info.evidon.com/pub_info/573', + privacyIcon: 'https://c.betrad.com/pub/icon1.png', + title: titleText, + image: {url: imgUrl, width: imgW, height: imgH}, + sponsoredBy: brandText, + clickTrackers: [], + jstracker: [] + } + + expect(response[0].native).to.be.deep.equal(expectedResponse) + }) + + it('Should interpret all assets', () => { + let allAssetsResponse = utils.deepClone(responseBody) + let iconUrl = imgUrl + '?icon=true', iconW = 10, iconH = 15 + let logoUrl = imgUrl + '?logo=true', logoW = 20, logoH = 25 + let bodyText = 'Some body text' + + allAssetsResponse.body.seatbid[0].bid[0].adm.assets.push(...[ + {id: 3, img: {w: iconW, h: iconH, url: iconUrl}}, + {id: 4, img: {w: logoW, h: logoH, url: logoUrl}}, + {id: 6, data: {value: bodyText}} + ]) + + let response = spec.interpretResponse(allAssetsResponse) + let expectedResponse = { + clickUrl: clickUrl, + impressionTrackers: [impUrl], + jstracker: [], + clickTrackers: [], + privacyLink: 'https://info.evidon.com/pub_info/573', + privacyIcon: 'https://c.betrad.com/pub/icon1.png', + title: titleText, + image: {url: imgUrl, width: imgW, height: imgH}, + icon: {url: iconUrl, width: iconW, height: iconH}, + logo: {url: logoUrl, width: logoW, height: logoH}, + body: bodyText, + sponsoredBy: brandText + } + + expect(response[0].native).to.be.deep.equal(expectedResponse) + }) + }) +}); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index 5134958d218..3b8fd32160b 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -1,7 +1,8 @@ import { expect } from 'chai'; -import { spec } from 'modules/nobidBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { deepClone } from 'src/utils'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/nobidBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; describe('Nobid Adapter', function () { const adapter = newBidder(spec); @@ -49,6 +50,184 @@ describe('Nobid Adapter', function () { }); }); + describe('isVideoBidRequestValid', function () { + let bid = { + bidder: 'nobid', + params: { + siteId: 2, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'], + position: 'atf', + mimes: ['video/x-flv', 'video/mp4', 'video/x-ms-wmv', 'application/x-shockwave-flash', 'application/javascript'], + minduration: 1, + maxduration: 30, + frameworks: [1, 2, 3, 4, 5, 6] + } + }, + adUnitCode: 'adunit-code', + sizes: [[640, 480]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + video: { + context: 'instream' + } + } + }; + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + bidder: 'nobid', + params: { + siteId: SITE_ID, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'], + position: 'atf', + mimes: ['video/x-flv', 'video/mp4', 'video/x-ms-wmv', 'application/x-shockwave-flash', 'application/javascript'], + minduration: 1, + maxduration: 30, + frameworks: [1, 2, 3, 4, 5, 6] + } + }, + adUnitCode: 'adunit-code', + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + } + } + ]; + + let bidderRequest = { + refererInfo: {referer: REFERER} + } + + it('should add source and version to the tag', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.sid).to.equal(SITE_ID); + expect(payload.l).to.exist.and.to.equal(encodeURIComponent(REFERER)); + expect(payload.a).to.exist; + expect(payload.t).to.exist; + expect(payload.tz).to.exist; + expect(payload.r).to.exist; + expect(payload.lang).to.exist; + expect(payload.ref).to.exist; + expect(payload.a[0].d).to.exist.and.to.equal('adunit-code'); + expect(payload.a[0].at).to.exist.and.to.equal('video'); + expect(payload.a[0].params.video).to.exist; + expect(payload.a[0].params.video.skippable).to.exist.and.to.equal(true); + expect(payload.a[0].params.video.playback_methods).to.exist.and.to.contain('auto_play_sound_off'); + expect(payload.a[0].params.video.position).to.exist.and.to.equal('atf'); + expect(payload.a[0].params.video.mimes).to.exist.and.to.contain('video/x-flv'); + expect(payload.a[0].params.video.minduration).to.exist.and.to.equal(1); + expect(payload.a[0].params.video.maxduration).to.exist.and.to.equal(30); + expect(payload.a[0].params.video.frameworks[0]).to.exist.and.to.equal(1); + expect(payload.a[0].params.video.frameworks[1]).to.exist.and.to.equal(2); + expect(payload.a[0].params.video.frameworks[2]).to.exist.and.to.equal(3); + expect(payload.a[0].params.video.frameworks[3]).to.exist.and.to.equal(4); + expect(payload.a[0].params.video.frameworks[4]).to.exist.and.to.equal(5); + expect(payload.a[0].params.video.frameworks[5]).to.exist.and.to.equal(6); + }); + }); + + describe('isVideoBidRequestValid', function () { + let bid = { + bidder: 'nobid', + params: { + siteId: 2, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'], + position: 'atf', + mimes: ['video/x-flv', 'video/mp4', 'video/x-ms-wmv', 'application/x-shockwave-flash', 'application/javascript'], + minduration: 1, + maxduration: 30, + frameworks: [1, 2, 3, 4, 5, 6] + } + }, + adUnitCode: 'adunit-code', + sizes: [[640, 480]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + video: { + context: 'outstream' + } + } + }; + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + bidder: 'nobid', + params: { + siteId: SITE_ID, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'], + position: 'atf', + mimes: ['video/x-flv', 'video/mp4', 'video/x-ms-wmv', 'application/x-shockwave-flash', 'application/javascript'], + minduration: 1, + maxduration: 30, + frameworks: [1, 2, 3, 4, 5, 6] + } + }, + adUnitCode: 'adunit-code', + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + } + } + ]; + + let bidderRequest = { + refererInfo: {referer: REFERER} + } + + it('should add source and version to the tag', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.sid).to.equal(SITE_ID); + expect(payload.l).to.exist.and.to.equal(encodeURIComponent(REFERER)); + expect(payload.a).to.exist; + expect(payload.t).to.exist; + expect(payload.tz).to.exist; + expect(payload.r).to.exist; + expect(payload.lang).to.exist; + expect(payload.ref).to.exist; + expect(payload.a[0].d).to.exist.and.to.equal('adunit-code'); + expect(payload.a[0].at).to.exist.and.to.equal('video'); + expect(payload.a[0].params.video).to.exist; + expect(payload.a[0].params.video.skippable).to.exist.and.to.equal(true); + expect(payload.a[0].params.video.playback_methods).to.exist.and.to.contain('auto_play_sound_off'); + expect(payload.a[0].params.video.position).to.exist.and.to.equal('atf'); + expect(payload.a[0].params.video.mimes).to.exist.and.to.contain('video/x-flv'); + expect(payload.a[0].params.video.minduration).to.exist.and.to.equal(1); + expect(payload.a[0].params.video.maxduration).to.exist.and.to.equal(30); + expect(payload.a[0].params.video.frameworks[0]).to.exist.and.to.equal(1); + expect(payload.a[0].params.video.frameworks[1]).to.exist.and.to.equal(2); + expect(payload.a[0].params.video.frameworks[2]).to.exist.and.to.equal(3); + expect(payload.a[0].params.video.frameworks[3]).to.exist.and.to.equal(4); + expect(payload.a[0].params.video.frameworks[4]).to.exist.and.to.equal(5); + expect(payload.a[0].params.video.frameworks[5]).to.exist.and.to.equal(6); + }); + }); + describe('buildRequests', function () { const SITE_ID = 2; const REFERER = 'https://www.examplereferer.com'; @@ -70,7 +249,7 @@ describe('Nobid Adapter', function () { refererInfo: {referer: REFERER} } - it('should add source and verison to the tag', function () { + it('should add source and version to the tag', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); expect(payload.sid).to.equal(SITE_ID); @@ -114,6 +293,39 @@ describe('Nobid Adapter', function () { }); }); + describe('buildRequestsRefreshCount', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': SITE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + let bidderRequest = { + refererInfo: {referer: REFERER} + } + + it('should refreshCount = 4', function () { + nobid.refreshLimit = 2; + nobid.refreshCount = 0; + spec.buildRequests(bidRequests, bidderRequest); + spec.buildRequests(bidRequests, bidderRequest); + spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(nobid.refreshCount).to.equal(3); + expect(typeof request).to.equal('undefined'); + }); + }); + describe('interpretResponse', function () { const CREATIVE_ID_300x250 = 'CREATIVE-100'; const ADUNIT_300x250 = 'ADUNIT-1'; @@ -209,24 +421,166 @@ describe('Nobid Adapter', function () { }); }); + describe('interpretResponseWithRefreshLimit', function () { + const CREATIVE_ID_300x250 = 'CREATIVE-100'; + const ADUNIT_300x250 = 'ADUNIT-1'; + const ADMARKUP_300x250 = 'ADMARKUP-300x250'; + const PRICE_300x250 = 0.51; + const REQUEST_ID = '3db3773286ee59'; + const DEAL_ID = 'deal123'; + const REFRESH_LIMIT = 3; + let response = { + country: 'US', + ip: '68.83.15.75', + device: 'COMPUTER', + site: 2, + rlimit: REFRESH_LIMIT, + bids: [ + {id: 1, + bdrid: 101, + divid: ADUNIT_300x250, + dealid: DEAL_ID, + creativeid: CREATIVE_ID_300x250, + size: {'w': 300, 'h': 250}, + adm: ADMARKUP_300x250, + price: '' + PRICE_300x250 + } + ] + }; + + it('should refreshLimit be respected', function () { + let bidderRequest = { + bids: [{ + bidId: REQUEST_ID, + adUnitCode: ADUNIT_300x250 + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest: bidderRequest}); + expect(nobid.refreshLimit).to.equal(REFRESH_LIMIT); + }); + }); + + describe('buildRequestsWithSupplyChain', function () { + const SITE_ID = 2; + let bidRequests = [ + { + bidder: 'nobid', + params: { + siteId: SITE_ID + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + coppa: true, + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + name: 'name.com', + hp: 1 + } + ] + } + } + } + ]; + + it('schain exist', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.schain).to.exist; + expect(payload.schain.validation).to.exist.and.to.equal('strict'); + expect(payload.schain.config.ver).to.exist.and.to.equal('1.0'); + expect(payload.schain.config.complete).to.exist.and.to.equal(1); + expect(payload.schain.config.nodes[0].asi).to.exist.and.to.equal('indirectseller.com'); + expect(payload.schain.config.nodes[0].sid).to.exist.and.to.equal('00001'); + expect(payload.schain.config.nodes[0].name).to.exist.and.to.equal('name.com'); + expect(payload.schain.config.nodes[0].hp).to.exist.and.to.equal(1); + expect(payload.coppa).to.exist; + expect(payload.coppa).to.exist.and.to.be.true; + expect(payload.a).to.be.lengthOf(1); + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponseWithUserLimit', function () { + const CREATIVE_ID_300x250 = 'CREATIVE-100'; + const ADUNIT_300x250 = 'ADUNIT-1'; + const ADMARKUP_300x250 = 'ADMARKUP-300x250'; + const PRICE_300x250 = 0.51; + const REQUEST_ID = '3db3773286ee59'; + const DEAL_ID = 'deal123'; + const ULIMIT = 1; + let response = { + country: 'US', + ip: '68.83.15.75', + device: 'COMPUTER', + site: 2, + ublock: ULIMIT, + bids: [ + {id: 1, + bdrid: 101, + divid: ADUNIT_300x250, + dealid: DEAL_ID, + creativeid: CREATIVE_ID_300x250, + size: {'w': 300, 'h': 250}, + adm: ADMARKUP_300x250, + price: '' + PRICE_300x250 + } + ] + }; + + it('should ULimit be respected', function () { + const bidderRequest = { + bids: [{ + bidId: REQUEST_ID, + adUnitCode: ADUNIT_300x250 + }] + } + const bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': 2 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + spec.interpretResponse({ body: response }, {bidderRequest: bidderRequest}); + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request).to.equal(undefined); + }); + }); + describe('getUserSyncs', function () { const GDPR_CONSENT_STRING = 'GDPR_CONSENT_STRING'; it('should get correct user sync when iframeEnabled', function () { let pixel = spec.getUserSyncs({iframeEnabled: true}) expect(pixel[0].type).to.equal('iframe'); - expect(pixel[0].url).to.equal('https://s3.amazonaws.com/nobid-public/sync.html'); + expect(pixel[0].url).to.equal('https://public.servenobid.com/sync.html'); }); it('should get correct user sync when iframeEnabled and pixelEnabled', function () { let pixel = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}) expect(pixel[0].type).to.equal('iframe'); - expect(pixel[0].url).to.equal('https://s3.amazonaws.com/nobid-public/sync.html'); + expect(pixel[0].url).to.equal('https://public.servenobid.com/sync.html'); }); it('should get correct user sync when iframeEnabled', function () { let pixel = spec.getUserSyncs({iframeEnabled: true}, {}, {gdprApplies: true, consentString: GDPR_CONSENT_STRING}) expect(pixel[0].type).to.equal('iframe'); - expect(pixel[0].url).to.equal('https://s3.amazonaws.com/nobid-public/sync.html?gdpr=1&gdpr_consent=' + GDPR_CONSENT_STRING); + expect(pixel[0].url).to.equal('https://public.servenobid.com/sync.html?gdpr=1&gdpr_consent=' + GDPR_CONSENT_STRING); }); it('should get correct user sync when !iframeEnabled', function () { @@ -234,6 +588,13 @@ describe('Nobid Adapter', function () { expect(pixel.length).to.equal(0); }); + it('should get correct user sync when !iframeEnabled and pixelEnabled', function () { + let pixel = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{body: {syncs: ['sync_url']}}]) + expect(pixel.length).to.equal(1); + expect(pixel[0].type).to.equal('image'); + expect(pixel[0].url).to.equal('sync_url'); + }); + it('should get correct user sync when !iframeEnabled', function () { let pixel = spec.getUserSyncs({}) expect(pixel.length).to.equal(0); diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index a87bff33f4b..a91e9ac27e8 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/oneVideoBidAdapter'; -import * as utils from 'src/utils'; -import {config} from 'src/config'; +import { spec } from 'modules/oneVideoBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('OneVideoBidAdapter', function () { let bidRequest; @@ -47,6 +46,7 @@ describe('OneVideoBidAdapter', function () { sid: 134, rewarded: 1, placement: 1, + hp: 1, inventoryid: 123 }, site: { @@ -90,7 +90,7 @@ describe('OneVideoBidAdapter', function () { }; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('should return true when the "pubId" param is missing', function () { + it('should return true when the "pubId" param exists', function () { bidRequest.params = { video: { playerWidth: 480, @@ -115,6 +115,87 @@ describe('OneVideoBidAdapter', function () { bidRequest.params = {}; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); + + it('should return false when the mediaType is "banner" and display="undefined" (DAP 3P)', function () { + bidRequest = { + mediaTypes: { + banner: { + sizes: [640, 480] + } + } + } + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }) + + it('should return true when the mediaType is "banner" and display=1 (DAP 3P)', function () { + bidRequest = { + mediaTypes: { + banner: { + sizes: [640, 480] + } + }, + bidder: 'oneVideo', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + playbackmethod: [1, 5], + sid: 134, + rewarded: 1, + placement: 1, + inventoryid: 123, + display: 1 + }, + site: { + id: 1, + page: 'https://news.yahoo.com/portfolios', + referrer: 'http://www.yahoo.com' + }, + pubId: 'brxd' + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }) + + it('should return false when the mediaType is "video" and context="outstream" and display=1 (DAP 3P)', function () { + bidRequest = { + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + params: { + video: { + display: 1 + } + } + } + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }) + + it('should return true for Multi-Format AdUnits, when the mediaTypes are both "banner" and "video" (Multi-Format Support)', function () { + bidRequest = { + mediaTypes: { + banner: { + sizes: [640, 480] + }, + video: { + context: 'outstream', + playerSize: [640, 480] + } + } + } + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }) }); describe('spec.buildRequests', function () { @@ -136,12 +217,15 @@ describe('OneVideoBidAdapter', function () { const placement = bidRequest.params.video.placement; const rewarded = bidRequest.params.video.rewarded; const inventoryid = bidRequest.params.video.inventoryid; + const VERSION = '3.0.3'; 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); expect(data.imp[0].ext.rewarded).to.equal(rewarded); expect(data.imp[0].video.placement).to.equal(placement); expect(data.imp[0].ext.inventoryid).to.equal(inventoryid); + expect(data.imp[0].ext.prebidver).to.equal('$prebid.version$'); + expect(data.imp[0].ext.adapterver).to.equal(VERSION); }); it('must parse bid size from a nested array', function () { @@ -153,6 +237,84 @@ describe('OneVideoBidAdapter', function () { expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); }); + + it('should set pubId to HBExchange when bid.params.video.e2etest = true', function () { + bidRequest.params.video.e2etest = true; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(spec.E2ETESTENDPOINT + 'HBExchange'); + }); + + it('should attach End 2 End test data', function () { + bidRequest.params.video.e2etest = true; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].bidfloor).to.not.exist; + expect(data.imp[0].video.w).to.equal(300); + expect(data.imp[0].video.h).to.equal(250); + expect(data.imp[0].video.mimes).to.eql(['video/mp4', 'application/javascript']); + expect(data.imp[0].video.api).to.eql([2]); + expect(data.site.page).to.equal('https://verizonmedia.com'); + expect(data.site.ref).to.equal('https://verizonmedia.com'); + expect(data.tmax).to.equal(1000); + }); + + it('it should create new schain and send it if video.params.sid exists', function () { + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + const schain = data.source.ext.schain; + expect(schain.nodes.length).to.equal(1); + expect(schain.nodes[0].sid).to.equal(bidRequest.params.video.sid); + expect(schain.nodes[0].rid).to.equal(data.id); + }) + + it('should send Global or Bidder specific schain if sid is not passed in video.params.sid', function () { + bidRequest.params.video.sid = null; + const globalSchain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'some-platform.com', + sid: '111111', + rid: bidRequest.id, + hp: 1 + }] + }; + bidRequest.schain = globalSchain; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + const schain = data.source.ext.schain; + expect(schain.nodes.length).to.equal(1); + expect(schain).to.equal(globalSchain); + }); + + it('should ignore Global or Bidder specific schain if video.params.sid exists and send new schain', function () { + const globalSchain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'some-platform.com', + sid: '111111', + rid: bidRequest.id, + hp: 1 + }] + }; + bidRequest.schain = globalSchain; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + const schain = data.source.ext.schain; + expect(schain.nodes.length).to.equal(1); + expect(schain.complete).to.equal(1); + expect(schain.nodes[0].sid).to.equal(bidRequest.params.video.sid); + expect(schain.nodes[0].rid).to.equal(data.id); + }) + + it('should append hp to new schain created by sid if video.params.hp is passed', function () { + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + const schain = data.source.ext.schain; + expect(schain.nodes[0].hp).to.equal(bidRequest.params.video.hp); + }) }); describe('spec.interpretResponse', function () { @@ -173,7 +335,7 @@ describe('OneVideoBidAdapter', function () { expect(bidResponse.length).to.equal(0); }); - it('should return a valid bid response with just "adm"', function () { + it('should return a valid video bid response with just "adm"', function () { const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); let o = { @@ -194,6 +356,26 @@ describe('OneVideoBidAdapter', function () { }; expect(bidResponse).to.deep.equal(o); }); + // @abrowning14 check that banner DAP response is appended to o.ad + mediaType: 'banner' + it('should return a valid DAP banner bid-response', function () { + bidRequest = { + mediaTypes: { + banner: { + sizes: [640, 480] + } + }, + params: { + video: { + display: 1 + } + } + } + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: '
DAP UNIT HERE
'}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ad).to.equal('
DAP UNIT HERE
'); + expect(bidResponse.mediaType).to.equal('banner'); + expect(bidResponse.renderer).to.be.undefined; + }); }); describe('when GDPR and uspConsent applies', function () { @@ -255,16 +437,10 @@ describe('OneVideoBidAdapter', function () { expect(request[0].data.regs.ext.gdpr).to.equal(1); expect(request[0].data.regs.ext.us_privacy).to.equal(bidderRequest.uspConsent); }); - - it('should send schain object', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); - const data = requests[0].data; - expect(data.source.ext.schain.nodes[0].sid).to.equal(bidRequest.params.video.sid); - expect(data.source.ext.schain.nodes[0].rid).to.equal(data.id); - }); }); + describe('should send banner object', function () { - it('should send banner object when display is 1', function () { + it('should send banner object when display is 1 and context="instream" (DAP O&O)', function () { bidRequest = { mediaTypes: { video: { @@ -289,7 +465,9 @@ describe('OneVideoBidAdapter', function () { placement: 1, inventoryid: 123, sid: 134, - display: 1 + display: 1, + minduration: 10, + maxduration: 30 }, site: { id: 1, @@ -311,9 +489,11 @@ describe('OneVideoBidAdapter', function () { expect(data.imp[0].ext.inventoryid).to.equal(bidRequest.params.video.inventoryid); expect(data.imp[0].banner.mimes).to.equal(bidRequest.params.video.mimes); expect(data.imp[0].banner.placement).to.equal(bidRequest.params.video.placement); + expect(data.imp[0].banner.ext.minduration).to.equal(bidRequest.params.video.minduration); + expect(data.imp[0].banner.ext.maxduration).to.equal(bidRequest.params.video.maxduration); expect(data.site.id).to.equal(bidRequest.params.site.id); }); - it('should send video object when display is other than 1', function () { + it('should send video object when display is other than 1 (VAST for All)', function () { bidRequest = { mediaTypes: { video: { @@ -358,7 +538,7 @@ describe('OneVideoBidAdapter', function () { expect(data.imp[0].video.pos).to.equal(position); expect(data.imp[0].video.mimes).to.equal(bidRequest.params.video.mimes); }); - it('should send video object when display is not passed', function () { + it('should send video object when display is not passed (VAST for All)', function () { bidRequest = { mediaTypes: { video: { @@ -381,7 +561,9 @@ describe('OneVideoBidAdapter', function () { delivery: [2], playbackmethod: [1, 5], placement: 123, - sid: 134 + sid: 134, + minduration: 10, + maxduration: 30 }, site: { id: 1, @@ -401,6 +583,24 @@ describe('OneVideoBidAdapter', function () { expect(data.imp[0].video.h).to.equal(height); expect(data.imp[0].video.pos).to.equal(position); expect(data.imp[0].video.mimes).to.equal(bidRequest.params.video.mimes); + expect(data.imp[0].video.protocols).to.equal(bidRequest.params.video.protocols); + expect(data.imp[0].video.linearity).to.equal(1); + expect(data.imp[0].video.maxduration).to.equal(bidRequest.params.video.maxduration); + expect(data.imp[0].video.minduration).to.equal(bidRequest.params.video.minduration); + }); + describe('getUserSyncs', function () { + const GDPR_CONSENT_STRING = 'GDPR_CONSENT_STRING'; + + it('should get correct user sync when iframeEnabled', function () { + let pixel = spec.getUserSyncs({pixelEnabled: true}, {}, {gdprApplies: true, consentString: GDPR_CONSENT_STRING}) + expect(pixel[2].type).to.equal('image'); + expect(pixel[2].url).to.equal('https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=1&gdpr_consent=' + GDPR_CONSENT_STRING + '&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0&gdpr=1&gdpr_consent=' + encodeURI(GDPR_CONSENT_STRING)); + }); + + it('should default to gdprApplies=0 when consentData is undefined', function () { + let pixel = spec.getUserSyncs({pixelEnabled: true}, {}, undefined); + expect(pixel[2].url).to.equal('https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=0&gdpr_consent=&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0&gdpr=0&gdpr_consent='); + }); }); }); }); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 93284566069..c1462c3814d 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -1,40 +1,119 @@ -import { spec } from 'modules/onetagBidAdapter'; +import { spec, isValid, hasTypeVideo } from 'modules/onetagBidAdapter.js'; import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import {INSTREAM, OUTSTREAM} from 'src/video.js'; describe('onetag', function () { - let bid = { - 'bidder': 'onetag', - 'params': { - 'pubId': '386276e072', - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': 'qwerty123' - }; + function createBid() { + return { + 'bidder': 'onetag', + 'params': { + 'pubId': '386276e072', + }, + 'adUnitCode': 'adunit-code', + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': 'qwerty123' + }; + } + + function createBannerBid(bidRequest) { + const bid = bidRequest || createBid(); + bid.mediaTypes = bid.mediaTypes || {}; + bid.mediaTypes.banner = { + sizes: [[300, 250]] + }; + return bid; + } + + function createInstreamVideoBid(bidRequest) { + const bid = bidRequest || createBid(); + bid.mediaTypes = bid.mediaTypes || {}; + bid.mediaTypes.video = { + context: 'instream', + mimes: ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + playerSize: [640, 480] + }; + return bid; + } + + function createOutstreamVideoBid(bidRequest) { + const bid = bidRequest || createBid(); + bid.mediaTypes = bid.mediaTypes || {}; + bid.mediaTypes.video = { + context: 'outstream', + mimes: ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + playerSize: [640, 480] + }; + return bid; + } + + function createMultiFormatBid() { + return createInstreamVideoBid(createBannerBid()); + } + + const bannerBid = createBannerBid(); + const instreamVideoBid = createInstreamVideoBid(); + const outstreamVideoBid = createOutstreamVideoBid(); describe('isBidRequestValid', function () { it('Should return true when required params are found', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bannerBid)).to.be.true; }); it('Should return false when pubId is not a string', function () { - bid.params.pubId = 30; - expect(spec.isBidRequestValid(bid)).to.be.false; + bannerBid.params.pubId = 30; + expect(spec.isBidRequestValid(bannerBid)).to.be.false; }); it('Should return false when pubId is undefined', function () { - bid.params.pubId = undefined; - expect(spec.isBidRequestValid(bid)).to.be.false; + bannerBid.params.pubId = undefined; + expect(spec.isBidRequestValid(bannerBid)).to.be.false; }); - it('Should return false when the sizes array is empty', function () { - bid.sizes = []; - expect(spec.isBidRequestValid(bid)).to.be.false; + describe('banner bidRequest', function () { + it('Should return false when the sizes array is empty', function () { + bannerBid.sizes = []; + expect(spec.isBidRequestValid(bannerBid)).to.be.false; + }); + }); + describe('video bidRequest', function () { + it('Should return false when the context is undefined', function () { + instreamVideoBid.mediaTypes.video.context = undefined; + expect(spec.isBidRequestValid(instreamVideoBid)).to.be.false; + }); + it('Should return false when the context is not instream or outstream', function () { + instreamVideoBid.mediaTypes.video.context = 'wrong'; + expect(spec.isBidRequestValid(instreamVideoBid)).to.be.false; + }); + it('Should return false when playerSize is undefined', function () { + const videoBid = createInstreamVideoBid(); + videoBid.mediaTypes.video.playerSize = undefined; + expect(spec.isBidRequestValid(videoBid)).to.be.false; + }); + it('Should return false when playerSize is not an array', function () { + const videoBid = createInstreamVideoBid(); + videoBid.mediaTypes.video.playerSize = 30; + expect(spec.isBidRequestValid(videoBid)).to.be.false; + }); + it('Should return false when playerSize is an empty array', function () { + const videoBid = createInstreamVideoBid(); + videoBid.mediaTypes.video.playerSize = []; + expect(spec.isBidRequestValid(videoBid)).to.be.false; + }); + it('Should return true when context is outstream', function () { + expect(spec.isBidRequestValid(outstreamVideoBid)).to.be.true; + }); + }); + describe('multi format bidRequest', function () { + const multiFormatBid = createMultiFormatBid(); + it('Should return true when correct multi format bid is passed', function () { + expect(spec.isBidRequestValid(multiFormatBid)).to.be.true; + }); }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); + let serverRequest = spec.buildRequests([bannerBid, instreamVideoBid]); it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -51,12 +130,12 @@ describe('onetag', function () { const d = serverRequest.data; try { const data = JSON.parse(d); - it('Should contains all keys', function () { + it('Should contain all keys', function () { expect(data).to.be.an('object'); - expect(data).to.have.all.keys('location', 'masked', 'referrer', 'sHeight', 'sWidth', 'timeOffset', 'date', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids'); + expect(data).to.include.all.keys('location', 'referrer', 'masked', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'timing', 'version'); expect(data.location).to.be.a('string'); - expect(data.masked).to.be.a('number'); - expect(data.referrer).to.be.a('string'); + expect(data.masked).to.be.oneOf([0, 1, 2]); + expect(data.referrer).to.satisfy(referrer => referrer === null || typeof referrer === 'string'); expect(data.sHeight).to.be.a('number'); expect(data.sWidth).to.be.a('number'); expect(data.wWidth).to.be.a('number'); @@ -68,30 +147,28 @@ describe('onetag', function () { expect(data.sLeft).to.be.a('number'); expect(data.sTop).to.be.a('number'); expect(data.hLength).to.be.a('number'); - expect(data.timeOffset).to.be.a('number'); - expect(data.date).to.be.a('string'); expect(data.bids).to.be.an('array'); - + expect(data.version).to.have.all.keys('prebid', 'adapter'); 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'); + if (hasTypeVideo(bid)) { + expect(bid).to.have.all.keys('adUnitCode', 'auctionId', 'bidId', 'bidderRequestId', 'pubId', 'transactionId', 'context', 'mimes', 'playerSize', 'protocols', 'maxDuration', 'api', 'type'); + } else if (isValid(BANNER, bid)) { + expect(bid).to.have.all.keys('adUnitCode', 'auctionId', 'bidId', 'bidderRequestId', 'pubId', 'transactionId', 'sizes', 'type'); + } expect(bid.bidId).to.be.a('string'); expect(bid.pubId).to.be.a('string'); } }); - } catch (e) { - console.log('Error while parsing'); - } + } catch (e) {} it('Returns empty data if no valid requests are passed', function () { 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'); - } + } catch (e) {} }); it('should send GDPR consent data', function () { let consentString = 'consentString'; @@ -105,7 +182,7 @@ describe('onetag', function () { gdprApplies: true } }; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); const payload = JSON.parse(serverRequest.data); expect(payload).to.exist; @@ -113,44 +190,61 @@ describe('onetag', function () { expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; }); + it('Should send us privacy string', function () { + let consentString = 'us_foo'; + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': consentString + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload.usPrivacy).to.exist; + expect(payload.usPrivacy).to.exist.and.to.equal(consentString); + }); }); describe('interpretResponse', function () { - const resObject = { - body: { - nobid: false, - bids: [{ - ad: '
Advertising
', - cpm: 13, - width: 300, - height: 250, - creativeId: '1820', - dealId: 'dishfo', - currency: 'USD', - requestId: 'sdiceobxcw' - }] - } - }; + const request = getBannerVideoRequest(); + const response = getBannerVideoResponse(); + const requestData = JSON.parse(request.data); it('Returns an array of valid server responses if response object is valid', function () { - 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'); + const interpretedResponse = spec.interpretResponse(response, request); + expect(interpretedResponse).to.be.an('array').that.is.not.empty; + for (let i = 0; i < interpretedResponse.length; i++) { + let dataItem = interpretedResponse[i]; + expect(dataItem).to.include.all.keys('requestId', 'cpm', 'width', 'height', 'ttl', 'creativeId', 'netRevenue', 'currency', 'meta', 'dealId'); + if (dataItem.meta.mediaType === VIDEO) { + const {context} = find(requestData.bids, (item) => item.bidId === dataItem.requestId); + if (context === INSTREAM) { + expect(dataItem).to.include.all.keys('videoCacheKey', 'vastUrl'); + expect(dataItem.vastUrl).to.be.a('string'); + expect(dataItem.videoCacheKey).to.be.a('string'); + } else if (context === OUTSTREAM) { + expect(dataItem).to.include.all.keys('renderer', 'vastXml', 'vastUrl'); + expect(dataItem.renderer).to.be.an('object'); + expect(dataItem.vastUrl).to.be.a('string'); + expect(dataItem.vastXml).to.be.a('string'); + } + } else if (dataItem.meta.mediaType === BANNER) { + expect(dataItem).to.include.all.keys('ad'); + expect(dataItem.ad).to.be.a('string'); + } 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', function () { - const serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; - }); + }); + it('Returns an empty array if response is not valid', function () { + const serverResponses = spec.interpretResponse('invalid_response', { data: '{}' }); + expect(serverResponses).to.be.an('array').that.is.empty; }); }); describe('getUserSyncs', function () { @@ -204,5 +298,114 @@ describe('onetag', function () { expect(syncs[0].url).to.include(sync_endpoint); expect(syncs[0].url).to.not.match(/(?:[?&](?:gdpr_consent=([^&]*)|gdpr=([^&]*)))+$/); }); + it('Should send us privacy string', function () { + let usConsentString = 'us_foo'; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, usConsentString); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:us_privacy=us_foo(?:[&][^&]*)*))+$/); + }); }); }); + +function getBannerVideoResponse() { + return { + body: { + nobid: false, + bids: [ + { + ad: '
Advertising
', + cpm: 13, + width: 300, + height: 250, + creativeId: '1820', + dealId: 'dishfo', + currency: 'USD', + requestId: 'banner', + mediaType: BANNER, + }, + { + cpm: 13, + width: 300, + height: 250, + creativeId: '1820', + dealId: 'dishfo', + currency: 'USD', + requestId: 'videoInstream', + vastUrl: 'https://videoinstream.org', + videoCacheKey: 'key', + mediaType: VIDEO + }, + { + cpm: 13, + width: 300, + height: 250, + creativeId: '1820', + dealId: 'dishfo', + currency: 'USD', + vastUrl: 'https://videooutstream.org', + requestId: 'videoOutstream', + ad: '', + rendererUrl: 'https://testRenderer', + mediaType: VIDEO + } + ] + } + }; +} + +function getBannerVideoRequest() { + return { + data: JSON.stringify({ + bids: [ + { + adUnitCode: 'target-div', + bidId: 'videoOutstream', + bidderRequestId: '12bb1e0f9fb669', + auctionId: '80784b4d-79ad-49ef-a006-75d8888b7609', + transactionId: '5f132731-3091-49b2-8fab-0e9c917733bc', + pubId: '386276e072', + context: 'outstream', + mimes: [], + playerSize: [], + type: 'video' + }, + { + adUnitCode: 'target-div', + bidId: 'videoInstream', + bidderRequestId: '12bb1e0f9fb669', + auctionId: '80784b4d-79ad-49ef-a006-75d8888b7609', + transactionId: '5f132731-3091-49b2-8fab-0e9c917733bc', + pubId: '386276e072', + context: 'instream', + mimes: [], + playerSize: [], + type: 'video' + } + ], + location: 'https%3A%2F%2Flocal.onetag.net%3A9000%2Fv2%2Fprebid-video%2Fvideo.html%3Fpbjs_debug%3Dtrue', + referrer: '0', + masked: 0, + wWidth: 860, + wHeight: 949, + oWidth: 1853, + oHeight: 1053, + sWidth: 1920, + sHeight: 1080, + aWidth: 1920, + aHeight: 1053, + sLeft: 1987, + sTop: 27, + xOffset: 0, + yOffset: 0, + docHidden: false, + hLength: 2, + timing: { + pageLoadTime: -1593433770022, + connectTime: 42, + renderTime: -1593433770092 + }, + onetagSid: 'user_id' + }) + } +} diff --git a/test/spec/modules/onomagicBidAdapter_spec.js b/test/spec/modules/onomagicBidAdapter_spec.js new file mode 100644 index 00000000000..7c71c3e5764 --- /dev/null +++ b/test/spec/modules/onomagicBidAdapter_spec.js @@ -0,0 +1,285 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/onomagicBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const URL = 'https://bidder.onomagic.com/hb'; + +describe('onomagicBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'onomagic', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e' + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'onomagic', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisherId not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].mediaTypes.banner.sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250 + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/open8BidAdapter_spec.js b/test/spec/modules/open8BidAdapter_spec.js index 7ef1c38abb2..506742bef9e 100644 --- a/test/spec/modules/open8BidAdapter_spec.js +++ b/test/spec/modules/open8BidAdapter_spec.js @@ -1,5 +1,5 @@ -import { spec } from 'modules/open8BidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/open8BidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const ENDPOINT = 'https://as.vt.open8.com/v1/control/prebid'; diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index 168f06aa915..805435abf80 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -1,9 +1,10 @@ import { expect } from 'chai'; -import openxAdapter from 'modules/openxAnalyticsAdapter'; -import { config } from 'src/config'; -import events from 'src/events'; +import openxAdapter from 'modules/openxAnalyticsAdapter.js'; +import { config } from 'src/config.js'; +import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; -import * as utils from 'src/utils'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; const { EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } @@ -152,12 +153,7 @@ describe('openx analytics adapter', function() { }, {}); } - let xhr; - let requests; - before(function() { - xhr = sinon.useFakeXMLHttpRequest(); - xhr.onCreate = request => requests.push(request); sinon.stub(events, 'getEvents').returns([]); openxAdapter.enableAnalytics({ options: { @@ -167,13 +163,11 @@ describe('openx analytics adapter', function() { }); after(function() { - xhr.restore(); events.getEvents.restore(); openxAdapter.disableAnalytics(); }); beforeEach(function() { - requests = []; openxAdapter.reset(); }); @@ -185,7 +179,7 @@ describe('openx analytics adapter', function() { [BID_REQUESTED, bidRequestedOpenX] ]); - expect(requests.length).to.equal(0); + expect(server.requests.length).to.equal(0); }); it('should send 1 request to the right endpoint', function() { @@ -195,9 +189,9 @@ describe('openx analytics adapter', function() { [BID_RESPONSE, bidResponseOpenX] ]); - expect(requests.length).to.equal(1); + expect(server.requests.length).to.equal(1); - const endpoint = requests[0].url.split('?')[0]; + const endpoint = server.requests[0].url.split('?')[0]; // note IE11 returns the default secure port, so we look for this alternate value as well in these tests expect(endpoint).to.be.oneOf(['https://ads.openx.net/w/1.0/pban', 'https://ads.openx.net:443/w/1.0/pban']); }); @@ -210,7 +204,7 @@ describe('openx analytics adapter', function() { [BID_RESPONSE, bidResponseOpenX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ 'hb.ct': String(bidRequestedOpenX.auctionStart), 'hb.rid': auctionInit.auctionId, @@ -236,7 +230,7 @@ describe('openx analytics adapter', function() { config.getConfig.restore(); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ 'hb.cur': 'bitcoin' }); @@ -249,7 +243,7 @@ describe('openx analytics adapter', function() { [BID_RESPONSE, bidResponseOpenX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.not.have.key('hb.cur'); }); }); @@ -276,7 +270,7 @@ describe('openx analytics adapter', function() { window.top.performance = originalPerf; Date.now.restore(); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ 'hb.dcl': String(timing.domContentLoadedEventEnd - timing.fetchStart), 'hb.dl': String(timing.loadEventEnd - timing.fetchStart), @@ -297,7 +291,7 @@ describe('openx analytics adapter', function() { window.top.performance = originalPerf; - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.not.have.keys( 'hb.dcl', 'hb.dl', @@ -317,7 +311,7 @@ describe('openx analytics adapter', function() { [BID_RESPONSE, bidResponseCloseX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ ts: bidResponseOpenX.ts, auid: bidRequestedOpenX.bids[0].params.unit @@ -332,7 +326,7 @@ describe('openx analytics adapter', function() { [BID_RESPONSE, bidResponseCloseX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ auid: bidRequestedOpenX.bids[0].params.unit }); @@ -346,7 +340,7 @@ describe('openx analytics adapter', function() { [BID_RESPONSE, bidResponseCloseX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.not.have.keys('auid', 'ts'); }); }); @@ -361,7 +355,7 @@ describe('openx analytics adapter', function() { [BID_RESPONSE, bidResponseCloseX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); const auctionStart = bidRequestedOpenX.auctionStart; expect(queryData).to.include({ 'hb.exn': [ @@ -394,7 +388,7 @@ describe('openx analytics adapter', function() { [BID_TIMEOUT, bidTimeoutOpenX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); const auctionStart = bidRequestedOpenX.auctionStart; expect(queryData).to.include({ 'hb.exn': [ @@ -425,7 +419,7 @@ describe('openx analytics adapter', function() { [BID_WON, bidWonOpenX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ 'hb.we': '0', 'hb.g1': 'false' @@ -439,7 +433,7 @@ describe('openx analytics adapter', function() { [BID_RESPONSE, bidResponseOpenX] ]); - const queryData = getQueryData(requests[0].url); + const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ 'hb.we': '-1', 'hb.g1': 'true' diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 353c3342627..0808d78c0aa 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,9 +1,9 @@ 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'; +import {spec, USER_ID_CODE_TO_QUERY_ARG} from 'modules/openxBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {userSync} from 'src/userSync.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; const URLBASE = '/w/1.0/arj'; const URLBASEVIDEO = '/v/1.0/avjp'; @@ -290,6 +290,38 @@ describe('OpenxAdapter', function () { expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); }); }); + + describe('and request config uses test', () => { + const videoBidWithTest = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain', + test: true + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + let mockBidderRequest = {refererInfo: {}}; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithTest)).to.equal(true); + }); + + it('should send video bid request to openx url via GET, with vtest=1 video parameter', function () { + const request = spec.buildRequests([videoBidWithTest], mockBidderRequest); + expect(request[0].data.vtest).to.equal(1); + }); + }); }); }); @@ -508,25 +540,6 @@ describe('OpenxAdapter', function () { expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); }); - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); - it('should send out custom bc parameter, if override is present', function () { const bidRequest = Object.assign({}, bidRequestsWithMediaTypes[0], @@ -599,6 +612,44 @@ describe('OpenxAdapter', function () { config.getConfig.restore(); }); + describe('when us_privacy applies', function () { + beforeEach(function () { + bidderRequest = { + uspConsent: '1YYN', + refererInfo: {} + }; + + 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.us_privacy).to.equal('1YYN'); + expect(request[1].data.us_privacy).to.equal('1YYN'); + }); + }); + + describe('when us_privacy does not applies', function () { + beforeEach(function () { + bidderRequest = { + refererInfo: {} + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + it('should not send the consent string, when consent string is undefined', function () { + delete bidderRequest.uspConsent; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('us_privacy'); + expect(request[1].data).to.not.have.property('us_privacy'); + }); + }); + describe('when GDPR applies', function () { beforeEach(function () { bidderRequest = { @@ -988,123 +1039,173 @@ describe('OpenxAdapter', function () { }); describe('when there are userid providers', function () { - describe('with publisher common id', function () { - it('should not send a pubcid query param when there is no crumbs.pubcid and no userId.pubcid defined in the bid requests', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('pubcid'); - }); + const EXAMPLE_DATA_BY_ATTR = { + britepoolid: '1111-britepoolid', + criteoId: '1111-criteoId', + digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + id5id: '1111-id5id', + idl_env: '1111-idl_env', + lipb: {lipbid: '1111-lipb'}, + netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', + parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, + pubcid: '1111-pubcid', + tdid: '1111-tdid', + }; - it('should send a pubcid query param when crumbs.pubcid is defined in the bid requests', function () { - const bidRequestsWithPubcid = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - crumbs: { - pubcid: 'c4a4c843-2368-4b5e-b3b1-6ee4702b9ad6' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }]; - const request = spec.buildRequests(bidRequestsWithPubcid, mockBidderRequest); - expect(request[0].data.pubcid).to.equal('c4a4c843-2368-4b5e-b3b1-6ee4702b9ad6'); + // generates the same set of tests for each id provider + utils._each(USER_ID_CODE_TO_QUERY_ARG, (userIdQueryArg, userIdProviderKey) => { + describe(`with userId attribute: ${userIdProviderKey}`, function () { + it(`should not send a ${userIdQueryArg} query param when there is no userId.${userIdProviderKey} defined in the bid requests`, function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data).to.not.have.any.keys(userIdQueryArg); + }); + + it(`should send a ${userIdQueryArg} query param when userId.${userIdProviderKey} is defined in the bid requests`, function () { + const bidRequestsWithUserId = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1' + }]; + // enrich bid request with userId key/value + bidRequestsWithUserId[0].userId[userIdProviderKey] = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; + + const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); + + let userIdValue; + // handle cases where userId key refers to an object + switch (userIdProviderKey) { + case 'digitrustid': + userIdValue = EXAMPLE_DATA_BY_ATTR.digitrustid.data.id; + break; + case 'lipb': + userIdValue = EXAMPLE_DATA_BY_ATTR.lipb.lipbid; + break; + case 'parrableId': + userIdValue = EXAMPLE_DATA_BY_ATTR.parrableId.eid; + break; + default: + userIdValue = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; + } + + expect(request[0].data[USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]]).to.equal(userIdValue); + }); }); + }); + }); - it('should send a pubcid query param when userId.pubcid is defined in the bid requests', function () { - const bidRequestsWithPubcid = [{ - bidder: 'openx', + describe('floors', function () { + it('should send out custom floors on bids that have customFloors specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { params: { - unit: '11', - delDomain: 'test-del-domain' - }, - userId: { - pubcid: 'c1a4c843-2368-4b5e-b3b1-6ee4702b9ad6' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }]; - const request = spec.buildRequests(bidRequestsWithPubcid, mockBidderRequest); - expect(request[0].data.pubcid).to.equal('c1a4c843-2368-4b5e-b3b1-6ee4702b9ad6'); - }); + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customFloor': 1.500001 + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('1500'); }); - describe('with the trade desk unified id', function () { - it('should not send a tdid query param when there is no userId.tdid defined in the bid requests', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('ttduuid'); + context('with floors module', function () { + let adServerCurrencyStub; + + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') }); - it('should send a tdid query param when userId.tdid is defined in the bid requests', function () { - const bidRequestsWithTdid = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - userId: { - tdid: '00000000-aaaa-1111-bbbb-222222222222' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send out floors on bids', function () { + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'AUS', + floor: 9.99 + } } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }]; - const request = spec.buildRequests(bidRequestsWithTdid, mockBidderRequest); - expect(request[0].data.ttduuid).to.equal('00000000-aaaa-1111-bbbb-222222222222'); + } + ); + + const bidRequest2 = Object.assign({}, + bidRequestsWithMediaTypes[1], + { + getFloor: () => { + return { + currency: 'AUS', + floor: 18.881 + } + } + } + ); + + const request = spec.buildRequests([bidRequest1, bidRequest2], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('9990,18881'); }); - }); - describe('with the liveRamp identity link envelope', function () { - it('should not send a tdid query param when there is no userId.lre defined in the bid requests', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('lre'); + it('should send out floors on bids in the default currency', function () { + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return {}; + } + } + ); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].currency).to.equal('USD'); }); - it('should send a lre query param when userId.lre is defined in the bid requests', function () { - const bidRequestsWithLiveRampEnvelope = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - userId: { - idl_env: '00000000-aaaa-1111-bbbb-222222222222' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] + it('should send out floors on bids in the ad server currency if defined', function () { + adServerCurrencyStub.returns('bitcoin'); + + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return {}; } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }]; - const request = spec.buildRequests(bidRequestsWithLiveRampEnvelope, mockBidderRequest); - expect(request[0].data.lre).to.equal('00000000-aaaa-1111-bbbb-222222222222'); + } + ); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); }); - }); - }); + }) + }) }); describe('buildRequests for video', function () { @@ -1142,6 +1243,11 @@ describe('OpenxAdapter', function () { expect(dataParams.vwd).to.equal(640); }); + it('shouldn\'t have the test parameter', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.vtest).to.be.undefined; + }); + it('should send a bc parameter', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); const dataParams = request[0].data; @@ -1171,7 +1277,7 @@ describe('OpenxAdapter', function () { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - } + }; mockBidderRequest = {refererInfo: {}}; }); @@ -1603,32 +1709,31 @@ describe('OpenxAdapter', function () { payload: {'bid': bidsWithMediaType[0], 'startTime': new Date()} }; const bidResponse = { - 'pub_rev': '1', + 'pub_rev': '1000', 'width': '640', 'height': '480', 'adid': '5678', - 'vastUrl': 'https://testvast.com/vastpath?colo=https://test-colo.com&ph=test-ph&ts=test-ts', + 'currency': 'AUD', + 'vastUrl': 'https://testvast.com', 'pixels': 'https://testpixels.net' }; it('should return correct bid response with MediaTypes', function () { - const expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': '640', - 'height': '480', - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'USD' - } - ]; + const expectedResponse = { + 'requestId': '30b31c1838de1e', + 'cpm': 1, + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'creativeId': '5678', + 'vastUrl': 'https://testvast.com', + 'ttl': 300, + 'netRevenue': true, + 'currency': 'AUD' + }; const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); + expect(result[0]).to.eql(expectedResponse); }); it('should return correct bid response with MediaType', function () { @@ -1726,6 +1831,85 @@ describe('OpenxAdapter', function () { ); expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); }); + + describe('when gdpr applies', function () { + let gdprConsent; + let gdprPixelUrl; + beforeEach(() => { + gdprConsent = { + consentString: 'test-gdpr-consent-string', + gdprApplies: true + }; + + gdprPixelUrl = 'https://testpixels.net?gdpr=1&gdpr_consent=gdpr-pixel-consent' + }); + + it('when there is a response, it should have the gdpr query params', () => { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [{body: {ads: {pixels: gdprPixelUrl}}}], + gdprConsent + ); + + expect(url).to.have.string('gdpr_consent=gdpr-pixel-consent'); + expect(url).to.have.string('gdpr=1'); + }); + + it('when there is no response, it should append gdpr query params', () => { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + gdprConsent + ); + expect(url).to.have.string('gdpr_consent=test-gdpr-consent-string'); + expect(url).to.have.string('gdpr=1'); + }); + + it('should not send signals if no consent object is available', function () { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + ); + expect(url).to.not.have.string('gdpr_consent='); + expect(url).to.not.have.string('gdpr='); + }); + }); + + describe('when ccpa applies', function () { + let usPrivacyConsent; + let uspPixelUrl; + beforeEach(() => { + usPrivacyConsent = 'TEST'; + uspPixelUrl = 'https://testpixels.net?us_privacy=AAAA' + }); + it('when there is a response, it should send the us privacy string from the response, ', () => { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [{body: {ads: {pixels: uspPixelUrl}}}], + undefined, + usPrivacyConsent + ); + + expect(url).to.have.string('us_privacy=AAAA'); + }); + it('when there is no response, it send have the us privacy string', () => { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + undefined, + usPrivacyConsent + ); + expect(url).to.have.string(`us_privacy=${usPrivacyConsent}`); + }); + + it('should not send signals if no consent string is available', function () { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + ); + expect(url).to.not.have.string('us_privacy='); + }); + }); }); /** diff --git a/test/spec/modules/optimeraBidAdapter_spec.js b/test/spec/modules/optimeraBidAdapter_spec.js index a0111ca9944..ada07fe25c2 100644 --- a/test/spec/modules/optimeraBidAdapter_spec.js +++ b/test/spec/modules/optimeraBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/optimeraBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/optimeraBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('OptimeraAdapter', function () { const adapter = newBidder(spec); diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index 9e7d10d7b04..df551311c0b 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -1,8 +1,6 @@ import {expect} from 'chai'; -import {spec} from 'modules/orbidderBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; -import openxAdapter from '../../../modules/openxAnalyticsAdapter'; -import {detectReferer} from 'src/refererDetection'; +import {spec} from 'modules/orbidderBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; describe('orbidderBidAdapter', () => { const adapter = newBidder(spec); @@ -93,12 +91,16 @@ describe('orbidderBidAdapter', () => { it('sends bid request to endpoint via https using post', () => { expect(request.method).to.equal('POST'); expect(request.url.indexOf('https://')).to.equal(0); - expect(request.url).to.equal(`${spec.orbidderHost}/bid`); + expect(request.url).to.equal(`${spec.hostname}/bid`); + }); + + it('contains prebid version parameter', () => { + expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version); }); it('sends correct bid parameters', () => { - // we add one, because we add referer information from bidderRequest object - expect(Object.keys(request.data).length).to.equal(Object.keys(defaultBidRequest).length + 1); + // we add two, because we add referer information and version from bidderRequest object + expect(Object.keys(request.data).length).to.equal(Object.keys(defaultBidRequest).length + 2); expect(request.data.pageUrl).to.equal('https://localhost:9876/'); // expect(request.data.referrer).to.equal(''); Object.keys(defaultBidRequest).forEach((key) => { @@ -149,39 +151,6 @@ describe('orbidderBidAdapter', () => { }); }); - describe('onCallbackHandler', () => { - let ajaxStub; - const bidObj = { - adId: 'testId', - test: 1, - pageUrl: 'www.someurl.de', - referrer: 'www.somereferrer.de', - requestId: '123req456' - }; - - spec.bidParams['123req456'] = {'accountId': '123acc456'}; - - let bidObjClone = deepClone(bidObj); - bidObjClone.pageUrl = detectReferer(window)().referer; - bidObjClone.params = [{'accountId': '123acc456'}]; - - beforeEach(() => { - ajaxStub = sinon.stub(spec, 'ajaxCall'); - }); - - afterEach(() => { - ajaxStub.restore(); - }); - - it('calls orbidder\'s callback endpoint', () => { - spec.onBidWon(bidObj); - expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0].indexOf('https://')).to.equal(0); - expect(ajaxStub.firstCall.args[0]).to.equal(`${spec.orbidderHost}/win`); - expect(ajaxStub.firstCall.args[1]).to.equal(JSON.stringify(bidObjClone)); - }); - }); - describe('interpretResponse', () => { it('should get correct bid response', () => { const serverResponse = [ diff --git a/test/spec/modules/otmBidAdapter_spec.js b/test/spec/modules/otmBidAdapter_spec.js index f3a98d43e57..8ac01c1657e 100644 --- a/test/spec/modules/otmBidAdapter_spec.js +++ b/test/spec/modules/otmBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/otmBidAdapter'; +import {spec} from 'modules/otmBidAdapter.js'; describe('otmBidAdapterTests', function () { it('validate_pub_params', function () { diff --git a/test/spec/modules/outconBidAdapter_spec.js b/test/spec/modules/outconBidAdapter_spec.js index d9e763b9df9..81c3fdded62 100644 --- a/test/spec/modules/outconBidAdapter_spec.js +++ b/test/spec/modules/outconBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/outconBidAdapter'; +import { spec } from '../../../modules/outconBidAdapter.js'; describe('outconBidAdapter', function () { describe('bidRequestValidity', function () { diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index 4ea3474d2b9..f20e75dfedd 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -1,7 +1,9 @@ import { expect } from 'chai'; -import { spec, getWidthAndHeightFromVideoObject, playerSizeIsNestedArray, defaultSize } from 'modules/ozoneBidAdapter'; -import { config } from 'src/config'; -import {Renderer} from '../../../src/Renderer'; +import { spec, getWidthAndHeightFromVideoObject, playerSizeIsNestedArray, defaultSize } from 'modules/ozoneBidAdapter.js'; +import { config } from 'src/config.js'; +import {Renderer} from '../../../src/Renderer.js'; +import {getGranularityKeyName, getGranularityObject} from '../../../modules/ozoneBidAdapter.js'; +import * as utils from '../../../src/utils.js'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const BIDDER_CODE = 'ozone'; /* @@ -9,6 +11,7 @@ const BIDDER_CODE = 'ozone'; NOTE - use firefox console to deep copy the objects to use here */ +var originalPropertyBag = {'lotameWasOverridden': 0, 'pageId': null}; var validBidRequests = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -18,11 +21,54 @@ var validBidRequests = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequestsMulti = [ + { + testId: 1, + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }, + { + testId: 2, + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff0', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c0', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsWithUserIdData = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87', + userId: {'pubcid': '12345678', 'id5id': 'ID5-someId', 'criteortus': {'ozone': {'userid': 'critId123'}}, 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, 'parrableId': {eid: 'parrableid123'}} + } +]; var validBidRequestsMinimal = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -45,7 +91,7 @@ var validBidRequestsNoSizes = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; @@ -59,7 +105,7 @@ var validBidRequestsWithBannerMediaType = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } @@ -73,90 +119,415 @@ var validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, video: {skippable: true, playback_method: ['auto_play_sound_off'], targetDiv: 'some-different-div-id-to-my-adunitcode'} } ] }, - mediaTypes: {video: {mimes: ['video/mp4'], 'context': 'outstream', 'sizes': [640, 480]}, native: {info: 'dummy data'}}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, video: {skippable: true, playback_method: ['auto_play_sound_off'], targetDiv: 'some-different-div-id-to-my-adunitcode'} } ] }, + mediaTypes: {video: {mimes: ['video/mp4'], 'context': 'outstream', 'sizes': [640, 480], playerSize: [640, 480]}, native: {info: 'dummy data'}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequests1OutstreamVideo2020 = [ + { + 'bidder': 'ozone', + 'testname': 'validBidRequests1OutstreamVideo2020', + 'params': { + 'publisherId': 'OZONERUP0001', + 'placementId': '8000000009', + 'siteId': '4204204201', + 'video': { + 'skippable': true, + 'playback_method': [ + 'auto_play_sound_off' + ] + }, + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ], + 'lotameData': { + 'Profile': { + 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', + 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', + 'Audiences': { + 'Audience': [ + { + 'id': '439847', + 'abbr': 'all' + }, + { + 'id': '446197', + 'abbr': 'Arts, Culture & Literature' + }, + { + 'id': '446198', + 'abbr': 'Business' + } + ] + } + } + } + }, + 'userId': { + 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + }, + 'userIdAsEids': [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', + 'atype': 1 + } + ] + } + ], + 'mediaTypes': { + 'video': { + 'playerSize': [ + [ + 640, + 480 + ] + ], + 'mimes': [ + 'video/mp4' + ], + 'context': 'outstream' + } + }, + 'adUnitCode': 'video-ad', + 'transactionId': '02c1ea7d-0bf2-451b-a122-1420040d1cf8', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '2899ec066a91ff8', + 'bidderRequestId': '1c1586b27a1b5c8', + 'auctionId': '0456c9b7-5ab2-4fec-9e10-f418d3d1f04c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } +]; + +// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest +var validBidderRequest1OutstreamVideo2020 = { + bidderRequest: { + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + auctionStart: 1536838908986, + bidderCode: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + bids: [ + { + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONERUP0001', + 'placementId': '8000000009', + 'siteId': '4204204201', + 'video': { + 'skippable': true, + 'playback_method': [ + 'auto_play_sound_off' + ] + }, + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 'uayf5jmv3', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ], + 'lotameData': { + 'Profile': { + 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', + 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', + 'Audiences': { + 'Audience': [ + { + 'id': '439847', + 'abbr': 'all' + }, + { + 'id': '446197', + 'abbr': 'Arts, Culture & Literature' + }, + { + 'id': '446198', + 'abbr': 'Business' + } + ] + } + } + } + }, + 'userId': { + 'id5id': 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA', + 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + }, + 'userIdAsEids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA', + 'atype': 1 + } + ] + }, + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', + 'atype': 1 + } + ] + } + ], + 'mediaTypes': { + 'video': { + 'playerSize': [ + [ + 640, + 480 + ] + ], + 'mimes': [ + 'video/mp4' + ], + 'context': 'outstream' + } + }, + 'adUnitCode': 'video-ad', + 'transactionId': 'ec20cc65-de38-4410-b5b3-50de5b7df66a', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '2899ec066a91ff8', + 'bidderRequestId': '1c1586b27a1b5c8', + 'auctionId': '0456c9b7-5ab2-4fec-9e10-f418d3d1f04c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000 + } +}; +// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var validBidderRequest = { - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - auctionStart: 1536838908986, - bidderCode: 'ozone', - bidderRequestId: '1c1586b27a1b5c8', - bids: [{ - adUnitCode: 'div-gpt-ad-1460505748561-0', + bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'ozone', + auctionStart: 1536838908986, + bidderCode: 'ozone', bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - }], - doneCbCallCount: 1, - start: 1536838908987, - timeout: 3000 + bids: [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000 + } }; // bidder request with GDPR - change the values for testing: // gdprConsent.gdprApplies (true/false) // gdprConsent.vendorData.purposeConsents (make empty, make null, remove it) // gdprConsent.vendorData.vendorConsents (remove 524, remove all, make the element null, remove it) +// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var bidderRequestWithFullGdpr = { - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - auctionStart: 1536838908986, - bidderCode: 'ozone', - bidderRequestId: '1c1586b27a1b5c8', - bids: [{ - adUnitCode: 'div-gpt-ad-1460505748561-0', + bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'ozone', + auctionStart: 1536838908986, + bidderCode: 'ozone', bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - }], - doneCbCallCount: 1, - start: 1536838908987, - timeout: 3000, - gdprConsent: { - 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', - 'vendorData': { - 'metadata': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', - 'gdprApplies': true, - 'hasGlobalScope': false, - 'cookieVersion': '1', - 'created': '2019-05-31T12:46:48.825', - 'lastUpdated': '2019-05-31T12:46:48.825', - 'cmpId': '28', - 'cmpVersion': '1', - 'consentLanguage': 'en', - 'consentScreen': '1', - 'vendorListVersion': 148, - 'maxVendorId': 631, - 'purposeConsents': { - '1': true, - '2': true, - '3': true, - '4': true, - '5': true + bids: [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000, + gdprConsent: { + 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'vendorData': { + 'metadata': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true, + 'hasGlobalScope': false, + 'cookieVersion': '1', + 'created': '2019-05-31T12:46:48.825', + 'lastUpdated': '2019-05-31T12:46:48.825', + 'cmpId': '28', + 'cmpVersion': '1', + 'consentLanguage': 'en', + 'consentScreen': '1', + 'vendorListVersion': 148, + 'maxVendorId': 631, + 'purposeConsents': { + '1': true, + '2': true, + '3': true, + '4': true, + '5': true + }, + 'vendorConsents': { + '468': true, + '522': true, + '524': true, /* 524 is ozone */ + '565': true, + '591': true + } }, - 'vendorConsents': { - '468': true, - '522': true, - '524': true, /* 524 is ozone */ - '565': true, - '591': true - } + 'gdprApplies': true + }, } +}; + +var gdpr1 = { + 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'vendorData': { + 'metadata': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true, + 'hasGlobalScope': false, + 'cookieVersion': '1', + 'created': '2019-05-31T12:46:48.825', + 'lastUpdated': '2019-05-31T12:46:48.825', + 'cmpId': '28', + 'cmpVersion': '1', + 'consentLanguage': 'en', + 'consentScreen': '1', + 'vendorListVersion': 148, + 'maxVendorId': 631, + 'purposeConsents': { + '1': true, + '2': true, + '3': true, + '4': true, + '5': true }, - 'gdprApplies': true + 'vendorConsents': { + '468': true, + '522': true, + '524': true, /* 524 is ozone */ + '565': true, + '591': true + } }, + 'gdprApplies': true +}; + +// simulating the Mirror +var bidderRequestWithPartialGdpr = { + bidderRequest: { + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + auctionStart: 1536838908986, + bidderCode: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + bids: [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { + publisherId: '9876abcd12-3', + customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], + lotameData: { + 'Profile': { + 'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', + 'Audiences': { + 'Audience': [{'id': '99999', 'abbr': 'sports'}, { + 'id': '88888', + 'abbr': 'movie' + }, {'id': '77777', 'abbr': 'blogger'}] + } + } + }, + placementId: '1310000099', + siteId: '1234567890', + id: 'fea37168-78f1-4a23-a40e-88437a99377e', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + imp: [{ + banner: {topframe: 1, w: 300, h: 250, format: [{w: 300, h: 250}, {w: 300, h: 600}]}, + id: '2899ec066a91ff8', + secure: 1, + tagid: 'undefined' + }] + }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000, + gdprConsent: { + 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true, + 'vendorData': { + 'metadata': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true + } + } + } }; // make sure the impid matches the request bidId @@ -170,10 +541,10 @@ var validResponse = { 'id': '677903815252395017', 'impid': '2899ec066a91ff8', 'price': 0.5, - 'adm': '', + 'adm': '', 'adid': '98493581', 'adomain': [ - 'https://prebid.org' + 'http://prebid.org' ], 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', 'cid': '9325', @@ -201,6 +572,7 @@ var validResponse = { 'seat': 'appnexus' } ], + 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ 'ext': { 'responsetimemillis': { 'appnexus': 47, @@ -215,7 +587,8 @@ var validResponse = { }, 'headers': {} }; -var validOutstreamResponse = { + +var validResponse2Bids = { 'body': { 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', 'seatbid': [ @@ -225,10 +598,10 @@ var validOutstreamResponse = { 'id': '677903815252395017', 'impid': '2899ec066a91ff8', 'price': 0.5, - 'adm': '', + 'adm': '', 'adid': '98493581', 'adomain': [ - 'https://prebid.org' + 'http://prebid.org' ], 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', 'cid': '9325', @@ -240,28 +613,53 @@ var validOutstreamResponse = { 'h': 600, 'ext': { 'prebid': { - 'type': 'video' + 'type': 'banner' }, 'bidder': { - 'unruly': { - 'renderer': { - 'config': { - 'targetingUUID': 'aafd3388-afaf-41f4-b271-0ac8e0325a7f', - 'siteId': 1052815, - 'featureOverrides': {} - }, - 'url': 'https://video.unrulymedia.com/native/native-loader.js#supplyMode=prebid?cb=6284685353877994', - 'id': 'unruly_inarticle' - }, - 'vast_url': 'data:text/xml;base64,PD94bWwgdmVyc2lvbj0i' + 'appnexus': { + 'brand_id': 555545, + 'auction_id': 6500448734132353000, + 'bidder_id': 2, + 'bid_ad_type': 0 } } } - } - ], - 'seat': 'unruly' + }, + { + 'id': '677903815252395010', + 'impid': '2899ec066a91ff0', + 'price': 0.9, + 'adm': '', + 'adid': '98493580', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', + 'cid': '9320', + 'crid': '98493580', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 555540, + 'auction_id': 6500448734132353000, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } ], + 'seat': 'appnexus' } ], + 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ 'ext': { 'responsetimemillis': { 'appnexus': 47, @@ -276,6 +674,189 @@ var validOutstreamResponse = { }, 'headers': {} }; +/* +A bidder returns a bid for both sizes in an adunit + */ +var validResponse2BidsSameAdunit = { + 'body': { + 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', + 'seatbid': [ + { + 'bid': [ + { + 'id': '677903815252395017', + 'impid': '2899ec066a91ff8', + 'price': 0.5, + 'adm': '', + 'adid': '98493581', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', + 'cid': '9325', + 'crid': '98493581', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 555545, + 'auction_id': 6500448734132353000, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + }, + { + 'id': '677903815252395010', + 'impid': '2899ec066a91ff8', + 'price': 0.9, + 'adm': '', + 'adid': '98493580', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', + 'cid': '9320', + 'crid': '98493580', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 555540, + 'auction_id': 6500448734132353000, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } ], + 'seat': 'ozappnexus' + } + ], + 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ + 'ext': { + 'responsetimemillis': { + 'appnexus': 47, + 'openx': 30 + } + }, + 'timing': { + 'start': 1536848078.089177, + 'end': 1536848078.142203, + 'TimeTaken': 0.05302619934082031 + } + }, + 'headers': {} +}; +/* + +SPECIAL CONSIDERATION FOR VIDEO TESTS: + +DO NOT USE _validVideoResponse directly - the interpretResponse function will modify it (adding a renderer!!!) so all +subsequent calls will already have a renderer attached!!! + +*/ +function getCleanValidVideoResponse() { + return JSON.parse(JSON.stringify(_validVideoResponse)); +} +var _validVideoResponse = { + 'body': { + 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', + 'seatbid': [ + { + 'bid': [ + { + 'id': '2899ec066a91ff8', + 'impid': '2899ec066a91ff8', + 'price': 31.7, + 'adm': '', + 'adomain': [ + 'sarr.properties' + ], + 'crid': 'ozone-655', + 'cat': [ + 'IAB21' + ], + 'w': 640, + 'h': 360, + 'ext': { + 'prebid': { + 'type': 'video' + } + }, + 'adId': '2899ec066a91ff8-2', + 'cpm': 31.7, + 'bidId': '2899ec066a91ff8', + 'requestId': '2899ec066a91ff8', + 'width': 640, + 'height': 360, + 'ad': '', + 'netRevenue': true, + 'creativeId': 'ozone-655', + 'currency': 'USD', + 'ttl': 300, + 'adserverTargeting': { + 'oz_ozbeeswax': 'ozbeeswax', + 'oz_ozbeeswax_pb': '31.7', + 'oz_ozbeeswax_crid': 'ozone-655', + 'oz_ozbeeswax_adv': 'sarr.properties', + 'oz_ozbeeswax_imp_id': '49d16ccc28663a8', + 'oz_ozbeeswax_adId': '49d16ccc28663a8-2', + 'oz_ozbeeswax_pb_r': '20.00', + 'oz_ozbeeswax_omp': '1', + 'oz_ozbeeswax_vid': 'outstream', + 'oz_auc_id': 'efa7fea0-7e87-4811-be86-fefb38c35fbb', + 'oz_winner': 'ozbeeswax', + 'oz_response_id': 'efa7fea0-7e87-4811-be86-fefb38c35fbb', + 'oz_winner_auc_id': '49d16ccc28663a8', + 'oz_winner_imp_id': '49d16ccc28663a8', + 'oz_pb_v': '2.4.0', + 'hb_bidder': 'ozone', + 'hb_adid': '49d16ccc28663a8-2', + 'hb_pb': '20.00', + 'hb_size': '640x360', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'originalCpm': 31.7, + 'originalCurrency': 'USD' + } + ], + 'seat': 'ozbeeswax' + } + ], + 'ext': { + 'responsetimemillis': { + 'beeswax': 9, + 'openx': 43, + 'ozappnexus': 31, + 'ozbeeswax': 7 + } + }, + 'timing': { + 'start': 1536848078.089177, + 'end': 1536848078.142203, + 'TimeTaken': 0.05302619934082031 + } + }, + 'headers': {} +}; + var validBidResponse1adWith2Bidders = { 'body': { 'id': '91221f96-b931-4acc-8f05-c2a1186fa5ac', @@ -320,53 +901,670 @@ var validBidResponse1adWith2Bidders = { }, 'bidder': { 'appnexus': { - 'brand_id': 14640, - 'auction_id': 1.8369641905139e+18, + 'brand_id': 14640, + 'auction_id': 1.8369641905139e+18, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'appnexus' + }, + { + 'bid': [ + { + 'id': '75665207-a1ca-49db-ba0e-a5e9c7d26f32', + 'impid': '37fff511779365a', + 'price': 1.046, + 'adm': '
removed
', + 'adomain': [ + 'kx.com' + ], + 'crid': '13005', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + } + } + } + ], + 'seat': 'openx' + } + ], + 'ext': { + 'responsetimemillis': { + 'appnexus': 91, + 'openx': 109, + 'ozappnexus': 46, + 'ozbeeswax': 2, + 'pangaea': 91 + } + } + }, + 'headers': {} +}; + +/* +testing 2 ads, 2 bidders, one bidder bids for both slots in one adunit + */ + +var multiRequest1 = [ + { + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONERUP0001', + 'siteId': '4204204201', + 'placementId': '0420420421', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 'uayf5jmv3', + 't8nyiude5', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ], + 'lotameData': { + 'Profile': { + 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', + 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', + 'Audiences': { + 'Audience': [ + { + 'id': '439847', + 'abbr': 'all' + }, + { + 'id': '446197', + 'abbr': 'Arts, Culture & Literature' + }, + { + 'id': '446198', + 'abbr': 'Business' + } + ] + } + } + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu', + 'transactionId': '6480bac7-31b5-4723-9145-ad8966660651', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2d30e86db743a8', + 'bidderRequestId': '1d03a1dfc563fc', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + { + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONERUP0001', + 'siteId': '4204204201', + 'placementId': '0420420421', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 't8nxz6qzd', + 't8nyiude5', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ], + 'lotameData': { + 'Profile': { + 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', + 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', + 'Audiences': { + 'Audience': [ + { + 'id': '439847', + 'abbr': 'all' + }, + { + 'id': '446197', + 'abbr': 'Arts, Culture & Literature' + }, + { + 'id': '446198', + 'abbr': 'Business' + } + ] + } + } + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ] + } + }, + 'adUnitCode': 'leaderboard', + 'transactionId': 'a49988e6-ae7c-46c4-9598-f18db49892a0', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ], + 'bidId': '3025f169863b7f8', + 'bidderRequestId': '1d03a1dfc563fc', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } +]; + +// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest +var multiBidderRequest1 = { + bidderRequest: { + 'bidderCode': 'ozone', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'bidderRequestId': '1d03a1dfc563fc', + 'bids': [ + { + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONERUP0001', + 'siteId': '4204204201', + 'placementId': '0420420421', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'txeh7uyo0', + 't8nxz6qzd', + 't8nyiude5', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ], + 'lotameData': { + 'Profile': { + 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', + 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', + 'Audiences': { + 'Audience': [ + { + 'id': '439847', + 'abbr': 'all' + }, + { + 'id': '446197', + 'abbr': 'Arts, Culture & Literature' + }, + { + 'id': '446198', + 'abbr': 'Business' + } + ] + } + } + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu', + 'transactionId': '6480bac7-31b5-4723-9145-ad8966660651', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2d30e86db743a8', + 'bidderRequestId': '1d03a1dfc563fc', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + { + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONERUP0001', + 'siteId': '4204204201', + 'placementId': '0420420421', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 't8nxz6qzd', + 't8nyiude5', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ], + 'lotameData': { + 'Profile': { + 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', + 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', + 'Audiences': { + 'Audience': [ + { + 'id': '439847', + 'abbr': 'all' + }, + { + 'id': '446197', + 'abbr': 'Arts, Culture & Literature' + }, + { + 'id': '446198', + 'abbr': 'Business' + } + ] + } + } + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ] + } + }, + 'adUnitCode': 'leaderboard', + 'transactionId': 'a49988e6-ae7c-46c4-9598-f18db49892a0', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ], + 'bidId': '3025f169863b7f8', + 'bidderRequestId': '1d03a1dfc563fc', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1592918645574, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'http://ozone.ardm.io/adapter/2.4.0/620x350-switch.html?guardian=true&pbjs_debug=true', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'http://ozone.ardm.io/adapter/2.4.0/620x350-switch.html?guardian=true&pbjs_debug=true' + ] + }, + 'gdprConsent': { + 'consentString': 'BOvy5sFO1dBa2AKAiBENDP-AAAAwVrv7_77-_9f-_f__9uj3Gr_v_f__32ccL5tv3h_7v-_7fi_-0nV4u_1tft9ydk1-5ctDztp507iakiPHmqNeb9n_mz1eZpRP58E09j53z7Ew_v8_v-b7BCPN_Y3v-8K96kA', + 'vendorData': { + 'metadata': 'BOvy5sFO1dBa2AKAiBENDPA', + 'gdprApplies': true, + 'hasGlobalConsent': false, + 'hasGlobalScope': false, + 'purposeConsents': { + '1': true, + '2': true, + '3': true, + '4': true, + '5': true + }, + 'vendorConsents': { + '1': true, + '2': true, + '3': false, + '4': true, + '5': true + } + }, + 'gdprApplies': true + }, + 'start': 1592918645578 + } +}; + +var multiResponse1 = { + 'body': { + 'id': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'seatbid': [ + { + 'bid': [ + { + 'id': '4419718600113204943', + 'impid': '2d30e86db743a8', + 'price': 0.2484, + 'adm': '', + 'adid': '119683582', + 'adomain': [ + 'https://ozoneproject.com' + ], + 'iurl': 'https://ams1-ib.adnxs.com/cr?id=119683582', + 'cid': '9979', + 'crid': '119683582', + 'cat': [ + 'IAB3' + ], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'ozone': {}, + 'appnexus': { + 'brand_id': 734921, + 'auction_id': 2995348111857539600, 'bidder_id': 2, 'bid_ad_type': 0 } } - } + }, + 'cpm': 0.2484, + 'bidId': '2d30e86db743a8', + 'requestId': '2d30e86db743a8', + 'width': 300, + 'height': 250, + 'ad': '', + 'netRevenue': true, + 'creativeId': '119683582', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.2484, + 'originalCurrency': 'USD' + }, + { + 'id': '18552976939844681', + 'impid': '3025f169863b7f8', + 'price': 0.0621, + 'adm': '', + 'adid': '120179216', + 'adomain': [ + 'appnexus.com' + ], + 'iurl': 'https://ams1-ib.adnxs.com/cr?id=120179216', + 'cid': '9979', + 'crid': '120179216', + 'w': 970, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'ozone': {}, + 'appnexus': { + 'brand_id': 1, + 'auction_id': 3449036134472542700, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + }, + 'cpm': 0.0621, + 'bidId': '3025f169863b7f8', + 'requestId': '3025f169863b7f8', + 'width': 970, + 'height': 250, + 'ad': '', + 'netRevenue': true, + 'creativeId': '120179216', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.0621, + 'originalCurrency': 'USD' + }, + { + 'id': '18552976939844999', + 'impid': '3025f169863b7f8', + 'price': 0.521, + 'adm': '', + 'adid': '120179216', + 'adomain': [ + 'appnexus.com' + ], + 'iurl': 'https://ams1-ib.adnxs.com/cr?id=120179216', + 'cid': '9999', + 'crid': '120179299', + 'w': 728, + 'h': 90, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'ozone': {}, + 'appnexus': { + 'brand_id': 1, + 'auction_id': 3449036134472542700, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + }, + 'cpm': 0.521, + 'bidId': '3025f169863b7f8', + 'requestId': '3025f169863b7f8', + 'width': 728, + 'height': 90, + 'ad': '', + 'netRevenue': true, + 'creativeId': '120179299', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.0621, + 'originalCurrency': 'USD' } ], - 'seat': 'appnexus' + 'seat': 'ozappnexus' }, { 'bid': [ { - 'id': '75665207-a1ca-49db-ba0e-a5e9c7d26f32', - 'impid': '37fff511779365a', - 'price': 1.046, - 'adm': '
removed
', - 'adomain': [ - 'kx.com' - ], - 'crid': '13005', + 'id': '1c605e8a-4992-4ec6-8a5c-f82e2938c2db', + 'impid': '2d30e86db743a8', + 'price': 0.01, + 'adm': '
', + 'crid': '540463358', 'w': 300, 'h': 250, 'ext': { 'prebid': { 'type': 'banner' + }, + 'bidder': { + 'ozone': {} } - } + }, + 'cpm': 0.01, + 'bidId': '2d30e86db743a8', + 'requestId': '2d30e86db743a8', + 'width': 300, + 'height': 250, + 'ad': '
', + 'netRevenue': true, + 'creativeId': '540463358', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.01, + 'originalCurrency': 'USD' + }, + { + 'id': '3edeb4f7-d91d-44e2-8aeb-4a2f6d295ce5', + 'impid': '3025f169863b7f8', + 'price': 0.01, + 'adm': '
', + 'crid': '540221061', + 'w': 970, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'ozone': {} + } + }, + 'cpm': 0.01, + 'bidId': '3025f169863b7f8', + 'requestId': '3025f169863b7f8', + 'width': 970, + 'height': 250, + 'ad': '
', + 'netRevenue': true, + 'creativeId': '540221061', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.01, + 'originalCurrency': 'USD' } ], 'seat': 'openx' } ], 'ext': { + 'debug': {}, 'responsetimemillis': { - 'appnexus': 91, - 'openx': 109, - 'ozappnexus': 46, - 'ozbeeswax': 2, - 'pangaea': 91 + 'beeswax': 6, + 'openx': 91, + 'ozappnexus': 40, + 'ozbeeswax': 6 } } }, 'headers': {} }; +/* +--------------------end of 2 slots, 2 ---------------------------- + */ + describe('ozone Adapter', function () { describe('isBidRequestValid', function () { // A test ad unit that will consistently return test creatives @@ -390,8 +1588,8 @@ describe('ozone Adapter', function () { placementId: '1310000099', publisherId: '9876abcd12-3', siteId: '1234567890', - customData: {'gender': 'bart', 'age': 'low'}, - lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, + customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], + lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, }, siteId: 1234567890 } @@ -615,67 +1813,88 @@ describe('ozone Adapter', function () { } }; - it('should not validate customData not being an object', function () { + it('should not validate customData not being an array', function () { expect(spec.isBidRequestValid(xBadCustomData)).to.equal(false); }); - var xCustomParams = { + var xBadCustomData_OLD_CUSTOMDATA_VALUE = { bidder: BIDDER_CODE, params: { 'placementId': '1234567890', 'publisherId': '9876abcd12-3', - 'customParams': {'info': 'this is not allowed'}, - siteId: '1234567890' + 'siteId': '1234567890', + 'customData': {'gender': 'bart', 'age': 'low'} } }; - it('should not validate customParams being sent', function () { - expect(spec.isBidRequestValid(xCustomParams)).to.equal(false); + it('should not validate customData being an object, not an array', function () { + expect(spec.isBidRequestValid(xBadCustomData_OLD_CUSTOMDATA_VALUE)).to.equal(false); }); - var xBadCustomData = { + var xBadCustomData_zerocd = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1111111110', + 'publisherId': '9876abcd12-3', + 'siteId': '1234567890', + 'customData': [] + } + }; + + it('should not validate customData array having no elements', function () { + expect(spec.isBidRequestValid(xBadCustomData_zerocd)).to.equal(false); + }); + + var xBadCustomData_notargeting = { bidder: BIDDER_CODE, params: { 'placementId': '1234567890', 'publisherId': '9876abcd12-3', - 'customData': 'this should be an object', + 'customData': [{'settings': {}, 'xx': {'gender': 'bart', 'age': 'low'}}], siteId: '1234567890' } }; - it('should not validate ozoneData being sent', function () { - expect(spec.isBidRequestValid(xBadCustomData)).to.equal(false); + it('should not validate customData[] having no "targeting"', function () { + expect(spec.isBidRequestValid(xBadCustomData_notargeting)).to.equal(false); }); - var xBadLotame = { + var xBadCustomData_tgt_not_obj = { bidder: BIDDER_CODE, params: { 'placementId': '1234567890', 'publisherId': '9876abcd12-3', - 'lotameData': 'this should be an object', + 'customData': [{'settings': {}, 'targeting': 'this should be an object'}], siteId: '1234567890' } }; - it('should not validate lotameData being sent', function () { - expect(spec.isBidRequestValid(xBadLotame)).to.equal(false); + it('should not validate customData[0].targeting not being an object', function () { + expect(spec.isBidRequestValid(xBadCustomData_tgt_not_obj)).to.equal(false); }); - var xBadVideoContext = { + var xBadCustomParams = { bidder: BIDDER_CODE, params: { 'placementId': '1234567890', 'publisherId': '9876abcd12-3', - 'lotameData': {}, - siteId: '1234567890' - }, - mediaTypes: { - video: { - mimes: ['video/mp4'], - 'context': 'instream'}, + 'siteId': '1234567890', + 'customParams': 'this key is no longer valid' } }; + it('should not validate customParams - this is a renamed key', function () { + expect(spec.isBidRequestValid(xBadCustomParams)).to.equal(false); + }); - it('should not validate video instream being sent', function () { - expect(spec.isBidRequestValid(xBadVideoContext)).to.equal(false); + var xBadLotame = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'lotameData': 'this should be an object', + siteId: '1234567890' + } + }; + it('should not validate lotameData being sent', function () { + expect(spec.isBidRequestValid(xBadLotame)).to.equal(false); }); var xBadVideoContext2 = { @@ -713,31 +1932,65 @@ describe('ozone Adapter', function () { it('should validate video outstream being sent', function () { expect(spec.isBidRequestValid(validVideoBidReq)).to.equal(true); }); + it('should validate video instream being sent even though its not properly supported yet', function () { + let instreamVid = JSON.parse(JSON.stringify(validVideoBidReq)); + instreamVid.mediaTypes.video.context = 'instream'; + expect(spec.isBidRequestValid(instreamVid)).to.equal(true); + }); + // validate lotame override parameters + it('should validate lotame override params', function () { + // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: + spec.getGetParametersAsObject = function() { + return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': 'tpid123'}; + }; + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); + it('should validate missing lotame override params', function () { + // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: + spec.getGetParametersAsObject = function() { + return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123'}; + }; + expect(spec.isBidRequestValid(validBidReq)).to.equal(false); + }); + it('should validate invalid lotame override params', function () { + // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: + spec.getGetParametersAsObject = function() { + return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123 "this ain\\t right!" eee'}; + }; + expect(spec.isBidRequestValid(validBidReq)).to.equal(false); + }); + it('should validate no lotame override params', function () { + // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: + spec.getGetParametersAsObject = function() { + return {}; + }; + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); }); describe('buildRequests', function () { it('sends bid request to OZONEURI via POST', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.url).to.equal(OZONEURI); expect(request.method).to.equal('POST'); }); it('sends data as a string', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); }); it('sends all bid parameters', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); it('adds all parameters inside the ext object only', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); var data = JSON.parse(request.data); - expect(data.imp[0].ext.ozone.lotameData).to.be.an('object'); - expect(data.imp[0].ext.ozone.customData).to.be.an('object'); + expect(data.ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(request).not.to.have.key('lotameData'); expect(request).not.to.have.key('customData'); }); @@ -745,44 +1998,44 @@ describe('ozone Adapter', function () { it('ignores ozoneData in & after version 2.1.1', function () { let validBidRequestsWithOzoneData = validBidRequests; validBidRequestsWithOzoneData[0].params.ozoneData = {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}; - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); var data = JSON.parse(request.data); - expect(data.imp[0].ext.ozone.lotameData).to.be.an('object'); - expect(data.imp[0].ext.ozone.customData).to.be.an('object'); + expect(data.ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.ozoneData).to.be.undefined; expect(request).not.to.have.key('lotameData'); expect(request).not.to.have.key('customData'); }); it('has correct bidder', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.bidderRequest.bids[0].bidder).to.equal(BIDDER_CODE); }); it('handles mediaTypes element correctly', function () { - const request = spec.buildRequests(validBidRequestsWithBannerMediaType, validBidderRequest); + const request = spec.buildRequests(validBidRequestsWithBannerMediaType, validBidderRequest.bidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); it('handles no ozone, lotame or custom data', function () { - const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest); + const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); it('handles video mediaType element correctly, with outstream video', function () { - const request = spec.buildRequests(validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo, validBidderRequest); + const request = spec.buildRequests(validBidRequests1OutstreamVideo2020, validBidderRequest.bidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); it('should not crash when there is no sizes element at all', function () { - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); it('should be able to handle non-single requests', function () { config.setConfig({'ozone': {'singleRequest': false}}); - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); expect(request).to.be.a('array'); expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); // need to reset the singleRequest config flag: @@ -791,11 +2044,13 @@ describe('ozone Adapter', function () { it('should add gdpr consent information to the request when ozone is true', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = validBidderRequest.bidderRequest; bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: true, vendorData: { + metadata: consentString, + gdprApplies: true, vendorConsents: {524: true}, purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} } @@ -804,36 +2059,36 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); const payload = JSON.parse(request.data); expect(payload.regs.ext.gdpr).to.equal(1); - expect(payload.regs.ext.oz_con).to.exist.and.to.equal(1); - expect(payload.regs.ext.gap).to.exist.and.to.be.an('array').and.to.eql([1, 2, 3, 4, 5]); + expect(payload.user.ext.consent).to.equal(consentString); }); - it('should add correct gdpr consent information to the request when user has accepted only some purpose consents', function () { + // mirror + it('should add gdpr consent information to the request when vendorData is missing vendorConsents (Mirror)', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = validBidderRequest.bidderRequest; bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: true, vendorData: { - vendorConsents: {524: true}, - purposeConsents: {1: true, 4: true, 5: true} + metadata: consentString, + gdprApplies: true } } const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); const payload = JSON.parse(request.data); expect(payload.regs.ext.gdpr).to.equal(1); - expect(payload.regs.ext.oz_con).to.exist.and.to.equal(1); - expect(payload.regs.ext.gap).to.exist.and.to.be.an('array').and.to.eql([1, 4, 5]); + expect(payload.user.ext.consent).to.equal(consentString); }); - - it('should add gdpr consent information to the request when ozone is false', function () { + it('should set regs.ext.gdpr flag to 0 when gdprApplies is false', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = validBidderRequest.bidderRequest; bidderRequest.gdprConsent = { consentString: consentString, - gdprApplies: true, + gdprApplies: false, vendorData: { + metadata: consentString, + gdprApplies: true, vendorConsents: {}, /* 524 is not present */ purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} } @@ -841,40 +2096,252 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); const payload = JSON.parse(request.data); - expect(payload.regs.ext.gdpr).to.equal(1); - expect(payload.regs.ext.oz_con).to.exist.and.to.equal(0); - expect(payload.regs.ext.gap).to.exist.and.to.be.an('array').and.to.eql([1, 2, 3, 4, 5]); + expect(payload.regs.ext.gdpr).to.equal(0); }); - it('should set regs.ext.gdpr flag to 0 when gdprApplies is false', function () { + it('should not have imp[N].ext.ozone.userId', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = validBidderRequest.bidderRequest; bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: false, vendorData: { - vendorConsents: {}, /* 524 is not present */ + metadata: consentString, + gdprApplies: true, + vendorConsents: {524: true}, purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} } }; - const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); + let bidRequests = validBidRequests; + // values from http://prebid.org/dev-docs/modules/userId.html#pubcommon-id + bidRequests[0]['userId'] = { + 'criteortus': '1111', + 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + 'id5id': '2222', + 'idl_env': '3333', + 'lipb': {'lipbid': '4444'}, + 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, + 'pubcid': '5555', + 'tdid': '6666' + }; + const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); - expect(payload.regs.ext.gdpr).to.equal(0); - expect(payload.regs.ext.oz_con).to.be.undefined; - expect(payload.regs.ext.gap).to.be.undefined; + let firstBid = payload.imp[0].ext.ozone; + expect(firstBid).to.not.have.property('userId'); + delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests + }); + + it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { + let bidRequests = validBidRequests; + // values from http://prebid.org/dev-docs/modules/userId.html#pubcommon-id + bidRequests[0]['userId'] = { + 'criteortus': '1111', + 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + 'id5id': '2222', + 'idl_env': '3333', + 'lipb': {'lipbid': '4444'}, + 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, + // 'pubcid': '5555', // remove pubcid from here to emulate the OLD module & cause the failover code to kick in + 'tdid': '6666' + }; + const request = spec.buildRequests(bidRequests, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); + delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests + }); + + it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019)', function() { + const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.user).to.exist; + expect(payload.user.ext).to.exist; + expect(payload.user.ext.eids).to.exist; + expect(payload.user.ext.eids[0]['source']).to.equal('pubcid'); + expect(payload.user.ext.eids[0]['uids'][0]['id']).to.equal('12345678'); + expect(payload.user.ext.eids[1]['source']).to.equal('pubcommon'); + expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('12345678'); + expect(payload.user.ext.eids[2]['source']).to.equal('id5-sync.com'); + expect(payload.user.ext.eids[2]['uids'][0]['id']).to.equal('ID5-someId'); + expect(payload.user.ext.eids[3]['source']).to.equal('criteortus'); + expect(payload.user.ext.eids[3]['uids'][0]['id']).to.equal('critId123'); + expect(payload.user.ext.eids[4]['source']).to.equal('liveramp.com'); + expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('liverampId'); + expect(payload.user.ext.eids[5]['source']).to.equal('liveintent.com'); + expect(payload.user.ext.eids[5]['uids'][0]['id']).to.equal('lipbidId123'); + expect(payload.user.ext.eids[6]['source']).to.equal('parrable.com'); + expect(payload.user.ext.eids[6]['uids'][0]['id']).to.equal('parrableid123'); + }); + + it('should use oztestmode GET value if set', function() { + // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: + spec.getGetParametersAsObject = function() { + return {'oztestmode': 'mytestvalue_123'}; + }; + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].ext.ozone.customData).to.be.an('array'); + expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); + }); + it('should use oztestmode GET value if set, even if there is no customdata in config', function() { + // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: + spec.getGetParametersAsObject = function() { + return {'oztestmode': 'mytestvalue_123'}; + }; + const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].ext.ozone.customData).to.be.an('array'); + expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); + }); + var specMock = utils.deepClone(spec); + it('should use a valid ozstoredrequest GET value if set to override the placementId values, and set oz_rw if we find it', function() { + // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: + specMock.getGetParametersAsObject = function() { + return {'ozstoredrequest': '1122334455'}; // 10 digits are valid + }; + const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.ozone.oz_rw).to.equal(1); + expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1122334455'); + }); + it('should NOT use an invalid ozstoredrequest GET value if set to override the placementId values, and set oz_rw to 0', function() { + // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: + specMock.getGetParametersAsObject = function() { + return {'ozstoredrequest': 'BADVAL'}; // 10 digits are valid + }; + const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.ozone.oz_rw).to.equal(0); + expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1310000099'); + }); + + it('should pick up the value of valid lotame override parameters when there is a lotame object', function () { + spec.getGetParametersAsObject = function() { + return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eee'}; + }; + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); + expect(payload.ext.ozone.oz_lot_rw).to.equal(1); + }); + it('should pick up the value of valid lotame override parameters when there is an empty lotame object', function () { + let nolotameBidReq = JSON.parse(JSON.stringify(validBidRequests)); + nolotameBidReq[0].params.lotameData = {}; + spec.getGetParametersAsObject = function() { + return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eeetpid'}; + }; + const request = spec.buildRequests(nolotameBidReq, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); + expect(payload.ext.ozone.lotameData.Profile.tpid).to.equal('123eeetpid'); + expect(payload.ext.ozone.lotameData.Profile.pid).to.equal('pid123'); + expect(payload.ext.ozone.oz_lot_rw).to.equal(1); + }); + it('should pick up the value of valid lotame override parameters when there is NO "lotame" key at all', function () { + let nolotameBidReq = JSON.parse(JSON.stringify(validBidRequests)); + delete (nolotameBidReq[0].params['lotameData']); + spec.getGetParametersAsObject = function() { + return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eeetpid'}; + }; + const request = spec.buildRequests(nolotameBidReq, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); + expect(payload.ext.ozone.lotameData.Profile.tpid).to.equal('123eeetpid'); + expect(payload.ext.ozone.lotameData.Profile.pid).to.equal('pid123'); + expect(payload.ext.ozone.oz_lot_rw).to.equal(1); + spec.propertyBag = originalPropertyBag; // tidy up + }); + // NOTE - only one negative test case; + // you can't send invalid lotame params to buildRequests because 'validate' will have rejected them + it('should not use lotame override parameters if they dont exist', function () { + expect(spec.propertyBag.lotameWasOverridden).to.equal(0); + spec.getGetParametersAsObject = function() { + return {}; // no lotame override params + }; + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.oz_lot_rw).to.equal(0); + }); + + it('should pick up the config value of coppa & set it in the request', function () { + config.setConfig({'coppa': true}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs).to.include.keys('coppa'); + expect(payload.regs.coppa).to.equal(1); + config.resetConfig(); + }); + it('should pick up the config value of coppa & only set it in the request if its true', function () { + config.setConfig({'coppa': false}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'regs.coppa')).to.be.undefined; + config.resetConfig(); + }); + it('should handle oz_omp_floor correctly', function () { + config.setConfig({'ozone': {'oz_omp_floor': 1.56}}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'ext.ozone.oz_omp_floor')).to.equal(1.56); + config.resetConfig(); + }); + it('should ignore invalid oz_omp_floor values', function () { + config.setConfig({'ozone': {'oz_omp_floor': '1.56'}}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'ext.ozone.oz_omp_floor')).to.be.undefined; + config.resetConfig(); + }); + it('should should contain a unique page view id in the auction request which persists across calls', function () { + let request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + let payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'ext.ozone.pv')).to.be.a('string'); + request = spec.buildRequests(validBidRequests1OutstreamVideo2020, validBidderRequest.bidderRequest); + let payload2 = JSON.parse(request.data); + expect(utils.deepAccess(payload2, 'ext.ozone.pv')).to.be.a('string'); + expect(utils.deepAccess(payload2, 'ext.ozone.pv')).to.equal(utils.deepAccess(payload, 'ext.ozone.pv')); + }); + it('should indicate that the whitelist was used when it contains valid data', function () { + config.setConfig({'ozone': {'oz_whitelist_adserver_keys': ['oz_ozappnexus_pb', 'oz_ozappnexus_imp_id']}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.oz_kvp_rw).to.equal(1); + config.resetConfig(); + }); + it('should indicate that the whitelist was not used when it contains no data', function () { + config.setConfig({'ozone': {'oz_whitelist_adserver_keys': []}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.oz_kvp_rw).to.equal(0); + config.resetConfig(); + }); + it('should indicate that the whitelist was not used when it is not set in the config', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.oz_kvp_rw).to.equal(0); + }); + it('should have openrtb video params', function() { + let allowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext']; + const request = spec.buildRequests(validBidRequests1OutstreamVideo2020, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + const vid = (payload.imp[0].video); + const keys = Object.keys(vid); + for (let i = 0; i < keys.length; i++) { + expect(allowed).to.include(keys[i]); + } + expect(payload.imp[0].video.ext).to.include({'context': 'outstream'}); }); }); describe('interpretResponse', function () { it('should build bid array', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.interpretResponse(validResponse, request); expect(result.length).to.equal(1); }); it('should have all relevant fields', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.interpretResponse(validResponse, request); const bid = result[0]; expect(bid.cpm).to.equal(validResponse.body.seatbid[0].bid[0].cpm); @@ -883,13 +2350,19 @@ describe('ozone Adapter', function () { }); it('should build bid array with gdpr', function () { - var validBidderRequestWithGdpr = validBidderRequest; - validBidderRequestWithGdpr.gdprConsent = {'gdprApplies': 1, 'consentString': 'This is the gdpr consent string'}; - const request = spec.buildRequests(validBidRequests, validBidderRequestWithGdpr); + let validBR = JSON.parse(JSON.stringify(bidderRequestWithFullGdpr.bidderRequest)); + validBR.gdprConsent = {'gdprApplies': 1, 'consentString': 'This is the gdpr consent string'}; + const request = spec.buildRequests(validBidRequests, validBR); // works the old way, with GDPR not enforced by default const result = spec.interpretResponse(validResponse, request); expect(result.length).to.equal(1); }); + it('should build bid array with only partial gdpr', function () { + var validBidderRequestWithGdpr = bidderRequestWithPartialGdpr.bidderRequest; + validBidderRequestWithGdpr.gdprConsent = {'gdprApplies': 1, 'consentString': 'This is the gdpr consent string'}; + const request = spec.buildRequests(validBidRequests, validBidderRequestWithGdpr); + }); + it('should fail ok if no seatbid in server response', function () { const result = spec.interpretResponse({}, {}); expect(result).to.be.an('array'); @@ -902,29 +2375,185 @@ describe('ozone Adapter', function () { expect(result).to.be.empty; }); - it('should have video renderer', function () { - const request = spec.buildRequests(validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo, validBidderRequest); - const result = spec.interpretResponse(validOutstreamResponse, request); + it('should have video renderer for outstream video', function () { + const request = spec.buildRequests(validBidRequests1OutstreamVideo2020, validBidderRequest1OutstreamVideo2020.bidderRequest); + const result = spec.interpretResponse(getCleanValidVideoResponse(), validBidderRequest1OutstreamVideo2020); const bid = result[0]; expect(bid.renderer).to.be.an.instanceOf(Renderer); }); + it('should have NO video renderer for instream video', function () { + let instreamRequestsObj = JSON.parse(JSON.stringify(validBidRequests1OutstreamVideo2020)); + instreamRequestsObj[0].mediaTypes.video.context = 'instream'; + let instreamBidderReq = JSON.parse(JSON.stringify(validBidderRequest1OutstreamVideo2020)); + instreamBidderReq.bidderRequest.bids[0].mediaTypes.video.context = 'instream'; + const request = spec.buildRequests(instreamRequestsObj, validBidderRequest1OutstreamVideo2020.bidderRequest); + const result = spec.interpretResponse(getCleanValidVideoResponse(), instreamBidderReq); + const bid = result[0]; + expect(bid.hasOwnProperty('renderer')).to.be.false; + }); + it('should correctly parse response where there are more bidders than ad slots', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.interpretResponse(validBidResponse1adWith2Bidders, request); expect(result.length).to.equal(2); }); + + it('should have a ttl of 600', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(result[0].ttl).to.equal(300); + }); + + it('should handle oz_omp_floor_dollars correctly, inserting 1 as necessary', function () { + config.setConfig({'ozone': {'oz_omp_floor': 0.01}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_omp')).to.equal('1'); + config.resetConfig(); + }); + it('should handle oz_omp_floor_dollars correctly, inserting 0 as necessary', function () { + config.setConfig({'ozone': {'oz_omp_floor': 2.50}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_omp')).to.equal('0'); + config.resetConfig(); + }); + it('should handle missing oz_omp_floor_dollars correctly, inserting nothing', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_omp')).to.be.undefined; + }); + it('should handle ext.bidder.ozone.floor correctly, setting flr & rid as necessary', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + let vres = JSON.parse(JSON.stringify(validResponse)); + vres.body.seatbid[0].bid[0].ext.bidder.ozone = {floor: 1, ruleId: 'ZjbsYE1q'}; + const result = spec.interpretResponse(vres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_flr')).to.equal(1); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_rid')).to.equal('ZjbsYE1q'); + config.resetConfig(); + }); + it('should handle ext.bidder.ozone.floor correctly, inserting 0 as necessary', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + let vres = JSON.parse(JSON.stringify(validResponse)); + vres.body.seatbid[0].bid[0].ext.bidder.ozone = {floor: 0, ruleId: 'ZjbXXE1q'}; + const result = spec.interpretResponse(vres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_flr')).to.equal(0); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_rid')).to.equal('ZjbXXE1q'); + config.resetConfig(); + }); + it('should handle ext.bidder.ozone.floor correctly, inserting nothing as necessary', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + let vres = JSON.parse(JSON.stringify(validResponse)); + vres.body.seatbid[0].bid[0].ext.bidder.ozone = {}; + const result = spec.interpretResponse(vres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_flr', null)).to.equal(null); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_rid', null)).to.equal(null); + config.resetConfig(); + }); + it('should handle ext.bidder.ozone.floor correctly, when bidder.ozone is not there', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + let vres = JSON.parse(JSON.stringify(validResponse)); + const result = spec.interpretResponse(vres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_flr', null)).to.equal(null); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_rid', null)).to.equal(null); + config.resetConfig(); + }); + it('should handle a valid whitelist, removing items not on the list & leaving others', function () { + config.setConfig({'ozone': {'oz_whitelist_adserver_keys': ['oz_appnexus_crid', 'oz_appnexus_adId']}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_adv')).to.be.undefined; + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_adId')).to.equal('2899ec066a91ff8-0-0'); + config.resetConfig(); + }); + it('should ignore a whitelist if enhancedAdserverTargeting is false', function () { + config.setConfig({'ozone': {'oz_whitelist_adserver_keys': ['oz_appnexus_crid', 'oz_appnexus_imp_id'], 'enhancedAdserverTargeting': false}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_adv')).to.be.undefined; + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_imp_id')).to.be.undefined; + config.resetConfig(); + }); + it('should correctly handle enhancedAdserverTargeting being false', function () { + config.setConfig({'ozone': {'enhancedAdserverTargeting': false}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_adv')).to.be.undefined; + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_imp_id')).to.be.undefined; + config.resetConfig(); + }); + it('should add flr into ads request if floor exists in the auction response', function () { + const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest.bidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2Bids)); + validres.body.seatbid[0].bid[0].ext.bidder.ozone = {'floor': 1}; + const result = spec.interpretResponse(validres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_flr')).to.equal(1); + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_appnexus_flr', '')).to.equal(''); + }); + it('should add rid into ads request if ruleId exists in the auction response', function () { + const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest.bidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2Bids)); + validres.body.seatbid[0].bid[0].ext.bidder.ozone = {'ruleId': 123}; + const result = spec.interpretResponse(validres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_rid')).to.equal(123); + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_appnexus_rid', '')).to.equal(''); + }); + it('should add oz_ozappnexus_sid (cid value) for all appnexus bids', function () { + const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest.bidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2BidsSameAdunit)); + const result = spec.interpretResponse(validres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_ozappnexus_sid')).to.equal(result[0].cid); + }); + it('should add unique adId values to each bid', function() { + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2BidsSameAdunit)); + const result = spec.interpretResponse(validres, request); + expect(result.length).to.equal(1); + expect(result[0]['price']).to.equal(0.9); + expect(result[0]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('2899ec066a91ff8-0-1'); + }); + it('should correctly process an auction with 2 adunits & multiple bidders one of which bids for both adslots', function() { + let validres = JSON.parse(JSON.stringify(multiResponse1)); + let request = spec.buildRequests(multiRequest1, multiBidderRequest1.bidderRequest); + let result = spec.interpretResponse(validres, request); + expect(result.length).to.equal(4); // one of the 5 bids will have been removed + expect(result[1]['price']).to.equal(0.521); + expect(result[1]['impid']).to.equal('3025f169863b7f8'); + expect(result[1]['id']).to.equal('18552976939844999'); + expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-2'); + // change the bid values so a different second bid for an impid by the same bidder gets dropped + validres = JSON.parse(JSON.stringify(multiResponse1)); + validres.body.seatbid[0].bid[1].price = 1.1; + validres.body.seatbid[0].bid[1].cpm = 1.1; + request = spec.buildRequests(multiRequest1, multiBidderRequest1.bidderRequest); + result = spec.interpretResponse(validres, request); + expect(result[1]['price']).to.equal(1.1); + expect(result[1]['impid']).to.equal('3025f169863b7f8'); + expect(result[1]['id']).to.equal('18552976939844681'); + expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-1'); + }); }); describe('userSyncs', function () { it('should fail gracefully if no server response', function () { - const result = spec.getUserSyncs('bad', false); + const result = spec.getUserSyncs('bad', false, gdpr1); expect(result).to.be.empty; }); it('should fail gracefully if server response is empty', function () { - const result = spec.getUserSyncs('bad', []); + const result = spec.getUserSyncs('bad', [], gdpr1); expect(result).to.be.empty; }); + it('should append the various values if they exist', function() { + // get the cookie bag populated + spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1); + expect(result).to.be.an('array'); + expect(result[0].url).to.include('publisherId=9876abcd12-3'); + expect(result[0].url).to.include('siteId=1234567890'); + expect(result[0].url).to.include('gdpr=1'); + expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); + }); }); describe('video object utils', function () { @@ -981,6 +2610,7 @@ describe('ozone Adapter', function () { expect(result).to.be.null; }); }); + describe('default size', function () { it('should should return default sizes if no obj is sent', function () { let obj = ''; @@ -989,4 +2619,204 @@ describe('ozone Adapter', function () { expect(result.defaultWidth).to.equal(300); }); }); + + describe('getGranularityKeyName', function() { + it('should return a string granularity as-is', function() { + const result = getGranularityKeyName('', 'this is it', ''); + expect(result).to.equal('this is it'); + }); + it('should return "custom" for a mediaTypeGranularity object', function() { + const result = getGranularityKeyName('', {}, ''); + expect(result).to.equal('custom'); + }); + it('should return "custom" for a mediaTypeGranularity object', function() { + const result = getGranularityKeyName('', false, 'string buckets'); + expect(result).to.equal('string buckets'); + }); + }); + + describe('getGranularityObject', function() { + it('should return an object as-is', function() { + const result = getGranularityObject('', {'name': 'mark'}, '', ''); + expect(result.name).to.equal('mark'); + }); + it('should return an object as-is', function() { + const result = getGranularityObject('', false, 'custom', {'name': 'rupert'}); + expect(result.name).to.equal('rupert'); + }); + }); + + describe('blockTheRequest', function() { + it('should return true if oz_request is false', function() { + config.setConfig({'ozone': {'oz_request': false}}); + let result = spec.blockTheRequest(bidderRequestWithFullGdpr); + expect(result).to.be.true; + config.resetConfig(); + }); + it('should return false if oz_request is true', function() { + config.setConfig({'ozone': {'oz_request': true}}); + let result = spec.blockTheRequest(bidderRequestWithFullGdpr); + expect(result).to.be.false; + config.resetConfig(); + }); + }); + describe('makeLotameObjectFromOverride', function() { + it('should update an object with valid lotame data', function () { + let objLotameOverride = {'oz_lotametpid': '1234', 'oz_lotameid': '12345', 'oz_lotamepid': '123456'}; + let result = spec.makeLotameObjectFromOverride( + objLotameOverride, + {'Profile': {'pid': 'originalpid', 'tpid': 'originaltpid', 'Audiences': {'Audience': [{'id': 'aud1'}]}}} + ); + expect(result.Profile.Audiences.Audience).to.be.an('array'); + expect(result.Profile.Audiences.Audience[0]).to.be.an('object'); + expect(result.Profile.Audiences.Audience[0]).to.deep.include({'id': '12345', 'abbr': '12345'}); + }); + it('should return the original object if it seems weird', function () { + let objLotameOverride = {'oz_lotametpid': '1234', 'oz_lotameid': '12345', 'oz_lotamepid': '123456'}; + let objLotameOriginal = {'Profile': {'pid': 'originalpid', 'tpid': 'originaltpid', 'somethingstrange': [{'id': 'aud1'}]}}; + let result = spec.makeLotameObjectFromOverride( + objLotameOverride, + objLotameOriginal + ); + expect(result).to.equal(objLotameOriginal); + }); + }); + describe('lotameDataIsValid', function() { + it('should allow a valid minimum lotame object', function() { + let obj = {'Profile': {'pid': '', 'tpid': '', 'Audiences': {'Audience': []}}}; + let result = spec.isLotameDataValid(obj); + expect(result).to.be.true; + }); + it('should allow a valid lotame object', function() { + let obj = {'Profile': {'pid': '12345', 'tpid': '45678', 'Audiences': {'Audience': [{'id': '1234', 'abbr': '567'}, {'id': '9999', 'abbr': '1111'}]}}}; + let result = spec.isLotameDataValid(obj); + expect(result).to.be.true; + }); + it('should disallow a lotame object without an Audience.id', function() { + let obj = {'Profile': {'tpid': '', 'pid': '', 'Audiences': {'Audience': [{'abbr': 'marktest'}]}}}; + let result = spec.isLotameDataValid(obj); + expect(result).to.be.false; + }); + }); + describe('getPageId', function() { + it('should return the same Page ID for multiple calls', function () { + let result = spec.getPageId(); + expect(result).to.be.a('string'); + let result2 = spec.getPageId(); + expect(result2).to.equal(result); + }); + }); + describe('getBidRequestForBidId', function() { + it('should locate a bid inside a bid array', function () { + let result = spec.getBidRequestForBidId('2899ec066a91ff8', validBidRequestsMulti); + expect(result.testId).to.equal(1); + result = spec.getBidRequestForBidId('2899ec066a91ff0', validBidRequestsMulti); + expect(result.testId).to.equal(2); + }); + }); + describe('getVideoContextForBidId', function() { + it('should locate the video context inside a bid', function () { + let result = spec.getVideoContextForBidId('2899ec066a91ff8', validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo); + expect(result).to.equal('outstream'); + }); + }); + describe('getLotameOverrideParams', function() { + it('should get 3 valid lotame params that exist in GET params', function () { + // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: + spec.getGetParametersAsObject = function() { + return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': 'tpid123'}; + }; + let result = spec.getLotameOverrideParams(); + expect(Object.keys(result).length).to.equal(3); + }); + it('should get only 1 valid lotame param that exists in GET params', function () { + // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: + spec.getGetParametersAsObject = function() { + return {'oz_lotameid': '123abc', 'xoz_lotamepid': 'pid123', 'xoz_lotametpid': 'tpid123'}; + }; + let result = spec.getLotameOverrideParams(); + expect(Object.keys(result).length).to.equal(1); + }); + }); + describe('unpackVideoConfigIntoIABformat', function() { + it('should correctly unpack a usual video config', function () { + let mediaTypes = { + playerSize: [640, 480], + mimes: ['video/mp4'], + context: 'outstream', + testKey: 'parent value' + }; + let bid_params_video = { + skippable: true, + playback_method: ['auto_play_sound_off'], + playbackmethod: 2, /* start on load, no sound */ + minduration: 5, + maxduration: 60, + skipmin: 5, + skipafter: 5, + testKey: 'child value' + }; + let result = spec.unpackVideoConfigIntoIABformat(mediaTypes, bid_params_video); + expect(result.mimes).to.be.an('array').that.includes('video/mp4'); + expect(result.ext.context).to.equal('outstream'); + expect(result.ext.skippable).to.be.true; // note - we add skip in a different step: addVideoDefaults + expect(result.ext.testKey).to.equal('child value'); + }); + }); + describe('addVideoDefaults', function() { + it('should correctly add video defaults', function () { + let mediaTypes = { + playerSize: [640, 480], + mimes: ['video/mp4'], + context: 'outstream', + }; + let bid_params_video = { + skippable: true, + playback_method: ['auto_play_sound_off'], + playbackmethod: 2, /* start on load, no sound */ + minduration: 5, + maxduration: 60, + skipmin: 5, + skipafter: 5, + testKey: 'child value' + }; + let result = spec.addVideoDefaults({}, mediaTypes, mediaTypes); + expect(result.placement).to.equal(3); + expect(result.skip).to.equal(0); + result = spec.addVideoDefaults({}, mediaTypes, bid_params_video); + expect(result.skip).to.equal(1); + }); + it('should correctly add video defaults including skippable in parent', function () { + let mediaTypes = { + playerSize: [640, 480], + mimes: ['video/mp4'], + context: 'outstream', + skippable: true + }; + let bid_params_video = { + playback_method: ['auto_play_sound_off'], + playbackmethod: 2, /* start on load, no sound */ + minduration: 5, + maxduration: 60, + skipmin: 5, + skipafter: 5, + testKey: 'child value' + }; + let result = spec.addVideoDefaults({}, mediaTypes, bid_params_video); + expect(result.placement).to.equal(3); + expect(result.skip).to.equal(1); + }); + }); + describe('removeSingleBidderMultipleBids', function() { + it('should remove the multi bid by ozappnexus for adslot 2d30e86db743a8', function() { + let validres = JSON.parse(JSON.stringify(multiResponse1)); + expect(validres.body.seatbid[0].bid.length).to.equal(3); + expect(validres.body.seatbid[0].seat).to.equal('ozappnexus'); + let response = spec.removeSingleBidderMultipleBids(validres.body.seatbid); + expect(response.length).to.equal(2); + expect(response[0].bid.length).to.equal(2); + expect(response[0].seat).to.equal('ozappnexus'); + expect(response[1].bid.length).to.equal(2); + }); + }); }); diff --git a/test/spec/modules/padsquadBidAdapter_spec.js b/test/spec/modules/padsquadBidAdapter_spec.js index 88c9680c888..7d0858ed25e 100644 --- a/test/spec/modules/padsquadBidAdapter_spec.js +++ b/test/spec/modules/padsquadBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/padsquadBidAdapter'; +import {spec} from 'modules/padsquadBidAdapter.js'; const REQUEST = { 'bidderCode': 'padsquad', @@ -212,6 +212,7 @@ describe('Padsquad bid adapter', function () { expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index].meta.advertiserDomains).to.deep.equal(RESPONSE.body.seatbid[0].bid[index].adomain); expect(bids[index]).to.have.property('ttl', 30); expect(bids[index]).to.have.property('netRevenue', true); } diff --git a/test/spec/modules/papyrusBidAdapter_spec.js b/test/spec/modules/papyrusBidAdapter_spec.js index f3c6f42f5b2..20fcced2950 100644 --- a/test/spec/modules/papyrusBidAdapter_spec.js +++ b/test/spec/modules/papyrusBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/papyrusBidAdapter'; +import { spec } from 'modules/papyrusBidAdapter.js'; const ENDPOINT = 'https://prebid.papyrus.global'; const BIDDER_CODE = 'papyrus'; diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 540e63aa630..7db22af82ab 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -1,75 +1,267 @@ import { expect } from 'chai'; -import {config} from 'src/config'; -import * as utils from 'src/utils'; +import find from 'core-js-pure/features/array/find.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { newStorageManager } from 'src/storageManager.js'; +import { getRefererInfo } from 'src/refererDetection.js'; +import { uspDataHandler } from 'src/adapterManager.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; -import { parrableIdSubmodule } from 'modules/parrableIdSystem'; +import { parrableIdSubmodule } from 'modules/parrableIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; + +const storage = newStorageManager(); const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; -const P_COOKIE_NAME = '_parrable_eid'; -const P_COOKIE_VALUE = '01.1563917337.test-eid'; +const P_COOKIE_NAME = '_parrable_id'; +const P_COOKIE_EID = '01.1563917337.test-eid'; +const P_XHR_EID = '01.1588030911.test-new-eid' const P_CONFIG_MOCK = { name: 'parrableId', params: { partner: 'parrable_test_partner_123,parrable_test_partner_456' - }, - storage: { - name: '_parrable_eid', - type: 'cookie', - expires: 364 } }; -describe('Parrable ID System', function() { - function getConfigMock() { - return { - userSync: { - syncDelay: 0, - userIds: [P_CONFIG_MOCK] - } +function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [P_CONFIG_MOCK] } } - function getAdUnitMock(code = 'adUnit-code') { - return { - code, - mediaTypes: {banner: {}, native: {}}, - sizes: [ - [300, 200], - [300, 600] - ], - bids: [{ - bidder: 'sampleBidder', - params: { placementId: 'banner-only-bidder' } - }] - }; +} + +function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; +} + +function serializeParrableId(parrableId) { + let str = ''; + if (parrableId.eid) { + str += 'eid:' + parrableId.eid; + } + if (parrableId.ibaOptout) { + str += ',ibaOptout:1'; + } + if (parrableId.ccpaOptout) { + str += ',ccpaOptout:1'; } + return str; +} - describe('Parrable ID in Bid Request', function() { - let adUnits; +function writeParrableCookie(parrableId) { + let cookieValue = encodeURIComponent(serializeParrableId(parrableId)); + storage.setCookie( + P_COOKIE_NAME, + cookieValue, + (new Date(Date.now() + 5000).toUTCString()), + 'lax' + ); +} + +function removeParrableCookie() { + storage.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); +} + +describe('Parrable ID System', function() { + describe('parrableIdSystem.getId() callback', function() { + let logErrorStub; + let callbackSpy = sinon.spy(); beforeEach(function() { - adUnits = [getAdUnitMock()]; + logErrorStub = sinon.stub(utils, 'logError'); + callbackSpy.resetHistory(); + writeParrableCookie({ eid: P_COOKIE_EID }); + }); + + afterEach(function() { + removeParrableCookie(); + logErrorStub.restore(); + }) + + it('creates xhr to Parrable that synchronizes the ID', function() { + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + + getIdResult.callback(callbackSpy); + + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + let data = JSON.parse(atob(queryParams.data)); + + expect(getIdResult.callback).to.be.a('function'); + expect(request.url).to.contain('h.parrable.com'); + + expect(queryParams).to.not.have.property('us_privacy'); + expect(data).to.deep.equal({ + eid: P_COOKIE_EID, + trackers: P_CONFIG_MOCK.params.partner.split(','), + url: getRefererInfo().referer + }); + + server.requests[0].respond(200, + { 'Content-Type': 'text/plain' }, + JSON.stringify({ eid: P_XHR_EID }) + ); + + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ + eid: P_XHR_EID + }); + + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + P_XHR_EID) + ); + }); + + it('xhr passes the uspString to Parrable', function() { + let uspString = '1YNN'; + uspDataHandler.setConsentData(uspString); + parrableIdSubmodule.getId( + P_CONFIG_MOCK.params, + null, + null + ).callback(callbackSpy); + uspDataHandler.setConsentData(null); + expect(server.requests[0].url).to.contain('us_privacy=' + uspString); + }); + + it('should log an error and continue to callback if ajax request errors', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = parrableIdSubmodule.getId({partner: 'prebid'}).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('h.parrable.com'); + request.respond( + 503, + null, + 'Unavailable' + ); + expect(logErrorStub.calledOnce).to.be.true; + expect(callBackSpy.calledOnce).to.be.true; + }); + }); + + describe('parrableIdSystem.getId() id', function() { + it('provides the stored Parrable values if a cookie exists', function() { + writeParrableCookie({ eid: P_COOKIE_EID }); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + removeParrableCookie(); + + expect(getIdResult.id).to.deep.equal({ + eid: P_COOKIE_EID + }); }); - it('should append parrableid to bid request', function(done) { - // simulate existing browser local storage values - utils.setCookie( - P_COOKIE_NAME, - P_COOKIE_VALUE, - (new Date(Date.now() + 5000).toUTCString()) + it('provides the stored legacy Parrable ID values if cookies exist', function() { + let oldEid = '01.111.old-eid'; + let oldEidCookieName = '_parrable_eid'; + let oldOptoutCookieName = '_parrable_optout'; + + storage.setCookie(oldEidCookieName, oldEid); + storage.setCookie(oldOptoutCookieName, 'true'); + + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + expect(getIdResult.id).to.deep.equal({ + eid: oldEid, + ibaOptout: true + }); + + // The ID system is expected to migrate old cookies to the new format + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + oldEid + ',ibaOptout:1') ); + expect(storage.getCookie(oldEidCookieName)).to.equal(null); + expect(storage.getCookie(oldOptoutCookieName)).to.equal(null); + }); + }); + describe('parrableIdSystem.decode()', function() { + it('provides the Parrable ID (EID) from a stored object', function() { + let eid = '01.123.4567890'; + let parrableId = { + eid, + ibaOptout: true + }; + + expect(parrableIdSubmodule.decode(parrableId)).to.deep.equal({ + parrableId + }); + }); + }); + + describe('userId requestBids hook', function() { + let adUnits; + + beforeEach(function() { + adUnits = [getAdUnitMock()]; + writeParrableCookie({ eid: P_COOKIE_EID, ibaOptout: true }); setSubmoduleRegistry([parrableIdSubmodule]); init(config); config.setConfig(getConfigMock()); + }); + + afterEach(function() { + removeParrableCookie(); + storage.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); + }); + + it('when a stored Parrable ID exists it is added to bids', function(done) { + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.parrableId'); + expect(bid.userId.parrableId.eid).to.equal(P_COOKIE_EID); + expect(bid.userId.parrableId.ibaOptout).to.equal(true); + const parrableIdAsEid = find(bid.userIdAsEids, e => e.source == 'parrable.com'); + expect(parrableIdAsEid).to.deep.equal({ + source: 'parrable.com', + uids: [{ + id: P_COOKIE_EID, + atype: 1, + ext: { + ibaOptout: true + } + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + + it('supplies an optout reason when the EID is missing due to CCPA non-consent', function(done) { + // the ID system itself will not write a cookie with an EID when CCPA=true + writeParrableCookie({ ccpaOptout: true }); requestBidsHook(function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.parrableid'); - expect(bid.userId.parrableid).to.equal(P_COOKIE_VALUE); + expect(bid).to.have.deep.nested.property('userId.parrableId'); + expect(bid.userId.parrableId).to.not.have.property('eid'); + expect(bid.userId.parrableId.ccpaOptout).to.equal(true); + const parrableIdAsEid = find(bid.userIdAsEids, e => e.source == 'parrable.com'); + expect(parrableIdAsEid).to.deep.equal({ + source: 'parrable.com', + uids: [{ + id: '', + atype: 1, + ext: { + ccpaOptout: true + } + }] + }); }); }); - utils.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); done(); }, { adUnits }); }); diff --git a/test/spec/modules/performaxBidAdapter_spec.js b/test/spec/modules/performaxBidAdapter_spec.js new file mode 100644 index 00000000000..43c256b9d13 --- /dev/null +++ b/test/spec/modules/performaxBidAdapter_spec.js @@ -0,0 +1,274 @@ +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; +import { spec } from 'modules/performaxBidAdapter'; + +describe('PerformaxAdapter', function () { + let bidRequests, bidderRequest; + let serverResponse, serverRequest; + + const URL = + 'https://dale.performax.cz/hb?slotId[]=3,2&client=hellboy:v0.0.1&auctionId=144b5079-8cbf-49a5-aca7-a68b3296cd6c'; + + bidRequests = [ + { + adUnitCode: 'postbid_iframe', + auctionId: '144b5079-8cbf-49a5-aca7-a68b3296cd6c', + bidId: '2a4332f6b2bc74', + bidRequestsCount: 1, + bidder: 'performax', + bidderRequestId: '1c7d8bf204f11e', + bidderRequestsCount: 1, + bidderWinsCount: 0, + mediaTypes: { + banner: { + sizes: [[300, 300]], + }, + }, + params: { + slotId: 3, + }, + sizes: [[300, 300]], + src: 'client', + transactionId: '14969d09-0068-4d5b-a34e-e35091561dee', + }, + { + adUnitCode: 'postbid_iframe2', + auctionId: '144b5079-8cbf-49a5-aca7-a68b3296cd6c', + bidId: '300bb0ac6a156a', + bidRequestsCount: 1, + bidder: 'performax', + bidderRequestId: '1c7d8bf204f11e', + bidderRequestsCount: 1, + bidderWinsCount: 0, + mediaTypes: { + banner: { + sizes: [[300, 300]], + }, + }, + params: { + slotId: 2, + }, + sizes: [[300, 300]], + src: 'client', + transactionId: '107cbebd-8c36-4456-b28c-91a19ba80151', + }, + ]; + + bidderRequest = { + auctionId: '144b5079-8cbf-49a5-aca7-a68b3296cd6c', + auctionStart: 1594281941845, + bidderCode: 'performax', + bidderRequestId: '1c7d8bf204f11e', + bids: bidRequests, + refererInfo: { + canonicalUrl: '', + numIframes: 0, + reachedTop: true, + referer: '', + }, + stack: [''], + start: 1594281941935, + timeout: 3600, + }; + + serverResponse = { + body: [ + { + ad: { + code: '$SYS_ID$ $VAR_NAME$ rest of the code', + data: { + SYS_ID: 1, + VAR_NAME: 'name', + }, + format_id: 2, + id: 11, + size: { + width: 300, + height: 300, + }, + tag_ids: [], + type: 'creative', + }, + cpm: 30, + creativeId: 'creative:11', + currency: 'CZK', + height: 300, + meta: { + agencyId: 1, + mediaType: 'banner', + }, + netRevenue: true, + requestId: '2a4332f6b2bc74', + ttl: 60, + width: 300, + }, + { + ad: { + code: '', + reason: 'Slot 2 does not allow HB requests', + type: 'empty', + }, + cpm: 0, + creativeId: null, + currency: 'CZK', + height: null, + meta: { + agencyId: null, + mediaType: 'banner', + }, + netRevenue: true, + requestId: '1c7d8bf204f11e', + ttl: 60, + width: 300, + }, + ], + }; + + serverRequest = { + data: { + bidderRequest: bidderRequest, + validBidRequests: bidRequests, + }, + method: 'POST', + options: { + contentType: 'application/json', + }, + url: URL, + }; + + describe('Bid validations', function () { + it('Valid bid', function () { + let validBid = { + bidder: 'performax', + params: { + slotId: 2, + }, + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('Invalid bid: required param is missing', function () { + let invalidBid = { + bidder: 'performax', + params: { + invalidParam: 2, + }, + }, + isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }); + }); + + describe('Build Url', function () { + it('Should return url', function () { + let url = spec.buildUrl(bidRequests, bidderRequest); + expect(url).to.equal(URL); + }); + }); + + describe('Build Request', function () { + it('Should not modify bidRequests and bidder Requests', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let originalBidderRequest = utils.deepClone(bidderRequest); + let request = spec.buildRequests(bidRequests, bidderRequest); + + expect(bidRequests).to.deep.equal(originalBidRequests); + expect(bidderRequest).to.deep.equal(originalBidderRequest); + }); + + it('Endpoint checking', function () { + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(URL); + expect(request.method).to.equal('POST'); + expect(request.options).to.deep.equal({ + contentType: 'application/json', + }); + }); + + it('Request params checking', function () { + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.validBidRequests).to.deep.equal(bidRequests); + expect(request.data.bidderRequest).to.deep.equal(bidderRequest); + }); + }); + + describe('Build Html', function () { + it('Ad with data: should return build html', function () { + let validAd = { + code: '$SYS_ID$ $VAR_NAME$ rest of the code', + data: { + SYS_ID: 1, + VAR_NAME: 'name', + }, + format_id: 2, + id: 11, + size: { + width: 300, + height: 300, + }, + tag_ids: [], + type: 'creative', + }; + let html = spec.buildHtml(validAd); + expect(html).to.equal('1 name rest of the code'); + }); + + it('Ad with partial data: should return html without data change', function () { + let adWithPartialData = { + code: '$SYS_ID$ $VAR_NAME$ rest of the code', + data: { + VAR_NAME: 'name', + }, + format_id: 2, + id: 11, + size: { + width: 300, + height: 300, + }, + tag_ids: [], + type: 'creative', + }; + let html = spec.buildHtml(adWithPartialData); + expect(html).to.equal('$SYS_ID$ name rest of the code'); + }); + + it('Ad without data: should return html without data change', function () { + let adWithoutData = { + code: '$SYS_ID$ $VAR_NAME$ rest of the code', + format_id: 2, + id: 11, + size: { + width: 300, + height: 300, + }, + tag_ids: [], + type: 'creative', + }; + let html = spec.buildHtml(adWithoutData); + expect(html).to.equal('$SYS_ID$ $VAR_NAME$ rest of the code'); + }); + }); + + describe('Interpret Response', function () { + it('Ad without data: should return html without data change', function () { + let ads = spec.interpretResponse(serverResponse, serverRequest); + expect(ads).to.have.length(1); + expect(ads[0]).to.deep.equal({ + ad: '1 name rest of the code', + cpm: 30, + creativeId: 'creative:11', + currency: 'CZK', + height: 300, + meta: { + agencyId: 1, + mediaType: 'banner', + }, + netRevenue: true, + requestId: '2a4332f6b2bc74', + ttl: 60, + width: 300, + }); + }); + }); +}); diff --git a/test/spec/modules/piximediaBidAdapter_spec.js b/test/spec/modules/piximediaBidAdapter_spec.js index 95e03734345..88a6433b71d 100644 --- a/test/spec/modules/piximediaBidAdapter_spec.js +++ b/test/spec/modules/piximediaBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/piximediaBidAdapter'; +import { spec } from 'modules/piximediaBidAdapter.js'; describe('piximediaAdapterTest', function() { describe('bidRequestValidity', function() { diff --git a/test/spec/modules/platformioBidAdapter_spec.js b/test/spec/modules/platformioBidAdapter_spec.js new file mode 100644 index 00000000000..ee753be17a7 --- /dev/null +++ b/test/spec/modules/platformioBidAdapter_spec.js @@ -0,0 +1,348 @@ +import {expect} from 'chai'; +import {spec} from 'modules/platformioBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('Platform.io Adapter Tests', function () { + const slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidId: 'bid12345', + mediaType: 'banner', + params: { + pubId: '29521', + siteId: '26047', + placementId: '123', + bidFloor: '0.001', + ifa: 'IFA', + latitude: '40.712775', + longitude: '-74.005973' + } + }, { + placementCode: '/DfpAccount2/slot2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bidId: 'bid23456', + mediaType: 'banner', + params: { + pubId: '29521', + siteId: '26047', + placementId: '1234', + 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', + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + }, + bidId: 'bid12345678', + mediaType: 'video', + video: { + skippable: true + }, + params: { + pubId: '29521', + placementId: '1234567', + siteId: '26047', + } + }]; + const appSlotConfig = [{ + placementCode: '/DfpAccount1/slot5', + bidId: 'bid12345', + params: { + pubId: '29521', + placementId: '1234', + app: { + id: '1111', + name: 'app name', + bundle: 'io.platform.apps', + storeUrl: 'https://platform.io/apps', + domain: 'platform.io' + } + } + }]; + + it('Verify build request', function () { + const request = spec.buildRequests(slotConfigs); + expect(request.url).to.equal('https://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(window.location.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'); + }); + + it('Verify parse response', function () { + 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', + w: 300, + h: 250 + }] + }], + 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); + }); + + it('Verify full passback', function () { + const request = spec.buildRequests(slotConfigs); + const bids = spec.interpretResponse({ body: null }, request) + expect(bids).to.have.lengthOf(0); + }); + + it('Verify Native request', function () { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('https://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); + }); + + it('Verify Native response', function () { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('https://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://adx1public.s3.amazonaws.com/creatives_icon.png', w: 100, h: 100 } }, + { id: 5, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_image.png', w: 300, h: 300 } } + ], + link: { url: 'https://brand.com/' } + } + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + nurl: 'https://rtb.adx1.com/log', + adm: JSON.stringify(nativeResponse) + }] + }], + 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://adx1public.s3.amazonaws.com/creatives_icon.png'); + expect(nativeBid.image.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_image.png'); + expect(nativeBid.image.width).to.equal(300); + expect(nativeBid.image.height).to.equal(300); + expect(nativeBid.icon.width).to.equal(100); + expect(nativeBid.icon.height).to.equal(100); + expect(nativeBid.clickUrl).to.equal(encodeURIComponent('https://brand.com/')); + expect(nativeBid.impressionTrackers).to.have.lengthOf(1); + expect(nativeBid.impressionTrackers[0]).to.equal('https://rtb.adx1.com/log'); + }); + + it('Verify Video request', function () { + const request = spec.buildRequests(videoSlotConfig); + expect(request.url).to.equal('https://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(window.location.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); + }); + + it('Verify parse video response', function () { + const request = spec.buildRequests(videoSlotConfig); + const videoRequest = JSON.parse(request.data); + const videoResponse = { + seatbid: [{ + bid: [{ + impid: videoRequest.imp[0].id, + price: 1.90, + adm: 'https://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('https://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); + }); + + it('Verifies bidder code', function () { + expect(spec.code).to.equal('platformio'); + }); + + it('Verifies supported media types', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(3); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('native'); + expect(spec.supportedMediaTypes[2]).to.equal('video'); + }); + + it('Verifies if bid request valid', function () { + 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); + }); + + it('Verify app requests', function () { + 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('https://platform.io/apps'); + expect(ortbRequest.app.domain).to.equal('platform.io'); + }); + + it('Verify GDPR', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'serialized_gpdr_data' + } + }; + const request = spec.buildRequests(slotConfigs, bidderRequest); + expect(request.url).to.equal('https://piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + 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'); + 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/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index f685103b5cb..5abe068c100 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,11 +1,13 @@ import { expect } from 'chai'; -import { PrebidServer as Adapter, resetSyncedStatus } from 'modules/prebidServerBidAdapter/index.js'; -import adapterManager from 'src/adapterManager'; -import * as utils from 'src/utils'; -import { ajax } from 'src/ajax'; -import { config } from 'src/config'; -import events from 'src/events'; -import CONSTANTS from 'src/constants'; +import { PrebidServer as Adapter, resetSyncedStatus, resetWurlMap } from 'modules/prebidServerBidAdapter/index.js'; +import adapterManager from 'src/adapterManager.js'; +import * as utils from 'src/utils.js'; +import { ajax } from 'src/ajax.js'; +import { config } from 'src/config.js'; +import events from 'src/events.js'; +import CONSTANTS from 'src/constants.json'; +import { server } from 'test/mocks/xhr.js'; +import { createEidsArray } from 'modules/userId/eids.js'; let CONFIG = { accountId: '1', @@ -259,7 +261,12 @@ const RESPONSE_OPENRTB = { 'w': 300, 'h': 250, 'ext': { - 'prebid': { 'type': 'banner' }, + 'prebid': { + 'type': 'banner', + 'event': { + 'win': 'http://wurl.org?id=333' + } + }, 'bidder': { 'appnexus': { 'brand_id': 1, @@ -273,6 +280,7 @@ const RESPONSE_OPENRTB = { 'seat': 'appnexus' }, ], + 'cur': 'EUR', 'ext': { 'responsetimemillis': { 'appnexus': 8, @@ -301,6 +309,7 @@ const RESPONSE_OPENRTB_VIDEO = { ext: { prebid: { type: 'video', + bidid: '654321' }, bidder: { appnexus: { @@ -429,6 +438,7 @@ describe('S2S Adapter', function () { done = sinon.spy(); beforeEach(function () { + config.resetConfig(); adapter = new Adapter(); BID_REQUESTS = [ { @@ -458,7 +468,8 @@ describe('S2S Adapter', function () { 'sizes': [300, 250], 'bidId': '123', 'bidderRequestId': '3d1063078dfcc8', - 'auctionId': '173afb6d132ba3' + 'auctionId': '173afb6d132ba3', + 'storedAuctionResponse': 11111 } ], 'auctionStart': 1510852447530, @@ -477,22 +488,15 @@ describe('S2S Adapter', function () { done.resetHistory(); }); - describe('request function', function () { - let xhr; - let requests; + after(function () { + config.resetConfig(); + }); + describe('request function', function () { beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - config.resetConfig(); resetSyncedStatus(); }); - afterEach(function () { - xhr.restore(); - }); - it('should not add outstrean without renderer', function () { let ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; @@ -500,7 +504,7 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: ortb2Config }); adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].banner).to.exist; expect(requestBid.imp[0].video).to.not.exist; }); @@ -511,7 +515,6 @@ describe('S2S Adapter', function () { describe('gdpr tests', function () { afterEach(function () { - config.resetConfig(); $$PREBID_GLOBAL$$.requestBids.removeAll(); }); @@ -529,7 +532,7 @@ describe('S2S Adapter', function () { }; adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.gdpr).is.equal(1); expect(requestBid.user.ext.consent).is.equal('abc123'); @@ -538,7 +541,38 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - requestBid = JSON.parse(requests[1].requestBody); + requestBid = JSON.parse(server.requests[1].requestBody); + + expect(requestBid.regs).to.not.exist; + expect(requestBid.user).to.not.exist; + }); + + it('adds additional consent information to ortb2 request depending on presence of module', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + + let gdprBidRequest = utils.deepClone(BID_REQUESTS); + gdprBidRequest[0].gdprConsent = { + consentString: 'abc123', + addtlConsent: 'superduperconsent', + gdprApplies: true + }; + + adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.regs.ext.gdpr).is.equal(1); + expect(requestBid.user.ext.consent).is.equal('abc123'); + expect(requestBid.user.ext.ConsentedProvidersSettings.consented_providers).is.equal('superduperconsent'); + + config.resetConfig(); + config.setConfig({ s2sConfig: CONFIG }); + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(server.requests[1].requestBody); expect(requestBid.regs).to.not.exist; expect(requestBid.user).to.not.exist; @@ -559,7 +593,7 @@ describe('S2S Adapter', function () { }; adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.equal(1); expect(requestBid.gdpr_consent).is.equal('abc123def'); @@ -581,7 +615,7 @@ describe('S2S Adapter', function () { }; adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.equal(0); expect(requestBid.gdpr_consent).is.undefined; @@ -601,7 +635,7 @@ describe('S2S Adapter', function () { }; adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.undefined; expect(requestBid.gdpr_consent).is.undefined; @@ -610,7 +644,6 @@ describe('S2S Adapter', function () { describe('us_privacy (ccpa) consent data', function () { afterEach(function () { - config.resetConfig(); $$PREBID_GLOBAL$$.requestBids.removeAll(); }); @@ -623,7 +656,7 @@ describe('S2S Adapter', function () { uspBidRequest[0].uspConsent = '1NYN'; adapter.callBids(REQUEST, uspBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.us_privacy).is.equal('1NYN'); @@ -631,7 +664,7 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - requestBid = JSON.parse(requests[1].requestBody); + requestBid = JSON.parse(server.requests[1].requestBody); expect(requestBid.regs).to.not.exist; }); @@ -645,7 +678,7 @@ describe('S2S Adapter', function () { uspBidRequest[0].uspConsent = '1YNN'; adapter.callBids(REQUEST, uspBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.us_privacy).is.equal('1YNN'); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -655,7 +688,6 @@ describe('S2S Adapter', function () { describe('gdpr and us_privacy (ccpa) consent data', function () { afterEach(function () { - config.resetConfig(); $$PREBID_GLOBAL$$.requestBids.removeAll(); }); @@ -672,7 +704,7 @@ describe('S2S Adapter', function () { }; adapter.callBids(REQUEST, consentBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.us_privacy).is.equal('1NYN'); expect(requestBid.regs.ext.gdpr).is.equal(1); @@ -682,7 +714,7 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - requestBid = JSON.parse(requests[1].requestBody); + requestBid = JSON.parse(server.requests[1].requestBody); expect(requestBid.regs).to.not.exist; expect(requestBid.user).to.not.exist; @@ -701,7 +733,7 @@ describe('S2S Adapter', function () { }; adapter.callBids(REQUEST, consentBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.us_privacy).is.equal('1YNN'); expect(requestBid.gdpr).is.equal(1); @@ -711,40 +743,6 @@ describe('S2S Adapter', function () { }); }); - it('adds digitrust id is present and user is not optout', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { s2sConfig: ortb2Config }; - config.setConfig(consentConfig); - - let digiTrustObj = { - privacy: { - optout: false - }, - id: 'testId', - keyv: 'testKeyV' - }; - - let digiTrustBidRequest = utils.deepClone(BID_REQUESTS); - digiTrustBidRequest[0].bids[0].userId = { digitrustid: { data: digiTrustObj } }; - - adapter.callBids(REQUEST, digiTrustBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); - - expect(requestBid.user.ext.digitrust).to.deep.equal({ - id: digiTrustObj.id, - keyv: digiTrustObj.keyv - }); - - digiTrustObj.privacy.optout = true; - - adapter.callBids(REQUEST, digiTrustBidRequest, addBidResponse, done, ajax); - requestBid = JSON.parse(requests[1].requestBody); - - expect(requestBid.user && request.user.ext && requestBid.user.ext.digitrust).to.not.exist; - }); - it('adds device and app objects to request', function () { const _config = { s2sConfig: CONFIG, @@ -754,7 +752,7 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.device).to.deep.equal({ ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', w: window.innerWidth, @@ -779,7 +777,7 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.device).to.deep.equal({ ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', w: window.innerWidth, @@ -791,6 +789,27 @@ describe('S2S Adapter', function () { }); }); + it('adds debugging value from storedAuctionResponse to OpenRTB', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + }); + const _config = { + s2sConfig: s2sConfig, + 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(server.requests[0].requestBody); + expect(requestBid.imp).to.exist.and.to.be.a('array'); + expect(requestBid.imp).to.have.lengthOf(1); + expect(requestBid.imp[0].ext).to.exist.and.to.be.a('object'); + expect(requestBid.imp[0].ext.prebid).to.exist.and.to.be.a('object'); + expect(requestBid.imp[0].ext.prebid.storedauctionresponse).to.exist.and.to.be.a('object'); + expect(requestBid.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); + }); + it('adds device.w and device.h even if the config lacks a device object', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' @@ -803,7 +822,7 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.device).to.deep.equal({ w: window.innerWidth, h: window.innerHeight @@ -825,7 +844,7 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].native).to.deep.equal({ request: JSON.stringify({ @@ -880,14 +899,37 @@ describe('S2S Adapter', function () { const _config = { s2sConfig: s2sConfig, + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } }; config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.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.publisher.id).to.exist.and.to.be.a('string'); + expect(requestBid.site.publisher.domain).to.exist.and.to.be.a('string'); expect(requestBid.site.page).to.exist.and.to.be.a('string'); + expect(requestBid.site.content).to.exist.and.to.be.a('object'); + expect(requestBid.site.content.language).to.exist.and.to.be.a('string'); + expect(requestBid.site).to.deep.equal({ + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + }, + page: 'http://mytestpage.com' + }); }); it('adds appnexus aliases to request', function () { @@ -906,13 +948,14 @@ describe('S2S Adapter', function () { adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext).to.deep.equal({ prebid: { aliases: { brealtime: 'appnexus' }, + auctiontimestamp: 1510852447530, targeting: { includebidderkeys: false, includewinners: true @@ -940,13 +983,14 @@ describe('S2S Adapter', function () { $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias); adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext).to.deep.equal({ prebid: { aliases: { [alias]: 'appnexus' }, + auctiontimestamp: 1510852447530, targeting: { includebidderkeys: false, includewinners: true @@ -969,7 +1013,7 @@ describe('S2S Adapter', function () { }; adapter.callBids(myRequest, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.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); @@ -993,7 +1037,7 @@ describe('S2S Adapter', function () { let bidRequest = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); expect(requestBid.account).is.equal('1'); @@ -1007,7 +1051,7 @@ describe('S2S Adapter', function () { let bidRequest = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); expect(requestBid.account).is.equal('1'); @@ -1019,7 +1063,7 @@ describe('S2S Adapter', function () { bidRequest = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); - requestBid = JSON.parse(requests[0].requestBody); + requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); expect(requestBid.account).is.equal('1'); @@ -1043,11 +1087,64 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].ext.appnexus).to.haveOwnProperty('key'); expect(requestBid.imp[0].ext.appnexus.key).to.be.equal('value') }); + describe('config site value is added to the oRTB request', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + adapterOptions: { + appnexus: { + key: 'value' + } + } + }); + const device = { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', + ip: '75.97.0.47' + }; + + it('and overrides publisher and page', function () { + config.setConfig({ + s2sConfig: s2sConfig, + site: { + domain: 'nytimes.com', + page: 'http://www.nytimes.com', + publisher: { id: '2' } + }, + device: device + }); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.site).to.exist.and.to.be.a('object'); + expect(requestBid.site.domain).to.equal('nytimes.com'); + expect(requestBid.site.page).to.equal('http://www.nytimes.com'); + expect(requestBid.site.publisher).to.exist.and.to.be.a('object'); + expect(requestBid.site.publisher.id).to.equal('2'); + }); + + it('and merges domain and page with the config site value', function () { + config.setConfig({ + s2sConfig: s2sConfig, + site: { + foo: 'bar' + }, + device: device + }); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.site).to.exist.and.to.be.a('object'); + expect(requestBid.site.foo).to.equal('bar'); + expect(requestBid.site.page).to.equal('http://mytestpage.com'); + expect(requestBid.site.publisher).to.exist.and.to.be.a('object'); + expect(requestBid.site.publisher.id).to.equal('1'); + }); + }); + it('when userId is defined on bids, it\'s properties should be copied to user.ext.tpid properties', function () { let ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; @@ -1060,15 +1157,17 @@ describe('S2S Adapter', function () { criteoId: '44VmRDeUE3ZGJ5MzRkRVJHU3BIUlJ6TlFPQUFU', tdid: 'abc123', pubcid: '1234', - parrableid: '01.1563917337.test-eid', + parrableId: { eid: '01.1563917337.test-eid' }, lipb: { lipbid: 'li-xyz', segments: ['segA', 'segB'] - } + }, + idl_env: '0000-1111-2222-3333' }; + userIdBidRequest[0].bids[0].userIdAsEids = createEidsArray(userIdBidRequest[0].bids[0].userId); adapter.callBids(REQUEST, userIdBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(requests[0].requestBody); + let requestBid = JSON.parse(server.requests[0].requestBody); expect(typeof requestBid.user.ext.eids).is.equal('object'); expect(Array.isArray(requestBid.user.ext.eids)).to.be.true; expect(requestBid.user.ext.eids.filter(eid => eid.source === 'adserver.org')).is.not.empty; @@ -1084,6 +1183,8 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments.length).is.equal(2); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[0]).is.equal('segA'); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[1]).is.equal('segB'); + // LiveRamp should exist + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveramp.com')[0].uids[0].id).is.equal('0000-1111-2222-3333'); }); it('when config \'currency.adServerCurrency\' value is an array: ORTB has property \'cur\' value set to a single item array', function () { @@ -1097,7 +1198,7 @@ describe('S2S Adapter', function () { const bidRequests = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); - const parsedRequestBody = JSON.parse(requests[0].requestBody); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.cur).to.deep.equal(['USD']); }); @@ -1112,7 +1213,7 @@ describe('S2S Adapter', function () { const bidRequests = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); - const parsedRequestBody = JSON.parse(requests[1].requestBody); + const parsedRequestBody = JSON.parse(server.requests[1].requestBody); expect(parsedRequestBody.cur).to.deep.equal(['NZ']); }); @@ -1124,7 +1225,7 @@ describe('S2S Adapter', function () { const bidRequests = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); - const parsedRequestBody = JSON.parse(requests[0].requestBody); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(typeof parsedRequestBody.cur).to.equal('undefined'); }); @@ -1145,7 +1246,7 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includebidderkeys'); expect(requestBid.ext.prebid.targeting.includebidderkeys).to.equal(false); @@ -1168,7 +1269,7 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includewinners'); expect(requestBid.ext.prebid.targeting.includewinners).to.equal(true); @@ -1189,11 +1290,12 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); expect(requestBid.ext.prebid).to.deep.equal({ + auctiontimestamp: 1510852447530, foo: 'bar', targeting: { includewinners: true, @@ -1220,11 +1322,12 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); expect(requestBid.ext.prebid).to.deep.equal({ + auctiontimestamp: 1510852447530, targeting: { includewinners: false, includebidderkeys: true @@ -1253,11 +1356,12 @@ describe('S2S Adapter', function () { config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); expect(requestBid.ext.prebid).to.deep.equal({ + auctiontimestamp: 1510852447530, cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' }, @@ -1289,73 +1393,254 @@ describe('S2S Adapter', function () { }; bidRequests[0].bids[0].schain = schainObject; adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); - const parsedRequestBody = JSON.parse(requests[0].requestBody); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.source.ext.schain).to.deep.equal(schainObject); - }) + }); + + it('passes first party data in request', () => { + const s2sBidRequest = utils.deepClone(REQUEST); + const bidRequests = utils.deepClone(BID_REQUESTS); + + const commonContext = { + keywords: ['power tools'], + search: 'drill' + }; + const commonUser = { + keywords: ['a', 'b'], + gender: 'M' + }; + + const context = { + content: { userrating: 4 }, + data: { + pageType: 'article', + category: 'tools' + } + }; + const user = { + yob: '1984', + geo: { country: 'ca' }, + data: { + registered: true, + interests: ['cars'] + } + }; + const allowedBidders = [ 'rubicon', 'appnexus' ]; + + const expected = allowedBidders.map(bidder => ({ + bidders: [ bidder ], + config: { fpd: { site: context, user } } + })); + + config.setConfig({ fpd: { context: commonContext, user: commonUser } }); + config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user } } }); + adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); + expect(parsedRequestBody.site.ext.data).to.deep.equal(commonContext); + expect(parsedRequestBody.user.ext.data).to.deep.equal(commonUser); + }); + + describe('pbAdSlot config', function () { + it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context\" is undefined', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + }); + + it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = {}; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + }); + + it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = { + context: { + pbAdSlot: '' + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + }); + + it('should send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = { + context: { + pbAdSlot: '/a/b/c' + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0].ext.context.data.pbadslot).to.equal('/a/b/c'); + }); + }); + + describe('GAM ad unit config', function () { + it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context\" is undefined', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + }); + + it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" is undefined', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = {}; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + }); + + it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" is empty string', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = { + context: { + adServer: { + adSlot: '' + } + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + }); + + it('should send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" value is a non-empty string', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = { + context: { + adServer: { + adSlot: '/a/b/c' + } + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0].ext.context.data.adslot).to.equal('/a/b/c'); + }); + }); }); describe('response handler', function () { - let server; - let logWarnSpy; - beforeEach(function () { - server = sinon.fakeServer.create(); sinon.stub(utils, 'triggerPixel'); sinon.stub(utils, 'insertUserSyncIframe'); sinon.stub(utils, 'logError'); sinon.stub(events, 'emit'); - logWarnSpy = sinon.spy(utils, 'logWarn'); }); afterEach(function () { - server.restore(); utils.triggerPixel.restore(); utils.insertUserSyncIframe.restore(); utils.logError.restore(); events.emit.restore(); - logWarnSpy.restore(); }); // TODO: test dependent on pbjs_api_spec. Needs to be isolated it('does not call addBidResponse and calls done when ad unit not set', function () { - server.respondWith(JSON.stringify(RESPONSE_NO_BID_NO_UNIT)); - config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_NO_BID_NO_UNIT)); sinon.assert.notCalled(addBidResponse); sinon.assert.calledOnce(done); }); it('does not call addBidResponse and calls done when server requests cookie sync', function () { - server.respondWith(JSON.stringify(RESPONSE_NO_COOKIE)); - config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_NO_COOKIE)); sinon.assert.notCalled(addBidResponse); sinon.assert.calledOnce(done); }); it('does not call addBidResponse and calls done when ad unit is set', function () { - server.respondWith(JSON.stringify(RESPONSE_NO_BID_UNIT_SET)); - config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_NO_BID_UNIT_SET)); sinon.assert.notCalled(addBidResponse); sinon.assert.calledOnce(done); }); it('registers successful bids and calls done when there are less bids than requests', function () { - server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); - config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); sinon.assert.calledOnce(addBidResponse); sinon.assert.calledOnce(done); @@ -1369,11 +1654,9 @@ describe('S2S Adapter', function () { }); it('should have dealId in bidObject', function () { - server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); - config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('dealId', 'test-dealid'); }); @@ -1389,9 +1672,8 @@ describe('S2S Adapter', function () { cacheResponse.seatbid.forEach(item => { item.bid[0].ext.prebid.targeting = targetingTestData }); - server.respondWith(JSON.stringify(cacheResponse)); adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); const response = addBidResponse.firstCall.args[1]; @@ -1402,6 +1684,24 @@ describe('S2S Adapter', function () { }); }); + it('should set the bidResponse currency to whats in the PBS response', function() { + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); + sinon.assert.calledOnce(addBidResponse); + const pbjsResponse = addBidResponse.firstCall.args[1]; + expect(pbjsResponse).to.have.property('currency', 'EUR'); + }); + + it('should set the default bidResponse currency when not specified in OpenRTB', function() { + let modifiedResponse = utils.deepClone(RESPONSE_OPENRTB); + modifiedResponse.cur = ''; + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(modifiedResponse)); + sinon.assert.calledOnce(addBidResponse); + const pbjsResponse = addBidResponse.firstCall.args[1]; + expect(pbjsResponse).to.have.property('currency', 'USD'); + }); + it('should pass through default adserverTargeting if present in bidObject for banner request', function () { const cacheResponse = utils.deepClone(RESPONSE_OPENRTB); @@ -1413,11 +1713,9 @@ describe('S2S Adapter', function () { item.bid[0].ext.prebid.targeting = targetingTestData }); - server.respondWith(JSON.stringify(cacheResponse)); - config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('adserverTargeting').that.deep.equals({ 'foo': 'bar' }); @@ -1429,11 +1727,9 @@ describe('S2S Adapter', function () { }; 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(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_NO_PBS_COOKIE)); sinon.assert.calledOnce(rubiconAdapter.registerSyncs); @@ -1451,9 +1747,8 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig }); - server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); sinon.assert.calledOnce(rubiconAdapter.registerSyncs); @@ -1466,9 +1761,8 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig }); - server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); sinon.assert.calledOnce(events.emit); const event = events.emit.firstCall.args; @@ -1481,8 +1775,29 @@ describe('S2S Adapter', function () { expect(response).to.have.property('bidderCode', 'appnexus'); expect(response).to.have.property('requestId', '123'); expect(response).to.have.property('cpm', 0.5); + expect(response).to.have.property('meta'); + expect(response.meta).to.have.property('advertiserDomains'); + expect(response.meta.advertiserDomains[0]).to.equal('appnexus.com'); expect(response).to.not.have.property('vastUrl'); expect(response).to.not.have.property('videoCacheKey'); + expect(response).to.have.property('ttl', 60); + }); + + it('respects defaultTtl', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + defaultTtl: 30 + }); + config.setConfig({ s2sConfig }); + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); + + sinon.assert.calledOnce(events.emit); + const event = events.emit.firstCall.args; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('ttl', 30); }); it('handles OpenRTB video responses', function () { @@ -1491,9 +1806,8 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig }); - server.respondWith(JSON.stringify(RESPONSE_OPENRTB_VIDEO)); adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); sinon.assert.calledOnce(addBidResponse); const response = addBidResponse.firstCall.args[1]; @@ -1519,9 +1833,9 @@ describe('S2S Adapter', function () { } } }); - server.respondWith(JSON.stringify(cacheResponse)); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); const response = addBidResponse.firstCall.args[1]; @@ -1545,9 +1859,8 @@ describe('S2S Adapter', function () { cacheResponse.seatbid.forEach(item => { item.bid[0].ext.prebid.targeting = targetingTestData }); - server.respondWith(JSON.stringify(cacheResponse)); adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); const response = addBidResponse.firstCall.args[1]; @@ -1572,9 +1885,8 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - server.respondWith(JSON.stringify(cacheResponse)); adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); const response = addBidResponse.firstCall.args[1]; @@ -1584,6 +1896,77 @@ describe('S2S Adapter', function () { expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=a5ad3993'); }); + it('handles response cache from ext.prebid.targeting with wurl', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.events = { + win: 'https://wurl.com?a=1&b=2' + }; + item.bid[0].ext.prebid.targeting = { + hb_uuid: 'a5ad3993', + hb_cache_host: 'prebid-cache.net', + hb_cache_path: '/cache' + } + }); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('pbsBidId', '654321'); + }); + + it('handles response cache from ext.prebid.targeting with wurl and removes invalid targeting', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.events = { + win: 'https://wurl.com?a=1&b=2' + }; + item.bid[0].ext.prebid.targeting = { + hb_uuid: 'a5ad3993', + hb_cache_host: 'prebid-cache.net', + hb_cache_path: '/cache', + hb_winurl: 'https://hbwinurl.com?a=1&b=2', + hb_bidid: '1234567890', + } + }); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + + expect(response.adserverTargeting).to.deep.equal({ + hb_uuid: 'a5ad3993', + hb_cache_host: 'prebid-cache.net', + hb_cache_path: '/cache' + }); + }); + + it('add request property pbsBidId with ext.prebid.bidid value', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + + expect(response).to.have.property('pbsBidId', '654321'); + }); + it('handles OpenRTB native responses', function () { sinon.stub(utils, 'getBidRequest').returns({ adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -1595,9 +1978,8 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig }); - server.respondWith(JSON.stringify(RESPONSE_OPENRTB_NATIVE)); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_NATIVE)); sinon.assert.calledOnce(addBidResponse); const response = addBidResponse.firstCall.args[1]; @@ -1612,21 +1994,107 @@ describe('S2S Adapter', function () { }); }); + describe('bid won events', function () { + let uniqueIdCount = 0; + let triggerPixelStub; + const staticUniqueIds = ['1000', '1001', '1002', '1003']; + + before(function () { + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + }); + + beforeEach(function () { + resetWurlMap(); + sinon.stub(utils, 'insertUserSyncIframe'); + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'getUniqueIdentifierStr').callsFake(() => { + uniqueIdCount++; + return staticUniqueIds[uniqueIdCount - 1]; + }); + triggerPixelStub.resetHistory(); + + config.setConfig({ + s2sConfig: Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + }) + }); + }); + + afterEach(function () { + utils.triggerPixel.resetHistory(); + utils.insertUserSyncIframe.restore(); + utils.logError.restore(); + utils.getUniqueIdentifierStr.restore(); + uniqueIdCount = 0; + }); + + after(function () { + triggerPixelStub.restore(); + }); + + it('should call triggerPixel if wurl is defined', function () { + const clonedResponse = utils.deepClone(RESPONSE_OPENRTB); + clonedResponse.seatbid[0].bid[0].ext.prebid.events = { + win: 'https://wurl.org' + }; + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); + + events.emit(CONSTANTS.EVENTS.BID_WON, { + auctionId: '173afb6d132ba3', + adId: '1000' + }); + + sinon.assert.calledOnce(addBidResponse); + expect(utils.triggerPixel.called).to.be.true; + expect(utils.triggerPixel.getCall(0).args[0]).to.include('https://wurl.org'); + }); + + it('should not call triggerPixel if the wurl cache does not contain the winning bid', function () { + const clonedResponse = utils.deepClone(RESPONSE_OPENRTB); + clonedResponse.seatbid[0].bid[0].ext.prebid.events = { + win: 'https://wurl.org' + }; + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); + + events.emit(CONSTANTS.EVENTS.BID_WON, { + auctionId: '173afb6d132ba3', + adId: 'missingAdId' + }); + + sinon.assert.calledOnce(addBidResponse) + expect(utils.triggerPixel.called).to.be.false; + }); + + it('should not call triggerPixel if wurl is undefined', function () { + const clonedResponse = utils.deepClone(RESPONSE_OPENRTB); + clonedResponse.seatbid[0].bid[0].ext.prebid.events = {}; + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); + + events.emit(CONSTANTS.EVENTS.BID_WON, { + auctionId: '173afb6d132ba3', + adId: '1060' + }); + + sinon.assert.calledOnce(addBidResponse) + expect(utils.triggerPixel.called).to.be.false; + }); + }) + describe('s2sConfig', function () { - let xhr; - let requests; let logErrorSpy; beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); logErrorSpy = sinon.spy(utils, 'logError'); resetSyncedStatus(); }); afterEach(function () { - xhr.restore(); utils.logError.restore(); }); @@ -1696,8 +2164,8 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['appnexus']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig).to.have.property('endpoint', '//prebid.adnxs.com/pbs/v1/openrtb2/auction'); - expect(vendorConfig).to.have.property('syncEndpoint', '//prebid.adnxs.com/pbs/v1/cookie_sync'); + expect(vendorConfig).to.have.property('endpoint', 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'); + expect(vendorConfig).to.have.property('syncEndpoint', 'https://prebid.adnxs.com/pbs/v1/cookie_sync'); expect(vendorConfig).to.have.property('timeout', 750); }); @@ -1717,20 +2185,28 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['rubicon']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig).to.have.property('endpoint', '//prebid-server.rubiconproject.com/openrtb2/auction'); - expect(vendorConfig).to.have.property('syncEndpoint', '//prebid-server.rubiconproject.com/cookie_sync'); + expect(vendorConfig).to.have.property('endpoint', 'https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(vendorConfig).to.have.property('syncEndpoint', 'https://prebid-server.rubiconproject.com/cookie_sync'); expect(vendorConfig).to.have.property('timeout', 750); }); it('should return proper defaults', function () { + const options = { + accountId: 'abc', + bidders: ['rubicon'], + defaultVendor: 'rubicon', + timeout: 750 + }; + + config.setConfig({ s2sConfig: options }); expect(config.getConfig('s2sConfig')).to.deep.equal({ 'accountId': 'abc', 'adapter': 'prebidServer', 'bidders': ['rubicon'], 'defaultVendor': 'rubicon', 'enabled': true, - 'endpoint': '//prebid-server.rubiconproject.com/openrtb2/auction', - 'syncEndpoint': '//prebid-server.rubiconproject.com/cookie_sync', + 'endpoint': 'https://prebid-server.rubiconproject.com/openrtb2/auction', + 'syncEndpoint': 'https://prebid-server.rubiconproject.com/cookie_sync', 'timeout': 750 }) }); @@ -1751,8 +2227,8 @@ describe('S2S Adapter', function () { accountId: 'abc', bidders: ['rubicon'], defaultVendor: 'rubicon', - endpoint: '//prebid-server.rubiconproject.com/openrtb2/auction', - syncEndpoint: '//prebid-server.rubiconproject.com/cookie_sync', + endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + syncEndpoint: 'https://prebid-server.rubiconproject.com/cookie_sync', }) }); @@ -1775,6 +2251,15 @@ describe('S2S Adapter', function () { }) }); + it('should set default s2s ttl', function () { + config.setConfig({ + s2sConfig: { + defaultTtl: 30 + } + }); + expect(config.getConfig('s2sConfig').defaultTtl).to.deep.equal(30); + }); + it('should set syncUrlModifier', function () { config.setConfig({ s2sConfig: { @@ -1856,7 +2341,7 @@ describe('S2S Adapter', function () { adapter.callBids(request, bidRequest, addBidResponse, done, ajax); - const requestBid = JSON.parse(requests[0].requestBody); + const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.deep.equal(['appnexus', 'rubicon']); }); }); diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js index cd414a70236..e87be40314c 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -1,12 +1,10 @@ -import prebidmanagerAnalytics from 'modules/prebidmanagerAnalyticsAdapter'; +import prebidmanagerAnalytics from 'modules/prebidmanagerAnalyticsAdapter.js'; import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; let events = require('src/events'); let constants = require('src/constants.json'); describe('Prebid Manager Analytics Adapter', function () { - let xhr; - let requests; - let bidWonEvent = { 'bidderCode': 'appnexus', 'width': 300, @@ -33,18 +31,8 @@ describe('Prebid Manager Analytics Adapter', function () { 'adUrl': 'ad url' }; - before(function () { - xhr = sinon.useFakeXMLHttpRequest(); - xhr.onCreate = request => requests.push(request); - }); - - after(function () { - xhr.restore(); - }); - describe('Prebid Manager Analytic tests', function () { beforeEach(function () { - requests = []; sinon.stub(events, 'getEvents').returns([]); }); @@ -67,7 +55,6 @@ describe('Prebid Manager Analytics Adapter', function () { }); it('bid won event', function() { - xhr.onCreate = request => requests.push(request); let bundleId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; prebidmanagerAnalytics.enableAnalytics({ provider: 'prebidmanager', @@ -79,11 +66,11 @@ describe('Prebid Manager Analytics Adapter', function () { events.emit(constants.EVENTS.BID_WON, bidWonEvent); prebidmanagerAnalytics.flush(); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://endpoint.prebidmanager.com/endpoint'); - expect(requests[0].requestBody.substring(0, 2)).to.equal('1:'); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://endpoint.prebidmanager.com/endpoint'); + expect(server.requests[0].requestBody.substring(0, 2)).to.equal('1:'); - const pmEvents = JSON.parse(requests[0].requestBody.substring(2)); + const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); expect(pmEvents.pageViewId).to.exist; expect(pmEvents.bundleId).to.equal(bundleId); expect(pmEvents.ver).to.equal(1); @@ -114,4 +101,38 @@ describe('Prebid Manager Analytics Adapter', function () { sinon.assert.callCount(prebidmanagerAnalytics.track, 6); }); }); + + describe('build utm tag data', function () { + beforeEach(function () { + localStorage.setItem('pm_utm_source', 'utm_source'); + localStorage.setItem('pm_utm_medium', 'utm_medium'); + localStorage.setItem('pm_utm_campaign', 'utm_camp'); + localStorage.setItem('pm_utm_term', ''); + localStorage.setItem('pm_utm_content', ''); + }); + afterEach(function () { + localStorage.removeItem('pm_utm_source'); + localStorage.removeItem('pm_utm_medium'); + localStorage.removeItem('pm_utm_campaign'); + localStorage.removeItem('pm_utm_term'); + localStorage.removeItem('pm_utm_content'); + prebidmanagerAnalytics.disableAnalytics() + }); + it('should build utm data from local storage', function () { + prebidmanagerAnalytics.enableAnalytics({ + provider: 'prebidmanager', + options: { + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); + + expect(pmEvents.utmTags.utm_source).to.equal('utm_source'); + expect(pmEvents.utmTags.utm_medium).to.equal('utm_medium'); + expect(pmEvents.utmTags.utm_campaign).to.equal('utm_camp'); + expect(pmEvents.utmTags.utm_term).to.equal(''); + expect(pmEvents.utmTags.utm_content).to.equal(''); + }); + }); }); diff --git a/test/spec/modules/priceFloorsSchema.json b/test/spec/modules/priceFloorsSchema.json new file mode 100644 index 00000000000..7b524da381e --- /dev/null +++ b/test/spec/modules/priceFloorsSchema.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "description": "A Price Floors object which is parsed and used to enforce dynamic floors depending on the properties of the json", + "type": "object", + "title": "Price Floors Enforcement", + "required": [ + "schema", + "values" + ], + "properties": { + "schema": { + "type": "object", + "description": "Defines the schema of the rules", + "required": [ + "fields" + ], + "properties": { + "fields": { + "type": "array", + "description": "The list of fields to be used for matching criteria of a bid response with a price floor", + "minItems": 1, + "uniqueItems": true, + "additionalItems": false, + "items": { + "type": "string" + } + }, + "delimiter": { + "type": "string", + "description": "The character used to differentiate the fields inside a single rule", + "examples": [ + "|", + "^", + "~" + ] + } + } + }, + "values": { + "type": "object", + "description": "A object with key : value pairs which constitutes a rule and floor", + "additionalProperties": { + "type": "number" + }, + "minProperties": 1, + "examples": [ + { + "123456/someSlot|300x250|www.prebid.org": 1.5, + "123456/someSlot|300x600|www.prebid.org": 2.5, + "123456/someSlot|300x600|*": 1.2, + "123456/someSlot|*|*": 0.8 + } + ] + }, + "currency": { + "type": "string", + "description": "The three digit Currency Code which the floors are provided in", + "examples": [ + "USD", + "EUR", + "JPY" + ], + "pattern": "^[a-zA-Z]{3}$" + }, + "modelVersion": { + "type": "string", + "description": "The floor modeling name to be used for tracking", + "examples": [ + "Prebid-Floor-Model-1.2" + ] + }, + "skipRate": { + "type": "integer", + "description": "The skip rate as to which flooring will be 'turned off' for a given auction", + "minimum": 0, + "maximum": 100 + }, + "default": { + "type": "number", + "description": "The default floor to use if no entry in the value matches a bid response", + "examples": [ + 0.75 + ] + } + } +} \ No newline at end of file diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js new file mode 100644 index 00000000000..0a006e0eb48 --- /dev/null +++ b/test/spec/modules/priceFloors_spec.js @@ -0,0 +1,1360 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import { getGlobal } from 'src/prebidGlobal.js'; +import CONSTANTS from 'src/constants.json'; +import { + _floorDataForAuction, + getFloorsDataForAuction, + getFirstMatchingFloor, + getFloor, + handleSetFloorsConfig, + requestBidsHook, + isFloorsDataValid, + addBidResponseHook, + fieldMatchingFunctions, + allowedFields +} from 'modules/priceFloors.js'; +import events from 'src/events.js'; + +describe('the price floors module', function () { + let logErrorSpy; + let logWarnSpy; + let sandbox; + let clock; + const basicFloorData = { + modelVersion: 'basic model', + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType'] + }, + values: { + 'banner': 1.0, + 'video': 5.0, + '*': 2.5 + } + }; + const basicFloorConfig = { + enabled: true, + auctionDelay: 0, + endpoint: {}, + enforcement: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + data: basicFloorData + } + const basicBidRequest = { + bidder: 'rubicon', + adUnitCode: 'test_div_1', + auctionId: '1234-56-789', + }; + + function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: { sizes: [[300, 200], [300, 600]] }, native: {}}, + bids: [{bidder: 'someBidder'}, {bidder: 'someOtherBidder'}] + }; + } + beforeEach(function() { + clock = sinon.useFakeTimers(); + sandbox = sinon.sandbox.create(); + logErrorSpy = sinon.spy(utils, 'logError'); + logWarnSpy = sinon.spy(utils, 'logWarn'); + }); + + afterEach(function() { + clock.restore(); + handleSetFloorsConfig({enabled: false}); + sandbox.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + // reset global bidder settings so no weird test side effects + getGlobal().bidderSettings = {}; + }); + + describe('getFloorsDataForAuction', function () { + it('converts basic input floor data into a floorData map for the auction correctly', function () { + // basic input where nothing needs to be updated + expect(getFloorsDataForAuction(basicFloorData)).to.deep.equal(basicFloorData); + + // if cur and delim not defined then default to correct ones (usd and |) + let inputFloorData = utils.deepClone(basicFloorData); + delete inputFloorData.currency; + delete inputFloorData.schema.delimiter; + expect(getFloorsDataForAuction(inputFloorData)).to.deep.equal(basicFloorData); + + // should not use defaults if differing values + inputFloorData.currency = 'EUR' + inputFloorData.schema.delimiter = '^' + let resultingData = getFloorsDataForAuction(inputFloorData); + expect(resultingData.currency).to.equal('EUR'); + expect(resultingData.schema.delimiter).to.equal('^'); + }); + + it('converts more complex floor data correctly', function () { + let inputFloorData = { + schema: { + fields: ['mediaType', 'size', 'domain'] + }, + values: { + 'banner|300x250|prebid.org': 1.0, + 'video|640x480|prebid.org': 5.0, + 'banner|728x90|rubicon.com': 3.5, + 'video|600x300|appnexus.com': 3.5, + '*|*|prebid.org': 3.5, + } + }; + let resultingData = getFloorsDataForAuction(inputFloorData); + expect(resultingData).to.deep.equal({ + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType', 'size', 'domain'] + }, + values: { + 'banner|300x250|prebid.org': 1.0, + 'video|640x480|prebid.org': 5.0, + 'banner|728x90|rubicon.com': 3.5, + 'video|600x300|appnexus.com': 3.5, + '*|*|prebid.org': 3.5, + } + }); + }); + + it('adds adUnitCode to the schema if the floorData comes from adUnit level to maintain scope', function () { + let inputFloorData = utils.deepClone(basicFloorData); + let resultingData = getFloorsDataForAuction(inputFloorData, 'test_div_1'); + expect(resultingData).to.deep.equal({ + modelVersion: 'basic model', + currency: 'USD', + schema: { + delimiter: '|', + fields: ['adUnitCode', 'mediaType'] + }, + values: { + 'test_div_1|banner': 1.0, + 'test_div_1|video': 5.0, + 'test_div_1|*': 2.5 + } + }); + + // uses the right delim if not | + inputFloorData.schema.delimiter = '^'; + resultingData = getFloorsDataForAuction(inputFloorData, 'this_is_a_div'); + expect(resultingData).to.deep.equal({ + modelVersion: 'basic model', + currency: 'USD', + schema: { + delimiter: '^', + fields: ['adUnitCode', 'mediaType'] + }, + values: { + 'this_is_a_div^banner': 1.0, + 'this_is_a_div^video': 5.0, + 'this_is_a_div^*': 2.5 + } + }); + }); + }); + + describe('getFirstMatchingFloor', function () { + it('selects the right floor for different mediaTypes', function () { + // banner with * size (not in rule file so does not do anything) + expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ + matchingFloor: 1.0, + matchingData: 'banner', + matchingRule: 'banner' + }); + // video with * size (not in rule file so does not do anything) + expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'video', size: '*'})).to.deep.equal({ + matchingFloor: 5.0, + matchingData: 'video', + matchingRule: 'video' + }); + // native (not in the rule list) with * size (not in rule file so does not do anything) + expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'native', size: '*'})).to.deep.equal({ + matchingFloor: 2.5, + matchingData: 'native', + matchingRule: '*' + }); + }); + it('selects the right floor for different sizes', function () { + let inputFloorData = { + currency: 'USD', + schema: { + delimiter: '|', + fields: ['size'] + }, + values: { + '300x250': 1.1, + '640x480': 2.2, + '728x90': 3.3, + '600x300': 4.4, + '*': 5.5, + } + } + // banner with 300x250 size + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: [300, 250]})).to.deep.equal({ + matchingFloor: 1.1, + matchingData: '300x250', + matchingRule: '300x250' + }); + // video with 300x250 size + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + matchingFloor: 1.1, + matchingData: '300x250', + matchingRule: '300x250' + }); + // native (not in the rule list) with 300x600 size + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'native', size: [600, 300]})).to.deep.equal({ + matchingFloor: 4.4, + matchingData: '600x300', + matchingRule: '600x300' + }); + // n/a mediaType with a size not in file should go to catch all + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: undefined, size: [1, 1]})).to.deep.equal({ + matchingFloor: 5.5, + matchingData: '1x1', + matchingRule: '*' + }); + }); + it('selects the right floor for more complex rules', function () { + let inputFloorData = { + currency: 'USD', + schema: { + delimiter: '^', + fields: ['adUnitCode', 'mediaType', 'size'] + }, + values: { + 'test_div_1^banner^300x250': 1.1, + 'test_div_1^video^640x480': 2.2, + 'test_div_2^*^*': 3.3, + '*^banner^300x250': 4.4, + 'weird_div^*^300x250': 5.5 + }, + default: 0.5 + }; + // banner with 300x250 size + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: [300, 250]})).to.deep.equal({ + matchingFloor: 1.1, + matchingData: 'test_div_1^banner^300x250', + matchingRule: 'test_div_1^banner^300x250' + }); + // video with 300x250 size -> No matching rule so should use default + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + matchingFloor: 0.5, + matchingData: 'test_div_1^video^300x250', + matchingRule: undefined + }); + // remove default and should still return the same floor as above since matches are cached + delete inputFloorData.default; + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + matchingFloor: 0.5, + matchingData: 'test_div_1^video^300x250', + matchingRule: undefined + }); + // update adUnitCode to test_div_2 with weird other params + let newBidRequest = { ...basicBidRequest, adUnitCode: 'test_div_2' } + expect(getFirstMatchingFloor(inputFloorData, newBidRequest, {mediaType: 'badmediatype', size: [900, 900]})).to.deep.equal({ + matchingFloor: 3.3, + matchingData: 'test_div_2^badmediatype^900x900', + matchingRule: 'test_div_2^*^*' + }); + }); + it('it does not break if floorData has bad values', function () { + let inputFloorData = {}; + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ + matchingFloor: undefined + }); + // if default is there use it + inputFloorData = { default: 5.0 }; + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ + matchingFloor: 5.0 + }); + }); + }); + describe('pre-auction tests', function () { + let exposedAdUnits; + const validateBidRequests = (getFloorExpected, FloorDataExpected) => { + exposedAdUnits.forEach(adUnit => adUnit.bids.forEach(bid => { + expect(bid.hasOwnProperty('getFloor')).to.equal(getFloorExpected); + expect(bid.floorData).to.deep.equal(FloorDataExpected); + })); + }; + const runStandardAuction = (adUnits = [getAdUnitMock('test_div_1')]) => { + requestBidsHook(config => exposedAdUnits = config.adUnits, { + auctionId: basicBidRequest.auctionId, + adUnits, + }); + }; + let fakeFloorProvider; + let actualAllowedFields = allowedFields; + let actualFieldMatchingFunctions = fieldMatchingFunctions; + const defaultAllowedFields = [...allowedFields]; + const defaultMatchingFunctions = {...fieldMatchingFunctions}; + beforeEach(function() { + fakeFloorProvider = sinon.fakeServer.create(); + }); + afterEach(function() { + fakeFloorProvider.restore(); + exposedAdUnits = undefined; + actualAllowedFields = [...defaultAllowedFields]; + actualFieldMatchingFunctions = {...defaultMatchingFunctions}; + }); + it('should not do floor stuff if no resulting floor object can be resolved for auciton', function () { + handleSetFloorsConfig({ + ...basicFloorConfig, + data: undefined + }); + runStandardAuction(); + validateBidRequests(false, { + skipped: true, + modelVersion: undefined, + location: 'noData', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined + }); + }); + it('should use adUnit level data if not setConfig or fetch has occured', function () { + handleSetFloorsConfig({ + ...basicFloorConfig, + data: undefined + }); + // attach floor data onto an adUnit and run an auction + let adUnitWithFloors1 = { + ...getAdUnitMock('adUnit-Div-1'), + floors: { + ...basicFloorData, + modelVersion: 'adUnit Model Version', // change the model name + } + }; + let adUnitWithFloors2 = { + ...getAdUnitMock('adUnit-Div-2'), + floors: { + ...basicFloorData, + values: { + 'banner': 5.0, + '*': 10.4 + } + } + }; + runStandardAuction([adUnitWithFloors1, adUnitWithFloors2]); + validateBidRequests(true, { + skipped: false, + modelVersion: 'adUnit Model Version', + location: 'adUnit', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined + }); + }); + it('bidRequests should have getFloor function and flooring meta data when setConfig occurs', function () { + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider'}); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'floorprovider' + }); + }); + it('should pick the right floorProvider', function () { + let inputFloors = { + ...basicFloorConfig, + floorProvider: 'providerA', + data: { + ...basicFloorData, + floorProvider: 'providerB', + } + }; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'providerB' + }); + + // if not at data level take top level + delete inputFloors.data.floorProvider; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'providerA' + }); + + // if none should be undefined + delete inputFloors.floorProvider; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined + }); + }); + it('should take the right skipRate depending on input', function () { + // first priority is data object + sandbox.stub(Math, 'random').callsFake(() => 0.99); + let inputFloors = { + ...basicFloorConfig, + skipRate: 10, + data: { + ...basicFloorData, + skipRate: 50 + } + }; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 50, + fetchStatus: undefined, + floorProvider: undefined + }); + + // if that does not exist uses topLevel skipRate setting + delete inputFloors.data.skipRate; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 10, + fetchStatus: undefined, + floorProvider: undefined + }); + + // if that is not there defaults to zero + delete inputFloors.skipRate; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined + }); + }); + it('should randomly pick a model if floorsSchemaVersion is 2', function () { + let inputFloors = { + ...basicFloorConfig, + floorProvider: 'floorprovider', + data: { + floorsSchemaVersion: 2, + currency: 'USD', + modelGroups: [ + { + modelVersion: 'model-1', + modelWeight: 10, + schema: { + delimiter: '|', + fields: ['mediaType'] + }, + values: { + 'banner': 1.0, + '*': 2.5 + } + }, { + modelVersion: 'model-2', + modelWeight: 40, + schema: { + delimiter: '|', + fields: ['size'] + }, + values: { + '300x250': 1.0, + '*': 2.5 + } + }, { + modelVersion: 'model-3', + modelWeight: 50, + schema: { + delimiter: '|', + fields: ['domain'] + }, + values: { + 'www.prebid.org': 1.0, + '*': 2.5 + } + } + ] + } + }; + handleSetFloorsConfig(inputFloors); + + // stub random to give us wanted vals + let randValue; + sandbox.stub(Math, 'random').callsFake(() => randValue); + + // 0 - 10 should use first model + randValue = 0.05; + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'model-1', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'floorprovider' + }); + + // 11 - 50 should use second model + randValue = 0.40; + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'model-2', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'floorprovider' + }); + + // 51 - 100 should use third model + randValue = 0.75; + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'model-3', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'floorprovider' + }); + }); + it('should not overwrite previous data object if the new one is bad', function () { + handleSetFloorsConfig({...basicFloorConfig}); + handleSetFloorsConfig({ + ...basicFloorConfig, + data: undefined + }); + handleSetFloorsConfig({ + ...basicFloorConfig, + data: 5 + }); + handleSetFloorsConfig({ + ...basicFloorConfig, + data: { + schema: {fields: ['thisIsNotAllowedSoShouldFail']}, + values: {'*': 1.2}, + modelVersion: 'FAIL' + } + }); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined + }); + }); + it('should dynamically add new schema fileds and functions if added via setConfig', function () { + let deviceSpoof; + handleSetFloorsConfig({ + ...basicFloorConfig, + data: { + schema: {fields: ['deviceType']}, + values: { + 'mobile': 1.0, + 'desktop': 2.0, + 'tablet': 3.0, + '*': 4.0 + } + }, + additionalSchemaFields: { + deviceType: () => deviceSpoof + } + }); + expect(allowedFields).to.contain('deviceType'); + expect(fieldMatchingFunctions['deviceType']).to.be.a('function'); + + // run getFloor to make sure it selcts right stuff! (other params do not matter since we only are testing deviceType) + runStandardAuction(); + + // set deviceType to mobile; + deviceSpoof = 'mobile'; + exposedAdUnits[0].bids[0].auctionId = basicBidRequest.auctionId + expect(exposedAdUnits[0].bids[0].getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.0 // 'mobile': 1.0, + }); + + // set deviceType to desktop; + deviceSpoof = 'desktop'; + expect(exposedAdUnits[0].bids[0].getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 // 'desktop': 2.0, + }); + + // set deviceType to tablet; + deviceSpoof = 'tablet'; + expect(exposedAdUnits[0].bids[0].getFloor()).to.deep.equal({ + currency: 'USD', + floor: 3.0 // 'tablet': 3.0 + }); + + // set deviceType to unknown; + deviceSpoof = 'unknown'; + expect(exposedAdUnits[0].bids[0].getFloor()).to.deep.equal({ + currency: 'USD', + floor: 4.0 // '*': 4.0 + }); + }); + it('Should continue auction of delay is hit without a response from floor provider', function () { + handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // start the auction it should delay and not immediately call `continueAuction` + runStandardAuction(); + + // exposedAdUnits should be undefined if the auction has not continued + expect(exposedAdUnits).to.be.undefined; + + // hit the delay + clock.tick(250); + + // log warn should be called and adUnits not undefined + expect(logWarnSpy.calledOnce).to.equal(true); + expect(exposedAdUnits).to.not.be.undefined; + + // the exposedAdUnits should be from the fetch not setConfig level data + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: 'timeout', + floorProvider: undefined + }); + fakeFloorProvider.respond(); + }); + it('It should fetch if config has url and bidRequests have fetch level flooring meta data', function () { + // init the fake server with response stuff + let fetchFloorData = { + ...basicFloorData, + modelVersion: 'fetch model name', // change the model name + }; + fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); + + // run setConfig indicating fetch + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // floor provider should be called + expect(fakeFloorProvider.requests.length).to.equal(1); + expect(fakeFloorProvider.requests[0].url).to.equal('http://www.fakeFloorProvider.json'); + + // start the auction it should delay and not immediately call `continueAuction` + runStandardAuction(); + + // exposedAdUnits should be undefined if the auction has not continued + expect(exposedAdUnits).to.be.undefined; + + // make the fetch respond + fakeFloorProvider.respond(); + expect(exposedAdUnits).to.not.be.undefined; + + // the exposedAdUnits should be from the fetch not setConfig level data + // and fetchStatus is success since fetch worked + validateBidRequests(true, { + skipped: false, + modelVersion: 'fetch model name', + location: 'fetch', + skipRate: 0, + fetchStatus: 'success', + floorProvider: 'floorprovider' + }); + }); + it('it should correctly overwrite floorProvider with fetch provider', function () { + // init the fake server with response stuff + let fetchFloorData = { + ...basicFloorData, + floorProvider: 'floorProviderD', // change the floor provider + modelVersion: 'fetch model name', // change the model name + }; + fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); + + // run setConfig indicating fetch + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorproviderC', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // floor provider should be called + expect(fakeFloorProvider.requests.length).to.equal(1); + expect(fakeFloorProvider.requests[0].url).to.equal('http://www.fakeFloorProvider.json'); + + // start the auction it should delay and not immediately call `continueAuction` + runStandardAuction(); + + // exposedAdUnits should be undefined if the auction has not continued + expect(exposedAdUnits).to.be.undefined; + + // make the fetch respond + fakeFloorProvider.respond(); + + // the exposedAdUnits should be from the fetch not setConfig level data + // and fetchStatus is success since fetch worked + validateBidRequests(true, { + skipped: false, + modelVersion: 'fetch model name', + location: 'fetch', + skipRate: 0, + fetchStatus: 'success', + floorProvider: 'floorProviderD' + }); + }); + it('it should correctly overwrite skipRate with fetch skipRate', function () { + // so floors does not skip + sandbox.stub(Math, 'random').callsFake(() => 0.99); + // init the fake server with response stuff + let fetchFloorData = { + ...basicFloorData, + modelVersion: 'fetch model name', // change the model name + }; + fetchFloorData.skipRate = 95; + fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); + + // run setConfig indicating fetch + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // floor provider should be called + expect(fakeFloorProvider.requests.length).to.equal(1); + expect(fakeFloorProvider.requests[0].url).to.equal('http://www.fakeFloorProvider.json'); + + // start the auction it should delay and not immediately call `continueAuction` + runStandardAuction(); + + // exposedAdUnits should be undefined if the auction has not continued + expect(exposedAdUnits).to.be.undefined; + + // make the fetch respond + fakeFloorProvider.respond(); + expect(exposedAdUnits).to.not.be.undefined; + + // the exposedAdUnits should be from the fetch not setConfig level data + // and fetchStatus is success since fetch worked + validateBidRequests(true, { + skipped: false, + modelVersion: 'fetch model name', + location: 'fetch', + skipRate: 95, + fetchStatus: 'success', + floorProvider: 'floorprovider' + }); + }); + it('Should not break if floor provider returns 404', function () { + // run setConfig indicating fetch + handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // run the auction and make server respond with 404 + fakeFloorProvider.respond(); + runStandardAuction(); + + // error should have been called for fetch error + expect(logErrorSpy.calledOnce).to.equal(true); + // should have caught the response error and still used setConfig data + // and fetch failed is true + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: 'error', + floorProvider: undefined + }); + }); + it('Should not break if floor provider returns non json', function () { + fakeFloorProvider.respondWith('Not valid response'); + + // run setConfig indicating fetch + handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // run the auction and make server respond + fakeFloorProvider.respond(); + runStandardAuction(); + + // error should have been called for response floor data not being valid + expect(logErrorSpy.calledOnce).to.equal(true); + // should have caught the response error and still used setConfig data + // and fetchStatus is 'success' but location is setConfig since it had bad data + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: 'success', + floorProvider: undefined + }); + }); + it('should handle not using fetch correctly', function () { + // run setConfig twice indicating fetch + fakeFloorProvider.respondWith(JSON.stringify(basicFloorData)); + handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // log warn should be called and server only should have one request + expect(logWarnSpy.calledOnce).to.equal(true); + expect(fakeFloorProvider.requests.length).to.equal(1); + expect(fakeFloorProvider.requests[0].url).to.equal('http://www.fakeFloorProvider.json'); + + // now we respond and then run again it should work and make another request + fakeFloorProvider.respond(); + handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + fakeFloorProvider.respond(); + + // now warn still only called once and server called twice + expect(logWarnSpy.calledOnce).to.equal(true); + expect(fakeFloorProvider.requests.length).to.equal(2); + + // should log error if method is not GET for now + expect(logErrorSpy.calledOnce).to.equal(false); + handleSetFloorsConfig({...basicFloorConfig, endpoint: {url: 'http://www.fakeFloorProvider.json', method: 'POST'}}); + expect(logErrorSpy.calledOnce).to.equal(true); + }); + describe('isFloorsDataValid', function () { + it('should return false if unknown floorsSchemaVersion', function () { + let inputFloorData = utils.deepClone(basicFloorData); + inputFloorData.floorsSchemaVersion = 3; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + }); + it('should work correctly for fields array', function () { + let inputFloorData = utils.deepClone(basicFloorData); + expect(isFloorsDataValid(inputFloorData)).to.to.equal(true); + + // no fields array + delete inputFloorData.schema.fields; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + + // Fields is not an array + inputFloorData.schema.fields = {}; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + inputFloorData.schema.fields = undefined; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + inputFloorData.schema.fields = 'adUnitCode'; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + + // fields has a value that is not one of the "allowed" fields + inputFloorData.schema.fields = ['adUnitCode', 'notValidMapping']; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + }); + it('should work correctly for values object', function () { + let inputFloorData = utils.deepClone(basicFloorData); + expect(isFloorsDataValid(inputFloorData)).to.to.equal(true); + + // no values object + delete inputFloorData.values; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + + // values is not correct type + inputFloorData.values = []; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + inputFloorData.values = '123455/slot'; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + + // is an object but structure is wrong + inputFloorData.values = { + 'banner': 'not a floor value' + }; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + inputFloorData.values = { + 'banner': undefined + }; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + + // should be true if at least one rule is valid + inputFloorData.schema.fields = ['adUnitCode', 'mediaType']; + inputFloorData.values = { + 'banner': 1.0, + 'test-div-1|native': 1.0, // only valid rule should still work and delete the other rules + 'video': 1.0, + '*': 1.0 + }; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(true); + expect(inputFloorData.values).to.deep.equal({ 'test-div-1|native': 1.0 }); + }); + it('should work correctly for floorsSchemaVersion 2', function () { + let inputFloorData = { + floorsSchemaVersion: 2, + currency: 'USD', + modelGroups: [ + { + modelVersion: 'model-1', + modelWeight: 10, + schema: { + delimiter: '|', + fields: ['mediaType'] + }, + values: { + 'banner': 1.0, + '*': 2.5 + } + }, { + modelVersion: 'model-2', + modelWeight: 40, + schema: { + delimiter: '|', + fields: ['size'] + }, + values: { + '300x250': 1.0, + '*': 2.5 + } + }, { + modelVersion: 'model-3', + modelWeight: 50, + schema: { + delimiter: '|', + fields: ['domain'] + }, + values: { + 'www.prebid.org': 1.0, + '*': 2.5 + } + } + ] + }; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(true); + + // remove one of the modelWeight's and it should be false + delete inputFloorData.modelGroups[1].modelWeight; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + inputFloorData.modelGroups[1].modelWeight = 40; + + // remove values from a model and it should not validate + const tempValues = {...inputFloorData.modelGroups[0].values}; + delete inputFloorData.modelGroups[0].values; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + inputFloorData.modelGroups[0].values = tempValues; + + // modelGroups should be an array and have at least one entry + delete inputFloorData.modelGroups; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + inputFloorData.modelGroups = []; + expect(isFloorsDataValid(inputFloorData)).to.to.equal(false); + }); + }); + describe('getFloor', function () { + let bidRequest = { + ...basicBidRequest, + getFloor + }; + it('returns empty if no matching data for auction is found', function () { + expect(bidRequest.getFloor({})).to.deep.equal({}); + }); + it('picks the right rule depending on input', function () { + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + // empty params into getFloor should use default of banner * FloorData Curr + let inputParams = {}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 1.0 + }); + + // ask for banner + inputParams = {mediaType: 'banner'}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 1.0 + }); + + // ask for video + inputParams = {mediaType: 'video'}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 5.0 + }); + + // ask for * + inputParams = {mediaType: '*'}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 2.5 + }); + }); + it('picks the right rule with more complex rules', function () { + _floorDataForAuction[bidRequest.auctionId] = { + ...basicFloorConfig, + data: { + currency: 'USD', + schema: { fields: ['mediaType', 'size'], delimiter: '|' }, + values: { + 'banner|300x250': 0.5, + 'banner|300x600': 1.5, + 'banner|728x90': 2.5, + 'banner|*': 3.5, + 'video|640x480': 4.5, + 'video|*': 5.5 + }, + default: 10.0 + } + }; + + // assumes banner * + let inputParams = {}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 3.5 + }); + + // ask for banner with a size + inputParams = {mediaType: 'banner', size: [300, 600]}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 1.5 + }); + + // ask for video with a size + inputParams = {mediaType: 'video', size: [640, 480]}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 4.5 + }); + + // ask for video with a size not in rules (should pick rule which has video and *) + inputParams = {mediaType: 'video', size: [111, 222]}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 5.5 + }); + + // ask for native * but no native rule so should use default value if there + inputParams = {mediaType: 'native', size: '*'}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 10.0 + }); + }); + it('should round up to 4 decimal places', function () { + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidRequest.auctionId].data.values = { + 'banner': 1.777777, + 'video': 1.1111111, + }; + + // assumes banner * + let inputParams = {mediaType: 'banner'}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 1.7778 + }); + + // assumes banner * + inputParams = {mediaType: 'video'}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal({ + currency: 'USD', + floor: 1.1112 + }); + }); + it('should return the adjusted floor if bidder has cpm adjustment function', function () { + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.5; + }, + }, + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.75; + }, + } + }; + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus' + }; + + // the conversion should be what the bidder would need to return in order to match the actual floor + // rubicon + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 // a 2.0 bid after rubicons cpm adjustment would be 1.0 and thus is the floor after adjust + }); + + // appnexus + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 // 1.3334 * 0.75 = 1.000005 which is the floor (we cut off getFloor at 4 decimal points) + }); + }); + it('should correctly pick the right attributes if * is passed in and context can be assumed', function () { + let inputBidReq = { + bidder: 'rubicon', + adUnitCode: 'test_div_2', + auctionId: '987654321', + mediaTypes: { + video: {} + }, + getFloor + }; + _floorDataForAuction[inputBidReq.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[inputBidReq.auctionId].data.values = { + '*': 1.0, + 'banner': 3.0, + 'video': 5.0 + }; + + // because bid req only has video, if a bidder asks for a floor for * we can actually give them the right mediaType + expect(inputBidReq.getFloor({mediaType: '*'})).to.deep.equal({ + currency: 'USD', + floor: 5.0 // 'video': 5.0 + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + + // Same for if only banner is in the input bid + inputBidReq.mediaTypes = {banner: {}}; + expect(inputBidReq.getFloor({mediaType: '*'})).to.deep.equal({ + currency: 'USD', + floor: 3.0 // 'banner': 3.0, + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + + // if both are present then it will really use the * + inputBidReq.mediaTypes = {banner: {}, video: {}}; + expect(inputBidReq.getFloor({mediaType: '*'})).to.deep.equal({ + currency: 'USD', + floor: 1.0 // '*': 1.0, + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + + // now if size can be inferred (meaning only one size is in the specified mediaType, it will use it) + _floorDataForAuction[inputBidReq.auctionId].data.schema.fields = ['mediaType', 'size']; + _floorDataForAuction[inputBidReq.auctionId].data.values = { + '*|*': 1.0, + 'banner|300x250': 2.0, + 'banner|728x90': 3.0, + 'banner|*': 4.0, + 'video|300x250': 5.0, + 'video|728x90': 6.0, + 'video|*': 7.0 + }; + // mediaType is banner and only one size, so if someone asks for banner * we should give them banner 300x250 + // instead of banner|* + inputBidReq.mediaTypes = {banner: {sizes: [[300, 250]]}}; + expect(inputBidReq.getFloor({mediaType: 'banner', size: '*'})).to.deep.equal({ + currency: 'USD', + floor: 2.0 // 'banner|300x250': 2.0, + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + + // now for video it should look at playersize (prebid core translates playersize into typical array of size arrays) + inputBidReq.mediaTypes = {video: {playerSize: [[728, 90]]}}; + expect(inputBidReq.getFloor({mediaType: 'video', size: '*'})).to.deep.equal({ + currency: 'USD', + floor: 6.0 // 'video|728x90': 6.0, + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + + // Now if multiple sizes are there, it will actually use * since can't infer + inputBidReq.mediaTypes = {banner: {sizes: [[300, 250], [728, 90]]}}; + expect(inputBidReq.getFloor({mediaType: 'banner', size: '*'})).to.deep.equal({ + currency: 'USD', + floor: 4.0 // 'banner|*': 4.0, + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + + // lastly, if you pass in * mediaType and * size it should resolve both if possble + inputBidReq.mediaTypes = {banner: {sizes: [[300, 250]]}}; + expect(inputBidReq.getFloor({mediaType: '*', size: '*'})).to.deep.equal({ + currency: 'USD', + floor: 2.0 // 'banner|300x250': 2.0, + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + + inputBidReq.mediaTypes = {video: {playerSize: [[300, 250]]}}; + expect(inputBidReq.getFloor({mediaType: '*', size: '*'})).to.deep.equal({ + currency: 'USD', + floor: 5.0 // 'video|300x250': 5.0, + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + + // now it has both mediaTypes so will use * mediaType and thus not use sizes either + inputBidReq.mediaTypes = {video: {playerSize: [[300, 250]]}, banner: {sizes: [[300, 250]]}}; + expect(inputBidReq.getFloor({mediaType: '*', size: '*'})).to.deep.equal({ + currency: 'USD', + floor: 1.0 // '*|*': 1.0, + }); + delete _floorDataForAuction[inputBidReq.auctionId].data.matchingInputs; + }); + }); + }); + describe('bidResponseHook tests', function () { + let returnedBidResponse; + let bidderRequest = { + bidderCode: 'appnexus', + auctionId: '123456', + bids: [{ + bidder: 'appnexus', + adUnitCode: 'test_div_1', + auctionId: '123456', + bidId: '1111' + }] + }; + let basicBidResponse = { + bidderCode: 'appnexus', + width: 300, + height: 250, + cpm: 0.5, + mediaType: 'banner', + requestId: '1111', + }; + beforeEach(function () { + returnedBidResponse = {}; + }); + function runBidResponse(bidResp = basicBidResponse) { + let next = (adUnitCode, bid) => { + returnedBidResponse = bid; + }; + addBidResponseHook.bind({ bidderRequest })(next, bidResp.adUnitCode, bidResp); + }; + it('continues with the auction if not floors data is present without any flooring', function () { + runBidResponse(); + expect(returnedBidResponse).to.not.haveOwnProperty('floorData'); + }); + it('if no matching rule it should not floor and should call log warn', function () { + _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidderRequest.auctionId].data.values = { 'video': 1.0 }; + runBidResponse(); + expect(returnedBidResponse).to.not.haveOwnProperty('floorData'); + expect(logWarnSpy.calledOnce).to.equal(true); + }); + it('if it finds a rule and floors should update the bid accordingly', function () { + _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidderRequest.auctionId].data.values = { 'banner': 1.0 }; + runBidResponse(); + expect(returnedBidResponse).to.haveOwnProperty('floorData'); + expect(returnedBidResponse.status).to.equal(CONSTANTS.BID_STATUS.BID_REJECTED); + expect(returnedBidResponse.cpm).to.equal(0); + }); + it('if it finds a rule and does not floor should update the bid accordingly', function () { + _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidderRequest.auctionId].data.values = { 'banner': 0.3 }; + runBidResponse(); + expect(returnedBidResponse).to.haveOwnProperty('floorData'); + expect(returnedBidResponse.floorData).to.deep.equal({ + floorValue: 0.3, + floorCurrency: 'USD', + floorRule: 'banner', + cpmAfterAdjustments: 0.5, + enforcements: { + bidAdjustment: true, + enforceJS: true, + enforcePBS: false, + floorDeals: false + }, + matchedFields: { + mediaType: 'banner' + } + }); + expect(returnedBidResponse.cpm).to.equal(0.5); + }); + it('if should work with more complex rules and update accordingly', function () { + _floorDataForAuction[bidderRequest.auctionId] = { + ...basicFloorConfig, + data: { + currency: 'USD', + schema: { fields: ['mediaType', 'size'], delimiter: '|' }, + values: { + 'banner|300x250': 0.5, + 'banner|300x600': 1.5, + 'banner|728x90': 2.5, + 'banner|*': 3.5, + 'video|640x480': 4.5, + 'video|*': 5.5 + }, + default: 10.0 + } + }; + runBidResponse(); + expect(returnedBidResponse).to.haveOwnProperty('floorData'); + expect(returnedBidResponse.floorData).to.deep.equal({ + floorValue: 0.5, + floorCurrency: 'USD', + floorRule: 'banner|300x250', + cpmAfterAdjustments: 0.5, + enforcements: { + bidAdjustment: true, + enforceJS: true, + enforcePBS: false, + floorDeals: false + }, + matchedFields: { + mediaType: 'banner', + size: '300x250' + } + }); + expect(returnedBidResponse.cpm).to.equal(0.5); + + // update bidResponse to have different combinations (should pick video|*) + runBidResponse({ + width: 300, + height: 250, + cpm: 7.5, + mediaType: 'video', + requestId: '1111', + }); + expect(returnedBidResponse).to.haveOwnProperty('floorData'); + expect(returnedBidResponse.floorData).to.deep.equal({ + floorValue: 5.5, + floorCurrency: 'USD', + floorRule: 'video|*', + cpmAfterAdjustments: 7.5, + enforcements: { + bidAdjustment: true, + enforceJS: true, + enforcePBS: false, + floorDeals: false + }, + matchedFields: { + mediaType: 'video', + size: '300x250' + } + }); + expect(returnedBidResponse.cpm).to.equal(7.5); + }); + }); + + describe('Post Auction Tests', function () { + let AUCTION_END_EVENT; + beforeEach(function () { + AUCTION_END_EVENT = { + auctionId: '123-45-6789' + }; + }); + it('should wait 3 seconds before deleting auction floor data', function () { + handleSetFloorsConfig({enabled: true}); + _floorDataForAuction[AUCTION_END_EVENT.auctionId] = utils.deepClone(basicFloorConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, AUCTION_END_EVENT); + // should still be here + expect(_floorDataForAuction[AUCTION_END_EVENT.auctionId]).to.not.be.undefined; + // tick for 4 seconds + clock.tick(4000); + // should be undefined now + expect(_floorDataForAuction[AUCTION_END_EVENT.auctionId]).to.be.undefined; + }); + }); +}); diff --git a/test/spec/modules/projectLimeLightBidAdapter_spec.js b/test/spec/modules/projectLimeLightBidAdapter_spec.js index f51e1188b73..778d8eedf7b 100644 --- a/test/spec/modules/projectLimeLightBidAdapter_spec.js +++ b/test/spec/modules/projectLimeLightBidAdapter_spec.js @@ -1,12 +1,13 @@ import {expect} from 'chai'; -import {spec} from '../../../modules/projectLimeLightBidAdapter'; +import {spec} from '../../../modules/projectLimeLightBidAdapter.js'; describe('ProjectLimeLightAdapter', function () { - let bid = { + const bid1 = { bidId: '2dd581a2b6281d', bidder: 'project-limelight', bidderRequestId: '145e1d6a7837c9', params: { + host: 'ads.project-limelight.com', adUnitId: 123, adUnitType: 'banner' }, @@ -14,46 +15,83 @@ describe('ProjectLimeLightAdapter', function () { auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', sizes: [[300, 250]], transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' - }; + } + const bid2 = { + bidId: '58ee9870c3164a', + bidder: 'project-limelight', + bidderRequestId: '209fdaf1c81649', + params: { + host: 'cpm.project-limelight.com', + adUnitId: 456, + adUnitType: 'banner' + }, + placementCode: 'placement_1', + auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', + sizes: [[350, 200]], + transactionId: '068867d1-46ec-40bb-9fa0-e24611786fb4' + } + const bid3 = { + bidId: '019645c7d69460', + bidder: 'project-limelight', + bidderRequestId: 'f2b15f89e77ba6', + params: { + host: 'ads.project-limelight.com', + adUnitId: 789, + adUnitType: 'video' + }, + placementCode: 'placement_2', + auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', + sizes: [[800, 600]], + transactionId: '738d5915-6651-43b9-9b6b-d50517350917' + } describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist; - expect(serverRequest.method).to.exist; - expect(serverRequest.url).to.exist; - expect(serverRequest.data).to.exist; - }); - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST'); - }); + const serverRequests = spec.buildRequests([bid1, bid2, bid3]) + it('Creates two ServerRequests', function() { + expect(serverRequests).to.exist + expect(serverRequests).to.have.lengthOf(2) + }) + serverRequests.forEach(serverRequest => { + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist + expect(serverRequest.method).to.exist + expect(serverRequest.url).to.exist + expect(serverRequest.data).to.exist + }) + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST') + }) + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data + expect(data).to.be.an('object') + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'adUnits') + expect(data.deviceWidth).to.be.a('number') + expect(data.deviceHeight).to.be.a('number') + expect(data.secure).to.be.a('boolean') + data.adUnits.forEach(adUnit => { + expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId') + expect(adUnit.id).to.be.a('number') + expect(adUnit.bidId).to.be.a('string') + expect(adUnit.type).to.be.a('string') + expect(adUnit.transactionId).to.be.a('string') + expect(adUnit.sizes).to.be.an('array') + }) + }) + }) it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://ads.project-limelight.com/hb'); - }); - it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'adUnits'); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.secure).to.be.a('boolean'); - let adUnits = data['adUnits']; - for (let i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId'); - expect(adUnit.id).to.be.a('number'); - expect(adUnit.bidId).to.be.a('string'); - expect(adUnit.type).to.be.a('string'); - expect(adUnit.transactionId).to.be.a('string'); - expect(adUnit.sizes).to.be.an('array'); - } - }); + expect(serverRequests[0].url).to.equal('https://ads.project-limelight.com/hb') + expect(serverRequests[1].url).to.equal('https://cpm.project-limelight.com/hb') + }) + it('Returns valid adUnits', function () { + validateAdUnit(serverRequests[0].data.adUnits[0], bid1) + validateAdUnit(serverRequests[1].data.adUnits[0], bid2) + validateAdUnit(serverRequests[0].data.adUnits[1], bid3) + }) it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.adUnits).to.be.an('array').that.is.empty; - }); - }); + const serverRequests = spec.buildRequests([]) + expect(serverRequests).to.be.an('array').that.is.empty + }) + }) describe('interpretBannerResponse', function () { let resObject = { body: [ { @@ -167,4 +205,55 @@ describe('ProjectLimeLightAdapter', function () { expect(spec.isBidRequestValid(bidFailed)).to.equal(false); }); }); + describe('interpretResponse', function() { + let resObject = { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + }; + it('should skip responses which do not contain required params', function() { + let bidResponses = { + body: [ { + mediaType: 'banner', + cpm: 0.3, + ttl: 1000, + currency: 'USD' + }, resObject ] + } + expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); + }); + it('should skip responses which do not contain expected mediaType', function() { + let bidResponses = { + body: [ { + requestId: '123', + mediaType: 'native', + cpm: 0.3, + creativeId: '123asd', + ttl: 1000, + currency: 'USD' + }, resObject ] + } + expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); + }); + }); }); + +function validateAdUnit(adUnit, bid) { + expect(adUnit.id).to.equal(bid.params.adUnitId) + expect(adUnit.bidId).to.equal(bid.bidId) + expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()) + expect(adUnit.transactionId).to.equal(bid.transactionId) + expect(adUnit.sizes).to.deep.equal(bid.sizes.map(size => { + return { + width: size[0], + height: size[1] + } + })) +} diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 302b7634fbc..410c3c59fb6 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -let spec = require('modules/proxistoreBidAdapter'); +let { spec } = require('modules/proxistoreBidAdapter'); const BIDDER_CODE = 'proxistore'; describe('ProxistoreBidAdapter', function () { @@ -10,6 +10,11 @@ describe('ProxistoreBidAdapter', function () { 'gdprConsent': { 'gdprApplies': true, 'consentString': 'CONSENT_STRING', + 'vendorData': { + 'vendorConsents': { + '418': true + } + } } }; let bid = { @@ -23,24 +28,55 @@ describe('ProxistoreBidAdapter', function () { transactionId: 511916005 }; describe('isBidRequestValid', function () { - it('it should be true if required params are presents', function () { + it('it should be true if required params are presents and there is no info in the local storage', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('it should be false if the value in the localstorage is less than 5minutes of the actual time', function() { + const date = new Date(); + date.setMinutes(date.getMinutes() - 1) + localStorage.setItem(`PX_NoAds_${bid.params.website}`, date) + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('it should be true if the value in the localstorage is more than 5minutes of the actual time', function() { + const date = new Date(); + date.setMinutes(date.getMinutes() - 10) + localStorage.setItem(`PX_NoAds_${bid.params.website}`, date) expect(spec.isBidRequestValid(bid)).to.equal(true); }); }); describe('buildRequests', function () { - const url = '//abs.proxistore.com/fr/v3/rtb/prebid'; + const url = 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi'; const request = spec.buildRequests([bid], bidderRequest); - it('should return an empty array if no cookie sent', function () { - expect(request).to.be.an('array'); - expect(request.length).to.equal(1); + it('should return a valid object', function () { + expect(request).to.be.an('object'); + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; }); it('request method should be POST', function () { - expect(request[0].method).to.equal('POST'); + expect(request.method).to.equal('POST'); }); it('should contain a valid url', function () { - expect(request[0].url).equal(url); - }) + expect(request.url).equal(url); + }); + it('should have the value consentGiven to true bc we have 418 in the vendor list', function () { + const data = JSON.parse(request.data); + + expect(data.gdpr.consentString).equal(bidderRequest.gdprConsent.consentString); + expect(data.gdpr.applies).to.be.true; + expect(data.gdpr.consentGiven).to.be.true; + }); + it('should have a property a length of bids equal to one if there is only one bid', function () { + const data = JSON.parse(request.data); + expect(data.hasOwnProperty('bids')).to.be.true; + expect(data.bids).to.be.an('array'); + expect(data.bids.length).equal(1); + expect(data.bids[0].hasOwnProperty('id')).to.be.true; + expect(data.bids[0].sizes).to.be.an('array'); + }); }); describe('interpretResponse', function () { @@ -66,7 +102,7 @@ describe('ProxistoreBidAdapter', function () { expect(spec.interpretResponse(badResponse, bid)).to.be.an('array'); expect(spec.interpretResponse(badResponse, bid).length).equal(0); }); - it('should interprnet the response correctly if it is valid', function () { + it('should interpret the response correctly if it is valid', function () { expect(interpretedResponse.cpm).equal(6.25); expect(interpretedResponse.creativeId).equal('48fd47c9-ce35-4fda-804b-17e16c8c36ac'); expect(interpretedResponse.currency).equal('EUR'); @@ -74,7 +110,12 @@ describe('ProxistoreBidAdapter', function () { expect(interpretedResponse.width).equal(300); expect(interpretedResponse.requestId).equal('923756713'); expect(interpretedResponse.netRevenue).to.be.true; - }) + expect(interpretedResponse.netRevenue).to.be.true; + }); + it('should have a value in the local storage if the response is empty', function() { + spec.interpretResponse(badResponse, bid); + expect(localStorage.getItem(`PX_NoAds_${bid.params.website}`)).to.be.string; + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index fd2bff5bac6..a46ff26c4b8 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -9,11 +9,11 @@ import { setStorageItem, getStorageItem, removeStorageItem, - getPubcidConfig } from 'modules/pubCommonId'; -import { getAdUnits } from 'test/fixtures/fixtures'; -import * as auctionModule from 'src/auction'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import * as utils from 'src/utils'; + getPubcidConfig } from 'modules/pubCommonId.js'; +import { getAdUnits } from 'test/fixtures/fixtures.js'; +import * as auctionModule from 'src/auction.js'; +import { registerBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; let events = require('src/events'); let constants = require('src/constants.json'); @@ -234,7 +234,7 @@ describe('Publisher Common ID', function () { }); }); - it('disable auto create', function() { + it.skip('disable auto create', function() { setConfig({ create: false }); diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js new file mode 100644 index 00000000000..52f2e3aeefe --- /dev/null +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -0,0 +1,447 @@ +import { expect } from 'chai'; + +import { spec } from 'modules/pubgeniusBidAdapter.js'; +import { deepClone, parseQueryStringParameters } from 'src/utils.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; + +const { + code, + supportedMediaTypes, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onTimeout, +} = spec; + +describe('pubGENIUS adapter', () => { + describe('code', () => { + it('should be pubgenius', () => { + expect(code).to.equal('pubgenius'); + }); + }); + + describe('supportedMediaTypes', () => { + it('should contain only banner', () => { + expect(supportedMediaTypes).to.deep.equal(['banner']); + }); + }); + + describe('isBidRequestValid', () => { + let bid = null; + + beforeEach(() => { + bid = { + mediaTypes: { + banner: { + sizes: [[300, 600], [300, 250]], + }, + }, + params: { + adUnitId: 1112, + }, + }; + }); + + it('should return true with numeric adUnitId ', () => { + expect(isBidRequestValid(bid)).to.be.true; + }); + + it('should return true with string adUnitId ', () => { + bid.params.adUnitId = '1112'; + + expect(isBidRequestValid(bid)).to.be.true; + }); + + it('should return false without adUnitId', () => { + delete bid.params.adUnitId; + + expect(isBidRequestValid(bid)).to.be.false; + }); + + it('should return false with adUnitId of invalid type', () => { + bid.params.adUnitId = [1112]; + + expect(isBidRequestValid(bid)).to.be.false; + }); + + it('should return false with empty sizes', () => { + bid.mediaTypes.banner.sizes = []; + + expect(isBidRequestValid(bid)).to.be.false; + }); + + it('should return false with invalid size', () => { + bid.mediaTypes.banner.sizes = [[300, 600, 250]]; + + expect(isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', () => { + const origBidderTimeout = config.getConfig('bidderTimeout'); + const origPageUrl = config.getConfig('pageUrl'); + const origCoppa = config.getConfig('coppa'); + + after(() => { + config.setConfig({ + bidderTimeout: origBidderTimeout, + pageUrl: origPageUrl, + coppa: origCoppa, + }); + }); + + let bidRequest = null; + let bidderRequest = null; + let expectedRequest = null; + + beforeEach(() => { + bidRequest = { + adUnitCode: 'test-div', + auctionId: 'fake-auction-id', + bidId: 'fakebidid', + bidder: 'pubgenius', + bidderRequestId: 'fakebidderrequestid', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + mediaTypes: { + banner: { + sizes: [[300, 600], [300, 250]], + }, + }, + params: { + adUnitId: 1112, + }, + transactionId: 'fake-transaction-id', + }; + + bidderRequest = { + auctionId: 'fake-auction-id', + bidderCode: 'pubgenius', + bidderRequestId: 'fakebidderrequestid', + refererInfo: {}, + }; + + expectedRequest = { + method: 'POST', + url: 'https://ortb.adpearl.io/prebid/auction', + data: { + id: 'fake-auction-id', + imp: [ + { + id: 'fakebidid', + banner: { + format: [{ w: 300, h: 600 }, { w: 300, h: 250 }], + topframe: 0, + }, + tagid: '1112', + }, + ], + tmax: 1200, + ext: { + pbadapter: { + version: '1.0.0', + }, + }, + }, + }; + + config.setConfig({ + bidderTimeout: 1200, + pageUrl: undefined, + coppa: undefined, + }); + }); + + it('should build basic requests correctly', () => { + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should build requests with multiple ad units', () => { + const bidRequest1 = deepClone(bidRequest); + bidRequest1.adUnitCode = 'test-div-1'; + bidRequest1.bidId = 'fakebidid1'; + bidRequest1.mediaTypes.banner.sizes = [[728, 90]]; + bidRequest1.params.adUnitId = '1111'; + + expectedRequest.data.imp.push({ + id: 'fakebidid1', + banner: { + format: [{ w: 728, h: 90 }], + topframe: 0, + }, + tagid: '1111', + }); + + expect(buildRequests([bidRequest, bidRequest1], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take bid floor in bidder params', () => { + bidRequest.params.bidFloor = 0.5; + expectedRequest.data.imp[0].bidfloor = 0.5; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take position in bidder params', () => { + bidRequest.params.position = 3; + expectedRequest.data.imp[0].banner.pos = 3; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take pageUrl in config over referer in refererInfo', () => { + config.setConfig({ pageUrl: 'http://pageurl.org' }); + bidderRequest.refererInfo.referer = 'http://referer.org'; + expectedRequest.data.site = { page: 'http://pageurl.org' }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should use canonical URL over referer in refererInfo', () => { + bidderRequest.refererInfo.canonicalUrl = 'http://pageurl.org'; + bidderRequest.refererInfo.referer = 'http://referer.org'; + expectedRequest.data.site = { page: 'http://pageurl.org' }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take gdprConsent when GDPR does not apply', () => { + bidderRequest.gdprConsent = { + gdprApplies: false, + consentString: 'fakeconsent', + }; + expectedRequest.data.regs = { + ext: { gdpr: 0 }, + }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take gdprConsent when GDPR applies', () => { + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'fakeconsent', + }; + expectedRequest.data.regs = { + ext: { gdpr: 1 }, + }; + expectedRequest.data.user = { + ext: { consent: 'fakeconsent' }, + }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take uspConsent', () => { + bidderRequest.uspConsent = '1---'; + expectedRequest.data.regs = { + ext: { us_privacy: '1---' }, + }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take schain', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '0001', + hp: 1 + } + ] + }; + bidRequest.schain = deepClone(schain); + expectedRequest.data.source = { + ext: { schain: deepClone(schain) }, + }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take coppa', () => { + config.setConfig({ coppa: true }); + expectedRequest.data.regs = { coppa: 1 }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should take user IDs', () => { + const eid = { + source: 'adserver.org', + uids: [ + { + id: 'fake-user-id', + atype: 1, + ext: { rtiPartner: 'TDID' }, + }, + ], + }; + bidRequest.userIdAsEids = [deepClone(eid)]; + expectedRequest.data.user = { + ext: { + eids: [deepClone(eid)], + }, + }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should not take unsupported user IDs', () => { + bidRequest.userIdAsEids = [ + { + source: 'pubcid.org', + uids: [ + { + id: 'fake-user-id', + atype: 1, + }, + ], + }, + ]; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + + it('should not take empty user IDs', () => { + bidRequest.userIdAsEids = []; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + }); + + describe('interpretResponse', () => { + let serverResponse = null; + let expectedBidResponse = null; + + beforeEach(() => { + serverResponse = { + body: { + seatbid: [ + { + seat: 'pubgenius', + bid: [ + { + impid: 'fakebidid', + price: 0.3, + w: 300, + h: 250, + adm: 'fake_creative', + exp: 60, + crid: 'fakecreativeid', + }, + ], + }, + ], + }, + }; + expectedBidResponse = { + requestId: 'fakebidid', + cpm: 0.3, + currency: 'USD', + width: 300, + height: 250, + ad: 'fake_creative', + ttl: 60, + creativeId: 'fakecreativeid', + netRevenue: true, + }; + }); + + it('should interpret response correctly', () => { + expect(interpretResponse(serverResponse)).to.deep.equal([expectedBidResponse]); + }); + + it('should interpret response with adomain', () => { + serverResponse.body.seatbid[0].bid[0].adomain = ['fakeaddomain']; + expectedBidResponse.meta = { + advertiserDomains: ['fakeaddomain'], + }; + + expect(interpretResponse(serverResponse)).to.deep.equal([expectedBidResponse]); + }); + + it('should interpret no bids', () => { + expect(interpretResponse({ body: {} })).to.deep.equal([]); + }); + }); + + describe('getUserSyncs', () => { + let syncOptions = null; + let expectedSync = null; + + beforeEach(() => { + syncOptions = { + iframeEnabled: true, + pixelEnabled: true, + }; + expectedSync = { + type: 'iframe', + url: 'https://ortb.adpearl.io/usersync/pixels.html?', + }; + }); + + it('should return iframe pixels', () => { + expect(getUserSyncs(syncOptions)).to.deep.equal([expectedSync]); + }); + + it('should return empty when iframe is not enabled', () => { + syncOptions.iframeEnabled = false; + + expect(getUserSyncs(syncOptions)).to.deep.equal([]); + }); + + it('should return sync when GDPR applies', () => { + const gdprConsent = { + gdprApplies: true, + consentString: 'fake-gdpr-consent', + }; + expectedSync.url = expectedSync.url + parseQueryStringParameters({ + gdpr: 1, + consent: 'fake-gdpr-consent', + }); + + expect(getUserSyncs(syncOptions, [], gdprConsent)).to.deep.equal([expectedSync]); + }); + + it('should return sync when GDPR does not apply', () => { + const gdprConsent = { + gdprApplies: false, + }; + expectedSync.url = expectedSync.url + parseQueryStringParameters({ gdpr: 0 }); + + expect(getUserSyncs(syncOptions, [], gdprConsent)).to.deep.equal([expectedSync]); + }); + + it('should return sync with US privacy', () => { + expectedSync.url = expectedSync.url + parseQueryStringParameters({ us_privacy: '1---' }); + + expect(getUserSyncs(syncOptions, [], undefined, '1---')).to.deep.equal([expectedSync]); + }); + }); + + describe('onTimeout', () => { + it('should send timeout data', () => { + const timeoutData = { + bidder: 'pubgenius', + bidId: 'fakebidid', + params: { + adUnitId: 1234, + }, + adUnitCode: 'fake-ad-unit-code', + timeout: 3000, + auctionId: 'fake-auction-id', + }; + onTimeout(timeoutData); + + expect(server.requests[0].method).to.equal('POST'); + expect(server.requests[0].url).to.equal('https://ortb.adpearl.io/prebid/events?type=timeout'); + expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal(timeoutData); + }); + }); +}); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js new file mode 100755 index 00000000000..537ba4483cf --- /dev/null +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -0,0 +1,702 @@ +import pubmaticAnalyticsAdapter from 'modules/pubmaticAnalyticsAdapter.js'; +import CONSTANTS from 'src/constants.json'; +import { config } from 'src/config.js'; +import { + setConfig, + addBidResponseHook, +} from 'modules/currency.js'; + +// 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, + BIDDER_DONE, + BID_WON, + BID_TIMEOUT, + SET_TARGETING + } +} = CONSTANTS; + +const BID = { + 'bidder': 'pubmatic', + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'statusMessage': 'Bid available', + 'bidId': '2ecff0db240757', + 'partnerImpId': 'partnerImpressionID-1', + 'adId': 'fake_ad_id', + 'source': 's2s', + 'requestId': '2ecff0db240757', + 'currency': 'USD', + 'creativeId': '3571560', + 'cpm': 1.22752, + 'originalCpm': 1.22752, + 'originalCurrency': 'USD', + 'ttl': 300, + 'netRevenue': false, + 'ad': '', + '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': 'pubmatic', + 'hb_adid': '2ecff0db240757', + 'hb_pb': 1.20, + 'hb_size': '640x480', + 'hb_source': 'server' + }, + getStatusCode() { + return 1; + } +}; + +const BID2 = Object.assign({}, BID, { + adUnitCode: '/19968336/header-bid-tag-1', + bidId: '3bd4ebb1c900e2', + partnerImpId: 'partnerImpressionID-2', + adId: 'fake_ad_id_2', + requestId: '3bd4ebb1c900e2', + width: 728, + height: 90, + mediaType: 'banner', + cpm: 1.52, + originalCpm: 1.52, + dealId: 'the-deal-id', + dealChannel: 'PMP', + mi: 'matched-impression', + seatBidId: 'aaaa-bbbb-cccc-dddd', + adserverTargeting: { + 'hb_bidder': 'pubmatic', + '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: { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag-1', + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': '1001' + } + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } + ], + 'adUnitCodes': ['/19968336/header-bid-tag-1'], + 'bidderRequests': [ { + 'bidderCode': 'pubmatic', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': '1001', + 'kgpv': 'this-is-a-kgpv' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'referer': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000 + }, + BID_REQUESTED: { + 'bidder': 'pubmatic', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': '1001', + 'video': { + 'minduration': 30, + 'skippable': true + } + }, + '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': 'pubmatic', + 'params': { + 'publisherId': '1001', + 'kgpv': 'this-is-a-kgpv' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[1000, 300], [970, 250], [728, 90]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', + 'sizes': [[1000, 300], [970, 250], [728, 90]], + 'bidId': '3bd4ebb1c900e2', + 'seatBidId': 'aaaa-bbbb-cccc-dddd', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + } + ], + 'auctionStart': 1519149536560, + 'timeout': 5000, + 'start': 1519149562216, + 'refererInfo': { + 'referer': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'gdprConsent': { + 'consentString': 'here-goes-gdpr-consent-string', + 'gdprApplies': true + } + }, + BID_RESPONSE: [ + BID, + BID2 + ], + AUCTION_END: { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + }, + BID_WON: [ + Object.assign({}, BID, { + 'status': 'rendered' + }), + Object.assign({}, BID2, { + 'status': 'rendered' + }) + ], + BIDDER_DONE: { + 'bidderCode': 'pubmatic', + 'bids': [ + BID, + Object.assign({}, BID2, { + 'serverResponseTimeMs': 42, + }) + ] + }, + BID_TIMEOUT: [ + { + 'bidId': '3bd4ebb1c900e2', + 'bidder': 'pubmatic', + 'adUnitCode': '/19968336/header-bid-tag-1', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + } + ] +}; + +function getLoggerJsonFromRequest(requestBody) { + return JSON.parse(decodeURIComponent(requestBody.split('json=')[1])); +} + +describe('pubmatic analytics adapter', function () { + let sandbox; + let xhr; + let requests; + let oldScreen; + let clock; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + + xhr = sandbox.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + + sandbox.stub(events, 'getEvents').returns([]); + + clock = sandbox.useFakeTimers(1519767013781); + + config.setConfig({ + s2sConfig: { + timeout: 1000, + accountId: 10000, + bidders: ['pubmatic'] + } + }) + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + }); + + it('should require publisherId', function () { + sandbox.stub(utils, 'logError'); + pubmaticAnalyticsAdapter.enableAnalytics({ + options: {} + }); + expect(utils.logError.called).to.equal(true); + }); + + describe('when handling events', function() { + beforeEach(function () { + pubmaticAnalyticsAdapter.enableAnalytics({ + options: { + publisherId: 9999, + profileId: 1111, + profileVersionId: 20 + } + }); + }); + + afterEach(function () { + pubmaticAnalyticsAdapter.disableAnalytics(); + }); + + it('Logger: best case + win tracker', function() { + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] + }); + + config.setConfig({ + testGroupId: 15 + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.pdvid).to.equal('20'); + expect(data.iid).to.equal('25c6d7f5-699a-4bfc-87c9-996f915341fa'); + expect(data.to).to.equal('3000'); + expect(data.purl).to.equal('http://www.test.com/page.html'); + expect(data.orig).to.equal('www.test.com'); + expect(data.tst).to.equal(1519767016); + expect(data.cns).to.equal('here-goes-gdpr-consent-string'); + expect(data.gdpr).to.equal(1); + expect(data.tgid).to.equal(15); + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + // slot 1 + expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].sz).to.deep.equal(['640x480']); + expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].piid).to.equal('partnerImpressionID-1'); + expect(data.s[0].ps[0].db).to.equal(0); + expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps[0].kgpsv).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps[0].psz).to.equal('640x480'); + expect(data.s[0].ps[0].eg).to.equal(1.23); + expect(data.s[0].ps[0].en).to.equal(1.23); + expect(data.s[0].ps[0].di).to.equal(''); + expect(data.s[0].ps[0].dc).to.equal(''); + expect(data.s[0].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l2).to.equal(0); + expect(data.s[0].ps[0].ss).to.equal(1); + expect(data.s[0].ps[0].t).to.equal(0); + expect(data.s[0].ps[0].wb).to.equal(1); + expect(data.s[0].ps[0].af).to.equal('video'); + expect(data.s[0].ps[0].ocpm).to.equal(1.23); + expect(data.s[0].ps[0].ocry).to.equal('USD'); + // slot 2 + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].piid).to.equal('partnerImpressionID-2'); + expect(data.s[1].ps[0].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('728x90'); + expect(data.s[1].ps[0].eg).to.equal(1.52); + expect(data.s[1].ps[0].en).to.equal(1.52); + expect(data.s[1].ps[0].di).to.equal('the-deal-id'); + expect(data.s[1].ps[0].dc).to.equal('PMP'); + expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(0); + expect(data.s[1].ps[0].wb).to.equal(1); + expect(data.s[1].ps[0].af).to.equal('banner'); + expect(data.s[1].ps[0].ocpm).to.equal(1.52); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.pubid).to.equal('9999'); + expect(decodeURIComponent(data.purl)).to.equal('http://www.test.com/page.html'); + expect(data.tst).to.equal('1519767014'); + expect(data.iid).to.equal('25c6d7f5-699a-4bfc-87c9-996f915341fa'); + expect(data.bidid).to.equal('2ecff0db240757'); + expect(data.pid).to.equal('1111'); + expect(data.pdvid).to.equal('20'); + expect(decodeURIComponent(data.slot)).to.equal('/19968336/header-bid-tag-0'); + expect(decodeURIComponent(data.kgpv)).to.equal('/19968336/header-bid-tag-0'); + expect(data.pn).to.equal('pubmatic'); + expect(data.eg).to.equal('1.23'); + expect(data.en).to.equal('1.23'); + expect(data.piid).to.equal('partnerImpressionID-1'); + }); + + it('bidCpmAdjustment: USD: Logger: best case + win tracker', function() { + const bidCopy = utils.deepClone(BID); + bidCopy.cpm = bidCopy.originalCpm * 2; // bidCpmAdjustment => bidCpm * 2 + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [bidCopy, MOCK.BID_RESPONSE[1]] + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, bidCopy); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_RESPONSE, bidCopy); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + expect(data.tgid).to.equal(0); + // slot 1 + expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].sz).to.deep.equal(['640x480']); + expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps[0].eg).to.equal(1.23); + expect(data.s[0].ps[0].en).to.equal(2.46); + expect(data.s[0].ps[0].wb).to.equal(1); + expect(data.s[0].ps[0].af).to.equal('video'); + expect(data.s[0].ps[0].ocpm).to.equal(1.23); + expect(data.s[0].ps[0].ocry).to.equal('USD'); + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.pubid).to.equal('9999'); + expect(data.tst).to.equal('1519767014'); + expect(data.iid).to.equal('25c6d7f5-699a-4bfc-87c9-996f915341fa'); + expect(data.eg).to.equal('1.23'); + expect(data.en).to.equal('2.46'); + }); + + it('bidCpmAdjustment: JPY: Logger: best case + win tracker', function() { + config.setConfig({ + testGroupId: 25 + }); + + setConfig({ + adServerCurrency: 'JPY', + rates: { + USD: { + JPY: 100 + } + } + }); + const bidCopy = utils.deepClone(BID); + bidCopy.originalCpm = 100; + bidCopy.originalCurrency = 'JPY'; + bidCopy.currency = 'JPY'; + bidCopy.cpm = bidCopy.originalCpm * 2; // bidCpmAdjustment => bidCpm * 2 + + 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, bidCopy); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_RESPONSE, bidCopy); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.tgid).to.equal(0);// test group id should be between 0-15 else set to 0 + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + // slot 1 + expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].sz).to.deep.equal(['640x480']); + expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps[0].eg).to.equal(1); + expect(data.s[0].ps[0].en).to.equal(200); + expect(data.s[0].ps[0].wb).to.equal(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 1 + expect(data.s[0].ps[0].af).to.equal('video'); + expect(data.s[0].ps[0].ocpm).to.equal(100); + expect(data.s[0].ps[0].ocry).to.equal('JPY'); + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.pubid).to.equal('9999'); + expect(data.tst).to.equal('1519767014'); + expect(data.iid).to.equal('25c6d7f5-699a-4bfc-87c9-996f915341fa'); + expect(data.eg).to.equal('1'); + expect(data.en).to.equal('200'); // bidPriceUSD is not getting set as currency module is not added + }); + + it('Logger: when bid is not submitted, default bid status 1 check: pubmatic set as s2s', function() { + config.setConfig({ + testGroupId: '25' + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(2); // 1 logger and 1 win-tracker + let request = requests[1]; // logger is executed late, trackers execute first + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.tgid).to.equal(0);// test group id should be an INT between 0-15 else set to 0 + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].db).to.equal(1); + expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('0x0'); + expect(data.s[1].ps[0].eg).to.equal(0); + expect(data.s[1].ps[0].en).to.equal(0); + expect(data.s[1].ps[0].di).to.equal(''); + expect(data.s[1].ps[0].dc).to.equal(''); + expect(data.s[1].ps[0].mi).to.equal(undefined); + expect(data.s[1].ps[0].l1).to.equal(0); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(0); + expect(data.s[1].ps[0].wb).to.equal(0); + expect(data.s[1].ps[0].af).to.equal(undefined); + expect(data.s[1].ps[0].ocpm).to.equal(0); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + }); + + it('Logger: post-timeout check without bid response', function() { + // db = 1 and t = 1 means bidder did NOT respond with a bid but we got a timeout notification + 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(2000 + 1000); + + expect(requests.length).to.equal(1); // 1 logger and 0 win-tracker + let request = requests[0]; + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].db).to.equal(1); + expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('0x0'); + expect(data.s[1].ps[0].eg).to.equal(0); + expect(data.s[1].ps[0].en).to.equal(0); + expect(data.s[1].ps[0].di).to.equal(''); + expect(data.s[1].ps[0].dc).to.equal(''); + expect(data.s[1].ps[0].mi).to.equal(undefined); + expect(data.s[1].ps[0].l1).to.equal(0); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(1); + expect(data.s[1].ps[0].wb).to.equal(0); + expect(data.s[1].ps[0].af).to.equal(undefined); + expect(data.s[1].ps[0].ocpm).to.equal(0); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + }); + + it('Logger: post-timeout check with bid response', function() { + // db = 1 and t = 1 means bidder did NOT respond with a bid but we got a timeout notification + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[1]] + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(2000 + 1000); + + expect(requests.length).to.equal(1); // 1 logger and 0 win-tracker + let request = requests[0]; + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('728x90'); + expect(data.s[1].ps[0].eg).to.equal(1.52); + expect(data.s[1].ps[0].en).to.equal(1.52); + expect(data.s[1].ps[0].di).to.equal('the-deal-id'); + expect(data.s[1].ps[0].dc).to.equal('PMP'); + expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(1); + expect(data.s[1].ps[0].wb).to.equal(1); // todo + expect(data.s[1].ps[0].af).to.equal('banner'); + expect(data.s[1].ps[0].ocpm).to.equal(1.52); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + }); + + it('Logger: currency conversion check', function() { + setConfig({ + adServerCurrency: 'JPY', + rates: { + USD: { + JPY: 100 + } + } + }); + const bidCopy = utils.deepClone(BID2); + bidCopy.currency = 'JPY'; + bidCopy.cpm = 100; + bidCopy.originalCpm = 100; + bidCopy.originalCurrency = 'JPY'; + + 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, bidCopy); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('728x90'); + expect(data.s[1].ps[0].eg).to.equal(1); + expect(data.s[1].ps[0].en).to.equal(100); + expect(data.s[1].ps[0].di).to.equal('the-deal-id'); + expect(data.s[1].ps[0].dc).to.equal('PMP'); + expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(0); + expect(data.s[1].ps[0].wb).to.equal(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 1 + expect(data.s[1].ps[0].af).to.equal('banner'); + expect(data.s[1].ps[0].ocpm).to.equal(100); + expect(data.s[1].ps[0].ocry).to.equal('JPY'); + }); + }); +}); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 902f8b1fd73..0f51a8df61c 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect} from 'chai'; -import {spec} from 'modules/pubmaticBidAdapter'; -import * as utils from 'src/utils'; -import {config} from 'src/config'; +import {spec} from 'modules/pubmaticBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {config} from 'src/config.js'; +import { createEidsArray } from 'modules/userId/eids.js'; const constants = require('src/constants.json'); describe('PubMatic adapter', function () { @@ -25,6 +26,9 @@ describe('PubMatic adapter', function () { let bannerBidResponse; let videoBidResponse; let schainConfig; + let outstreamBidRequest; + let validOutstreamBidRequest; + let outstreamVideoBidResponse; beforeEach(function () { schainConfig = { @@ -54,7 +58,7 @@ describe('PubMatic adapter', function () { } }, params: { - publisherId: '301', + publisherId: '5670', adSlot: '/15671365/DMDemo@300x250:0', kadfloor: '1.2', pmzoneid: 'aabc, ddef', @@ -533,6 +537,10 @@ describe('PubMatic adapter', function () { 'body': { 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', 'seatbid': [{ + 'seat': 'seat-id', + 'ext': { + 'buyid': 'BUYER-ID-987' + }, 'bid': [{ 'id': '74858439-49D7-4169-BA5D-44A046315B2F', 'impid': '22bddb28db77d', @@ -548,6 +556,9 @@ describe('PubMatic adapter', function () { } }] }, { + 'ext': { + 'buyid': 'BUYER-ID-789' + }, 'bid': [{ 'id': '74858439-49D7-4169-BA5D-44A046315BEF', 'impid': '22bddb28db77e', @@ -648,7 +659,89 @@ describe('PubMatic adapter', function () { }] }] } - } + }; + outstreamBidRequest = + [ + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bidder: 'pubmatic', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + publisherId: '5670', + outstreamAU: 'pubmatic-test', + adSlot: 'Div1@0x0', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + } + ]; + + validOutstreamBidRequest = { + auctionId: '92489f71-1bf2-49a0-adf9-000cea934729', + auctionStart: 1585918458868, + bidderCode: 'pubmatic', + bidderRequestId: '47acc48ad47af5', + bids: [{ + adUnitCode: 'video1', + auctionId: '92489f71-1bf2-49a0-adf9-000cea934729', + bidId: '47acc48ad47af5', + bidRequestsCount: 1, + bidder: 'pubmatic', + bidderRequestId: '47acc48ad47af5', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: { + publisherId: '5670', + outstreamAU: 'pubmatic-test', + adSlot: 'Div1@0x0', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + }, + sizes: [[768, 432], [640, 480], [630, 360]], + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + }], + start: 11585918458869, + timeout: 3000 + }; + + outstreamVideoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '0fb4905b-1234-4152-86be-c6f6d259ba99', + 'impid': '47acc48ad47af5', + 'price': 1.3, + 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.pubmatic.com', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + }; }); describe('implementation', function () { @@ -717,7 +810,17 @@ describe('PubMatic adapter', function () { let request = spec.buildRequests(bidRequests); expect(request.url).to.equal('https://hbopenbid.pubmatic.com/translator?source=prebid-client'); expect(request.method).to.equal('POST'); - }); + }); + + it('should return bidderRequest property', function() { + let request = spec.buildRequests(bidRequests, validOutstreamBidRequest); + expect(request.bidderRequest).to.equal(validOutstreamBidRequest); + }); + + it('bidderRequest should be undefined if bidderRequest is not present', function() { + let request = spec.buildRequests(bidRequests); + expect(request.bidderRequest).to.be.undefined; + }); it('test flag not sent when pubmaticTest=true is absent in page url', function() { let request = spec.buildRequests(bidRequests); @@ -725,7 +828,9 @@ describe('PubMatic adapter', function () { expect(data.test).to.equal(undefined); }); - it('test flag set to 1 when pubmaticTest=true is present in page url', function() { + // disabled this test case as it refreshes the whole suite when in karma watch mode + // todo: needs a fix + xit('test flag set to 1 when pubmaticTest=true is present in page url', function() { window.location.href += '#pubmaticTest=true'; // now all the test cases below will have window.location.href with #pubmaticTest=true let request = spec.buildRequests(bidRequests); @@ -766,6 +871,143 @@ describe('PubMatic adapter', function () { expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); }); + it('Set content from config, set site.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.content).to.deep.equal(content); + sandbox.restore(); + }); + + it('Merge the device info from config', function() { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + device: { + 'newkey': 'new-device-data' + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.device.js).to.equal(1); + expect(data.device.dnt).to.equal((navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0); + expect(data.device.h).to.equal(screen.height); + expect(data.device.w).to.equal(screen.width); + expect(data.device.language).to.equal(navigator.language); + expect(data.device.newkey).to.equal('new-device-data');// additional data from config + sandbox.restore(); + }); + + it('Merge the device info from config; data from config overrides the info we have gathered', function() { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + device: { + newkey: 'new-device-data', + language: 'MARATHI' + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.device.js).to.equal(1); + expect(data.device.dnt).to.equal((navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0); + expect(data.device.h).to.equal(screen.height); + expect(data.device.w).to.equal(screen.width); + expect(data.device.language).to.equal('MARATHI');// // data overriding from config + expect(data.device.newkey).to.equal('new-device-data');// additional data from config + sandbox.restore(); + }); + + it('Set app from config, copy publisher and ext from site, unset site', function() { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + app: { + bundle: 'org.prebid.mobile.demoapp', + domain: 'prebid.org' + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); + expect(data.app.domain).to.equal('prebid.org'); + expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); + expect(data.app.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); + expect(data.site).to.not.exist; + sandbox.restore(); + }); + + it('Set app, content from config, copy publisher and ext from site, unset site, config.content in app.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content, + app: { + bundle: 'org.prebid.mobile.demoapp', + domain: 'prebid.org' + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); + expect(data.app.domain).to.equal('prebid.org'); + expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); + expect(data.app.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); + expect(data.app.content).to.deep.equal(content); + expect(data.site).to.not.exist; + sandbox.restore(); + }); + + it('Set app.content, content from config, copy publisher and ext from site, unset site, config.app.content in app.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + const appContent = { + id: 'app-content-id-2' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content, + app: { + bundle: 'org.prebid.mobile.demoapp', + domain: 'prebid.org', + content: appContent + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); + expect(data.app.domain).to.equal('prebid.org'); + expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); + expect(data.app.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); + expect(data.app.content).to.deep.equal(appContent); + expect(data.site).to.not.exist; + sandbox.restore(); + }); + it('Request params check: without adSlot', function () { delete bidRequests[0].params.adSlot; let request = spec.buildRequests(bidRequests); @@ -1034,534 +1276,200 @@ describe('PubMatic adapter', function () { expect(data2.regs).to.equal(undefined);// USP/CCPAs }); - it('Request should have digitrust params', function() { - window.DigiTrust = { - getUser: function () { - } + describe('setting imp.floor using floorModule', function() { + /* + Use the minimum value among floor from floorModule per mediaType + If params.adfloor is set then take max(kadfloor, min(floors from floorModule)) + set imp.bidfloor only if it is more than 0 + */ + + let newRequest; + let floorModuleTestData; + let getFloor = function(req) { + return floorModuleTestData[req.mediaType]; }; - var bidRequest = {}; - let sandbox = sinon.sandbox.create(); - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: true, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 4 - } - }) - ); - let request = spec.buildRequests(bidRequests, bidRequest); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'digitru.st', - 'uids': [{ - 'id': 'testId', - 'atype': 1, - 'ext': { - 'keyv': 4 + beforeEach(() => { + floorModuleTestData = { + 'banner': { + 'currency': 'USD', + 'floor': 1.50 + }, + 'video': { + 'currency': 'USD', + 'floor': 2.50 + }, + 'native': { + 'currency': 'USD', + 'floor': 3.50 } - }] - }]); - sandbox.restore(); - delete window.DigiTrust; + }; + newRequest = utils.deepClone(bannerVideoAndNativeBidRequests); + newRequest[0].getFloor = getFloor; + }); + + it('bidfloor should be undefined if calculation is <= 0', function() { + floorModuleTestData.banner.floor = 0; // lowest of them all + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); + }); + + it('ignore floormodule o/p if floor is not number', function() { + floorModuleTestData.banner.floor = 'INR'; + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(2.5); // video will be lowest now + }); + + it('ignore floormodule o/p if currency is not matched', function() { + floorModuleTestData.banner.currency = 'INR'; + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(2.5); // video will be lowest now + }); + + it('kadfloor is not passed, use minimum from floorModule', function() { + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + + it('kadfloor is passed as 3, use kadfloor as it is highest', function() { + newRequest[0].params.kadfloor = '3.0';// yes, we want it as a string + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(3); + }); + + it('kadfloor is passed as 1, use min of fllorModule as it is highest', function() { + newRequest[0].params.kadfloor = '1.0';// yes, we want it as a string + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); }); - it('Request should not have digitrust params when DigiTrust not loaded', function() { - let request = spec.buildRequests(bidRequests, {}); + it('should NOT include coppa flag in bid request if coppa config is not present', () => { + const request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); + if (data.regs) { + // in case GDPR is set then data.regs will exist + expect(data.regs.coppa).to.equal(undefined); + } else { + expect(data.regs).to.equal(undefined); + } }); - it('Request should not have digitrust params due to optout', function() { - window.DigiTrust = { - getUser: function () { - } - }; + it('should include coppa flag in bid request if coppa is set to true', () => { let sandbox = sinon.sandbox.create(); - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: true, - identity: { - privacy: {optout: true}, - id: 'testId', - keyv: 4 - } - }) - ); - - let request = spec.buildRequests(bidRequests, {}); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); + expect(data.regs.coppa).to.equal(1); sandbox.restore(); - delete window.DigiTrust; }); - it('Request should not have digitrust params due to failure', function() { - window.DigiTrust = { - getUser: function () { - } - }; + it('should NOT include coppa flag in bid request if coppa is set to false', () => { let sandbox = sinon.sandbox.create(); - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: false, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 4 - } - }) - ); - - let request = spec.buildRequests(bidRequests, {}); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': false + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); + if (data.regs) { + // in case GDPR is set then data.regs will exist + expect(data.regs.coppa).to.equal(undefined); + } else { + expect(data.regs).to.equal(undefined); + } sandbox.restore(); - delete window.DigiTrust; }); - describe('DigiTrustId from config', function() { - var origGetConfig; + describe('AdsrvrOrgId from userId module', function() { let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); - window.DigiTrust = { - getUser: sandbox.spy() - }; }); afterEach(() => { sandbox.restore(); - delete window.DigiTrust; }); - it('Request should have digiTrustId config params', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - digiTrustId: { - success: true, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 4 - } - } - }; - return config[key]; - }); - + it('Request should have AdsrvrOrgId config params', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.tdid = 'TTD_ID_FROM_USER_ID_MODULE'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ - 'source': 'digitru.st', + 'source': 'adserver.org', 'uids': [{ - 'id': 'testId', + 'id': 'TTD_ID_FROM_USER_ID_MODULE', 'atype': 1, 'ext': { - 'keyv': 4 + 'rtiPartner': 'TDID' } }] }]); - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); }); - it('Request should not have digiTrustId config params due to optout', function() { + it('Request should have adsrvrOrgId from UserId Module if config and userId module both have TTD ID', function() { sandbox.stub(config, 'getConfig').callsFake((key) => { var config = { - digiTrustId: { - success: true, - identity: { - privacy: {optout: true}, - id: 'testId', - keyv: 4 - } + adsrvrOrgId: { + 'TDID': 'TTD_ID_FROM_CONFIG', + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' } - } + }; return config[key]; }); + bidRequests[0].userId = {}; + bidRequests[0].userId.tdid = 'TTD_ID_FROM_USER_ID_MODULE'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); - }); - - it('Request should not have digiTrustId config params due to failure', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - digiTrustId: { - success: false, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 4 - } + expect(data.user.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'TTD_ID_FROM_USER_ID_MODULE', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' } - } - return config[key]; - }); + }] + }]); + }); + it('Request should NOT have adsrvrOrgId params if userId is NOT object', function() { let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal(undefined); - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); }); - it('Request should not have digiTrustId config params if they do not exist', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = {}; - return config[key]; - }); - - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); - // should have called DigiTrust.getUser() once - expect(window.DigiTrust.getUser.calledOnce).to.equal(true); - }); - - it('should NOT include coppa flag in bid request if coppa config is not present', () => { - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - if (data.regs) { - // in case GDPR is set then data.regs will exist - expect(data.regs.coppa).to.equal(undefined); - } else { - expect(data.regs).to.equal(undefined); - } - }); - - it('should include coppa flag in bid request if coppa is set to true', () => { - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': true - }; - return config[key]; - }); - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.regs.coppa).to.equal(1); - }); - - it('should NOT include coppa flag in bid request if coppa is set to false', () => { - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': false - }; - return config[key]; - }); - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - if (data.regs) { - // in case GDPR is set then data.regs will exist - expect(data.regs.coppa).to.equal(undefined); - } else { - expect(data.regs).to.equal(undefined); - } - }); - }); - - describe('AdsrvrOrgId from config', function() { - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('Request should have adsrvrOrgId config params', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID': '5e740345-c25e-436d-b466-5f2f9fa95c17', - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2018-10-01T07:05:40' - } - }; - return config[key]; - }); - - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'adserver.org', - 'uids': [{ - 'id': '5e740345-c25e-436d-b466-5f2f9fa95c17', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }]); - }); - - it('Request should NOT have adsrvrOrgId config params if id in adsrvrOrgId is NOT string', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID': 1, - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2018-10-01T07:05:40' - } - }; - return config[key]; - }); - - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); - }); - - it('Request should NOT have adsrvrOrgId config params if adsrvrOrgId is NOT object', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: null - }; - return config[key]; - }); - - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); - }); - - it('Request should NOT have adsrvrOrgId config params if id in adsrvrOrgId is NOT set', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2018-10-01T07:05:40' - } - }; - return config[key]; - }); - - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); - }); - }); - - describe('AdsrvrOrgId from userId module', function() { - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('Request should have AdsrvrOrgId config params', function() { - bidRequests[0].userId = {}; - bidRequests[0].userId.tdid = 'TTD_ID_FROM_USER_ID_MODULE'; - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'adserver.org', - 'uids': [{ - 'id': 'TTD_ID_FROM_USER_ID_MODULE', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }]); - }); - - it('Request should have adsrvrOrgId from UserId Module if config and userId module both have TTD ID', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID': 'TTD_ID_FROM_CONFIG', - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2018-10-01T07:05:40' - } - }; - return config[key]; - }); - bidRequests[0].userId = {}; - bidRequests[0].userId.tdid = 'TTD_ID_FROM_USER_ID_MODULE'; - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'adserver.org', - 'uids': [{ - 'id': 'TTD_ID_FROM_USER_ID_MODULE', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }]); - }); - - it('Request should NOT have adsrvrOrgId params if userId is NOT object', function() { - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); - }); - - it('Request should NOT have adsrvrOrgId params if userId.tdid is NOT string', function() { - bidRequests[0].userId = { - tdid: 1234 - }; - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); - }); - }); - - describe('AdsrvrOrgId and Digitrust', function() { - // here we are considering cases only of accepting DigiTrustId from config - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - window.DigiTrust = { - getUser: sandbox.spy() - }; - }); - - afterEach(() => { - sandbox.restore(); - delete window.DigiTrust; - }); - - it('Request should have id of both AdsrvrOrgId and Digitrust if both have returned valid ids', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID': '5e740345-c25e-436d-b466-5f2f9fa95c17', - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2018-10-01T07:05:40' - }, - digiTrustId: { - success: true, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 4 - } - } - }; - return config[key]; - }); - - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'digitru.st', - 'uids': [{ - 'id': 'testId', - 'atype': 1, - 'ext': { - 'keyv': 4 - } - }] - }, { - 'source': 'adserver.org', - 'uids': [{ - 'id': '5e740345-c25e-436d-b466-5f2f9fa95c17', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }]); - }); - - it('Request should have id of only AdsrvrOrgId and NOT Digitrust if only AdsrvrOrgId have returned valid id', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID': '5e740345-c25e-436d-b466-5f2f9fa95c17', - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2018-10-01T07:05:40' - }, - digiTrustId: { - success: true, - identity: { - privacy: {optout: true}, - id: 'testId', - keyv: 4 - } - } - }; - return config[key]; - }); - - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'adserver.org', - 'uids': [{ - 'id': '5e740345-c25e-436d-b466-5f2f9fa95c17', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }]); - }); - - it('Request should have id of only Digitrust and NOT AdsrvrOrgId if only Digitrust have returned valid id', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2018-10-01T07:05:40' - }, - digiTrustId: { - success: true, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 4 - } - } - }; - return config[key]; - }); - - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'digitru.st', - 'uids': [{ - 'id': 'testId', - 'atype': 1, - 'ext': { - 'keyv': 4 - } - }] - }]); - }); - - it('Request should NOT have id of Digitrust and NOT AdsrvrOrgId if only both have NOT returned valid ids', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2018-10-01T07:05:40' - }, - digiTrustId: { - success: true, - identity: { - privacy: {optout: true}, - id: 'testId', - keyv: 4 - } - } - }; - return config[key]; - }); - + it('Request should NOT have adsrvrOrgId params if userId.tdid is NOT string', function() { + bidRequests[0].userId = { + tdid: 1234 + }; let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal(undefined); @@ -1573,6 +1481,7 @@ describe('PubMatic adapter', function () { it('send the pubcommon id if it is present', function() { bidRequests[0].userId = {}; bidRequests[0].userId.pubcid = 'pub_common_user_id'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ @@ -1587,54 +1496,22 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; bidRequests[0].userId.pubcid = 1; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.pubcid = []; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.pubcid = null; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.pubcid = {}; - request = spec.buildRequests(bidRequests, {}); - data = JSON.parse(request.data); - expect(data.user.eids).to.equal(undefined); - }); - }); - - describe('Digitrust Id', function() { - it('send the digitrust id if it is present', function() { - bidRequests[0].userId = {}; - bidRequests[0].userId.digitrustid = {data: {id: 'digitrust_user_id'}}; - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'digitru.st', - 'uids': [{ - 'id': 'digitrust_user_id', - 'atype': 1 - }] - }]); - }); - - it('do not pass if not string', function() { - bidRequests[0].userId = {}; - bidRequests[0].userId.digitrustid = {data: {id: 1}}; - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.digitrustid = {data: {id: []}}; - request = spec.buildRequests(bidRequests, {}); - data = JSON.parse(request.data); - expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.digitrustid = {data: {id: null}}; - request = spec.buildRequests(bidRequests, {}); - data = JSON.parse(request.data); - expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.digitrustid = {data: {id: {}}}; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); @@ -1645,6 +1522,7 @@ describe('PubMatic adapter', function () { it('send the id5 id if it is present', function() { bidRequests[0].userId = {}; bidRequests[0].userId.id5id = 'id5-user-id'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ @@ -1659,18 +1537,22 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; bidRequests[0].userId.id5id = 1; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.id5id = []; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.id5id = null; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.id5id = {}; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); @@ -1681,6 +1563,7 @@ describe('PubMatic adapter', function () { it('send the criteo id if it is present', function() { bidRequests[0].userId = {}; bidRequests[0].userId.criteoId = 'criteo-user-id'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ @@ -1695,18 +1578,22 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; bidRequests[0].userId.criteoId = 1; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.criteoId = []; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.criteoId = null; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.criteoId = {}; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); @@ -1717,6 +1604,7 @@ describe('PubMatic adapter', function () { it('send the identity-link id if it is present', function() { bidRequests[0].userId = {}; bidRequests[0].userId.idl_env = 'identity-link-user-id'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ @@ -1731,18 +1619,22 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; bidRequests[0].userId.idl_env = 1; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.idl_env = []; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.idl_env = null; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.idl_env = {}; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); @@ -1753,6 +1645,7 @@ describe('PubMatic adapter', function () { it('send the LiveIntent id if it is present', function() { bidRequests[0].userId = {}; bidRequests[0].userId.lipb = { lipbid: 'live-intent-user-id' }; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ @@ -1767,18 +1660,22 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; bidRequests[0].userId.lipb = { lipbid: 1 }; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.lipb.lipbid = []; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.lipb.lipbid = null; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.lipb.lipbid = {}; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); @@ -1788,7 +1685,8 @@ describe('PubMatic adapter', function () { describe('Parrable Id', function() { it('send the Parrable id if it is present', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.parrableid = 'parrable-user-id'; + bidRequests[0].userId.parrableId = { eid: 'parrable-user-id' }; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ @@ -1800,21 +1698,25 @@ describe('PubMatic adapter', function () { }]); }); - it('do not pass if not string', function() { + it('do not pass if not object with eid key', function() { bidRequests[0].userId = {}; bidRequests[0].userId.parrableid = 1; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.parrableid = []; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.parrableid = null; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.parrableid = {}; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); @@ -1825,6 +1727,7 @@ describe('PubMatic adapter', function () { it('send the Britepool id if it is present', function() { bidRequests[0].userId = {}; bidRequests[0].userId.britepoolid = 'britepool-user-id'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ @@ -1839,18 +1742,63 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; bidRequests[0].userId.britepoolid = 1; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.britepoolid = []; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.britepoolid = null; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); bidRequests[0].userId.britepoolid = {}; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); + + describe('NetId', function() { + it('send the NetId if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.netId = 'netid-user-id'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'netid.de', + 'uids': [{ + 'id': 'netid-user-id', + 'atype': 1 + }] + }]); + }); + + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.netId = 1; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.netId = []; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.netId = null; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.netId = {}; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); @@ -2299,6 +2247,112 @@ describe('PubMatic adapter', function () { expect(data.site.ext).to.not.exist; }); + it('Request params deals check', function () { + let multipleBidRequests = [ + { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200', + currency: 'AUD', + deals: ['deal-id-1', 'deal-id-2', 'dea'] // "dea" will not be passed as more than 3 characters needed + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + }, + { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200', + currency: 'GBP', + deals: ['deal-id-100', 'deal-id-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' + } + ]; + + let request = spec.buildRequests(multipleBidRequests); + let data = JSON.parse(request.data); + // case 1 - deals are passed as expected, ['', ''] , in both adUnits + expect(data.imp[0].pmp).to.deep.equal({ + 'private_auction': 0, + 'deals': [ + { + 'id': 'deal-id-1' + }, + { + 'id': 'deal-id-2' + } + ] + }); + expect(data.imp[1].pmp).to.deep.equal({ + 'private_auction': 0, + 'deals': [ + { + 'id': 'deal-id-100' + }, + { + 'id': 'deal-id-200' + } + ] + }); + + // case 2 - deals not present in adunit[0] + delete multipleBidRequests[0].params.deals; + request = spec.buildRequests(multipleBidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].pmp).to.not.exist; + + // case 3 - deals is present in adunit[0], but is not an array + multipleBidRequests[0].params.deals = 123; + request = spec.buildRequests(multipleBidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].pmp).to.not.exist; + + // case 4 - deals is present in adunit[0] as an array but one of the value is not a string + multipleBidRequests[0].params.deals = [123, 'deal-id-1']; + request = spec.buildRequests(multipleBidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].pmp).to.deep.equal({ + 'private_auction': 0, + 'deals': [ + { + 'id': 'deal-id-1' + } + ] + }); + }); + describe('Request param bcat checking', function() { let multipleBidRequests = [ { @@ -2413,10 +2467,15 @@ describe('PubMatic adapter', function () { expect(response[0].netRevenue).to.equal(false); expect(response[0].ttl).to.equal(300); expect(response[0].meta.networkId).to.equal(123); + expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); expect(response[0].meta.buyerId).to.equal(976); expect(response[0].meta.clickUrl).to.equal('blackrock.com'); + expect(response[0].meta.advertiserDomains[0]).to.equal('blackrock.com'); expect(response[0].referrer).to.include(data.site.ref); expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); + expect(response[0].pm_seat).to.equal(bidResponses.body.seatbid[0].seat); + expect(response[0].pm_dspid).to.equal(bidResponses.body.seatbid[0].bid[0].ext.dspid); + expect(response[0].partnerImpId).to.equal(bidResponses.body.seatbid[0].bid[0].id); expect(response[1].requestId).to.equal(bidResponses.body.seatbid[1].bid[0].impid); expect(response[1].cpm).to.equal((bidResponses.body.seatbid[1].bid[0].price).toFixed(2)); @@ -2432,10 +2491,15 @@ describe('PubMatic adapter', function () { expect(response[1].netRevenue).to.equal(false); expect(response[1].ttl).to.equal(300); expect(response[1].meta.networkId).to.equal(422); + expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); expect(response[1].meta.buyerId).to.equal(832); expect(response[1].meta.clickUrl).to.equal('hivehome.com'); + expect(response[1].meta.advertiserDomains[0]).to.equal('hivehome.com'); expect(response[1].referrer).to.include(data.site.ref); expect(response[1].ad).to.equal(bidResponses.body.seatbid[1].bid[0].adm); + expect(response[1].pm_seat).to.equal(bidResponses.body.seatbid[1].seat || null); + expect(response[1].pm_dspid).to.equal(bidResponses.body.seatbid[1].bid[0].ext.dspid); + expect(response[0].partnerImpId).to.equal(bidResponses.body.seatbid[0].bid[0].id); }); it('should check for dealChannel value selection', function () { @@ -2485,7 +2549,6 @@ describe('PubMatic adapter', function () { it('should check for valid video mediaType in case of multiformat request', function() { let request = spec.buildRequests(videoBidRequests); let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].mediaType).to.equal('video'); }); @@ -2495,40 +2558,86 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('native'); }); + + it('should assign renderer if bid is video and request is for outstream', function() { + let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.exist; + }); + + it('should not assign renderer if bidderRequest is not present', function() { + let request = spec.buildRequests(outstreamBidRequest); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is video and request is for instream', function() { + let request = spec.buildRequests(videoBidRequests); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is native', function() { + let request = spec.buildRequests(nativeBidRequests); + let response = spec.interpretResponse(nativeBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is of banner', function() { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + expect(response[0].renderer).to.not.exist; + }); }); describe('getUserSyncs', function() { - const syncurl = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=5670'; + const syncurl_iframe = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=5670'; + const syncurl_image = 'https://image8.pubmatic.com/AdServer/ImgSync?p=5670'; let sandbox; beforeEach(function () { sandbox = sinon.sandbox.create(); }); afterEach(function() { sandbox.restore(); - }) + }); - it('execute only if iframeEnabled', function() { + it('execute as per config', function() { expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ - type: 'iframe', url: syncurl + type: 'iframe', url: syncurl_iframe + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal([{ + type: 'image', url: syncurl_image }]); - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.equal(undefined); }); it('CCPA/USP', function() { expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ - type: 'iframe', url: `${syncurl}&us_privacy=1NYN` + type: 'iframe', url: `${syncurl_iframe}&us_privacy=1NYN` + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, '1NYN')).to.deep.equal([{ + type: 'image', url: `${syncurl_image}&us_privacy=1NYN` }]); }); it('GDPR', function() { expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=foo` + type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo` }]); expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl}&gdpr=0&gdpr_consent=foo` + type: 'iframe', url: `${syncurl_iframe}&gdpr=0&gdpr_consent=foo` }]); expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=` + type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=` + }]); + + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${syncurl_image}&gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${syncurl_image}&gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${syncurl_image}&gdpr=1&gdpr_consent=` }]); }); @@ -2540,7 +2649,10 @@ describe('PubMatic adapter', function () { return config[key]; }); expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl}&coppa=1` + type: 'iframe', url: `${syncurl_iframe}&coppa=1` + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal([{ + type: 'image', url: `${syncurl_image}&coppa=1` }]); }); @@ -2552,7 +2664,10 @@ describe('PubMatic adapter', function () { return config[key]; }); expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl}` + type: 'iframe', url: `${syncurl_iframe}` + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal([{ + type: 'image', url: `${syncurl_image}` }]); }); @@ -2564,7 +2679,10 @@ describe('PubMatic adapter', function () { return config[key]; }); expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ - type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN&coppa=1` + type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN&coppa=1` + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + type: 'image', url: `${syncurl_image}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN&coppa=1` }]); }); }); diff --git a/test/spec/modules/pubperfAnalyticsAdapter_spec.js b/test/spec/modules/pubperfAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b316b44617a --- /dev/null +++ b/test/spec/modules/pubperfAnalyticsAdapter_spec.js @@ -0,0 +1,55 @@ +import pubperfAnalytics from 'modules/pubperfAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let events = require('src/events'); +let utils = require('src/utils.js'); +let constants = require('src/constants.json'); + +describe('Pubperf Analytics Adapter', function() { + describe('Prebid Manager Analytic tests', function() { + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + sinon.stub(utils, 'logError'); + }); + + afterEach(function() { + events.getEvents.restore(); + utils.logError.restore(); + }); + + it('should throw error, when pubperf_pbjs is not defined', function() { + pubperfAnalytics.enableAnalytics({ + provider: 'pubperf' + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + expect(server.requests.length).to.equal(0); + expect(utils.logError.called).to.equal(true); + }); + + it('track event without errors', function() { + sinon.spy(pubperfAnalytics, 'track'); + + window['pubperf_pbjs'] = function() {}; + + pubperfAnalytics.enableAnalytics({ + provider: 'pubperf' + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + sinon.assert.callCount(pubperfAnalytics.track, 6); + }); + }); +}); diff --git a/test/spec/modules/pubstackAnalyticsAdapter_spec.js b/test/spec/modules/pubstackAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..e3db334c888 --- /dev/null +++ b/test/spec/modules/pubstackAnalyticsAdapter_spec.js @@ -0,0 +1,38 @@ +import * as utils from 'src/utils.js'; +import pubstackAnalytics from '../../../modules/pubstackAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager'; +import events from 'src/events'; +import constants from 'src/constants.json' + +describe('Pubstack Analytics Adapter', () => { + const scope = utils.getWindowSelf(); + let queue = []; + + beforeEach(() => { + scope.PubstackAnalytics = (...args) => queue.push(args); + adapterManager.enableAnalytics({ + provider: 'pubstack' + }); + queue = [] + }); + + afterEach(() => { + pubstackAnalytics.disableAnalytics(); + }); + + it('should forward all events to the queue', () => { + // Given + const args = 'any-args' + + // When + events.emit(constants.EVENTS.AUCTION_END, args) + events.emit(constants.EVENTS.BID_REQUESTED, args) + events.emit(constants.EVENTS.BID_ADJUSTMENT, args) + events.emit(constants.EVENTS.BID_RESPONSE, args) + events.emit(constants.EVENTS.BID_WON, args) + events.emit(constants.EVENTS.NO_BID, args) + + // Then + expect(queue.length).to.eql(6); + }); +}); diff --git a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js index b77788fff4a..5e4b2be894e 100644 --- a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js @@ -1,17 +1,10 @@ -import pubwiseAnalytics from 'modules/pubwiseAnalyticsAdapter'; +import pubwiseAnalytics from 'modules/pubwiseAnalyticsAdapter.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); describe('PubWise Prebid Analytics', function () { - let xhr; - - before(function () { - xhr = sinon.useFakeXMLHttpRequest(); - }); - after(function () { - xhr.restore(); pubwiseAnalytics.disableAnalytics(); }); diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 80cf00d2ff8..d71bd018ab3 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -1,11 +1,16 @@ /* eslint dot-notation:0, quote-props:0 */ import {expect} from 'chai'; -import {spec} from 'modules/pulsepointBidAdapter'; -import {deepClone} from 'src/utils'; +import {spec} from 'modules/pulsepointBidAdapter.js'; +import {deepClone} from 'src/utils.js'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[728, 90], [160, 600]] + } + }, bidId: 'bid12345', params: { cp: 'p10000', @@ -626,7 +631,7 @@ describe('PulsePoint Adapter Tests', function () { criteoId: 'criteo_id234', idl_env: 'idl_id123', id5id: 'id5id_234', - parrableid: 'parrable_id234', + parrableId: { eid: 'parrable_id234' }, lipb: { lipbid: 'liveintent_id123' } @@ -655,4 +660,45 @@ describe('PulsePoint Adapter Tests', function () { userVerify(ortbRequest.user.ext.eids[4], 'parrable.com', 'parrable_id234'); userVerify(ortbRequest.user.ext.eids[5], 'liveintent.com', 'liveintent_id123'); }); + it('Verify multiple adsizes', function () { + const bidRequests = deepClone(slotConfigs); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request).to.be.not.null; + expect(request.data).to.be.not.null; + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(2); + // first impression has multi sizes + expect(ortbRequest.imp[0].banner).to.not.be.null; + expect(ortbRequest.imp[0].banner.w).to.equal(300); + expect(ortbRequest.imp[0].banner.h).to.equal(250); + expect(ortbRequest.imp[0].banner.format).to.not.be.null; + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(2); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); + expect(ortbRequest.imp[0].banner.format[1].w).to.equal(160); + expect(ortbRequest.imp[0].banner.format[1].h).to.equal(600); + // slot 2 + expect(ortbRequest.imp[1].banner).to.not.be.null; + expect(ortbRequest.imp[1].banner.w).to.equal(728); + expect(ortbRequest.imp[1].banner.h).to.equal(90); + expect(ortbRequest.imp[1].banner.format).to.be.null; + // adsize on response + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'This is an Ad', + crid: 'Creative#123', + w: 728, + h: 90 + }] + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.width).to.equal(728); + expect(bid.height).to.equal(90); + }); }); diff --git a/test/spec/modules/pxyzBidAdapter_spec.js b/test/spec/modules/pxyzBidAdapter_spec.js new file mode 100644 index 00000000000..6d8c6056076 --- /dev/null +++ b/test/spec/modules/pxyzBidAdapter_spec.js @@ -0,0 +1,235 @@ +import { expect } from 'chai'; +import { spec } from 'modules/pxyzBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { deepClone } from 'src/utils.js'; + +const URL = 'https://ads.playground.xyz/host-config/prebid?v=2'; +const GDPR_CONSENT = 'XYZ-CONSENT'; + +const BIDDER_REQUEST = { + refererInfo: { + referer: 'https://example.com' + } +}; + +describe('pxyzBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'pxyz', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [320, 50]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'pxyz', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, BIDDER_REQUEST); + const data = JSON.parse(request.data); + const banner = data.imp[0].banner; + + expect(Object.keys(data.imp[0].ext)).to.have.members(['appnexus', 'pxyz']); + expect([banner.w, banner.h]).to.deep.equal([300, 250]); + expect(banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + expect(request.url).to.equal(URL); + expect(request.method).to.equal('POST'); + }); + + describe('CCPA', function () { + describe('when USP consent object is NOT present in bidder request', function () { + const request = spec.buildRequests(bidRequests, BIDDER_REQUEST); + const data = JSON.parse(request.data); + it('should not populate ext.gdpr or ext.consent', function () { + expect(data).to.not.have.property('Regs.ext.us_privacy'); + }); + }); + + describe('when USP consent object is present in bidder request', function () { + describe('when GDPR is applicable', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, BIDDER_REQUEST, { uspConsent: '1YYY' }) + ); + const data = JSON.parse(request.data); + it('should set Regs.ext.us_privacy with the correct value', function () { + expect(data.Regs.ext['us_privacy']).to.equal('1YYY'); + }); + }); + }); + }); + + describe('GDPR', function () { + describe('when no GDPR consent object is present in bidder request', function () { + const request = spec.buildRequests(bidRequests, BIDDER_REQUEST); + const data = JSON.parse(request.data); + it('should not populate ext.gdpr or ext.consent', function () { + expect(data).to.not.have.property('Regs.ext.consent'); + }); + }); + + describe('when GDPR consent object is present in bidder request', function () { + describe('when GDPR is applicable', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, BIDDER_REQUEST, { + gdprConsent: { gdprApplies: true, consentString: GDPR_CONSENT } + }) + ); + const data = JSON.parse(request.data); + it('should set ext.gdpr with 1', function () { + expect(data.Regs.ext.gdpr).to.equal(1); + }); + it('should set ext.consent', function () { + expect(data.User.ext.consent).to.equal('XYZ-CONSENT'); + }); + }); + describe('when GDPR is NOT applicable', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, BIDDER_REQUEST, { + gdprConsent: { gdprApplies: false, consentString: GDPR_CONSENT } + }) + ); + const data = JSON.parse(request.data); + it('should set ext.gdpr to 0', function () { + expect(data.Regs.ext.gdpr).to.equal(0); + }); + it('should populate ext.consent', function () { + expect(data.User.ext.consent).to.equal('XYZ-CONSENT'); + }); + }); + }); + }); + }) + + describe('interpretResponse', function () { + let response = { + 'id': 'bidd_id', + 'seatbid': [ { + 'bid': [ + { + 'id': '4434762738980910431', + 'impid': '221f2bdc1fbc31', + 'price': 1, + 'adid': '91673066', + 'adm': '', + 'adomain': [ 'pg.xyz' ], + 'iurl': 'http://pgxyz.com/cr?id=91673066', + 'cid': 'c_id', + 'crid': 'c_rid', + 'h': 50, + 'w': 320, + 'ext': { + 'appnexus': { + 'brand_id': 1, + 'auction_id': 1087655594852566000, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + ], + 'seat': '4321' + }], + 'bidid': '6894227111893743356', + 'cur': 'AUD' + }; + + let bidderRequest = { + 'bidderCode': 'pxyz' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '221f2bdc1fbc31', + 'cpm': 1, + 'creativeId': 91673066, + 'width': 300, + 'height': 50, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'AUD', + 'ttl': 300, + 'netRevenue': true + } + ]; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid response', function () { + const response = undefined; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + const syncUrl = '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID'; + + describe('when iframeEnabled is true', function () { + const syncOptions = { + 'iframeEnabled': true + } + it('should return one image type user sync pixel', function () { + let result = spec.getUserSyncs(syncOptions); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image') + expect(result[0].url).to.equal(syncUrl); + }); + }); + + describe('when iframeEnabled is false', function () { + const syncOptions = { + 'iframeEnabled': false + } + it('should return one image type user sync pixel', function () { + let result = spec.getUserSyncs(syncOptions); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image') + expect(result[0].url).to.equal(syncUrl); + }); + }); + }) +}); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 7e7d47d3644..cd168ec61e6 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -8,9 +8,10 @@ import { QUANTCAST_PROTOCOL, QUANTCAST_PORT, spec as qcSpec -} from '../../../modules/quantcastBidAdapter'; -import { newBidder } from '../../../src/adapters/bidderFactory'; -import { parse } from 'src/url'; +} from '../../../modules/quantcastBidAdapter.js'; +import { newBidder } from '../../../src/adapters/bidderFactory.js'; +import { parseUrl } from 'src/utils.js'; +import { config } from 'src/config.js'; describe('Quantcast adapter', function () { const quantcastAdapter = newBidder(qcSpec); @@ -89,13 +90,13 @@ describe('Quantcast adapter', function () { describe('`buildRequests`', function () { it('sends secure bid requests', function () { const requests = qcSpec.buildRequests([bidRequest]); - const url = parse(requests[0]['url']); + const url = parseUrl(requests[0]['url']); expect(url.protocol).to.equal('https'); }); it('sends bid requests to Quantcast Canary Endpoint if `publisherId` is `test-publisher`', function () { const requests = qcSpec.buildRequests([bidRequest]); - const url = parse(requests[0]['url']); + const url = parseUrl(requests[0]['url']); expect(url.hostname).to.equal(QUANTCAST_TEST_DOMAIN); }); @@ -138,6 +139,7 @@ describe('Quantcast adapter', function () { bidId: '2f7b179d443f14', gdprSignal: 0, uspSignal: 0, + coppa: 0, prebidJsVersion: '$prebid.version$' }; @@ -205,6 +207,7 @@ describe('Quantcast adapter', function () { bidId: '2f7b179d443f14', gdprSignal: 0, uspSignal: 0, + coppa: 0, prebidJsVersion: '$prebid.version$' }; @@ -240,6 +243,7 @@ describe('Quantcast adapter', function () { bidId: '2f7b179d443f14', gdprSignal: 0, uspSignal: 0, + coppa: 0, prebidJsVersion: '$prebid.version$' }; @@ -271,6 +275,7 @@ describe('Quantcast adapter', function () { bidId: '2f7b179d443f14', gdprSignal: 0, uspSignal: 0, + coppa: 0, prebidJsVersion: '$prebid.version$' }; @@ -334,6 +339,7 @@ describe('Quantcast adapter', function () { bidId: '2f7b179d443f14', gdprSignal: 0, uspSignal: 0, + coppa: 0, prebidJsVersion: '$prebid.version$' }; @@ -342,13 +348,254 @@ describe('Quantcast adapter', function () { }); it('propagates GDPR consent string and signal', function () { - const bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentString' } } + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString' + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + + expect(parsed.gdprSignal).to.equal(1); + expect(parsed.gdprConsent).to.equal('consentString'); + }); + + it('allows TCF v1 request with consent for purpose 1', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendorConsents: { + '11': true + }, + purposeConsents: { + '1': true + } + }, + apiVersion: 1 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + + expect(parsed.gdprSignal).to.equal(1); + expect(parsed.gdprConsent).to.equal('consentString'); + }); + + it('blocks TCF v1 request without vendor consent', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendorConsents: { + '11': false + }, + purposeConsents: { + '1': true + } + }, + apiVersion: 1 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('blocks TCF v1 request without consent for purpose 1', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendorConsents: { + '11': true + }, + purposeConsents: { + '1': false + } + }, + apiVersion: 1 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('allows TCF v2 request from Germany for purpose 1', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + publisherCC: 'DE', + purposeOneTreatment: true + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + + expect(parsed.gdprSignal).to.equal(1); + expect(parsed.gdprConsent).to.equal('consentString'); + }); + + it('allows TCF v2 request when Quantcast has consent for purpose 1', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': true + } + }, + purpose: { + consents: { + '1': true + } + } + }, + apiVersion: 2 + } + }; + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); const parsed = JSON.parse(requests[0].data); + expect(parsed.gdprSignal).to.equal(1); expect(parsed.gdprConsent).to.equal('consentString'); }); + it('blocks TCF v2 request when no consent for Quantcast', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': false + } + }, + purpose: { + consents: { + '1': true + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('blocks TCF v2 request when no consent for purpose 1', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': true + } + }, + purpose: { + consents: { + '1': false + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('blocks TCF v2 request when Quantcast not allowed by publisher', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': true + } + }, + purpose: { + consents: { + '1': true + } + }, + publisher: { + restrictions: { + '1': { + '11': 0 + } + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('blocks TCF v2 request when legitimate interest required', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': true + } + }, + purpose: { + consents: { + '1': true + } + }, + publisher: { + restrictions: { + '1': { + '11': 2 + } + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + it('propagates US Privacy/CCPA consent information', function () { const bidderRequest = { uspConsent: 'consentString' } const requests = qcSpec.buildRequests([bidRequest], bidderRequest); @@ -357,6 +604,50 @@ describe('Quantcast adapter', function () { expect(parsed.uspConsent).to.equal('consentString'); }); + describe('propagates coppa', function() { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('propagates coppa as 1 if coppa param is set to true in the bid request', function () { + bidRequest.params = { + publisherId: 'test_publisher_id', + coppa: true + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + expect(JSON.parse(requests[0].data).coppa).to.equal(1); + }); + + it('propagates coppa as 0 if there is no coppa param or coppa is set to false in the bid request', function () { + const requestsWithoutCoppa = qcSpec.buildRequests([bidRequest], bidderRequest); + expect(JSON.parse(requestsWithoutCoppa[0].data).coppa).to.equal(0); + + bidRequest.params = { + publisherId: 'test_publisher_id', + coppa: false + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + const config = { + 'coppa': false + }; + return config[key]; + }); + const requestsWithFalseCoppa = qcSpec.buildRequests([bidRequest], bidderRequest); + expect(JSON.parse(requestsWithFalseCoppa[0].data).coppa).to.equal(0); + }); + }); + describe('`interpretResponse`', function () { // The sample response is from https://wiki.corp.qc/display/adinf/QCX const body = { @@ -492,5 +783,67 @@ describe('Quantcast adapter', function () { expect(interpretedResponse.length).to.equal(0); }); + + it('should return pixel url when available userSync available', function () { + const syncOptions = { + pixelEnabled: true + }; + const serverResponses = [ + { + body: { + userSync: { + url: 'http://quantcast.com/pixelUrl' + } + } + }, + { + body: { + + } + } + ]; + + const actualSyncs = qcSpec.getUserSyncs(syncOptions, serverResponses); + const expectedSync = { + type: 'image', + url: 'http://quantcast.com/pixelUrl' + }; + expect(actualSyncs.length).to.equal(1); + expect(actualSyncs[0]).to.deep.equal(expectedSync); + qcSpec.resetUserSync(); + }); + + it('should not return user syncs if done already', function () { + const syncOptions = { + pixelEnabled: true + }; + const serverResponses = [ + { + body: { + userSync: { + url: 'http://quantcast.com/pixelUrl' + } + } + }, + { + body: { + + } + } + ]; + + let actualSyncs = qcSpec.getUserSyncs(syncOptions, serverResponses); + const expectedSync = { + type: 'image', + url: 'http://quantcast.com/pixelUrl' + }; + expect(actualSyncs.length).to.equal(1); + expect(actualSyncs[0]).to.deep.equal(expectedSync); + + actualSyncs = qcSpec.getUserSyncs(syncOptions, serverResponses); + expect(actualSyncs.length).to.equal(0); + + qcSpec.resetUserSync(); + }); }); }); diff --git a/test/spec/modules/quantumBidAdapter_spec.js b/test/spec/modules/quantumBidAdapter_spec.js deleted file mode 100644 index ba020fbc536..00000000000 --- a/test/spec/modules/quantumBidAdapter_spec.js +++ /dev/null @@ -1,325 +0,0 @@ -import { expect } from 'chai' -import { spec } from 'modules/quantumBidAdapter' -import { newBidder } from 'src/adapters/bidderFactory' - -const ENDPOINT = 'https://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': 'https://s.sspqns.com/imp/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4/', - 'native': { - 'link': { - 'url': 'https://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': 'https://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': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-500x500' - } - }, - { - 'id': 6, - 'video': { - 'vasttag': 'https://elasticad.net/vast.xml' - } - }, - { - 'id': 2001, - 'data': { - 'value': 'https://elasticad.net' - } - }, - { - 'id': 2002, - 'data': { - 'value': 'vast' - } - }, - { - 'id': 2007, - 'data': { - 'value': 'click' - } - }, - { - 'id': 10, - 'data': { - 'value': 'ad.SSP.1x1 sponsor' - } - }, - { - 'id': 2003, - 'data': { - 'value': 'https://elasticad.net' - } - }, - { - 'id': 2004, - 'data': { - 'value': 'prism' - } - }, - { - 'id': 2005, - 'data': { - 'value': '/home' - } - }, - { - 'id': 2006, - 'data': { - 'value': 'https://elasticad.net/vast.xml' - } - }, - { - 'id': 2022, - 'data': { - 'value': 'Lorem ipsum....' - } - } - ], - 'imptrackers': [], - 'ver': '1.1' - }, - 'sync': [ - 'https://match.adsrvr.org/track/cmb/generic?ttd_pid=s6e8ued&ttd_tpi=1' - ] -} - -const nativeServerResponse = { - 'price': 0.3, - 'debug': [ - '' - ], - 'is_fallback': false, - 'nurl': 'https://s.sspqns.com/imp/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4/', - 'native': { - 'link': { - 'url': 'https://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': 'https://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': 'https://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': 'https://elasticad.net' - } - } - ], - 'imptrackers': [], - 'ver': '1.1' - }, - 'sync': [ - 'https://match.adsrvr.org/track/cmb/generic?ttd_pid=s6e8ued&ttd_tpi=1' - ] -} - -describe('quantumBidAdapter', function () { - const adapter = newBidder(spec) - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function') - }) - }) - - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(REQUEST)).to.equal(true) - }) - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, REQUEST) - delete bid.params - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) - }) - - describe('buildRequests', function () { - let bidRequests = [REQUEST] - - const request = spec.buildRequests(bidRequests, {}) - - it('sends bid request to ENDPOINT via GET', function () { - expect(request[0].method).to.equal('GET') - }) - }) - - describe('GDPR conformity', function () { - const bidRequests = [{ - 'bidder': 'quantum', - 'mediaType': 'native', - 'params': { - placementId: 21546 - }, - adUnitCode: 'aaa', - transactionId: '2b8389fe-615c-482d-9f1a-376fb8f7d6b0', - sizes: [[0, 0]], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - const bidderRequest = { - gdprConsent: { - consentString: 'awefasdfwefasdfasd', - gdprApplies: true - } - }; - - it('should transmit correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.quantx_gdpr).to.equal(1); - expect(requests[0].data.quantx_user_consent_string).to.equal('awefasdfwefasdfasd'); - }); - }); - - describe('GDPR absence conformity', function () { - const bidRequests = [{ - 'bidder': 'quantum', - 'mediaType': 'native', - 'params': { - placementId: 21546 - }, - adUnitCode: 'aaa', - transactionId: '2b8389fe-615c-482d-9f1a-376fb8f7d6b0', - sizes: [[0, 0]], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - const bidderRequest = { - gdprConsent: undefined - }; - - it('should transmit correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.quantx_gdpr).to.be.undefined; - expect(requests[0].data.quantx_user_consent_string).to.be.undefined; - }); - }); - - describe('interpretResponse', function () { - let bidderRequest = { - bidderCode: 'bidderCode', - bids: [] - } - - it('handles native request : should get correct bid response', function () { - 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', function () { - 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', function () { - 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/quantumdexBidAdapter_spec.js b/test/spec/modules/quantumdexBidAdapter_spec.js new file mode 100644 index 00000000000..82429cbedae --- /dev/null +++ b/test/spec/modules/quantumdexBidAdapter_spec.js @@ -0,0 +1,604 @@ +import { expect } from 'chai' +import { spec } from 'modules/quantumdexBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' +import { userSync } from '../../../src/userSync.js'; + +describe('QuantumdexBidAdapter', function () { + const adapter = newBidder(spec) + + describe('.code', function () { + it('should return a bidder code of quantumdex', function () { + expect(spec.code).to.equal('quantumdex') + }) + }) + + describe('inherited functions', function () { + it('should exist and be a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('.isBidRequestValid', function () { + it('should return false if there are no params', () => { + const bid = { + 'bidder': 'quantumdex', + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if there is no siteId param', () => { + const bid = { + 'bidder': 'quantumdex', + 'adUnitCode': 'adunit-code', + params: { + site_id: '1a2b3c4d5e6f1a2b3c4d', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if there is no mediaTypes', () => { + const bid = { + 'bidder': 'quantumdex', + 'adUnitCode': 'adunit-code', + params: { + siteId: '1a2b3c4d5e6f1a2b3c4d' + }, + 'mediaTypes': { + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true if the bid is valid', () => { + const bid = { + 'bidder': 'quantumdex', + 'adUnitCode': 'adunit-code', + params: { + siteId: '1a2b3c4d5e6f1a2b3c4d' + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + describe('banner', () => { + it('should return false if there are no banner sizes', () => { + const bid = { + 'bidder': 'quantumdex', + 'adUnitCode': 'adunit-code', + params: { + siteId: '1a2b3c4d5e6f1a2b3c4d' + }, + 'mediaTypes': { + banner: { + + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true if there is banner sizes', () => { + const bid = { + 'bidder': 'quantumdex', + 'adUnitCode': 'adunit-code', + params: { + siteId: '1a2b3c4d5e6f1a2b3c4d' + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('video', () => { + it('should return false if there is no playerSize defined in the video mediaType', () => { + const bid = { + 'bidder': 'quantumdex', + 'adUnitCode': 'adunit-code', + params: { + siteId: '1a2b3c4d5e6f1a2b3c4d', + sizes: [[300, 250], [300, 600]] + }, + 'mediaTypes': { + video: { + context: 'instream' + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true if there is playerSize defined on the video mediaType', () => { + const bid = { + 'bidder': 'quantumdex', + 'adUnitCode': 'adunit-code', + params: { + siteId: '1a2b3c4d5e6f1a2b3c4d', + }, + 'mediaTypes': { + video: { + context: 'instream', + playerSize: [[640, 480]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + }); + + describe('.buildRequests', function () { + beforeEach(function () { + sinon.stub(userSync, 'canBidderRegisterSync'); + }); + afterEach(function () { + userSync.canBidderRegisterSync.restore(); + }); + let bidRequest = [{ + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 0 + }, + ] + }, + 'bidder': 'quantumdex', + 'params': { + 'siteId': '1a2b3c4d5e6f1a2b3c4d', + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'targetKey': 0, + 'bidId': '30b31c1838de1f', + }, + { + 'bidder': 'quantumdex', + 'params': { + 'ad_unit': '/7780971/sparks_prebid_LB', + 'sizes': [[300, 250], [300, 600]], + 'referrer': 'overrides_top_window_location' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[120, 600], [300, 600], [160, 600]], + 'targetKey': 1, + 'bidId': '30b31c1838de1e', + }]; + + let bidderRequests = { + 'gdprConsent': { + 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'vendorData': {}, + 'gdprApplies': true + }, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com', + 'stack': ['https://example.com'] + }, + uspConsent: 'someCCPAString' + }; + + it('should return a properly formatted request', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.method).to.equal('POST') + expect(bidRequests.bidderRequests).to.eql(bidRequest); + }) + + it('should return a properly formatted request with GDPR applies set to true', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.method).to.equal('POST') + expect(bidRequests.data.gdpr.gdprApplies).to.equal('true') + expect(bidRequests.data.gdpr.consentString).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') + }) + + it('should return a properly formatted request with GDPR applies set to false', function () { + bidderRequests.gdprConsent.gdprApplies = false; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.method).to.equal('POST') + expect(bidRequests.data.gdpr.gdprApplies).to.equal('false') + expect(bidRequests.data.gdpr.consentString).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') + }) + it('should return a properly formatted request with GDPR applies set to false with no consent_string param', function () { + let bidderRequests = { + 'gdprConsent': { + 'consentString': undefined, + 'vendorData': {}, + 'gdprApplies': false + }, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com', + 'stack': ['https://example.com'] + } + }; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.method).to.equal('POST') + expect(bidRequests.data.gdpr.gdprApplies).to.equal('false') + expect(bidRequests.data.gdpr).to.not.include.keys('consentString') + }) + it('should return a properly formatted request with GDPR applies set to true with no consentString param', function () { + let bidderRequests = { + 'gdprConsent': { + 'consentString': undefined, + 'vendorData': {}, + 'gdprApplies': true + }, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com', + 'stack': ['https://example.com'] + } + }; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.method).to.equal('POST') + expect(bidRequests.data.gdpr.gdprApplies).to.equal('true') + expect(bidRequests.data.gdpr).to.not.include.keys('consentString') + }) + it('should return a properly formatted request with schain defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) + }); + it('should return a properly formatted request with us_privacy included', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); + }); + }); + + describe('.interpretResponse', function () { + const bidRequests = { + 'method': 'POST', + 'url': 'https://useast.quantumdex.io/auction/quantumdex', + 'withCredentials': true, + 'data': { + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', + 'height': 937, + 'width': 1920, + 'dnt': 0, + 'language': 'vi' + }, + 'site': { + 'id': '343', + 'page': 'https://www.example.com/page', + 'referrer': '', + 'hostname': 'www.example.com' + } + }, + 'bidderRequests': [ + { + 'bidder': 'quantumdex', + 'params': { + 'siteId': '343' + }, + 'crumbs': { + 'pubcid': 'c2b2ba08-9954-4850-8ee5-2bf4a2b35eff' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[160, 600], [120, 600]] + } + }, + 'adUnitCode': 'vi_3431035_1', + 'transactionId': '994d8404-4a28-4c43-98d8-a38dbb061910', + 'sizes': [[160, 600], [120, 600]], + 'bidId': '3000aa31c41a29c21', + 'bidderRequestId': '299926b3d3628cdd7', + 'auctionId': '22445943-a0aa-4c63-a413-4deb64fcff1c', + 'src': 'client', + 'bidRequestsCount': 41, + 'bidderRequestsCount': 41, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'freegames66.com', + 'sid': '343', + 'hp': 1 + } + ] + } + }, + { + 'bidder': 'quantumdex', + 'params': { + 'siteId': '343' + }, + 'crumbs': { + 'pubcid': 'c2b2ba08-9954-4850-8ee5-2bf4a2b35eff' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [250, 250], [200, 200], [180, 150]] + } + }, + 'adUnitCode': 'vi_3431033_1', + 'transactionId': '800fe87e-bfec-43a5-ace0-c4e2373ff4b5', + 'sizes': [[300, 250], [250, 250], [200, 200], [180, 150]], + 'bidId': '30024615be22ef66a', + 'bidderRequestId': '299926b3d3628cdd7', + 'auctionId': '22445943-a0aa-4c63-a413-4deb64fcff1c', + 'src': 'client', + 'bidRequestsCount': 41, + 'bidderRequestsCount': 41, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'freegames66.com', + 'sid': '343', + 'hp': 1 + } + ] + } + }, + { + 'bidder': 'quantumdex', + 'params': { + 'siteId': '343' + }, + 'crumbs': { + 'pubcid': 'c2b2ba08-9954-4850-8ee5-2bf4a2b35eff' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'video/x-flv', + 'video/x-ms-wmv', + 'application/vnd.apple.mpegurl', + 'application/x-mpegurl', + 'video/3gpp', + 'video/mpeg', + 'video/ogg', + 'video/quicktime', + 'video/webm', + 'video/x-m4v', + 'video/ms-asf', + 'video/x-msvideo' + ], + 'protocols': [1, 2, 3, 4, 5, 6], + 'playbackmethod': [6], + 'maxduration': 120, + 'linearity': 1, + 'api': [2] + } + }, + 'adUnitCode': 'vi_3431909', + 'transactionId': '33d83d87-43cc-499b-aabe-5c22eb6acfbb', + 'sizes': [[640, 480]], + 'bidId': '1854b40107d6745c', + 'bidderRequestId': '1840763b6bda185d', + 'auctionId': 'df495de0-5d42-471f-a501-73bcd7254b80', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'freegames66.com', + 'sid': '343', + 'hp': 1 + } + ] + } + } + ] + }; + + let serverResponse = { + 'body': { + 'bids': [ + { + 'requestId': '3000aa31c41a29c21', + 'cpm': 1.07, + 'width': 160, + 'height': 600, + 'ad': `
Quantumdex AD
`, + 'ttl': 500, + 'creativeId': '1234abcd', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'quantumdex', + 'mediaType': 'banner' + }, + { + 'requestId': '30024615be22ef66a', + 'cpm': 1, + 'width': 300, + 'height': 250, + 'ad': `
Quantumdex AD
`, + 'ttl': 500, + 'creativeId': '1234abcd', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'quantumdex', + 'mediaType': 'banner' + }, + { + 'requestId': '1854b40107d6745c', + 'cpm': 1.25, + 'width': 300, + 'height': 250, + 'vastXml': 'quantumdex', + 'ttl': 500, + 'creativeId': '30292e432662bd5f86d90774b944b038', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'quantumdex', + 'mediaType': 'video' + } + ], + 'pixel': [{ + 'url': 'https://example.com/pixel.png', + 'type': 'image' + }] + } + }; + + let prebidResponse = [ + { + 'requestId': '3000aa31c41a29c21', + 'cpm': 1.07, + 'width': 160, + 'height': 600, + 'ad': `
Quantumdex AD
`, + 'ttl': 500, + 'creativeId': '1234abcd', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'quantumdex', + 'mediaType': 'banner' + }, + { + 'requestId': '30024615be22ef66a', + 'cpm': 1, + 'width': 300, + 'height': 250, + 'ad': `
Quantumdex AD
`, + 'ttl': 500, + 'creativeId': '1234abcd', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'quantumdex', + 'mediaType': 'banner' + }, + { + 'requestId': '1854b40107d6745c', + 'cpm': 1.25, + 'width': 300, + 'height': 250, + 'vastXml': 'quantumdex', + 'ttl': 500, + 'creativeId': '30292e432662bd5f86d90774b944b038', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'quantumdex', + 'mediaType': 'video' + } + ]; + + it('should map bidResponse to prebidResponse', function () { + const response = spec.interpretResponse(serverResponse, bidRequests); + response.forEach((resp, i) => { + expect(resp.requestId).to.equal(prebidResponse[i].requestId); + expect(resp.cpm).to.equal(prebidResponse[i].cpm); + expect(resp.width).to.equal(prebidResponse[i].width); + expect(resp.height).to.equal(prebidResponse[i].height); + expect(resp.ttl).to.equal(prebidResponse[i].ttl); + expect(resp.creativeId).to.equal(prebidResponse[i].creativeId); + expect(resp.netRevenue).to.equal(prebidResponse[i].netRevenue); + expect(resp.currency).to.equal(prebidResponse[i].currency); + expect(resp.dealId).to.equal(prebidResponse[i].dealId); + if (resp.mediaType === 'video') { + expect(resp.vastXml.indexOf('quantumdex')).to.be.greaterThan(0); + } + if (resp.mediaType === 'banner') { + expect(resp.ad.indexOf('Quantumdex AD')).to.be.greaterThan(0); + } + }); + }); + }); + + describe('.getUserSyncs', function () { + let bidResponse = [{ + 'body': { + 'pixel': [{ + 'url': 'https://pixel-test', + 'type': 'image' + }] + } + }]; + + it('should return one sync pixel', function () { + 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', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); + }); + + it('should return an empty array when sync is enabled but no sync pixel returned', function () { + const pixel = Object.assign({}, bidResponse); + delete pixel[0].body.pixel; + expect(spec.getUserSyncs({ pixelEnabled: true }, bidResponse)).to.have.length(0); + }); + + it('should return an empty array', function () { + expect(spec.getUserSyncs({ pixelEnabled: false }, bidResponse)).to.have.length(0); + expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); + }); + }); +}); diff --git a/test/spec/modules/radsBidAdapter_spec.js b/test/spec/modules/radsBidAdapter_spec.js index 6981955f261..c629daf3da5 100644 --- a/test/spec/modules/radsBidAdapter_spec.js +++ b/test/spec/modules/radsBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/radsBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/radsBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const RADS_ENDPOINT_URL = 'https://rads.recognified.net/md.request.php'; diff --git a/test/spec/modules/rakutenBidAdapter_spec.js b/test/spec/modules/rakutenBidAdapter_spec.js new file mode 100644 index 00000000000..15b22afbe29 --- /dev/null +++ b/test/spec/modules/rakutenBidAdapter_spec.js @@ -0,0 +1,171 @@ +import { expect } from 'chai' +import { spec } from 'modules/rakutenBidAdapter/index.js' +import { newBidder } from 'src/adapters/bidderFactory.js' +import {config} from '../../../src/config.js'; + +describe('rakutenBidAdapter', function() { + const adapter = newBidder(spec); + const ENDPOINT = 'https://s-bid.rmp.rakuten.com/h'; + let sandbox; + + beforeEach(function() { + config.resetConfig(); + }); + + afterEach(function () { + config.resetConfig(); + }); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }); + + describe('isBidRequestValid', () => { + let bid = { + bidder: 'rakuten', + params: { + adSpotId: '56789' + } + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }); + + it('should return false when required params are not passed', () => { + bid.params.adSpotId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false) + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }); + + describe('buildRequests', () => { + const bidRequests = [ + { + // banner + params: { + adSpotId: '58278' + } + } + ]; + + const bidderRequest = { + bids: bidRequests, + refererInfo: { + referer: 'http://test.com', + stack: ['http://test.com'] + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {}, + gdprApplies: true + }, + uspConsent: '1YN-' + }; + + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET') + expect(request.data.gdpr).to.equal(1); + expect(request.data.cd).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(request.data.ccpa).to.equal('1YN-'); + }); + + it('allows url override', () => { + config.setConfig({ + rakuten: { + endpoint: '//test.rakuten.com' + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.url).to.equal('//test.rakuten.com'); + }) + }); + + describe('interpretResponse', () => { + const bidRequests = { + banner: { + method: 'GET', + url: '', + data: { + t: '56789', + s: 'https', + ua: + 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36', + l: 'ja', + d: 'examples.com', + tp: 'https://examples.com/foo/fuga', + pp: 'https://examples.com/hoge/muga' + } + } + }; + + const serverResponse = { + noAd: [], + noAd2: { + requestId: 'biequa9oaph4we' + }, + banner: { + requestId: 'biequa9oaph4we', + cpm: 37.66, + width: 300, + height: 250, + creativeId: 140281, + dealId: 'phoh3pad-ai4ah-xoh7x-ahk7cheasae3oh', + currency: 'USD', + netRevenue: 300, + ttl: 3000, + ad: '' + } + }; + + it('handles nobid responses', () => { + const result = spec.interpretResponse( + { body: serverResponse.noAd }, + bidRequests.banner + ); + expect(result.length).to.equal(0); + + const result2 = spec.interpretResponse( + { body: serverResponse.noAd2 }, + bidRequests.banner + ); + expect(result2.length).to.equal(0); + }) + }); + describe('spec.getUserSyncs', function () { + const syncResponse = [{ + body: { + request_id: 'biequa9oaph4we', + sync_urls: ['https://rdn1.test/sync?uid=9876543210', 'https://rdn2.test/sync?uid=9876543210'] + } + }]; + const nosyncResponse = [{ + body: { + request_id: 'biequa9oaph4we', + sync_urls: [] + } + }]; + let syncOptions + beforeEach(function () { + syncOptions = { + pixelEnabled: true + } + }); + it('sucess usersync url', function () { + const result = []; + result.push({type: 'image', url: 'https://rdn1.test/sync?uid=9876543210'}); + result.push({type: 'image', url: 'https://rdn2.test/sync?uid=9876543210'}); + expect(spec.getUserSyncs(syncOptions, syncResponse)).to.deep.equal(result); + }); + }); +}); diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js new file mode 100644 index 00000000000..eb9077fac39 --- /dev/null +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -0,0 +1,233 @@ +import { expect } from 'chai'; +import { spec, ENDPOINT } from 'modules/readpeakBidAdapter.js'; +import { config } from 'src/config.js'; +import { parseUrl } from 'src/utils.js'; + +describe('ReadPeakAdapter', function() { + let bidRequest; + let serverResponse; + let serverRequest; + let bidderRequest; + + beforeEach(function() { + bidderRequest = { + refererInfo: { + referer: 'https://publisher.com/home' + } + }; + + bidRequest = { + bidder: 'readpeak', + nativeParams: { + title: { required: true, len: 200 }, + image: { wmin: 100 }, + sponsoredBy: {}, + body: { required: false }, + cta: { required: false } + }, + params: { + bidfloor: 5.0, + 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', function() { + it('should return true when the required params are passed', function() { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when the native params are missing', function() { + bidRequest.nativeParams = undefined; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the "publisherId" param is missing', function() { + bidRequest.params = { + bidfloor: 5.0 + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no bid params are passed', function() { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when a bid request is not passed', function() { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); + + describe('spec.buildRequests', function() { + it('should create a POST request for every bid', function() { + const request = spec.buildRequests([bidRequest], bidderRequest); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT); + }); + + it('should attach request data', function() { + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); + + const request = spec.buildRequests([bidRequest], bidderRequest); + + 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, + page: bidderRequest.refererInfo.referer, + domain: parseUrl(bidderRequest.refererInfo.referer).hostname + }); + expect(data.device).to.deep.contain({ + ua: navigator.userAgent, + language: navigator.language + }); + expect(data.cur).to.deep.equal(['EUR']); + }); + }); + + describe('spec.interpretResponse', function() { + it('should return no bids if the response is not valid', function() { + const bidResponse = spec.interpretResponse({ body: null }, serverRequest); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid bid response', function() { + 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/realTimeModule_spec.js b/test/spec/modules/realTimeModule_spec.js index 807781d5a9c..ca149fe7a44 100644 --- a/test/spec/modules/realTimeModule_spec.js +++ b/test/spec/modules/realTimeModule_spec.js @@ -2,20 +2,26 @@ import { init, requestBidsHook, setTargetsAfterRequestBids, - deepMerge -} from 'modules/rtdModule/index'; + deepMerge, + validateProviderDataForGPT +} from 'modules/rtdModule/index.js'; import { init as browsiInit, addBrowsiTag, isIdMatchingAdUnit, - setData -} from 'modules/browsiRtdProvider'; -import {config} from 'src/config'; -import {makeSlot} from '../integration/faker/googletag'; + setData, + getMacroId +} from 'modules/browsiRtdProvider.js'; +import { + init as audigentInit, + setData as setAudigentData +} from 'modules/audigentRtdProvider.js'; +import { config } from 'src/config.js'; +import { makeSlot } from '../integration/faker/googletag.js'; let expect = require('chai').expect; -describe('Real time module', function() { +describe('Real time module', function () { const conf = { 'realTimeData': { 'auctionDelay': 250, @@ -27,13 +33,14 @@ describe('Real time module', function() { 'pubKey': 'testPub', 'keyName': 'bv' } + }, { + 'name': 'audigent' }] - } }; - const predictions = - {p: { + const predictions = { + p: { 'browsiAd_2': { 'w': [ '/57778053/Browsi_Demo_Low', @@ -56,27 +63,36 @@ describe('Real time module', function() { 'p': 0.85 } } - }; + }; + + const audigentSegments = { + audigent_segments: { 'a': 1, 'b': 2 } + } function getAdUnitMock(code = 'adUnit-code') { return { code, - mediaTypes: {banner: {}, native: {}}, + mediaTypes: { banner: {}, native: {} }, sizes: [[300, 200], [300, 600]], - bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + bids: [{ bidder: 'sampleBidder', params: { placementId: 'banner-only-bidder' } }] }; } function createSlots() { - const slot1 = makeSlot({code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1'}); - return [slot1]; + const slot1 = makeSlot({ code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1' }); + const slot2 = makeSlot({ code: '/57778053/Browsi', divId: 'browsiAd_1' }); + return [slot1, slot2]; } - describe('Real time module with browsi provider', function() { + describe('Real time module with browsi provider', function () { afterEach(function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); }); + after(function () { + config.resetConfig(); + }); + it('check module using bidsBackCallback', function () { let adUnits1 = [getAdUnitMock('browsiAd_1')]; let targeting = []; @@ -96,16 +112,13 @@ describe('Real time module', function() { targeting.push(Object.keys(value).toString()); }); }); - } - setTargetsAfterRequestBids(afterBidHook, adUnits1, true); - setTimeout(() => { expect(targeting.indexOf('bv')).to.be.greaterThan(-1); - }, 200); + } + setTargetsAfterRequestBids(afterBidHook, adUnits1, true); }); it('check module using requestBidsHook', function () { - console.log('entrance', new Date().getMinutes() + ':' + new Date().getSeconds()); let adUnits1 = [getAdUnitMock('browsiAd_1')]; let targeting = []; let dataReceived = null; @@ -122,16 +135,15 @@ describe('Real time module', function() { targeting.push(Object.keys(value).toString()); }); }); - } - requestBidsHook(afterBidHook, {adUnits: adUnits1}); - setTimeout(() => { + expect(targeting.indexOf('bv')).to.be.greaterThan(-1); dataReceived.adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid.realTimeData).to.have.property('bv'); }); }); - }, 200); + } + requestBidsHook(afterBidHook, { adUnits: adUnits1 }); }); it('check object deep merge', function () { @@ -172,6 +184,28 @@ describe('Real time module', function() { assert.deepEqual(expected, merged); }); + it('check data validation for GPT targeting', function () { + // non strings values should be removed + const obj = { + valid: {'key': 'value'}, + invalid: {'key': ['value']}, + combine: { + 'a': 'value', + 'b': [] + } + }; + + const expected = { + valid: {'key': 'value'}, + invalid: {}, + combine: { + 'a': 'value', + } + }; + const validationResult = validateProviderDataForGPT(obj); + assert.deepEqual(expected, validationResult); + }); + it('check browsi sub module', function () { const script = addBrowsiTag('scriptUrl.com'); expect(script.getAttribute('data-sitekey')).to.equal('testKey'); @@ -179,15 +213,70 @@ describe('Real time module', function() { expect(script.async).to.equal(true); const slots = createSlots(); - const test1 = isIdMatchingAdUnit('browsiAd_1', slots, ['/57778053/Browsi_Demo_300x250']); // true - const test2 = isIdMatchingAdUnit('browsiAd_1', slots, ['/57778053/Browsi_Demo_300x250', '/57778053/Browsi']); // true - const test3 = isIdMatchingAdUnit('browsiAd_1', slots, ['/57778053/Browsi_Demo_Low']); // false - const test4 = isIdMatchingAdUnit('browsiAd_1', slots, []); // true + const test1 = isIdMatchingAdUnit(slots[0], ['/57778053/Browsi_Demo_300x250']); // true + const test2 = isIdMatchingAdUnit(slots[0], ['/57778053/Browsi_Demo_300x250', '/57778053/Browsi']); // true + const test3 = isIdMatchingAdUnit(slots[0], ['/57778053/Browsi_Demo_Low']); // false + const test4 = isIdMatchingAdUnit(slots[0], []); // true expect(test1).to.equal(true); expect(test2).to.equal(true); expect(test3).to.equal(false); expect(test4).to.equal(true); + + // macro results + slots[0].setTargeting('test', ['test', 'value']); + // slot getTargeting doesn't act like GPT so we can't expect real value + const macroResult = getMacroId({p: '/'}, slots[0]); + expect(macroResult).to.equal('/57778053/Browsi_Demo_300x250/NA'); + + const macroResultB = getMacroId({}, slots[0]); + expect(macroResultB).to.equal('browsiAd_1'); + + const macroResultC = getMacroId({p: '', s: {s: 0, e: 1}}, slots[0]); + expect(macroResultC).to.equal('/'); }) }); + + describe('Real time module with Audigent provider', function () { + before(function () { + init(config); + audigentInit(config); + config.setConfig(conf); + setAudigentData(audigentSegments); + }); + + afterEach(function () { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + config.resetConfig(); + }); + + it('check module using requestBidsHook', function () { + let adUnits1 = [getAdUnitMock('audigentAd_1')]; + let targeting = []; + let dataReceived = null; + + // set slot + const slotsB = createSlots(); + window.googletag.pubads().setSlots(slotsB); + + function afterBidHook(data) { + dataReceived = data; + slotsB.map(s => { + targeting = []; + s.getTargeting().map(value => { + targeting.push(Object.keys(value).toString()); + }); + }); + + dataReceived.adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid.realTimeData).to.have.property('audigent_segments'); + expect(bid.realTimeData.audigent_segments).to.deep.equal(audigentSegments.audigent_segments); + }); + }); + } + + requestBidsHook(afterBidHook, { adUnits: adUnits1 }); + }); + }); }); diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js index 359fb329359..e51a4e2e3a2 100644 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ b/test/spec/modules/realvuAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import realvuAnalyticsAdapter, { lib } from 'modules/realvuAnalyticsAdapter'; +import realvuAnalyticsAdapter, { lib } from 'modules/realvuAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; function addDiv(id) { @@ -71,7 +71,7 @@ describe('RealVu', function() { ] }; let result = realvuAnalyticsAdapter.checkIn(bid, '1Y'); - const b = window.top1.realvu_aa; + const b = Object.assign({}, window.top1.realvu_aa); let a = b.ads[0]; // console.log('a: ' + a.x + ', ' + a.y + ', ' + a.w + ', ' + a.h); // console.log('b: ' + b.x1 + ', ' + b.y1 + ', ' + b.x2 + ', ' + b.y2); @@ -116,7 +116,7 @@ describe('RealVu', function() { eventType: CONSTANTS.EVENTS.BID_RESPONSE, args: args }); - const boost = window.top1.realvu_aa; + const boost = Object.assign({}, window.top1.realvu_aa); expect(boost.ads[boost.len - 1].bids.length).to.equal(1); realvuAnalyticsAdapter.track({ @@ -128,8 +128,11 @@ describe('RealVu', function() { }); describe('Boost.', function () { - const boost = window.top1.realvu_aa; - + // const boost = window.top1.realvu_aa; + let boost; + beforeEach(function() { + boost = Object.assign({}, window.top1.realvu_aa); + }); it('brd', function () { let a1 = document.getElementById('ad1'); let p = boost.brd(a1, 'Left'); diff --git a/test/spec/modules/reklamstoreBidAdapter_spec.js b/test/spec/modules/reklamstoreBidAdapter_spec.js index 92f309fd54f..1dcd6c17ca4 100644 --- a/test/spec/modules/reklamstoreBidAdapter_spec.js +++ b/test/spec/modules/reklamstoreBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/reklamstoreBidAdapter'; +import { spec } from 'modules/reklamstoreBidAdapter.js'; describe('reklamstoreBidAdapterTests', function() { let bidRequestData = { diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js new file mode 100644 index 00000000000..cd4918460db --- /dev/null +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -0,0 +1,332 @@ +import { expect } from 'chai'; +import { spec } from 'modules/relaidoBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const UUID_KEY = 'relaido_uuid'; +const DEFAULT_USER_AGENT = window.navigator.userAgent; +const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1'; + +const setUADefault = () => { window.navigator.__defineGetter__('userAgent', function () { return DEFAULT_USER_AGENT }) }; +const setUAMobile = () => { window.navigator.__defineGetter__('userAgent', function () { return MOBILE_USER_AGENT }) }; + +describe('RelaidoAdapter', function () { + const relaido_uuid = 'hogehoge'; + let bidRequest; + let bidderRequest; + let serverResponse; + let serverRequest; + + beforeEach(function () { + bidRequest = { + bidder: 'relaido', + params: { + placementId: '100000', + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [640, 360] + ] + } + }, + adUnitCode: 'test', + bidId: '2ed93003f7bb99', + bidderRequestId: '1c50443387a1f2', + auctionId: '413ed000-8c7a-4ba1-a1fa-9732e006f8c3', + transactionId: '5c2d064c-7b76-42e8-a383-983603afdc45', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; + bidderRequest = { + timeout: 1000, + refererInfo: { + referer: 'https://publisher.com/home' + } + }; + serverResponse = { + body: { + status: 'ok', + price: 500, + model: 'vcpm', + currency: 'JPY', + creativeId: 1000, + uuid: relaido_uuid, + vast: '', + playerUrl: 'https://relaido/player.js', + syncUrl: 'https://relaido/sync.html' + } + }; + serverRequest = { + method: 'GET', + bidId: bidRequest.bidId, + width: bidRequest.mediaTypes.video.playerSize[0][0], + height: bidRequest.mediaTypes.video.playerSize[0][1], + mediaType: 'video', + }; + localStorage.setItem(UUID_KEY, relaido_uuid); + }); + + describe('spec.isBidRequestValid', function () { + it('should return true when the required params are passed by video', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when the required params are passed by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [300, 250] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + setUADefault(); + }); + + it('should return false when the uuid are missing', function () { + localStorage.removeItem(UUID_KEY); + const result = !!(utils.isSafariBrowser()); + expect(spec.isBidRequestValid(bidRequest)).to.equal(result); + }); + + it('should return false when the placementId params are missing', function () { + bidRequest.params.placementId = undefined; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the mediaType video params are missing', function () { + bidRequest.mediaTypes = { + video: {} + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the mediaType banner params are missing', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: {} + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + setUADefault(); + }); + + it('should return false when the non-mobile', function () { + bidRequest.mediaTypes = { + banner: { + sizes: [ + [300, 250] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the mediaTypes params are missing', function () { + bidRequest.mediaTypes = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('spec.buildRequests', function () { + it('should build bid requests by video', function () { + const bidRequests = spec.buildRequests([bidRequest], bidderRequest); + expect(bidRequests).to.have.lengthOf(1); + const request = bidRequests[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://api.relaido.jp/bid/v1/prebid/100000'); + expect(request.bidId).to.equal(bidRequest.bidId); + expect(request.width).to.equal(bidRequest.mediaTypes.video.playerSize[0][0]); + expect(request.height).to.equal(bidRequest.mediaTypes.video.playerSize[0][1]); + expect(request.mediaType).to.equal('video'); + expect(request.data.ref).to.equal(bidderRequest.refererInfo.referer); + expect(request.data.timeout_ms).to.equal(bidderRequest.timeout); + expect(request.data.ad_unit_code).to.equal(bidRequest.adUnitCode); + expect(request.data.auction_id).to.equal(bidRequest.auctionId); + expect(request.data.bidder).to.equal(bidRequest.bidder); + expect(request.data.bidder_request_id).to.equal(bidRequest.bidderRequestId); + expect(request.data.bid_requests_count).to.equal(bidRequest.bidRequestsCount); + expect(request.data.bid_id).to.equal(bidRequest.bidId); + expect(request.data.transaction_id).to.equal(bidRequest.transactionId); + expect(request.data.media_type).to.equal('video'); + expect(request.data.uuid).to.equal(relaido_uuid); + expect(request.data.width).to.equal(bidRequest.mediaTypes.video.playerSize[0][0]); + expect(request.data.height).to.equal(bidRequest.mediaTypes.video.playerSize[0][1]); + }); + + it('should build bid requests by banner', function () { + bidRequest.mediaTypes = { + banner: { + sizes: [ + [640, 360] + ] + } + }; + const bidRequests = spec.buildRequests([bidRequest], bidderRequest); + expect(bidRequests).to.have.lengthOf(1); + const request = bidRequests[0]; + expect(request.mediaType).to.equal('banner'); + }); + + it('The referrer should be the last', function () { + const bidRequests = spec.buildRequests([bidRequest], bidderRequest); + expect(bidRequests).to.have.lengthOf(1); + const request = bidRequests[0]; + const keys = Object.keys(request.data); + expect(keys[0]).to.equal('version'); + expect(keys[keys.length - 1]).to.equal('ref'); + }); + }); + + describe('spec.interpretResponse', function () { + it('should build bid response by video', function () { + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + expect(bidResponses).to.have.lengthOf(1); + const response = bidResponses[0]; + expect(response.requestId).to.equal(serverRequest.bidId); + expect(response.width).to.equal(serverRequest.width); + expect(response.height).to.equal(serverRequest.height); + expect(response.cpm).to.equal(serverResponse.body.price); + expect(response.currency).to.equal(serverResponse.body.currency); + expect(response.creativeId).to.equal(serverResponse.body.creativeId); + expect(response.vastXml).to.equal(serverResponse.body.vast); + expect(response.ad).to.be.undefined; + }); + + it('should build bid response by banner', function () { + serverRequest.mediaType = 'banner'; + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + expect(bidResponses).to.have.lengthOf(1); + const response = bidResponses[0]; + expect(response.requestId).to.equal(serverRequest.bidId); + expect(response.width).to.equal(serverRequest.width); + expect(response.height).to.equal(serverRequest.height); + expect(response.cpm).to.equal(serverResponse.body.price); + expect(response.currency).to.equal(serverResponse.body.currency); + expect(response.creativeId).to.equal(serverResponse.body.creativeId); + expect(response.vastXml).to.be.undefined; + expect(response.ad).to.include(`
`); + expect(response.ad).to.include(``); + expect(response.ad).to.include(`window.RelaidoPlayer.renderAd`); + }); + + it('should not build bid response', function () { + serverResponse = {}; + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + expect(bidResponses).to.have.lengthOf(0); + }); + + it('should not build bid response', function () { + serverResponse = { + body: { + status: 'no_ad', + } + }; + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + expect(bidResponses).to.have.lengthOf(0); + }); + }); + + describe('spec.getUserSyncs', function () { + it('should choose iframe sync urls', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: true}, [serverResponse]); + expect(userSyncs).to.deep.equal([{ + type: 'iframe', + url: serverResponse.body.syncUrl + }]); + }); + + it('should choose iframe sync urls if serverResponse are empty', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: true}, []); + expect(userSyncs).to.deep.equal([{ + type: 'iframe', + url: 'https://api.relaido.jp/tr/v1/prebid/sync.html' + }]); + }); + + it('should choose iframe sync urls if syncUrl are undefined', function () { + serverResponse.body.syncUrl = undefined; + let userSyncs = spec.getUserSyncs({iframeEnabled: true}, [serverResponse]); + expect(userSyncs).to.deep.equal([{ + type: 'iframe', + url: 'https://api.relaido.jp/tr/v1/prebid/sync.html' + }]); + }); + + it('should return empty if iframeEnabled are false', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false}, [serverResponse]); + expect(userSyncs).to.have.lengthOf(0); + }); + }); + + describe('spec.onBidWon', function () { + let stub; + beforeEach(() => { + stub = sinon.stub(utils, 'triggerPixel'); + }); + afterEach(() => { + stub.restore(); + }); + + it('Should create nurl pixel if bid nurl', function () { + let bid = { + bidder: bidRequest.bidder, + creativeId: serverResponse.body.creativeId, + cpm: serverResponse.body.price, + params: [bidRequest.params], + auctionId: bidRequest.auctionId, + requestId: bidRequest.bidId, + adId: '3b286a4db7031f', + adUnitCode: bidRequest.adUnitCode, + ref: window.location.href, + } + spec.onBidWon(bid); + const parser = utils.parseUrl(stub.getCall(0).args[0]); + const query = parser.search; + expect(parser.hostname).to.equal('api.relaido.jp'); + expect(parser.pathname).to.equal('/tr/v1/prebid/win.gif'); + expect(query.placement_id).to.equal('100000'); + expect(query.creative_id).to.equal('1000'); + expect(query.price).to.equal('500'); + expect(query.auction_id).to.equal('413ed000-8c7a-4ba1-a1fa-9732e006f8c3'); + expect(query.bid_id).to.equal('2ed93003f7bb99'); + expect(query.ad_id).to.equal('3b286a4db7031f'); + expect(query.ad_unit_code).to.equal('test'); + expect(query.ref).to.include(window.location.href); + }); + }); + + describe('spec.onTimeout', function () { + let stub; + beforeEach(() => { + stub = sinon.stub(utils, 'triggerPixel'); + }); + afterEach(() => { + stub.restore(); + }); + + it('Should create nurl pixel if bid nurl', function () { + const data = [{ + bidder: bidRequest.bidder, + bidId: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + auctionId: bidRequest.auctionId, + params: [bidRequest.params], + timeout: bidderRequest.timeout, + }]; + spec.onTimeout(data); + const parser = utils.parseUrl(stub.getCall(0).args[0]); + const query = parser.search; + expect(parser.hostname).to.equal('api.relaido.jp'); + expect(parser.pathname).to.equal('/tr/v1/prebid/timeout.gif'); + expect(query.placement_id).to.equal('100000'); + expect(query.timeout).to.equal('1000'); + expect(query.auction_id).to.equal('413ed000-8c7a-4ba1-a1fa-9732e006f8c3'); + expect(query.bid_id).to.equal('2ed93003f7bb99'); + expect(query.ad_unit_code).to.equal('test'); + expect(query.ref).to.include(window.location.href); + }); + }); +}); diff --git a/test/spec/modules/reloadBidAdapter_spec.js b/test/spec/modules/reloadBidAdapter_spec.js index 674c810d48a..b22dd9e7b92 100644 --- a/test/spec/modules/reloadBidAdapter_spec.js +++ b/test/spec/modules/reloadBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/reloadBidAdapter'; +import { spec } from 'modules/reloadBidAdapter.js'; let getParams = () => { return JSON.parse(JSON.stringify({ diff --git a/test/spec/modules/resultsmediaBidAdapter_spec.js b/test/spec/modules/resultsmediaBidAdapter_spec.js new file mode 100644 index 00000000000..0e2d4c0013a --- /dev/null +++ b/test/spec/modules/resultsmediaBidAdapter_spec.js @@ -0,0 +1,574 @@ +import {spec} from '../../../modules/resultsmediaBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +import * as sinon from 'sinon'; + +var r1adapter = spec; + +describe('resultsmedia adapter tests', function () { + beforeEach(function() { + this.defaultBidderRequest = { + 'refererInfo': { + 'referer': 'Reference Page', + 'stack': [ + 'aodomain.dvl', + 'page.dvl' + ] + } + }; + }); + + describe('Verify 1.0 POST Banner Bid Request', function () { + it('buildRequests works', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + expect(bidRequest.url).to.have.string('https://bid306.rtbsrv.com/bidder/?bid=3mhdom&zoneId=9999&hbv='); + expect(bidRequest.method).to.equal('POST'); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.site).to.not.equal(null); + expect(openrtbRequest.site.ref).to.equal('Reference Page'); + expect(openrtbRequest.device).to.not.equal(null); + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + expect(openrtbRequest.device.dnt).to.equal(0); + expect(openrtbRequest.imp[0].banner).to.not.equal(null); + expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(openrtbRequest.imp[0].ext.bidder.zoneId).to.equal(9999); + }); + + it('interpretResponse works', function() { + var bidList = { + 'body': [ + { + 'impid': 'div-gpt-ad-1438287399331-0', + 'w': 300, + 'h': 250, + 'adm': '
My Compelling Ad
', + 'price': 1, + 'crid': 'cr-cfy24' + } + ] + }; + + var bannerBids = r1adapter.interpretResponse(bidList); + + expect(bannerBids.length).to.equal(1); + const bid = bannerBids[0]; + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('cr-cfy24'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.cpm).to.equal(1.0); + expect(bid.ttl).to.equal(350); + }); + }); + + describe('Verify POST Video Bid Request', function() { + it('buildRequests works', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'sizes': [ + [300, 250] + ], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + expect(bidRequest.url).to.have.string('https://bid306.rtbsrv.com/bidder/?bid=3mhdom&zoneId=9999&hbv='); + expect(bidRequest.method).to.equal('POST'); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.site).to.not.equal(null); + expect(openrtbRequest.device).to.not.equal(null); + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + expect(openrtbRequest.device).to.have.property('dnt'); + expect(openrtbRequest.imp[0].video).to.not.equal(null); + expect(openrtbRequest.imp[0].video.w).to.equal(640); + expect(openrtbRequest.imp[0].video.h).to.equal(480); + expect(openrtbRequest.imp[0].video.mimes[0]).to.equal('video/mp4'); + expect(openrtbRequest.imp[0].video.protocols).to.eql([2, 3, 5, 6]); + expect(openrtbRequest.imp[0].video.startdelay).to.equal(0); + expect(openrtbRequest.imp[0].video.skip).to.equal(0); + expect(openrtbRequest.imp[0].video.playbackmethod).to.eql([1, 2, 3, 4]); + expect(openrtbRequest.imp[0].video.delivery[0]).to.equal(1); + expect(openrtbRequest.imp[0].video.api).to.eql([1, 2, 5]); + }); + + it('interpretResponse works', function() { + var bidList = { + 'body': [ + { + 'impid': 'div-gpt-ad-1438287399331-1', + 'price': 1, + 'adm': 'https://example.com/', + 'adomain': [ + 'test.com' + ], + 'cid': '467415', + 'crid': 'cr-vid', + 'w': 800, + 'h': 600 + } + ] + }; + + var videoBids = r1adapter.interpretResponse(bidList); + + expect(videoBids.length).to.equal(1); + const bid = videoBids[0]; + expect(bid.width).to.equal(800); + expect(bid.height).to.equal(600); + expect(bid.vastUrl).to.equal('https://example.com/'); + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('cr-vid'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.cpm).to.equal(1.0); + expect(bid.ttl).to.equal(600); + }); + }); + + describe('misc buildRequests', function() { + it('should send GDPR Consent data to resultsmedia tag', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-3', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var consentString = 'testConsentString'; + var gdprBidderRequest = this.defaultBidderRequest; + gdprBidderRequest.gdprConsent = { + 'gdprApplies': true, + 'consentString': consentString + }; + + var bidRequest = r1adapter.buildRequests(bidRequestList, gdprBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.user.ext.consent).to.equal(consentString); + expect(openrtbRequest.regs.ext.gdpr).to.equal(true); + }); + + it('prefer 2.0 sizes', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(600); + }); + + it('does not return request for invalid banner size configuration', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300]] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + expect(bidRequest.method).to.be.undefined; + }); + + it('does not return request for missing banner size configuration', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': {} + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + expect(bidRequest.method).to.be.undefined; + }); + + it('reject bad sizes', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': {'sizes': [['400', '500'], ['4n0', '5g0']]} + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); + }); + + it('dnt is correctly set to 1', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var dntStub = sinon.stub(utils, 'getDNT').returns(1); + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + dntStub.restore(); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.device.dnt).to.equal(1); + }); + + it('supports string video sizes', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': ['600', '300'] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.equal(600); + expect(openrtbRequest.imp[0].video.h).to.equal(300); + }); + + it('rejects bad video sizes', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': ['badWidth', 'badHeight'] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.be.undefined; + expect(openrtbRequest.imp[0].video.h).to.be.undefined; + }); + + it('supports missing video size', function () { + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.be.undefined; + expect(openrtbRequest.imp[0].video.h).to.be.undefined; + }); + + it('should return empty site data when refererInfo is missing', function() { + delete this.defaultBidderRequest.refererInfo; + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.site.domain).to.equal(''); + expect(openrtbRequest.site.page).to.equal(''); + expect(openrtbRequest.site.ref).to.equal(''); + }); + }); + + it('should return empty site.domain and site.page when refererInfo.stack is empty', function() { + this.defaultBidderRequest.refererInfo.stack = []; + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.site.domain).to.equal(''); + expect(openrtbRequest.site.page).to.equal(''); + expect(openrtbRequest.site.ref).to.equal('Reference Page'); + }); + + it('should secure correctly', function() { + this.defaultBidderRequest.refererInfo.stack[0] = ['https://securesite.dvl']; + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.imp[0].secure).to.equal(1); + }); + + it('should pass schain', function() { + var schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 1 + }] + }; + var bidRequestList = [ + { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead', + 'schain': schain + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.source.ext.schain).to.deep.equal(schain); + }); + + describe('misc interpretResponse', function () { + it('No bid response', function() { + var noBidResponse = r1adapter.interpretResponse({ + 'body': '' + }); + expect(noBidResponse.length).to.equal(0); + }); + }); + + describe('isBidRequestValid', function () { + var bid = { + 'bidder': 'resultsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'bannerDiv' + }; + + it('should return true when required params found', function () { + expect(r1adapter.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId missing', function () { + delete bid.params.zoneId; + expect(r1adapter.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('getUserSyncs', function () { + it('returns an empty string', function () { + expect(r1adapter.getUserSyncs()).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/revcontentBidAdapter_spec.js b/test/spec/modules/revcontentBidAdapter_spec.js new file mode 100644 index 00000000000..0a0263837c6 --- /dev/null +++ b/test/spec/modules/revcontentBidAdapter_spec.js @@ -0,0 +1,351 @@ +// jshint esversion: 6, es3: false, node: true +import {assert, expect} from 'chai'; +import {spec} from 'modules/revcontentBidAdapter.js'; +import { NATIVE } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; + +describe('revcontent adapter', function () { + let serverResponse, bidRequest, bidResponses; + let bids = []; + + describe('isBidRequestValid', function () { + let bid = { + bidder: 'revcontent', + nativeParams: {}, + params: { + size: {width: 300, height: 250}, + apiKey: '8a33fa9cf220ae685dcc3544f847cdda858d3b1c', + userId: 673, + domain: 'test.com', + endpoint: 'trends-s0.revcontent.com' + } + }; + + it('should return true when required params found', function () { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params are missing', function () { + bid.params.apiKey = undefined; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests', function () { + it('should send request with correct structure', function () { + let validBidRequests = [{ + bidder: 'revcontent', + nativeParams: {}, + params: { + size: {width: 300, height: 250}, + apiKey: '8a33fa9cf220ae685dcc3544f847cdda858d3b1c', + userId: 673, + widgetId: 33861, + endpoint: 'trends-s0.revcontent.com' + } + }]; + let request = spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}); + request = request[0]; + assert.equal(request.method, 'POST'); + assert.equal(request.url, 'https://trends-s0.revcontent.com/rtb?apiKey=8a33fa9cf220ae685dcc3544f847cdda858d3b1c&userId=673&widgetId=33861'); + assert.deepEqual(request.options, {contentType: 'application/json'}); + assert.ok(request.data); + }); + + it('should have default request structure', function () { + let keys = 'method,options,url,data,bid'.split(','); + let validBidRequests = [{ + bidder: 'revcontent', + nativeParams: {}, + params: { + size: {width: 300, height: 250}, + apiKey: '8a33fa9cf220ae685dcc3544f847cdda858d3b1c', + userId: 673, + domain: 'test.com', + endpoint: 'trends-s0.revcontent.com' + } + }]; + let request = spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}); + + request = request[0]; + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); + + it('should send info about device and unique bidfloor', function () { + let validBidRequests = [{ + bidder: 'revcontent', + nativeParams: {}, + params: { + size: {width: 300, height: 250}, + apiKey: '8a33fa9cf220ae685dcc3544f847cdda858d3b1c', + userId: 673, + domain: 'test.com', + endpoint: 'trends-s0.revcontent.com', + bidfloor: 0.05 + } + }]; + let request = spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}); + request = JSON.parse(request[0].data); + assert.equal(request.imp[0].bidfloor, 0.05); + assert.equal(request.device.ua, navigator.userAgent); + }); + + it('should send info about the site and default bidfloor', function () { + let validBidRequests = [{ + bidder: 'revcontent', + nativeParams: { + image: { + required: false + }, + 'title': { + required: false, + len: 140 + }, + clickUrl: { + required: false + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + } + }, + params: { + size: {width: 300, height: 250}, + apiKey: '8a33fa9cf220ae685dcc3544f847cdda858d3b1c', + userId: 673, + domain: 'test.com', + endpoint: 'trends-s0.revcontent.com' + } + }]; + let refererInfo = {referer: 'page'}; + let request = spec.buildRequests(validBidRequests, {refererInfo}); + + request = JSON.parse(request[0].data); + assert.equal(request.imp[0].bidfloor, 0.1); + assert.deepEqual(request.site, { + domain: 'test.com', + page: 'page', + cat: ['IAB17'], + publisher: {id: 673, domain: 'test.com'} + }); + }); + }); + + describe('interpretResponse', function () { + it('should return if no body in response', function () { + let serverResponse = {}; + let bidRequest = {}; + + assert.ok(!spec.interpretResponse(serverResponse, bidRequest)); + }); + + const serverResponse = { + body: { + id: '5d61ca27-1b7a-4d5a-90ad-bbfc93e53f58', + seatbid: [ + { + bid: [ + { + id: '6bbe3eed-f443-4e2b-a8da-57fd6327b37d', + impid: '1', + price: 0.1, + adid: '4162547', + nurl: 'https://trends-s0.revcontent.com/push/track/?p=${AUCTION_PRICE}&d=nTCdHIfsgKOLFuV7DS1LF%2FnTk5HiFduGU65BgKgB%2BvKyG9YV7ceQWN76HMbBE0C6gwQeXUjravv3Hq5x9TT8CM6r2oUNgkGC9mhgv2yroTH9i3cSoH%2BilxyY19fMXFirtBz%2BF%2FEXKi4bsNh%2BDMPfj0L4elo%2FJEZmx4nslvOneJJjsFjJJtUJc%2F3UPivOisSCa%2B36mAgFQqt%2FSWBriYB%2BVAufz70LaGspF6T6jDzuIyVFJUpLhZVDtLRSJEzh7Lyzzw1FmYarp%2FPg0gZDY48aDdjw5A3Tlj%2Bap0cPHLDprNOyF0dmHDn%2FOVJEDRTWvrQ2JNK1t%2Fg1bGHIih0ec6XBVIBNurqRpLFBuUY6LgXCt0wRZWTByTEZ8AEv8IoYVILJAL%2BXL%2F9IyS4eTcdOUfn5X7gT8QBghCrAFrsCg8ZXKgWddTEXbpN1lU%2FzHdI5eSHkxkJ6WcYxSkY9PyripaIbmKiyb98LQMgTD%2B20RJO5dAmXTQTAcauw6IUPTjgSPEU%2Bd6L5Txd3CM00Hbd%2Bw1bREIQcpKEmlMwrRSwe4bu1BCjlh5A9gvU9Xc2sf7ekS3qPPmtp059r5IfzdNFQJB5aH9HqeDEU%2FxbMHx4ggMgojLBBL1fKrCKLAteEDQxd7PVmFJv7GHU2733vt5TnjKiEhqxHVFyi%2B0MIYMGIziM5HfUqfq3KUf%2F%2FeiCtJKXjg7FS6hOambdimSt7BdGDIZq9QECWdXsXcQqqVLwli27HYDMFVU3TWWRyjkjbhnQID9gQJlcpwIi87jVAODb6qP%2FKGQ%3D%3D', + adm: '{"ver":"1.1","assets":[{"id":3,"required":1,"img":{"url":"//img.revcontent.com/?url=https://revcontent-p0.s3.amazonaws.com/content/images/15761052960288727821.jpg&static=true"}},{"id":0,"required":1,"title":{"text":"Do You Eat Any of These Craving-trigger Foods?"}},{"id":5,"required":1,"data":{"value":""}}],"link":{"url":"https://trends-s0.revcontent.com/click.php?d=A7EVbNYBVyonty19Ak08zCr9J54qg%2Bmduq6p0Zyn5%2F%2Bapm4deUo9VAXmOGEIbUBf6i7m3%2F%2FWJm%2FzTha8SJ%2Br9MZL9jhhUxDeiKb6aRY1biLrvr6tFUd1phvtKqVmPd76l9VBLFMxS1brSzKjRCJlIGmyGJg7ueFvxpE9X%2BpHmdbE2uqUdRC49ENO3XZyHCCKMAZ8XD29fasX9Kli9mKpZTqw8vayFlXbVYSUwB8wfSwCt1sIUrt0aICYc0jcyWU3785GTS1xXzQj%2FIVszFYYrdTWd%2BDijjNZtFny0OomPHp8lRy5VcQVCuLpw0Fks4myvsE38XcNvs4wO3tWTNrI%2BMqcW1%2BD2OnMSq5nN5FCbmi2ly%2F1LbN9fibaFvW%2FQbzQhN9ZsAwmhm409UTtdmSA6hd96vDxDWLeUJhVO3UQyI0yq2TtVnB9tEICD8mZNWwYehOab%2BQ1EWmTerF6ZCDx8RyZus1UrsDfRwvTCyUjCmkZhmeo4QVJkpPy6QobCsngSaxkkKhH%2Fb7coZyBXXEt3ORoYBLUbfRO6nR8GdIt8413vrYr4gTAroh46VcWK0ls0gFNe2u3%2FqP%2By1yLKbzDVaR%2Fa02G%2Biiqbw86sCYfsy7qK9atyjNTm8RkH6JLESUzxc6IEazu4iwHKGnu5phTacmseXCi8y9Y5AdBZn8VnLP%2F2a%2FyAqq93xEH%2BIrkAdhGRY1tY39rBYAtvH%2FVyNFZcong%2FutUMYbp0WhDNyfl6iWxmpE28Cx9KDcqXss0NIwQm0AWeu8ogJCIG3faAkm5PdFsUdf2X9h3HuFDbnbvnXW27ml6z9GykEzv%2F8aSZlMZ"}}' + } + ] + } + ], + bidid: '7f729368-edb2-427a-bde7-a55b3bf8837c' + }, + headers: {} + }; + + const bidRequest = { + method: 'POST', + options: { + contentType: 'application/json' + }, + url: 'https://trends-s0.revcontent.com/rtb?apiKey=8a33fa9cf220ae685dcc3544f847cdda858d3b1c&userId=673', + data: '{"id":"5d61ca27-1b7a-4d5a-90ad-bbfc93e53f58","imp":[{"id":1,"bidderRequestId":"14e4dab7b5396e8","auctionId":"5d61ca27-1b7a-4d5a-90ad-bbfc93e53f58","transactionId":"69e69abf-a3ea-484d-a81c-d48dd0d5eaa3","native":{"request":{"ver":"1.1","context":2,"contextsubtype":21,"plcmttype":4,"plcmtcnt":4,"assets":[{"required":0,"id":3,"img":{"type":3}},{"required":0,"id":0,"title":{"len":140}},{"required":0,"id":5,"data":{"type":1}}]},"ver":"1.1","battr":[1,3,8,11,17]},"instl":0,"bidfloor":0.1,"secure":"1"}],"site":{"domain":"test.com","page":"https://feudfun.com/test22/revcontent_example.php","cat":["IAB17"],"publisher":{"id":673,"domain":"test.com"}},"device":{"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0","language":"en"},"user":{"id":1},"at":2,"bcat":["IAB24","IAB25","IAB25-1","IAB25-2","IAB25-3","IAB25-4","IAB25-5","IAB25-6","IAB25-7","IAB26","IAB26-1","IAB26-2","IAB26-3","IAB26-4"]}', + bid: [ + { + bidder: 'revcontent', + params: { + size: { + width: 300, + height: 250 + }, + apiKey: '8a33fa9cf220ae685dcc3544f847cdda858d3b1c', + userId: 673, + domain: 'test.com', + endpoint: 'trends-s0.revcontent.com' + }, + crumbs: { + pubcid: '7a0b4adc-c109-49f0-aadc-4a4b62ebe269' + }, + nativeParams: { + image: { + required: false + }, + 'title': { + required: false, + len: 140 + }, + clickUrl: { + required: false + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + } + }, + mediaTypes: { + native: { + image: { + required: false + }, + title: { + required: false, + len: 140 + }, + clickUrl: { + required: false + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + } + } + }, + adUnitCode: '/19968336/header-bid-tag-1', + transactionId: '69e69abf-a3ea-484d-a81c-d48dd0d5eaa3', + sizes: [], + bidId: '294a7f446202848', + bidderRequestId: '14e4dab7b5396e8', + auctionId: '5d61ca27-1b7a-4d5a-90ad-bbfc93e53f58', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + } + ] + }; + + it('should set correct native params', function () { + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + + assert.equal(result.bidder, 'revcontent'); + assert.equal(result.bidderCode, 'revcontent'); + assert.equal(result.mediaType, 'native'); + assert.equal(result.requestId, '294a7f446202848'); + assert.equal(result.cpm, '0.1'); + assert.equal(result.creativeId, '4162547'); + }); + + it('validate template 728x90', function () { + bidRequest.bid[0].params.size.width = 728; + bidRequest.bid[0].params.size.height = 90; + + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + assert.equal(result.bidder, 'revcontent'); + assert.equal(result.bidderCode, 'revcontent'); + assert.equal(result.mediaType, 'native'); + assert.equal(result.requestId, '294a7f446202848'); + assert.equal(result.cpm, '0.1'); + assert.equal(result.creativeId, '4162547'); + }); + + it('validate template 300x600', function () { + bidRequest.bid[0].params.size.width = 300; + bidRequest.bid[0].params.size.height = 600; + + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + assert.equal(result.bidder, 'revcontent'); + assert.equal(result.bidderCode, 'revcontent'); + assert.equal(result.mediaType, 'native'); + assert.equal(result.requestId, '294a7f446202848'); + assert.equal(result.cpm, '0.1'); + assert.equal(result.creativeId, '4162547'); + }); + + it('validate template custom template', function () { + bidRequest.bid[0].params.template = '

{title}

SEE MORE
'; + + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + assert.equal(result.bidder, 'revcontent'); + assert.equal(result.bidderCode, 'revcontent'); + assert.equal(result.mediaType, 'native'); + assert.equal(result.requestId, '294a7f446202848'); + assert.equal(result.cpm, '0.1'); + assert.equal(result.creativeId, '4162547'); + }); + + it('validate template custom invalid template', function () { + bidRequest.bid[0].params.size.width = 100; + bidRequest.bid[0].params.size.height = 200; + + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + assert.equal(result.bidder, 'revcontent'); + assert.equal(result.bidderCode, 'revcontent'); + assert.equal(result.mediaType, 'native'); + assert.equal(result.requestId, '294a7f446202848'); + assert.equal(result.cpm, '0.1'); + assert.equal(result.creativeId, '4162547'); + }); + + it('should return empty when there is no bids in response', function () { + const serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [{bid: []}], + cur: 'USD' + } + }; + let bidRequest = { + data: {}, + bids: [{bidId: 'bidId1'}] + }; + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + assert.ok(!result); + }); + }); + + describe('onBidWon', function () { + it('default bid won', function () { + const bid = { + nurl: 'https://trends-s0.revcontent.com/push/track/?p=${AUCTION_PRICE}&d=nTCdHIfsgKOLFuV7DS1LF%2FnTk5HiFduGU65BgKgB%2BvKyG9YV7ceQWN76HMbBE0C6gwQeXUjravv3Hq5x9TT8CM6r2oUNgkGC9mhgv2yroTH9i3cSoH%2BilxyY19fMXFirtBz%2BF%2FEXKi4bsNh%2BDMPfj0L4elo%2FJEZmx4nslvOneJJjsFjJJtUJc%2F3UPivOisSCa%2B36mAgFQqt%2FSWBriYB%2BVAufz70LaGspF6T6jDzuIyVFJUpLhZVDtLRSJEzh7Lyzzw1FmYarp%2FPg0gZDY48aDdjw5A3Tlj%2Bap0cPHLDprNOyF0dmHDn%2FOVJEDRTWvrQ2JNK1t%2Fg1bGHIih0ec6XBVIBNurqRpLFBuUY6LgXCt0wRZWTByTEZ8AEv8IoYVILJAL%2BXL%2F9IyS4eTcdOUfn5X7gT8QBghCrAFrsCg8ZXKgWddTEXbpN1lU%2FzHdI5eSHkxkJ6WcYxSkY9PyripaIbmKiyb98LQMgTD%2B20RJO5dAmXTQTAcauw6IUPTjgSPEU%2Bd6L5Txd3CM00Hbd%2Bw1bREIQcpKEmlMwrRSwe4bu1BCjlh5A9gvU9Xc2sf7ekS3qPPmtp059r5IfzdNFQJB5aH9HqeDEU%2FxbMHx4ggMgojLBBL1fKrCKLAteEDQxd7PVmFJv7GHU2733vt5TnjKiEhqxHVFyi%2B0MIYMGIziM5HfUqfq3KUf%2F%2FeiCtJKXjg7FS6hOambdimSt7BdGDIZq9QECWdXsXcQqqVLwli27HYDMFVU3TWWRyjkjbhnQID9gQJlcpwIi87jVAODb6qP%2FKGQ%3D%3D', + cpm: '0.1' + }; + const result = spec.onBidWon(bid); + assert.ok(result); + }); + }); + + describe('onBidWon', function() { + const bid = { + nurl: 'https://trends-s0.revcontent.com/push/track/?p=${AUCTION_PRICE}&d=nTCdHIfsgKOLFuV7DS1LF%2FnTk5HiFduGU65BgKgB%2BvKyG9YV7ceQWN76HMbBE0C6gwQeXUjravv3Hq5x9TT8CM6r2oUNgkGC9mhgv2yroTH9i3cSoH%2BilxyY19fMXFirtBz%2BF%2FEXKi4bsNh%2BDMPfj0L4elo%2FJEZmx4nslvOneJJjsFjJJtUJc%2F3UPivOisSCa%2B36mAgFQqt%2FSWBriYB%2BVAufz70LaGspF6T6jDzuIyVFJUpLhZVDtLRSJEzh7Lyzzw1FmYarp%2FPg0gZDY48aDdjw5A3Tlj%2Bap0cPHLDprNOyF0dmHDn%2FOVJEDRTWvrQ2JNK1t%2Fg1bGHIih0ec6XBVIBNurqRpLFBuUY6LgXCt0wRZWTByTEZ8AEv8IoYVILJAL%2BXL%2F9IyS4eTcdOUfn5X7gT8QBghCrAFrsCg8ZXKgWddTEXbpN1lU%2FzHdI5eSHkxkJ6WcYxSkY9PyripaIbmKiyb98LQMgTD%2B20RJO5dAmXTQTAcauw6IUPTjgSPEU%2Bd6L5Txd3CM00Hbd%2Bw1bREIQcpKEmlMwrRSwe4bu1BCjlh5A9gvU9Xc2sf7ekS3qPPmtp059r5IfzdNFQJB5aH9HqeDEU%2FxbMHx4ggMgojLBBL1fKrCKLAteEDQxd7PVmFJv7GHU2733vt5TnjKiEhqxHVFyi%2B0MIYMGIziM5HfUqfq3KUf%2F%2FeiCtJKXjg7FS6hOambdimSt7BdGDIZq9QECWdXsXcQqqVLwli27HYDMFVU3TWWRyjkjbhnQID9gQJlcpwIi87jVAODb6qP%2FKGQ%3D%3D', + cpm: '0.1' + }; + + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('make sure only 1 ajax call is happening', function() { + spec.onBidWon(bid); + expect(utils.triggerPixel.calledOnce).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/rhythmoneBidAdapter_spec.js b/test/spec/modules/rhythmoneBidAdapter_spec.js index 80cbdb4a0af..d9342332e61 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -1,5 +1,5 @@ -import {spec} from '../../../modules/rhythmoneBidAdapter'; -import * as utils from '../../../src/utils'; +import {spec} from '../../../modules/rhythmoneBidAdapter.js'; +import * as utils from '../../../src/utils.js'; import * as sinon from 'sinon'; var r1adapter = spec; diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index cd6690e3276..f18e67a3ac5 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -2,20 +2,20 @@ import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' import { spec -} from 'modules/richaudienceBidAdapter'; -import {config} from 'src/config'; -import * as utils from 'src/utils'; +} from 'modules/richaudienceBidAdapter.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; describe('Richaudience adapter tests', function () { - var DEFAULT_PARAMS = [{ + var DEFAULT_PARAMS_NEW_SIZES = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', - sizes: [ - [300, 250], - [300, 600], - [728, 90], - [970, 250] - ], + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, bidder: 'richaudience', params: { bidfloor: 0.5, @@ -29,13 +29,37 @@ describe('Richaudience adapter tests', function () { user: {} }]; - var DEFAULT_PARAMS_NEW_SIZES = [{ + var DEFAULT_PARAMS_VIDEO_IN = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', mediaTypes: { - banner: { - sizes: [ - [300, 250], [300, 600], [728, 90], [970, 250]] + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_VIDEO_OUT = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'] } }, bidder: 'richaudience', @@ -51,6 +75,34 @@ describe('Richaudience adapter tests', function () { user: {} }]; + var DEFAULT_PARAMS_VIDEO_OUT_PARAMS = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + player: { + init: 'close', + end: 'close', + skin: 'dark' + } + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + var DEFAULT_PARAMS_APP = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', @@ -135,34 +187,12 @@ describe('Richaudience adapter tests', function () { } } - it('Verify build request', function () { - config.setConfig({ - 'currency': { - 'adServerCurrency': 'USD' - } - }); - - const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_PARAMS_GDPR); - - expect(request[0]).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('sizes'); - expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); - expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); - expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); - expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(600); - expect(requestContent.sizes[2]).to.have.property('w').and.to.equal(728); - expect(requestContent.sizes[2]).to.have.property('h').and.to.equal(90); - expect(requestContent.sizes[3]).to.have.property('w').and.to.equal(970); - expect(requestContent.sizes[3]).to.have.property('h').and.to.equal(250); - }); - it('Referer undefined', function() { config.setConfig({ 'currency': {'adServerCurrency': 'USD'} }) - const request = spec.buildRequests(DEFAULT_PARAMS, { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', gdprApplies: true @@ -174,7 +204,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('referer').and.to.equal(null); }) - it('Verify build request to prebid 3.0', function() { + it('Verify build request to prebid 3.0 display test', function() { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -212,6 +242,45 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('numIframes').and.to.equal(0); }) + it('Verify build request to prebid video inestream', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('demand').and.to.equal('video'); + expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); + // expect(requestContent.videoData.playerSize[0][0]).to.equal('640'); + // expect(requestContent.videoData.playerSize[0][0]).to.equal('480'); + }) + + it('Verify build request to prebid video outstream', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); + }) + describe('gdpr test', function () { it('Verify build request with GDPR', function () { config.setConfig({ @@ -497,7 +566,7 @@ describe('Richaudience adapter tests', function () { }); it('Verify interprete response', function () { - const request = spec.buildRequests(DEFAULT_PARAMS, { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', gdprApplies: true @@ -523,8 +592,26 @@ describe('Richaudience adapter tests', function () { expect(bid.dealId).to.equal('dealId'); }); - it('no banner media response', function () { - const request = spec.buildRequests(DEFAULT_PARAMS, { + it('no banner media response inestream', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + const bid = bids[0]; + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + }); + + it('no banner media response outstream', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', gdprApplies: true @@ -537,7 +624,9 @@ describe('Richaudience adapter tests', function () { const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); const bid = bids[0]; + expect(bid.mediaType).to.equal('video'); expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); }); it('Verifies bidder_code', function () { @@ -550,7 +639,7 @@ describe('Richaudience adapter tests', function () { }); it('Verifies if bid request is valid', function () { - expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); expect(spec.isBidRequestValid({})).to.equal(false); expect(spec.isBidRequestValid({ @@ -612,13 +701,14 @@ describe('Richaudience adapter tests', function () { })).to.equal(true); }); - it('Verifies user sync', function () { + it('Verifies user syncs iframe', function () { var syncs = spec.getUserSyncs({ iframeEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', gdprApplies: true }); + expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); syncs = spec.getUserSyncs({ @@ -647,10 +737,13 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true } }); + }); - syncs = spec.getUserSyncs({ + it('Verifies user syncs image', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: false, pixelEnabled: true - }, [], { + }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', referer: 'http://domain.com', gdprApplies: true @@ -659,13 +752,25 @@ describe('Richaudience adapter tests', function () { expect(syncs[0].type).to.equal('image'); syncs = spec.getUserSyncs({ + iframeEnabled: false, pixelEnabled: true - }, [], { + }, [BID_RESPONSE], { consentString: '', referer: 'http://domain.com', gdprApplies: true }) expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); }); }); diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 2676c3a59b6..9add7ed5f7d 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import analyticsAdapter from 'modules/rivrAnalyticsAdapter'; +import * as utils from 'src/utils.js'; +import analyticsAdapter from 'modules/rivrAnalyticsAdapter.js'; import { sendImpressions, handleClickEventWithClosureScope, @@ -15,10 +15,10 @@ import { storeAndReturnRivrUsrIdCookie, arrayDifference, activelyWaitForBannersToRender, -} from 'modules/rivrAnalyticsAdapter'; +} from 'modules/rivrAnalyticsAdapter.js'; import {expect} from 'chai'; -import adapterManager from 'src/adapterManager'; -import * as ajax from 'src/ajax'; +import adapterManager from 'src/adapterManager.js'; +import * as ajax from 'src/ajax.js'; import CONSTANTS from 'src/constants.json'; const events = require('../../../src/events'); diff --git a/test/spec/modules/roxotAnalyticsAdapter_spec.js b/test/spec/modules/roxotAnalyticsAdapter_spec.js index d40ceb61cd5..79c58e36735 100644 --- a/test/spec/modules/roxotAnalyticsAdapter_spec.js +++ b/test/spec/modules/roxotAnalyticsAdapter_spec.js @@ -1,13 +1,11 @@ -import roxotAnalytic from 'modules/roxotAnalyticsAdapter'; +import roxotAnalytic from 'modules/roxotAnalyticsAdapter.js'; import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; let events = require('src/events'); let constants = require('src/constants.json'); describe('Roxot Prebid Analytic', function () { - let xhr; - let requests; - let roxotConfigServerUrl = 'config-server'; let roxotEventServerUrl = 'event-server'; let publisherId = 'test_roxot_prebid_analytics_publisher_id'; @@ -161,17 +159,8 @@ describe('Roxot Prebid Analytic', function () { let bidderDone = bidRequested; let bidWon = bidAdjustmentWithBid; - before(function () { - xhr = sinon.useFakeXMLHttpRequest(); - xhr.onCreate = request => requests.push(request); - }); - after(function () { - xhr.restore(); - }); - describe('correct build and send events', function () { beforeEach(function () { - requests = []; sinon.stub(events, 'getEvents').returns([]); }); afterEach(function () { @@ -188,9 +177,9 @@ describe('Roxot Prebid Analytic', function () { } }); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://' + roxotConfigServerUrl + '/c?publisherId=' + publisherId + '&host=localhost'); - requests[0].respond(200, {'Content-Type': 'application/json'}, '{"a": 1, "i": 1, "bat": 1}'); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://' + roxotConfigServerUrl + '/c?publisherId=' + publisherId + '&host=localhost'); + server.requests[0].respond(200, {'Content-Type': 'application/json'}, '{"a": 1, "i": 1, "bat": 1}'); events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); @@ -205,13 +194,13 @@ describe('Roxot Prebid Analytic', function () { events.emit(constants.EVENTS.BIDDER_DONE, bidderDone); events.emit(constants.EVENTS.BID_WON, bidWon); - expect(requests.length).to.equal(4); + expect(server.requests.length).to.equal(4); - expect(requests[1].url).to.equal('https://' + roxotEventServerUrl + '/a?publisherId=' + publisherId + '&host=localhost'); - expect(requests[2].url).to.equal('https://' + roxotEventServerUrl + '/bat?publisherId=' + publisherId + '&host=localhost'); - expect(requests[3].url).to.equal('https://' + roxotEventServerUrl + '/i?publisherId=' + publisherId + '&host=localhost'); + expect(server.requests[1].url).to.equal('https://' + roxotEventServerUrl + '/a?publisherId=' + publisherId + '&host=localhost'); + expect(server.requests[2].url).to.equal('https://' + roxotEventServerUrl + '/bat?publisherId=' + publisherId + '&host=localhost'); + expect(server.requests[3].url).to.equal('https://' + roxotEventServerUrl + '/i?publisherId=' + publisherId + '&host=localhost'); - let auction = JSON.parse(requests[1].requestBody); + let auction = JSON.parse(server.requests[1].requestBody); expect(auction).to.include.all.keys('event', 'eventName', 'options', 'data'); expect(auction.event).to.equal('a'); @@ -228,7 +217,7 @@ describe('Roxot Prebid Analytic', function () { expect(auction.data.adUnits[bidAfterTimeoutAdUnit].bidders[bidder].status).to.equal('timeout'); expect(auction.data.adUnits[noBidAdUnit].bidders[bidder].status).to.equal('noBid'); - let bidAfterTimeout = JSON.parse(requests[2].requestBody); + let bidAfterTimeout = JSON.parse(server.requests[2].requestBody); expect(bidAfterTimeout).to.include.all.keys('event', 'eventName', 'options', 'data'); expect(bidAfterTimeout.event).to.equal('bat'); @@ -237,7 +226,7 @@ describe('Roxot Prebid Analytic', function () { expect(bidAfterTimeout.data.bidder).to.equal(bidder); expect(bidAfterTimeout.data.cpm).to.equal(bidAdjustmentAfterTimeout.cpm); - let impression = JSON.parse(requests[3].requestBody); + let impression = JSON.parse(server.requests[3].requestBody); expect(impression).to.include.all.keys('event', 'eventName', 'options', 'data'); expect(impression.event).to.equal('i'); @@ -250,7 +239,6 @@ describe('Roxot Prebid Analytic', function () { describe('support ad unit filter', function () { beforeEach(function () { - requests = []; sinon.stub(events, 'getEvents').returns([]); }); afterEach(function () { @@ -268,9 +256,9 @@ describe('Roxot Prebid Analytic', function () { } }); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://' + roxotConfigServerUrl + '/c?publisherId=' + publisherId + '&host=localhost'); - requests[0].respond(200, {'Content-Type': 'application/json'}, '{"a": 1, "i": 1, "bat": 1}'); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://' + roxotConfigServerUrl + '/c?publisherId=' + publisherId + '&host=localhost'); + server.requests[0].respond(200, {'Content-Type': 'application/json'}, '{"a": 1, "i": 1, "bat": 1}'); events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); @@ -285,12 +273,12 @@ describe('Roxot Prebid Analytic', function () { events.emit(constants.EVENTS.BIDDER_DONE, bidderDone); events.emit(constants.EVENTS.BID_WON, bidWon); - expect(requests.length).to.equal(3); + expect(server.requests.length).to.equal(3); - expect(requests[1].url).to.equal('https://' + roxotEventServerUrl + '/a?publisherId=' + publisherId + '&host=localhost'); - expect(requests[2].url).to.equal('https://' + roxotEventServerUrl + '/bat?publisherId=' + publisherId + '&host=localhost'); + expect(server.requests[1].url).to.equal('https://' + roxotEventServerUrl + '/a?publisherId=' + publisherId + '&host=localhost'); + expect(server.requests[2].url).to.equal('https://' + roxotEventServerUrl + '/bat?publisherId=' + publisherId + '&host=localhost'); - let auction = JSON.parse(requests[1].requestBody); + let auction = JSON.parse(server.requests[1].requestBody); expect(auction.data.adUnits).to.include.all.keys(noBidAdUnit, bidAfterTimeoutAdUnit); expect(auction.data.adUnits).to.not.include.all.keys(bidAdUnit); }); @@ -298,7 +286,6 @@ describe('Roxot Prebid Analytic', function () { describe('should correct parse config', function () { beforeEach(function () { - requests = []; sinon.stub(events, 'getEvents').returns([]); }); @@ -421,7 +408,7 @@ describe('Roxot Prebid Analytic', function () { options: publisherOptions }); - requests[0].respond(500); + server.requests[0].respond(500); expect(roxotAnalytic.getOptions().serverConfig).to.deep.equal({a: 1, i: 1, bat: 1, isError: 1}); }); diff --git a/test/spec/modules/rtbdemandBidAdapter_spec.js b/test/spec/modules/rtbdemandBidAdapter_spec.js index 6b5ef215162..be9872ec01b 100644 --- a/test/spec/modules/rtbdemandBidAdapter_spec.js +++ b/test/spec/modules/rtbdemandBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/rtbdemandBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/rtbdemandBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('rtbdemandAdapter', function () { const adapter = newBidder(spec); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 6c6faf4e4d0..efaed87dd15 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('RTBHouseAdapter', () => { const adapter = newBidder(spec); @@ -69,6 +69,18 @@ describe('RTBHouseAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', 'transactionId': 'example-transaction-id', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'directseller.com', + 'sid': '00001', + 'rid': 'BidRequest1', + 'hp': 1 + } + ] + } } ]; const bidderRequest = { @@ -173,6 +185,36 @@ describe('RTBHouseAdapter', () => { expect(data.imp[0].bidfloor).to.equal(0.01) }); + it('should include source.ext.schain in request', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.source.ext.schain).to.deep.equal({ + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'directseller.com', + 'sid': '00001', + 'rid': 'BidRequest1', + 'hp': 1 + } + ] + }); + }); + + it('should not include invalid schain', () => { + const bidRequest = Object.assign([], bidRequests); + bidRequest[0].schain = { + 'nodes': [{ + 'unknown_key': 1 + }] + }; + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.source).to.not.have.property('ext'); + }); + describe('native imp', () => { function basicRequest(extension) { return Object.assign({ diff --git a/test/spec/modules/rtbsapeBidAdapter_spec.js b/test/spec/modules/rtbsapeBidAdapter_spec.js new file mode 100644 index 00000000000..4c3814385b3 --- /dev/null +++ b/test/spec/modules/rtbsapeBidAdapter_spec.js @@ -0,0 +1,166 @@ +import {expect} from 'chai'; +import {spec} from 'modules/rtbsapeBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {executeRenderer, Renderer} from 'src/Renderer.js'; + +describe('rtbsapeBidAdapterTests', function () { + describe('isBidRequestValid', function () { + it('valid', function () { + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {banner: true}, params: {placeId: 4321}})).to.equal(true); + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {video: true}, params: {placeId: 4321}})).to.equal(true); + }); + + it('invalid', function () { + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {banner: true}, params: {}})).to.equal(false); + expect(spec.isBidRequestValid({bidder: 'rtbsape', params: {placeId: 4321}})).to.equal(false); + }); + }); + + it('buildRequests', function () { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'rtbsape', + params: {placeId: 4321}, + sizes: [[240, 400]] + }]; + let bidderRequest = { + auctionId: '2e208334-cafe-4c2c-b06b-f055ff876852', + bidderRequestId: '1392d0aa613366', + refererInfo: {} + }; + let request = spec.buildRequests(bidRequestData, bidderRequest); + expect(request.data.auctionId).to.equal('2e208334-cafe-4c2c-b06b-f055ff876852'); + expect(request.data.requestId).to.equal('1392d0aa613366'); + expect(request.data.bids[0].bidId).to.equal('bid1234'); + expect(request.data.timezone).to.not.equal(undefined); + }); + + describe('interpretResponse', function () { + it('banner', function () { + let serverResponse = { + body: { + bids: [{ + requestId: 'bid1234', + cpm: 2.21, + currency: 'RUB', + width: 240, + height: 400, + netRevenue: true, + ad: 'Ad html' + }] + } + }; + let bids = spec.interpretResponse(serverResponse, {data: {bids: [{mediaTypes: {banner: true}}]}}); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.cpm).to.equal(2.21); + expect(bid.currency).to.equal('RUB'); + expect(bid.width).to.equal(240); + expect(bid.height).to.equal(400); + expect(bid.netRevenue).to.equal(true); + expect(bid.requestId).to.equal('bid1234'); + expect(bid.ad).to.equal('Ad html'); + }); + + describe('video (outstream)', function () { + let bid; + + before(() => { + let serverResponse = { + body: { + bids: [{ + requestId: 'bid1234', + adUnitCode: 'ad-bid1234', + cpm: 3.32, + currency: 'RUB', + width: 600, + height: 340, + netRevenue: true, + vastUrl: 'https://cdn-rtb.sape.ru/vast/4321.xml', + meta: { + mediaType: 'video' + } + }] + } + }; + let serverRequest = { + data: { + bids: [{ + bidId: 'bid1234', + adUnitCode: 'ad-bid1234', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: { + placeId: 4321, + video: { + playerMuted: false + } + } + }] + } + }; + let bids = spec.interpretResponse(serverResponse, serverRequest); + expect(bids).to.have.lengthOf(1); + bid = bids[0]; + }); + + it('should add renderer', () => { + expect(bid).to.have.own.property('renderer'); + expect(bid.renderer).to.be.instanceof(Renderer); + expect(bid.renderer.url).to.equal('https://cdn-rtb.sape.ru/js/player.js'); + expect(bid.playerMuted).to.equal(false); + }); + + it('should create player instance', () => { + let spy = false; + + window.sapeRtbPlayerHandler = function (id, w, h, m) { + const player = {addSlot: () => [id, w, h, m]} + expect(spy).to.equal(false); + spy = sinon.spy(player, 'addSlot'); + return player; + }; + + executeRenderer(bid.renderer, bid); + expect(spy).to.not.equal(false); + expect(spy.called).to.be.true; + + const spyCall = spy.getCall(0); + expect(spyCall.args[0].url).to.be.equal('https://cdn-rtb.sape.ru/vast/4321.xml'); + expect(spyCall.returnValue[0]).to.be.equal('ad-bid1234'); + expect(spyCall.returnValue[1]).to.be.equal(600); + expect(spyCall.returnValue[2]).to.be.equal(340); + expect(spyCall.returnValue[3]).to.be.equal(false); + }); + }); + }); + + it('getUserSyncs', function () { + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.be.an('array').that.to.have.lengthOf(1); + expect(syncs[0]).to.deep.equal({type: 'iframe', url: 'https://www.acint.net/mc/?dp=141'}); + }); + + describe('onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('called once', function () { + spec.onBidWon({cpm: '2.21', nurl: 'https://ssp-rtb.sape.ru/track?event=win'}); + expect(utils.triggerPixel.calledOnce).to.equal(true); + }); + + it('called false', function () { + spec.onBidWon({cpm: '2.21'}); + expect(utils.triggerPixel.called).to.equal(false); + }); + }); +}); diff --git a/test/spec/modules/rtbsolutionsBidAdapter_spec.js b/test/spec/modules/rtbsolutionsBidAdapter_spec.js index a9ffcd2ce4b..c47b086fe50 100644 --- a/test/spec/modules/rtbsolutionsBidAdapter_spec.js +++ b/test/spec/modules/rtbsolutionsBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/rtbsolutionsBidAdapter'; +import { spec } from 'modules/rtbsolutionsBidAdapter.js'; describe('rtbsolutionsBidAdapterTests', function () { it('validate_pub_params_1', function () { diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..0c2c83a4b37 --- /dev/null +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -0,0 +1,1005 @@ +import rubiconAnalyticsAdapter, { + SEND_TIMEOUT, + parseBidResponse, + getHostNameFromReferer, +} from 'modules/rubiconAnalyticsAdapter.js'; +import CONSTANTS from 'src/constants.json'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; + +import { + setConfig, + addBidResponseHook, +} from 'modules/currency.js'; + +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.js'" causes the events.getEvents stub not to work... +let events = require('src/events.js'); +let utils = require('src/utils.js'); + +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_WON, + BID_TIMEOUT, + SET_TARGETING + } +} = CONSTANTS; + +const BID = { + 'bidder': 'rubicon', + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'statusMessage': 'Bid available', + 'bidId': '2ecff0db240757', + 'adId': 'fake_ad_id', + 'source': 'client', + 'requestId': '2ecff0db240757', + 'currency': 'USD', + 'creativeId': '3571560', + 'cpm': 1.22752, + 'ttl': 300, + 'netRevenue': false, + 'ad': '', + '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', + bidId: '3bd4ebb1c900e2', + adId: 'fake_ad_id', + requestId: '3bd4ebb1c900e2', + width: 728, + height: 90, + mediaType: 'banner', + cpm: 1.52, + source: 'server', + seatBidId: 'aaaa-bbbb-cccc-dddd', + rubiconTargeting: { + 'rpfl_elemid': '/19968336/header-bid-tag1', + 'rpfl_14062': '2_tier0100' + }, + 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: { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag1', + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 + } + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } + ], + 'adUnitCodes': ['/19968336/header-bid-tag1'], + 'bidderRequests': [ { + 'bidderCode': 'rubicon', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'referer': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000, + 'config': { + 'accountId': 1001, 'endpoint': '//localhost:9999/event' + } + }, + BID_REQUESTED: { + 'bidder': 'rubicon', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'rubicon', + 'params': { + 'accountId': '1001', + '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, + 'refererInfo': { + 'referer': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + }, + BID_RESPONSE: [ + BID, + BID2 + ], + AUCTION_END: { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + }, + BID_WON: [ + Object.assign({}, BID, { + 'status': 'rendered' + }), + Object.assign({}, BID2, { + 'status': 'rendered' + }) + ], + BIDDER_DONE: { + 'bidderCode': 'rubicon', + 'bids': [ + BID, + Object.assign({}, BID2, { + 'serverResponseTimeMs': 42, + }) + ] + }, + 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', + 'referrerHostname': 'www.test.com', + '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', + 'accountId': 1001, + 'siteId': 70608, + 'zoneId': 335918, + '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': '1001', + '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': 'aaaa-bbbb-cccc-dddd', + '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, + 'siteId': 70608, + 'zoneId': 335918, + 'params': { + 'accountId': '1001', + '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': 'aaaa-bbbb-cccc-dddd', + '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' + } + ], + 'wrapperName': '10000_fakewrapper_test' +}; + +function performStandardAuction() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); +} + +describe('rubicon analytics adapter', function () { + let sandbox; + let clock; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + + sandbox.stub(events, 'getEvents').returns([]); + + clock = sandbox.useFakeTimers(1519767013781); + + rubiconAnalyticsAdapter.referrerHostname = ''; + + config.setConfig({ + s2sConfig: { + timeout: 1000, + accountId: 10000, + }, + rubicon: { + wrapperName: '10000_fakewrapper_test' + } + }) + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + }); + + it('should require accountId', function () { + sandbox.stub(utils, 'logError'); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event' + } + }); + + expect(utils.logError.called).to.equal(true); + }); + + it('should require endpoint', function () { + sandbox.stub(utils, 'logError'); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + accountId: 1001 + } + }); + + expect(utils.logError.called).to.equal(true); + }); + + describe('sampling', function () { + beforeEach(function () { + sandbox.stub(Math, 'random').returns(0.08); + sandbox.stub(utils, 'logError'); + }); + + afterEach(function () { + rubiconAnalyticsAdapter.disableAnalytics(); + }); + + describe('with options.samplingFactor', function () { + it('should sample', function () { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + samplingFactor: 10 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + }); + + it('should unsample', function () { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + samplingFactor: 20 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(0); + }); + + it('should throw errors for invalid samplingFactor', function () { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + samplingFactor: 30 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(0); + expect(utils.logError.called).to.equal(true); + }); + }); + describe('with options.sampling', function () { + it('should sample', function () { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + sampling: 0.1 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + }); + + it('should unsample', function () { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + sampling: 0.05 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(0); + }); + + it('should throw errors for invalid samplingFactor', function () { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + sampling: 1 / 30 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(0); + expect(utils.logError.called).to.equal(true); + }); + }); + }); + + describe('when handling events', function () { + beforeEach(function () { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + }); + + afterEach(function () { + rubiconAnalyticsAdapter.disableAnalytics(); + }); + + it('should build a batched message from prebid events', function () { + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + let request = server.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); + }); + + function performFloorAuction(provider) { + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.bidderRequests[0].bids[0].floorData = { + skipped: false, + modelVersion: 'someModelName', + location: 'setConfig', + skipRate: 15, + fetchStatus: 'error', + floorProvider: provider + }; + let flooredResponse = { + ...BID, + floorData: { + floorValue: 4, + floorRule: '12345/sports|video', + floorCurrency: 'USD', + cpmAfterAdjustments: 2.1, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + matchedFields: { + gptSlot: '12345/sports', + mediaType: 'video' + } + }, + status: 'bidRejected', + cpm: 0, + getStatusCode() { + return 2; + } + }; + + let notFlooredResponse = { + ...BID2, + floorData: { + floorValue: 1, + floorRule: '12345/news|banner', + floorCurrency: 'USD', + cpmAfterAdjustments: 1.55, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + matchedFields: { + gptSlot: '12345/news', + mediaType: 'banner' + } + } + }; + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, flooredResponse); + events.emit(BID_RESPONSE, notFlooredResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[1]); + clock.tick(SEND_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + return message; + } + + it('should capture price floor information correctly', function () { + let message = performFloorAuction('rubicon') + + // verify our floor stuff is passed + // top level floor info + expect(message.auctions[0].floors).to.deep.equal({ + location: 'setConfig', + modelName: 'someModelName', + skipped: false, + enforcement: true, + dealsEnforced: false, + skipRate: 15, + fetchStatus: 'error', + provider: 'rubicon' + }); + // first adUnit's adSlot + expect(message.auctions[0].adUnits[0].adSlot).to.equal('12345/sports'); + // since no other bids, we set adUnit status to no-bid + expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); + // first adUnits bid is rejected + expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected'); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); + // if bid rejected should take cpmAfterAdjustments val + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); + + // second adUnit's adSlot + expect(message.auctions[0].adUnits[1].adSlot).to.equal('12345/news'); + // top level adUnit status is success + expect(message.auctions[0].adUnits[1].status).to.equal('success'); + // second adUnits bid is success + expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); + }); + + it('should still send floor info if provider is not rubicon', function () { + let message = performFloorAuction('randomProvider') + + // verify our floor stuff is passed + // top level floor info + expect(message.auctions[0].floors).to.deep.equal({ + location: 'setConfig', + modelName: 'someModelName', + skipped: false, + enforcement: true, + dealsEnforced: false, + skipRate: 15, + fetchStatus: 'error', + provider: 'randomProvider' + }); + // first adUnit's adSlot + expect(message.auctions[0].adUnits[0].adSlot).to.equal('12345/sports'); + // since no other bids, we set adUnit status to no-bid + expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); + // first adUnits bid is rejected + expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected'); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); + // if bid rejected should take cpmAfterAdjustments val + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); + + // second adUnit's adSlot + expect(message.auctions[0].adUnits[1].adSlot).to.equal('12345/news'); + // top level adUnit status is success + expect(message.auctions[0].adUnits[1].status).to.equal('success'); + // second adUnits bid is success + expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); + }); + + it('should correctly overwrite bidId if seatBidId is on the bidResponse', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let seatBidResponse = utils.deepClone(BID2); + seatBidResponse.seatBidId = 'abc-123-do-re-me'; + + const setTargeting = { + [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting + }; + + const bidWon = Object.assign({}, seatBidResponse, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('abc-123-do-re-me'); + expect(message.bidsWon[0].bidId).to.equal('abc-123-do-re-me'); + }); + + it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let duplicateResponse1 = utils.deepClone(BID2); + duplicateResponse1.cpm = 1.0; + duplicateResponse1.adserverTargeting.hb_pb = '1.0'; + duplicateResponse1.adserverTargeting.hb_adid = '1111'; + let duplicateResponse2 = utils.deepClone(BID2); + duplicateResponse2.cpm = 5.5; + duplicateResponse2.adserverTargeting.hb_pb = '5.5'; + duplicateResponse2.adserverTargeting.hb_adid = '5555'; + let duplicateResponse3 = utils.deepClone(BID2); + duplicateResponse3.cpm = 0.1; + duplicateResponse3.adserverTargeting.hb_pb = '0.1'; + duplicateResponse3.adserverTargeting.hb_adid = '3333'; + + const setTargeting = { + [duplicateResponse2.adUnitCode]: duplicateResponse2.adserverTargeting + }; + + const bidWon = Object.assign({}, duplicateResponse2, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, duplicateResponse1); + events.emit(BID_RESPONSE, duplicateResponse2); + events.emit(BID_RESPONSE, duplicateResponse3); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(5.5); + expect(message.auctions[0].adUnits[0].adserverTargeting.hb_pb).to.equal('5.5'); + expect(message.auctions[0].adUnits[0].adserverTargeting.hb_adid).to.equal('5555'); + expect(message.bidsWon.length).to.equal(1); + expect(message.bidsWon[0].bidResponse.bidPriceUSD).to.equal(5.5); + expect(message.bidsWon[0].adserverTargeting.hb_pb).to.equal('5.5'); + expect(message.bidsWon[0].adserverTargeting.hb_adid).to.equal('5555'); + }); + + it('should send batched message without BID_WON if necessary and further BID_WON events individually', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(SEND_TIMEOUT + 1000); + + events.emit(BID_WON, MOCK.BID_WON[1]); + + expect(server.requests.length).to.equal(2); + + let message = JSON.parse(server.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(server.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', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(SEND_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.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'); + }); + + it('should successfully convert bid price to USD in parseBidResponse', function () { + // Set the rates + setConfig({ + adServerCurrency: 'JPY', + rates: { + USD: { + JPY: 100 + } + } + }); + + // set our bid response to JPY + const bidCopy = utils.deepClone(BID2); + bidCopy.currency = 'JPY'; + bidCopy.cpm = 100; + + // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response + let innerBid; + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bidCopy); + + // Use the rubi analytics parseBidResponse Function to get the resulting cpm from the bid response! + const bidResponseObj = parseBidResponse(innerBid); + expect(bidResponseObj).to.have.property('bidPriceUSD'); + expect(bidResponseObj.bidPriceUSD).to.equal(1.0); + }); + }); + + describe('config with integration type', () => { + it('should use the integration type provided in the config instead of the default', () => { + sandbox.stub(config, 'getConfig').callsFake(function (key) { + const config = { + 'rubicon.int_type': 'testType' + }; + return config[key]; + }); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.integration).to.equal('testType'); + + rubiconAnalyticsAdapter.disableAnalytics(); + }); + }); + + it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { + let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); + inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); + inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; + expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); + + // not non-UTF char's in query / path which break if noDecodeWholeURL not set + inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; + expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + }); +}); diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json index 686aced840f..16cca629d8c 100644 --- a/test/spec/modules/rubiconAnalyticsSchema.json +++ b/test/spec/modules/rubiconAnalyticsSchema.json @@ -292,7 +292,8 @@ "enum": [ "success", "no-bid", - "error" + "error", + "rejected" ] }, "error": { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index d0c0773ff45..808d4840418 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1,21 +1,19 @@ import {expect} from 'chai'; -import adapterManager from 'src/adapterManager'; -import {spec, getPriceGranularity, masSizeOrdering, resetUserSync, hasVideoMediaType, FASTLANE_ENDPOINT} from 'modules/rubiconBidAdapter'; +import adapterManager from 'src/adapterManager.js'; +import {spec, getPriceGranularity, masSizeOrdering, resetUserSync, hasVideoMediaType, FASTLANE_ENDPOINT} from 'modules/rubiconBidAdapter.js'; 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'; -import find from 'core-js/library/fn/array/find'; - -var CONSTANTS = require('src/constants.json'); +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid +const PBS_INTEGRATION = 'pbjs'; describe('the rubicon adapter', function () { let sandbox, bidderRequest, - sizeMap; + sizeMap, + getFloorResponse; /** * @typedef {Object} sizeMapConverted @@ -224,8 +222,10 @@ describe('the rubicon adapter', function () { lipb: { lipbid: '0000-1111-2222-3333', segments: ['segA', 'segB'] - } - } + }, + idl_env: '1111-2222-3333-4444' + }; + bid.storedAuctionResponse = 11111; } function createVideoBidderRequestNoVideo() { @@ -272,7 +272,7 @@ describe('the rubicon adapter', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); - + getFloorResponse = {}; bidderRequest = { bidderCode: 'rubicon', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', @@ -409,6 +409,43 @@ describe('the rubicon adapter', function () { }); }); + it('should correctly send hard floors when getFloor function is present and returns valid floor', function () { + // default getFloor response is empty object so should not break and not send hard_floor + bidderRequest.bids[0].getFloor = () => getFloorResponse; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + expect(data.rp_hard_floor).to.be.undefined; + + // not an object should work and not send + getFloorResponse = undefined; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + data = parseQuery(request.data); + expect(data.rp_hard_floor).to.be.undefined; + + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR', floor: 1.0}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + data = parseQuery(request.data); + expect(data.rp_hard_floor).to.be.undefined; + + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + data = parseQuery(request.data); + expect(data.rp_hard_floor).to.be.undefined; + + // make it respond with USD floor and string floor + getFloorResponse = {currency: 'USD', floor: '1.23'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + data = parseQuery(request.data); + expect(data.rp_hard_floor).to.equal('1.23'); + + // make it respond with USD floor and num floor + getFloorResponse = {currency: 'USD', floor: 1.23}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + data = parseQuery(request.data); + expect(data.rp_hard_floor).to.equal('1.23'); + }); it('should not send p_pos to AE if not params.position specified', function() { var noposRequest = utils.deepClone(bidderRequest); delete noposRequest.bids[0].params.position; @@ -979,6 +1016,51 @@ describe('the rubicon adapter', function () { expect(data[key]).to.equal(value); }); }); + + it('should use first party data from getConfig over the bid params, if present', () => { + const context = { + keywords: ['e', 'f'], + rating: '4-star' + }; + const user = { + keywords: ['d'], + gender: 'M', + yob: '1984', + geo: { country: 'ca' } + }; + + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + fpd: { + context, + user + } + }; + return utils.deepAccess(config, key); + }); + + const expectedQuery = { + 'kw': 'a,b,c,d,e,f', + 'tg_v.ucat': 'new', + 'tg_v.lastsearch': 'iphone', + 'tg_v.likes': 'sports,video games', + 'tg_v.gender': 'M', + 'tg_v.yob': '1984', + 'tg_v.geo': '{"country":"ca"}', + 'tg_i.rating': '4-star', + 'tg_i.prodtype': 'tech,mobile', + }; + + // get the built request + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + // make sure that tg_v, tg_i, and kw values are correct + Object.keys(expectedQuery).forEach(key => { + let value = expectedQuery[key]; + expect(data[key]).to.deep.equal(value); + }); + }); }); describe('singleRequest config', function () { @@ -1259,7 +1341,168 @@ describe('the rubicon adapter', function () { expect(unescapedData.indexOf('&tg_v.LIseg=segD,segE&') !== -1).to.equal(true); }); }); + + describe('LiveRamp support', function () { + it('should send x_liverampidl when userId defines idl_env', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + idl_env: '1111-2222-3333-4444' + }; + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['x_liverampidl']).to.equal('1111-2222-3333-4444'); + }); + }); }) + + describe('Prebid AdSlot', function () { + beforeEach(function () { + // enforce that the bid at 0 does not have a 'context' property + if (bidderRequest.bids[0].hasOwnProperty('fpd')) { + delete bidderRequest.bids[0].fpd; + } + }); + + it('should not send \"tg_i.pbadslot’\" if \"fpd.context\" object is not valid', function () { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.pbadslot’'); + }); + + it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" is undefined', function () { + bidderRequest.bids[0].fpd = {}; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.pbadslot’'); + }); + + it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { + bidderRequest.bids[0].fpd = { + context: { + pbAdSlot: '' + } + }; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.pbadslot'); + }); + + it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string', function () { + bidderRequest.bids[0].fpd = { + context: { + pbAdSlot: 'abc' + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('tg_i.pbadslot'); + expect(data['tg_i.pbadslot']).to.equal('abc'); + }); + + it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].fpd = { + context: { + pbAdSlot: '/a/b/c' + } + }; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('tg_i.pbadslot'); + expect(data['tg_i.pbadslot']).to.equal('a/b/c'); + }); + }); + + describe('GAM ad unit', function () { + beforeEach(function () { + // enforce that the bid at 0 does not have a 'context' property + if (bidderRequest.bids[0].hasOwnProperty('fpd')) { + delete bidderRequest.bids[0].fpd; + } + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context\" object is not valid', function () { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" is undefined', function () { + bidderRequest.bids[0].fpd = {}; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" value is an empty string', function () { + bidderRequest.bids[0].fpd = { + context: { + adServer: { + adSlot: '' + } + } + }; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + }); + + it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string', function () { + bidderRequest.bids[0].fpd = { + context: { + adServer: { + adSlot: 'abc' + } + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('tg_i.dfp_ad_unit_code'); + expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); + }); + + it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].fpd = { + context: { + adServer: { + adSlot: 'a/b/c' + } + } + }; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('tg_i.dfp_ad_unit_code'); + expect(data['tg_i.dfp_ad_unit_code']).to.equal('a/b/c'); + }); + }); }); describe('for video requests', function () { @@ -1295,12 +1538,16 @@ describe('the rubicon adapter', function () { expect(post.site.content.language).to.equal('en'); expect(imp.ext.rubicon.video.skip).to.equal(1); expect(imp.ext.rubicon.video.skipafter).to.equal(15); + expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); expect(post.user.ext.tpid).that.is.an('object'); expect(post.user.ext.tpid.source).to.equal('liveintent.com'); expect(post.user.ext.tpid.uid).to.equal('0000-1111-2222-3333'); + // LiveRamp should exist + expect(post.user.ext.eids[1].source).to.equal('liveramp_idl'); + expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); expect(post.rp).that.is.an('object'); expect(post.rp.target).that.is.an('object'); expect(post.rp.target.LIseg).that.is.an('array'); @@ -1310,12 +1557,46 @@ describe('the rubicon adapter', function () { expect(post.regs.ext.us_privacy).to.equal('1NYN'); expect(post).to.have.property('ext').that.is.an('object'); expect(post.ext.prebid.targeting.includewinners).to.equal(true); - expect(post.ext.prebid).to.have.property('cache').that.is.an('object') - expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object') - expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean') - expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false) + expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); + expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); }); + it('should correctly set bidfloor on imp when getfloor in scope', function () { + createVideoBidderRequest(); + // default getFloor response is empty object so should not break and not send hard_floor + bidderRequest.bids[0].getFloor = () => getFloorResponse; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + // not an object should work and not send + expect(request.data.imp[0].bidfloor).to.be.undefined; + + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR', floor: 1.0}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.be.undefined; + + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.be.undefined; + + // make it respond with USD floor and string floor + getFloorResponse = {currency: 'USD', floor: '1.23'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.23); + + // make it respond with USD floor and num floor + getFloorResponse = {currency: 'USD', floor: 1.23}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.23); + }); it('should add alias name to PBS Request', function() { createVideoBidderRequest(); @@ -1568,6 +1849,106 @@ describe('the rubicon adapter', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(request.data.regs.coppa).to.equal(1); }); + + it('should include first party data', () => { + createVideoBidderRequest(); + + const context = { + keywords: ['e', 'f'], + rating: '4-star' + }; + const user = { + keywords: ['d'], + gender: 'M', + yob: '1984', + geo: { country: 'ca' } + }; + + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + fpd: { + context, + user + } + }; + return utils.deepAccess(config, key); + }); + + const expected = [{ + bidders: [ 'rubicon' ], + config: { + fpd: { + site: Object.assign({}, bidderRequest.bids[0].params.inventory, context), + user: Object.assign({}, bidderRequest.bids[0].params.visitor, user) + } + } + }]; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.ext.prebid.bidderconfig).to.deep.equal(expected); + }); + + it('should include storedAuctionResponse in video bid request', function () { + createVideoBidderRequest(); + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp).to.exist.and.to.be.a('array'); + expect(request.data.imp).to.have.lengthOf(1); + expect(request.data.imp[0].ext).to.exist.and.to.be.a('object'); + expect(request.data.imp[0].ext.prebid).to.exist.and.to.be.a('object'); + expect(request.data.imp[0].ext.prebid.storedauctionresponse).to.exist.and.to.be.a('object'); + expect(request.data.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); + }); + + it('should include pbAdSlot in bid request', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].fpd = { + context: { + pbAdSlot: '1234567890' + } + }; + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); + }); + + it('should include GAM ad unit in bid request', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].fpd = { + context: { + adServer: { + adSlot: '1234567890' + } + } + }; + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.context.data.adslot).to.equal('1234567890'); + }); + + it('should use the integration type provided in the config instead of the default', () => { + createVideoBidderRequest(); + sandbox.stub(config, 'getConfig').callsFake(function (key) { + const config = { + 'rubicon.int_type': 'testType' + }; + return config[key]; + }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); + }); }); describe('combineSlotUrlParams', function () { @@ -1633,6 +2014,12 @@ describe('the rubicon adapter', function () { } }); }); + + it('should not fail if keywords param is not an array', function () { + bidderRequest.bids[0].params.keywords = 'a,b,c'; + const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); + expect(slotParams.kw).to.equal(''); + }); }); describe('hasVideoMediaType', function () { diff --git a/test/spec/modules/s2sTesting_spec.js b/test/spec/modules/s2sTesting_spec.js index 52377dcabf2..5c7f3004dee 100644 --- a/test/spec/modules/s2sTesting_spec.js +++ b/test/spec/modules/s2sTesting_spec.js @@ -1,5 +1,5 @@ -import s2sTesting from 'modules/s2sTesting'; -import { config } from 'src/config'; +import s2sTesting from 'modules/s2sTesting.js'; +import { config } from 'src/config.js'; var expect = require('chai').expect; diff --git a/test/spec/modules/saambaaBidAdapter_spec.js b/test/spec/modules/saambaaBidAdapter_spec.js new file mode 100755 index 00000000000..80a85ae895d --- /dev/null +++ b/test/spec/modules/saambaaBidAdapter_spec.js @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import { spec } from 'modules/saambaaBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; + +describe('saambaaBidAdapter', function () { + let bidRequests; + let bidRequestsVid; + + beforeEach(function () { + bidRequests = [{'bidder': 'saambaa', 'params': {'pubid': '121ab139faf7ac67428a23f1d0a9a71b', 'floor': 0.5, 'placement': 1234, size: '320x250'}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + + bidRequestsVid = [{'bidder': 'saambaa', 'params': {'pubid': '121ab139faf7ac67428a23f1d0a9a71b', 'floor': 1.0, 'placement': 1234, size: '320x480', 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + }); + + describe('spec.isBidRequestValid', function () { + it('should return true when the required params are passed for banner', function () { + const bidRequest = bidRequests[0]; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when the required params are passed for video', function () { + const bidRequests = bidRequestsVid[0]; + expect(spec.isBidRequestValid(bidRequests)).to.equal(true); + }); + + it('should return false when no pub id params are passed', function () { + const bidRequest = bidRequests[0]; + bidRequest.params.pubid = ''; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no placement params are passed', function () { + const bidRequest = bidRequests[0]; + bidRequest.params.placement = ''; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when a bid request is not passed', function () { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); + + describe('spec.buildRequests', function () { + it('should create a POST request for each bid', function () { + const bidRequest = bidRequests[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + }); + + it('should create a POST request for each bid in video request', function () { + const bidRequest = bidRequestsVid[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + }); + + it('should have domain in request', function () { + const bidRequest = bidRequests[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].data.site.domain).length !== 0; + }); + }); + + describe('spec.interpretResponse', function () { + describe('for banner bids', function () { + it('should return no bids if the response is not valid', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + + if (typeof bidResponse !== 'undefined') { + expect(bidResponse.length).to.equal(0); + } else { + expect(true).to.equal(true); + } + }); + + it('should return no bids if the response is empty', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); + if (typeof bidResponse !== 'undefined') { + expect(bidResponse.length).to.equal(0); + } else { expect(true).to.equal(true); } + }); + + it('should return valid video bid responses', function () { + let _mediaTypes = VIDEO; + const saambaabidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; + const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'} + const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, saambaabidreqVid); + delete bidResponseVid['vastUrl']; + delete bidResponseVid['ad']; + expect(bidResponseVid).to.deep.equal({ + requestId: bidRequestsVid[0].bidId, + bidderCode: 'saambaa', + creativeId: serverResponseVid.seatbid[0].bid[0].crid, + cpm: serverResponseVid.seatbid[0].bid[0].price, + width: serverResponseVid.seatbid[0].bid[0].w, + height: serverResponseVid.seatbid[0].bid[0].h, + mediaType: 'video', + meta: { advertiserDomains: serverResponseVid.seatbid[0].bid[0].adomain }, + currency: 'USD', + netRevenue: true, + ttl: 60 + }); + }); + + it('should return valid banner bid responses', function () { + const saambaabidreq = {bids: {}}; + bidRequests.forEach(bid => { + let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); + saambaabidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, + w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], + h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] + + }; + }); + const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '
', 'adomain': ['advertiserdomain.com'], 'iurl': '', 'cid': 'campaign1', 'crid': 'abcde-12345', 'w': 300, 'h': 250}], 'seat': '19513bcfca8006'}], 'bidid': '19513bcfca8006', 'cur': 'USD', 'w': 300, 'h': 250}; + + const bidResponse = spec.interpretResponse({ body: serverResponse }, saambaabidreq); + expect(bidResponse).to.deep.equal({ + requestId: bidRequests[0].bidId, + ad: serverResponse.seatbid[0].bid[0].adm, + bidderCode: 'saambaa', + creativeId: serverResponse.seatbid[0].bid[0].crid, + cpm: serverResponse.seatbid[0].bid[0].price, + width: serverResponse.seatbid[0].bid[0].w, + height: serverResponse.seatbid[0].bid[0].h, + mediaType: 'banner', + meta: { advertiserDomains: serverResponse.seatbid[0].bid[0].adomain }, + currency: 'USD', + netRevenue: true, + ttl: 60 + }); + }); + }); + }); +}); diff --git a/test/spec/modules/scaleableAnalyticsAdapter_spec.js b/test/spec/modules/scaleableAnalyticsAdapter_spec.js index 300f72751a4..70b94a2b807 100644 --- a/test/spec/modules/scaleableAnalyticsAdapter_spec.js +++ b/test/spec/modules/scaleableAnalyticsAdapter_spec.js @@ -1,24 +1,49 @@ -import scaleableAnalytics from 'modules/scaleableAnalyticsAdapter'; +import scaleableAnalytics from 'modules/scaleableAnalyticsAdapter.js'; import { expect } from 'chai'; -import events from 'src/events'; +import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; -import adapterManager from 'src/adapterManager'; +import { server } from 'test/mocks/xhr.js'; const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; const AUCTION_INIT = CONSTANTS.EVENTS.AUCTION_INIT; -const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; const BID_WON = CONSTANTS.EVENTS.BID_WON; const AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; describe('Scaleable Analytics Adapter', function() { + const bidsReceivedObj = { + adUnitCode: '12345', + bidderCode: 'test-code', + cpm: 3.14, + currency: 'USD', + dealId: null, + mediaType: 'banner', + timeToRespond: 285, + size: '300x250' + }; + const MOCK_DATA = { adUnitCode: '12345', + auctionEnd: { + bidsReceived: [bidsReceivedObj] + }, + bidderRequests: [{ + bids: [{ + adUnitCode: '12345', + bidder: 'test-code', + params: { + test: 'value' + } + }] + }], site: '5c4fab7a829e955d6c265e72', bidResponse: { adUnitCode: '12345', bidderCode: 'test-code', cpm: 3.14, - timeToRespond: 285 + timeToRespond: 285, + params: [{ + test: 'value' + }] }, bidTimeout: [ { @@ -28,40 +53,54 @@ describe('Scaleable Analytics Adapter', function() { ] }; - MOCK_DATA.expectedBidResponse = { - event: 'bids', - bids: [{ - code: MOCK_DATA.bidResponse.bidderCode, - cpm: MOCK_DATA.bidResponse.cpm, - ttr: MOCK_DATA.bidResponse.timeToRespond - }], - adunit: MOCK_DATA.adUnitCode, - site: MOCK_DATA.site - }; + const bidObj = MOCK_DATA.bidderRequests[0].bids[0]; - MOCK_DATA.expectedBidTimeout = { - event: 'bids', - bids: [], - timeouts: [MOCK_DATA.bidTimeout[0].bidder], - adunit: MOCK_DATA.bidTimeout[0].adUnitCode, - site: MOCK_DATA.site - }; + const expectedBidRequests = [{bidder: 'scaleable_adunit_request'}].concat([ + { + bidder: bidObj.bidder, + params: bidObj.params + } + ]); - let xhr; - let requests; + MOCK_DATA.expectedRequestResponse = { + event: 'request', + site: MOCK_DATA.site, + adunits: [{ + code: bidObj.adUnitCode, + bidRequests: expectedBidRequests + }] + } - before(function() { - xhr = sinon.useFakeXMLHttpRequest(); - xhr.onCreate = request => requests.push(request); - }); + MOCK_DATA.expectedBidTimeout = { + [MOCK_DATA.bidTimeout[0].adUnitCode]: [{ + timeouts: 1, + bidder: MOCK_DATA.bidTimeout[0].bidder + }] + }; - after(function() { - xhr.restore(); - }); + MOCK_DATA.expectedAuctionEndResponse = { + event: 'bids', + site: MOCK_DATA.site, + adunits: [{ + code: MOCK_DATA.auctionEnd.bidsReceived[0].adUnitCode, + bidData: [{ + bidder: bidsReceivedObj.bidderCode, + cpm: bidsReceivedObj.cpm, + currency: bidsReceivedObj.currency, + dealId: bidsReceivedObj.dealId, + type: bidsReceivedObj.mediaType, + ttr: bidsReceivedObj.timeToRespond, + size: bidsReceivedObj.size + }] + }, + { + bidData: MOCK_DATA.expectedBidTimeout[MOCK_DATA.bidTimeout[0].adUnitCode], + code: MOCK_DATA.bidTimeout[0].adUnitCode + }] + } describe('Event Handling', function() { beforeEach(function() { - requests = []; sinon.stub(events, 'getEvents').returns([]); scaleableAnalytics.enableAnalytics({ @@ -79,44 +118,34 @@ describe('Scaleable Analytics Adapter', function() { it('should handle the auction init event', function(done) { events.emit(AUCTION_INIT, { - adUnitCodes: [MOCK_DATA.adUnitCode] + adUnitCodes: [MOCK_DATA.adUnitCode], + bidderRequests: MOCK_DATA.bidderRequests }); - const result = JSON.parse(requests[0].requestBody); - expect(result).to.deep.equal({ - event: 'request', - site: MOCK_DATA.site, - adunit: MOCK_DATA.adUnitCode - }); + const result = JSON.parse(server.requests[0].requestBody); + expect(result).to.deep.equal(MOCK_DATA.expectedRequestResponse); done(); }); - it('should handle the bid response event', function() { - events.emit(BID_RESPONSE, MOCK_DATA.bidResponse); - - const actual = scaleableAnalytics.getAuctionData(); - - expect(actual[MOCK_DATA.adUnitCode]).to.deep.equal(MOCK_DATA.expectedBidResponse); - }); - it('should handle the bid timeout event', function() { events.emit(BID_TIMEOUT, MOCK_DATA.bidTimeout); const actual = scaleableAnalytics.getAuctionData(); - expect(actual[MOCK_DATA.bidTimeout[0].adUnitCode]).to.deep.equal(MOCK_DATA.expectedBidTimeout); + expect(actual).to.deep.equal(MOCK_DATA.expectedBidTimeout); }); it('should handle the bid won event', function(done) { events.emit(BID_WON, MOCK_DATA.bidResponse); - const result = JSON.parse(requests[0].requestBody); + const result = JSON.parse(server.requests[0].requestBody); expect(result).to.deep.equal({ adunit: MOCK_DATA.adUnitCode, code: MOCK_DATA.bidResponse.bidderCode, cpm: MOCK_DATA.bidResponse.cpm, ttr: MOCK_DATA.bidResponse.timeToRespond, + params: MOCK_DATA.bidResponse.params, event: 'win', site: MOCK_DATA.site }); @@ -125,10 +154,10 @@ describe('Scaleable Analytics Adapter', function() { }); it('should handle the auction end event', function(done) { - events.emit(AUCTION_END, {}); + events.emit(AUCTION_END, MOCK_DATA.auctionEnd); - const result = JSON.parse(requests[0].requestBody); - expect(result).to.deep.equal(MOCK_DATA.expectedBidResponse); + const result = JSON.parse(server.requests[0].requestBody); + expect(result).to.deep.equal(MOCK_DATA.expectedAuctionEndResponse); done(); }); diff --git a/test/spec/modules/schain_spec.js b/test/spec/modules/schain_spec.js index 8f5104f1822..34d0cff9a60 100644 --- a/test/spec/modules/schain_spec.js +++ b/test/spec/modules/schain_spec.js @@ -1,4 +1,6 @@ -import {isValidSchainConfig, isSchainObjectValid, copySchainObjectInAdunits} from '../../../modules/schain'; +import { isValidSchainConfig, isSchainObjectValid, makeBidRequestsHook } from '../../../modules/schain.js'; +import { deepClone } from '../../../src/utils.js'; +import {config} from '../../../src/config.js'; import { expect } from 'chai'; describe('#isValidSchainConfig: module config validation', function() { @@ -245,42 +247,241 @@ describe('#isSchainObjectValid: schain object validation', function() { }) }); -describe('Passing schain object to adUnits', function() { - let schainConfig; - - beforeEach(function() { - schainConfig = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ +describe('#makeBidRequestsHook', function() { + const bidderRequests = [ + { + 'bidderCode': 'rubicon', + 'bids': [ { - 'asi': 'indirectseller.com', - 'sid': '00001', - 'hp': 1 - }, + 'bidder': 'rubicon', + 'params': { + 'accountId': 14062, + 'siteId': 70608, + 'zoneId': 498816 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2e6d166eb869c3' + } + ], + }, + { + 'bidderCode': 'districtm', + 'bids': [ { - 'asi': 'indirectseller-2.com', - 'sid': '00002', - 'hp': 2 + 'bidder': 'districtm', + 'params': { + 'placementId': 13144370 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '41cdeddf7b6905' } - ] - }; + ], + }, + { + 'bidderCode': 'appnexus', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '626cc7f1c4ccfc' + } + ], + + } + ]; + + const globalSchainConfig = { + 'schain': { + 'validation': 'off', + 'config': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 1 + } + ] + } + } + }; + + const goodStrictBidderConfig = { + bidders: ['appnexus'], + config: { + 'schain': { + 'validation': 'strict', + 'config': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'myoverride1.com', + 'sid': '00001', + 'hp': 1, + 'name': 'node1' + }, + { + 'asi': 'myoverride2.com', + 'sid': '00001', + 'hp': 1, + 'name': 'node2' + } + ] + } + } + } + } + + const badStrictBidderConfig = { + bidders: ['appnexus'], + config: { + 'schain': { + 'validation': 'strict', + 'config': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'myoverride1.com', + 'sid': 1, + 'hp': 1, + 'name': 342 + }, + { + 'asi': 'myoverride2.com', + 'sid': 2, + 'hp': 1, + 'name': '342' + } + ] + } + } + } + }; + + const goodRelaxedBidderConfig = { + bidders: ['districtm'], + config: { + 'schain': { + 'validation': 'relaxed', + 'config': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'myoverride.com', + 'sid': '00001', + 'hp': 1, + 'name': 'goodConfig' + } + ] + } + } + } + }; + + const badRelaxedBidderConfig = { + bidders: ['districtm'], + config: { + 'schain': { + 'validation': 'relaxed', + 'config': { + 'ver': 1, + 'complete': 1, + 'nodes': [ + { + 'asi': 'myoverride.com', + 'sid': 1, + 'hp': 1 + } + ] + } + } + } + }; + + beforeEach(function () { + config.setConfig(globalSchainConfig); }); - it('schain object should be applied to all adUnits', function() { - let adUnits = [ - { - bids: [{}, {}] - }, - { - bids: [{}, {}] + afterEach(function () { + config.resetConfig(); + + config.setBidderConfig({ + bidders: ['districtm'], + config: { + schain: null + } + }); + + config.setBidderConfig({ + bidders: ['appnexus'], + config: { + schain: null } - ]; - copySchainObjectInAdunits(adUnits, schainConfig); - expect(adUnits[0].bids[0].schain).to.equal(schainConfig); - expect(adUnits[0].bids[1].schain).to.equal(schainConfig); - expect(adUnits[1].bids[0].schain).to.equal(schainConfig); - expect(adUnits[1].bids[1].schain).to.equal(schainConfig); + }); + }); + + it('should properly read from bidder schain + global schain configs', function() { + function testCallback(bidderRequests) { + expect(bidderRequests[0].bids[0].schain).to.exist; + expect(bidderRequests[0].bids[0].schain).to.deep.equal(globalSchainConfig.schain.config); + expect(bidderRequests[1].bids[0].schain).to.exist; + expect(bidderRequests[1].bids[0].schain).to.deep.equal(goodRelaxedBidderConfig.config.schain.config); + expect(bidderRequests[2].bids[0].schain).to.exist; + expect(bidderRequests[2].bids[0].schain).to.deep.equal(goodStrictBidderConfig.config.schain.config); + } + + const testBidderRequests = deepClone(bidderRequests); + config.setBidderConfig(goodStrictBidderConfig); + config.setBidderConfig(goodRelaxedBidderConfig); + + makeBidRequestsHook(testCallback, testBidderRequests); + }); + + it('should reject bad strict config but allow a bad relaxed config for bidders trying to override it', function () { + function testCallback(bidderRequests) { + expect(bidderRequests[0].bids[0].schain).to.exist; + expect(bidderRequests[0].bids[0].schain).to.deep.equal(globalSchainConfig.schain.config); + expect(bidderRequests[1].bids[0].schain).to.exist; + expect(bidderRequests[1].bids[0].schain).to.deep.equal(badRelaxedBidderConfig.config.schain.config); + expect(bidderRequests[2].bids[0].schain).to.be.undefined; + } + + const testBidderRequests = deepClone(bidderRequests); + config.setBidderConfig(badStrictBidderConfig); + config.setBidderConfig(badRelaxedBidderConfig); + + makeBidRequestsHook(testCallback, testBidderRequests); }); }); diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js new file mode 100755 index 00000000000..81af9546ff0 --- /dev/null +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -0,0 +1,186 @@ +// jshint esversion: 6, es3: false, node: true +import {assert, expect} from 'chai'; +import {spec} from 'modules/seedingAllianceBidAdapter.js'; +import { NATIVE } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; + +describe('SeedingAlliance adapter', function () { + let serverResponse, bidRequest, bidResponses; + let bid = { + 'bidder': 'seedingAlliance', + 'params': { + 'adUnitId': '1hq8' + } + }; + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when AdUnitId is not set', function () { + delete bid.params.adUnitId; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests', function () { + it('should send request with correct structure', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {} + }]; + + let request = spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }); + + assert.equal(request.method, 'POST'); + assert.ok(request.data); + }); + + it('should have default request structure', function () { + let keys = 'site,device,cur,imp,user,regs'.split(','); + let validBidRequests = [{ + bidId: 'bidId', + params: {} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); + + it('Verify the auction ID', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + auctionId: 'auctionId' + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' }, auctionId: validBidRequests[0].auctionId }).data); + + assert.equal(request.id, validBidRequests[0].auctionId); + }); + + it('Verify the device', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.device.ua, navigator.userAgent); + }); + + it('Verify native asset ids', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + nativeParams: { + body: { + required: true, + len: 350 + }, + image: { + required: true + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + cta: { + required: true + }, + icon: { + required: true + } + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + + assert.equal(assets[0].id, 1); + assert.equal(assets[1].id, 3); + assert.equal(assets[2].id, 0); + assert.equal(assets[3].id, 2); + assert.equal(assets[4].id, 4); + assert.equal(assets[5].id, 5); + }); + }); + + describe('interpretResponse', function () { + const goodResponse = { + body: { + cur: 'EUR', + id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [ + { + seat: 'seedingAlliance', + bid: [{ + adm: { + native: { + assets: [ + {id: 0, title: {text: 'this is a title'}} + ], + imptrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], + link: { + clicktrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], + url: 'https://domain.for/ad/' + } + } + }, + impid: 1, + price: 0.55 + }] + } + ] + } + }; + const badResponse = { body: { + cur: 'EUR', + id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [] + }}; + + const bidRequest = { + data: {}, + bids: [{ bidId: 'bidId1' }] + }; + + it('should return null if body is missing or empty', function () { + const result = spec.interpretResponse(badResponse, bidRequest); + assert.equal(result.length, 0); + + delete badResponse.body + + const result1 = spec.interpretResponse(badResponse, bidRequest); + assert.equal(result.length, 0); + }); + + it('should return the correct params', function () { + const result = spec.interpretResponse(goodResponse, bidRequest); + const bid = goodResponse.body.seatbid[0].bid[0]; + + assert.deepEqual(result[0].currency, goodResponse.body.cur); + assert.deepEqual(result[0].requestId, bidRequest.bids[0].bidId); + assert.deepEqual(result[0].cpm, bid.price); + assert.deepEqual(result[0].creativeId, bid.crid); + assert.deepEqual(result[0].mediaType, 'native'); + assert.deepEqual(result[0].bidderCode, 'seedingAlliance'); + }); + + it('should return the correct tracking links', function () { + const result = spec.interpretResponse(goodResponse, bidRequest); + const bid = goodResponse.body.seatbid[0].bid[0]; + const regExpPrice = new RegExp('price=' + bid.price); + + result[0].native.clickTrackers.forEach(function (clickTracker) { + assert.ok(clickTracker.search(regExpPrice) > -1); + }); + + result[0].native.impressionTrackers.forEach(function (impTracker) { + assert.ok(impTracker.search(regExpPrice) > -1); + }); + }); + }); +}); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 4bd4b599c55..5c8c58196f7 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter' +import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js' function getSlotConfigs(mediaTypes, params) { return { diff --git a/test/spec/modules/segmentoBidAdapter_spec.js b/test/spec/modules/segmentoBidAdapter_spec.js new file mode 100644 index 00000000000..17ad424f73f --- /dev/null +++ b/test/spec/modules/segmentoBidAdapter_spec.js @@ -0,0 +1,187 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/segmentoBidAdapter.js'; + +const BIDDER_CODE = 'segmento'; +const URL = 'https://prebid-bidder.rutarget.ru/bid'; +const SYNC_IFRAME_URL = 'https://tag.rutarget.ru/tag?event=otherPage&check=true&response=syncframe&synconly=true'; +const SYNC_IMAGE_URL = 'https://tag.rutarget.ru/tag?event=otherPage&check=true&synconly=true'; +const RUB = 'RUB'; +const TIME_TO_LIVE = 0; + +describe('SegmentoAdapter', function () { + describe('isBidRequestValid', function () { + const bid = { + bidder: BIDDER_CODE, + bidId: '51ef8751f9aead', + params: { + placementId: 34 + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + sizes: [[320, 50], [300, 250], [300, 600]], + bidderRequestId: '418b37f85e772c', + auctionId: '18fd8b8b0bd757' + }; + + it('should return true if placementId is a number', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if placementId is not a number', function () { + bid.params.placementId = 'placementId'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if no placementId param', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bids = [{ + bidder: 'segmento', + bidId: '51ef8751f9aead', + params: { + placementId: 34 + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + sizes: [[320, 50], [300, 250], [300, 600]], + bidderRequestId: '418b37f85e772c', + auctionId: '18fd8b8b0bd757' + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://comepage.com' + } + }; + + const request = spec.buildRequests(bids, bidderRequest); + it('should return POST method', function () { + expect(request.method).to.equal('POST'); + }); + + it('should return valid url', function () { + expect(request.url).to.equal(URL); + }); + + it('should return valid data', function () { + const data = request.data; + expect(data).to.have.all.keys('settings', 'places'); + expect(data.settings.currency).to.be.equal(RUB); + expect(data.settings.referrer).to.be.a('string'); + expect(data.settings.referrer).to.be.equal(bidderRequest.refererInfo.referer); + const places = data.places; + for (let i = 0; i < places.length; i++) { + const place = places[i]; + const bid = bids[i]; + expect(place).to.have.all.keys('id', 'placementId', 'sizes'); + expect(place.id).to.be.a('string'); + expect(place.id).to.be.equal(bid.bidId); + expect(place.placementId).to.be.a('number'); + expect(place.placementId).to.be.equal(bid.params.placementId); + expect(place.sizes).to.be.an('array'); + expect(place.sizes).to.deep.equal(bid.sizes); + } + }); + + it('should return empty places if no valid bids are passed', function () { + const request = spec.buildRequests([], {}); + expect(request.data.places).to.be.an('array').to.deep.equal([]); + }); + }); + + describe('interpretResponse', function() { + const serverResponse = { + body: { + bids: [{ + id: '51ef8751f9aead', + cpm: 0.23, + currency: RUB, + creativeId: 123, + displayUrl: 'displayUrl?t=123&p=456', + size: { + width: 300, + height: 250 + } + }] + } + }; + + const emptyServerResponse = { + body: { + bids: [] + } + }; + + it('should return valid data', function () { + const response = spec.interpretResponse(serverResponse); + expect(response).to.be.an('array'); + for (let i = 0; i < response.length; i++) { + const item = response[i]; + const bid = serverResponse.body.bids[i]; + expect(item).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'creativeId', + 'currency', 'netRevenue', 'ttl', 'adUrl'); + expect(item.requestId).to.be.a('string'); + expect(item.requestId).to.be.equal(bid.id); + expect(item.cpm).to.be.a('number'); + expect(item.cpm).to.be.equal(bid.cpm); + expect(item.width).to.be.a('number'); + expect(item.width).to.be.equal(bid.size.width); + expect(item.height).to.be.a('number'); + expect(item.height).to.be.equal(bid.size.height); + expect(item.creativeId).to.be.a('number'); + expect(item.creativeId).to.be.equal(bid.creativeId); + expect(item.currency).to.be.a('string'); + expect(item.currency).to.be.equal(bid.currency); + expect(item.netRevenue).to.be.a('boolean'); + expect(item.netRevenue).to.equal(true); + expect(item.ttl).to.be.a('number'); + expect(item.ttl).to.be.equal(TIME_TO_LIVE); + expect(item.adUrl).to.be.a('string'); + expect(item.adUrl).to.be.equal(bid.displayUrl); + } + }); + + it('should return empty array if no bids', function () { + const response = spec.interpretResponse(emptyServerResponse); + expect(response).to.be.an('array').to.deep.equal([]); + }); + + it('should return empty array if server response is invalid', function () { + const response = spec.interpretResponse({}); + expect(response).to.be.an('array').to.deep.equal([]); + }); + }); + + describe('getUserSyncs', function() { + it('should return iframe type if iframe enabled', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + const sync = syncs[0]; + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(sync).to.have.all.keys('type', 'url'); + expect(sync.type).to.be.a('string'); + expect(sync.type).to.be.equal('iframe'); + expect(sync.url).to.be.a('string'); + expect(sync.url).to.be.equal(SYNC_IFRAME_URL); + }); + + it('should return iframe type if iframe disabled, but image enable', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true }); + const sync = syncs[0]; + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(sync).to.have.all.keys('type', 'url'); + expect(sync.type).to.be.a('string'); + expect(sync.type).to.be.equal('image'); + expect(sync.url).to.be.a('string'); + expect(sync.url).to.be.equal(SYNC_IMAGE_URL); + }); + + it('should return empty array if iframe and pixels disabled', function () { + const syncs = spec.getUserSyncs({}); + expect(syncs).to.be.an('array').to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/sekindoUMBidAdapter_spec.js b/test/spec/modules/sekindoUMBidAdapter_spec.js index 2718d26b2b4..2c361c21303 100644 --- a/test/spec/modules/sekindoUMBidAdapter_spec.js +++ b/test/spec/modules/sekindoUMBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/sekindoUMBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/sekindoUMBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('sekindoUMAdapter', function () { const adapter = newBidder(spec); diff --git a/test/spec/modules/serverbidServerBidAdapter_spec.js b/test/spec/modules/serverbidServerBidAdapter_spec.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/spec/modules/shareUserIds_spec.js b/test/spec/modules/shareUserIds_spec.js index 4ae5f93a1a6..451892919cb 100644 --- a/test/spec/modules/shareUserIds_spec.js +++ b/test/spec/modules/shareUserIds_spec.js @@ -1,4 +1,4 @@ -import {userIdTargeting} from '../../../modules/userIdTargeting'; +import {userIdTargeting} from '../../../modules/userIdTargeting.js'; import { expect } from 'chai'; describe('#userIdTargeting', function() { @@ -22,7 +22,7 @@ describe('#userIdTargeting', function() { pubads.clearTargeting(); pubads.setTargeting('test', ['TEST']); userIdTargeting(userIds, JSON.stringify(config)); - expect(pubads.getTargeting()).to.deep.equal({test: ['TEST']}); + expect(pubads.getTargeting('test')).to.deep.equal(['TEST']); }); it('all UserIds are passed as is with GAM: true', function() { @@ -31,7 +31,8 @@ describe('#userIdTargeting', function() { pubads.setTargeting('test', ['TEST']); delete config.GAM_KEYS; userIdTargeting(userIds, config); - expect(pubads.getTargeting()).to.deep.equal({test: ['TEST'], tdid: ['my-tdid']}); + expect(pubads.getTargeting('test')).to.deep.equal(['TEST']); + expect(pubads.getTargeting('tdid')).to.deep.equal(['my-tdid']); }) it('Publisher prefered key-names are used', function() { @@ -39,7 +40,8 @@ describe('#userIdTargeting', function() { pubads.clearTargeting(); pubads.setTargeting('test', ['TEST']); userIdTargeting(userIds, config); - expect(pubads.getTargeting()).to.deep.equal({test: ['TEST'], 'TD_ID': ['my-tdid']}); + expect(pubads.getTargeting('test')).to.deep.equal(['TEST']); + expect(pubads.getTargeting('TD_ID')).to.deep.equal(['my-tdid']); }); it('Publisher does not want to pass an id', function() { @@ -48,6 +50,6 @@ describe('#userIdTargeting', function() { pubads.setTargeting('test', ['TEST']); config.GAM_KEYS.tdid = ''; userIdTargeting(userIds, config); - expect(pubads.getTargeting()).to.deep.equal({test: ['TEST']}); + expect(pubads.getTargeting('test')).to.deep.equal(['TEST']); }); }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index a8caefc5240..92f9fd11eeb 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); const bidRequests = [ @@ -40,7 +40,7 @@ const bidRequests = [ const prebidRequests = [ { method: 'GET', - url: 'https://btlr.sharethrough.com' + '/WYu2BXv1/v1', + url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' @@ -52,7 +52,7 @@ const prebidRequests = [ }, { method: 'GET', - url: 'https://btlr.sharethrough.com' + '/WYu2BXv1/v1', + url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' @@ -64,7 +64,7 @@ const prebidRequests = [ }, { method: 'GET', - url: 'https://btlr.sharethrough.com' + '/WYu2BXv1/v1', + url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' @@ -77,7 +77,7 @@ const prebidRequests = [ }, { method: 'GET', - url: 'https://btlr.sharethrough.com' + '/WYu2BXv1/v1', + url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' @@ -89,7 +89,7 @@ const prebidRequests = [ }, { method: 'GET', - url: 'https://btlr.sharethrough.com' + '/WYu2BXv1/v1', + url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' @@ -120,9 +120,9 @@ const bidderResponse = { header: { get: (header) => header } }; -const setUserAgent = (str) => { +const setUserAgent = (uaString) => { window.navigator['__defineGetter__']('userAgent', function () { - return str; + return uaString; }); }; @@ -217,10 +217,8 @@ describe('sharethrough adapter spec', function () { it('should return an array of requests', function () { const builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].url).to.eq( - 'https://btlr.sharethrough.com/WYu2BXv1/v1'); - expect(builtBidRequests[1].url).to.eq( - 'https://btlr.sharethrough.com/WYu2BXv1/v1'); + expect(builtBidRequests[0].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); + expect(builtBidRequests[1].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); expect(builtBidRequests[0].method).to.eq('GET'); }); @@ -250,6 +248,34 @@ describe('sharethrough adapter spec', function () { expect(builtBidRequests[0].data.instant_play_capable).to.be.false; }); + it('should set the secure parameter to false when the protocol is http', function() { + const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('http:'); + const bidRequest = spec.buildRequests(bidRequests, null)[0]; + expect(bidRequest.data.secure).to.be.false; + stub.restore() + }); + + it('should set the secure parameter to true when the protocol is https', function() { + const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('https:'); + const bidRequest = spec.buildRequests(bidRequests, null)[0]; + expect(bidRequest.data.secure).to.be.true; + stub.restore() + }); + + it('should set the secure parameter to true when the protocol is neither http or https', function() { + const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('about:'); + const bidRequest = spec.buildRequests(bidRequests, null)[0]; + expect(bidRequest.data.secure).to.be.true; + stub.restore() + }); + + it('should add ccpa parameter if uspConsent is present', function () { + const uspConsent = '1YNN'; + const bidderRequest = { uspConsent: uspConsent }; + const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(bidRequest.data.us_privacy).to.eq(uspConsent); + }); + it('should add consent parameters if gdprConsent is present', function () { const gdprConsent = { consentString: 'consent_string123', gdprApplies: true }; const bidderRequest = { gdprConsent: gdprConsent }; @@ -413,7 +439,7 @@ describe('sharethrough adapter spec', function () { expect(() => btoa(JSON.stringify(bidderResponse))).to.throw(); expect(() => resp = sharethroughInternal.b64EncodeUnicode(JSON.stringify(bidderResponse))).not.to.throw(); expect(adMarkup).to.match( - /data-str-native-key="pKey" data-stx-response-name=\"str_response_bidId\"/); + /data-str-native-key="pKey" data-stx-response-name="str_response_bidId"/); expect(!!adMarkup.indexOf(resp)).to.eql(true); // insert functionality to autodetect whether or not in safeframe, and handle JS insertion @@ -428,10 +454,10 @@ describe('sharethrough adapter spec', function () { expect(() => btoa(JSON.stringify(bidderResponse))).to.throw(); expect(() => resp = sharethroughInternal.b64EncodeUnicode(JSON.stringify(bidderResponse))).not.to.throw(); expect(adMarkup).to.match( - /data-str-native-key="pKey" data-stx-response-name=\"str_response_bidId\"/); + /data-str-native-key="pKey" data-stx-response-name="str_response_bidId"/); expect(!!adMarkup.indexOf(resp)).to.eql(true); expect(adMarkup).to.match( - /"'; diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js new file mode 100644 index 00000000000..95eb36d8a0d --- /dev/null +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -0,0 +1,505 @@ +import { spec } from 'modules/smaatoBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {config} from 'src/config.js'; + +const imageAd = { + image: { + img: { + url: 'https://prebid/static/ad.jpg', + w: 320, + h: 50, + ctaurl: 'https://prebid/track/ctaurl' + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } +}; + +const richmediaAd = { + richmedia: { + mediadata: { + content: '

RICHMEDIA CONTENT

', + w: 800, + h: 600 + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } +}; + +const ADTYPE_IMG = 'Img'; +const ADTYPE_RICHMEDIA = 'Richmedia'; +const ADTYPE_VIDEO = 'Video'; + +const context = { + keywords: 'power tools,drills' +}; + +const user = { + keywords: 'a,b', + gender: 'M', + yob: 1984 +}; + +const openRtbBidResponse = (adType) => { + let adm = ''; + + switch (adType) { + case ADTYPE_IMG: + adm = JSON.stringify(imageAd); + break; + case ADTYPE_RICHMEDIA: + adm = JSON.stringify(richmediaAd); + break; + case ADTYPE_VIDEO: + adm = ''; + break; + default: + throw Error('Invalid AdType'); + } + + let resp = { + body: { + bidid: '04db8629-179d-4bcd-acce-e54722969006', + cur: 'USD', + ext: {}, + id: '5ebea288-f13a-4754-be6d-4ade66c68877', + seatbid: [ + { + bid: [ + { + 'adm': adm, + 'adomain': [ + 'smaato.com' + ], + 'bidderName': 'smaato', + 'cid': 'CM6523', + 'crid': 'CR69381', + 'dealid': '12345', + 'id': '6906aae8-7f74-4edd-9a4f-f49379a3cadd', + 'impid': '226416e6e6bf41', + 'iurl': 'https://prebid/iurl', + 'nurl': 'https://prebid/nurl', + 'price': 0.01, + 'w': 350, + 'h': 50 + } + ], + seat: 'CM6523' + } + ], + }, + headers: { + get: function (header) { + if (header === 'X-SMT-ADTYPE') { + return adType; + } + } + } + }; + return resp; +}; + +const request = { + method: 'POST', + url: 'https://prebid.ad.smaato.net/oapi/prebid', + data: '' +}; + +const interpretedBidsImg = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + ad: '
\"\"\"\"
', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'banner' + } + } +]; + +const interpretedBidsRichmedia = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + ad: '

RICHMEDIA CONTENT

\"\"\"\"
', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'banner' + } + } +]; + +const interpretedBidsVideo = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + vastXml: '', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'video' + } + } +]; + +const defaultBidderRequest = { + gdprConsent: { + consentString: 'HFIDUYFIUYIUYWIPOI87392DSU', + gdprApplies: true + }, + uspConsent: 'uspConsentString', + refererInfo: { + referer: 'http://example.com/page.html', + }, + timeout: 1200 +}; + +const minimalBidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } +}; + +const singleBannerBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: { + sizes: [[300, 50]] + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +const singleVideoBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[768, 1024]], + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + maxduration: 30, + startdelay: 0, + linearity: 1, + protocols: [7], + skip: 1, + skipmin: 5, + api: [7], + ext: {rewarded: 0} + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +const combinedBannerAndVideoBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: { + sizes: [[300, 50]] + }, + video: { + context: 'outstream', + playerSize: [[768, 1024]], + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + maxduration: 30, + startdelay: 0, + linearity: 1, + protocols: [7], + skip: 1, + skipmin: 5, + api: [7], + ext: {rewarded: 0} + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +describe('smaatoBidAdapterTest', () => { + describe('isBidRequestValid', () => { + it('has valid params', () => { + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: '456'}})).to.be.true; + expect(spec.isBidRequestValid(singleBannerBidRequest)).to.be.true; + }); + it('has invalid params', () => { + expect(spec.isBidRequestValid({})).to.be.false; + expect(spec.isBidRequestValid({params: {}})).to.be.false; + expect(spec.isBidRequestValid({params: {publisherId: '123'}})).to.be.false; + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: 456}})).to.be.false; + }); + }); + + describe('buildRequests', () => { + beforeEach(() => { + this.req = JSON.parse(spec.buildRequests([singleBannerBidRequest], defaultBidderRequest).data); + this.sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + this.sandbox.restore(); + }); + + it('can override endpoint', () => { + const overridenEndpoint = 'https://prebid/bidder'; + let bidRequest = utils.deepClone(singleBannerBidRequest); + utils.deepSetValue(bidRequest, 'params.endpoint', overridenEndpoint); + const actualEndpoint = spec.buildRequests([bidRequest], defaultBidderRequest).url; + expect(actualEndpoint).to.equal(overridenEndpoint); + }); + + it('sends correct imps', () => { + expect(this.req.imp).to.deep.equal([ + { + id: 'bidId', + banner: { + w: 300, + h: 50, + format: [ + { + h: 50, + w: 300 + } + ] + }, + tagid: 'adspaceId' + } + ]) + }); + + it('sends correct site', () => { + expect(this.req.site.id).to.exist.and.to.be.a('string'); + expect(this.req.site.domain).to.exist.and.to.be.a('string'); + expect(this.req.site.page).to.exist.and.to.be.a('string'); + expect(this.req.site.ref).to.equal('http://example.com/page.html'); + expect(this.req.site.publisher.id).to.equal('publisherId'); + }) + + it('sends gdpr applies if exists', () => { + expect(this.req.regs.ext.gdpr).to.equal(1); + expect(this.req.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); + }); + + it('sends no gdpr applies if no gdpr exists', () => { + let req_without_gdpr = JSON.parse(spec.buildRequests([singleBannerBidRequest], minimalBidderRequest).data); + expect(req_without_gdpr.regs.ext.gdpr).to.not.exist; + expect(req_without_gdpr.user.ext.consent).to.not.exist; + }); + + it('sends usp if exists', () => { + expect(this.req.regs.ext.us_privacy).to.equal('uspConsentString'); + }); + + it('sends tmax', () => { + expect(this.req.tmax).to.equal(1200); + }); + + it('sends no usp if no usp exists', () => { + let req_without_usp = JSON.parse(spec.buildRequests([singleBannerBidRequest], minimalBidderRequest).data); + expect(req_without_usp.regs.ext.us_privacy).to.not.exist; + }); + + it('sends fp data', () => { + this.sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + fpd: { + context, + user + } + }; + return utils.deepAccess(config, key); + }); + let bidRequest = utils.deepClone(singleBannerBidRequest); + let req_fpd = JSON.parse(spec.buildRequests([bidRequest], defaultBidderRequest).data); + expect(req_fpd.user.gender).to.equal('M'); + expect(req_fpd.user.yob).to.equal(1984); + expect(req_fpd.user.keywords).to.eql('a,b'); + expect(req_fpd.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); + expect(req_fpd.site.keywords).to.eql('power tools,drills'); + expect(req_fpd.site.publisher.id).to.equal('publisherId'); + }) + }); + + describe('buildRequests for video imps', () => { + it('sends correct video imps', () => { + let req = JSON.parse(spec.buildRequests([singleVideoBidRequest], defaultBidderRequest).data); + expect(req.imp).to.deep.equal([ + { + id: 'bidId', + video: { + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + startdelay: 0, + linearity: 1, + h: 1024, + maxduration: 30, + skip: 1, + protocols: [7], + ext: { + rewarded: 0 + }, + skipmin: 5, + api: [7], + w: 768 + }, + tagid: 'adspaceId' + } + ]) + }); + it('allows combines banner and video imp in single bid request', () => { + let req = JSON.parse(spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest).data); + expect(req.imp).to.deep.equal([ + { + id: 'bidId', + banner: { + w: 300, + h: 50, + format: [ + { + h: 50, + w: 300 + } + ] + }, + video: { + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + startdelay: 0, + linearity: 1, + h: 1024, + maxduration: 30, + skip: 1, + protocols: [7], + ext: { + rewarded: 0 + }, + skipmin: 5, + api: [7], + w: 768 + }, + tagid: 'adspaceId' + } + ]) + }); + it('allows two imps in the same bid request', () => { + let req = JSON.parse(spec.buildRequests([singleBannerBidRequest, singleBannerBidRequest], defaultBidderRequest).data); + expect(req.imp).to.have.length(2); + }); + }); + + describe('interpretResponse', () => { + it('single image reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_IMG), request); + assert.deepStrictEqual(bids, interpretedBidsImg); + }); + it('single richmedia reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_RICHMEDIA), request); + assert.deepStrictEqual(bids, interpretedBidsRichmedia); + }); + it('single video reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_VIDEO), request); + assert.deepStrictEqual(bids, interpretedBidsVideo); + }); + it('ignores bid response with invalid ad type', () => { + let resp = openRtbBidResponse(ADTYPE_IMG); + resp.headers.get = (header) => { + if (header === 'X-SMT-ADTYPE') { + return undefined; + } + } + const bids = spec.interpretResponse(resp, request); + expect(bids).to.be.empty + }); + it('uses correct TTL when expire header exists', () => { + const clock = sinon.useFakeTimers(); + clock.tick(2000); + let resp = openRtbBidResponse(ADTYPE_IMG); + resp.headers.get = (header) => { + if (header === 'X-SMT-ADTYPE') { + return ADTYPE_IMG; + } + if (header === 'X-SMT-Expires') { + return 2000 + (400 * 1000); + } + } + const bids = spec.interpretResponse(resp, request); + expect(bids[0].ttl).to.equal(400); + clock.restore(); + }); + }); +}); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js new file mode 100644 index 00000000000..2faad47aca8 --- /dev/null +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -0,0 +1,547 @@ +import { + expect +} from 'chai'; +import { + spec +} from 'modules/smartadserverBidAdapter.js'; +import { + newBidder +} from 'src/adapters/bidderFactory.js'; +import { + config +} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { requestBidsHook } from 'modules/consentManagement.js'; + +// Default params with optional ones +describe('Smart bid adapter tests', function () { + var DEFAULT_PARAMS = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'smartadserver', + params: { + domain: 'https://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' + }]; + + var DEFAULT_PARAMS_WITH_EIDS = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'smartadserver', + params: { + domain: 'https://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', + userId: { + britepoolid: '1111', + criteoId: '1111', + digitrustid: { data: { id: 'DTID', keyv: 4, privacy: { optout: false }, producer: 'ABC', version: 2 } }, + id5id: '1111', + idl_env: '1111', + lipbid: '1111', + parrableid: 'eidVersion.encryptionKeyReference.encryptedValue', + pubcid: '1111', + tdid: '1111', + netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', + } + }]; + + // Default params without optional ones + var DEFAULT_PARAMS_WO_OPTIONAL = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ], + } + }, + + bidder: 'smartadserver', + params: { + domain: 'https://prg.smartadserver.com', + siteId: '1234', + pageId: '5678', + formatId: '90' + }, + requestId: 'efgh5678' + }]; + + var BID_RESPONSE = { + 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('Verify build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('https://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.not.have.property('pageDomain'); + 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('Verify parse response', function () { + 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(function () { + spec.interpretResponse(BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('Verifies bidder code', function () { + expect(spec.code).to.equal('smartadserver'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('smart'); + }); + + it('Verifies if bid request valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + 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); + }); + + it('Verifies user sync', function () { + 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'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false + }, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); + }); + + describe('gdpr tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==', + gdprApplies: true + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr').and.to.equal(true); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOKAVy4OKAVy4ABAB8AAAAAZ+A=='); + }); + + it('Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.not.have.property('gdpr'); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOKAVy4OKAVy4ABAB8AAAAAZ+A=='); + }); + }); + + describe('ccpa/us privacy tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('Verify build request with us privacy', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + + const uspConsentValue = '1YNN' + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + uspConsent: uspConsentValue + }); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('us_privacy').and.to.equal(uspConsentValue); + }); + }); + + describe('Instream video tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + const INSTREAM_DEFAULT_PARAMS = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]] // It seems prebid.js transforms the player size array into an array of array... + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + video: { + protocol: 6 + } + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]; + + var INSTREAM_BID_RESPONSE = { + body: { + cpm: 12, + width: 640, + height: 480, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake-vast.url', + ad: '', + cSyncUrl: 'http://awesome.fake.csync.url' + } + }; + + it('Verify instream video build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('https://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.not.have.property('pageDomain'); + 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); + expect(requestContent).to.have.property('isVideo').and.to.equal(true); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); + expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(640); + expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(480); + }); + + it('Verify instream parse response', function () { + const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS); + const bids = spec.interpretResponse(INSTREAM_BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(12); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastUrl).to.equal('http://awesome.fake-vast.url'); + expect(bid.vastXml).to.equal(''); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + 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(INSTREAM_DEFAULT_PARAMS[0].bidId); + + expect(function () { + spec.interpretResponse(INSTREAM_BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('Verify not handled media type return empty request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests([{ + adUnitCode: 'sas_42', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'badcontext' + } + }, + params: { + domain: 'https://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' + }, INSTREAM_DEFAULT_PARAMS[0]]); + expect(request[0]).to.be.empty; + expect(request[1]).to.not.be.empty; + }); + }); + + describe('External ids tests', function () { + it('Verify external ids in request and ids found', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WITH_EIDS); + expect(request[0]).to.have.property('url').and.to.equal('https://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('eids'); + expect(requestContent.eids).to.not.equal(null).and.to.not.be.undefined; + expect(requestContent.eids.length).to.greaterThan(0); + for (let index in requestContent.eids) { + let eid = requestContent.eids[index]; + expect(eid.source).to.not.equal(null).and.to.not.be.undefined; + expect(eid.uids).to.not.equal(null).and.to.not.be.undefined; + for (let uidsIndex in eid.uids) { + let uid = eid.uids[uidsIndex]; + expect(uid.id).to.not.equal(null).and.to.not.be.undefined; + } + } + }); + }); + + describe('Supply Chain Serializer tests', function () { + it('Verify a multi node supply chain serialization matches iab example', function() { + let schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1, + 'rid': 'bid-request-2', + 'name': 'intermediary', + 'domain': 'intermediary.com' + } + ] + }; + + let serializedSchain = spec.serializeSupplyChain(schain); + expect(serializedSchain).to.equal('1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com!exchange2.com,abcd,1,bid-request-2,intermediary,intermediary.com'); + }); + + it('Verifiy that null schain produce null result', function () { + let actual = spec.serializeSupplyChain(null); + expect(null, actual); + }); + + it('Verifiy that schain with null nodes produce null result', function () { + let schain = { + 'ver': '1.0', + 'complete': 1 + + }; + let actual = spec.serializeSupplyChain(null); + expect(null, actual); + }); + }); +}); diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js index 955dd3ba471..cb5ceee0870 100644 --- a/test/spec/modules/smartrtbBidAdapter_spec.js +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai' -import { spec, _getPlatform } from 'modules/smartrtbBidAdapter' -import { newBidder } from 'src/adapters/bidderFactory' +import { spec, _getPlatform } from 'modules/smartrtbBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' const br = { body: { diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js new file mode 100644 index 00000000000..2780e88255d --- /dev/null +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -0,0 +1,231 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/smartyadsBidAdapter.js'; + +describe('SmartyadsAdapter', function () { + let bid = { + bidId: '23fhj33i987f', + bidder: 'smartyads', + params: { + placementId: 0, + traffic: 'banner' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://ssp-nj.webtradehub.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', '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', 'sizes'); + 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', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://ssp-nj.webtradehub.com/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 78a793137b6..0ac242ce0e1 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -1,9 +1,9 @@ import { expect } from 'chai'; -import { spec } from 'modules/smilewantedBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { config } from 'src/config'; -import * as utils from 'src/utils'; -import { requestBidsHook } from 'modules/consentManagement'; +import { spec } from 'modules/smilewantedBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { requestBidsHook } from 'modules/consentManagement.js'; const DISPLAY_REQUEST = [{ adUnitCode: 'sw_300x250', diff --git a/test/spec/modules/smmsBidAdapter_spec.js b/test/spec/modules/smmsBidAdapter_spec.js index 3ed952811ea..3cbeb91cf5a 100644 --- a/test/spec/modules/smmsBidAdapter_spec.js +++ b/test/spec/modules/smmsBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; -import {spec, _getUrlVars} from 'modules/smmsBidAdapter'; -import * as utils from 'src/utils'; +import {spec, _getUrlVars} from 'modules/smmsBidAdapter.js'; +import * as utils from 'src/utils.js'; const BASE_URI = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/banner' const NATIVE_BASE_URI = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/native' diff --git a/test/spec/modules/somoBidAdapter_spec.js b/test/spec/modules/somoBidAdapter_spec.js new file mode 100644 index 00000000000..32e9c6c5323 --- /dev/null +++ b/test/spec/modules/somoBidAdapter_spec.js @@ -0,0 +1,545 @@ +import {expect} from 'chai'; +import {spec} from 'modules/somoBidAdapter.js'; +import * as utils from 'src/utils.js'; + +describe('Somo Audience Adapter Tests', function () { + describe('isBidRequestValid', function () { + it('should return false when given an invalid bid', function () { + const bid = { + bidder: 'somo', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + it('should return true when given a placementId bid', function () { + const bid = { + bidder: 'somo', + params: { + placementId: 'test' + } + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + describe('buildBannerRequests', function () { + it('should properly build a banner request with type not defined and sizes not defined', function () { + const bidRequests = [{ + bidder: 'somo', + params: { + placementId: 'test' + } + }]; + const bidderRequest = { + refererInfo: { + referer: 'https://www.test.com/page?var=val', + canonicalUrl: 'https://www.test.com/page' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].url).to.equal('https://publisher-east.mobileadtrading.com/rtb/bid?s=test'); + expect(request[0].method).to.equal('POST'); + const ortbRequest = request[0].data; + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.ref).to.equal('https://www.test.com/page?var=val'); + expect(ortbRequest.site.page).to.equal('https://www.test.com/page'); + expect(ortbRequest.site.domain).to.not.be.undefined; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.device).to.not.equal(null); + expect(ortbRequest.device.ua).to.equal(navigator.userAgent); + expect(ortbRequest.imp[0].bidfloor).to.not.be.null; + expect(ortbRequest.imp[0].banner).to.not.equal(null); + }); + + it('should properly build a banner request with sizes defined in 2d array', function () { + const bidRequests = [{ + bidder: 'somo', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'test' + } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://www.test.com/page?var=val', + canonicalUrl: 'https://www.test.com/page' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].url).to.equal('https://publisher-east.mobileadtrading.com/rtb/bid?s=test'); + expect(request[0].method).to.equal('POST'); + const ortbRequest = request[0].data; + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.ref).to.equal('https://www.test.com/page?var=val'); + expect(ortbRequest.site.page).to.equal('https://www.test.com/page'); + expect(ortbRequest.site.domain).to.not.be.undefined; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].bidfloor).to.not.be.null; + 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); + }); + it('should properly build a banner request with sizes defined in 1d array', function () { + const bidRequests = [{ + bidder: 'somo', + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + params: { + placementId: 'test' + } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://www.test.com/page?var=val', + canonicalUrl: 'https://www.test.com/page' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].url).to.equal('https://publisher-east.mobileadtrading.com/rtb/bid?s=test'); + expect(request[0].method).to.equal('POST'); + const ortbRequest = request[0].data; + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.ref).to.equal('https://www.test.com/page?var=val'); + expect(ortbRequest.site.page).to.equal('https://www.test.com/page'); + expect(ortbRequest.site.domain).to.not.be.undefined; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].bidfloor).to.not.be.null; + 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].banner.mimes).to.equal(undefined); + expect(ortbRequest.imp[0].banner.btype).to.equal(undefined); + expect(ortbRequest.imp[0].banner.pos).to.equal(undefined); + expect(ortbRequest.imp[0].banner.battr).to.equal(undefined); + }); + + it('should populate optional banner parameters', function () { + const bidRequests = [ + { + bidder: 'somo', + mediaTypes: { + banner: { + sizes: [[300, 200]] + } + }, + mediaType: 'banner', + params: { + placementId: 'test', + banner: { + mimes: 'video/mp4', + btype: '4', + pos: '1', + battr: 'ibv', + } + } + } + ]; + const request = spec.buildRequests(bidRequests); + const ortbRequest = request[0].data; + 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(200); + expect(ortbRequest.imp[0].banner.mimes).to.equal('video/mp4'); + expect(ortbRequest.imp[0].banner.btype).to.equal('4'); + expect(ortbRequest.imp[0].banner.pos).to.equal('1'); + expect(ortbRequest.imp[0].banner.battr).to.equal('ibv'); + }); + }); + + describe('buildVideoRequests', function () { + it('should properly build a video request with sizes defined', function () { + const bidRequests = [{ + bidder: 'somo', + mediaTypes: { + video: { + sizes: [200, 300] + } + }, + params: { + placementId: 'test' + } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://www.test.com/page?var=val', + canonicalUrl: 'https://www.test.com/page' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request[0].data; + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.ref).to.equal('https://www.test.com/page?var=val'); + expect(ortbRequest.site.page).to.equal('https://www.test.com/page'); + expect(ortbRequest.site.domain).to.not.be.undefined; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video).to.not.equal(null); + expect(ortbRequest.imp[0].video.w).to.equal(200); + expect(ortbRequest.imp[0].video.h).to.equal(300); + }); + + it('should properly build a video request with sizes defined in 2d array', function () { + const bidRequests = [{ + bidder: 'somo', + mediaTypes: { + video: { + sizes: [[200, 300]] + } + }, + params: { + placementId: 'test' + } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://www.test.com/page?var=val', + canonicalUrl: 'https://www.test.com/page' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request[0].data; + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.ref).to.equal('https://www.test.com/page?var=val'); + expect(ortbRequest.site.page).to.equal('https://www.test.com/page'); + expect(ortbRequest.site.domain).to.not.be.undefined; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video).to.not.equal(null); + expect(ortbRequest.imp[0].video.w).to.equal(200); + expect(ortbRequest.imp[0].video.h).to.equal(300); + }); + it('should properly build a video request with sizes not defined', function () { + const bidRequests = [{ + bidder: 'somo', + mediaType: 'video', + params: { + placementId: 'test' + } + }]; + const request = spec.buildRequests(bidRequests); + const ortbRequest = request[0].data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video).to.not.equal(null); + expect(ortbRequest.imp[0].video.mimes).to.equal(undefined); + expect(ortbRequest.imp[0].video.minduration).to.equal(undefined); + expect(ortbRequest.imp[0].video.maxduration).to.equal(undefined); + expect(ortbRequest.imp[0].video.protocols).to.equal(undefined); + expect(ortbRequest.imp[0].video.startdelay).to.equal(undefined); + expect(ortbRequest.imp[0].video.linearity).to.equal(undefined); + expect(ortbRequest.imp[0].video.skip).to.equal(undefined); + expect(ortbRequest.imp[0].video.delivery).to.equal(undefined); + expect(ortbRequest.imp[0].video.pos).to.equal(undefined); + expect(ortbRequest.imp[0].video.api).to.equal(undefined); + expect(ortbRequest.imp[0].video.battr).to.equal(undefined); + }); + + it('should populate optional video parameters', function () { + const bidRequests = [ + { + bidder: 'somo', + mediaTypes: { + video: { + sizes: [[200, 300]] + } + }, + params: { + placementId: 'test', + video: { + mimes: 'video/mp4', + minduration: '15', + maxduration: '30', + protocols: 'mp4', + startdelay: '0', + linearity: 'linear', + skip: '1', + delivery: 'web', + pos: '1', + api: 'VPAID 1.0', + battr: 'ibv', + } + } + } + ]; + const request = spec.buildRequests(bidRequests); + const ortbRequest = request[0].data; + expect(ortbRequest.imp[0].video).to.not.equal(null); + expect(ortbRequest.imp[0].video.w).to.equal(200); + expect(ortbRequest.imp[0].video.h).to.equal(300); + expect(ortbRequest.imp[0].video.mimes).to.equal('video/mp4'); + expect(ortbRequest.imp[0].video.minduration).to.equal('15'); + expect(ortbRequest.imp[0].video.maxduration).to.equal('30'); + expect(ortbRequest.imp[0].video.protocols).to.equal('mp4'); + expect(ortbRequest.imp[0].video.startdelay).to.equal('0'); + expect(ortbRequest.imp[0].video.linearity).to.equal('linear'); + expect(ortbRequest.imp[0].video.skip).to.equal('1'); + expect(ortbRequest.imp[0].video.delivery).to.equal('web'); + expect(ortbRequest.imp[0].video.pos).to.equal('1'); + expect(ortbRequest.imp[0].video.api).to.equal('VPAID 1.0'); + expect(ortbRequest.imp[0].video.battr).to.equal('ibv'); + }); + }); + + describe('buildSiteRequests', function () { + it('should fill in basic site parameters', function () { + const bidRequests = [{ + bidder: 'somo', + params: { + placementId: 'test' + } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://www.test.com/page?var=val', + canonicalUrl: 'https://www.test.com/page' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request[0].data; + expect(ortbRequest.app).to.equal(null); + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.ref).to.equal('https://www.test.com/page?var=val'); + expect(ortbRequest.site.page).to.equal('https://www.test.com/page'); + expect(ortbRequest.site.domain).to.not.be.undefined; + }); + + it('should fill in optional site parameters', function () { + const bidRequests = [{ + bidder: 'somo', + params: { + placementId: 'test', + site: { + domain: 'somoaudience.com', + name: 'Somo Audience', + cat: 'IAB-25', + keywords: 'unit testing', + content: 'Unit Testing' + } + } + }]; + const request = spec.buildRequests(bidRequests); + const ortbRequest = request[0].data; + expect(ortbRequest.app).to.equal(null); + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.name).to.equal('Somo Audience'); + expect(ortbRequest.site.domain).to.equal('somoaudience.com'); + expect(ortbRequest.site.cat).to.equal('IAB-25'); + expect(ortbRequest.site.keywords).to.equal('unit testing'); + expect(ortbRequest.site.content).to.equal('Unit Testing'); + }) + }); + + describe('buildAppRequests', function () { + it('should fill in app parameters', function () { + const bidRequests = [{ + bidder: 'somo', + params: { + placementId: 'test', + app: { + bundle: 'com.somoaudience.apps', + storeUrl: 'http://somoaudience.com/apps', + domain: 'somoaudience.com', + name: 'Generic SomoAudience App 5', + cat: 'IAB-25', + keywords: 'unit testing', + content: 'Unit Testing', + ver: '5.423-s', + } + } + }]; + const request = spec.buildRequests(bidRequests); + 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'); + expect(ortbRequest.app.name).to.equal('Generic SomoAudience App 5'); + expect(ortbRequest.app.ver).to.equal('5.423-s'); + expect(ortbRequest.app.cat).to.equal('IAB-25'); + expect(ortbRequest.app.keywords).to.equal('unit testing'); + expect(ortbRequest.app.content).to.equal('Unit Testing'); + }); + }); + + describe('buildGDPRRequests', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'test' + }, + }; + + it('should properly build request with gdpr consent', function () { + const bidRequests = [{ + bidder: 'somo', + params: { + placementId: 'test' + } + }]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request[0].data; + expect(ortbRequest.reqs).to.not.equal(undefined); + expect(ortbRequest.reqs.ext).to.not.equal(undefined); + expect(ortbRequest.reqs.ext.gdpr).to.equal(true); + expect(ortbRequest.user).to.not.equal(undefined); + expect(ortbRequest.user.ext).to.not.equal(undefined); + expect(ortbRequest.user.ext.consent).to.equal('test'); + }); + it('should properly build request with gdpr not applies', function () { + bidderRequest.gdprConsent.gdprApplies = false; + const bidRequests = [{ + bidder: 'somo', + params: { + placementId: 'test' + } + }]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request[0].data; + expect(ortbRequest.reqs).to.not.equal(undefined); + expect(ortbRequest.reqs.ext).to.not.equal(undefined); + expect(ortbRequest.reqs.ext.gdpr).to.equal(false); + expect(ortbRequest.user).to.not.equal(undefined); + expect(ortbRequest.user.ext).to.not.equal(undefined); + expect(ortbRequest.user.ext.consent).to.equal('test'); + }); + }); + + describe('buildExtraArgsRequests', function () { + it('should populate optional parameters', function () { + const bidRequests = [ + { + bidder: 'somo', + params: { + placementId: 'test', + bcat: ['IAB-2', 'IAB-7'], + badv: ['somoaudience.com', 'mobileadtrading.com'], + bidfloor: '0.05', + }, + } + ]; + const request = spec.buildRequests(bidRequests); + const ortbRequest = request[0].data; + expect(ortbRequest.imp[0].bidfloor).to.not.be.null; + expect(ortbRequest.imp[0].bidfloor).to.be.equal('0.05'); + expect(ortbRequest.bcat).to.not.be.null; + expect(ortbRequest.bcat).to.have.lengthOf(2); + expect(ortbRequest.bcat).to.contain('IAB-2'); + expect(ortbRequest.badv).to.not.be.null; + expect(ortbRequest.badv).to.have.lengthOf(2); + expect(ortbRequest.badv).to.contain('somoaudience.com'); + }); + }); + }); + + describe('interpretResponse', function () { + it('Verify banner parse response', function () { + const bidRequests = [ + { + bidder: 'somo', + params: { + placementId: 'test', + }, + bidId: '234234234', + } + ]; + const request = spec.buildRequests(bidRequests); + const ortbRequest = request[0].data; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'Somo Test Ad' + }], + bidId: '234234234' + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, {bidRequest: bidRequests[0]}); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.equal('Somo Test Ad'); + }); + + it('Verify video parse response', function () { + const bidRequests = [ + { + bidder: 'somo', + mediaTypes: { + video: { + } + }, + params: { + placementId: 'test', + }, + bidId: '234234234', + } + ]; + const request = spec.buildRequests(bidRequests); + const ortbRequest = request[0].data; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'Somo Test Ad' + }], + bidId: '234234234' + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, {bidRequest: bidRequests[0]}); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.vastXml).to.equal('Somo Test Ad'); + }); + }); + + describe('user sync', function () { + it('should register the pixel sync url', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('should pass gdpr params', function () { + let syncs = spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: false, consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.contains('gdpr=0'); + }); + + it('should pass gdpr applies params', function () { + let syncs = spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: true, consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.contains('gdpr=1'); + expect(syncs[0].url).to.contains('gdpr_consent=test'); + }); + }); +}); diff --git a/test/spec/modules/sonobiAnalyticsAdapter_spec.js b/test/spec/modules/sonobiAnalyticsAdapter_spec.js index 389dfee34f9..76ff88836d4 100644 --- a/test/spec/modules/sonobiAnalyticsAdapter_spec.js +++ b/test/spec/modules/sonobiAnalyticsAdapter_spec.js @@ -1,25 +1,20 @@ -import sonobiAnalytics from 'modules/sonobiAnalyticsAdapter'; +import sonobiAnalytics from 'modules/sonobiAnalyticsAdapter.js'; import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); describe('Sonobi Prebid Analytic', function () { - let xhr; - let requests = []; var clock; describe('enableAnalytics', function () { beforeEach(function () { - requests = []; - xhr = sinon.useFakeXMLHttpRequest(); - xhr.onCreate = request => requests.push(request); sinon.stub(events, 'getEvents').returns([]); clock = sinon.useFakeTimers(Date.now()); }); afterEach(function () { - xhr.restore(); events.getEvents.restore(); clock.restore(); }); @@ -81,8 +76,8 @@ describe('Sonobi Prebid Analytic', function () { events.emit(constants.EVENTS.AUCTION_END, {auctionId: '13', bidsReceived: [bid]}); clock.tick(5000); - expect(requests).to.have.length(1); - expect(JSON.parse(requests[0].requestBody)).to.have.length(3) + expect(server.requests).to.have.length(1); + expect(JSON.parse(server.requests[0].requestBody)).to.have.length(3) done(); }); }); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 52d755a1faf..52821072a21 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai' -import { spec, _getPlatform } from 'modules/sonobiBidAdapter' -import { newBidder } from 'src/adapters/bidderFactory' -import {userSync} from '../../../src/userSync'; +import { spec, _getPlatform } from 'modules/sonobiBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' +import {userSync} from '../../../src/userSync.js'; describe('SonobiBidAdapter', function () { const adapter = newBidder(spec) @@ -300,49 +300,9 @@ describe('SonobiBidAdapter', function () { 'reachedTop': true, 'referer': 'https://example.com', 'stack': ['https://example.com'] - } + }, + uspConsent: 'someCCPAString' }; - it('should include the digitrust id and keyv', () => { - window.DigiTrust = { - getUser: function () { - } - }; - let sandbox = sinon.sandbox.create(); - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: true, - identity: { - id: 'Vb0YJIxTMJV4W0GHRdJ3MwyiOVYJjYEgc2QYdBSG', - keyv: 4, - version: 2, - privacy: {} - } - }) - ); - const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.data.digid).to.equal('Vb0YJIxTMJV4W0GHRdJ3MwyiOVYJjYEgc2QYdBSG'); - expect(bidRequests.data.digkeyv).to.equal(4); - sandbox.restore(); - delete window.DigiTrust; - }); - - it('should not include the digitrust id and keyv', () => { - window.DigiTrust = { - getUser: function () { - } - }; - let sandbox = sinon.sandbox.create(); - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: false - }) - ); - const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.data.digid).to.be.undefined; - expect(bidRequests.data.digkeyv).to.be.undefined; - sandbox.restore(); - delete window.DigiTrust; - }); it('should return a properly formatted request', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) @@ -435,62 +395,8 @@ describe('SonobiBidAdapter', function () { it('should return null if there is nothing to bid on', function () { const bidRequests = spec.buildRequests([{params: {}}], bidderRequests) expect(bidRequests).to.equal(null); - }) - - it('should return a properly formatted request with commonid as hfa', function () { - delete bidRequest[0].params.hfa; - delete bidRequest[1].params.hfa; - bidRequest[0].crumbs = {'pubcid': 'abcd-efg-0101'}; - bidRequest[1].crumbs = {'pubcid': 'abcd-efg-0101'}; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.hfa).to.equal('PRE-abcd-efg-0101'); }); - it('should return a properly formatted request with commonid from User ID as hfa', function () { - delete bidRequest[0].params.hfa; - delete bidRequest[1].params.hfa; - bidRequest[0].userId = {'pubcid': 'abcd-efg-0101'}; - bidRequest[1].userId = {'pubcid': 'abcd-efg-0101'}; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.hfa).to.equal('PRE-abcd-efg-0101'); - delete bidRequest[0].userId; - delete bidRequest[1].userId; - }) - - it('should return a properly formatted request with unified id from User ID as tdid', function () { - delete bidRequest[0].params.tdid; - delete bidRequest[1].params.tdid; - bidRequest[0].userId = {'tdid': 'td-abcd-efg-0101'}; - bidRequest[1].userId = {'tdid': 'td-abcd-efg-0101'}; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.tdid).to.equal('td-abcd-efg-0101'); - }) - - it('should return a properly formatted request with hfa preferred over commonid', function () { - bidRequest[0].params.hfa = 'hfakey'; - bidRequest[1].params.hfa = 'hfakey'; - bidRequest[0].crumbs = {'pubcid': 'abcd-efg-0101'}; - bidRequest[1].crumbs = {'pubcid': 'abcd-efg-0101'}; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') - expect(bidRequests.method).to.equal('GET') - expect(bidRequests.data.ref).not.to.be.empty - expect(bidRequests.data.s).not.to.be.empty - expect(bidRequests.data.hfa).to.equal('hfakey') - }) - it('should set ius as 0 if Sonobi cannot drop iframe pixels', function () { userSync.canBidderRegisterSync.returns(false); const bidRequests = spec.buildRequests(bidRequest, bidderRequests); @@ -545,7 +451,26 @@ describe('SonobiBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.kw).to.equal('sports,news,some_other_keyword'); }); - }) + + it('should return a properly formatted request with us_privacy included', function() { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); + }); + + it('should make a request to the url defined in the bidder param', function() { + const bRequest = [ + { + ...bidRequest[0], + params: { + ...bidRequest[0].params, + bid_request_url: 'https://iad-2-apex.go.sonobi.com/trinity.json' + } + } + ]; + const bidRequests = spec.buildRequests(bRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://iad-2-apex.go.sonobi.com/trinity.json'); + }) + }); describe('.interpretResponse', function () { const bidRequests = { diff --git a/test/spec/modules/sortableAnalyticsAdapter_spec.js b/test/spec/modules/sortableAnalyticsAdapter_spec.js index 90bd5fcdf22..258c4b8f74d 100644 --- a/test/spec/modules/sortableAnalyticsAdapter_spec.js +++ b/test/spec/modules/sortableAnalyticsAdapter_spec.js @@ -1,13 +1,12 @@ import {expect} from 'chai'; -import sortableAnalyticsAdapter, {TIMEOUT_FOR_REGISTRY, DEFAULT_PBID_TIMEOUT} from 'modules/sortableAnalyticsAdapter'; -import events from 'src/events'; +import sortableAnalyticsAdapter, {TIMEOUT_FOR_REGISTRY, DEFAULT_PBID_TIMEOUT} from 'modules/sortableAnalyticsAdapter.js'; +import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; -import * as prebidGlobal from 'src/prebidGlobal'; +import * as prebidGlobal from 'src/prebidGlobal.js'; +import {server} from 'test/mocks/xhr.js'; describe('Sortable Analytics Adapter', function() { - let requests; let sandbox; - let xhr; let clock; const initialConfig = { @@ -137,8 +136,6 @@ describe('Sortable Analytics Adapter', function() { beforeEach(function() { sandbox = sinon.sandbox.create(); - xhr = sandbox.useFakeXMLHttpRequest(); - xhr.onCreate = (request) => requests.push(request); clock = sandbox.useFakeTimers(); sandbox.stub(events, 'getEvents').returns([]); sandbox.stub(prebidGlobal, 'getGlobal').returns({ @@ -152,12 +149,12 @@ describe('Sortable Analytics Adapter', function() { } }); - requests = []; sortableAnalyticsAdapter.enableAnalytics(initialConfig); }); afterEach(function() { sandbox.restore(); + clock.restore(); sortableAnalyticsAdapter.disableAnalytics(); }); @@ -179,6 +176,9 @@ describe('Sortable Analytics Adapter', function() { }); describe('events tracking', function() { + beforeEach(function() { + server.requests = []; + }); it('should send the PBID event', function() { events.emit(CONSTANTS.EVENTS.AUCTION_INIT, TEST_DATA.AUCTION_INIT); events.emit(CONSTANTS.EVENTS.BID_REQUESTED, TEST_DATA.BID_REQUESTED); @@ -190,8 +190,8 @@ describe('Sortable Analytics Adapter', function() { clock.tick(DEFAULT_PBID_TIMEOUT); - expect(requests.length).to.equal(1); - let result = JSON.parse(requests[0].requestBody); + expect(server.requests.length).to.equal(1); + let result = JSON.parse(server.requests[0].requestBody); expect(result).to.have.own.property('pbid'); expect(result.pbid).to.deep.include({ ai: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', @@ -242,10 +242,10 @@ describe('Sortable Analytics Adapter', function() { clock.tick(TIMEOUT_FOR_REGISTRY); - expect(requests.length).to.equal(2); - const pbid_req = JSON.parse(requests[0].requestBody); + expect(server.requests.length).to.equal(2); + const pbid_req = JSON.parse(server.requests[0].requestBody); expect(pbid_req).to.have.own.property('pbid'); - const pbwon_req = JSON.parse(requests[1].requestBody); + const pbwon_req = JSON.parse(server.requests[1].requestBody); expect(pbwon_req).to.have.own.property('pbrw'); expect(pbwon_req.pbrw).to.deep.equal({ ac: '300x250', @@ -270,10 +270,10 @@ describe('Sortable Analytics Adapter', function() { clock.tick(TIMEOUT_FOR_REGISTRY); - expect(requests.length).to.equal(2); - const pbid_req = JSON.parse(requests[0].requestBody); + expect(server.requests.length).to.equal(2); + const pbid_req = JSON.parse(server.requests[0].requestBody); expect(pbid_req).to.have.own.property('pbid'); - const pbto_req = JSON.parse(requests[1].requestBody); + const pbto_req = JSON.parse(server.requests[1].requestBody); expect(pbto_req).to.have.own.property('pbto'); expect(pbto_req.pbto).to.deep.equal({ ai: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', @@ -291,8 +291,8 @@ describe('Sortable Analytics Adapter', function() { clock.tick(TIMEOUT_FOR_REGISTRY); - expect(requests.length).to.equal(1); - const err_req = JSON.parse(requests[0].requestBody); + expect(server.requests.length).to.equal(1); + const err_req = JSON.parse(server.requests[0].requestBody); expect(err_req).to.have.own.property('pber'); expect(err_req.pber).to.include({ args: '{}', diff --git a/test/spec/modules/sortableBidAdapter_spec.js b/test/spec/modules/sortableBidAdapter_spec.js new file mode 100644 index 00000000000..8d2b4eea87d --- /dev/null +++ b/test/spec/modules/sortableBidAdapter_spec.js @@ -0,0 +1,539 @@ +import { expect } from 'chai'; +import { spec } from 'modules/sortableBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; + +describe('sortableBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + function makeBid() { + return { + 'bidder': 'sortable', + 'params': { + 'tagId': '403370', + 'siteId': 'example.com', + 'keywords': { + 'key1': 'val1', + 'key2': 'val2' + }, + 'floorSizeMap': { + '728x90': 0.15, + '300x250': 1.20 + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + } + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(makeBid())).to.equal(true); + }); + + it('should return false when tagId not passed correctly', function () { + let bid = makeBid(); + delete bid.params.tagId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when sizes not passed correctly', function () { + let bid = makeBid(); + delete bid.sizes; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when sizes are wrong length', function () { + let bid = makeBid(); + bid.sizes = [[300]]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when sizes are empty', function () { + let bid = makeBid(); + bid.sizes = []; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = makeBid(); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when the floorSizeMap is invalid', function () { + let bid = makeBid(); + bid.params.floorSizeMap = { + 'sixforty by foureighty': 1234 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params.floorSizeMap = { + '728x90': 'three' + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params.floorSizeMap = 'a'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when the floorSizeMap is missing or empty', function () { + let bid = makeBid(); + bid.params.floorSizeMap = {}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.floorSizeMap; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when the keywords are invalid', function () { + let bid = makeBid(); + bid.params.keywords = { + 'badval': 1234 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params.keywords = 'a'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when the keywords are missing or empty', function () { + let bid = makeBid(); + bid.params.keywords = {}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.keywords; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true with video media type', () => { + const videoBid = { + 'bidder': 'sortable', + 'params': { + 'tagId': '403370', + 'siteId': 'example.com', + }, + 'adUnitCode': 'adunit-code', + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + 'video': { + } + } + }; + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + 'bidder': 'sortable', + 'params': { + 'tagId': '403370', + 'siteId': 'example.com', + 'floor': 0.21, + 'keywords': { + 'key1': 'val1', + 'key2': 'val2' + }, + 'floorSizeMap': { + '728x90': 0.15, + '300x250': 1.20 + } + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }, { + 'bidder': 'sortable', + 'params': { + 'tagId': '403371', + 'siteId': 'example.com', + 'floor': 0.21 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + 'native': { + 'body': {'required': true, 'sendId': true}, + 'clickUrl': {'required': true, 'sendId': true}, + 'cta': {'required': true, 'sendId': true}, + 'icon': {'required': true, 'sendId': true}, + 'image': {'required': true, 'sendId': true}, + 'sponsoredBy': {'required': true, 'sendId': true}, + 'title': {'required': true, 'sendId': true, 'len': 100} + } + } + }]; + + const request = spec.buildRequests(bidRequests, {refererInfo: { referer: 'http://example.com/page?param=val' }}); + const requestBody = JSON.parse(request.data); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('attaches source and version to endpoint URL as query params', function () { + const ENDPOINT = `https://c.deployads.com/openrtb2/auction?src=$$REPO_AND_VERSION$$&host=example.com`; + expect(request.url).to.equal(ENDPOINT); + }); + + it('sends screen dimensions', function () { + expect(requestBody.site.device.w).to.equal(screen.width); + expect(requestBody.site.device.h).to.equal(screen.height); + }); + + it('includes the ad size in the bid request', function () { + expect(requestBody.imp[0].banner.format[0].w).to.equal(300); + expect(requestBody.imp[0].banner.format[0].h).to.equal(250); + }); + + it('includes the params in the bid request', function () { + expect(requestBody.imp[0].ext.keywords).to.deep.equal( + {'key1': 'val1', + 'key2': 'val2'} + ); + expect(requestBody.site.publisher.id).to.equal('example.com'); + expect(requestBody.imp[0].tagid).to.equal('403370'); + expect(requestBody.imp[0].bidfloor).to.equal(0.21); + }); + + it('should have the floor size map set', function () { + expect(requestBody.imp[0].ext.floorSizeMap).to.deep.equal({ + '728x90': 0.15, + '300x250': 1.20 + }); + }); + + it('sets domain and href correctly', function () { + expect(requestBody.site.domain).to.equal('example.com'); + expect(requestBody.site.page).to.equal('http://example.com/page?param=val'); + }); + + it('should have the version in native object set for native bid', function() { + expect(requestBody.imp[1].native.ver).to.equal('1'); + }); + + it('should have the assets set for native bid', function() { + const assets = JSON.parse(requestBody.imp[1].native.request).assets; + expect(assets[0]).to.deep.equal({'title': {'len': 100}, 'required': 1, 'id': 0}); + expect(assets[1]).to.deep.equal({'img': {'type': 3, 'wmin': 1, 'hmin': 1}, 'required': 1, 'id': 1}); + expect(assets[2]).to.deep.equal({'img': {'type': 1, 'wmin': 1, 'hmin': 1}, 'required': 1, 'id': 2}); + expect(assets[3]).to.deep.equal({'data': {'type': 2}, 'required': 1, 'id': 3}); + expect(assets[4]).to.deep.equal({'data': {'type': 12}, 'required': 1, 'id': 4}); + expect(assets[5]).to.deep.equal({'data': {'type': 1}, 'required': 1, 'id': 5}); + }); + + const videoBidRequests = [{ + 'bidder': 'sortable', + 'params': { + 'tagId': '403370', + 'siteId': 'example.com', + 'video': { + 'minduration': 5, + 'maxduration': 10, + 'startdelay': 0 + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'mimes': ['video/x-ms-wmv'], + 'playerSize': [[400, 300]], + 'api': [0], + 'protocols': [2, 3], + 'playbackmethod': [1] + } + } + }]; + + const videoRequest = spec.buildRequests(videoBidRequests, {refererInfo: { referer: 'http://localhost:9876/' }}); + const videoRequestBody = JSON.parse(videoRequest.data); + + it('should include video params', () => { + const video = videoRequestBody.imp[0].video; + expect(video.mimes).to.deep.equal(['video/x-ms-wmv']); + expect(video.w).to.equal(400); + expect(video.h).to.equal(300); + expect(video.api).to.deep.equal([0]); + expect(video.protocols).to.deep.equal([2, 3]); + expect(video.playbackmethod).to.deep.equal([1]); + expect(video.minduration).to.equal(5); + expect(video.maxduration).to.equal(10); + expect(video.startdelay).to.equal(0); + }); + + it('sets domain and href correctly', function () { + expect(videoRequestBody.site.domain).to.equal('localhost'); + expect(videoRequestBody.site.page).to.equal('http://localhost:9876/'); + }); + + const gdprBidRequests = [{ + 'bidder': 'sortable', + 'params': { + 'tagId': '403370', + 'siteId': 'example.com', + 'floor': 0.21, + 'keywords': {}, + 'floorSizeMap': {} + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + + function getGdprRequestBody(gdprApplies, consentString) { + const gdprRequest = spec.buildRequests(gdprBidRequests, {'gdprConsent': { + 'gdprApplies': gdprApplies, + 'consentString': consentString + }, + refererInfo: { + referer: 'http://localhost:9876/' + }}); + return JSON.parse(gdprRequest.data); + } + + it('should handle gdprApplies being present and true', function() { + const gdprRequestBody = getGdprRequestBody(true, consentString); + expect(gdprRequestBody.regs.ext.gdpr).to.equal(1); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdprApplies being present and false', function() { + const gdprRequestBody = getGdprRequestBody(false, consentString); + expect(gdprRequestBody.regs.ext.gdpr).to.equal(0); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdprApplies being undefined', function() { + const gdprRequestBody = getGdprRequestBody(undefined, consentString); + expect(gdprRequestBody.regs).to.deep.equal({ext: {}}); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdprConsent being undefined', function() { + const gdprRequest = spec.buildRequests(gdprBidRequests, {refererInfo: { referer: 'http://localhost:9876/' }}); + const gdprRequestBody = JSON.parse(gdprRequest.data); + expect(gdprRequestBody.regs).to.deep.equal({ext: {}}); + expect(gdprRequestBody.user).to.equal(undefined); + }) + }); + + describe('interpretResponse', function () { + function makeResponse() { + return { + body: { + 'id': '5e5c23a5ba71e78', + 'seatbid': [ + { + 'bid': [ + { + 'id': '6vmb3isptf', + 'crid': 'sortablescreative', + 'impid': '322add653672f68', + 'price': 1.22, + 'adm': '', + 'attr': [5], + 'h': 90, + 'nurl': 'http://nurl', + 'w': 728 + } + ], + 'seat': 'MOCK' + } + ], + 'bidid': '5e5c23a5ba71e78' + } + }; + } + + function makeNativeResponse() { + return { + body: { + 'id': '5e5c23a5ba71e77', + 'seatbid': [ + { + 'bid': [ + { + 'id': '6vmb3isptf', + 'crid': 'sortablescreative', + 'impid': '322add653672f67', + 'price': 1.55, + 'adm': '{"native":{"link":{"clicktrackers":[],"url":"https://www.sortable.com/"},"assets":[{"title":{"text":"Ads With Sortable"},"id":1},{"img":{"w":790,"url":"https://path.to/image","h":294},"id":2},{"img":{"url":"https://path.to/icon"},"id":3},{"data":{"value":"Body here"},"id":4},{"data":{"value":"Learn More"},"id":5},{"data":{"value":"Sortable"},"id":6}],"imptrackers":[],"ver":1}}', + 'ext': {'ad_format': 'native'}, + 'h': 90, + 'nurl': 'http://nurl', + 'w': 728 + } + ], + 'seat': 'MOCK' + } + ], + 'bidid': '5e5c23a5ba71e77' + } + }; + } + + const expectedBid = { + 'requestId': '322add653672f68', + 'cpm': 1.22, + 'width': 728, + 'height': 90, + 'creativeId': 'sortablescreative', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ttl': 60, + 'ad': '
' + }; + + const expectedNativeBid = { + 'requestId': '322add653672f67', + 'cpm': 1.55, + 'width': 728, + 'height': 90, + 'creativeId': 'sortablescreative', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'sortable': { 'ad_format': 'native' }, + 'mediaType': 'native', + 'ttl': 60, + 'native': { + 'clickUrl': 'https://www.sortable.com/', + 'title': 'Ads With Sortable', + 'image': {'url': 'https://path.to/image', 'height': 294, 'width': 790}, + 'icon': 'https://path.to/icon', + 'body': 'Body here', + 'cta': 'Learn More', + 'sponsoredBy': 'Sortable' + } + }; + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(makeResponse()); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + + it('should handle a missing crid', function () { + let noCridResponse = makeResponse(); + delete noCridResponse.body.seatbid[0].bid[0].crid; + const fallbackCrid = noCridResponse.body.seatbid[0].bid[0].id; + let noCridResult = Object.assign({}, expectedBid, {'creativeId': fallbackCrid}); + let result = spec.interpretResponse(noCridResponse); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(noCridResult); + }); + + it('should handle a missing nurl', function () { + let noNurlResponse = makeResponse(); + delete noNurlResponse.body.seatbid[0].bid[0].nurl; + let noNurlResult = Object.assign({}, expectedBid); + noNurlResult.ad = ''; + let result = spec.interpretResponse(noNurlResponse); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(noNurlResult); + }); + + it('should handle a missing adm', function () { + let noAdmResponse = makeResponse(); + delete noAdmResponse.body.seatbid[0].bid[0].adm; + let noAdmResult = Object.assign({}, expectedBid); + delete noAdmResult.ad; + noAdmResult.adUrl = 'http://nurl'; + let result = spec.interpretResponse(noAdmResponse); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(noAdmResult); + }); + + it('handles empty bid response', function () { + let response = { + body: { + 'id': '5e5c23a5ba71e78', + 'seatbid': [] + } + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + + it('should get the correct native bid response', function () { + let result = spec.interpretResponse(makeNativeResponse()); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedNativeBid); + }); + + it('fail to parse invalid native bid response', function () { + let response = makeNativeResponse(); + response.body.seatbid[0].bid[0].adm = ''; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + + it('should keep custom properties', () => { + const customProperties = {test: 'a test message', param: {testParam: 1}}; + const expectedResult = Object.assign({}, expectedBid, {[spec.code]: customProperties}); + const response = makeResponse(); + response.body.seatbid[0].bid[0].ext = customProperties; + const result = spec.interpretResponse(response); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedResult); + }); + + it('should handle instream response', () => { + const response = makeResponse(); + const bid = response.body.seatbid[0].bid[0]; + delete bid.nurl; + bid.ext = {ad_format: 'instream'}; + const result = spec.interpretResponse(response)[0]; + expect(result.mediaType).to.equal('video'); + expect(result.vastXml).to.equal(bid.adm); + }); + + it('should return iframe syncs', () => { + const syncResponse = { + ext: { + sync_dsps: [ + ['iframe', 'http://example-dsp/sync-iframe'], + ['image', 'http://example-dsp/sync-image'] + ] + } + }; + expect(spec.getUserSyncs({iframeEnabled: true}, [{body: syncResponse}])).to.deep.equal([{ + type: 'iframe', + url: 'http://example-dsp/sync-iframe' + }]); + }); + + it('should return image syncs', () => { + const syncResponse = { + ext: { + sync_dsps: [ + ['iframe', 'http://example-dsp/sync-iframe'], + ['image', 'http://example-dsp/sync-image'] + ] + } + }; + expect(spec.getUserSyncs({pixelEnabled: true}, [{body: syncResponse}])).to.deep.equal([{ + type: 'image', + url: 'http://example-dsp/sync-image' + }]); + }); + }); +}); diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..d6795331417 --- /dev/null +++ b/test/spec/modules/sovrnAnalyticsAdapter_spec.js @@ -0,0 +1,541 @@ +import sovrnAnalyticsAdapter from '../../../modules/sovrnAnalyticsAdapter.js'; +import { expect } from 'chai'; +import {config} from 'src/config.js'; +import adaptermanager from 'src/adapterManager.js'; +import { server } from 'test/mocks/xhr.js'; +var assert = require('assert'); + +let events = require('src/events'); +let constants = require('src/constants.json'); + +/** + * Emit analytics events + * @param {array} eventArr - array of objects to define the events that will fire + * @param {object} eventObj - key is eventType, value is event + * @param {string} auctionId - the auction id to attached to the events + */ +function emitEvent(eventType, event, auctionId) { + event.auctionId = auctionId; + events.emit(constants.EVENTS[eventType], event); +} + +let auctionStartTimestamp = Date.now(); +let timeout = 3000; +let auctionInit = { + timestamp: auctionStartTimestamp, + timeout: timeout +}; +let bidderCode = 'sovrn'; +let bidderRequestId = '123bri'; +let adUnitCode = 'div'; +let adUnitCode2 = 'div2'; +let bidId = 'bidid'; +let bidId2 = 'bidid2'; +let tId = '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a'; +let tId2 = '99dca3ee-a80a-46d7-a4a0-cbcba463d97e'; +let bidRequested = { + auctionStart: auctionStartTimestamp, + bidderCode: bidderCode, + bidderRequestId: bidderRequestId, + bids: [ + { + adUnitCode: adUnitCode, + bidId: bidId, + bidder: bidderCode, + bidderRequestId: '10340af0c7dc72', + sizes: [[300, 250]], + startTime: auctionStartTimestamp + 100, + transactionId: tId + }, + { + adUnitCode: adUnitCode2, + bidId: bidId2, + bidder: bidderCode, + bidderRequestId: '10340af0c7dc72', + sizes: [[300, 250]], + startTime: auctionStartTimestamp + 100, + transactionId: tId2 + } + ], + doneCbCallCount: 1, + start: auctionStartTimestamp, + timeout: timeout +}; +let bidResponse = { + bidderCode: bidderCode, + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '3870e27a5752fb', + mediaType: 'banner', + source: 'client', + requestId: bidId, + cpm: 0.8584999918937682, + creativeId: 'cridprebidrtb', + dealId: null, + currency: 'USD', + netRevenue: true, + ad: '
divvy mcdiv
', + ttl: 60000, + responseTimestamp: auctionStartTimestamp + 150, + requestTimestamp: auctionStartTimestamp + 100, + bidder: bidderCode, + adUnitCode: adUnitCode, + timeToRespond: 50, + pbLg: '0.50', + pbMg: '0.80', + pbHg: '0.85', + pbAg: '0.85', + pbDg: '0.85', + pbCg: '', + size: '300x250', + adserverTargeting: { + hb_bidder: bidderCode, + hb_adid: '3870e27a5752fb', + hb_pb: '0.85' + }, + status: 'rendered' +}; + +let bidResponse2 = { + bidderCode: bidderCode, + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '9999e27a5752fb', + mediaType: 'banner', + source: 'client', + requestId: bidId2, + cpm: 0.12, + creativeId: 'cridprebidrtb', + dealId: null, + currency: 'USD', + netRevenue: true, + ad: '
divvy mcdiv
', + ttl: 60000, + responseTimestamp: auctionStartTimestamp + 150, + requestTimestamp: auctionStartTimestamp + 100, + bidder: bidderCode, + adUnitCode: adUnitCode2, + timeToRespond: 50, + pbLg: '0.10', + pbMg: '0.10', + pbHg: '0.10', + pbAg: '0.10', + pbDg: '0.10', + pbCg: '', + size: '300x250', + adserverTargeting: { + hb_bidder: bidderCode, + hb_adid: '9999e27a5752fb', + hb_pb: '0.10' + }, + status: 'rendered' +}; +let bidAdjustment = {}; +for (var k in bidResponse) bidAdjustment[k] = bidResponse[k]; +bidAdjustment.cpm = 0.8; +let bidAdjustmentNoMatchingRequest = { + bidderCode: 'not-sovrn', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '1', + mediaType: 'banner', + source: 'client', + requestId: '1', + cpm: 0.10, + creativeId: '', + dealId: null, + currency: 'USD', + netRevenue: true, + ad: '
divvy mcdiv
', + ttl: 60000, + responseTimestamp: auctionStartTimestamp + 150, + requestTimestamp: auctionStartTimestamp + 100, + bidder: 'not-sovrn', + adUnitCode: '', + timeToRespond: 50, + pbLg: '0.00', + pbMg: '0.10', + pbHg: '0.10', + pbAg: '0.10', + pbDg: '0.10', + pbCg: '', + size: '300x250', + adserverTargeting: { + hb_bidder: 'not-sovrn', + hb_adid: '1', + hb_pb: '0.10' + }, +}; +let bidResponseNoMatchingRequest = bidAdjustmentNoMatchingRequest; + +describe('Sovrn Analytics Adapter', function () { + beforeEach(() => { + sinon.stub(events, 'getEvents').returns([]); + }); + afterEach(() => { + events.getEvents.restore(); + }); + + describe('enableAnalytics ', function () { + beforeEach(() => { + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + + it('should catch all events if affiliate id present', function () { + adaptermanager.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + + sinon.assert.callCount(sovrnAnalyticsAdapter.track, 5); + }); + + it('should catch no events if no affiliate id', function () { + adaptermanager.enableAnalytics({ + provider: 'sovrn', + options: { + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + + sinon.assert.callCount(sovrnAnalyticsAdapter.track, 0); + }); + }); + + describe('sovrnAnalyticsAdapter ', function() { + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + it('should have correct type', function () { + assert.equal(sovrnAnalyticsAdapter.getAdapterType(), 'endpoint') + }) + }); + + describe('auction data collector ', function() { + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + it('should create auctiondata record from init ', function () { + let auctionId = '123.123.123.123'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + assert(currentAuction); + let expectedTimeOutData = { + buffer: config.getConfig('timeoutBuffer'), + bidder: config.getConfig('bidderTimeout'), + }; + expect(currentAuction.auction.timeouts).to.deep.equal(expectedTimeOutData); + assert.equal(currentAuction.auction.payload, 'auction'); + assert.equal(currentAuction.auction.priceGranularity, config.getConfig('priceGranularity')) + assert.equal(currentAuction.auction.auctionId, auctionId); + assert.equal(currentAuction.auction.sovrnId, 123); + }); + it('should create a bidrequest object ', function() { + let auctionId = '234.234.234.234'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + assert(currentAuction); + let requests = currentAuction.auction.requests; + assert(requests); + assert.equal(requests.length, 1); + assert.equal(requests[0].bidderCode, bidderCode); + assert.equal(requests[0].bidderRequestId, bidderRequestId); + assert.equal(requests[0].timeout, timeout); + let bids = requests[0].bids; + assert(bids); + assert.equal(bids.length, 2); + assert.equal(bids[0].bidId, bidId); + assert.equal(bids[0].bidder, bidderCode); + assert.equal(bids[0].transactionId, tId); + assert.equal(bids[0].sizes.length, 1); + assert.equal(bids[0].sizes[0][0], 300); + assert.equal(bids[0].sizes[0][1], 250); + expect(requests[0]).to.not.have.property('doneCbCallCount'); + expect(requests[0]).to.not.have.property('auctionId'); + }); + it('should add results to the bid with response ', function () { + let auctionId = '345.345.345.345'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + emitEvent('BID_RESPONSE', bidResponse, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + let returnedBid = currentAuction.auction.requests[0].bids[0]; + assert.equal(returnedBid.bidId, bidId); + assert.equal(returnedBid.bidder, bidderCode); + assert.equal(returnedBid.transactionId, tId); + assert.equal(returnedBid.sizes.length, 1); + assert.equal(returnedBid.sizes[0][0], 300); + assert.equal(returnedBid.sizes[0][1], 250); + assert.equal(returnedBid.adserverTargeting.hb_adid, '3870e27a5752fb'); + assert.equal(returnedBid.adserverTargeting.hb_bidder, bidderCode); + assert.equal(returnedBid.adserverTargeting.hb_pb, '0.85'); + assert.equal(returnedBid.cpm, 0.8584999918937682); + }); + it('should add new unsynced bid if no request exists for response ', function () { + let auctionId = '456.456.456.456'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + emitEvent('BID_RESPONSE', bidResponseNoMatchingRequest, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + let requests = currentAuction.auction.requests; + assert(requests); + assert.equal(requests.length, 1); + let bidRequest = requests[0].bids[0]; + expect(bidRequest).to.not.have.property('adserverTargeting'); + expect(bidRequest).to.not.have.property('cpm'); + expect(currentAuction.auction.unsynced[0]).to.deep.equal(bidResponseNoMatchingRequest); + }); + it('should adjust the bid ', function () { + let auctionId = '567.567.567.567'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + emitEvent('BID_ADJUSTMENT', bidResponse, auctionId); + emitEvent('BID_RESPONSE', bidAdjustment, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + let returnedBid = currentAuction.auction.requests[0].bids[0]; + assert.equal(returnedBid.cpm, 0.8); + assert.equal(returnedBid.originalValues.cpm, 0.8584999918937682); + }); + }); + describe('auction data send ', function() { + let expectedPostBody = { + sovrnId: 123, + auctionId: '678.678.678.678', + payload: 'auction', + priceGranularity: 'medium', + }; + let expectedRequests = { + bidderCode: 'sovrn', + bidderRequestId: '123bri', + timeout: 3000 + }; + let expectedBids = { + adUnitCode: 'div', + bidId: 'bidid', + bidder: 'sovrn', + bidderRequestId: '10340af0c7dc72', + transactionId: '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '3870e27a5752fb', + mediaType: 'banner', + source: 'client', + cpm: 0.8584999918937682, + creativeId: 'cridprebidrtb', + dealId: null, + currency: 'USD', + netRevenue: true, + ttl: 60000, + timeToRespond: 50, + size: '300x250', + status: 'rendered', + isAuctionWinner: true + }; + let SecondAdUnitExpectedBids = { + adUnitCode: 'div2', + bidId: 'bidid2', + bidder: 'sovrn', + bidderRequestId: '10340af0c7dc72', + transactionId: '99dca3ee-a80a-46d7-a4a0-cbcba463d97e', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '9999e27a5752fb', + mediaType: 'banner', + source: 'client', + cpm: 0.12, + creativeId: 'cridprebidrtb', + dealId: null, + currency: 'USD', + netRevenue: true, + ttl: 60000, + timeToRespond: 50, + size: '300x250', + status: 'rendered', + isAuctionWinner: true + }; + let expectedAdServerTargeting = { + hb_bidder: 'sovrn', + hb_adid: '3870e27a5752fb', + hb_pb: '0.85' + }; + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + it('should send auction data ', function () { + let auctionId = '678.678.678.678'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + emitEvent('BID_RESPONSE', bidResponse, auctionId); + emitEvent('BID_RESPONSE', bidResponse2, auctionId) + emitEvent('AUCTION_END', {}, auctionId); + let requestBody = JSON.parse(server.requests[0].requestBody); + let requestsFromRequestBody = requestBody.requests[0]; + let bidsFromRequests = requestsFromRequestBody.bids[0]; + expect(requestBody).to.deep.include(expectedPostBody); + expect(requestBody.timeouts).to.deep.equal({buffer: 400, bidder: 3000}); + expect(requestsFromRequestBody).to.deep.include(expectedRequests); + expect(bidsFromRequests).to.deep.include(expectedBids); + let bidsFromRequests2 = requestsFromRequestBody.bids[1]; + expect(bidsFromRequests2).to.deep.include(SecondAdUnitExpectedBids); + expect(bidsFromRequests.adserverTargeting).to.deep.include(expectedAdServerTargeting); + }); + }); + describe('bid won data send ', function() { + let auctionId = '789.789.789.789'; + let creativeId = 'cridprebidrtb'; + let requestId = 'requestId69'; + let bidWonEvent = { + ad: 'html', + adId: 'adId', + adUnitCode: adUnitCode, + auctionId: auctionId, + bidder: bidderCode, + bidderCode: bidderCode, + cpm: 1.01, + creativeId: creativeId, + currency: 'USD', + height: 250, + mediaType: 'banner', + requestId: requestId, + size: '300x250', + source: 'client', + status: 'rendered', + statusMessage: 'Bid available', + timeToRespond: 421, + ttl: 60, + width: 300 + }; + let expectedBidWonBody = { + sovrnId: 123, + payload: 'winner' + }; + let expectedWinningBid = { + bidderCode: bidderCode, + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: 'adId', + mediaType: 'banner', + source: 'client', + requestId: requestId, + cpm: 1.01, + creativeId: creativeId, + currency: 'USD', + ttl: 60, + auctionId: auctionId, + bidder: bidderCode, + adUnitCode: adUnitCode, + timeToRespond: 421, + size: '300x250', + }; + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + it('should send bid won data ', function () { + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_WON', bidWonEvent, auctionId); + let requestBody = JSON.parse(server.requests[0].requestBody); + expect(requestBody).to.deep.include(expectedBidWonBody); + expect(requestBody.winningBid).to.deep.include(expectedWinningBid); + }); + }); + describe('Error Tracking', function() { + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics() + sovrnAnalyticsAdapter.track.restore() + }); + it('should send an error message when a bid is received for a closed auction', function() { + let auctionId = '678.678.678.678'; + emitEvent('AUCTION_INIT', auctionInit, auctionId) + emitEvent('BID_REQUESTED', bidRequested, auctionId) + emitEvent('AUCTION_END', {}, auctionId) + server.requests[0].respond(200) + emitEvent('BID_RESPONSE', bidResponse, auctionId) + let requestBody = JSON.parse(server.requests[1].requestBody) + expect(requestBody.payload).to.equal('error') + expect(requestBody.message).to.include('Event Received after Auction Close Auction Id') + }) + }) +}) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 44adb4d1cbd..54ccff870eb 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; -import {LogError, spec} from 'modules/sovrnBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; +import {LogError, spec} from 'modules/sovrnBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; @@ -70,12 +70,17 @@ describe('sovrnBidAdapter', function() { }); it('sets the proper banner object', function() { - const payload = JSON.parse(request.data); + const payload = JSON.parse(request.data) expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) expect(payload.imp[0].banner.w).to.equal(1) expect(payload.imp[0].banner.h).to.equal(1) }) + it('includes the ad unit code int the request', function() { + const payload = JSON.parse(request.data); + expect(payload.imp[0].adunitcode).to.equal('adunit-code') + }) + it('accepts a single array as a size', function() { const singleSize = [{ 'bidder': 'sovrn', @@ -152,6 +157,25 @@ describe('sovrnBidAdapter', function() { expect(data.user.ext.consent).to.equal(consentString); }); + it('should send us_privacy if bidderRequest has a value for uspConsent', function () { + let uspString = '1NYN'; + let bidderRequest = { + 'bidderCode': 'sovrn', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + uspConsent: uspString, + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext['us_privacy']).to.equal(uspString); + }); + it('converts tagid to string', function () { const ivBidRequests = [{ 'bidder': 'sovrn', @@ -215,7 +239,7 @@ describe('sovrnBidAdapter', function() { expect(data.source.ext.schain.nodes.length).to.equal(1) }); - it('should add digitrust data if present', function() { + it('should add the unifiedID if present', function() { const digitrustRequests = [{ 'bidder': 'sovrn', 'params': { @@ -230,12 +254,7 @@ describe('sovrnBidAdapter', function() { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', 'userId': { - 'digitrustid': { - 'data': { - 'id': 'digitrust-id-123', - 'keyv': 4 - } - } + 'tdid': 'SOMESORTOFID', } }].concat(bidRequests); const bidderRequest = { @@ -243,13 +262,13 @@ describe('sovrnBidAdapter', function() { referer: 'http://example.com/page.html', } }; - const data = JSON.parse(spec.buildRequests(digitrustRequests, bidderRequest).data); - expect(data.user.ext.digitrust.id).to.equal('digitrust-id-123'); - expect(data.user.ext.digitrust.keyv).to.equal(4); - }); + const data = JSON.parse(spec.buildRequests(digitrustRequests, bidderRequest).data); + expect(data.user.ext.eids[0].source).to.equal('adserver.org') + expect(data.user.ext.eids[0].uids[0].id).to.equal('SOMESORTOFID') + expect(data.user.ext.eids[0].uids[0].ext.rtiPartner).to.equal('TDID') + }) }); - describe('interpretResponse', function () { let response; beforeEach(function () { @@ -399,13 +418,56 @@ describe('sovrnBidAdapter', function() { const expectedReturnStatement = [ { 'type': 'iframe', - 'url': 'https://ap.lijit.com/beacon?informer=13487408&gdpr_consent=', + 'url': 'https://ap.lijit.com/beacon?informer=13487408', } ]; const returnStatement = spec.getUserSyncs(syncOptions, serverResponse); expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); }); + it('should include gdpr consent string if present', function() { + const gdprConsent = { + gdprApplies: 1, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + } + const expectedReturnStatement = [ + { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, + } + ]; + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, ''); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + }); + + it('should include us privacy string if present', function() { + const uspString = '1NYN'; + const expectedReturnStatement = [ + { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, + } + ]; + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + }); + + it('should include all privacy strings if present', function() { + const gdprConsent = { + gdprApplies: 1, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + } + const uspString = '1NYN'; + const expectedReturnStatement = [ + { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, + } + ]; + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + }); + it('should not return if iid missing on server response', function() { const returnStatement = spec.getUserSyncs(syncOptions, []); expect(returnStatement).to.be.empty; diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index d1be5586583..94cc335fd8e 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec, GOOGLE_CONSENT} from 'modules/spotxBidAdapter'; +import {spec, GOOGLE_CONSENT} from 'modules/spotxBidAdapter.js'; describe('the spotx adapter', function () { function getValidBidObject() { @@ -144,7 +144,11 @@ describe('the spotx adapter', function () { price_floor: 123, start_delay: true, number_of_ads: 2, - spotx_all_google_consent: 1 + spotx_all_google_consent: 1, + min_duration: 5, + max_duration: 10, + placement_type: 1, + position: 1 }; bid.userId = { @@ -169,6 +173,10 @@ describe('the spotx adapter', function () { request = spec.buildRequests([bid], bidRequestObj)[0]; expect(request.data.id).to.equal(54321); + expect(request.data.imp.video).to.contain({ + minduration: 5, + maxduration: 10 + }) expect(request.data.imp.video.ext).to.deep.equal({ ad_volume: 1, hide_skin: 1, @@ -177,7 +185,9 @@ describe('the spotx adapter', function () { outstream_function: '987', custom: {bar: 'foo'}, sdk_name: 'Prebid 1+', - versionOrtb: '2.3' + versionOrtb: '2.3', + placement: 1, + pos: 1 }); expect(request.data.imp.video.startdelay).to.equal(1); @@ -273,6 +283,54 @@ describe('the spotx adapter', function () { expect(request.data.regs.ext.gdpr).to.equal(1); expect(request.data.user.ext.consent).to.equal('consent123'); }); + + it('should pass CCPA us_privacy string', function() { + var request; + + bidRequestObj.uspConsent = '1YYY' + + request = spec.buildRequests([bid], bidRequestObj)[0]; + expect(request.data.regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('should pass both GDPR params and CCPA us_privacy', function() { + var request; + + bidRequestObj.gdprConsent = { + consentString: 'consent123', + gdprApplies: true + }; + bidRequestObj.uspConsent = '1YYY' + + request = spec.buildRequests([bid], bidRequestObj)[0]; + expect(request.data.regs.ext.gdpr).to.equal(1); + expect(request.data.user.ext.consent).to.equal('consent123'); + expect(request.data.regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('should pass min and max duration params', function() { + var request; + + bid.params.min_duration = 3 + bid.params.max_duration = 15 + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.imp.video.minduration).to.equal(3); + expect(request.data.imp.video.maxduration).to.equal(15); + }); + + it('should pass placement_type and position params', function() { + var request; + + bid.params.placement_type = 2 + bid.params.position = 5 + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.imp.video.ext.placement).to.equal(2); + expect(request.data.imp.video.ext.pos).to.equal(5); + }); }); describe('interpretResponse', function() { @@ -323,6 +381,7 @@ describe('the spotx adapter', function () { impid: 123, cur: 'USD', price: 12, + adomain: ['abc.com'], crid: 321, w: 400, h: 300, @@ -334,6 +393,7 @@ describe('the spotx adapter', function () { impid: 124, cur: 'USD', price: 13, + adomain: ['def.com'], w: 200, h: 100, ext: { @@ -351,6 +411,7 @@ describe('the spotx adapter', function () { expect(responses).to.be.an('array').with.length(2); expect(responses[0].cache_key).to.equal('cache123'); expect(responses[0].channel_id).to.equal(12345); + expect(responses[0].meta.advertiserDomains[0]).to.equal('abc.com'); expect(responses[0].cpm).to.equal(12); expect(responses[0].creativeId).to.equal(321); expect(responses[0].currency).to.equal('USD'); @@ -360,10 +421,12 @@ describe('the spotx adapter', function () { expect(responses[0].requestId).to.equal(123); expect(responses[0].ttl).to.equal(360); expect(responses[0].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); + expect(responses[0].videoCacheKey).to.equal('cache123'); expect(responses[0].width).to.equal(400); expect(responses[1].cache_key).to.equal('cache124'); expect(responses[1].channel_id).to.equal(12345); expect(responses[1].cpm).to.equal(13); + expect(responses[1].meta.advertiserDomains[0]).to.equal('def.com'); expect(responses[1].creativeId).to.equal(''); expect(responses[1].currency).to.equal('USD'); expect(responses[1].height).to.equal(100); @@ -372,6 +435,7 @@ describe('the spotx adapter', function () { expect(responses[1].requestId).to.equal(124); expect(responses[1].ttl).to.equal(360); expect(responses[1].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache124'); + expect(responses[1].videoCacheKey).to.equal('cache124'); expect(responses[1].width).to.equal(200); }); }); diff --git a/test/spec/modules/staqAnalyticsAdapter_spec.js b/test/spec/modules/staqAnalyticsAdapter_spec.js index 1b35f491854..f8e3ba83bbe 100644 --- a/test/spec/modules/staqAnalyticsAdapter_spec.js +++ b/test/spec/modules/staqAnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ -import analyticsAdapter, { ExpiringQueue, getUmtSource, storage } from 'modules/staqAnalyticsAdapter'; +import analyticsAdapter, { ExpiringQueue, getUmtSource, storage } from 'modules/staqAnalyticsAdapter.js'; import { expect } from 'chai'; -import adapterManager from 'src/adapterManager'; +import adapterManager from 'src/adapterManager.js'; import CONSTANTS from 'src/constants.json'; const events = require('../../../src/events'); diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js index 72ea428304a..c2278313b2d 100644 --- a/test/spec/modules/stvBidAdapter_spec.js +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/stvBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/stvBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const VADS_ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; diff --git a/test/spec/modules/sublimeBidAdapter_spec.js b/test/spec/modules/sublimeBidAdapter_spec.js index 45173b09953..ae9e293a757 100644 --- a/test/spec/modules/sublimeBidAdapter_spec.js +++ b/test/spec/modules/sublimeBidAdapter_spec.js @@ -1,10 +1,30 @@ import { expect } from 'chai'; -import { spec } from 'modules/sublimeBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec, sendEvent, log, setState, state } from 'modules/sublimeBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +let utils = require('src/utils'); describe('Sublime Adapter', function() { const adapter = newBidder(spec); + describe('sendEvent', function() { + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + it('should trigger pixel', function () { + sandbox.spy(utils, 'triggerPixel'); + sendEvent('test', true); + expect(utils.triggerPixel.called).to.equal(true); + }); + + afterEach(function () { + sandbox.restore(); + }); + }) + describe('inherited functions', function() { it('exists and is a function', function() { expect(adapter.callBids).to.exist.and.to.be.a('function'); @@ -129,6 +149,7 @@ describe('Sublime Adapter', function() { currency: 'USD', netRevenue: true, ttl: 600, + pbav: '0.5.2', ad: '', }, ]; @@ -170,6 +191,7 @@ describe('Sublime Adapter', function() { netRevenue: true, ttl: 600, ad: '', + pbav: '0.5.2', }; expect(result[0]).to.deep.equal(expectedResponse); @@ -219,6 +241,7 @@ describe('Sublime Adapter', function() { netRevenue: true, ttl: 600, ad: '', + pbav: '0.5.2', }; expect(result[0]).to.deep.equal(expectedResponse); @@ -250,6 +273,10 @@ describe('Sublime Adapter', function() { let expectedResponse = []; expect(result).to.deep.equal(expectedResponse); + + describe('On bid Time out', function () { + spec.onTimeout(result); + }); }); }); }); diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index 918e4e394bd..1521f4a2e63 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -1,6 +1,7 @@ import { assert, expect } from 'chai'; -import { BANNER } from 'src/mediaTypes'; -import { spec } from 'modules/synacormediaBidAdapter'; +import { BANNER } from 'src/mediaTypes.js'; +import {config} from 'src/config.js'; +import { spec } from 'modules/synacormediaBidAdapter.js'; describe('synacormediaBidAdapter ', function () { describe('isBidRequestValid', function () { @@ -67,9 +68,13 @@ describe('synacormediaBidAdapter ', function () { }, mediaTypes: { banner: { - h: 600, - pos: 0, - w: 300, + format: [ + { + w: 300, + h: 600 + } + ], + pos: 0 } }, }; @@ -170,23 +175,29 @@ describe('synacormediaBidAdapter ', function () { } }; - let expectedDataImp1 = { - banner: { - h: 250, - pos: 0, - w: 300, + let bidderRequestWithCCPA = { + auctionId: 'xyz123', + refererInfo: { + referer: 'https://test.com/foo/bar' }, - id: 'b9876abcd-300x250', - tagid: '1234', - bidfloor: 0.5 + uspConsent: '1YYY' }; - let expectedDataImp2 = { + + let expectedDataImp1 = { banner: { - h: 600, - pos: 0, - w: 300, + format: [ + { + h: 250, + w: 300 + }, + { + h: 600, + w: 300 + } + ], + pos: 0 }, - id: 'b9876abcd-300x600', + id: 'b9876abcd', tagid: '1234', bidfloor: 0.5 }; @@ -200,7 +211,7 @@ describe('synacormediaBidAdapter ', function () { expect(req.url).to.contain('https://prebid.technoratimedia.com/openrtb/bids/prebid?'); expect(req.data).to.exist.and.to.be.an('object'); expect(req.data.id).to.equal('xyz123'); - expect(req.data.imp).to.eql([expectedDataImp1, expectedDataImp2]); + expect(req.data.imp).to.eql([expectedDataImp1]); // video test let reqVideo = spec.buildRequests([validBidRequestVideo], bidderRequestVideo); @@ -229,13 +240,17 @@ describe('synacormediaBidAdapter ', function () { expect(req).to.have.property('url'); expect(req.url).to.contain('https://prebid.technoratimedia.com/openrtb/bids/prebid?'); expect(req.data.id).to.equal('xyz123'); - expect(req.data.imp).to.eql([expectedDataImp1, expectedDataImp2, { + expect(req.data.imp).to.eql([expectedDataImp1, { banner: { - h: 600, - pos: 0, - w: 300, + format: [ + { + h: 600, + w: 300 + } + ], + pos: 0 }, - id: 'bfoobar-300x600', + id: 'bfoobar', tagid: '5678', bidfloor: 0.5 }]); @@ -259,11 +274,15 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.imp).to.eql([ { banner: { - h: 250, - pos: 0, - w: 300, + format: [ + { + h: 250, + w: 300 + } + ], + pos: 0 }, - id: 'bfoobar-300x250', + id: 'bfoobar', tagid: '5678', bidfloor: 0.5 } @@ -288,11 +307,15 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.imp).to.eql([ { banner: { - h: 250, - pos: 0, - w: 300, + format: [ + { + h: 250, + w: 300 + } + ], + pos: 0 }, - id: 'b9876abcd-300x250', + id: 'b9876abcd', tagid: '1234', } ]); @@ -315,11 +338,15 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.imp).to.eql([ { banner: { - h: 250, - pos: 0, - w: 300, + format: [ + { + h: 250, + w: 300 + } + ], + pos: 0 }, - id: 'b9876abcd-300x250', + id: 'b9876abcd', tagid: '1234', } ]); @@ -343,11 +370,15 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.imp).to.eql([ { banner: { - h: 250, - w: 300, - pos: 1, + format: [ + { + h: 250, + w: 300 + } + ], + pos: 1 }, - id: 'b9876abcd-300x250', + id: 'b9876abcd', tagid: '1234' } ]); @@ -370,11 +401,15 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.imp).to.eql([ { banner: { - h: 250, - w: 300, - pos: 0, + format: [ + { + h: 250, + w: 300 + } + ], + pos: 0 }, - id: 'b9876abcd-300x250', + id: 'b9876abcd', tagid: '1234' } ]); @@ -533,26 +568,170 @@ describe('synacormediaBidAdapter ', function () { } ]); }); + it('should contain the CCPA privacy string when UspConsent is in bidder request', function() { + // banner test + let req = spec.buildRequests([validBidRequest], bidderRequestWithCCPA); + expect(req).be.an('object'); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('https://prebid.technoratimedia.com/openrtb/bids/prebid?'); + expect(req.data).to.exist.and.to.be.an('object'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.regs.ext.us_privacy).to.equal('1YYY'); + expect(req.data.imp).to.eql([expectedDataImp1]); + }) + }); + + describe('Bid Requests with schain object ', function() { + let validBidReq = { + bidder: 'synacormedia', + params: { + seatId: 'prebid', + placementId: 'demo1', + pos: 1, + video: {} + }, + renderer: { + url: '../syncOutstreamPlayer.js' + }, + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream' + } + }, + adUnitCode: 'div-1', + transactionId: '0869f34e-090b-4b20-84ee-46ff41405a39', + sizes: [[300, 250]], + bidId: '22b3a2268d9f0e', + bidderRequestId: '1d195910597e13', + auctionId: '3375d336-2aea-4ee7-804c-6d26b621ad20', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } + }; + let bidderRequest = { + refererInfo: { + referer: 'http://localhost:9999/' + }, + bidderCode: 'synacormedia', + auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110', + bidderRequestId: '16d438671bfbec', + bids: [ + { + bidder: 'synacormedia', + params: { + seatId: 'prebid', + placementId: 'demo1', + pos: 1, + video: {} + }, + renderer: { + url: '../syncOutstreamPlayer.js' + }, + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream' + } + }, + adUnitCode: 'div-1', + sizes: [[300, 250]], + bidId: '211c0236bb8f4e', + bidderRequestId: '16d438671bfbec', + auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } + } + ], + auctionStart: 1580310345205, + timeout: 1000, + start: 1580310345211 + }; + + it('should return valid bid request with schain object', function () { + let req = spec.buildRequests([validBidReq], bidderRequest); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?src=$$REPO_AND_VERSION$$'); + expect(req.data).to.have.property('source'); + expect(req.data.source).to.have.property('ext'); + expect(req.data.source.ext).to.have.property('schain'); + }); }); describe('interpretResponse', function () { let bidResponse = { id: '10865933907263896~9998~0', - impid: 'b9876abcd-300x250', + impid: 'b9876abcd', price: 0.13, crid: '1022-250', adm: '', - nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}' + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}', + w: 300, + h: 250 }; let bidResponse2 = { id: '10865933907263800~9999~0', - impid: 'b9876abcd-300x600', + impid: 'b9876abcd', price: 1.99, crid: '9993-013', adm: '', - nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=OTk5OX4wJkFVQ1RJT05fU0VBVF9JR&AUCTION_PRICE=${AUCTION_PRICE}' + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=OTk5OX4wJkFVQ1RJT05fU0VBVF9JR&AUCTION_PRICE=${AUCTION_PRICE}', + w: 300, + h: 600 }; + let bidRequest = { + data: { + id: '', + imp: [ + { + id: 'abc123', + banner: { + format: [ + { + w: 400, + h: 350 + } + ], + pos: 1 + } + } + ], + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; let serverResponse; beforeEach(function() { serverResponse = { @@ -567,6 +746,26 @@ describe('synacormediaBidAdapter ', function () { }); it('should return 1 video bid when 1 bid is in the video response', function () { + bidRequest = { + data: { + id: 'abcd1234', + imp: [ + { + video: { + w: 640, + h: 480 + }, + id: 'v2da7322b2df61f' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; let serverRespVideo = { body: { id: 'abcd1234', @@ -575,14 +774,16 @@ describe('synacormediaBidAdapter ', function () { bid: [ { id: '11339128001692337~9999~0', - impid: 'v2da7322b2df61f-640x480', + impid: 'v2da7322b2df61f', price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', adomain: [ 'psacentral.org' ], cid: 'bidder-crid', crid: 'bidder-cid', - cat: [] + cat: [], + w: 640, + h: 480 } ], seat: '9999' @@ -592,7 +793,7 @@ describe('synacormediaBidAdapter ', function () { }; // serverResponse.body.seatbid[0].bid.push(bidResponse); - let resp = spec.interpretResponse(serverRespVideo); + let resp = spec.interpretResponse(serverRespVideo, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '2da7322b2df61f', @@ -613,7 +814,7 @@ describe('synacormediaBidAdapter ', function () { it('should return 1 bid when 1 bid is in the response', function () { serverResponse.body.seatbid[0].bid.push(bidResponse); - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '9876abcd', @@ -636,7 +837,7 @@ describe('synacormediaBidAdapter ', function () { seat: '9999', bid: [bidResponse2], }); - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(2); expect(resp[0]).to.eql({ requestId: '9876abcd', @@ -668,7 +869,7 @@ describe('synacormediaBidAdapter ', function () { }); it('should not return a bid when no bid is in the response', function () { - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').that.is.empty; }); @@ -676,6 +877,161 @@ describe('synacormediaBidAdapter ', function () { expect(spec.interpretResponse({ body: null })).to.not.exist; expect(spec.interpretResponse({ body: 'some error text' })).to.not.exist; }); + + it('should not include videoCacheKey property on the returned response when cache url is present in the config', function () { + let sandbox = sinon.sandbox.create(); + let serverRespVideo = { + body: { + id: 'abcd1234', + seatbid: [ + { + bid: [ + { + id: '11339128001692337~9999~0', + impid: 'v2da7322b2df61f', + price: 0.45, + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', + adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', + adomain: [ 'psacentral.org' ], + cid: 'bidder-crid', + crid: 'bidder-cid', + cat: [], + w: 640, + h: 480 + } + ], + seat: '9999' + } + ] + } + }; + + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'cache.url': 'faKeCacheUrl' + }; + return config[key]; + }); + + let resp = spec.interpretResponse(serverRespVideo, bidRequest); + sandbox.restore(); + expect(resp[0].videoCacheKey).to.not.exist; + }); + + it('should use video bid request height and width if not present in response', function () { + bidRequest = { + data: { + id: 'abcd1234', + imp: [ + { + video: { + w: 300, + h: 250 + }, + id: 'v2da7322b2df61f' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; + + let serverRespVideo = { + body: { + id: 'abcd1234', + seatbid: [ + { + bid: [ + { + id: '11339128001692337~9999~0', + impid: 'v2da7322b2df61f', + price: 0.45, + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', + adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', + adomain: [ 'psacentral.org' ], + cid: 'bidder-crid', + crid: 'bidder-cid', + cat: [] + } + ], + seat: '9999' + } + ] + } + }; + let resp = spec.interpretResponse(serverRespVideo, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.eql({ + requestId: '2da7322b2df61f', + adId: '11339128001692337-9999-0', + cpm: 0.45, + width: 300, + height: 250, + creativeId: '9999_bidder-cid', + currency: 'USD', + netRevenue: true, + mediaType: 'video', + ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', + ttl: 60, + videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', + vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' + }); + }); + + it('should use banner bid request height and width if not present in response', function () { + bidRequest = { + data: { + id: 'abc123', + imp: [ + { + banner: { + format: [{ + w: 400, + h: 350 + }] + }, + id: 'babc123' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; + + bidResponse = { + id: '10865933907263896~9998~0', + impid: 'babc123', + price: 0.13, + crid: '1022-250', + adm: '', + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}', + }; + + serverResponse.body.seatbid[0].bid.push(bidResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.eql({ + requestId: 'abc123', + adId: '10865933907263896-9998-0', + cpm: 0.13, + width: 400, + height: 350, + creativeId: '9998_1022-250', + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: '', + ttl: 60 + }); + }); }); describe('getUserSyncs', function () { it('should return a usersync when iframes is enabled', function () { diff --git a/test/spec/modules/taphypeBidAdapter_spec.js b/test/spec/modules/taphypeBidAdapter_spec.js index 2fcdd964520..6b36973814e 100644 --- a/test/spec/modules/taphypeBidAdapter_spec.js +++ b/test/spec/modules/taphypeBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/taphypeBidAdapter'; +import { spec } from 'modules/taphypeBidAdapter.js'; describe('taphypeBidAdapterTests', function () { it('validate_pub_params', function () { diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 5809be995dd..17841b271d4 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; -import {spec} from 'modules/teadsBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; +import {spec} from 'modules/teadsBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; const ENDPOINT = 'https://a.teads.tv/hb/bid-request'; const AD_SCRIPT = '"'; @@ -109,6 +109,22 @@ describe('teadsBidAdapter', () => { expect(request.method).to.equal('POST'); }); + it('should send US Privacy to endpoint', function() { + let usPrivacy = 'OHHHFCP1' + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': usPrivacy + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.us_privacy).to.exist; + expect(payload.us_privacy).to.equal(usPrivacy); + }); + it('should send GDPR to endpoint', function() { let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; let bidderRequest = { @@ -120,7 +136,8 @@ describe('teadsBidAdapter', () => { 'gdprApplies': true, 'vendorData': { 'hasGlobalConsent': false - } + }, + 'apiVersion': 1 } }; @@ -130,6 +147,7 @@ describe('teadsBidAdapter', () => { expect(payload.gdpr_iab).to.exist; expect(payload.gdpr_iab.consent).to.equal(consentString); expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(1); }); it('should add referer info to payload', function () { @@ -148,6 +166,57 @@ describe('teadsBidAdapter', () => { expect(payload.referrer).to.deep.equal('https://example.com/page.html') }); + it('should add networkBandwidth info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderResquestDefault); + const payload = JSON.parse(request.data); + + const bandwidth = window.navigator && window.navigator.connection && window.navigator.connection.downlink; + + expect(payload.networkBandwidth).to.exist; + + if (bandwidth) { + expect(payload.networkBandwidth).to.deep.equal(bandwidth.toString()); + } else { + expect(payload.networkBandwidth).to.deep.equal(''); + } + }); + + it('should add pageReferrer info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageReferrer).to.exist; + expect(payload.pageReferrer).to.deep.equal(document.referrer); + }); + + it('should add timeToFirstByte info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderResquestDefault); + const payload = JSON.parse(request.data); + const performance = window.performance || window.webkitPerformance || window.msPerformance || window.mozPerformance; + + const ttfbExpectedV2 = performance && + typeof performance.getEntriesByType === 'function' && + Object.prototype.toString.call(performance.getEntriesByType) === '[object Function]' && + performance.getEntriesByType('navigation')[0] && + performance.getEntriesByType('navigation')[0].responseStart && + performance.getEntriesByType('navigation')[0].requestStart && + performance.getEntriesByType('navigation')[0].responseStart >= 0 && + performance.getEntriesByType('navigation')[0].requestStart >= 0 && + Math.round( + performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart + ); + + expect(payload.timeToFirstByte).to.exist; + + if (ttfbExpectedV2) { + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpectedV2.toString()); + } else { + const ttfbExpectedV1 = performance.timing.responseStart - performance.timing.requestStart; + + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpectedV1.toString()); + } + }); + it('should send GDPR to endpoint with 11 status', function() { let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; let bidderRequest = { @@ -159,7 +228,8 @@ describe('teadsBidAdapter', () => { 'gdprApplies': true, 'vendorData': { 'hasGlobalScope': true - } + }, + 'apiVersion': 1 } }; @@ -169,6 +239,32 @@ describe('teadsBidAdapter', () => { expect(payload.gdpr_iab).to.exist; expect(payload.gdpr_iab.consent).to.equal(consentString); expect(payload.gdpr_iab.status).to.equal(11); + expect(payload.gdpr_iab.apiVersion).to.equal(1); + }); + + it('should send GDPR TCF2 to endpoint with 12 status', function() { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': true + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(2); }); it('should send GDPR to endpoint with 22 status', function() { @@ -180,7 +276,8 @@ describe('teadsBidAdapter', () => { 'gdprConsent': { 'consentString': undefined, 'gdprApplies': undefined, - 'vendorData': undefined + 'vendorData': undefined, + 'apiVersion': 1 } }; @@ -190,6 +287,7 @@ describe('teadsBidAdapter', () => { expect(payload.gdpr_iab).to.exist; expect(payload.gdpr_iab.consent).to.equal(''); expect(payload.gdpr_iab.status).to.equal(22); + expect(payload.gdpr_iab.apiVersion).to.equal(1); }); it('should send GDPR to endpoint with 0 status', function() { @@ -203,7 +301,8 @@ describe('teadsBidAdapter', () => { 'gdprApplies': false, 'vendorData': { 'hasGlobalScope': false - } + }, + 'apiVersion': 1 } }; @@ -213,6 +312,55 @@ describe('teadsBidAdapter', () => { expect(payload.gdpr_iab).to.exist; expect(payload.gdpr_iab.consent).to.equal(consentString); expect(payload.gdpr_iab.status).to.equal(0); + expect(payload.gdpr_iab.apiVersion).to.equal(1); + }); + + it('should send GDPR to endpoint with 0 status when gdprApplies = false (vendorData = undefined)', function() { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': undefined, + 'gdprApplies': false, + 'vendorData': undefined, + 'apiVersion': 1 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(''); + expect(payload.gdpr_iab.status).to.equal(0); + expect(payload.gdpr_iab.apiVersion).to.equal(1); + }); + + it('should send GDPR to endpoint with 12 status when apiVersion = 0', function() { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'hasGlobalScope': false + }, + 'apiVersion': 0 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(0); }); it('should use good mediaTypes video playerSizes', function() { @@ -306,39 +454,63 @@ describe('teadsBidAdapter', () => { }); describe('interpretResponse', function() { - let bids = { - 'body': { - 'responses': [{ - 'ad': AD_SCRIPT, + it('should get correct bid responses', function() { + let bids = { + 'body': { + 'responses': [{ + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 250, + 'bidId': '3ede2a3fa0db94', + 'ttl': 360, + 'width': 300, + 'creativeId': 'er2ee', + 'placementId': 34 + }, { + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 200, + 'bidId': '4fef3b4gb1ec15', + 'ttl': 360, + 'width': 350, + 'creativeId': 'fs3ff', + 'placementId': 34, + 'dealId': 'ABC_123' + }] + } + }; + let expectedResponse = [ + { 'cpm': 0.5, - 'currency': 'USD', + 'width': 300, 'height': 250, + 'currency': 'USD', 'netRevenue': true, - 'bidId': '3ede2a3fa0db94', 'ttl': 360, - 'width': 300, + 'ad': AD_SCRIPT, + 'requestId': '3ede2a3fa0db94', 'creativeId': 'er2ee', 'placementId': 34 - }] - } - }; - - it('should get correct bid response', function() { - let expectedResponse = { - 'cpm': 0.5, - 'width': 300, - 'height': 250, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360, - 'ad': AD_SCRIPT, - 'requestId': '3ede2a3fa0db94', - 'creativeId': 'er2ee', - 'placementId': 34 - }; + }, { + 'cpm': 0.5, + 'width': 350, + 'height': 200, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + 'ad': AD_SCRIPT, + 'requestId': '4fef3b4gb1ec15', + 'creativeId': 'fs3ff', + 'placementId': 34, + 'dealId': 'ABC_123' + } + ] + ; let result = spec.interpretResponse(bids); - expect(result[0]).to.deep.equal(expectedResponse); + expect(result).to.eql(expectedResponse); }); it('handles nobid responses', function() { @@ -352,71 +524,4 @@ describe('teadsBidAdapter', () => { expect(result.length).to.equal(0); }); }); - - it('should call userSync with good params', function() { - let bids = [{ - 'body': { - 'responses': [{ - 'ad': '', + 'ttl': 360, + 'creativeId': '5cb5dc9375c0e', + 'netRevenue': true, + 'currency': 'EUR', + 'sync': ['https:\/\/SYNC_URL'] + }] + } + }; + + it('should handle response', function() { + let expectedResponse = [ + { + cpm: 10.0, + width: '1', + height: '1', + currency: 'EUR', + netRevenue: true, + ttl: 360, + ad: '', + requestId: '242d506d4e4f15', + creativeId: '5cb5dc9375c0e' + } + ]; + + let result = spec.interpretResponse(serverResponse); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('should handles empty response', function() { + let serverResponse = { + 'body': { + 'responses': [] + } + }; + + let result = spec.interpretResponse(serverResponse); + expect(result.length).to.equal(0); + }); + + describe('getUserSyncs', () => { + it('should push user sync images if enabled', () => { + const syncOptions = { pixelEnabled: true }; + const syncs = spec.getUserSyncs(syncOptions, [serverResponse]); + + expect(syncs[0]).to.deep.equal({ + type: 'image', + url: 'https://SYNC_URL' + }); + }) + }); + }); +}); diff --git a/test/spec/modules/viewdeosDXBidAdapter_spec.js b/test/spec/modules/viewdeosDXBidAdapter_spec.js index 80082347687..f9bee1b0efe 100644 --- a/test/spec/modules/viewdeosDXBidAdapter_spec.js +++ b/test/spec/modules/viewdeosDXBidAdapter_spec.js @@ -1,8 +1,8 @@ import {expect} from 'chai'; -import {spec} from 'modules/viewdeosDXBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; +import {spec} from 'modules/viewdeosDXBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; -const ENDPOINT = '//hb.sync.viewdeos.com/auction/'; +const ENDPOINT = 'https://ghb.sync.viewdeos.com/auction/'; const DISPLAY_REQUEST = { 'bidder': 'viewdeos', @@ -43,6 +43,7 @@ const SERVER_VIDEO_RESPONSE = { } ] }; +const SERVER_OUSTREAM_VIDEO_RESPONSE = SERVER_VIDEO_RESPONSE; const SERVER_DISPLAY_RESPONSE = { 'source': {'aid': 12345, 'pubId': 54321}, @@ -74,6 +75,25 @@ const SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS = { 'cookieURLSTypes': ['image', 'iframe'] }; +const outstreamVideoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{ + 'params': { + 'aid': 12345, + 'outstream': { + 'video_controls': 'show' + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + bidId: '2e41f65424c87c' + }] +}; + const videoBidderRequest = { bidderCode: 'bidderCode', bids: [{mediaTypes: {video: {}}, bidId: '2e41f65424c87c'}] @@ -119,9 +139,17 @@ const displayEqResponse = [{ cpm: 0.9 }]; -describe('viewdeosDXBidAdapter', function () { // todo remove only +describe('viewdeosDXBidAdapter', function () { const adapter = newBidder(spec); + describe('when outstream params are passing properly', function() { + const videoBids = spec.interpretResponse({body: SERVER_OUSTREAM_VIDEO_RESPONSE}, {bidderRequest: outstreamVideoBidderRequest}); + it('should return renderer with expected outstream params config', function() { + expect(!!videoBids[0].renderer).to.equal(true); + expect(videoBids[0].renderer.getConfig().video_controls).to.equal('show'); + }) + }) + describe('user syncs as image', function () { it('should be returned if pixel enabled', function () { const syncs = spec.getUserSyncs({pixelEnabled: true}, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index b14f5331fd1..2a4f8e4c7b5 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import { spec } from 'modules/visxBidAdapter'; -import { config } from 'src/config'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/visxBidAdapter.js'; +import { config } from 'src/config.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('VisxAdapter', function () { const adapter = newBidder(spec); @@ -46,6 +46,14 @@ describe('VisxAdapter', function () { } }; const referrer = bidderRequest.refererInfo.referer; + const schainObject = { + ver: '1.0', + nodes: [ + {asi: 'exchange2.com', sid: 'abcd', hp: 1}, + {asi: 'exchange1.com', sid: '1234!abcd', hp: 1, name: 'publisher, Inc.', domain: 'publisher.com'} + ] + }; + const schainString = JSON.stringify(schainObject); let bidRequests = [ { 'bidder': 'visx', @@ -200,6 +208,42 @@ describe('VisxAdapter', function () { expect(payload).to.have.property('gdpr_consent', 'AAA'); expect(payload).to.have.property('gdpr_applies', 1); }); + + it('if schain is present payload must have schain param', function () { + const schainBidRequests = [ + Object.assign({schain: schainObject}, bidRequests[0]), + bidRequests[1], + bidRequests[2] + ]; + const request = spec.buildRequests(schainBidRequests, bidderRequest); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('schain', schainString); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '903535,903535,903536'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('cur', 'EUR'); + }); + + it('if userId is available payload must have appropriate params', function () { + const schainBidRequests = [ + Object.assign({userId: { + tdid: '111', + id5id: '222', + digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}} + }}, bidRequests[0]), + bidRequests[1], + bidRequests[2] + ]; + const request = spec.buildRequests(schainBidRequests, bidderRequest); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('tdid', '111'); + expect(payload).to.have.property('id5', '222'); + expect(payload).to.have.property('dtid', 'DTID'); + }); }); describe('interpretResponse', function () { @@ -238,7 +282,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -295,7 +338,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -308,7 +350,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -321,7 +362,6 @@ describe('VisxAdapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -357,7 +397,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'visx', 'currency': 'PLN', 'netRevenue': true, 'ttl': 360, @@ -487,7 +526,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -500,7 +538,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -513,7 +550,6 @@ describe('VisxAdapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -526,7 +562,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 4
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -539,7 +574,6 @@ describe('VisxAdapter', function () { 'width': 350, 'height': 600, 'ad': '
test content 5
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -600,7 +634,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -613,7 +646,6 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 2
', - 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, diff --git a/test/spec/modules/vmgBidAdapter_spec.js b/test/spec/modules/vmgBidAdapter_spec.js index 688c03577fd..41aa077adc7 100644 --- a/test/spec/modules/vmgBidAdapter_spec.js +++ b/test/spec/modules/vmgBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/vmgBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/vmgBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; describe('VmgAdapter', function () { const adapter = newBidder(spec); diff --git a/test/spec/modules/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js index 7b37e393575..729b0a723e4 100644 --- a/test/spec/modules/vrtcalBidAdapter_spec.js +++ b/test/spec/modules/vrtcalBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from '../../../modules/vrtcalBidAdapter'; +import {spec} from '../../../modules/vrtcalBidAdapter.js'; describe('Vrtcal Adapter', function () { let bid = { diff --git a/test/spec/modules/vubleAnalyticsAdapter_spec.js b/test/spec/modules/vubleAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/spec/modules/vubleBidAdapter_spec.js b/test/spec/modules/vubleBidAdapter_spec.js index b00dbca9b01..2f0d1c1e262 100644 --- a/test/spec/modules/vubleBidAdapter_spec.js +++ b/test/spec/modules/vubleBidAdapter_spec.js @@ -1,8 +1,8 @@ // 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'; +import {spec as adapter} from 'modules/vubleBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('VubleAdapter', function () { describe('Check methods existance', function () { @@ -202,7 +202,27 @@ describe('VubleAdapter', function () { adUnitCode: '' } }; - let bidderRequest = { + let request4 = { + method: 'POST', + url: 'https://player.mediabong.net/prebid/request', + data: { + width: '640', + height: '360', + pub_id: '3', + zone_id: '3579', + context: 'instream', + floor_price: 0, + url: '', + env: 'net', + bid_id: 'ijkl', + adUnitCode: '', + gdpr_consent: { + consent_string: 'test', + gdpr_applies: true + } + } + }; + let bidderRequest1 = { refererInfo: { referer: 'https://www.vuble.tv/', reachedTop: true, @@ -214,10 +234,17 @@ describe('VubleAdapter', function () { ] } }; + let bidderRequest2 = { + 'gdprConsent': { + consentString: 'test', + gdprApplies: true + } + }; it('must return the right formatted requests', function () { expect(adapter.buildRequests([bid1, bid2])).to.deep.equal([request1, request2]); - expect(adapter.buildRequests([bid3], bidderRequest)).to.deep.equal([request3]); + expect(adapter.buildRequests([bid3], bidderRequest1)).to.deep.equal([request3]); + expect(adapter.buildRequests([bid3], bidderRequest2)).to.deep.equal([request4]); }); }); diff --git a/test/spec/modules/waardexBidAdapter_spec.js b/test/spec/modules/waardexBidAdapter_spec.js new file mode 100644 index 00000000000..8732b2bd51f --- /dev/null +++ b/test/spec/modules/waardexBidAdapter_spec.js @@ -0,0 +1,183 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/waardexBidAdapter.js'; +import { auctionManager } from 'src/auctionManager.js'; +import { deepClone } from 'src/utils.js'; + +describe('waardexBidAdapter', () => { + const validBid = { + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + } + }; + + describe('isBidRequestValid', () => { + it('Should return true. bidId and params such as placementId and zoneId are present', () => { + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + it('Should return false. bidId is not present in bid object', () => { + const invalidBid = deepClone(validBid); + delete invalidBid.bidId; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + it('Should return false. zoneId is not present in bid.params object', () => { + const invalidBid = deepClone(validBid); + delete invalidBid.params.zoneId; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', () => { + let getAdUnitsStub; + const validBidRequests = [{ + bidId: 'fergr675ujgh', + mediaTypes: { + banner: { + sizes: [[300, 600], [300, 250]] + } + }, + params: { + bidfloor: 1.5, + position: 1, + instl: 1, + zoneId: 100 + }, + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://www.google.com/?some_param=some_value' + }, + }; + + beforeEach(() => getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(() => [])); + afterEach(() => getAdUnitsStub.restore()); + + it('should return valid build request object', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { + data: payload, + url, + method, + } = request; + + expect(payload.bidRequests[0]).deep.equal({ + bidId: validBidRequests[0].bidId, + bidfloor: validBidRequests[0].params.bidfloor, + position: validBidRequests[0].params.position, + instl: validBidRequests[0].params.instl, + banner: { + sizes: [ + { + width: validBidRequests[0].mediaTypes.banner.sizes[0][0], + height: validBidRequests[0].mediaTypes.banner.sizes[0][1] + }, + { + width: validBidRequests[0].mediaTypes.banner.sizes[1][0], + height: validBidRequests[0].mediaTypes.banner.sizes[1][1] + }, + ], + } + }); + const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid?pubId=${validBidRequests[0].params.zoneId}`; + expect(url).to.equal(ENDPOINT); + expect(method).to.equal('POST'); + }); + }); + + describe('interpretResponse', () => { + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + id: 'someId', + price: 3.3, + w: 250, + h: 300, + crid: 'dspCreativeIdHere', + adm: 'html markup here', + dealId: '123456789', + cid: 'dsp campaign id', + adomain: 'advertisers domain', + ext: { + mediaType: 'banner', + }, + }], + }], + }, + }; + + it('bid response is valid', () => { + const result = spec.interpretResponse(serverResponse); + const expected = [{ + requestId: serverResponse.body.seatbid[0].bid[0].id, + cpm: serverResponse.body.seatbid[0].bid[0].price, + currency: 'USD', + width: serverResponse.body.seatbid[0].bid[0].w, + height: serverResponse.body.seatbid[0].bid[0].h, + creativeId: serverResponse.body.seatbid[0].bid[0].crid, + netRevenue: true, + ttl: 3000, + ad: serverResponse.body.seatbid[0].bid[0].adm, + dealId: serverResponse.body.seatbid[0].bid[0].dealid, + meta: { + cid: serverResponse.body.seatbid[0].bid[0].cid, + adomain: serverResponse.body.seatbid[0].bid[0].adomain, + mediaType: serverResponse.body.seatbid[0].bid[0].ext.mediaType, + }, + }]; + expect(result).deep.equal(expected); + }); + + it('invalid bid response. requestId is not exists in bid response', () => { + const invalidServerResponse = deepClone(serverResponse); + delete invalidServerResponse.body.seatbid[0].bid[0].id; + + const result = spec.interpretResponse(invalidServerResponse); + expect(result).deep.equal([]); + }); + + it('invalid bid response. cpm is not exists in bid response', () => { + const invalidServerResponse = deepClone(serverResponse); + delete invalidServerResponse.body.seatbid[0].bid[0].price; + + const result = spec.interpretResponse(invalidServerResponse); + expect(result).deep.equal([]); + }); + + it('invalid bid response. creativeId is not exists in bid response', () => { + const invalidServerResponse = deepClone(serverResponse); + delete invalidServerResponse.body.seatbid[0].bid[0].crid; + + const result = spec.interpretResponse(invalidServerResponse); + expect(result).deep.equal([]); + }); + + it('invalid bid response. width is not exists in bid response', () => { + const invalidServerResponse = deepClone(serverResponse); + delete invalidServerResponse.body.seatbid[0].bid[0].w; + + const result = spec.interpretResponse(invalidServerResponse); + expect(result).deep.equal([]); + }); + + it('invalid bid response. height is not exists in bid response', () => { + const invalidServerResponse = deepClone(serverResponse); + delete invalidServerResponse.body.seatbid[0].bid[0].h; + + const result = spec.interpretResponse(invalidServerResponse); + expect(result).deep.equal([]); + }); + + it('invalid bid response. ad is not exists in bid response', () => { + const invalidServerResponse = deepClone(serverResponse); + delete invalidServerResponse.body.seatbid[0].bid[0].adm; + + const result = spec.interpretResponse(invalidServerResponse); + expect(result).deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/welectBidAdapter_spec.js b/test/spec/modules/welectBidAdapter_spec.js new file mode 100644 index 00000000000..2b929be5586 --- /dev/null +++ b/test/spec/modules/welectBidAdapter_spec.js @@ -0,0 +1,205 @@ +import {expect} from 'chai'; +import {spec as adapter} from 'modules/welectBidAdapter.js'; + +describe('WelectAdapter', function () { + describe('Check methods existance', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + }); + + describe('Check method isBidRequestValid return', function () { + let bid = { + bidder: 'welect', + params: { + placementId: 'exampleAlias', + domain: 'www.welect.de' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + }; + let bid2 = { + bidder: 'welect', + params: { + domain: 'www.welect.de' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360] + } + }, + }; + + it('should be true', function () { + expect(adapter.isBidRequestValid(bid)).to.be.true; + }); + + it('should be false because the placementId is missing', function () { + expect(adapter.isBidRequestValid(bid2)).to.be.false; + }); + }); + + describe('Check buildRequests method', function () { + // Bids to be formatted + let bid1 = { + bidder: 'welect', + params: { + placementId: 'exampleAlias' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc' + }; + let bid2 = { + bidder: 'welect', + params: { + placementId: 'exampleAlias', + domain: 'www.welect2.de' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc', + gdprConsent: { + gdprApplies: 1, + gdprConsent: 'some_string' + } + }; + + let data1 = { + bid_id: 'abdc', + width: 640, + height: 360 + } + + let data2 = { + bid_id: 'abdc', + width: 640, + height: 360, + gdpr_consent: { + gdpr_applies: 1, + gdpr_consent: 'some_string' + } + } + + // Formatted requets + let request1 = { + method: 'POST', + url: 'https://www.welect.de/api/v2/preflight/by_alias/exampleAlias', + data: data1, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + }; + + let request2 = { + method: 'POST', + url: 'https://www.welect2.de/api/v2/preflight/by_alias/exampleAlias', + data: data2, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + } + + it('defaults to www.welect.de, without gdpr object', function () { + expect(adapter.buildRequests([bid1])).to.deep.equal([request1]); + }) + + it('must return the right formatted requests, with gdpr object', function () { + expect(adapter.buildRequests([bid2])).to.deep.equal([request2]); + }); + }); + + describe('Check interpretResponse method return', function () { + // invalid server response + let unavailableResponse = { + body: { + available: false + } + }; + + let availableResponse = { + body: { + available: true, + bidResponse: { + ad: { + video: 'some vast url' + }, + cpm: 17, + creativeId: 'svmpreview', + currency: 'EUR', + netRevenue: true, + requestId: 'some bid id', + ttl: 120, + vastUrl: 'some vast url', + height: 640, + width: 320 + } + } + } + // bid Request + let bid = { + data: { + bid_id: 'some bid id', + width: 640, + height: 320, + gdpr_consent: { + gdpr_applies: 1, + gdpr_consent: 'some_string' + } + }, + method: 'POST', + url: 'https://www.welect.de/api/v2/preflight/by_alias/exampleAlias', + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + }; + // Formatted reponse + let result = { + ad: { + video: 'some vast url' + }, + cpm: 17, + creativeId: 'svmpreview', + currency: 'EUR', + height: 640, + netRevenue: true, + requestId: 'some bid id', + ttl: 120, + vastUrl: 'some vast url', + width: 320 + } + + it('if response reflects unavailability, should be empty', function () { + expect(adapter.interpretResponse(unavailableResponse, bid)).to.deep.equal([]); + }); + + it('if response reflects availability, should equal result', function() { + expect(adapter.interpretResponse(availableResponse, bid)).to.deep.equal([result]) + }) + }); +}); diff --git a/test/spec/modules/widespaceBidAdapter_spec.js b/test/spec/modules/widespaceBidAdapter_spec.js new file mode 100644 index 00000000000..382bf2c593e --- /dev/null +++ b/test/spec/modules/widespaceBidAdapter_spec.js @@ -0,0 +1,265 @@ +import {expect} from 'chai'; +import {spec, storage} from 'modules/widespaceBidAdapter.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +describe('+widespaceAdatperTest', function () { + // 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 bidderRequest object + const bidderRequest = { + auctionId: 'bf1e57ee-fff2-4304-8143-91aaf423a944', + auctionStart: 1527418994278, + bidderCode: 'widespace', + bidderRequestId: '37a5f053efef34', + timeout: 3000, + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + vendorData: { + hasGlobalScope: false + } + } + }; + + // Dummy bid response with ad code + const bidResponse = { + body: [{ + 'adId': '12345', + 'bidId': '67890', + 'code': '
', + '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', function () { + it('bidRequest with sid and currency params', function () { + expect(spec.isBidRequestValid({ + bidder: 'widespace', + params: { + sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad3', + currency: 'EUR' + } + })).to.equal(true); + }); + + it('-bidRequest with missing sid', function () { + expect(spec.isBidRequestValid({ + bidder: 'widespace', + params: { + currency: 'EUR' + } + })).to.equal(false); + }); + + it('-bidRequest with missing currency', function () { + expect(spec.isBidRequestValid({ + bidder: 'widespace', + params: { + sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad3' + } + })).to.equal(true); + }); + }); + + describe('+bidRequest', function () { + let request; + const UrlRegExp = /^((ftp|http|https):)?\/\/[^ "]+$/; + before(function() { + request = spec.buildRequests(bidRequest, bidderRequest); + }) + + let fakeLocalStorage = {}; + let lsSetStub; + let lsGetStub; + let lsRemoveStub; + + beforeEach(function () { + lsSetStub = sinon.stub(storage, 'setDataInLocalStorage').callsFake(function (name, value) { + fakeLocalStorage[name] = value; + }); + + lsGetStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake(function (key) { + return fakeLocalStorage[key] || null; + }); + + lsRemoveStub = sinon.stub(storage, 'removeDataFromLocalStorage').callsFake(function (key) { + if (key && (fakeLocalStorage[key] !== null || fakeLocalStorage[key] !== undefined)) { + delete fakeLocalStorage[key]; + } + return true; + }); + }); + + afterEach(function () { + lsSetStub.restore(); + lsGetStub.restore(); + lsRemoveStub.restore(); + fakeLocalStorage = {}; + }); + + it('-bidRequest method is POST', function () { + expect(request[0].method).to.equal('POST'); + }); + + it('-bidRequest url is valid', function () { + expect(UrlRegExp.test(request[0].url)).to.equal(true); + }); + + it('-bidRequest data exist', function () { + expect(request[0].data).to.exist; + }); + + it('-bidRequest data is form data', function () { + expect(typeof request[0].data).to.equal('string'); + }); + + it('-bidRequest options have header type', function () { + expect(request[0].options.contentType).to.exist; + }); + + it('-cookie test for wsCustomData ', function () { + expect(request[0].data.indexOf('hb.cd') > -1).to.equal(true); + }); + }); + + describe('+interpretResponse', function () { + it('-required params available in response', function () { + 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('-empty result if noad responded', function () { + const noAdResult = spec.interpretResponse(bidResponseNoAd, bidRequest); + expect(noAdResult.length).to.equal(0); + }); + + it('-empty response should not breake anything in adapter', function () { + const noResponse = spec.interpretResponse({}, bidRequest); + expect(noResponse.length).to.equal(0); + }); + }); + + describe('+getUserSyncs', function () { + it('-always return an array', function () { + const userSync_test1 = spec.getUserSyncs({}, [bidResponse]); + expect(Array.isArray(userSync_test1)).to.equal(true); + + const userSync_test2 = spec.getUserSyncs({}, [bidResponseNoAd]); + expect(Array.isArray(userSync_test2)).to.equal(true); + + const userSync_test3 = spec.getUserSyncs({}, [bidResponse, bidResponseNoAd]); + expect(Array.isArray(userSync_test3)).to.equal(true); + + const userSync_test4 = spec.getUserSyncs(); + expect(Array.isArray(userSync_test4)).to.equal(true); + + const userSync_test5 = spec.getUserSyncs({}, []); + expect(Array.isArray(userSync_test5)).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/windtalkerBidAdapter_spec.js b/test/spec/modules/windtalkerBidAdapter_spec.js new file mode 100644 index 00000000000..222a7611b01 --- /dev/null +++ b/test/spec/modules/windtalkerBidAdapter_spec.js @@ -0,0 +1,348 @@ +import {expect} from 'chai'; +import {spec} from 'modules/windtalkerBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('Windtalker Adapter Tests', function () { + const slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidId: 'bid12345', + mediaType: 'banner', + params: { + pubId: '29521', + siteId: '26047', + placementId: '123', + bidFloor: '0.001', + ifa: 'IFA', + latitude: '40.712775', + longitude: '-74.005973' + } + }, { + placementCode: '/DfpAccount2/slot2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bidId: 'bid23456', + mediaType: 'banner', + params: { + pubId: '29521', + siteId: '26047', + placementId: '1234', + 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', + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + }, + bidId: 'bid12345678', + mediaType: 'video', + video: { + skippable: true + }, + params: { + pubId: '29521', + placementId: '1234567', + siteId: '26047', + } + }]; + const appSlotConfig = [{ + placementCode: '/DfpAccount1/slot5', + bidId: 'bid12345', + params: { + pubId: '29521', + placementId: '1234', + app: { + id: '1111', + name: 'app name', + bundle: 'io.windtalker.apps', + storeUrl: 'https://windtalker.io/apps', + domain: 'windtalker.io' + } + } + }]; + + it('Verify build request', function () { + const request = spec.buildRequests(slotConfigs); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + 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(window.location.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'); + }); + + it('Verify parse response', function () { + 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', + w: 300, + h: 250 + }] + }], + 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); + }); + + it('Verify full passback', function () { + const request = spec.buildRequests(slotConfigs); + const bids = spec.interpretResponse({ body: null }, request) + expect(bids).to.have.lengthOf(0); + }); + + it('Verify Native request', function () { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + 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); + }); + + it('Verify Native response', function () { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + 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://adx1public.s3.amazonaws.com/creatives_icon.png', w: 100, h: 100 } }, + { id: 5, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_image.png', w: 300, h: 300 } } + ], + link: { url: 'https://brand.com/' } + } + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + nurl: 'https://rtb.adx1.com/log', + adm: JSON.stringify(nativeResponse) + }] + }], + 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://adx1public.s3.amazonaws.com/creatives_icon.png'); + expect(nativeBid.image.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_image.png'); + expect(nativeBid.image.width).to.equal(300); + expect(nativeBid.image.height).to.equal(300); + expect(nativeBid.icon.width).to.equal(100); + expect(nativeBid.icon.height).to.equal(100); + expect(nativeBid.clickUrl).to.equal(encodeURIComponent('https://brand.com/')); + expect(nativeBid.impressionTrackers).to.have.lengthOf(1); + expect(nativeBid.impressionTrackers[0]).to.equal('https://rtb.adx1.com/log'); + }); + + it('Verify Video request', function () { + const request = spec.buildRequests(videoSlotConfig); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + 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(window.location.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); + }); + + it('Verify parse video response', function () { + const request = spec.buildRequests(videoSlotConfig); + const videoRequest = JSON.parse(request.data); + const videoResponse = { + seatbid: [{ + bid: [{ + impid: videoRequest.imp[0].id, + price: 1.90, + adm: 'https://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('https://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); + }); + + it('Verifies bidder code', function () { + expect(spec.code).to.equal('windtalker'); + }); + + it('Verifies supported media types', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(3); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('native'); + expect(spec.supportedMediaTypes[2]).to.equal('video'); + }); + + it('Verifies if bid request valid', function () { + 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); + }); + + it('Verify app requests', function () { + 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.windtalker.apps'); + expect(ortbRequest.app.storeurl).to.equal('https://windtalker.io/apps'); + expect(ortbRequest.app.domain).to.equal('windtalker.io'); + }); + + it('Verify GDPR', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'serialized_gpdr_data' + } + }; + const request = spec.buildRequests(slotConfigs, bidderRequest); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + 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'); + 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/wipesBidAdapter_spec.js b/test/spec/modules/wipesBidAdapter_spec.js new file mode 100644 index 00000000000..c453eca82c5 --- /dev/null +++ b/test/spec/modules/wipesBidAdapter_spec.js @@ -0,0 +1,150 @@ +import {expect} from 'chai'; +import {spec} from 'modules/wipesBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +const ENDPOINT_URL = 'https://adn-srv.reckoner-api.com/v1/prebid'; + +describe('wipesBidAdapter', function () { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'wipes', + 'params': { + asid: 'dWyPondh2EGB_bNlrVjzIXRZO9F0k1dpo0I8ZvQ' + }, + 'adUnitCode': 'adunit-code', + 'bidId': '51ef8751f9aead', + 'bidderRequestId': '15246a574e859f', + 'auctionId': 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when asid not passed correctly', function () { + bid.params.asid = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'wipes', + 'params': { + asid: 'dWyPondh2EGB_bNlrVjzIXRZO9F0k1dpo0I8ZvQ' + }, + 'adUnitCode': 'adunit-code', + 'bidId': '51ef8751f9aead', + 'bidderRequestId': '15246a574e859f', + 'auctionId': 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + }, + { + 'bidder': 'wipes', + 'params': { + asid: 'dWyPondh2EGB_bNlrVjzIXRZO9F0k1dpo0I8ZvQ' + }, + 'adUnitCode': 'adunit-code2', + 'bidId': '51ef8751f9aead', + 'bidderRequestId': '15246a574e859f', + 'auctionId': 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + } + ]; + + let bidderRequest = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://example.com', + stack: ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('sends bid request to our endpoint via GET', function () { + 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', function () { + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[1].url).to.equal(ENDPOINT_URL); + }); + + it('adUnitCode should be sent as uc parameters on any requests', function () { + expect(request[0].data.asid).to.equal('dWyPondh2EGB_bNlrVjzIXRZO9F0k1dpo0I8ZvQ'); + expect(request[1].data.asid).to.equal('dWyPondh2EGB_bNlrVjzIXRZO9F0k1dpo0I8ZvQ'); + }); + }); + + describe('interpretResponse', function () { + let bidRequestVideo = [ + { + 'method': 'GET', + 'url': ENDPOINT_URL, + 'data': { + 'asid': 'dWyPondh2EGB_bNlrVjzIXRZO9F0k1dpo0I8ZvQ', + 'bid_id': '23beaa6af6cdde', + } + } + ]; + + let serverResponseVideo = { + body: { + 'uuid': 'a42947f8-f8fd-4cf7-bb72-31a87ab1f6ff', + 'ad_tag': '', + 'height': 160, + 'width': 300, + 'cpm': 850, + 'status_message': '', + 'currency': 'JPY', + 'video_creative_id': 600004, + 'bid_id': '23beaa6af6cdde' + } + }; + + it('should get the correct bid response for video', function () { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 850, + 'width': 300, + 'height': 160, + 'creativeId': '600004', + 'dealId': undefined, + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 3000, + 'referrer': '', + 'mediaType': 'banner', + 'ad': '' + }]; + let result = spec.interpretResponse(serverResponseVideo, bidRequestVideo[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].mediaType).to.equal(expectedResponse[0].mediaType); + }); + + it('handles empty bid response', function () { + let response = { + body: { + 'uid': 'a42947f8-f8fd-4cf7-bb72-31a87ab1f6ff', + 'height': 0, + 'crid': '', + 'statusMessage': '', + 'width': 0, + 'cpm': 0 + } + }; + let result = spec.interpretResponse(response, bidRequestVideo[0]); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/xhbBidAdapter_spec.js b/test/spec/modules/xhbBidAdapter_spec.js index 004bdcc4b54..a12abc74c64 100644 --- a/test/spec/modules/xhbBidAdapter_spec.js +++ b/test/spec/modules/xhbBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import { spec } from 'modules/xhbBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { deepClone } from 'src/utils'; +import { spec } from 'modules/xhbBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { deepClone } from 'src/utils.js'; const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index c8709969e00..1e20343c1cd 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai' -import { spec } from 'modules/yieldlabBidAdapter' -import { newBidder } from 'src/adapters/bidderFactory' +import { spec } from 'modules/yieldlabBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' const REQUEST = { 'bidder': 'yieldlab', @@ -10,7 +10,12 @@ const REQUEST = { 'adSize': '728x90', 'targeting': { 'key1': 'value1', - 'key2': 'value2' + 'key2': 'value2', + 'notDoubleEncoded': 'value3,value4' + }, + 'customParams': { + 'extraParam': true, + 'foo': 'bar' }, 'extId': 'abc' }, @@ -18,7 +23,14 @@ const REQUEST = { 'auctionId': '2e41f65424c87c', 'adUnitCode': 'adunit-code', 'bidId': '2d925f27f5079f', - 'sizes': [728, 90] + 'sizes': [728, 90], + 'userIdAsEids': [{ + 'source': 'netid.de', + 'uids': [{ + 'id': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', + 'atype': 1 + }] + }] } const RESPONSE = { @@ -27,9 +39,14 @@ const RESPONSE = { format: 0, id: 1111, price: 1, - pid: 2222 + pid: 2222, + adtype: 'BANNER' } +const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { + 'adtype': 'VIDEO' +}) + describe('yieldlabBidAdapter', function () { const adapter = newBidder(spec) @@ -68,8 +85,30 @@ describe('yieldlabBidAdapter', function () { expect(request.validBidRequests).to.eql([REQUEST]) }) - it('passes targeting to bid request', function () { - expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2') + it('passes single-encoded targeting to bid request', function () { + expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4') + }) + + it('passes userids to bid request', function () { + expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg') + }) + + it('passes extra params to bid request', function () { + expect(request.url).to.include('extraParam=true&foo=bar') + }) + + const refererRequest = spec.buildRequests(bidRequests, { + refererInfo: { + canonicalUrl: undefined, + numIframes: 0, + reachedTop: true, + referer: 'https://www.yieldlab.de/test?with=querystring', + stack: ['https://www.yieldlab.de/test?with=querystring'] + } + }) + + it('passes encoded referer to bid request', function () { + expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring') }) const gdprRequest = spec.buildRequests(bidRequests, { @@ -140,7 +179,7 @@ describe('yieldlabBidAdapter', function () { } } }) - const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [VIDEO_REQUEST]}) + const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST]}) expect(result[0].requestId).to.equal('2d925f27f5079f') expect(result[0].cpm).to.equal(0.01) @@ -158,7 +197,7 @@ describe('yieldlabBidAdapter', function () { } } }) - const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [OUTSTREAM_REQUEST]}) + const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [OUTSTREAM_REQUEST]}) expect(result[0].renderer.id).to.equal('2d925f27f5079f') expect(result[0].renderer.url).to.equal('https://ad2.movad.net/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event') diff --git a/test/spec/modules/yieldliftBidAdapter_spec.js b/test/spec/modules/yieldliftBidAdapter_spec.js new file mode 100644 index 00000000000..640a0f4e613 --- /dev/null +++ b/test/spec/modules/yieldliftBidAdapter_spec.js @@ -0,0 +1,257 @@ +import {expect} from 'chai'; +import {spec} from 'modules/yieldliftBidAdapter.js'; + +const REQUEST = { + 'bidderCode': 'yieldlift', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'yieldlift', + 'params': { + 'unitId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'bidId': 'bidId1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'yieldlift', + 'params': { + 'unitId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'bidId': 'bidId2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'responseId', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'bidId1', + 'impid': 'bidId1', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + }, + { + 'id': 'bidId2', + 'impid': 'bidId2', + 'price': 0.1, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 386046, + 'auction_id': 517067951122925501, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'yieldlift' + } + ], + 'ext': { + 'usersync': { + 'sovrn': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlsovrn', + 'type': 'iframe' + } + ] + }, + 'appnexus': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlappnexus', + 'type': 'pixel' + } + ] + } + }, + 'responsetimemillis': { + 'appnexus': 127 + } + } + } +}; + +describe('YieldLift', function () { + describe('isBidRequestValid', function () { + it('should accept request if only unitId is passed', function () { + let bid = { + bidder: 'yieldlift', + params: { + unitId: 'unitId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should accept request if only networkId is passed', function () { + let bid = { + bidder: 'yieldlift', + params: { + networkId: 'networkId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should accept request if only publisherId is passed', function () { + let bid = { + bidder: 'yieldlift', + params: { + publisherId: 'publisherId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('reject requests without params', function () { + let bid = { + bidder: 'yieldlift', + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('creates request data', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST); + + expect(request).to.exist.and.to.be.a('object'); + const payload = JSON.parse(request.data); + expect(payload.imp[0]).to.have.property('id', REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(REQUEST.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + }); + + describe('interpretResponse', function () { + it('have bids', function () { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + validateBidOnIndex(1); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].impid); + expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index]).to.have.property('ttl', 30); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, REQUEST); + + expect(bids).to.be.empty; + }); + }); + + describe('getUserSyncs', function () { + it('handles no parameters', function () { + let opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + it('returns non if sync is not allowed', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + + expect(opts).to.be.an('array').that.is.empty; + }); + + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['sovrn'].syncs[0].url); + }); + + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['appnexus'].syncs[0].url); + }); + + it('all sync enabled should return all results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(2); + }); + }); +}); diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 6a4a142975f..caeb26266fe 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -1,51 +1,55 @@ import { expect } from 'chai'; -import { spec } from 'modules/yieldmoBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import * as utils from 'src/utils'; +import { spec } from 'modules/yieldmoBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; describe('YieldmoAdapter', function () { const adapter = newBidder(spec); const ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; let tdid = '8d146286-91d4-4958-aff4-7e489dd1abd6'; + let criteoId = 'aff4'; let bid = { bidder: 'yieldmo', params: { - bidFloor: 0.1 + bidFloor: 0.1, }, adUnitCode: 'adunit-code', mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] - } + sizes: [ + [300, 250], + [300, 600], + ], + }, }, bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', crumbs: { - pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da' + pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da', }, userId: { tdid, - } + }, }; let bidArray = [bid]; let bidderRequest = { - 'bidderCode': 'yieldmo', - 'auctionId': 'e3a336ad-2761-4a1c-b421-ecc7c5294a34', - 'bidderRequestId': '14c4ede8c693f', - 'bids': bidArray, - 'auctionStart': 1520001292880, - 'timeout': 3000, - 'start': 1520001292884, - 'doneCbCallCount': 0, - 'refererInfo': { - 'numIframes': 1, - 'reachedTop': true, - 'referer': 'yieldmo.com' - } - } + bidderCode: 'yieldmo', + auctionId: 'e3a336ad-2761-4a1c-b421-ecc7c5294a34', + bidderRequestId: '14c4ede8c693f', + bids: bidArray, + auctionStart: 1520001292880, + timeout: 3000, + start: 1520001292884, + doneCbCallCount: 0, + refererInfo: { + numIframes: 1, + reachedTop: true, + referer: 'yieldmo.com', + }, + }; describe('isBidRequestValid', function () { it('should return true when necessary information is found', function () { @@ -77,38 +81,44 @@ describe('YieldmoAdapter', function () { }); it('should not blow up if crumbs is undefined', function () { - let bidArray = [ - { ...bid, crumbs: undefined } - ] - expect(function () { spec.buildRequests(bidArray, bidderRequest) }).not.to.throw() - }) + let bidArray = [{ ...bid, crumbs: undefined }]; + expect(function () { + spec.buildRequests(bidArray, bidderRequest); + }).not.to.throw(); + }); it('should place bid information into the p parameter of data', function () { let placementInfo = spec.buildRequests(bidArray, bidderRequest).data.p; - expect(placementInfo).to.equal('[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1}]'); + expect(placementInfo).to.equal( + '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1}]' + ); bidArray.push({ bidder: 'yieldmo', params: { - bidFloor: 0.2 + bidFloor: 0.2, }, adUnitCode: 'adunit-code-1', mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] - } + sizes: [ + [300, 250], + [300, 600], + ], + }, }, bidId: '123456789', bidderRequestId: '987654321', auctionId: '0246810', crumbs: { - pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da' - } - + pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da', + }, }); // multiple placements placementInfo = spec.buildRequests(bidArray, bidderRequest).data.p; - expect(placementInfo).to.equal('[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1},{"placement_id":"adunit-code-1","callback_id":"123456789","sizes":[[300,250],[300,600]],"bidFloor":0.2}]'); + expect(placementInfo).to.equal( + '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1},{"placement_id":"adunit-code-1","callback_id":"123456789","sizes":[[300,250],[300,600]],"bidFloor":0.2}]' + ); }); it('should add placement id if given', function () { @@ -130,12 +140,13 @@ describe('YieldmoAdapter', function () { 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; expect(data.hasOwnProperty('pubcid')).to.be.true; + expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":""}'); + expect(data.us_privacy).to.equal(''); }); it('should add pubcid as parameter of request', function () { @@ -145,18 +156,23 @@ describe('YieldmoAdapter', function () { adUnitCode: 'adunit-code', mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] - } + sizes: [ + [300, 250], + [300, 600], + ], + }, }, bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', userId: { - pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da2' - } + pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da2', + }, }; const data = spec.buildRequests([pubcidBid], bidderRequest).data; - expect(data.pubcid).to.deep.equal('c604130c-0144-4b63-9bf2-c2bd8c8d86da2'); + expect(data.pubcid).to.deep.equal( + 'c604130c-0144-4b63-9bf2-c2bd8c8d86da2' + ); }); it('should add unified id as parameter of request', function () { @@ -166,46 +182,79 @@ describe('YieldmoAdapter', function () { adUnitCode: 'adunit-code', mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] - } + sizes: [ + [300, 250], + [300, 600], + ], + }, }, bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', userId: { tdid, - } + }, }; const data = spec.buildRequests([unifiedIdBid], bidderRequest).data; expect(data.tdid).to.deep.equal(tdid); }); + it('should add CRITEO RTUS id as parameter of request', function () { + const criteoIdBid = { + bidder: 'yieldmo', + params: {}, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: { + criteoId, + }, + }; + const data = spec.buildRequests([criteoIdBid], bidderRequest).data; + expect(data.cri_prebid).to.deep.equal(criteoId); + }); + it('should add gdpr information to request if available', () => { bidderRequest.gdprConsent = { - 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', - 'vendorData': {'blerp': 1}, - 'gdprApplies': true - } + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: { blerp: 1 }, + gdprApplies: true, + }; const data = spec.buildRequests(bidArray, bidderRequest).data; - expect(data.userConsent).equal(JSON.stringify({ - 'gdprApplies': true, - 'cmp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' - })); + expect(data.userConsent).equal( + JSON.stringify({ + gdprApplies: true, + cmp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + }) + ); }); it('should add ccpa information to request if available', () => { const privacy = '1YNY'; - bidderRequest.us_privacy = privacy; + bidderRequest.uspConsent = privacy; const data = spec.buildRequests(bidArray, bidderRequest).data; expect(data.us_privacy).equal(privacy); }); it('should add schain if it is in the bidRequest', () => { - const schain = {'ver': '1.0', 'complete': 1, 'nodes': [{'asi': 'indirectseller.com', 'sid': '00001', 'hp': 1}]}; + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; bidArray[0].schain = schain; const request = spec.buildRequests([bidArray[0]], bidderRequest); expect(request.data.schain).equal(JSON.stringify(schain)); - }) + }); }); describe('interpretResponse', function () { @@ -213,17 +262,20 @@ describe('YieldmoAdapter', function () { beforeEach(function () { serverResponse = { - body: [{ - callback_id: '21989fdbef550a', - cpm: 3.45455, - width: 300, - height: 250, - ad: '
', - creative_id: '9874652394875' - }], - header: 'header?' + body: [ + { + callback_id: '21989fdbef550a', + cpm: 3.45455, + width: 300, + height: 250, + ad: + '
', + creative_id: '9874652394875', + }, + ], + header: 'header?', }; - }) + }); it('should correctly reorder the server response', function () { const newResponse = spec.interpretResponse(serverResponse); @@ -237,7 +289,8 @@ describe('YieldmoAdapter', function () { currency: 'USD', netRevenue: true, ttl: 300, - ad: '
' + ad: + '
', }); }); @@ -248,30 +301,13 @@ describe('YieldmoAdapter', function () { serverResponse.body[0].cpm = null; response = spec.interpretResponse(serverResponse); - expect(response).to.deep.equal([]) + expect(response).to.deep.equal([]); }); }); describe('getUserSync', function () { - 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', function () { - 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([]); - } + expect(spec.getUserSyncs()).to.deep.equal([]); }); }); }); diff --git a/test/spec/modules/yieldoneAnalyticsAdapter_spec.js b/test/spec/modules/yieldoneAnalyticsAdapter_spec.js index b7b4cd82b07..81a6365bba2 100644 --- a/test/spec/modules/yieldoneAnalyticsAdapter_spec.js +++ b/test/spec/modules/yieldoneAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ -import yieldoneAnalytics from 'modules/yieldoneAnalyticsAdapter'; -import { targeting } from 'src/targeting'; +import yieldoneAnalytics from 'modules/yieldoneAnalyticsAdapter.js'; +import { targeting } from 'src/targeting.js'; import { expect } from 'chai'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; @@ -113,6 +113,7 @@ describe('Yieldone Prebid Analytic', function () { const responses = [ { + ad: 'test ad content 1', width: 300, height: 250, statusMessage: 'Bid available', @@ -124,6 +125,7 @@ describe('Yieldone Prebid Analytic', function () { timeToRespond: 100 }, { + ad: 'test ad content 2', width: 336, height: 280, statusMessage: 'Bid available', @@ -135,6 +137,7 @@ describe('Yieldone Prebid Analytic', function () { timeToRespond: 100 }, { + ad: 'test ad content 3', width: 300, height: 250, statusMessage: 'Bid available', @@ -158,6 +161,7 @@ describe('Yieldone Prebid Analytic', function () { ]; const winner = { + ad: 'test ad content 3', width: 300, height: 250, statusMessage: 'Bid available', @@ -169,69 +173,75 @@ describe('Yieldone Prebid Analytic', function () { timeToRespond: 100 }; + const auctionEnd = { + auctionId: auctionId, + bidsReceived: responses.slice(0, 3) + }; + + const preparedResponses = responses.map((resp) => { + const res = Object.assign({}, resp); + delete res.ad; + return res; + }); + const expectedEvents = [ { eventType: constants.EVENTS.AUCTION_INIT, - page: {url: testReferrer}, params: { config: initOptions, - auctionId: auctionId, - pubId: initOptions.pubId + auctionId: auctionId } }, { eventType: constants.EVENTS.BID_REQUESTED, - page: {url: testReferrer}, - params: Object.assign({pubId: initOptions.pubId}, request[0]) + params: Object.assign(request[0]) }, { eventType: constants.EVENTS.BID_REQUESTED, - page: {url: testReferrer}, - params: Object.assign({pubId: initOptions.pubId}, request[1]) + params: Object.assign(request[1]) }, { eventType: constants.EVENTS.BID_REQUESTED, - page: {url: testReferrer}, - params: Object.assign({pubId: initOptions.pubId}, request[2]) + params: Object.assign(request[2]) }, { eventType: constants.EVENTS.BID_RESPONSE, - page: {url: testReferrer}, - params: Object.assign({pubId: initOptions.pubId}, responses[0]) + params: Object.assign(preparedResponses[0]) }, { eventType: constants.EVENTS.BID_RESPONSE, - page: {url: testReferrer}, - params: Object.assign({pubId: initOptions.pubId}, responses[1]) + params: Object.assign(preparedResponses[1]) }, { eventType: constants.EVENTS.BID_RESPONSE, - page: {url: testReferrer}, - params: Object.assign({pubId: initOptions.pubId}, responses[2]) + params: Object.assign(preparedResponses[2]) }, { eventType: constants.EVENTS.BID_TIMEOUT, - page: {url: testReferrer}, - params: Object.assign({pubId: initOptions.pubId}, request[2]) - }, - { - eventType: constants.EVENTS.AUCTION_END, - page: {url: testReferrer}, - params: { - auctionId: auctionId, - pubId: initOptions.pubId, - adServerTargeting: fakeTargeting - } + params: Object.assign(request[2]) } ]; + const expectedResult = { + pubId: initOptions.pubId, + page: {url: testReferrer}, + wrapper_version: '$prebid.version$', + events: expectedEvents + }; + const preparedWinnerParams = Object.assign({adServerTargeting: fakeTargeting}, winner); + delete preparedWinnerParams.ad; const wonExpectedEvents = [ { eventType: constants.EVENTS.BID_WON, - page: {url: testReferrer}, - params: Object.assign({pubId: initOptions.pubId, adServerTargeting: fakeTargeting}, winner) + params: preparedWinnerParams } ]; + const wonExpectedResult = { + pubId: initOptions.pubId, + page: {url: testReferrer}, + wrapper_version: '$prebid.version$', + events: wonExpectedEvents + }; adapterManager.enableAnalytics({ provider: 'yieldone', @@ -250,9 +260,9 @@ describe('Yieldone Prebid Analytic', function () { events.emit(constants.EVENTS.BID_TIMEOUT, [responses[3], responses[4]]); - events.emit(constants.EVENTS.AUCTION_END, {auctionId: auctionId}); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); - expect(yieldoneAnalytics.eventsStorage[auctionId]).to.deep.equal(expectedEvents); + expect(yieldoneAnalytics.eventsStorage[auctionId]).to.deep.equal(expectedResult); delete yieldoneAnalytics.eventsStorage[auctionId]; @@ -260,7 +270,7 @@ describe('Yieldone Prebid Analytic', function () { events.emit(constants.EVENTS.BID_WON, winner); sinon.assert.callCount(sendStatStub, 2); - expect(yieldoneAnalytics.eventsStorage[auctionId]).to.deep.equal(wonExpectedEvents); + expect(yieldoneAnalytics.eventsStorage[auctionId]).to.deep.equal(wonExpectedResult); delete yieldoneAnalytics.eventsStorage[auctionId]; done(); diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js new file mode 100644 index 00000000000..84065682297 --- /dev/null +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -0,0 +1,245 @@ +import { expect } from 'chai'; +import { spec } from 'modules/yieldoneBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const ENDPOINT = 'https://y.one.impact-ad.jp/h_bid'; +const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; +const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/dac-video-prebid.min.js'; + +describe('yieldoneBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'yieldone', + 'params': { + placementId: '36891' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [336, 280]], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId not passed correctly', function () { + bid.params.placementId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'yieldone', + 'params': { + placementId: '36891' + }, + 'adUnitCode': 'adunit-code1', + 'sizes': [[300, 250], [336, 280]], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'yieldone', + 'params': { + placementId: '47919' + }, + 'adUnitCode': 'adunit-code2', + 'sizes': [[300, 250]], + 'bidId': '382091349b149f"', + 'bidderRequestId': '"1f9c98192de251"', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + + let bidderRequest = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://example.com', + stack: ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('sends bid request to our endpoint via GET', function () { + 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', function () { + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); + }); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data.sz).to.equal('300x250'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + const request = spec.buildRequests([bidRequest], bidderRequest); + expect(request[0].data.w).to.equal('300'); + expect(request[0].data.h).to.equal('250'); + }); + + it('adUnitCode should be sent as uc parameters on any requests', function () { + expect(request[0].data.uc).to.equal('adunit-code1'); + expect(request[1].data.uc).to.equal('adunit-code2'); + }); + }); + + describe('interpretResponse', function () { + let bidRequestBanner = [ + { + 'method': 'GET', + 'url': 'https://y.one.impact-ad.jp/h_bid', + 'data': { + 'v': 'hb1', + 'p': '36891', + 'sz': '300x250,336x280', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i' + } + } + ]; + + let serverResponseBanner = { + body: { + 'adTag': '', + 'uid': '23beaa6af6cdde', + 'height': 250, + 'width': 300, + 'cpm': 0.0536616, + 'crid': '2494768', + 'currency': 'JPY', + 'statusMessage': 'Bid available', + 'dealId': 'P1-FIX-7800-DSP-MON' + } + }; + + it('should get the correct bid response for banner', function () { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 53.6616, + 'width': 300, + 'height': 250, + 'creativeId': '2494768', + 'dealId': 'P1-FIX-7800-DSP-MON', + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 3000, + 'referrer': '', + 'mediaType': 'banner', + 'ad': '' + }]; + let result = spec.interpretResponse(serverResponseBanner, bidRequestBanner[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].mediaType).to.equal(expectedResponse[0].mediaType); + }); + + let serverResponseVideo = { + body: { + 'uid': '23beaa6af6cdde', + 'height': 360, + 'width': 640, + 'cpm': 0.0536616, + 'dealId': 'P1-FIX-766-DSP-MON', + 'crid': '2494768', + 'currency': 'JPY', + 'statusMessage': 'Bid available', + 'adm': '' + } + }; + + let bidRequestVideo = [ + { + 'method': 'GET', + 'url': 'https://y.one.impact-ad.jp/h_bid', + 'data': { + 'v': 'hb1', + 'p': '41993', + 'w': '640', + 'h': '360', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i' + } + } + ]; + + it('should get the correct bid response for video', function () { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 53.6616, + 'width': 640, + 'height': 360, + 'creativeId': '2494768', + 'dealId': 'P1-FIX-7800-DSP-MON', + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 3000, + 'referrer': '', + 'mediaType': 'video', + 'vastXml': '', + 'renderer': { + id: '23beaa6af6cdde', + url: VIDEO_PLAYER_URL + } + }]; + let result = spec.interpretResponse(serverResponseVideo, bidRequestVideo[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].mediaType).to.equal(expectedResponse[0].mediaType); + expect(result[0].renderer.id).to.equal(expectedResponse[0].renderer.id); + expect(result[0].renderer.url).to.equal(expectedResponse[0].renderer.url); + }); + + it('handles empty bid response', function () { + let response = { + body: { + 'uid': '2c0b634db95a01', + 'height': 0, + 'crid': '', + 'statusMessage': 'Bid returned empty or error response', + 'width': 0, + 'cpm': 0 + } + }; + let result = spec.interpretResponse(response, bidRequestBanner[0]); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + it('handles empty sync options', function () { + expect(spec.getUserSyncs({})).to.be.undefined; + }); + + it('should return a sync url if iframe syncs are enabled', function () { + expect(spec.getUserSyncs({ + 'iframeEnabled': true + })).to.deep.equal([{ + type: 'iframe', url: USER_SYNC_URL + }]); + }); + }); +}); diff --git a/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..24781d749e0 --- /dev/null +++ b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js @@ -0,0 +1,545 @@ +import yuktamediaAnalyticsAdapter from 'modules/yuktamediaAnalyticsAdapter.js'; +import { expect } from 'chai'; +let events = require('src/events'); +let constants = require('src/constants.json'); + +let prebidAuction = { + 'auctionInit': { + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'timestamp': 1595850680304, + }, + 'bidRequested': { + 'bidderCode': 'appnexus', + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'bidderRequestId': '181df4d465699c', + 'bids': [ + { + 'bidder': 'appnexus', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'userId': { + 'id5id': 'ID5-ZHMOxXeRXPe3inZKGD-Lj0g7y8UWdDbsYXQ_n6aWMQ', + 'parrableid': '01.1595590997.46d951017bdc272ca50b88dbcfb0545cfc636bec3e3d8c02091fb1b413328fb2fd3baf65cb4114b3f782895fd09f82f02c9042b85b42c4654d08ba06dc77f0ded936c8ea3fc4085b4a99', + 'pubcid': '100a8bc9-f588-4c22-873e-a721cb68bc34' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2bccebeda7fbe4', + 'bidderRequestId': '181df4d465699c', + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1595850680304, + 'timeout': 1100, + 'start': 1595850680307 + }, + 'noBid': {}, + 'bidTimeout': [], + 'bidResponse': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'getStatusCode': function () { return 1; }, + 'adId': '3ade442375213f', + 'requestId': '2bccebeda7fbe4', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'responseTimestamp': 1595850681254, + 'requestTimestamp': 1595850680307, + 'bidder': 'appnexus', + 'timeToRespond': 947, + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '3ade442375213f', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + } + }, + 'auctionEnd': { + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954' + }, + 'bidWon': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'requestId': '2bccebeda7fbe4', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'responseTimestamp': 1595850681254, + 'requestTimestamp': 1595850680307, + 'bidder': 'appnexus', + 'timeToRespond': 947, + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '3ade442375213f', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered' + } +}; + +let prebidNativeAuction = { + 'auctionInit': { + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'timestamp': 1595589742100, + }, + 'bidRequested': { + 'bidderCode': 'appnexus', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'tid': 'f9c220eb-e44f-412b-92ff-8c24085ca675', + 'bids': [ + { + 'bidder': 'appnexus', + 'bid_id': '19a879bd73bc8d', + 'nativeParams': { + 'title': { + 'required': true, + 'len': 800 + }, + 'image': { + 'required': true, + 'sizes': [ + 989, + 742 + ] + }, + 'sponsoredBy': { + 'required': true + } + }, + 'mediaTypes': { + 'native': { + 'title': { + 'required': true, + 'len': 800 + }, + 'image': { + 'required': true, + 'sizes': [ + 989, + 742 + ] + }, + 'sponsoredBy': { + 'required': true + } + } + }, + 'adUnitCode': '/19968336/prebid_native_example_1', + 'sizes': [], + 'bidId': '19a879bd73bc8d', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'src': 's2s', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 0, + 'bidderWinsCount': 0 + }, + { + 'bidder': 'appnexus', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '28f8cc7d10f2db', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'src': 's2s', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 2, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1595589742100, + 'timeout': 1000, + 'src': 's2s', + 'start': 1595589742108 + }, + 'bidRequested1': { + 'bidderCode': 'ix', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'bidderRequestId': '5e64168f3654af', + 'bids': [ + { + 'bidder': 'ix', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'dfp-ad-rightrail_top', + 'sizes': [[300, 250]], + 'bidId': '9424dea605368f', + 'bidderRequestId': '5e64168f3654af', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'src': 's2s' + } + ], + 'auctionStart': 1595589742100, + 'timeout': 1000, + 'src': 's2s', + 'start': 1595589742108 + }, + 'noBid': { + 'bidder': 'ix', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'dfp-ad-rightrail_top', + 'transactionId': 'd99d90e0-663a-459d-8c87-4c92ce6a527c', + 'sizes': [[300, 250]], + 'bidId': '9424dea605368f', + 'bidderRequestId': '5e64168f3654af', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'src': 's2s', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 4, + 'bidderWinsCount': 0 + }, + 'bidTimeout': [ + { + 'bidId': '28f8cc7d10f2db', + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128' + } + ], + 'bidResponse': { + 'bidderCode': 'appnexus', + 'statusMessage': 'Bid available', + 'source': 's2s', + 'getStatusCode': function () { return 1; }, + 'cpm': 10, + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_pb': '10.00', + 'hb_adid': '4e756c72ee9044', + 'hb_size': 'undefinedxundefined', + 'hb_source': 's2s', + 'hb_format': 'native', + 'hb_native_linkurl': 'some_long_url', + 'hb_native_title': 'This is a Prebid Native Creative', + 'hb_native_brand': 'Prebid.org' + }, + 'native': { + 'clickUrl': { + 'url': 'some_long_url' + }, + 'impressionTrackers': [ + 'some_long_url' + ], + 'javascriptTrackers': [], + 'image': { + 'url': 'some_long_image_path', + 'width': 989, + 'height': 742 + }, + 'title': 'This is a Prebid Native Creative', + 'sponsoredBy': 'Prebid.org' + }, + 'currency': 'USD', + 'ttl': 60, + 'netRevenue': true, + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'responseTimestamp': 1595589742827, + 'requestTimestamp': 1595589742108, + 'bidder': 'appnexus', + 'adUnitCode': '/19968336/prebid_native_example_1', + 'timeToRespond': 719, + 'size': 'undefinedxundefined' + }, + 'auctionEnd': { + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128' + }, + 'bidWon': { + 'bidderCode': 'appnexus', + 'mediaType': 'native', + 'source': 's2s', + 'getStatusCode': function () { return 1; }, + 'cpm': 10, + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_pb': '10.00', + 'hb_adid': '4e756c72ee9044', + 'hb_size': 'undefinedxundefined', + 'hb_source': 's2s', + 'hb_format': 'native', + 'hb_native_linkurl': 'some_long_url', + 'hb_native_title': 'This is a Prebid Native Creative', + 'hb_native_brand': 'Prebid.org' + }, + 'native': { + 'clickUrl': { + 'url': 'some_long_url' + }, + 'impressionTrackers': [ + 'some_long_url' + ], + 'javascriptTrackers': [], + 'image': { + 'url': 'some_long_image_path', + 'width': 989, + 'height': 742 + }, + 'title': 'This is a Prebid Native Creative', + 'sponsoredBy': 'Prebid.org' + }, + 'currency': 'USD', + 'ttl': 60, + 'netRevenue': true, + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'responseTimestamp': 1595589742827, + 'requestTimestamp': 1595589742108, + 'bidder': 'appnexus', + 'adUnitCode': '/19968336/prebid_native_example_1', + 'timeToRespond': 719, + 'size': 'undefinedxundefined', + 'status': 'rendered' + } +} + +describe('yuktamedia analytics adapter', function () { + beforeEach(() => { + sinon.stub(events, 'getEvents').returns([]); + }); + afterEach(() => { + events.getEvents.restore(); + }); + + describe('enableAnalytics', function () { + beforeEach(() => { + sinon.spy(yuktamediaAnalyticsAdapter, 'track'); + }); + afterEach(() => { + yuktamediaAnalyticsAdapter.disableAnalytics(); + yuktamediaAnalyticsAdapter.track.restore(); + }); + + it('should catch all events', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); + events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); + events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 6); + }); + + it('should catch no events if no pubKey and pubId', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + + sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 0); + }); + + it('should catch nobid, timeout and biwon event events', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); + events.emit(constants.EVENTS.AUCTION_INIT, prebidNativeAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidNativeAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidNativeAuction[constants.EVENTS.BID_REQUESTED + '1']); + events.emit(constants.EVENTS.NO_BID, prebidNativeAuction[constants.EVENTS.NO_BID]); + events.emit(constants.EVENTS.BID_TIMEOUT, prebidNativeAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(constants.EVENTS.BID_RESPONSE, prebidNativeAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(constants.EVENTS.AUCTION_END, prebidNativeAuction[constants.EVENTS.AUCTION_END]); + events.emit(constants.EVENTS.AUCTION_END, prebidNativeAuction[constants.EVENTS.BID_WON]); + sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 8); + }); + }); + + describe('build utm tag data', function () { + beforeEach(function () { + localStorage.setItem('yuktamediaAnalytics_utm_source', 'prebid'); + localStorage.setItem('yuktamediaAnalytics_utm_medium', 'ad'); + localStorage.setItem('yuktamediaAnalytics_utm_campaign', ''); + localStorage.setItem('yuktamediaAnalytics_utm_term', ''); + localStorage.setItem('yuktamediaAnalytics_utm_content', ''); + localStorage.setItem('yuktamediaAnalytics_utm_timeout', Date.now()); + }); + + afterEach(function () { + localStorage.clear(); + }); + + it('should build utm data from local storage', function () { + let utmTagData = yuktamediaAnalyticsAdapter.buildUtmTagData({ + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + }); + expect(utmTagData.utm_source).to.equal('prebid'); + expect(utmTagData.utm_medium).to.equal('ad'); + expect(utmTagData.utm_campaign).to.equal(''); + expect(utmTagData.utm_term).to.equal(''); + expect(utmTagData.utm_content).to.equal(''); + }); + + it('should return empty object for disabled utm setting', function () { + let utmTagData = yuktamediaAnalyticsAdapter.buildUtmTagData({ + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: false, + enableSession: true, + enableUserIdCollection: true + }); + expect(utmTagData).deep.equal({}); + }); + }); + + describe('build session information', function () { + beforeEach(() => { + sinon.spy(yuktamediaAnalyticsAdapter, 'track'); + localStorage.clear(); + }); + afterEach(() => { + yuktamediaAnalyticsAdapter.disableAnalytics(); + yuktamediaAnalyticsAdapter.track.restore(); + localStorage.clear(); + }); + + it('should create session id in local storage if enabled', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); + events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); + events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + expect(localStorage.getItem('yuktamediaAnalytics_session_id')).to.not.equal(null); + }); + + it('should not create session id in local storage if disabled', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: false, + enableUserIdCollection: true + } + }); + events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); + events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + expect(localStorage.getItem('yuktamediaAnalytics_session_id')).to.equal(null); + }); + }); +}); diff --git a/test/spec/modules/zedoBidAdapter_spec.js b/test/spec/modules/zedoBidAdapter_spec.js new file mode 100644 index 00000000000..8e5a789656e --- /dev/null +++ b/test/spec/modules/zedoBidAdapter_spec.js @@ -0,0 +1,354 @@ +import { expect } from 'chai'; +import { spec } from 'modules/zedoBidAdapter'; + +describe('The ZEDO bidding adapter', function () { + describe('isBidRequestValid', function () { + it('should return false when given an invalid bid', function () { + const bid = { + bidder: 'zedo', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return true when given a channelcode bid', function () { + const bid = { + bidder: 'zedo', + params: { + channelCode: 20000000, + dimId: 9 + }, + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidderRequest = { + timeout: 3000, + }; + + it('should properly build a channelCode request for dim Id with type not defined', function () { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [[300, 200]], + params: { + channelCode: 20000000, + dimId: 10, + pubId: 1 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^https:\/\/saxp.zedo.com\/asw\/fmh.json/); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":1,"width":300,"height":200,"dimension":10,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}]}'); + }); + + it('should properly build a channelCode request for video with type defined', function () { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream', + }, + }, + params: { + channelCode: 20000000, + dimId: 85 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^https:\/\/saxp.zedo.com\/asw\/fmh.json/); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"Inarticle"}]}]}'); + }); + + describe('buildGDPRRequests', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + timeout: 3000, + gdprConsent: { + 'consentString': consentString, + 'gdprApplies': true + } + }; + + it('should properly build request with gdpr consent', function () { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [[300, 200]], + params: { + channelCode: 20000000, + dimId: 10 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":0,"width":300,"height":200,"dimension":10,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}],"gdpr":1,"gdpr_consent":"BOJ8RZsOJ8RZsABAB8AAAAAZ+A=="}'); + }); + }); + }); + describe('interpretResponse', function () { + it('should return an empty array when there is bid response', function () { + const response = {}; + const request = { bidRequests: [] }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a bid response with no valid creative', function () { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '600', + 'width': '160', + 'isFoc': true, + 'creativeDetails': { + 'type': 'StdBanner', + 'adContent': { + 'focImage': { + 'url': 'https://c13.zedo.com/OzoDB/0/0/0/blank.gif', + 'target': '_blank', + } + } + }, + 'cpm': '0' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'p12345', + bidId: 'test-bidId', + params: { + channelCode: 2000000, + dimId: 9 + } + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a bid response with valid display creative', function () { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '600', + 'width': '160', + 'isFoc': true, + 'creativeDetails': { + 'type': 'StdBanner', + 'adContent': '' + }, + 'bidCpm': '720000' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + channelCode: 2000000, + dimId: 9 + }, + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('ad1d762'); + expect(bids[0].cpm).to.equal(0.72); + expect(bids[0].width).to.equal('160'); + expect(bids[0].height).to.equal('600'); + }); + + it('should properly parse a bid response with valid video creative', function () { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '480', + 'width': '640', + 'isFoc': true, + 'creativeDetails': { + 'type': 'VAST', + 'adContent': '' + }, + 'bidCpm': '780000' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + channelCode: 2000000, + dimId: 85 + }, + }] + }; + + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('ad1d762'); + expect(bids[0].cpm).to.equal(0.78); + expect(bids[0].width).to.equal('640'); + expect(bids[0].height).to.equal('480'); + expect(bids[0].adType).to.equal('VAST'); + expect(bids[0].vastXml).to.not.equal(''); + expect(bids[0].ad).to.be.an('undefined'); + expect(bids[0].renderer).not.to.be.an('undefined'); + }); + }); + + describe('user sync', function () { + it('should register the iframe sync url', function () { + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + }); + + it('should pass gdpr params', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: false, consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=0'); + }); + }); + + describe('bid events', function () { + it('should trigger a win pixel', function () { + const bid = { + 'bidderCode': 'zedo', + 'width': '300', + 'height': '250', + 'statusMessage': 'Bid available', + 'adId': '148018fe5e', + 'cpm': 0.5, + 'ad': 'dummy data', + 'ad_id': '12345', + 'sizeId': '15', + 'adResponse': + { + 'creatives': [ + { + 'adId': '12345', + 'height': '480', + 'width': '640', + 'isFoc': true, + 'creativeDetails': { + 'type': 'VAST', + 'adContent': '' + }, + 'seeder': { + 'network': 1234, + 'servedChan': 1234567, + }, + 'cpm': '1200000', + 'servedChan': 1234, + }] + }, + 'params': [{ + 'channelCode': '123456', + 'dimId': '85' + }], + 'requestTimestamp': 1540401686, + 'responseTimestamp': 1540401687, + 'timeToRespond': 6253, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.53', + 'adUnitCode': '/123456/header-bid-tag-0', + 'bidder': 'zedo', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'zedo', + 'hb_adid': '148018fe5e', + 'hb_pb': '10.00', + } + }; + spec.onBidWon(bid); + spec.onTimeout(bid); + }); + it('should trigger a timeout pixel', function () { + const bid = { + 'bidderCode': 'zedo', + 'width': '300', + 'height': '250', + 'statusMessage': 'Bid available', + 'adId': '148018fe5e', + 'cpm': 0.5, + 'ad': 'dummy data', + 'ad_id': '12345', + 'sizeId': '15', + 'params': [{ + 'channelCode': '123456', + 'dimId': '85' + }], + 'timeout': 1, + 'requestTimestamp': 1540401686, + 'responseTimestamp': 1540401687, + 'timeToRespond': 6253, + 'adUnitCode': '/123456/header-bid-tag-0', + 'bidder': 'zedo', + 'size': '300x250', + }; + spec.onBidWon(bid); + spec.onTimeout(bid); + }); + }); +}); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 2fb2fd810fa..bd56ba53e4a 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid, getAssetMessage } from 'src/native'; +import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid, getAssetMessage } from 'src/native.js'; import CONSTANTS from 'src/constants.json'; const utils = require('src/utils'); diff --git a/test/spec/refererDetection_spec.js b/test/spec/refererDetection_spec.js index b2ef4e2058f..90892d915fe 100644 --- a/test/spec/refererDetection_spec.js +++ b/test/spec/refererDetection_spec.js @@ -1,4 +1,4 @@ -import { detectReferer } from 'src/refererDetection'; +import { detectReferer } from 'src/refererDetection.js'; import { expect } from 'chai'; var mocks = { diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index da10c67f223..77d806e4dbc 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import { Renderer } from 'src/Renderer'; -import * as utils from 'src/utils'; -import { loadExternalScript } from 'src/adloader'; +import { Renderer } from 'src/Renderer.js'; +import * as utils from 'src/utils.js'; +import { loadExternalScript } from 'src/adloader.js'; describe('Renderer', function () { describe('Renderer: A renderer installed on a bid response', function () { @@ -126,10 +126,13 @@ describe('Renderer', function () { id: 1, adUnitCode: 'video1' }); + testRenderer.setRender(() => {}) + + testRenderer.render() expect(utilsSpy.callCount).to.equal(1); }); - it('should call loadExternalScript() for script not defined on adUnit', function() { + it('should call loadExternalScript() for script not defined on adUnit, only when .render() is called', function() { $$PREBID_GLOBAL$$.adUnits = [{ code: 'video1', renderer: { @@ -143,6 +146,9 @@ describe('Renderer', function () { id: 1, adUnitCode: undefined }); + expect(loadExternalScript.called).to.be.false; + + testRenderer.render() expect(loadExternalScript.called).to.be.true; }); }); diff --git a/test/spec/sizeMapping_spec.js b/test/spec/sizeMapping_spec.js index 254dcb8003e..78dd9797c36 100644 --- a/test/spec/sizeMapping_spec.js +++ b/test/spec/sizeMapping_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { resolveStatus, setSizeConfig, sizeSupported } from 'src/sizeMapping'; -import includes from 'core-js/library/fn/array/includes'; +import { resolveStatus, setSizeConfig, sizeSupported } from 'src/sizeMapping.js'; +import includes from 'core-js-pure/features/array/includes.js'; let utils = require('src/utils'); let deepClone = utils.deepClone; diff --git a/test/spec/unit/adServerManager_spec.js b/test/spec/unit/adServerManager_spec.js index 4ae475ac013..ec58ff7f5b3 100644 --- a/test/spec/unit/adServerManager_spec.js +++ b/test/spec/unit/adServerManager_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { getGlobal } from 'src/prebidGlobal'; -import { registerVideoSupport } from 'src/adServerManager'; +import { getGlobal } from 'src/prebidGlobal.js'; +import { registerVideoSupport } from 'src/adServerManager.js'; const prebid = getGlobal(); diff --git a/test/spec/unit/adUnits_spec.js b/test/spec/unit/adUnits_spec.js index fb666feb9b8..a8443d36522 100644 --- a/test/spec/unit/adUnits_spec.js +++ b/test/spec/unit/adUnits_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { adunitCounter } from 'src/adUnits'; +import { adunitCounter } from 'src/adUnits.js'; describe('Adunit Counter', function () { const ADUNIT_ID_1 = 'test1'; diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index bd8f880378b..9ccdd6aef59 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1,19 +1,19 @@ import { expect } from 'chai'; -import adapterManager, { gdprDataHandler } from 'src/adapterManager'; +import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; import { getAdUnits, getServerTestingConfig, getServerTestingsAds, getBidRequests -} from 'test/fixtures/fixtures'; +} from 'test/fixtures/fixtures.js'; 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'; -import s2sTesting from 'modules/s2sTesting'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import { registerBidder } from 'src/adapters/bidderFactory.js'; +import { setSizeConfig } from 'src/sizeMapping.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import s2sTesting from 'modules/s2sTesting.js'; var events = require('../../../../src/events'); const CONFIG = { @@ -43,16 +43,23 @@ var rubiconAdapterMock = { callBids: sinon.stub() }; +var badAdapterMock = { + bidder: 'badBidder', + callBids: sinon.stub().throws(Error('some fake error')) +}; + describe('adapterManager tests', function () { let orgAppnexusAdapter; let orgAdequantAdapter; let orgPrebidServerAdapter; let orgRubiconAdapter; + let orgBadBidderAdapter; before(function () { orgAppnexusAdapter = adapterManager.bidderRegistry['appnexus']; orgAdequantAdapter = adapterManager.bidderRegistry['adequant']; orgPrebidServerAdapter = adapterManager.bidderRegistry['prebidServer']; orgRubiconAdapter = adapterManager.bidderRegistry['rubicon']; + orgBadBidderAdapter = adapterManager.bidderRegistry['badBidder']; }); after(function () { @@ -60,6 +67,7 @@ describe('adapterManager tests', function () { adapterManager.bidderRegistry['adequant'] = orgAdequantAdapter; adapterManager.bidderRegistry['prebidServer'] = orgPrebidServerAdapter; adapterManager.bidderRegistry['rubicon'] = orgRubiconAdapter; + adapterManager.bidderRegistry['badBidder'] = orgBadBidderAdapter; config.setConfig({s2sConfig: { enabled: false }}); }); @@ -72,11 +80,16 @@ describe('adapterManager tests', function () { sinon.stub(utils, 'logError'); appnexusAdapterMock.callBids.reset(); adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; + adapterManager.bidderRegistry['badBidder'] = badAdapterMock; }); afterEach(function () { utils.logError.restore(); delete adapterManager.bidderRegistry['appnexus']; + delete adapterManager.bidderRegistry['rubicon']; + delete adapterManager.bidderRegistry['badBidder']; + config.resetConfig(); }); it('should log an error if a bidder is used that does not exist', function () { @@ -95,6 +108,34 @@ describe('adapterManager tests', function () { sinon.assert.called(utils.logError); }); + it('should catch a bidder adapter thrown error and continue with other bidders', function () { + const adUnits = [{ + code: 'adUnit-code', + sizes: [[728, 90]], + bids: [ + {bidder: 'appnexus', params: {placementId: 'id'}}, + {bidder: 'badBidder', params: {placementId: 'id'}}, + {bidder: 'rubicon', params: {account: 1111, site: 2222, zone: 3333}} + ] + }]; + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + + let doneBidders = []; + function mockDoneCB() { + doneBidders.push(this.bidderCode) + } + adapterManager.callBids(adUnits, bidRequests, () => {}, mockDoneCB); + sinon.assert.calledOnce(appnexusAdapterMock.callBids); + sinon.assert.calledOnce(badAdapterMock.callBids); + sinon.assert.calledOnce(rubiconAdapterMock.callBids); + + expect(utils.logError.calledOnce).to.be.true; + expect(utils.logError.calledWith( + 'badBidder Bid Adapter emitted an uncaught error when parsing their bidRequest' + )).to.be.true; + // done should be called for our bidder! + expect(doneBidders.indexOf('badBidder') === -1).to.be.false; + }); it('should emit BID_REQUESTED event', function () { // function to count BID_REQUESTED events let cnt = 0; @@ -175,7 +216,7 @@ describe('adapterManager tests', function () { buildRequests: { data: 1 }, - test1: { speedy: true }, + test1: { speedy: true, fun: { test: true } }, interpretResponse: 'baseInterpret', afterInterpretResponse: 'anotherBaseInterpret' }); @@ -212,7 +253,7 @@ describe('adapterManager tests', function () { data: 1, test: 2 }, - { fun: { safe: true, cheap: false }, speedy: true }, + { fun: { safe: true, cheap: false, test: true }, speedy: true }, { amazing: true }, 'appnexusInterpret', 'anotherBaseInterpret' @@ -221,14 +262,14 @@ describe('adapterManager tests', function () { { data: 1 }, - { speedy: true }, + { fun: { test: true }, speedy: true }, undefined, 'baseInterpret', 'anotherBaseInterpret' ], 'rubicon': [ 'rubiconBuild', - { speedy: true }, + { fun: { test: true }, speedy: true }, { amazing: true }, null, 'anotherBaseInterpret' diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index e8ddf52c128..692cf9a6475 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,11 +1,11 @@ -import { newBidder, registerBidder, preloadBidderMappingFile } from 'src/adapters/bidderFactory'; -import adapterManager from 'src/adapterManager'; -import * as ajax from 'src/ajax'; +import { newBidder, registerBidder, preloadBidderMappingFile, storage } from 'src/adapters/bidderFactory.js'; +import adapterManager from 'src/adapterManager.js'; +import * as ajax from 'src/ajax.js'; 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'; +import { userSync } from 'src/userSync.js' +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; const CODE = 'sampleBidder'; const MOCK_BIDS_REQUEST = { @@ -56,15 +56,66 @@ describe('bidders created by newBidder', function () { describe('when the ajax response is irrelevant', function () { let ajaxStub; + let getConfigSpy; beforeEach(function () { ajaxStub = sinon.stub(ajax, 'ajax'); addBidResponseStub.reset(); + getConfigSpy = sinon.spy(config, 'getConfig'); doneStub.reset(); }); afterEach(function () { ajaxStub.restore(); + getConfigSpy.restore(); + }); + + it('should let registerSyncs run with invalid alias and aliasSync enabled', function () { + config.setConfig({ + userSync: { + aliasSyncEnabled: true + } + }); + spec.code = 'fakeBidder'; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); + }); + + it('should let registerSyncs run with valid alias and aliasSync enabled', function () { + config.setConfig({ + userSync: { + aliasSyncEnabled: true + } + }); + spec.code = 'aliasBidder'; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); + }); + + it('should let registerSyncs run with invalid alias and aliasSync disabled', function () { + config.setConfig({ + userSync: { + aliasSyncEnabled: false + } + }); + spec.code = 'fakeBidder'; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); + }); + + it('should not let registerSyncs run with valid alias and aliasSync disabled', function () { + config.setConfig({ + userSync: { + aliasSyncEnabled: false + } + }); + spec.code = 'aliasBidder'; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(false); }); it('should handle bad bid requests gracefully', function () { @@ -196,7 +247,7 @@ describe('bidders created by newBidder', function () { bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); + expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2`); expect(ajaxStub.firstCall.args[2]).to.be.undefined; expect(ajaxStub.firstCall.args[3]).to.deep.equal({ method: 'GET', @@ -220,7 +271,7 @@ describe('bidders created by newBidder', function () { bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); + expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2`); expect(ajaxStub.firstCall.args[2]).to.be.undefined; expect(ajaxStub.firstCall.args[3]).to.deep.equal({ method: 'GET', @@ -350,8 +401,12 @@ describe('bidders created by newBidder', function () { adUnitCode: 'mock/placement', currency: 'USD', netRevenue: true, - ttl: 300 + ttl: 300, + bidderCode: 'sampleBidder', + sampleBidder: {advertiserId: '12345', networkId: '111222'} }; + const bidderRequest = Object.assign({}, MOCK_BIDS_REQUEST); + bidderRequest.bids[0].bidder = 'sampleBidder'; spec.isBidRequestValid.returns(true); spec.buildRequests.returns({ method: 'POST', @@ -362,7 +417,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); @@ -372,6 +427,8 @@ describe('bidders created by newBidder', function () { expect(bidObject.originalCurrency).to.equal(bid.currency); expect(doneStub.calledOnce).to.equal(true); expect(logErrorSpy.callCount).to.equal(0); + expect(bidObject.meta).to.exist; + expect(bidObject.meta).to.deep.equal({advertiserId: '12345', networkId: '111222'}); }); it('should call spec.getUserSyncs() with the response', function () { @@ -594,6 +651,28 @@ describe('registerBidder', function () { expect(registerBidAdapterStub.secondCall.args[1]).to.equal('foo') expect(registerBidAdapterStub.thirdCall.args[1]).to.equal('bar') }); + + it('should register alias with their gvlid', function() { + const aliases = [ + { + code: 'foo', + gvlid: 1 + }, + { + code: 'bar', + gvlid: 2 + }, + { + code: 'baz' + } + ] + const thisSpec = Object.assign(newEmptySpec(), { aliases: aliases }); + registerBidder(thisSpec); + + expect(registerBidAdapterStub.getCall(1).args[0].getSpec().gvlid).to.equal(1); + expect(registerBidAdapterStub.getCall(2).args[0].getSpec().gvlid).to.equal(2); + expect(registerBidAdapterStub.getCall(3).args[0].getSpec().gvlid).to.equal(undefined); + }) }) describe('validate bid response: ', function () { @@ -790,42 +869,32 @@ describe('preload mapping url hook', function() { let fakeTranslationServer; let getLocalStorageStub; let adapterManagerStub; + let adUnits = [{ + code: 'midroll_1', + mediaTypes: { + video: { + context: 'adpod' + } + }, + bids: [ + { + bidder: 'sampleBidder1', + params: { + placementId: 14542875, + } + } + ] + }]; beforeEach(function () { - fakeTranslationServer = sinon.fakeServer.create(); - getLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage'); + fakeTranslationServer = server; + getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); - }); - - afterEach(function() { - getLocalStorageStub.restore(); - adapterManagerStub.restore(); - config.resetConfig(); - }); - - it('should preload mapping url file', function() { config.setConfig({ 'adpod': { 'brandCategoryExclusion': true } }); - let adUnits = [{ - code: 'midroll_1', - mediaTypes: { - video: { - context: 'adpod' - } - }, - bids: [ - { - bidder: 'sampleBidder1', - params: { - placementId: 14542875, - } - } - ] - }]; - getLocalStorageStub.returns(null); adapterManagerStub.withArgs('sampleBidder1').returns({ getSpec: function() { return { @@ -839,16 +908,21 @@ describe('preload mapping url hook', function() { } } }); + }); + + afterEach(function() { + getLocalStorageStub.restore(); + adapterManagerStub.restore(); + config.resetConfig(); + }); + + it('should preload mapping url file', function() { + getLocalStorageStub.returns(null); preloadBidderMappingFile(sinon.spy(), adUnits); expect(fakeTranslationServer.requests.length).to.equal(1); }); it('should preload mapping url file for all bidders', function() { - config.setConfig({ - 'adpod': { - 'brandCategoryExclusion': true - } - }); let adUnits = [{ code: 'midroll_1', mediaTypes: { @@ -872,19 +946,6 @@ describe('preload mapping url hook', function() { ] }]; getLocalStorageStub.returns(null); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function() { - return { - 'getMappingFileInfo': function() { - return { - url: 'http://sample.com', - refreshInDays: 7, - key: `sampleBidder1MappingFile` - } - } - } - } - }); adapterManagerStub.withArgs('sampleBidder2').returns({ getSpec: function() { return { @@ -909,4 +970,30 @@ describe('preload mapping url hook', function() { preloadBidderMappingFile(sinon.spy(), adUnits); expect(fakeTranslationServer.requests.length).to.equal(2); }); + + it('should make ajax call to update mapping file if data found in localstorage is expired', function() { + let clock = sinon.useFakeTimers(utils.timestamp()); + getLocalStorageStub.returns(JSON.stringify({ + lastUpdated: utils.timestamp() - 8 * 24 * 60 * 60 * 1000, + mapping: { + 'iab-1': '1' + } + })); + preloadBidderMappingFile(sinon.spy(), adUnits); + expect(fakeTranslationServer.requests.length).to.equal(1); + clock.restore(); + }); + + it('should not make ajax call to update mapping file if data found in localstorage and is not expired', function () { + let clock = sinon.useFakeTimers(utils.timestamp()); + getLocalStorageStub.returns(JSON.stringify({ + lastUpdated: utils.timestamp(), + mapping: { + 'iab-1': '1' + } + })); + preloadBidderMappingFile(sinon.spy(), adUnits); + expect(fakeTranslationServer.requests.length).to.equal(0); + clock.restore(); + }); }); diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js new file mode 100644 index 00000000000..0b406242f90 --- /dev/null +++ b/test/spec/unit/core/storageManager_spec.js @@ -0,0 +1,46 @@ +import { resetData, getCoreStorageManager, storageCallbacks, getStorageManager } from 'src/storageManager.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; + +describe('storage manager', function() { + beforeEach(function() { + resetData(); + }); + + afterEach(function() { + config.resetConfig(); + }) + + it('should allow to set cookie for core modules without checking gdpr enforcements', function() { + const coreStorage = getCoreStorageManager(); + let date = new Date(); + date.setTime(date.getTime() + (24 * 60 * 60 * 1000)); + let expires = date.toUTCString(); + coreStorage.setCookie('hello', 'world', expires); + expect(coreStorage.getCookie('hello')).to.equal('world'); + }); + + it('should add done callbacks to storageCallbacks array', function() { + let noop = sinon.spy(); + const coreStorage = getStorageManager(); + + coreStorage.setCookie('foo', 'bar', null, null, null, noop); + coreStorage.getCookie('foo', noop); + coreStorage.localStorageIsEnabled(noop); + coreStorage.cookiesAreEnabled(noop); + coreStorage.setDataInLocalStorage('foo', 'bar', noop); + coreStorage.getDataFromLocalStorage('foo', noop); + coreStorage.removeDataFromLocalStorage('foo', noop); + coreStorage.hasLocalStorage(noop); + + expect(storageCallbacks.length).to.equal(8); + }); + + it('should allow bidder to access device if gdpr enforcement module is not included', function() { + let deviceAccessSpy = sinon.spy(utils, 'hasDeviceAccess'); + const storage = getStorageManager(); + storage.setCookie('foo1', 'baz1'); + expect(deviceAccessSpy.calledOnce).to.equal(true); + deviceAccessSpy.restore(); + }) +}); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index ad94ebccfb2..3aae6e3c33a 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -1,10 +1,10 @@ import { expect } from 'chai'; -import { targeting as targetingInstance, filters, sortByDealAndPriceBucket } from 'src/targeting'; -import { config } from 'src/config'; -import { getAdUnits, createBidReceived } from 'test/fixtures/fixtures'; +import { targeting as targetingInstance, filters, getHighestCpmBidsFromBidPool, sortByDealAndPriceBucketOrCpm } from 'src/targeting.js'; +import { config } from 'src/config.js'; +import { getAdUnits, createBidReceived } from 'test/fixtures/fixtures.js'; import CONSTANTS from 'src/constants.json'; -import { auctionManager } from 'src/auctionManager'; -import * as utils from 'src/utils'; +import { auctionManager } from 'src/auctionManager.js'; +import * as utils from 'src/utils.js'; const bid1 = { 'bidderCode': 'rubicon', @@ -338,11 +338,44 @@ describe('targeting tests', function () { bid4 = utils.deepClone(bid1); bid4.adserverTargeting['hb_bidder'] = bid4.bidder = bid4.bidderCode = 'appnexus'; bid4.cpm = 2.25; + bid4.adId = '8383838'; enableSendAllBids = true; bidsReceived.push(bid4); }); + it('when sendBidsControl.bidLimit is set greater than 0 in getHighestCpmBidsFromBidPool', function () { + config.setConfig({ + sendBidsControl: { + bidLimit: 2, + dealPrioritization: true + } + }); + + const bids = getHighestCpmBidsFromBidPool(bidsReceived, utils.getHighestCpm, 2); + + expect(bids.length).to.equal(3); + expect(bids[0].adId).to.equal('8383838'); + expect(bids[1].adId).to.equal('148018fe5e'); + expect(bids[2].adId).to.equal('48747745'); + }); + + it('when sendBidsControl.bidLimit is set greater than 0 and deal priortization is false in getHighestCpmBidsFromBidPool', function () { + config.setConfig({ + sendBidsControl: { + bidLimit: 2, + dealPrioritization: false + } + }); + + const bids = getHighestCpmBidsFromBidPool(bidsReceived, utils.getHighestCpm, 2); + + expect(bids.length).to.equal(3); + expect(bids[0].adId).to.equal('8383838'); + expect(bids[1].adId).to.equal('148018fe5e'); + expect(bids[2].adId).to.equal('48747745'); + }); + it('selects the top n number of bids when enableSendAllBids is true and and bitLimit is set', function () { config.setConfig({ sendBidsControl: { @@ -369,7 +402,7 @@ describe('targeting tests', function () { expect(limitedBids.length).to.equal(2); }); - it('Sends all bids when enableSendAllBids is true and and bitLimit is set to 0', function () { + it('Sends all bids when enableSendAllBids is true and and bidLimit is set to 0', function () { config.setConfig({ sendBidsControl: { bidLimit: 0 @@ -687,123 +720,174 @@ describe('targeting tests', function () { }); }); - describe('sortByDealAndPriceBucket', function() { + describe('sortByDealAndPriceBucketOrCpm', function() { it('will properly sort bids when some bids have deals and some do not', function () { let bids = [{ - adUnitTargeting: { + adserverTargeting: { hb_adid: 'abc', hb_pb: '1.00', hb_deal: '1234' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'def', hb_pb: '0.50', } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'ghi', hb_pb: '20.00', hb_deal: '4532' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'jkl', hb_pb: '9.00', hb_deal: '9864' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'mno', hb_pb: '50.00', } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'pqr', hb_pb: '100.00', } }]; - bids.sort(sortByDealAndPriceBucket); - expect(bids[0].adUnitTargeting.hb_adid).to.equal('ghi'); - expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); - expect(bids[2].adUnitTargeting.hb_adid).to.equal('abc'); - expect(bids[3].adUnitTargeting.hb_adid).to.equal('pqr'); - expect(bids[4].adUnitTargeting.hb_adid).to.equal('mno'); - expect(bids[5].adUnitTargeting.hb_adid).to.equal('def'); + bids.sort(sortByDealAndPriceBucketOrCpm()); + expect(bids[0].adserverTargeting.hb_adid).to.equal('ghi'); + expect(bids[1].adserverTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adserverTargeting.hb_adid).to.equal('abc'); + expect(bids[3].adserverTargeting.hb_adid).to.equal('pqr'); + expect(bids[4].adserverTargeting.hb_adid).to.equal('mno'); + expect(bids[5].adserverTargeting.hb_adid).to.equal('def'); }); it('will properly sort bids when all bids have deals', function () { let bids = [{ - adUnitTargeting: { + adserverTargeting: { hb_adid: 'abc', hb_pb: '1.00', hb_deal: '1234' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'def', hb_pb: '0.50', hb_deal: '4321' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'ghi', hb_pb: '2.50', hb_deal: '4532' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'jkl', hb_pb: '2.00', hb_deal: '9864' } }]; - bids.sort(sortByDealAndPriceBucket); - expect(bids[0].adUnitTargeting.hb_adid).to.equal('ghi'); - expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); - expect(bids[2].adUnitTargeting.hb_adid).to.equal('abc'); - expect(bids[3].adUnitTargeting.hb_adid).to.equal('def'); + bids.sort(sortByDealAndPriceBucketOrCpm()); + expect(bids[0].adserverTargeting.hb_adid).to.equal('ghi'); + expect(bids[1].adserverTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adserverTargeting.hb_adid).to.equal('abc'); + expect(bids[3].adserverTargeting.hb_adid).to.equal('def'); }); it('will properly sort bids when no bids have deals', function () { let bids = [{ - adUnitTargeting: { + adserverTargeting: { hb_adid: 'abc', hb_pb: '1.00' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'def', hb_pb: '0.10' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'ghi', hb_pb: '10.00' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'jkl', hb_pb: '10.01' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'mno', hb_pb: '1.00' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'pqr', hb_pb: '100.00' } }]; - bids.sort(sortByDealAndPriceBucket); - expect(bids[0].adUnitTargeting.hb_adid).to.equal('pqr'); - expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); - expect(bids[2].adUnitTargeting.hb_adid).to.equal('ghi'); - expect(bids[3].adUnitTargeting.hb_adid).to.equal('abc'); - expect(bids[4].adUnitTargeting.hb_adid).to.equal('mno'); - expect(bids[5].adUnitTargeting.hb_adid).to.equal('def'); + bids.sort(sortByDealAndPriceBucketOrCpm()); + expect(bids[0].adserverTargeting.hb_adid).to.equal('pqr'); + expect(bids[1].adserverTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adserverTargeting.hb_adid).to.equal('ghi'); + expect(bids[3].adserverTargeting.hb_adid).to.equal('abc'); + expect(bids[4].adserverTargeting.hb_adid).to.equal('mno'); + expect(bids[5].adserverTargeting.hb_adid).to.equal('def'); + }); + + it('will properly sort bids when some bids have deals and some do not and by cpm when flag is set to true', function () { + let bids = [{ + cpm: 1.04, + adserverTargeting: { + hb_adid: 'abc', + hb_pb: '1.00', + hb_deal: '1234' + } + }, { + cpm: 0.50, + adserverTargeting: { + hb_adid: 'def', + hb_pb: '0.50', + hb_deal: '4532' + } + }, { + cpm: 0.53, + adserverTargeting: { + hb_adid: 'ghi', + hb_pb: '0.50', + hb_deal: '4532' + } + }, { + cpm: 9.04, + adserverTargeting: { + hb_adid: 'jkl', + hb_pb: '9.00', + hb_deal: '9864' + } + }, { + cpm: 50.00, + adserverTargeting: { + hb_adid: 'mno', + hb_pb: '50.00', + } + }, { + cpm: 100.00, + adserverTargeting: { + hb_adid: 'pqr', + hb_pb: '100.00', + } + }]; + bids.sort(sortByDealAndPriceBucketOrCpm(true)); + expect(bids[0].adserverTargeting.hb_adid).to.equal('jkl'); + expect(bids[1].adserverTargeting.hb_adid).to.equal('abc'); + expect(bids[2].adserverTargeting.hb_adid).to.equal('ghi'); + expect(bids[3].adserverTargeting.hb_adid).to.equal('def'); + expect(bids[4].adserverTargeting.hb_adid).to.equal('pqr'); + expect(bids[5].adserverTargeting.hb_adid).to.equal('mno'); }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index b59911153e5..960ccf08c92 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -7,27 +7,22 @@ import { getTargetingKeysBidLandscape, getAdUnits, createBidReceived -} from 'test/fixtures/fixtures'; -import { auctionManager, newAuctionManager } from 'src/auctionManager'; -import { targeting, newTargeting, filters } from 'src/targeting'; -import { config as configObj } from 'src/config'; -import * as ajaxLib from 'src/ajax'; -import * as auctionModule from 'src/auction'; -import { newBidder, registerBidder } from 'src/adapters/bidderFactory'; -import find from 'core-js/library/fn/array/find'; +} from 'test/fixtures/fixtures.js'; +import { auctionManager, newAuctionManager } from 'src/auctionManager.js'; +import { targeting, newTargeting, filters } from 'src/targeting.js'; +import { config as configObj } from 'src/config.js'; +import * as ajaxLib from 'src/ajax.js'; +import * as auctionModule from 'src/auction.js'; +import { registerBidder } from 'src/adapters/bidderFactory.js'; +import { _sendAdToCreative } from 'src/secureCreatives.js'; +import find from 'core-js-pure/features/array/find.js'; var assert = require('chai').assert; var expect = require('chai').expect; -var urlParse = require('url-parse'); - -var prebid = require('src/prebid'); var utils = require('src/utils'); -var bidfactory = require('src/bidfactory'); -var adloader = require('test/mocks/adloaderStub'); var adapterManager = require('src/adapterManager').default; var events = require('src/events'); -var adserver = require('src/adserver'); var CONSTANTS = require('src/constants.json'); // These bid adapters are required to be loaded for the following tests to work @@ -56,7 +51,7 @@ function resetAuction() { var Slot = function Slot(elementId, pathId) { var slot = { - targeting: [], + targeting: {}, getSlotElementId: function getSlotElementId() { return elementId; }, @@ -66,24 +61,24 @@ var Slot = function Slot(elementId, pathId) { }, setTargeting: function setTargeting(key, value) { - var obj = []; - obj[key] = value; - this.targeting.push(obj); + this.targeting[key] = Array.isArray(value) ? value : [value]; }, - getTargeting: function getTargeting() { - return this.targeting; + getTargeting: function getTargeting(key) { + return this.targeting[key] || []; }, getTargetingKeys: function getTargetingKeys() { - return []; + return Object.getOwnPropertyNames(this.targeting); }, clearTargeting: function clearTargeting() { - return googletag.pubads().getSlots(); + this.targeting = {}; + return this; } }; slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); + slot.spyGetSlotElementId = sinon.spy(slot, 'getSlotElementId'); return slot; }; @@ -121,11 +116,15 @@ window.googletag = { }, setTargeting: function(key, arrayOfValues) { - self._targeting[key] = arrayOfValues; + self._targeting[key] = Array.isArray(arrayOfValues) ? arrayOfValues : [arrayOfValues]; + }, + + getTargeting: function(key) { + return self._targeting[key] || []; }, - getTargeting: function() { - return self._targeting; + getTargetingKeys: function() { + return Object.getOwnPropertyNames(self._targeting); }, clearTargeting: function() { @@ -856,17 +855,57 @@ describe('Unit: Prebid Module', function () { it('should set googletag targeting keys after calling setTargetingForGPTAsync function', function () { var slots = createSlotArrayScenario2(); window.googletag.pubads().setSlots(slots); - $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); + $$PREBID_GLOBAL$$.setTargetingForGPTAsync([config.adUnitCodes[0]]); - var targeting = []; - slots[1].getTargeting().map(function (value) { - var temp = []; - temp.push(Object.keys(value).toString()); - temp.push(value[Object.keys(value)]); - targeting.push(temp); + // we need to transform the spySetTargeting into something that looks like + // googletag's targeting structure + // googletag setTargeting will override old value if invoked with same key + + const targeting = []; + slots[1].getTargetingKeys().map(function (key) { + const value = slots[1].getTargeting(key); + targeting.push([key, value]); + }); + + var invokedTargetingMap = {}; + slots[1].spySetTargeting.args.map(function (entry) { + invokedTargetingMap[entry[0]] = entry[1]; }); - assert.deepEqual(slots[1].spySetTargeting.args, targeting, 'google tag targeting options not matching'); + var invokedTargeting = []; + + Object.getOwnPropertyNames(invokedTargetingMap).map(function (key) { + const value = Array.isArray(invokedTargetingMap[key]) ? invokedTargetingMap[key] : [invokedTargetingMap[key]]; // values are always returned as array in googletag + invokedTargeting.push([key, value]); + }); + assert.deepEqual(targeting, invokedTargeting, 'google tag targeting options not matching'); + }); + + it('should set googletag targeting keys to specific slot with customSlotMatching', function () { + // same ad unit code but two differnt divs + // we make sure we can set targeting for a specific one with customSlotMatching + + $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: false }); + + var slots = [ + new Slot('div-id-one', config.adUnitCodes[0]), + new Slot('div-id-two', config.adUnitCodes[0]), + new Slot(config.adUnitElementIDs[2], config.adUnitCodes[2]) + ]; + + slots[0].spySetTargeting.resetHistory(); + slots[1].spySetTargeting.resetHistory(); + window.googletag.pubads().setSlots(slots); + + $$PREBID_GLOBAL$$.setTargetingForGPTAsync([config.adUnitCodes[0]], (slot) => { + return (adUnitCode) => { + return slots[0].getSlotElementId() === slot.getSlotElementId(); + }; + }); + + var expected = getTargetingKeys(); + expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected); + expect(slots[1].spySetTargeting.args).to.not.deep.contain.members(expected); }); it('should set targeting when passed a string ad unit code with enableSendAllBids', function () { @@ -898,6 +937,40 @@ describe('Unit: Prebid Module', function () { expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected); }); + it('should find correct gpt slot based on ad id rather than ad unit code when resizing secure creative', function () { + var slots = [ + new Slot('div-not-matching-adunit-code-1', config.adUnitCodes[0]), + new Slot('div-not-matching-adunit-code-2', config.adUnitCodes[0]), + new Slot('div-not-matching-adunit-code-3', config.adUnitCodes[0]) + ]; + + slots[1].setTargeting('hb_adid', ['someAdId']); + slots[1].spyGetSlotElementId.resetHistory(); + window.googletag.pubads().setSlots(slots); + + const mockAdObject = { + adId: 'someAdId', + ad: '', + adUrl: 'http://creative.prebid.org/${AUCTION_PRICE}', + width: 300, + height: 250, + renderer: null, + cpm: '1.00', + adUnitCode: config.adUnitCodes[0], + }; + + const event = { + source: { postMessage: sinon.stub() }, + origin: 'origin.sf.com' + }; + + _sendAdToCreative(mockAdObject, event); + + expect(slots[0].spyGetSlotElementId.called).to.equal(false); + expect(slots[1].spyGetSlotElementId.called).to.equal(true); + expect(slots[2].spyGetSlotElementId.called).to.equal(false); + }); + it('Calling enableSendAllBids should set targeting to include standard keys with bidder' + ' append to key name', function () { var slots = createSlotArray(); @@ -1142,9 +1215,14 @@ describe('Unit: Prebid Module', function () { describe('requestBids', function () { let logMessageSpy; let makeRequestsStub; - let xhr; let adUnits; let clock; + before(function () { + clock = sinon.useFakeTimers(); + }); + after(function () { + clock.restore(); + }); let bidsBackHandlerStub = sinon.stub(); const BIDDER_CODE = 'sampleBidder'; @@ -1186,7 +1264,6 @@ describe('Unit: Prebid Module', function () { logMessageSpy = sinon.spy(utils, 'logMessage'); makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); makeRequestsStub.returns(bidRequests); - xhr = sinon.useFakeXMLHttpRequest(); adUnits = [{ code: 'adUnit-code', @@ -1215,7 +1292,6 @@ describe('Unit: Prebid Module', function () { adapterManager.makeBidRequests.restore(); auctionModule.newAuction.restore(); utils.logMessage.restore(); - xhr.restore(); }); it('should execute callback after timeout', function () { @@ -1233,7 +1309,6 @@ describe('Unit: Prebid Module', function () { spec.isBidRequestValid.returns(true); spec.interpretResponse.returns(bids); - clock = sinon.useFakeTimers(); let requestObj = { bidsBackHandler: null, // does not need to be defined because of newAuction mock in beforeEach timeout: 2000, @@ -1287,7 +1362,6 @@ describe('Unit: Prebid Module', function () { auction.getBidsReceived = function() { return [adResponse]; } auction.getAuctionId = () => auctionId; - clock = sinon.useFakeTimers(); let requestObj = { bidsBackHandler: null, // does not need to be defined because of newAuction mock in beforeEach timeout: 2000, @@ -1337,18 +1411,6 @@ describe('Unit: Prebid Module', function () { }); describe('requestBids', function () { - let xhr; - let requests; - - beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - }); - - afterEach(function () { - xhr.restore(); - }); var adUnitsBackup; var auctionManagerStub; let logMessageSpy; @@ -1392,7 +1454,7 @@ describe('Unit: Prebid Module', function () { try { $$PREBID_GLOBAL$$.requestBids({}); } catch (e) { - console.log(e); + console.log(e); // eslint-disable-line } assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); }); @@ -1708,8 +1770,6 @@ describe('Unit: Prebid Module', function () { }); describe('multiformat requests', function () { - let spyCallBids; - let createAuctionStub; let adUnits; beforeEach(function () { @@ -1729,14 +1789,10 @@ describe('Unit: Prebid Module', function () { }]; adUnitCodes = ['adUnit-code']; configObj.setConfig({maxRequestsPerOrigin: Number.MAX_SAFE_INTEGER || 99999999}); - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - spyCallBids = sinon.spy(adapterManager, 'callBids'); - createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); + sinon.spy(adapterManager, 'callBids'); }) afterEach(function () { - auctionModule.newAuction.restore(); adapterManager.callBids.restore(); }); @@ -1759,7 +1815,6 @@ describe('Unit: Prebid Module', function () { const spyArgs = adapterManager.callBids.getCall(0); const biddersCalled = spyArgs.args[0][0].bids; - // only appnexus supports native expect(biddersCalled.length).to.equal(1); }); @@ -1981,6 +2036,228 @@ describe('Unit: Prebid Module', function () { assert.ok(spyEventsOn.calledWith('bidWon', Function)); events.on.restore(); }); + + describe('beforeRequestBids', function () { + let bidRequestedHandler; + let beforeRequestBidsHandler; + let bidsBackHandler = function bidsBackHandler() {}; + + let bidsBackSpy; + let bidRequestedSpy; + let beforeRequestBidsSpy; + + beforeEach(function () { + resetAuction(); + bidsBackSpy = sinon.spy(bidsBackHandler); + googletag.pubads().setSlots(createSlotArrayScenario2()); + }); + + afterEach(function () { + bidsBackSpy.resetHistory(); + + if (bidRequestedSpy) { + $$PREBID_GLOBAL$$.offEvent('bidRequested', bidRequestedSpy); + bidRequestedSpy.resetHistory(); + } + + if (beforeRequestBidsSpy) { + $$PREBID_GLOBAL$$.offEvent('beforeRequestBids', beforeRequestBidsSpy); + beforeRequestBidsSpy.resetHistory(); + } + }); + + it('should allow creation of a fpd.context.pbAdSlot property on adUnits from inside the event handler', function () { + // verify adUnits passed to handler then alter the adUnits + beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) { + expect(beforeRequestBidsAdUnits).to.be.a('array'); + expect(beforeRequestBidsAdUnits).to.have.lengthOf(1); + expect(beforeRequestBidsAdUnits[0]).to.be.a('object'); + // adUnit should not contain a context property yet + expect(beforeRequestBidsAdUnits[0]).to.not.have.property('fpd') + // alter the adUnit by adding the property for context.pbAdSlot + beforeRequestBidsAdUnits[0].fpd = { + context: { + pbAdSlot: '/19968336/header-bid-tag-pbadslot-0' + } + }; + }; + beforeRequestBidsSpy = sinon.spy(beforeRequestBidsHandler); + + // use this handler to verify if the adUnits alterations were applied successfully by the beforeRequestBids handler + bidRequestedHandler = function bidRequestedHandler(bidRequest) { + expect(bidRequest).to.be.a('object'); + expect(bidRequest).to.have.property('bids'); + expect(bidRequest.bids).to.be.a('array'); + expect(bidRequest.bids).to.have.lengthOf(1); + const bid = bidRequest['bids'][0]; + expect(bid).to.be.a('object'); + expect(bid).to.have.property('fpd'); + expect(bid.fpd).to.be.a('object'); + expect(bid.fpd).to.have.property('context'); + expect(bid.fpd.context).to.be.a('object'); + expect(bid.fpd.context).to.have.property('pbAdSlot'); + expect(bid.fpd.context.pbAdSlot).to.equal('/19968336/header-bid-tag-pbadslot-0'); + }; + bidRequestedSpy = sinon.spy(bidRequestedHandler); + + $$PREBID_GLOBAL$$.onEvent('beforeRequestBids', beforeRequestBidsSpy); + $$PREBID_GLOBAL$$.onEvent('bidRequested', bidRequestedSpy); + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [{ + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: [[750, 350]] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13122370 + } + }] + }], + bidsBackHandler: bidsBackSpy + }); + + sinon.assert.calledOnce(beforeRequestBidsSpy); + sinon.assert.calledOnce(bidRequestedSpy); + }); + + it('should allow creation of a fpd.context.pbAdSlot property on adUnits from inside the event handler', function () { + // verify adUnits passed to handler then alter the adUnits + beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) { + expect(beforeRequestBidsAdUnits).to.be.a('array'); + expect(beforeRequestBidsAdUnits).to.have.lengthOf(2); + expect(beforeRequestBidsAdUnits[0]).to.be.a('object'); + expect(beforeRequestBidsAdUnits[1]).to.be.a('object'); + // adUnit should not contain a context property yet + expect(beforeRequestBidsAdUnits[0]).to.not.have.property('fpd'); + expect(beforeRequestBidsAdUnits[1]).to.not.have.property('fpd'); + // alter the adUnit by adding the property for context.pbAdSlot + beforeRequestBidsAdUnits[0].fpd = { + context: { + pbAdSlot: '/19968336/header-bid-tag-pbadslot-0' + } + }; + beforeRequestBidsAdUnits[1].fpd = { + context: { + pbAdSlot: '/19968336/header-bid-tag-pbadslot-1' + } + }; + }; + beforeRequestBidsSpy = sinon.spy(beforeRequestBidsHandler); + + // use this handler to verify if the adUnits alterations were applied successfully by the beforeRequestBids handler + bidRequestedHandler = function bidRequestedHandler(bidRequest) { + expect(bidRequest).to.be.a('object'); + expect(bidRequest).to.have.property('bids'); + expect(bidRequest.bids).to.be.a('array'); + expect(bidRequest.bids).to.have.lengthOf(2); + const bid0 = bidRequest['bids'][0]; + expect(bid0).to.be.a('object'); + expect(bid0).to.have.property('fpd'); + expect(bid0.fpd).to.be.a('object'); + expect(bid0.fpd).to.have.property('context'); + expect(bid0.fpd.context).to.be.a('object'); + expect(bid0.fpd.context).to.have.property('pbAdSlot'); + expect(bid0.fpd.context.pbAdSlot).to.equal('/19968336/header-bid-tag-pbadslot-0'); + + const bid1 = bidRequest['bids'][1]; + expect(bid1).to.be.a('object'); + expect(bid1).to.have.property('fpd'); + expect(bid1.fpd).to.be.a('object'); + expect(bid1.fpd).to.have.property('context'); + expect(bid1.fpd.context).to.be.a('object'); + expect(bid1.fpd.context).to.have.property('pbAdSlot'); + expect(bid1.fpd.context.pbAdSlot).to.equal('/19968336/header-bid-tag-pbadslot-1'); + }; + bidRequestedSpy = sinon.spy(bidRequestedHandler); + + $$PREBID_GLOBAL$$.onEvent('beforeRequestBids', beforeRequestBidsSpy); + $$PREBID_GLOBAL$$.onEvent('bidRequested', bidRequestedSpy); + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [{ + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: [[750, 350]] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13122370 + } + }] + }, { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[750, 350]] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 14122380 + } + }] + }], + bidsBackHandler: bidsBackSpy + }); + + sinon.assert.calledOnce(beforeRequestBidsSpy); + sinon.assert.calledOnce(bidRequestedSpy); + }); + + it('should not create a context property on adUnits if not added by handler', function () { + // verify adUnits passed to handler then alter the adUnits + beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) { + expect(beforeRequestBidsAdUnits).to.be.a('array'); + expect(beforeRequestBidsAdUnits).to.have.lengthOf(1); + expect(beforeRequestBidsAdUnits[0]).to.be.a('object'); + // adUnit should not contain a context property yet + expect(beforeRequestBidsAdUnits[0]).to.not.have.property('context') + }; + beforeRequestBidsSpy = sinon.spy(beforeRequestBidsHandler); + + // use this handler to verify if the adUnits alterations were applied successfully by the beforeRequestBids handler + bidRequestedHandler = function bidRequestedHandler(bidRequest) { + expect(bidRequest).to.be.a('object'); + expect(bidRequest).to.have.property('bids'); + expect(bidRequest.bids).to.be.a('array'); + expect(bidRequest.bids).to.have.lengthOf(1); + const bid = bidRequest['bids'][0]; + expect(bid).to.be.a('object'); + expect(bid).to.not.have.property('context'); + }; + bidRequestedSpy = sinon.spy(bidRequestedHandler); + + $$PREBID_GLOBAL$$.onEvent('beforeRequestBids', beforeRequestBidsSpy); + $$PREBID_GLOBAL$$.onEvent('bidRequested', bidRequestedSpy); + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [{ + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: [[750, 350]] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13122370 + } + }] + }], + bidsBackHandler: bidsBackSpy + }); + + sinon.assert.calledOnce(beforeRequestBidsSpy); + sinon.assert.calledOnce(bidRequestedSpy); + }); + }); }); describe('offEvent', function () { diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index f0f26bf5653..566154f0003 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -1,8 +1,8 @@ import { _sendAdToCreative -} from '../../../src/secureCreatives'; +} from '../../../src/secureCreatives.js'; import { expect } from 'chai'; -import * as utils from 'src/utils'; +import * as utils from 'src/utils.js'; describe('secureCreatives', () => { describe('_sendAdToCreative', () => { @@ -30,14 +30,14 @@ describe('secureCreatives', () => { cpm: '1.00', adUnitCode: 'some_dom_id' }; - const remoteDomain = '*'; - const source = { - postMessage: sinon.stub() + const event = { + source: { postMessage: sinon.stub() }, + origin: 'origin.sf.com' }; - _sendAdToCreative(mockAdObject, remoteDomain, source); - expect(JSON.parse(source.postMessage.args[0][0]).ad).to.equal(''); - expect(JSON.parse(source.postMessage.args[0][0]).adUrl).to.equal('http://creative.prebid.org/1.00'); + _sendAdToCreative(mockAdObject, event); + expect(JSON.parse(event.source.postMessage.args[0][0]).ad).to.equal(''); + expect(JSON.parse(event.source.postMessage.args[0][0]).adUrl).to.equal('http://creative.prebid.org/1.00'); window.googletag = oldVal; window.apntag = oldapntag; }); diff --git a/test/spec/url_spec.js b/test/spec/url_spec.js deleted file mode 100644 index 90047273043..00000000000 --- a/test/spec/url_spec.js +++ /dev/null @@ -1,94 +0,0 @@ -import {format, parse} from '../../src/url'; -import { expect } from 'chai'; - -describe('helpers.url', function () { - describe('parse()', function () { - let parsed; - - beforeEach(function () { - parsed = parse('http://example.com:3000/pathname/?search=test&foo=bar&bar=foo%26foo%3Dxxx#hash'); - }); - - it('extracts the protocol', function () { - expect(parsed).to.have.property('protocol', 'http'); - }); - - it('extracts the hostname', function () { - expect(parsed).to.have.property('hostname', 'example.com'); - }); - - it('extracts the port', function () { - expect(parsed).to.have.property('port', 3000); - }); - - it('extracts the pathname', function () { - expect(parsed).to.have.property('pathname', '/pathname/'); - }); - - it('extracts the search query', function () { - expect(parsed).to.have.property('search'); - expect(parsed.search).to.eql({ - foo: 'xxx', - search: 'test', - bar: 'foo', - }); - }); - - it('extracts the hash', function () { - expect(parsed).to.have.property('hash', 'hash'); - }); - - it('extracts the host', function () { - expect(parsed).to.have.property('host', 'example.com:3000'); - }); - }); - - describe('parse(url, {noDecodeWholeURL: true})', function () { - let parsed; - - beforeEach(function () { - parsed = parse('http://example.com:3000/pathname/?search=test&foo=bar&bar=foo%26foo%3Dxxx#hash', {noDecodeWholeURL: true}); - }); - - it('extracts the search query', function () { - expect(parsed).to.have.property('search'); - expect(parsed.search).to.eql({ - foo: 'bar', - search: 'test', - bar: 'foo%26foo%3Dxxx', - }); - }); - }); - - describe('format()', function () { - it('formats an object in to a URL', function () { - expect(format({ - protocol: 'http', - hostname: 'example.com', - port: 3000, - pathname: '/pathname/', - search: {foo: 'bar', search: 'test', bar: 'foo%26foo%3Dxxx'}, - hash: 'hash' - })).to.equal('http://example.com:3000/pathname/?foo=bar&search=test&bar=foo%26foo%3Dxxx#hash'); - }); - - it('will use defaults for missing properties', function () { - expect(format({ - hostname: 'example.com' - })).to.equal('http://example.com'); - }); - }); - - describe('parse(url, {decodeSearchAsString: true})', function () { - let parsed; - - beforeEach(function () { - parsed = parse('http://example.com:3000/pathname/?search=test&foo=bar&bar=foo%26foo%3Dxxx#hash', {decodeSearchAsString: true}); - }); - - it('extracts the search query', function () { - 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 806e86e998b..2b68349ca31 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -1,8 +1,8 @@ import { expect } from 'chai'; -import { config } from 'src/config'; +import { config } from 'src/config.js'; // Use require since we need to be able to write to these vars const utils = require('../../src/utils'); -let { newUserSync } = require('../../src/userSync'); +let { newUserSync, USERSYNC_DEFAULT_CONFIG } = require('../../src/userSync'); describe('user sync', function () { let triggerPixelStub; @@ -34,6 +34,7 @@ describe('user sync', function () { }); beforeEach(function () { + config.setConfig({ userSync: USERSYNC_DEFAULT_CONFIG }); triggerPixelStub = sinon.stub(utils, 'triggerPixel'); logWarnStub = sinon.stub(utils, 'logWarn'); shuffleStub = sinon.stub(utils, 'shuffle').callsFake((array) => array.reverse()); @@ -47,6 +48,7 @@ describe('user sync', function () { shuffleStub.restore(); getUniqueIdentifierStrStub.restore(); insertUserSyncIframeStub.restore(); + config.resetConfig(); }); it('should register and fire a pixel URL', function () { @@ -374,6 +376,85 @@ describe('user sync', function () { expect(insertUserSyncIframeStub.getCall(0)).to.be.null; }); + it('should still allow default image syncs if setConfig only defined iframe', function () { + const userSync = newUserSync({ + config: config.getConfig('userSync'), + browserSupportsCookies: true + }); + + config.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: ['bidderXYZ'], + filter: 'include' + } + } + } + }); + + userSync.registerSync('image', 'testBidder', 'http://example.com'); + userSync.registerSync('iframe', 'bidderXYZ', 'http://example.com/iframe'); + userSync.syncUsers(); + expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com'); + expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe'); + }); + + it('should override default image syncs if setConfig used image filter', function () { + const userSync = newUserSync({ + config: config.getConfig('userSync'), + browserSupportsCookies: true + }); + + config.setConfig({ + userSync: { + filterSettings: { + image: { + bidders: ['bidderXYZ'], + filter: 'exclude' + } + } + } + }); + + userSync.registerSync('image', 'testBidder', 'http://example.com'); + userSync.registerSync('image', 'bidderXYZ', 'http://example.com/image-blocked'); + userSync.syncUsers(); + expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com'); + expect(triggerPixelStub.getCall(1)).to.be.null; + }); + + it('should override default image syncs if setConfig used all filter', function() { + const userSync = newUserSync({ + config: config.getConfig('userSync'), + browserSupportsCookies: true + }); + + config.setConfig({ + userSync: { + filterSettings: { + all: { + bidders: ['bidderXYZ'], + filter: 'exclude' + } + } + } + }); + + userSync.registerSync('image', 'testBidder', 'http://example.com'); + userSync.registerSync('image', 'bidderXYZ', 'http://example.com/image-blocked'); + userSync.registerSync('iframe', 'testBidder', 'http://example.com/iframe'); + userSync.registerSync('iframe', 'bidderXYZ', 'http://example.com/iframe-blocked'); + userSync.syncUsers(); + expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com'); + expect(triggerPixelStub.getCall(1)).to.be.null; + expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe'); + expect(insertUserSyncIframeStub.getCall(1)).to.be.null; + }); + describe('publicAPI', function () { describe('canBidderRegisterSync', function () { describe('with filterSettings', function () { diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js old mode 100755 new mode 100644 index 012dfacd92f..fca59633ebe --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,7 +1,7 @@ -import { getAdServerTargeting } from 'test/fixtures/fixtures'; +import { getAdServerTargeting } from 'test/fixtures/fixtures.js'; import { expect } from 'chai'; import CONSTANTS from 'src/constants.json'; -import * as utils from 'src/utils'; +import * as utils from 'src/utils.js'; var assert = require('assert'); @@ -18,24 +18,6 @@ describe('Utils', function () { type_array = 'Array', type_function = 'Function'; - 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('getBidIdParameter', function () { it('should return value of the key in input object', function () { var obj = { @@ -81,12 +63,12 @@ describe('Utils', function () { describe('parseQueryStringParameters', function () { it('should append query string to existing using the input obj', function () { var obj = { - a: '1', - b: '2' + a: 'http://example.com/?foo=bar&bar=foo', + b: 'abc["def"]' }; var output = utils.parseQueryStringParameters(obj); - var expectedResult = 'a=' + encodeURIComponent('1') + '&b=' + encodeURIComponent('2') + '&'; + var expectedResult = 'a=' + encodeURIComponent('http://example.com/?foo=bar&bar=foo') + '&b=' + encodeURIComponent('abc["def"]'); assert.equal(output, expectedResult); }); @@ -699,20 +681,6 @@ 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 = ``; - assert.equal(output, expected); - }); - }); - describe('getDefinedParams', function () { it('builds an object consisting of defined params', function () { const adUnit = { @@ -833,6 +801,98 @@ describe('Utils', function () { }); }); + describe('URL helpers', function () { + describe('parseUrl()', function () { + let parsed; + + beforeEach(function () { + parsed = utils.parseUrl('http://example.com:3000/pathname/?search=test&foo=bar&bar=foo%26foo%3Dxxx#hash'); + }); + + it('extracts the protocol', function () { + expect(parsed).to.have.property('protocol', 'http'); + }); + + it('extracts the hostname', function () { + expect(parsed).to.have.property('hostname', 'example.com'); + }); + + it('extracts the port', function () { + expect(parsed).to.have.property('port', 3000); + }); + + it('extracts the pathname', function () { + expect(parsed).to.have.property('pathname', '/pathname/'); + }); + + it('extracts the search query', function () { + expect(parsed).to.have.property('search'); + expect(parsed.search).to.eql({ + foo: 'xxx', + search: 'test', + bar: 'foo', + }); + }); + + it('extracts the hash', function () { + expect(parsed).to.have.property('hash', 'hash'); + }); + + it('extracts the host', function () { + expect(parsed).to.have.property('host', 'example.com:3000'); + }); + }); + + describe('parseUrl(url, {noDecodeWholeURL: true})', function () { + let parsed; + + beforeEach(function () { + parsed = utils.parseUrl('http://example.com:3000/pathname/?search=test&foo=bar&bar=foo%26foo%3Dxxx#hash', {noDecodeWholeURL: true}); + }); + + it('extracts the search query', function () { + expect(parsed).to.have.property('search'); + expect(parsed.search).to.eql({ + foo: 'bar', + search: 'test', + bar: 'foo%26foo%3Dxxx', + }); + }); + }); + + describe('buildUrl()', function () { + it('formats an object in to a URL', function () { + expect(utils.buildUrl({ + protocol: 'http', + hostname: 'example.com', + port: 3000, + pathname: '/pathname/', + search: {foo: 'bar', search: 'test', bar: 'foo%26foo%3Dxxx'}, + hash: 'hash' + })).to.equal('http://example.com:3000/pathname/?foo=bar&search=test&bar=foo%26foo%3Dxxx#hash'); + }); + + it('will use defaults for missing properties', function () { + expect(utils.buildUrl({ + hostname: 'example.com' + })).to.equal('http://example.com'); + }); + }); + + describe('parseUrl(url, {decodeSearchAsString: true})', function () { + let parsed; + + beforeEach(function () { + parsed = utils.parseUrl('http://example.com:3000/pathname/?search=test&foo=bar&bar=foo%26foo%3Dxxx#hash', {decodeSearchAsString: true}); + }); + + it('extracts the search query', function () { + expect(parsed).to.have.property('search'); + expect(parsed.search).to.equal('?search=test&foo=bar&bar=foo&foo=xxx'); + }); + }); + }); + describe('transformBidderParamKeywords', function () { it('returns an array of objects when keyvalue is an array', function () { let keywords = { @@ -922,4 +982,200 @@ describe('Utils', function () { }); }); }); + + describe('isSafariBrowser', function () { + let userAgentStub; + let userAgent; + + before(function () { + userAgentStub = sinon.stub(navigator, 'userAgent').get(function () { + return userAgent; + }); + }); + + after(function () { + userAgentStub.restore(); + }); + + it('properly detects safari', function () { + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'; + expect(utils.isSafariBrowser()).to.equal(true); + }); + it('does not flag Chrome on MacOS', function () { + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'; + expect(utils.isSafariBrowser()).to.equal(false); + }); + it('does not flag Chrome iOS', function () { + userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/80.0.3987.95 Mobile/15E148 Safari/604.1'; + expect(utils.isSafariBrowser()).to.equal(false); + }); + it('does not flag Firefox iOS', function () { + userAgent = 'Mozilla/5.0 (iPhone; CPU OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/23.0 Mobile/15E148 Safari/605.1.15'; + expect(utils.isSafariBrowser()).to.equal(false); + }); + it('does not flag Windows Edge', function () { + userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43'; + expect(utils.isSafariBrowser()).to.equal(false); + }); + }); + + describe('mergeDeep', function() { + it('properly merge objects that share same property names', function() { + const object1 = { + propA: { + subPropA: 'abc' + } + }; + const object2 = { + propA: { + subPropB: 'def' + } + }; + + const resultWithoutMergeDeep = Object.assign({}, object1, object2); + expect(resultWithoutMergeDeep).to.deep.equal({ + propA: { + subPropB: 'def' + } + }); + + const resultWithMergeDeep = utils.mergeDeep({}, object1, object2); + expect(resultWithMergeDeep).to.deep.equal({ + propA: { + subPropA: 'abc', + subPropB: 'def' + } + }); + }); + + it('properly merge objects that have different depths', function() { + const object1 = { + depth0_A: { + depth1_A: { + depth2_A: 123 + } + } + }; + const object2 = { + depth0_A: { + depth1_A: { + depth2_B: { + depth3_A: { + depth4_A: 'def' + } + } + }, + depth1_B: 'abc' + } + }; + const object3 = { + depth0_B: 456 + }; + + const result = utils.mergeDeep({}, object1, object2, object3); + expect(result).to.deep.equal({ + depth0_A: { + depth1_A: { + depth2_A: 123, + depth2_B: { + depth3_A: { + depth4_A: 'def' + } + } + }, + depth1_B: 'abc' + }, + depth0_B: 456 + }); + }); + + it('properly merge objects with various property types', function() { + const object1 = { + depth0_A: { + depth1_A: ['a', 'b', 'c'], + depth1_B: 'abc', + depth1_C: 123 + } + }; + const object2 = { + depth0_A: { + depth1_A: ['d', 'e', 'f'], + depth1_D: true, + } + }; + + const result = utils.mergeDeep({}, object1, object2); + expect(result).to.deep.equal({ + depth0_A: { + depth1_A: ['a', 'b', 'c', 'd', 'e', 'f'], + depth1_B: 'abc', + depth1_C: 123, + depth1_D: true, + } + }); + }); + }); + + describe('deepEqual', function() { + it('should return "true" if comparing the same object', function() { + const obj1 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] }, + ], + }, + }; + const obj2 = obj1; + expect(utils.deepEqual(obj1, obj2)).to.equal(true); + }); + it('should return "true" if two deeply nested objects are equal', function() { + const obj1 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] }, + ], + }, + }; + const obj2 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] }, + ], + }, + }; + expect(utils.deepEqual(obj1, obj2)).to.equal(true); + }); + it('should return "true" if comparting the same primitive values', function() { + const primitive1 = 'Prebid.js'; + const primitive2 = 'Prebid.js'; + expect(utils.deepEqual(primitive1, primitive2)).to.equal(true); + }); + it('should return "false" if comparing two different primitive values', function() { + const primitive1 = 12; + const primitive2 = 123; + expect(utils.deepEqual(primitive1, primitive2)).to.equal(false); + }); + it('should return "false" if comparing two different deeply nested objects', function() { + const obj1 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] }, + ], + }, + }; + const obj2 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [728, 90]] }, + ], + }, + } + expect(utils.deepEqual(obj1, obj2)).to.equal(false); + }); + }); }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 8f423a799ac..6bb214af8a0 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,10 +1,54 @@ -import 'mocha/mocha'; import chai from 'chai'; -import { getCacheUrl, store } from 'src/videoCache'; -import { config } from 'src/config'; +import { getCacheUrl, store } from 'src/videoCache.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; const should = chai.should(); +function getMockBid(bidder, auctionId, bidderRequestId) { + return { + 'bidder': bidder, + 'params': { + 'placementId': '10433394', + 'member': 123, + 'keywords': { + 'foo': ['bar', 'baz'], + 'fizz': ['buzz'] + } + }, + 'bid_id': '12345abc', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', + 'sizes': [300, 250], + 'bidId': '123', + 'bidderRequestId': bidderRequestId, + 'auctionId': auctionId, + 'storedAuctionResponse': 11111 + }; +} + +function getMockBidRequest(bidder = 'appnexus', auctionId = '173afb6d132ba3', bidderRequestId = '3d1063078dfcc8') { + return { + 'bidderCode': bidder, + 'auctionId': auctionId, + 'bidderRequestId': bidderRequestId, + 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'bids': [getMockBid(bidder, auctionId, bidderRequestId)], + 'auctionStart': 1510852447530, + 'timeout': 5000, + 'src': 's2s', + 'doneCbCallCount': 0, + 'refererInfo': { + 'referer': 'http://mytestpage.com' + } + } +} + describe('The video cache', function () { function assertError(callbackSpy) { callbackSpy.calledOnce.should.equal(true); @@ -17,24 +61,11 @@ describe('The video cache', function () { } describe('when the cache server is unreachable', function () { - let xhr; - let requests; - - beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = (request) => requests.push(request); - }); - - afterEach(function () { - xhr.restore(); - }); - it('should execute the callback with an error when store() is called', function () { const callback = sinon.spy(); store([{ vastUrl: 'my-mock-url.com' }], callback); - requests[0].respond(503, { + server.requests[0].respond(503, { 'Content-Type': 'plain/text', }, 'The server could not save anything at the moment.'); @@ -44,13 +75,7 @@ describe('The video cache', function () { }); describe('when the cache server is available', function () { - let xhr; - let requests; - beforeEach(function () { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = (request) => requests.push(request); config.setConfig({ cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache' @@ -59,7 +84,6 @@ describe('The video cache', function () { }); afterEach(function () { - xhr.restore(); config.resetConfig(); }); @@ -139,7 +163,7 @@ describe('The video cache', function () { }]; store(bids, function () { }); - const request = requests[0]; + const request = server.requests[0]; request.method.should.equal('POST'); request.url.should.equal('https://prebid.adnxs.com/pbc/v1/cache'); request.requestHeaders['Content-Type'].should.equal('text/plain;charset=utf-8'); @@ -187,7 +211,7 @@ describe('The video cache', function () { }]; store(bids, function () { }); - const request = requests[0]; + const request = server.requests[0]; request.method.should.equal('POST'); request.url.should.equal('https://prebid.adnxs.com/pbc/v1/cache'); request.requestHeaders['Content-Type'].should.equal('text/plain;charset=utf-8'); @@ -212,10 +236,65 @@ describe('The video cache', function () { JSON.parse(request.requestBody).should.deep.equal(payload); }); + it('should include additional params in request payload should config.cache.vasttrack be true and bidderRequest argument was defined', () => { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache', + vasttrack: true + } + }); + + const customKey1 = 'vasttrack_123'; + const customKey2 = 'vasttrack_abc'; + const vastXml1 = 'testvast1'; + const vastXml2 = 'testvast2'; + + const bids = [{ + vastXml: vastXml1, + ttl: 25, + customCacheKey: customKey1, + requestId: '12345abc', + bidder: 'appnexus' + }, { + vastXml: vastXml2, + ttl: 25, + customCacheKey: customKey2, + requestId: 'cba54321', + bidder: 'rubicon' + }]; + + store(bids, function () { }, getMockBidRequest()); + const request = server.requests[0]; + request.method.should.equal('POST'); + request.url.should.equal('https://prebid.adnxs.com/pbc/v1/cache'); + request.requestHeaders['Content-Type'].should.equal('text/plain;charset=utf-8'); + let payload = { + puts: [{ + type: 'xml', + value: vastXml1, + ttlseconds: 25, + key: customKey1, + bidid: '12345abc', + bidder: 'appnexus', + timestamp: 1510852447530 + }, { + type: 'xml', + value: vastXml2, + ttlseconds: 25, + key: customKey2, + bidid: 'cba54321', + bidder: 'rubicon', + timestamp: 1510852447530 + }] + }; + + JSON.parse(request.requestBody).should.deep.equal(payload); + }); + function assertRequestMade(bid, expectedValue) { store([bid], function () { }); - const request = requests[0]; + const request = server.requests[0]; request.method.should.equal('POST'); request.url.should.equal('https://prebid.adnxs.com/pbc/v1/cache'); request.requestHeaders['Content-Type'].should.equal('text/plain;charset=utf-8'); @@ -232,7 +311,7 @@ describe('The video cache', function () { function fakeServerCall(bid, responseBody) { const callback = sinon.spy(); store([bid], callback); - requests[0].respond( + server.requests[0].respond( 200, { 'Content-Type': 'application/json', diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index dc6c11a7491..72a585049c3 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,4 +1,4 @@ -import { isValidVideoBid } from 'src/video'; +import { isValidVideoBid } from 'src/video.js'; describe('video.js', function () { it('validates valid instream bids', function () { diff --git a/test/test_index.js b/test/test_index.js index 206f1be1f52..53d75e36176 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,5 +1,6 @@ require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); +require('test/mocks/xhr.js'); var testsContext = require.context('.', true, /_spec$/); testsContext.keys().forEach(testsContext); diff --git a/wdio.conf.js b/wdio.conf.js index 22765e34346..4d93f3d88d3 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -9,22 +9,24 @@ function getCapabilities() { return platformMap[os]; } - // only Edge 16, Chrome 74 & Firefox 66 run as part of functional tests + // only IE 11, Chrome 80 & Firefox 73 run as part of functional tests // rest of the browsers are discarded. - delete browsers['bs_ie_11_windows_10']; - delete browsers['bs_edge_17_windows_10']; - delete browsers['bs_chrome_75_windows_10']; - delete browsers['bs_firefox_67_windows_10']; - delete browsers['bs_safari_11_mac_high_sierra']; + delete browsers['bs_chrome_79_windows_10']; + delete browsers['bs_firefox_72_windows_10']; + delete browsers['bs_safari_11_mac_catalina']; delete browsers['bs_safari_12_mac_mojave']; + // disable all edge browsers due to wdio bug for switchToFrame: https://github.com/webdriverio/webdriverio/issues/3880 + delete browsers['bs_edge_18_windows_10']; + delete browsers['bs_edge_17_windows_10']; let capabilities = [] Object.keys(browsers).forEach(key => { let browser = browsers[key]; capabilities.push({ browserName: browser.browser, - platform: getPlatform(browser.os), - version: browser.browser_version, + os: getPlatform(browser.os), + os_version: browser.os_version, + browser_version: browser.browser_version, acceptSslCerts: true, 'browserstack.networkLogs': true, 'browserstack.console': 'verbose', @@ -35,28 +37,29 @@ function getCapabilities() { } exports.config = { - // TODO: below change is only for testing and is to be removed. - specs: [ - './test/spec/e2e/banner/*.spec.js' - ], - services: ['browserstack'], - user: process.env.BROWSERSTACK_USERNAME, - key: process.env.BROWSERSTACK_ACCESS_KEY, - browserstackLocal: true, - // Do not increase this, since we have only 5 parallel tests in browserstack account - maxInstances: 5, - capabilities: getCapabilities(), - logLevel: 'silent', // Level of logging verbosity: silent | verbose | command | data | result | error - coloredLogs: true, - waitforTimeout: 60000, // Default timeout for all waitFor* commands. - connectionRetryTimeout: 60000, // Default timeout in milliseconds for request if Selenium Grid doesn't send response - connectionRetryCount: 3, // Default request retries count - framework: 'mocha', - mochaOpts: { - ui: 'bdd', - timeout: 60000, - compilers: ['js:babel-register'], - }, - // if you see error, update this to spec reporter and logLevel above to get detailed report. - reporters: ['concise'] -}; + specs: [ + './test/spec/e2e/**/*.spec.js' + ], + services: [ + ['browserstack', { + browserstackLocal: true + }] + ], + user: process.env.BROWSERSTACK_USERNAME, + key: process.env.BROWSERSTACK_ACCESS_KEY, + maxInstances: 5, // Do not increase this, since we have only 5 parallel tests in browserstack account + capabilities: getCapabilities(), + logLevel: 'silent', // put option here: info | trace | debug | warn| error | silent + bail: 0, + waitforTimeout: 60000, // Default timeout for all waitFor* commands. + connectionRetryTimeout: 60000, // Default timeout in milliseconds for request if Selenium Grid doesn't send response + connectionRetryCount: 3, // Default request retries count + framework: 'mocha', + mochaOpts: { + ui: 'bdd', + timeout: 60000, + compilers: ['js:babel-register'], + }, + // if you see error, update this to spec reporter and logLevel above to get detailed report. + reporters: ['concise'] +}