diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..175cc2a4705 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,34 @@ + + +## Type of change + +- [ ] Bugfix +- [ ] Feature +- [ ] New bidder adapter +- [ ] Code style update (formatting, local variables) +- [ ] Refactoring (no functional changes, no api changes) +- [ ] Build related changes +- [ ] CI related changes +- [ ] Other + +## Description of change + + + +- test parameters for validating bids +``` +{ + bidder: '', + params: { + // ... + } +} +``` +- contact email of the adapter’s maintainer +- [ ] official adapter submission + + +## Other information + diff --git a/CHANGELOG b/CHANGELOG index 7f80d1c99ee..6b828b342fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +AOL Prebid 1.3.0 +---------------- +Updated to Prebid 0.13.0. + + AOL Prebid 1.2.0 ---------------- Updated to Prebid 0.12.0. diff --git a/adapters.json b/adapters.json index b0e5e41d1a9..369bfad55af 100644 --- a/adapters.json +++ b/adapters.json @@ -1,14 +1,17 @@ [ "aardvark", + "adblade", "adequant", "adform", "admedia", "aol", "appnexus", "appnexusAst", + "getintent", "indexExchange", "kruxlink", "openx", + "piximedia", "pubmatic", "pulsepoint", "rubicon", @@ -24,6 +27,7 @@ "jcm", "underdogmedia", "memeglobal", + "centro", { "appnexus": { "alias": "brealtime" @@ -33,5 +37,10 @@ "appnexus": { "alias": "pagescience" } + }, + { + "appnexusAst": { + "supportedMediaTypes": ["video"] + } } ] diff --git a/browsers.json b/browsers.json new file mode 100644 index 00000000000..a4aa7cc06e4 --- /dev/null +++ b/browsers.json @@ -0,0 +1,198 @@ +{ + "bs_ie_13_windows_10": { + "base": "BrowserStack", + "os_version": "10", + "browser": "edge", + "browser_version": "13.0", + "device": null, + "os": "Windows" + }, + "bs_ie_11_windows_10": { + "base": "BrowserStack", + "os_version": "10", + "browser": "ie", + "browser_version": "11.0", + "device": null, + "os": "Windows" + }, + "bs_firefox_46_windows_10": { + "base": "BrowserStack", + "os_version": "10", + "browser": "firefox", + "browser_version": "46.0", + "device": null, + "os": "Windows" + }, + "bs_chrome_51_windows_10": { + "base": "BrowserStack", + "os_version": "10", + "browser": "chrome", + "browser_version": "51.0", + "device": null, + "os": "Windows" + }, + "bs_ie_11_windows_8.1": { + "base": "BrowserStack", + "os_version": "8.1", + "browser": "ie", + "browser_version": "11.0", + "device": null, + "os": "Windows" + }, + "bs_firefox_46_windows_8.1": { + "base": "BrowserStack", + "os_version": "8.1", + "browser": "firefox", + "browser_version": "46.0", + "device": null, + "os": "Windows" + }, + "bs_chrome_51_windows_8.1": { + "base": "BrowserStack", + "os_version": "8.1", + "browser": "chrome", + "browser_version": "51.0", + "device": null, + "os": "Windows" + }, + "bs_ie_10_windows_8": { + "base": "BrowserStack", + "os_version": "8", + "browser": "ie", + "browser_version": "10.0", + "device": null, + "os": "Windows" + }, + "bs_firefox_46_windows_8": { + "base": "BrowserStack", + "os_version": "8", + "browser": "firefox", + "browser_version": "46.0", + "device": null, + "os": "Windows" + }, + "bs_chrome_51_windows_8": { + "base": "BrowserStack", + "os_version": "8", + "browser": "chrome", + "browser_version": "51.0", + "device": null, + "os": "Windows" + }, + "bs_ie_11_windows_7": { + "base": "BrowserStack", + "os_version": "7", + "browser": "ie", + "browser_version": "11.0", + "device": null, + "os": "Windows" + }, + "bs_ie_10_windows_7": { + "base": "BrowserStack", + "os_version": "7", + "browser": "ie", + "browser_version": "10.0", + "device": null, + "os": "Windows" + }, + "bs_ie_9_windows_7": { + "base": "BrowserStack", + "os_version": "7", + "browser": "ie", + "browser_version": "9.0", + "device": null, + "os": "Windows" + }, + "bs_firefox_46_windows_7": { + "base": "BrowserStack", + "os_version": "7", + "browser": "firefox", + "browser_version": "46.0", + "device": null, + "os": "Windows" + }, + "bs_chrome_51_windows_7": { + "base": "BrowserStack", + "os_version": "7", + "browser": "chrome", + "browser_version": "51.0", + "device": null, + "os": "Windows" + }, + "bs_safari_9.1_mac_elcapitan": { + "base": "BrowserStack", + "os_version": "El Capitan", + "browser": "safari", + "browser_version": "9.1", + "device": null, + "os": "OS X" + }, + "bs_firefox_46_mac_elcapitan": { + "base": "BrowserStack", + "os_version": "El Capitan", + "browser": "firefox", + "browser_version": "46.0", + "device": null, + "os": "OS X" + }, + "bs_chrome_51_mac_elcapitan": { + "base": "BrowserStack", + "os_version": "El Capitan", + "browser": "chrome", + "browser_version": "51.0", + "device": null, + "os": "OS X" + }, + "bs_safari_8_mac_yosemite": { + "base": "BrowserStack", + "os_version": "Yosemite", + "browser": "safari", + "browser_version": "8.0", + "device": null, + "os": "OS X" + }, + "bs_firefox_46_mac_yosemite": { + "base": "BrowserStack", + "os_version": "Yosemite", + "browser": "firefox", + "browser_version": "46.0", + "device": null, + "os": "OS X" + }, + "bs_chrome_51_mac_yosemite": { + "base": "BrowserStack", + "os_version": "Yosemite", + "browser": "chrome", + "browser_version": "51.0", + "device": null, + "os": "OS X" + }, + "bs_safari_7.1_mac_mavericks": { + "base": "BrowserStack", + "os_version": "Mavericks", + "browser": "safari", + "browser_version": "7.1", + "device": null, + "os": "OS X" + }, + "bs_firefox_46_mac_mavericks": { + "base": "BrowserStack", + "os_version": "Mavericks", + "browser": "firefox", + "browser_version": "46.0", + "device": null, + "os": "OS X" + }, + "bs_chrome_49_mac_mavericks": { + "base": "BrowserStack", + "os_version": "Mavericks", + "browser": "chrome", + "browser_version": "49.0", + "device": null, + "os": "OS X" + }, + "Chrome_travis_ci": { + "base": "Chrome", + "flags": ["--no-sandbox"] + } +} diff --git a/gulpHelpers.js b/gulpHelpers.js index 0edf5b4dde1..342cd04dbb8 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -1,7 +1,9 @@ -const fs = require('fs'); +// this will have all of a copy of the normal fs methods as well +const fs = require('fs.extra'); const path = require('path'); const argv = require('yargs').argv; const MANIFEST = 'package.json'; +const exec = require('child_process').exec; module.exports = { parseBrowserArgs: function (argv) { @@ -47,5 +49,62 @@ module.exports = { } catch (error) {} } + }, + + createEnd2EndTestReport : function(targetDestinationDir) { + var browsers = require('./browsers.json'); + var env = ['default']; + var input = 'bs'; + for(var key in browsers) { + if(key.substring(0, input.length) === input) { + env.push(key); + } + } + + //create new directory structure + fs.rmrfSync(targetDestinationDir); + env.forEach(item => { + fs.mkdirpSync(targetDestinationDir + '/' + item); + }); + + //move xml files to newly created directory + var walker = fs.walk('./build/coverage/e2e/reports'); + walker.on("file", function (root, stat, next) { + env.forEach(item => { + if(stat.name.search(item) !== -1) { + var src = root + '/' + stat.name; + var dest = targetDestinationDir + '/' + item + '/' + stat.name; + fs.copy(src, dest, {replace: true}, function(err) { + if(err) { + throw err; + } + }); + } + }); + next(); + }); + + //run junit-viewer to read xml and create html + env.forEach(item => { + //junit-viewer --results="./custom-reports/chrome51" --save="./chrome.html" + var cmd = 'junit-viewer --results="' + targetDestinationDir + '/' + item + '" --save="' + targetDestinationDir + '/' + item +'.html"'; + exec(cmd); + }); + + //create e2e-results.html + var html = 'End to End Testing Result
Note: Refresh in 2-3 seconds if it says "Cannot get ....."
'; + var li = ''; + var tabs = ''; + env.forEach(function(item,i) { + i++; + li = li + '
  • '+item+'
  • '; + tabs = tabs + '
    '; + }); + html = html + '' + tabs; + html = html + '
    '; + + var filepath = targetDestinationDir + '/results.html'; + fs.openSync(filepath, 'w+'); + fs.writeFileSync(filepath, html); } }; diff --git a/gulpfile.js b/gulpfile.js index 4f0f5b89c50..3eb26f8de43 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -27,12 +27,15 @@ var dateString = 'Updated: ' + (new Date()).toISOString().substring(0, 10); var packageNameVersion = prebid.name + '_' + prebid.version; var banner = '/* <%= prebid.name %> v<%= prebid.version %>, ' + dateString + ' */\n'; var analyticsDirectory = '../analytics'; +var port = 9999; // Tasks gulp.task('default', ['clean', 'quality', 'webpack']); gulp.task('serve', ['clean', 'quality', 'devpack', 'webpack', 'watch', 'test']); +gulp.task('serve-nw', ['clean', 'quality', 'devpack', 'webpack', 'watch', 'e2etest']); + gulp.task('run-tests', ['clean', 'quality', 'webpack', 'test']); gulp.task('build', ['clean', 'quality', 'webpack', 'devpack', 'zip']); @@ -170,7 +173,7 @@ gulp.task('watch', function () { gulp.watch(['integrationExamples/gpt/*.html'], ['test']); gulp.watch(['src/**/*.js'], ['quality', 'webpack', 'devpack', 'test']); connect.server({ - port: 9999, + port: port, root: './', livereload: true }); @@ -181,7 +184,7 @@ gulp.task('quality', ['hint', 'jscs']); gulp.task('hint', function () { return gulp.src('src/**/*.js') .pipe(jshint('.jshintrc')) - .pipe(jshint.reporter('default')) + .pipe(jshint.reporter('jshint-stylish')) .pipe(jshint.reporter('fail')); }); @@ -206,3 +209,47 @@ gulp.task('docs', ['clean-docs'], function () { }) .pipe(gulp.dest('docs')); }); + +gulp.task('e2etest', function() { + var cmd = '--env default'; + if(argv.browserstack) { + var browsers = require('./browsers.json'); + var env = []; + var input = 'bs'; + for(var key in browsers) { + if(key.substring(0, input.length) === input) { + env.push(key); + } + } + cmd = '--env default,' + env.join(','); + } + + if(argv.browserstack) { + cmd = cmd + ' --config nightwatch.conf.js'; + } else { + cmd = cmd + ' --config nightwatch.json'; + } + + if (argv.group) { + cmd = cmd + ' --group ' + argv.group; + } + + cmd = cmd + ' --reporter ./test/spec/e2e/custom-reporter/pbjs-html-reporter.js'; + return gulp.src('') + .pipe(shell('nightwatch ' + cmd)); +}); + +gulp.task('e2etest-report', function() { + var targetDestinationDir = './e2etest-report'; + helpers.createEnd2EndTestReport(targetDestinationDir); + connect.server({ + port: port, + root: './', + livereload: true + }); + + setTimeout(function() { + opens('http://localhost:' + port + '/' + targetDestinationDir.slice(2) + '/results.html'); + }, 5000); + +}); diff --git a/integrationExamples/gpt/.gitignore b/integrationExamples/gpt/.gitignore new file mode 100644 index 00000000000..fb64ad0cc6e --- /dev/null +++ b/integrationExamples/gpt/.gitignore @@ -0,0 +1 @@ +pbjs_adblade_example_gpt.html diff --git a/integrationExamples/gpt/load_pbjs_before_dfp_example.html b/integrationExamples/gpt/load_pbjs_before_dfp_example.html new file mode 100644 index 00000000000..76ef49640aa --- /dev/null +++ b/integrationExamples/gpt/load_pbjs_before_dfp_example.html @@ -0,0 +1,265 @@ + + + + + + +

    Prebid.js Test

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

    Prebid.js Test

    +
    + +
    +
    + +
    + + diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 4b9b502aecf..b090d1f0c96 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -4,19 +4,27 @@ Prebid.js integration example + + + + +
    + +
    + + + + + diff --git a/karma.conf.js b/karma.conf.js index 7fecced9845..cd5076c15d8 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -24,204 +24,7 @@ module.exports = function (config) { }, // define browsers - customLaunchers: { - bs_ie_13_windows_10: { - base: 'BrowserStack', - os_version: '10', - browser: 'edge', - browser_version: '13.0', - device: null, - os: 'Windows' - }, - bs_ie_11_windows_10: { - base: 'BrowserStack', - os_version: '10', - browser: 'ie', - browser_version: '11.0', - device: null, - os: 'Windows' - }, - bs_firefox_46_windows_10: { - base: 'BrowserStack', - os_version: '10', - browser: 'firefox', - browser_version: '46.0', - device: null, - os: 'Windows' - }, - bs_chrome_51_windows_10: { - base: 'BrowserStack', - os_version: '10', - browser: 'chrome', - browser_version: '51.0', - device: null, - os: 'Windows' - }, - 'bs_ie_11_windows_8.1': { - base: 'BrowserStack', - os_version: '8.1', - browser: 'ie', - browser_version: '11.0', - device: null, - os: 'Windows' - }, - 'bs_firefox_46_windows_8.1': { - base: 'BrowserStack', - os_version: '8.1', - browser: 'firefox', - browser_version: '46.0', - device: null, - os: 'Windows' - }, - 'bs_chrome_51_windows_8.1': { - base: 'BrowserStack', - os_version: '8.1', - browser: 'chrome', - browser_version: '51.0', - device: null, - os: 'Windows' - }, - bs_ie_10_windows_8: { - base: 'BrowserStack', - os_version: '8', - browser: 'ie', - browser_version: '10.0', - device: null, - os: 'Windows' - }, - bs_firefox_46_windows_8: { - base: 'BrowserStack', - os_version: '8', - browser: 'firefox', - browser_version: '46.0', - device: null, - os: 'Windows' - }, - bs_chrome_51_windows_8: { - base: 'BrowserStack', - os_version: '8', - browser: 'chrome', - browser_version: '51.0', - device: null, - os: 'Windows' - }, - bs_ie_11_windows_7: { - base: 'BrowserStack', - os_version: '7', - browser: 'ie', - browser_version: '11.0', - device: null, - os: 'Windows' - }, - bs_ie_10_windows_7: { - base: 'BrowserStack', - os_version: '7', - browser: 'ie', - browser_version: '10.0', - device: null, - os: 'Windows' - }, - bs_ie_9_windows_7: { - base: 'BrowserStack', - os_version: '7', - browser: 'ie', - browser_version: '9.0', - device: null, - os: 'Windows' - }, - bs_firefox_46_windows_7: { - base: 'BrowserStack', - os_version: '7', - browser: 'firefox', - browser_version: '46.0', - device: null, - os: 'Windows' - }, - bs_chrome_51_windows_7: { - base: 'BrowserStack', - os_version: '7', - browser: 'chrome', - browser_version: '51.0', - device: null, - os: 'Windows' - }, - 'bs_safari_9.1_mac_elcapitan': { - base: 'BrowserStack', - os_version: 'El Capitan', - browser: 'safari', - browser_version: '9.1', - device: null, - os: 'OS X' - }, - bs_firefox_46_mac_elcapitan: { - base: 'BrowserStack', - os_version: 'El Capitan', - browser: 'firefox', - browser_version: '46.0', - device: null, - os: 'OS X' - }, - bs_chrome_51_mac_elcapitan: { - base: 'BrowserStack', - os_version: 'El Capitan', - browser: 'chrome', - browser_version: '51.0', - device: null, - os: 'OS X' - }, - bs_safari_8_mac_yosemite: { - base: 'BrowserStack', - os_version: 'Yosemite', - browser: 'safari', - browser_version: '8.0', - device: null, - os: 'OS X' - }, - bs_firefox_46_mac_yosemite: { - base: 'BrowserStack', - os_version: 'Yosemite', - browser: 'firefox', - browser_version: '46.0', - device: null, - os: 'OS X' - }, - bs_chrome_51_mac_yosemite: { - base: 'BrowserStack', - os_version: 'Yosemite', - browser: 'chrome', - browser_version: '51.0', - device: null, - os: 'OS X' - }, - 'bs_safari_7.1_mac_mavericks': { - base: 'BrowserStack', - os_version: 'Mavericks', - browser: 'safari', - browser_version: '7.1', - device: null, - os: 'OS X' - }, - bs_firefox_46_mac_mavericks: { - base: 'BrowserStack', - os_version: 'Mavericks', - browser: 'firefox', - browser_version: '46.0', - device: null, - os: 'OS X' - }, - bs_chrome_49_mac_mavericks: { - base: 'BrowserStack', - os_version: 'Mavericks', - browser: 'chrome', - browser_version: '49.0', - device: null, - os: 'OS X' - }, - Chrome_travis_ci: { - base: 'Chrome', - flags: ['--no-sandbox'] - } - }, + customLaunchers: require('./browsers.json'), // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter @@ -302,6 +105,10 @@ module.exports = function (config) { // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false, + browserDisconnectTimeout : 10000, // default 2000 + browserDisconnectTolerance : 1, // default 0 + browserNoActivityTimeout : 4*60*1000, //default 10000 + captureTimeout : 4*60*1000, //default 60000 plugins: [ 'karma-browserstack-launcher', diff --git a/loaders/adapterLoader.js b/loaders/adapterLoader.js index dd8b4f44a8c..e34306b17b6 100644 --- a/loaders/adapterLoader.js +++ b/loaders/adapterLoader.js @@ -14,6 +14,7 @@ const adapters = getAdapters('../adapters.json'); const files = fs.readdirSync('src/adapters').map((file) => file.replace(/\.[^/.]+$/, '')); const adapterNames = adapters.map(getNames).filter(getUniques); const aliases = adapters.filter(getAliases); +const videoAdapters = adapters.filter(getVideoAdapters).map(getNames); var options = { start: '/** INSERT ADAPTERS - DO NOT EDIT OR REMOVE */', @@ -57,6 +58,7 @@ function insertAdapters() { const name = Object.keys(adapter)[0]; return `exports.aliasBidAdapter('${name}','${adapter[name].alias}');\n`; })) + .concat(`exports.videoAdapters = ${JSON.stringify(videoAdapters)};`) .join('/*!ADAPTER REGISTER DELIMITER*/'); } @@ -112,4 +114,13 @@ function getAliases(adapter) { return adapter && name && adapter[name].alias; } +/** + * Returns adapter objects that support video + */ +function getVideoAdapters(adapter) { + const name = Object.keys(adapter)[0]; + return adapter && name && adapter[name].supportedMediaTypes + && adapter[name].supportedMediaTypes.includes('video'); +} + module.exports = blockLoader(options); diff --git a/nightwatch.browserstack.json b/nightwatch.browserstack.json new file mode 100644 index 00000000000..6476cb42f05 --- /dev/null +++ b/nightwatch.browserstack.json @@ -0,0 +1,46 @@ +{ + "src_folders": ["./test/spec/e2e"], + "output_folder": "./build/coverage/e2e/reports", + "custom_commands_path": "", + "custom_assertions_path": "", + "page_objects_path": "", + "globals_path": "", + + "selenium" : { + "start_process" : false, + "host" : "hub.browserstack.com", + "port" : 80 + }, + + "test_settings": { + "default": { + "launch_url" : "http://hub.browserstack.com", + "selenium_port" : 80, + "selenium_host" : "hub.browserstack.com", + "silent": true, + "exclude":["custom-assertions","custom-commands","common","custom-reporter"], + "screenshots" : { + "enabled" : false, + "path" : "" + }, + "desiredCapabilities": { + "browserName": "chrome", + "browser_version" : "51.0", + "platform" : "WINDOWS", + + "javascriptEnabled": true, + "acceptSslCerts": true, + + "os" : "WINDOWS", + "os_version" : "7", + "browser" : "Chrome", + "browser_version" : "51.0", + "browserstack.local": true, + "browserstack.debug": true, + "browserstack.selenium_version" : "2.53.0", + "browserstack.user": "${BROWSERSTACK_USERNAME}", + "browserstack.key": "${BROWSERSTACK_KEY}" + } + } + } +} diff --git a/nightwatch.conf.js b/nightwatch.conf.js new file mode 100644 index 00000000000..c0583fee4df --- /dev/null +++ b/nightwatch.conf.js @@ -0,0 +1,19 @@ +module.exports = (function(settings) { + var browsers = require('./browsers.json'); + for(var browser in browsers) { + var desiredCapabilities = { + "browserName": browsers[browser].browser, + "version": browsers[browser].browser_version, + "platform": browsers[browser].os, + "os": browsers[browser].os, + "os_version": browsers[browser].os_version, + "browser": browsers[browser].browser, + "browser_version": browsers[browser].browser_version, + }; + + settings.test_settings[browser] = {} + settings.test_settings[browser]['desiredCapabilities'] = desiredCapabilities; + } + return settings; + +})(require('./nightwatch.browserstack.json')); diff --git a/nightwatch.json b/nightwatch.json new file mode 100644 index 00000000000..851748ee1cf --- /dev/null +++ b/nightwatch.json @@ -0,0 +1,40 @@ +{ + "src_folders": ["./test/spec/e2e"], + "output_folder": "./build/coverage/e2e/reports", + "custom_commands_path" : "", + "custom_assertions_path" : "./test/spec/e2e/custom-assertions", + "page_objects_path" : "", + "globals_path" : "", + "end_session_on_fail" : true, + "skip_testcases_on_fail" : false, + + "selenium" : { + "start_process" : true, + "server_path" : "${SELENIUM_JAR_PATH}", + "log_path" : "", + "host" : "127.0.0.1", + "port" : 4444, + "cli_args" : { + "webdriver.ie.driver" : "" + } + }, + + "test_settings" : { + "default" : { + "launch_url" : "http://localhost", + "selenium_port" : 4444, + "selenium_host" : "localhost", + "silent": true, + "exclude":["custom-assertions","custom-commands","common","custom-reporter"], + "screenshots" : { + "enabled" : false, + "path" : "" + }, + "desiredCapabilities": { + "browserName": "firefox", + "javascriptEnabled": true, + "acceptSslCerts": true + } + } + } +} diff --git a/package.json b/package.json index 7ced7aee04b..f0d758c6d49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.12.0", + "version": "0.13.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -25,14 +25,11 @@ "babel-preset-es2015": "^6.5.0", "block-loader": "^2.1.0", "chai": "^3.3.0", - "chai-as-promised": "^5.1.0", - "clone": "^0.1.17", "coveralls": "^2.11.11", "del": "^2.2.0", + "ejs": "^2.5.1", "es5-shim": "^4.5.2", - "eslint": "^1.9.0", - "express": "^4.10.2", - "fuzzyset.js": "0.0.1", + "fs.extra": "^1.3.2", "gulp": "^3.8.7", "gulp-babel": "^6.1.2", "gulp-clean": "^0.3.1", @@ -52,7 +49,7 @@ "gulp-zip": "^3.1.0", "istanbul": "^0.3.2", "istanbul-instrumenter-loader": "^0.1.2", - "jquery": "1.11.1", + "jshint-stylish": "^2.2.1", "json-loader": "^0.5.1", "karma": "^0.13.2", "karma-babel-preprocessor": "^6.0.1", @@ -77,15 +74,13 @@ "karma-sinon": "^1.0.4", "karma-webpack": "^1.5.1", "localtunnel": "^1.3.0", - "lodash": "^2.4.1", + "mkpath": "^1.0.0", "mocha": "^1.21.4", + "nightwatch": "^0.9.5", "open": "0.0.5", "phantomjs": "^1.9.18", "querystringify": "0.0.3", - "raw-loader": "^0.5.1", - "redis": "^0.12.1", "requirejs": "^2.1.20", - "run-sequence": "^1.1.4", "sinon": "^1.12.1", "string-replace-webpack-plugin": "0.0.3", "uglify-js": "^2.4.15", diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 8a2cabc772f..ce82bcd47dd 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -17,6 +17,7 @@ function getBids({ bidderCode, requestId, bidderRequestId, adUnits }) { return adUnit.bids.filter(bid => bid.bidder === bidderCode) .map(bid => Object.assign(bid, { placementCode: adUnit.code, + mediaType: adUnit.mediaType, sizes: adUnit.sizes, bidId: utils.getUniqueIdentifierStr(), bidderRequestId, @@ -28,8 +29,10 @@ function getBids({ bidderCode, requestId, bidderRequestId, adUnits }) { exports.callBids = ({ adUnits, cbTimeout }) => { const requestId = utils.generateUUID(); + const auctionStart = Date.now(); + const auctionInit = { - timestamp: Date.now(), + timestamp: auctionStart, requestId, }; events.emit(CONSTANTS.EVENTS.AUCTION_INIT, auctionInit); @@ -44,6 +47,7 @@ exports.callBids = ({ adUnits, cbTimeout }) => { bidderRequestId, bids: getBids({ bidderCode, requestId, bidderRequestId, adUnits }), start: new Date().getTime(), + auctionStart: auctionStart, timeout: cbTimeout }; utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`); diff --git a/src/adapters/aardvark.js b/src/adapters/aardvark.js index 28f750f4375..48407660a8f 100644 --- a/src/adapters/aardvark.js +++ b/src/adapters/aardvark.js @@ -58,7 +58,6 @@ var AardvarkAdapter = function AardvarkAdapter() { })[0]; var returnedBidIDs = {}; - var placementIDmap = {}; if (rtkResponseObj.length > 0) { rtkResponseObj.forEach(function (bid) { @@ -68,7 +67,7 @@ var AardvarkAdapter = function AardvarkAdapter() { return r.params.sc === bid.id; })[0]; if (currentBid) { - var bidResponse = bidfactory.createBid(1); + var bidResponse = bidfactory.createBid(1, currentBid); bidResponse.bidderCode = "aardvark"; bidResponse.cpm = bid.cpm; bidResponse.ad = bid.adm; @@ -86,19 +85,12 @@ var AardvarkAdapter = function AardvarkAdapter() { } //All bids are back - lets add a bid response for anything that did not receive a bid. - var initialSC = []; - bidsObj.bids.forEach(function (bid) { - initialSC.push(bid.params.sc); - placementIDmap[bid.params.sc] = bid.placementCode; - }); - - let difference = initialSC.filter(x => Object.keys(returnedBidIDs).indexOf(x) === -1); + let difference = bidsObj.bids.filter(x => Object.keys(returnedBidIDs).indexOf(x.params.sc) === -1); - difference.forEach(function (shortcode) { - var bidResponse = bidfactory.createBid(2); - var placementcode = placementIDmap[shortcode]; + difference.forEach(function (bidRequest) { + var bidResponse = bidfactory.createBid(2, bidRequest); bidResponse.bidderCode = "aardvark"; - bidmanager.addBidResponse(placementcode, bidResponse); + bidmanager.addBidResponse(bidRequest.placementCode, bidResponse); }); diff --git a/src/adapters/adapter.js b/src/adapters/adapter.js index 30c6dc10bb5..5e5ea47cb18 100644 --- a/src/adapters/adapter.js +++ b/src/adapters/adapter.js @@ -10,7 +10,7 @@ function Adapter(code) { } function callBids() { - } + } return { callBids: callBids, diff --git a/src/adapters/adblade.js b/src/adapters/adblade.js new file mode 100644 index 00000000000..47a1ce0e04a --- /dev/null +++ b/src/adapters/adblade.js @@ -0,0 +1,128 @@ +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + +/** + * Adapter for requesting bids from Adblade + * To request an Adblade Header partner account + * or for additional integration support please + * register at http://www.adblade.com. + */ +var AdbladeAdapter = function AdbladeAdapter() { + 'use strict'; + + const BIDDER_CODE = 'adblade'; + const BASE_URI = '//rtb.adblade.com/prebidjs/bid?'; + const DEFAULT_BID_FLOOR = 0.0000000001; + + function _callBids(params) { + var bids = params.bids || [], + referrer = utils.getTopWindowUrl(), + loc = utils.getTopWindowLocation(), + domain = loc.hostname, + partnerId = 0, + bidRequests = {}; + + if (bids.length > 0) { + partnerId = '' + bids[0].params.partnerId; + } + + utils._each(bids, function(bid) { + utils._each(bid.sizes, function(size) { + let key = size[0] + 'x' + size[1]; + + bidRequests[key] = bidRequests[key] || { + 'site': { + 'id': partnerId, + 'page': referrer, + 'domain': domain, + 'publisher': { + 'id': partnerId, + 'name': referrer, + 'domain': domain + } + }, + 'id': params.requestId, + 'imp': [], + 'device': { + 'ua': window.navigator.userAgent, + }, + 'cur': ['USD'], + 'user': {} + }; + + bidRequests[key].imp.push({ + 'id': bid.bidId, + 'bidfloor': bid.params.bidFloor || DEFAULT_BID_FLOOR, + 'tag': bid.placementCode, + 'banner': { + 'w': size[0], + 'h': size[1], + }, + 'secure': 0 + (loc.protocol === 'https') + }); + }); + }); + + utils._each(bidRequests, function (bidRequest) { + adloader.loadScript( + utils.tryAppendQueryString( + utils.tryAppendQueryString( + BASE_URI, + 'callback', + '$$PREBID_GLOBAL$$.adbladeResponse' + ), + 'json', + JSON.stringify( + bidRequest + ) + ) + ); + }); + } + + $$PREBID_GLOBAL$$.adbladeResponse = function (response) { + var auctionIdRe = /\$(%7B|\{)AUCTION_ID(%7D|\})/gi, + auctionPriceRe = /\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, + clickUrlRe = /\$(%7B|\{)CLICK_URL(%7D|\})/gi; + + if (typeof(response) === 'undefined' || !response.hasOwnProperty('seatbid') || utils.isEmpty(response.seatbid)) { + // handle empty bids + var bidsRequested = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === BIDDER_CODE).bids; + if (bidsRequested.length > 0) { + let bid = bidfactory.createBid(2); + bid.bidderCode = BIDDER_CODE; + bidmanager.addBidResponse(bidsRequested[0].placementCode, bid); + } + + return; + } + + utils._each(response.seatbid, function(seatbid) { + utils._each(seatbid.bid, function(seatbidBid) { + var bidRequest = utils.getBidRequest(seatbidBid.impid), + ad = seatbidBid.adm + utils.createTrackPixelHtml(seatbidBid.nurl); + + ad = ad.replace(auctionIdRe, seatbidBid.impid); + ad = ad.replace(clickUrlRe, ''); + ad = ad.replace(auctionPriceRe, seatbidBid.price); + + let bid = bidfactory.createBid(1); + + bid.bidderCode = BIDDER_CODE; + bid.cpm = seatbidBid.price; + bid.ad = ad; + bid.width = seatbidBid.w; + bid.height = seatbidBid.h; + bidmanager.addBidResponse(bidRequest.placementCode, bid); + }); + }); + }; + + return { + callBids: _callBids + }; +}; + +module.exports = AdbladeAdapter; diff --git a/src/adapters/analytics/aolPartnersIds.json b/src/adapters/analytics/aolPartnersIds.json index 9dcbe1cc9db..587fdba1681 100644 --- a/src/adapters/analytics/aolPartnersIds.json +++ b/src/adapters/analytics/aolPartnersIds.json @@ -26,5 +26,9 @@ "jcm": 25, "appnexusAst": 26, "underdogmedia": 27, - "memeglobal": 28 + "memeglobal": 28, + "centro": 29, + "adblade": 30, + "piximedia": 31, + "getintent": 32 } diff --git a/src/adapters/appnexusAst.js b/src/adapters/appnexusAst.js index e62180668a7..28375b223ff 100644 --- a/src/adapters/appnexusAst.js +++ b/src/adapters/appnexusAst.js @@ -6,6 +6,8 @@ import { ajax } from 'src/ajax'; import { STATUS } from 'src/constants'; const ENDPOINT = '//ib.adnxs.com/ut/v2/prebid'; +const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skipppable', 'playback_method', 'frameworks']; /** * Bidder adapter for /ut endpoint. Given the list of all ad unit tag IDs, @@ -38,6 +40,15 @@ function AppnexusAstAdapter() { tag.keywords = getKeywords(bid.params.keywords); } + if (bid.mediaType === 'video') {tag.require_asset_url = true;} + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => VIDEO_TARGETING.includes(param)) + .forEach(param => tag.video[param] = bid.params.video[param]); + } + return tag; }); @@ -45,7 +56,7 @@ function AppnexusAstAdapter() { const payload = JSON.stringify({tags: [...tags]}); ajax(ENDPOINT, handleResponse, payload, { contentType: 'text/plain', - preflight: false, + withCredentials : true }); } }; @@ -77,18 +88,19 @@ function AppnexusAstAdapter() { const type = tag.ads && tag.ads[0].ad_type; let status; - if (cpm !== 0 && type === 'banner') { + if (cpm !== 0 && (type === 'banner' || type === 'video')) { status = STATUS.GOOD; } else { status = STATUS.NO_BID; } - if (type && type !== 'banner') { + if (type && (type !== 'banner' && type !== 'video')) { utils.logError(`${type} ad type not supported`); } tag.bidId = tag.uuid; // bidfactory looks for bidId on requested bid const bid = createBid(status, tag); + if (type === 'video') bid.mediaType = 'video'; const placement = bidRequests[bid.adId].placementCode; bidmanager.addBidResponse(placement, bid); }); @@ -157,16 +169,23 @@ function AppnexusAstAdapter() { if (status === STATUS.GOOD) { bid.cpm = tag.ads[0].cpm; - bid.creative_id = tag.ads[0].creativeId; - bid.width = tag.ads[0].rtb.banner.width; - bid.height = tag.ads[0].rtb.banner.height; - bid.ad = tag.ads[0].rtb.banner.content; - try { - const url = tag.ads[0].rtb.trackers[0].impression_urls[0]; - const tracker = utils.createTrackPixelHtml(url); - bid.ad += tracker; - } catch (error) { - utils.logError('Error appending tracking pixel', error); + bid.creative_id = tag.ads[0].creative_id; + + if (tag.ads[0].rtb.video) { + bid.width = tag.ads[0].rtb.video.player_width; + bid.height = tag.ads[0].rtb.video.player_height; + bid.vastUrl = tag.ads[0].rtb.video.asset_url; + } else { + bid.width = tag.ads[0].rtb.banner.width; + bid.height = tag.ads[0].rtb.banner.height; + bid.ad = tag.ads[0].rtb.banner.content; + try { + const url = tag.ads[0].rtb.trackers[0].impression_urls[0]; + const tracker = utils.createTrackPixelHtml(url); + bid.ad += tracker; + } catch (error) { + utils.logError('Error appending tracking pixel', error); + } } } diff --git a/src/adapters/centro.js b/src/adapters/centro.js new file mode 100644 index 00000000000..7e7dc100c77 --- /dev/null +++ b/src/adapters/centro.js @@ -0,0 +1,120 @@ +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + +var CentroAdapter = function CentroAdapter() { + var baseUrl = '//t.brand-server.com/hb', + devUrl = '//staging.brand-server.com/hb', + bidderCode = 'centro', + handlerPrefix = 'adCentroHandler_', + + LOG_ERROR_MESS = { + noUnit: 'Bid has no unit', + noAdTag: 'Bid has missmatch format.', + noBid: 'Response has no bid.', + anotherCode: 'Bid has another bidderCode - ', + undefBid: 'Bid is undefined', + unitNum: 'Requested unit is ' + }; + + function _makeHandler(handlerName, unit, placementCode) { + return function(response){ + try { + delete window[handlerName]; + } catch(err) {//catching for old IE + window[handlerName] = undefined; + } + _responseProcessing(response, unit, placementCode); + }; + } + + function _sendBidRequest(bid) { + var placementCode = bid.placementCode, + size = bid.sizes && bid.sizes[0]; + + bid = bid.params; + if (!bid.unit) { + //throw exception, or call utils.logError + utils.logError(LOG_ERROR_MESS.noUnit, bidderCode); + return; + } + var query = ['s=' + bid.unit];//,'url=www.abc15.com','sz=320x50']; + var isDev = bid.unit.toString() === '28136'; + + if (bid.page_url) { + query.push('url=' + encodeURIComponent(bid.page_url)); + } + //check size format + if ( + size instanceof Array && + size.length===2 && + typeof size[0] === 'number' && + typeof size[1] === 'number' + ) { + query.push('sz=' + size.join('x')); + } + //make handler name for JSONP request + var handlerName = handlerPrefix + bid.unit + size.join('x'); + query.push('callback=' + handlerName); + + //maybe is needed add some random parameter to disable cache + //query.push('r='+Math.round(Math.random() * 1e5)); + + window[handlerName] = _makeHandler(handlerName, bid.unit, placementCode); + + adloader.loadScript((document.location.protocol === 'https:'? 'https:' : 'http:') + (isDev? devUrl : baseUrl) + '?' + query.join('&')); + } + + /* + "sectionID": 7302, + "height": 250, + "width": 300, + "value": 3.2, + "adTag":'' + */ + function _responseProcessing(resp, unit, placementCode) { + var bidObject; + var bid = resp && resp.bid || resp; + + if (bid && bid.adTag && bid.sectionID === unit) { + bidObject = bidfactory.createBid(1); + bidObject.cpm = bid.value; + bidObject.ad = bid.adTag; + bidObject.width = bid.width; + bidObject.height = bid.height; + } else { + //throw exception, or call utils.logError with resp.statusMessage + utils.logError(LOG_ERROR_MESS.unitNum + unit + '. ' + (bid? bid.statusMessage || LOG_ERROR_MESS.noAdTag : LOG_ERROR_MESS.noBid), bidderCode); + bidObject = bidfactory.createBid(2); + } + bidObject.bidderCode = bidderCode; + bidmanager.addBidResponse(placementCode, bidObject); + } + + /* + { + bidderCode: "centro", + bids: [ + { + unit: '3242432', + page_url: "http://", + size: [300, 250] + */ + function _callBids(params) { + var bid, bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + bid = bids[i]; + if (bid && bid.bidder === bidderCode) { + _sendBidRequest(bid); + } + } + } + + return { + callBids: _callBids + }; +}; + + +module.exports = CentroAdapter; diff --git a/src/adapters/getintent.js b/src/adapters/getintent.js new file mode 100644 index 00000000000..1293d98f903 --- /dev/null +++ b/src/adapters/getintent.js @@ -0,0 +1,63 @@ +/*jshint loopfunc: true */ + +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); + +var GetIntentAdapter = function GetIntentAdapter() { + var headerBiddingStaticJS = window.location.protocol + '//cdn.adhigh.net/adserver/hb.js'; + + function _callBids(params) { + if (typeof window.gi_hb === 'undefined') { + adloader.loadScript(headerBiddingStaticJS, function() { + bid(params); + }, true); + } else { + bid(params); + } + } + + function addOptional(params, request, props) { + for (var i = 0; i < props.length; i++) { + if (params.hasOwnProperty(props[i])) { + request[props[i]] = params[props[i]]; + } + } + } + + function bid(params) { + var bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + var bidRequest = bids[i]; + var request = { + pid: bidRequest.params.pid, // required + tid: bidRequest.params.tid, // required + known: bidRequest.params.known || 1, + size: bidRequest.sizes[0].join("x"), + }; + addOptional(bidRequest.params, request, ['cur', 'floor']); + window.gi_hb.makeBid(request, function(bidResponse) { + if (bidResponse.no_bid === 1) { + var nobid = bidfactory.createBid(2); + nobid.bidderCode = bidRequest.bidder; + bidmanager.addBidResponse(bidRequest.placementCode, nobid); + } else { + var size = bidResponse.size.split('x'); + var bid = bidfactory.createBid(1); + bid.bidderCode = bidRequest.bidder; + bid.cpm = bidResponse.cpm; + bid.ad = bidResponse.ad; + bid.width = size[0]; + bid.height = size[1]; + bidmanager.addBidResponse(bidRequest.placementCode, bid); + } + }); + } + } + + return { + callBids: _callBids + }; +}; + +module.exports = GetIntentAdapter; diff --git a/src/adapters/indexExchange.js b/src/adapters/indexExchange.js index 39c417e8197..8ee3fa2f4f6 100644 --- a/src/adapters/indexExchange.js +++ b/src/adapters/indexExchange.js @@ -404,36 +404,28 @@ var IndexExchangeAdapter = function IndexExchangeAdapter() { bid.width = slotObj.width; bid.height = slotObj.height; bid.siteID = slotObj.siteID; - + if ( typeof _IndexRequestData.targetIDToResp === 'object' && typeof _IndexRequestData.targetIDToResp[cpmAndSlotId] === 'object' && typeof _IndexRequestData.targetIDToResp[cpmAndSlotId].dealID !== 'undefined' ) { + bid.dealId = _IndexRequestData.targetIDToResp[cpmAndSlotId].dealID; + } bids.push(bid); } } var currentBid = undefined; - //Pick the highest bidding price for this slot if (bids.length > 0) { - // Choose the highest bid + // Add all bid responses for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; - if (typeof currentBid === 'undefined') { - currentBid = bid; - continue; - } - - if (bid.cpm > currentBid.cpm) { - currentBid = bid; - } + bidmanager.addBidResponse(adUnitCode, bids[i]); } - // No bids for expected bid, pass bid } else { var bid = bidfactory.createBid(2); bid.bidderCode = ADAPTER_CODE; currentBid = bid; + bidmanager.addBidResponse(adUnitCode, currentBid); } - bidmanager.addBidResponse(adUnitCode, currentBid); } } catch (e) { utils.logError('Error calling index adapter', ADAPTER_NAME, e); diff --git a/src/adapters/kruxlink.js b/src/adapters/kruxlink.js index d29fa589e71..37f258284c2 100644 --- a/src/adapters/kruxlink.js +++ b/src/adapters/kruxlink.js @@ -25,19 +25,25 @@ function _makeCallback(id, placements) { delete $$PREBID_GLOBAL$$[callback]; // Add in the bid respones - for (var i = 0; i < response.seatbid.length; i++) { - var seatbid = response.seatbid[i]; - for (var j = 0; j < seatbid.bid.length; j++) { - var bid = seatbid.bid[j]; - _makeBidResponse(placements[bid.impid], bid); - delete placements[bid.impid]; + if (response.seatbid !== undefined) { + for (var i = 0; i < response.seatbid.length; i++) { + var seatbid = response.seatbid[i]; + if (seatbid.bid !== undefined) { + for (var j = 0; j < seatbid.bid.length; j++) { + var bid = seatbid.bid[j]; + if (bid.impid !== undefined) { + _makeBidResponse(placements[bid.impid], bid); + delete placements[bid.impid]; + } + } + } } } // Add any no-bids remaining - for (var placementCode in placements) { - if (placements.hasOwnProperty(placementCode)) { - _makeBidResponse(placementCode); + for (var impid in placements) { + if (placements.hasOwnProperty(impid)) { + _makeBidResponse(placements[impid]); } } }; diff --git a/src/adapters/openx.js b/src/adapters/openx.js index 82d4e466941..4e8704235d4 100644 --- a/src/adapters/openx.js +++ b/src/adapters/openx.js @@ -81,7 +81,6 @@ var OpenxAdapter = function OpenxAdapter(options) { // Get ad response adUnit = response.getOrCreateAdUnit(bid.params.unit); - // If 'pub_rev' (CPM) isn't returned we got an empty response if (adUnit.get('pub_rev')) { adResponse = adResponse = bidfactory.createBid(1); @@ -91,6 +90,9 @@ var OpenxAdapter = function OpenxAdapter(options) { adResponse.cpm = Number(adUnit.get('pub_rev')) / 1000; adResponse.ad = adUnit.get('html'); + if(adUnit.get('deal_id') !== undefined) { + adResponse.dealId = adUnit.get('deal_id'); + } // Add record/impression pixel to the creative HTML var recordPixel = OX.utils.template(response.getRecordTemplate(), { diff --git a/src/adapters/piximedia.js b/src/adapters/piximedia.js new file mode 100644 index 00000000000..f0d9c1e8143 --- /dev/null +++ b/src/adapters/piximedia.js @@ -0,0 +1,157 @@ +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var bidmanager = require('../bidmanager.js'); +var bidfactory = require('../bidfactory.js'); +var adloader = require('../adloader.js'); +var Adapter = require('./adapter.js'); + +var PiximediaAdapter = function PiximediaAdapter() { + var PREBID_URL = '//static.adserver.pm/prebid'; + var baseAdapter = Adapter.createNew('piximedia'); + var bidStash = {}; + + var tryAppendPixiQueryString = function(url, name, value) { + return url + "/" + encodeURIComponent(name) + "=" + value; + }; + + baseAdapter.callBids = function callBidsPiximedia(params) { + utils._each(params.bids, function(bid) { + + // valid bids must include a siteId and an placementId + if (bid.placementCode && bid.sizes && bid.params && bid.params.siteId && bid.params.placementId) { + + var sizes = bid.params.hasOwnProperty('sizes')? bid.params.sizes: bid.sizes; + sizes = utils.parseSizesInput(sizes); + + var cbid = utils.getUniqueIdentifierStr(); + + // we allow overriding the URL in the params + var url = bid.params.prebidUrl || PREBID_URL; + + // params are passed to the Piximedia endpoint, including custom params + for(var key in bid.params) { + /* istanbul ignore else */ + if(bid.params.hasOwnProperty(key)) { + var value = bid.params[key]; + switch(key) { + case "siteId": + url = tryAppendPixiQueryString(url, 'site_id', encodeURIComponent(value)); + break; + + case "placementId": + url = tryAppendPixiQueryString(url, 'placement_id', encodeURIComponent(value)); + break; + + case "dealId": + url = tryAppendPixiQueryString(url, 'l_id', encodeURIComponent(value)); + break; + + case "sizes": + case "prebidUrl": + break; + + default: + if(typeof value === "function") { + url = tryAppendPixiQueryString(url, key, encodeURIComponent((value(baseAdapter, params, bid) || "").toString())); + } else { + url = tryAppendPixiQueryString(url, key, encodeURIComponent((value || "").toString())); + } + break; + } + } + } + + url = tryAppendPixiQueryString(url, 'jsonp', '$$PREBID_GLOBAL$$.handlePiximediaCallback'); + url = tryAppendPixiQueryString(url, 'sizes', encodeURIComponent(sizes.join(","))); + url = tryAppendPixiQueryString(url, 'cbid', encodeURIComponent(cbid)); + url = tryAppendPixiQueryString(url, 'rand', String(Math.floor(Math.random() * 1000000000))); + + bidStash[cbid] = { + 'bidObj': bid, + 'url': url, + 'start': new Date().getTime() + }; + utils.logMessage('[Piximedia] Dispatching header Piximedia to URL ' + url); + adloader.loadScript(url); + } + }); + }; + + /* + * Piximedia's bidResponse should look like: + * + * { + * 'foundbypm': true, // a Boolean, indicating if an ad was found + * 'currency': 'EUR', // the currency, as a String + * 'cpm': 1.99, // the win price as a Number, in currency + * 'dealId': null, // or string value of winning deal ID + * 'width': 300, // width in pixels of winning ad + * 'height': 250, // height in pixels of winning ad + * 'html': 'tag_here' // HTML tag to load if we are picked + * } + * + */ + $$PREBID_GLOBAL$$.handlePiximediaCallback = function(bidResponse) { + if (bidResponse && bidResponse.hasOwnProperty("foundbypm")) { + var stash = bidStash[bidResponse.cbid]; // retrieve our stashed data, by using the cbid + var bid; + + if (stash) { + var bidObj = stash.bidObj; + var timelapsed = new Date().getTime(); + timelapsed = timelapsed - stash.start; + + if (bidResponse.foundbypm && bidResponse.width && bidResponse.height && bidResponse.html && bidResponse.cpm && bidResponse.currency) { + + /* we have a valid ad to display */ + bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD); + bid.bidderCode = bidObj.bidder; + bid.width = bidResponse.width; + bid.height = bidResponse.height; + bid.ad = bidResponse.html; + bid.cpm = bidResponse.cpm; + bid.currency = bidResponse.currency; + + if (bidResponse.dealId) { + bid.dealId = bidResponse.dealId; + } else { + bid.dealId = null; + } + + bidmanager.addBidResponse(bidObj.placementCode, bid); + + utils.logMessage('[Piximedia] Registered bidresponse from URL ' + stash.url + + ' (time: ' + String(timelapsed) + ')'); + utils.logMessage('[Piximedia] ======> BID placementCode: ' + bidObj.placementCode + + ' CPM: ' + String(bid.cpm) + ' ' + bid.currency + + ' Format: ' + String(bid.width) + 'x' + String(bid.height)); + } else { + + /* we have no ads to display */ + bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); + bid.bidderCode = bidObj.bidder; + bidmanager.addBidResponse(bidObj.placementCode, bid); + + utils.logMessage('[Piximedia] Registered BLANK bidresponse from URL ' + stash.url + + ' (time: ' + String(timelapsed) + ')'); + utils.logMessage('[Piximedia] ======> NOBID placementCode: ' + bidObj.placementCode); + } + + // We should no longer need this stashed object, so drop reference: + bidStash[bidResponse.cbid] = null; + + } else { + utils.logMessage("[Piximedia] Couldn't find stash for cbid=" + bidResponse.cbid); + } + } + }; + + // return an object with PiximediaAdapter methods + return { + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode, + getBidderCode: baseAdapter.getBidderCode + }; +}; + +module.exports = PiximediaAdapter; diff --git a/src/adapters/pubmatic.js b/src/adapters/pubmatic.js index 489dd2a9ddd..cd6f1d76487 100644 --- a/src/adapters/pubmatic.js +++ b/src/adapters/pubmatic.js @@ -13,7 +13,7 @@ var PubmaticAdapter = function PubmaticAdapter() { var bids; var _pm_pub_id; var _pm_optimize_adslots = []; - let iframeId = ''; + let iframe; function _callBids(params) { bids = params.bids; @@ -32,8 +32,8 @@ var PubmaticAdapter = function PubmaticAdapter() { //create the iframe - var iframe = utils.createInvisibleIframe(); - iframeId = iframe.id; + iframe = utils.createInvisibleIframe(); + var elToAppend = document.getElementsByTagName('head')[0]; //insert the iframe into document @@ -75,8 +75,8 @@ var PubmaticAdapter = function PubmaticAdapter() { let bidDetailsMap = {}; let progKeyValueMap = {}; try { - bidDetailsMap = window.frames[iframeId].contentWindow.bidDetailsMap; - progKeyValueMap = window.frames[iframeId].contentWindow.progKeyValueMap; + bidDetailsMap = iframe.contentWindow.bidDetailsMap; + progKeyValueMap = iframe.contentWindow.progKeyValueMap; } catch(e) { utils.logError(e, 'Error parsing Pubmatic response'); diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 60964dd2ae7..847ebca6888 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -8,6 +8,8 @@ var bidmanager = require('../bidmanager'); var bidfactory = require('../bidfactory'); var adloader = require('../adloader'); +const TIMEOUT_BUFFER = 100; + /** * @class RubiconAdapter * Prebid adapter for Rubicon's header bidding client @@ -285,35 +287,38 @@ var RubiconAdapter = function RubiconAdapter() { /** * Request the specified bids from * Rubicon - * @param {Object} params the bidder-level params (from prebid) + * @param {Object} bidderRequest the bidder-level params (from prebid) * @param {Array} params.bids the bids requested */ - function _callBids(params) { + function _callBids(bidderRequest) { // start the timer; want to measure from // even just loading the SDK _bidStart = (new Date).getTime(); - _mapSizes(params.bids); + _mapSizes(bidderRequest.bids); - if (utils.isEmpty(params.bids)) { + if (utils.isEmpty(bidderRequest.bids)) { return; } // on the first bid, set up the SDK if (!RUBICON_INITIALIZED) { - _initSDK(params.bids[0].params); + _initSDK(bidderRequest.bids[0].params); } _rready(function () { var slots = []; - var bids = params.bids; + var bids = bidderRequest.bids; for (var i=0, ln=bids.length; i < ln; i++) { slots.push(_defineSlot(bids[i])); } - var parameters = { slots: slots }; + var parameters = { + slots: slots, + timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart - TIMEOUT_BUFFER) + }; var callback = function () { _bidsReady(slots); }; diff --git a/src/adapters/sonobi.js b/src/adapters/sonobi.js index aa3e9aa3562..1833bc9330c 100644 --- a/src/adapters/sonobi.js +++ b/src/adapters/sonobi.js @@ -4,89 +4,111 @@ var adloader = require('../adloader.js'); var utils = require('../utils'); var SonobiAdapter = function SonobiAdapter(){ - var test = false; // tag tester = true || false - var cb_map = {}; + var keymakerAssoc = {}; // Remember placement codes for callback mapping + var bidIdAssoc = {}; // Remember bids for bid complete reporting - function _phone_in(params){ + function _phone_in(request){ var trinity = 'https://apex.go.sonobi.com/trinity.js?key_maker='; - var bids = params.bids || []; - adloader.loadScript(trinity + JSON.stringify(_keymaker(bids)) + '&cv=' + _operator()); + var adSlots = request.bids || []; + var bidderRequestId = request.bidderRequestId; + adloader.loadScript(trinity + JSON.stringify(_keymaker(adSlots)) + '&cv=' + _operator(bidderRequestId)); } - function _keymaker(bids){ // Make keys + function _keymaker(adSlots){ // Keymaker makes keys var keyring = {}; - utils._each(bids, function(o){ - var sizes = utils.parseSizesInput(o.sizes).toString(); - if (utils.isEmpty(sizes)){ - utils.logWarn('Sonobi adapter expects sizes for ' + o.placementCode); - } - switch(true){ - case (!o.params.ad_unit && !o.params.placement_id): - utils.logError('Sonobi unable to bid: Missing parameters for ' + o.placementCode); - break; - case (!!o.params.ad_unit && !!o.params.placement_id): - utils.logError('Sonobi unable to bid: Extra parameters for ' + o.placementCode); - break; - case (!!o.params.ad_unit && o.params.ad_unit.length === 0): - utils.logError('Sonobi unable to bid: Empty ad_unit for ' + o.placementCode); - break; - case (!!o.params.placement_id && o.params.placement_id.length === 0): - utils.logError('Sonobi unable to bid: Empty placement_id for ' + o.placementCode); - break; - case (!!o.params.placement_id): // Morpeus style - keyring[o.params.dom_id] = o.params.placement_id + (test ? '-test' : '') + '|' + sizes; - cb_map[o.params.dom_id] = o.placementCode; - break; - case (!!o.params.ad_unit && o.params.ad_unit.charAt(0) !== '/'): - // DFP docs do not necessarily require leading slash? - add it in if it's not there. - o.params.ad_unit = '/' + o.params.ad_unit; - /* falls through */ - case (!!o.params.ad_unit): // Cypher style - keyring[o.params.ad_unit + '|' + o.params.dom_id] = sizes; - cb_map[o.params.ad_unit + '|' + o.params.dom_id] = o.placementCode; - break; - default: // I don't know how it's broken, but it is. - utils.logError('Sonobi unable to bid: Improper parameters for ' + o.placementCode); + utils._each(adSlots, function(bidSlot){ + if(bidSlot.params){ + // Optional (please don't set these as the word 'OPTIONAL' come on now why would you think that was OK?) + var dom_id = (bidSlot.params.dom_id && !utils.isEmpty(bidSlot.params.dom_id)) ? bidSlot.params.dom_id : !utils.isEmpty(bidSlot.placementCode) ? bidSlot.placementCode : "dom_" + utils.getUniqueIdentifierStr(); + var floor = (bidSlot.params.floor) ? bidSlot.params.floor : null; + // Mandatory + var slotIdentifier = (bidSlot.params.ad_unit) ? bidSlot.params.ad_unit : (bidSlot.params.placement_id) ? bidSlot.params.placement_id : null; + var sizes = utils.parseSizesInput(bidSlot.sizes).toString() || null; + if (utils.isEmpty(sizes)){ + utils.logError('Sonobi adapter expects sizes for ' + bidSlot.placementCode); + } + var args = (sizes) ? ((floor) ? (sizes + '|f=' + floor) : (sizes)) : (floor) ? ('f=' + floor) : ''; + if (/[0-9a-fA-F]+/.test(slotIdentifier) && slotIdentifier.length === 20){ + // Placements are 20 character hex + keyring[dom_id] = slotIdentifier + '|' + args; + keymakerAssoc[dom_id] = bidSlot.placementCode; + bidIdAssoc[bidSlot.placementCode] = bidSlot; + } else if (/\/?[0-9]*\/(.*\/?)*/.test(slotIdentifier)){ + // AdUnitCode rules from DFP allow a lot of things you wouldn't expect + slotIdentifier = slotIdentifier.charAt(0) === '/' ? slotIdentifier : '/' + slotIdentifier; + // Consistency isn't really their thing they can't even decide if leading slash matters + keyring[slotIdentifier + '|' + dom_id] = args; + keymakerAssoc[slotIdentifier + '|' + dom_id] = bidSlot.placementCode; + bidIdAssoc[bidSlot.placementCode] = bidSlot; + } else { + keymakerAssoc[dom_id] = bidSlot.placementCode; + bidIdAssoc[bidSlot.placementCode] = bidSlot; + _failure(bidSlot.placementCode); + utils.logError('The ad unit code or Sonobi Placement id for slot ' + bidSlot.placementCode + ' is invalid'); + } } }); return keyring; } - function _operator(){ // Uniqify callbacks - var uniq = "cb" + utils.getUniqueIdentifierStr(); - window[uniq] = _trinity; - return uniq; + function _operator(bidderRequestId){ // Name jsonp callbacks by request + var cb_name = "sbi_" + bidderRequestId; + window[cb_name] = _trinity; + return cb_name; } - function _trinity(response){ // Callback + function _trinity(response){ // Call back var slots = response.slots || {}; var sbi_dc = response.sbi_dc || ''; - var bidObject = {}; - for (var slot in slots) { - if (slots[slot].sbi_aid){ - bidObject = bidfactory.createBid(1); - bidObject.bidderCode = 'sonobi'; - bidObject.cpm = Number(slots[slot].sbi_mouse); - bidObject.ad = _get_creative(sbi_dc, slots[slot].sbi_aid); - bidObject.width = Number(slots[slot].sbi_size.split('x')[0]); - bidObject.height = Number(slots[slot].sbi_size.split('x')[1]); - bidmanager.addBidResponse(cb_map[slot], bidObject); - } else { // No aid? No ad. - bidObject = bidfactory.createBid(2); - bidObject.bidderCode = 'sonobi'; - bidmanager.addBidResponse(cb_map[slot], bidObject); + utils._each(slots, function(bid, slot_id){ + var placementCode = keymakerAssoc[slot_id]; + if (bid.sbi_aid && bid.sbi_mouse && bid.sbi_size){ // I got the money you got the stuff? + _success(placementCode, sbi_dc, bid); + } else { + _failure(placementCode); } + delete keymakerAssoc[slot_id]; // You're done get outta here + }); + } + + function _seraph(placementCode){ // Search for the one + var theOne = bidIdAssoc[placementCode]; + delete bidIdAssoc[placementCode]; // Eliminate him + return theOne; + } + + function _success(placementCode, sbi_dc, bid){ + var goodBid = bidfactory.createBid(1, _seraph(placementCode)); + if(bid.sbi_dozer){ + goodBid.dealId = bid.sbi_dozer; } + goodBid.bidderCode = 'sonobi'; + goodBid.ad = _creative(sbi_dc, bid.sbi_aid); + goodBid.cpm = Number(bid.sbi_mouse); + // Video creatives will return sbi_size="outstream", default to 1x1 + goodBid.width = Number(bid.sbi_size.split('x')[0]) || 1; + goodBid.height = Number(bid.sbi_size.split('x')[1]) || 1; + bidmanager.addBidResponse(placementCode, goodBid); + } + + function _failure(placementCode){ + var failBid = bidfactory.createBid(2, _seraph(placementCode)); + failBid.bidderCode = 'sonobi'; + bidmanager.addBidResponse(placementCode, failBid); } - function _get_creative(sbi_dc, sbi_aid){ - var creative = ''; - return creative; + function _creative(sbi_dc, sbi_aid){ + var src = 'https://' + sbi_dc + 'apex.go.sonobi.com/sbi.js?aid=' + sbi_aid + '&as=null'; + return ''; } - return { callBids: _phone_in }; + return { + callBids: _phone_in, + formRequest: _keymaker, + parseResponse: _trinity, + success: _success, + failure: _failure + }; }; module.exports = SonobiAdapter; diff --git a/src/adapters/triplelift.js b/src/adapters/triplelift.js index 998b8229cb8..608a62d6cc5 100644 --- a/src/adapters/triplelift.js +++ b/src/adapters/triplelift.js @@ -85,7 +85,7 @@ var TripleLiftAdapter = function TripleLiftAdapter() { var bid = []; if (tlResponseObj && tlResponseObj.cpm && tlResponseObj.cpm !== 0) { - bid = bidfactory.createBid(1); + bid = bidfactory.createBid(1, bidObj); bid.bidderCode = 'triplelift'; bid.cpm = tlResponseObj.cpm; bid.ad = tlResponseObj.ad; @@ -99,7 +99,7 @@ var TripleLiftAdapter = function TripleLiftAdapter() { // @if NODE_ENV='debug' utils.logMessage('No prebid response from TripleLift for inventory code: ' + bidObj.params.inventoryCode); // @endif - bid = bidfactory.createBid(2); + bid = bidfactory.createBid(2, bidObj); bid.bidderCode = 'triplelift'; bidmanager.addBidResponse(placementCode, bid); } diff --git a/src/adserver.js b/src/adserver.js new file mode 100644 index 00000000000..6760554dc69 --- /dev/null +++ b/src/adserver.js @@ -0,0 +1,53 @@ +import {formatQS} from './url'; + +//Adserver parent class +const AdServer = function(attr) { + this.name = attr.adserver; + this.code = attr.code; + this.getWinningBidByCode = function() { + var bidObject = $$PREBID_GLOBAL$$._bidsReceived.find(bid => bid.adUnitCode === this.code); + return bidObject; + }; +}; + +//DFP ad server +exports.dfpAdserver = function (options, urlComponents) { + var adserver = new AdServer(options); + adserver.urlComponents = urlComponents; + + var dfpReqParams = { + 'env' : 'vp', + 'gdfp_req' : '1', + 'impl' : 's', + 'unviewed_position_start' : '1' + }; + + var dfpParamsWithVariableValue = ['output', 'iu', 'sz', 'url', 'correlator', 'description_url', 'hl']; + + var getCustomParams = function(targeting) { + return encodeURIComponent(formatQS(targeting)); + }; + + adserver.appendQueryParams = function() { + var bid = adserver.getWinningBidByCode(); + this.urlComponents.search.description_url = encodeURIComponent(bid.vastUrl); + this.urlComponents.search.cust_params = getCustomParams(bid.adserverTargeting); + this.urlComponents.correlator = Date.now(); + }; + + adserver.verifyAdserverTag = function() { + for(var key in dfpReqParams) { + if(!this.urlComponents.search.hasOwnProperty(key) || this.urlComponents.search[key] !== dfpReqParams[key]) { + return false; + } + } + for(var i in dfpParamsWithVariableValue) { + if(!this.urlComponents.search.hasOwnProperty(dfpParamsWithVariableValue[i])) { + return false; + } + } + return true; + }; + + return adserver; +}; diff --git a/src/ajax.js b/src/ajax.js index 0e74772a81a..7286689fc02 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,10 +1,12 @@ import {parse as parseURL, format as formatURL} from './url'; -/** - * Simple cross-browser ajax request function - * https://gist.github.com/Xeoncross/7663273 +var utils = require('./utils'); + +const XHR_DONE = 4; - * IE 5.5+, Firefox, Opera, Chrome, Safari XHR object +/** + * Simple IE9+ and cross-browser ajax request function + * Note: x-domain requests in IE9 do not support the use of cookies * * @param url string url * @param callback object callback @@ -12,45 +14,58 @@ import {parse as parseURL, format as formatURL} from './url'; * @param options object */ -export const ajax = function ajax(url, callback, data, options = {}) { - let x; +export function ajax(url, callback, data, options = {}) { - try { - if (window.XMLHttpRequest) { - x = new window.XMLHttpRequest('MSXML2.XMLHTTP.3.0'); - } + let x, + method = options.method || (data ? 'POST' : 'GET'), + // For IE9 support use XDomainRequest instead of XMLHttpRequest. + useXDomainRequest = window.XDomainRequest && + (!window.XMLHttpRequest || new window.XMLHttpRequest().responseType === undefined); - if (window.ActiveXObject) { - x = new window.ActiveXObject('MSXML2.XMLHTTP.3.0'); - } + if (useXDomainRequest) { + x = new window.XDomainRequest(); + x.onload = handler; - const method = options.method || (data ? 'POST' : 'GET'); + // http://stackoverflow.com/questions/15786966/xdomainrequest-aborts-post-on-ie-9 + x.onerror = function () { + utils.logMessage('xhr onerror'); + }; + x.ontimeout = function () { + utils.logMessage('xhr timeout'); + }; + x.onprogress = function() { + utils.logMessage('xhr onprogress'); + }; - if (method === 'GET' && data) { - let urlInfo = parseURL(url); - Object.assign(urlInfo.search, data); - url = formatURL(urlInfo); - } + } else { + x = new window.XMLHttpRequest(); + x.onreadystatechange = handler; + } + + if (method === 'GET' && data) { + let urlInfo = parseURL(url); + Object.assign(urlInfo.search, data); + url = formatURL(urlInfo); + } - //x = new (window.XMLHttpRequest || window.ActiveXObject)('MSXML2.XMLHTTP.3.0'); - x.open(method, url, 1); + x.open(method, url); + if (!useXDomainRequest) { if (options.withCredentials) { x.withCredentials = true; - } else { + } + if (options.preflight) { x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - x.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); } + x.setRequestHeader('Content-Type', options.contentType || 'text/plain'); + } - //x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - x.onreadystatechange = function () { - if (x.readyState > 3 && callback) { - callback(x.responseText, x); - } - }; + x.send(method === 'POST' && data); - x.send(method === 'POST' && data); - } catch (e) { - console.log(e); + function handler() { + if (x.readyState === XHR_DONE && callback) { + callback(x.responseText, x); + } } -}; + +} diff --git a/src/bidmanager.js b/src/bidmanager.js index be34cd3627e..43832d9fafa 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -83,7 +83,8 @@ exports.addBidResponse = function (adUnitCode, bid) { bid.timeToRespond = bid.responseTimestamp - bid.requestTimestamp; - if (_currentTimeoutIndex >= _timeouts.length && bid.timeToRespond > $$PREBID_GLOBAL$$.bidderTimeout) { + if (_currentTimeoutIndex >= _timeouts.length && + $$PREBID_GLOBAL$$.cbTimeout + $$PREBID_GLOBAL$$.timeoutBuffer) { const timedOut = true; this.executeCallback(timedOut); @@ -182,12 +183,24 @@ function getKeyValueTargetingPairs(bidderCode, custBidObj) { if (bidderCode && custBidObj && bidder_settings && bidder_settings[bidderCode] && bidder_settings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { setKeys(keyValues, bidder_settings[bidderCode], custBidObj); custBidObj.alwaysUseBid = bidder_settings[bidderCode].alwaysUseBid; + filterIfSendStandardTargeting(bidder_settings[bidderCode]); } //2) set keys from standard setting. NOTE: this API doesn't seem to be in use by any Adapter else if (defaultBidderSettingsMap[bidderCode]) { setKeys(keyValues, defaultBidderSettingsMap[bidderCode], custBidObj); custBidObj.alwaysUseBid = defaultBidderSettingsMap[bidderCode].alwaysUseBid; + filterIfSendStandardTargeting(defaultBidderSettingsMap[bidderCode]); + } + + function filterIfSendStandardTargeting(bidderSettings) { + if (typeof bidderSettings.sendStandardTargeting !== "undefined" && bidder_settings[bidderCode].sendStandardTargeting === false) { + for(var key in keyValues) { + if(CONSTANTS.TARGETING_KEYS.indexOf(key) !== -1) { + delete keyValues[key]; + } + } + } } return keyValues; @@ -211,13 +224,25 @@ function setKeys(keyValues, bidderSettings, custBidObj) { if (utils.isFn(value)) { try { - keyValues[key] = value(custBidObj); + value = value(custBidObj); } catch (e) { utils.logError('bidmanager', 'ERROR', e); } + } + + if ( + typeof bidderSettings.suppressEmptyKeys !== "undefined" && bidderSettings.suppressEmptyKeys === true && + ( + utils.isEmptyStr(value) || + value === null || + value === undefined + ) + ) { + utils.logInfo("suppressing empty key '" + key + "' from adserver targeting"); } else { keyValues[key] = value; } + }); return keyValues; @@ -307,9 +332,9 @@ exports.executeCallback = function (timedOut) { processCallbacks([externalOneTimeCallback]); } finally { - $$PREBID_GLOBAL$$.clearAuction(); externalOneTimeCallback = null; exports.setTimeouts([]); + $$PREBID_GLOBAL$$.clearAuction(); } } }; diff --git a/src/prebid.js b/src/prebid.js index 75c8a6b130b..fa9d596eaeb 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,12 +1,12 @@ /** @module $$PREBID_GLOBAL$$ */ +import { getGlobal } from './prebidGlobal'; import { flatten, uniques, getKeys, isGptPubadsDefined, getHighestCpm } from './utils'; +import { videoAdUnit, hasNonVideoBidder } from './video'; import 'polyfill'; +import {parse as parseURL, format as formatURL} from './url'; -// if $$PREBID_GLOBAL$$ already exists in global document scope, use it, if not, create the object -window.$$PREBID_GLOBAL$$ = (window.$$PREBID_GLOBAL$$ || {}); -window.$$PREBID_GLOBAL$$.que = window.$$PREBID_GLOBAL$$.que || []; -var $$PREBID_GLOBAL$$ = window.$$PREBID_GLOBAL$$; +var $$PREBID_GLOBAL$$ = getGlobal(); var CONSTANTS = require('./constants.json'); var utils = require('./utils.js'); var bidmanager = require('./bidmanager.js'); @@ -14,6 +14,7 @@ var adaptermanager = require('./adaptermanager'); var bidfactory = require('./bidfactory'); var adloader = require('./adloader'); var events = require('./events'); +var adserver = require('./adserver.js'); /* private variables */ @@ -42,6 +43,13 @@ $$PREBID_GLOBAL$$._sendAllBids = false; //default timeout for all bids $$PREBID_GLOBAL$$.bidderTimeout = $$PREBID_GLOBAL$$.bidderTimeout || 3000; + +// current timeout set in `requestBids` or to default `bidderTimeout` +$$PREBID_GLOBAL$$.cbTimeout = $$PREBID_GLOBAL$$.cbTimeout || 200; + +// timeout buffer to adjust for bidder CDN latency +$$PREBID_GLOBAL$$.timeoutBuffer = 200; + $$PREBID_GLOBAL$$.logging = $$PREBID_GLOBAL$$.logging || false; //let the world know we are loaded @@ -182,7 +190,7 @@ function getDealTargeting() { [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] }; }) - .concat({ [dealKey]: [bid.adserverTargeting[dealKey]] }) + .concat({ [dealKey.substring(0, 20)]: [bid.adserverTargeting[dealKey]] }) }; }); } @@ -229,10 +237,10 @@ function getBidLandscapeTargeting() { function getAllTargeting() { // Get targeting for the winning bid. Add targeting for any bids that have // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. - var targeting = getDealTargeting() - .concat(getWinningBidTargeting()) + var targeting = getWinningBidTargeting() .concat(getAlwaysUseBidTargeting()) - .concat($$PREBID_GLOBAL$$._sendAllBids ? getBidLandscapeTargeting() : []); + .concat($$PREBID_GLOBAL$$._sendAllBids ? getBidLandscapeTargeting() : []) + .concat(getDealTargeting()); //store a reference of the targeting keys targeting.map(adUnitCode => { @@ -269,7 +277,7 @@ function removeComplete() { // also remove bids that have an empty or error status so known as not pending for render responses.filter(bid => bid.getStatusCode && bid.getStatusCode() === 2) - .forEach(bid => responses.slice(responses.indexOf(bid), 1)); + .forEach(bid => responses.splice(responses.indexOf(bid), 1)); } ////////////////////////////////// @@ -441,7 +449,9 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { var url = adObject.adUrl; var ad = adObject.ad; - if (ad) { + if (doc===document || adObject.mediaType === 'video') { + utils.logError('Error trying to write ad. Ad render call ad id ' + id + ' was prevented from writing to the main document.'); + } else if (ad) { doc.write(ad); doc.close(); if (doc.defaultView && doc.defaultView.frameElement) { @@ -507,7 +517,7 @@ $$PREBID_GLOBAL$$.clearAuction = function() { * @param adUnitCodes */ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes }) { - const cbTimeout = timeout || $$PREBID_GLOBAL$$.bidderTimeout; + const cbTimeout = $$PREBID_GLOBAL$$.cbTimeout = timeout || $$PREBID_GLOBAL$$.bidderTimeout; adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits; // if specific adUnitCodes filter adUnits for those codes @@ -515,6 +525,15 @@ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, a adUnits = adUnits.filter(adUnit => adUnitCodes.includes(adUnit.code)); } + // for video-enabled adUnits, only request bids if all bidders support video + const invalidVideoAdUnits = adUnits.filter(videoAdUnit).filter(hasNonVideoBidder); + invalidVideoAdUnits.forEach(adUnit => { + utils.logError(`adUnit ${adUnit.code} has 'mediaType' set to 'video' but contains a bidder that doesn't support video. No Prebid demand requests will be triggered for this adUnit.`); + for (let i = 0; i < adUnits.length; i++) { + if (adUnits[i].code === adUnit.code) {adUnits.splice(i, 1);} + } + }); + if (auctionRunning) { bidRequestQueue.push(() => { $$PREBID_GLOBAL$$.requestBids({ bidsBackHandler, cbTimeout, adUnits }); @@ -762,4 +781,33 @@ $$PREBID_GLOBAL$$.getAllWinningBids = function () { return $$PREBID_GLOBAL$$._winningBids; }; +/** + * Build master video tag from publishers adserver tag + * @param {string} adserverTag default url + * @param {object} options options for video tag + */ +$$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag = function (adserverTag, options) { + utils.logInfo('Invoking $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag', arguments); + var urlComponents = parseURL(adserverTag); + + //return original adserverTag if no bids received + if($$PREBID_GLOBAL$$._bidsReceived.length === 0) { + return adserverTag; + } + + var masterTag = ''; + if(options.adserver.toLowerCase() === 'dfp') { + var dfpAdserverObj = adserver.dfpAdserver(options, urlComponents); + if(!dfpAdserverObj.verifyAdserverTag()) { + utils.logError('Invalid adserverTag, required google params are missing in query string'); + } + dfpAdserverObj.appendQueryParams(); + masterTag = formatURL(dfpAdserverObj.urlComponents); + } else { + utils.logError('Only DFP adserver is supported'); + return; + } + return masterTag; +}; + processQue(); diff --git a/src/prebidGlobal.js b/src/prebidGlobal.js new file mode 100644 index 00000000000..0ba9edcab1a --- /dev/null +++ b/src/prebidGlobal.js @@ -0,0 +1,8 @@ +// if $$PREBID_GLOBAL$$ already exists in global document scope, use it, if not, create the object +// global defination should happen BEFORE imports to avoid global undefined errors. +window.$$PREBID_GLOBAL$$ = (window.$$PREBID_GLOBAL$$ || {}); +window.$$PREBID_GLOBAL$$.que = window.$$PREBID_GLOBAL$$.que || []; + +export function getGlobal() { + return window.$$PREBID_GLOBAL$$; +} diff --git a/src/utils.js b/src/utils.js index 3175ef7455f..a5e0c23d4ac 100644 --- a/src/utils.js +++ b/src/utils.js @@ -179,14 +179,18 @@ exports.parseGPTSingleSizeArray = function (singleSize) { } }; -exports.getTopWindowUrl = function () { +exports.getTopWindowLocation = function () { try { - return window.top.location.href; + return window.top.location; } catch (e) { - return window.location.href; + return window.location; } }; +exports.getTopWindowUrl = function () { + return this.getTopWindowLocation().href; +}; + exports.logWarn = function (msg) { if (debugTurnedOn() && console.warn) { console.warn('WARNING: ' + msg); @@ -356,6 +360,15 @@ exports.isEmpty = function (object) { return true; }; +/** + * Return if string is empty, null, or undefined + * @param str string to test + * @returns {boolean} if string is empty + */ +exports.isEmptyStr = function(str) { + return this.isStr(str) && (!str || 0 === str.length); +}; + /** * Iterate object with the function * falls back to es5 `forEach` @@ -507,9 +520,9 @@ export function getValue(obj, key) { return obj[key]; } -export function getBidderCodes() { +export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { // this could memoize adUnits - return $$PREBID_GLOBAL$$.adUnits.map(unit => unit.bids.map(bid => bid.bidder) + return adUnits.map(unit => unit.bids.map(bid => bid.bidder) .reduce(flatten, [])).reduce(flatten).filter(uniques); } diff --git a/src/video.js b/src/video.js new file mode 100644 index 00000000000..eee1a9dbbd8 --- /dev/null +++ b/src/video.js @@ -0,0 +1,8 @@ +import { videoAdapters } from './adaptermanager'; + +/** + * Helper functions for working with video-enabled adUnits + */ +export const videoAdUnit = adUnit => adUnit.mediaType === 'video'; +const nonVideoBidder = bid => !videoAdapters.includes(bid.bidder); +export const hasNonVideoBidder = adUnit => adUnit.bids.filter(nonVideoBidder).length; diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 363e6ac8a95..75a90bb2b42 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -973,90 +973,6 @@ export function getAdUnits() { "requestId": "1ff753bd4ae5cb" } ] - }, - { - "code": "/7780971/apex_sparks_300", - "sizes": [ - [ - 300, - 250 - ] - ], - "bids": [ - { - "bidder": "sonobi", - "params": { - "dom_id": "div-gpt-ad-1455548812677-0", - "ad_unit": "/7780971/apex_sparks_300" - }, - "placementCode": "/7780971/apex_sparks_300", - "sizes": [ - [ - 300, - 250 - ] - ], - "bidId": "43a45a2cd8c6ef9", - "bidderRequestId": "42e2fe519b7c653", - "requestId": "1ff753bd4ae5cb" - } - ] - }, - { - "code": "/7780971/apex_sparks_skyscraper_x600", - "sizes": [ - [ - 300, - 600 - ] - ], - "bids": [ - { - "bidder": "sonobi", - "params": { - "dom_id": "div-gpt-ad-1455548812677-1", - "placement_id": "21d2da738fe0ba795cfb-test" - }, - "placementCode": "/7780971/apex_sparks_skyscraper_x600", - "sizes": [ - [ - 300, - 600 - ] - ], - "bidId": "44f16af92ca7607", - "bidderRequestId": "42e2fe519b7c653", - "requestId": "1ff753bd4ae5cb" - } - ] - }, - { - "code": "/7780971/apex_sparks_banner_x90", - "sizes": [ - [ - 728, - 90 - ] - ], - "bids": [ - { - "bidder": "sonobi", - "params": { - "dom_id": "div-gpt-ad-1455548812677-2", - "ad_unit": "/7780971/apex_sparks_banner_x90" - }, - "placementCode": "/7780971/apex_sparks_banner_x90", - "sizes": [ - [ - 728, - 90 - ] - ], - "bidId": "45e5c0084508efb", - "bidderRequestId": "42e2fe519b7c653", - "requestId": "1ff753bd4ae5cb" - } - ] } ]; }; diff --git a/test/helpers/index_adapter_response.js b/test/helpers/index_adapter_response.js new file mode 100644 index 00000000000..cd3d59b5757 --- /dev/null +++ b/test/helpers/index_adapter_response.js @@ -0,0 +1,96 @@ +var OPEN_MARKET = 'IOM'; +var PRIVATE_MARKET = 'IPM'; + +function cygnus_index_parse_res( response ) { + try { + if (response) { + if (typeof _IndexRequestData !== "object" || typeof _IndexRequestData.impIDToSlotID !== "object" || typeof _IndexRequestData.impIDToSlotID[response.id] === "undefined") { + return; + } + var targetMode = 1; + var callbackFn; + if (typeof _IndexRequestData.reqOptions === 'object' && typeof _IndexRequestData.reqOptions[response.id] === 'object') { + if (typeof _IndexRequestData.reqOptions[response.id].callback === "function") { + callbackFn = _IndexRequestData.reqOptions[response.id].callback; + } + if (typeof _IndexRequestData.reqOptions[response.id].targetMode === "number") { + targetMode = _IndexRequestData.reqOptions[response.id].targetMode; + } + } + + _IndexRequestData.lastRequestID = response.id; + _IndexRequestData.targetIDToBid = {}; + _IndexRequestData.targetIDToResp = {}; + var allBids = []; + var seatbidLength = typeof response.seatbid === "undefined" ? 0 : response.seatbid.length; + for (var i = 0; i < seatbidLength; i++) { + for (var j = 0; j < response.seatbid[i].bid.length; j++) { + var bid = response.seatbid[i].bid[j]; + if (typeof bid.ext !== "object" || typeof bid.ext.pricelevel !== "string") { + continue; + } + if (typeof _IndexRequestData.impIDToSlotID[response.id][bid.impid] === "undefined") { + continue; + } + var slotID = _IndexRequestData.impIDToSlotID[response.id][bid.impid]; + if (typeof index_slots === "undefined") { + index_slots = []; + } + if (typeof _IndexRequestData.targetIDToBid === "undefined") { + _IndexRequestData.targetIDToBid = {}; + } + if (typeof _IndexRequestData.targetIDToResp === "undefined") { + _IndexRequestData.targetIDToResp = {}; + } + var targetID; + var targetPrefix; + if (typeof bid.ext.dealid === "string") { + if (targetMode === 1) { + targetID = slotID + bid.ext.pricelevel; + } else { + targetID = slotID + "_" + bid.ext.dealid; + } + targetPrefix = PRIVATE_MARKET + '_'; + } else { + targetID = slotID + bid.ext.pricelevel; + targetPrefix = OPEN_MARKET + '_'; + } + index_slots.push(targetPrefix + targetID); + if (_IndexRequestData.targetIDToBid[targetID] === undefined) { + _IndexRequestData.targetIDToBid[targetID] = [bid.adm]; + } else { + _IndexRequestData.targetIDToBid[targetID].push(bid.adm); + } + var impBid = {}; + impBid.impressionID = bid.impid; + if (typeof bid.ext.dealid !== 'undefined') { + impBid.dealID = bid.ext.dealid; + } + impBid.bid = bid.price; + impBid.slotID = slotID; + impBid.priceLevel = bid.ext.pricelevel; + impBid.target = targetPrefix + targetID; + _IndexRequestData.targetIDToResp[targetID] = impBid; + allBids.push(impBid); + } + } + if (typeof callbackFn === "function") { + if (allBids.length === 0) { + callbackFn(response.id); + } else { + callbackFn(response.id, allBids); + } + } + + } + } catch (e) { } + if (typeof index_slots === "undefined") { + index_slots = []; + } + + if (typeof window.cygnus_index_ready_state === 'function') { + window.cygnus_index_ready_state(); + } +} + +exports.cygnus_index_parse_res = cygnus_index_parse_res; diff --git a/test/helpers/index_adapter_utils.js b/test/helpers/index_adapter_utils.js new file mode 100644 index 00000000000..5dac1b1679f --- /dev/null +++ b/test/helpers/index_adapter_utils.js @@ -0,0 +1,324 @@ +var AllowedAdUnits = [[728, 90], [120, 600], [300, 250], [160, 600], [336, 280], [234, 60], [300, 600], [300, 50], [320, 50], [970, 250], [300, 1050], [970, 90], [180, 150]]; +var UnsupportedAdUnits = [[700, 100], [100, 600], [300, 200], [100, 600], [300, 200], [200, 60], [900, 200], [300, 1000], [900, 90], [100, 100]]; + +exports.supportedSizes = AllowedAdUnits; +exports.unsupportedSizes = UnsupportedAdUnits; + +var DefaultSiteID = 234567; +var DefaultPlacementCodePrefix = "placementCode-"; +var DefaultCurrency = 'USD'; +var DefaultDspID = 124; +var DefaultTradingDeskID = 3456; +var DefaultCreativeID = 123234; +var DefaultBrandID = 123356; +var DefaultBrand = "LA Tourism & Convention Board"; +var DefaultAdDoman = 2342342; +var DefaultPriceLevel = 1000; //only this is important? +var DefaultDeal = '515'; +var DefaultDealName = 'name: testdeal'; +var DefaultDealID = 'ixdl'; + +var ADAPTER_CODE = 'indexExchange'; + +exports.DefaultSiteID = DefaultSiteID; +exports.DefaultPlacementCodePrefix = DefaultPlacementCodePrefix; +exports.DefaultCurrency = DefaultCurrency; +exports.DefaultDspID = DefaultDspID; +exports.DefaultTradingDeskID = DefaultTradingDeskID; +exports.DefaultCreativeID = DefaultCreativeID; +exports.DefaultBrandID = DefaultBrandID; +exports.DefaultBrand = DefaultBrand; +exports.DefaultAdDoman = DefaultAdDoman; +exports.DefaultPriceLevel = DefaultPriceLevel; +exports.DefaultDeal = DefaultDeal; +exports.DefaultDealName = DefaultDealName; +exports.DefaultDealID = DefaultDealID; + +exports.ADAPTER_CODE = ADAPTER_CODE; + +function _createBidSlot(placementCode, indexSlotID, sizes, config) { + config = config || {}; + var bid = {}; + bid.bidder = ('bidder' in config) ? config.bidder : ADAPTER_CODE; + bid.placementCode = placementCode; + bid.params = {}; + bid.params.id = indexSlotID; + bid.params.siteID = ('siteID' in config) ? config.siteID : DefaultSiteID; + bid.sizes = sizes; + + //optional parameter + if ( typeof config.timeout !== 'undefined' ){ + bid.params.timeout = config.timeout; + } + if ( typeof config.tier2SiteID !== 'undefined' ){ + bid.params.tier2SiteID = config.tier2SiteID; + } + if ( typeof config.tier3SiteID !== 'undefined' ){ + bid.params.tier3SiteID = config.tier3SiteID; + } + + //special parameter + if ( typeof(config.missingSlotID) !== 'undefined' ){ + delete bid.params.id; + } + if ( typeof(config.missingSiteID) !== 'undefined' ){ + delete bid.params.siteID; + } + return bid; +} + +exports.createBidSlot = _createBidSlot; + +exports.createBidSlots = function( numSlot, numSize ) { + if( typeof numSlot === 'undefined' ) numSlot = 1; + if( typeof numSize === 'undefined' ) numSize = 1; + + var bids = new Array( numSlot ); + + var mkPlacementCode = function(i, j) { return DefaultPlacementCodePrefix + i + "_" + j; }; + for( var i=0; i [bid.ext.sid, bid])); + + var compared = compareOnKeys(lstore, rstore); + var matched = compared.intersection.map(function(pair) { return { configured: pair.left, sent: pair.right, name: pair.name } }); + + return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly } , matched: matched}; +} + +exports.matchBidsOnSize = function(lhs, rhs) { + var lonly = []; + var ronly = []; + + var configured = []; + for (var i = 0; i < lhs.length; i++) { + var group = lhs[i]; + for (var j = 0; j < group.length; j++) { + var bid = group[j]; + configured.push([bid.size[0] + 'x' + bid.size[1], bid]); + } + } + + var lstore = createObjectFromArray(configured); + var rstore = createObjectFromArray(rhs.map(bid => [ bid.banner.w + 'x' + bid.banner.h, bid])); + + var compared = compareOnKeys(lstore, rstore); + var matched = compared.intersection.map(function(pair) { return { configured: pair.left, sent: pair.right, name: pair.name } }); + + return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly } , matched: matched}; +} + +exports.getBidResponse = function( configuredBids, urlJSON, optionalPriceLevel, optionalResponseIdentifier, optionalPassOnBid, optionalResponseParam ) { + if( typeof configuredBids === 'undefined' || typeof urlJSON === 'undefined' ) return {}; + var response = {}; + + response.cur = DefaultCurrency; + response.id = urlJSON.r.id; + response.seatbid = []; + + optionalPassOnBid = optionalPassOnBid || []; + + var priceLevel = DefaultPriceLevel; + var adCount = 1; + + for( var i=0; i< configuredBids.length; i++ ) { + + var bidObj = {}; + bidObj.seat = (DefaultTradingDeskID+i).toString(); + bidObj.bid = []; + + var sizes = configuredBids[i].sizes; + var impressionID = 1; + for( var j=0; j { expect(secondPlacementCode).to.eql('bar'); }); + it('should include bid request bidId as the adId', () => { + expect(firstBid).to.have.property('adId', 'bidId1'); + expect(secondBid).to.have.property('adId', 'bidId2'); + }); + it('should have a good statusCode', () => { expect(firstBid.getStatusCode()).to.eql(1); expect(secondBid.getStatusCode()).to.eql(1); @@ -176,6 +181,11 @@ describe('aardvark adapter', () => { expect(secondBid.getStatusCode()).to.eql(2); }); + it('should include bid request bidId as the adId', () => { + expect(firstBid).to.have.property('adId', 'bidId1'); + expect(secondBid).to.have.property('adId', 'bidId2'); + }); + it('should pass the correct placement code as first param', () => { let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0]; diff --git a/test/spec/adapters/adblade_spec.js b/test/spec/adapters/adblade_spec.js new file mode 100644 index 00000000000..d98a2b3a6ca --- /dev/null +++ b/test/spec/adapters/adblade_spec.js @@ -0,0 +1,168 @@ +import {expect} from 'chai'; +import Adapter from '../../../src/adapters/adblade'; +import bidManager from '../../../src/bidmanager'; +import adLoader from '../../../src/adloader'; + +describe('adblade adapter', () => { + 'use strict'; + + let bidsRequestedOriginal; + let adapter; + let sandbox; + + const bidderRequest = { + bidderCode: 'adblade', + bids: [ + { + bidId: 'bidId1', + bidder: 'adblade', + placementCode: 'foo', + sizes: [[728, 90]], + params: { + partnerId: 1, + } + } + ] + }; + + beforeEach(() => { + bidsRequestedOriginal = pbjs._bidsRequested; + pbjs._bidsRequested = []; + + adapter = new Adapter(); + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + + pbjs._bidsRequested = bidsRequestedOriginal; + }); + + describe('callBids', () => { + + beforeEach(() => { + sandbox.stub(adLoader, 'loadScript'); + adapter.callBids(bidderRequest); + }); + + it('should load script', () => { + sinon.assert.calledOnce(adLoader.loadScript); + + expect(adLoader.loadScript.firstCall.args[0]).to.include('adblade.com'); + expect(adLoader.loadScript.firstCall.args[0]).to.include('prebidjs'); + }); + + }); + + describe('adbladeResponse', () => { + + it('should exist and be a function', () => { + expect(pbjs.adbladeResponse).to.exist.and.to.be.a('function'); + }); + }); + + describe('add bids to the manager', () => { + + let firstBid; + + beforeEach(() => { + sandbox.stub(bidManager, 'addBidResponse'); + + pbjs._bidsRequested.push(bidderRequest); + + // respond + let bidderReponse = { + "cur": "USD", + "id": "03a9404f-7b39-4d04-b50b-6459b9aa3ffa", + "seatbid": [ + { + "seat": "1", + "bid": [ + { + "nurl": "http://example.com", + "crid": "20063", + "adomain": [ + "www.adblade.com" + ], + "price": 3, + "w": 728, + "h": 90, + "id": "1", + "adm": "
    ", + "impid": "bidId1", + "cid": "99" + } + ] + } + ] + }; + pbjs.adbladeResponse(bidderReponse); + + firstBid = bidManager.addBidResponse.firstCall.args[1]; + }); + + it('should add a bid object for each bid', () => { + sinon.assert.calledOnce(bidManager.addBidResponse); + }); + + it('should pass the correct placement code as first param', () => { + let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; + + expect(firstPlacementCode).to.eql('foo'); + }); + + it('should have a good statusCode', () => { + expect(firstBid.getStatusCode()).to.eql(1); + }); + + it('should add the CPM to the bid object', () => { + expect(firstBid).to.have.property('cpm', 3); + }); + + it('should include the ad to the bid object', () => { + expect(firstBid).to.have.property('ad'); + }); + + it('should include the size to the bid object', () => { + expect(firstBid).to.have.property('width', 728); + expect(firstBid).to.have.property('height', 90); + }); + }); + + describe('add empty bids if no bid returned', () => { + + let firstBid; + + beforeEach(() => { + sandbox.stub(bidManager, 'addBidResponse'); + + pbjs._bidsRequested.push(bidderRequest); + + // respond + let bidderReponse = {}; + pbjs.adbladeResponse(bidderReponse); + + firstBid = bidManager.addBidResponse.firstCall.args[1]; + }); + + it('should add a bid object for each bid', () => { + sinon.assert.calledOnce(bidManager.addBidResponse); + }); + + it('should have an error statusCode', () => { + expect(firstBid.getStatusCode()).to.eql(2); + }); + + it('should pass the correct placement code as first param', () => { + let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; + + expect(firstPlacementCode).to.eql('foo'); + }); + + it('should add the bidder code to the bid object', () => { + expect(firstBid).to.have.property('bidderCode', 'adblade'); + }); + + }); +}); diff --git a/test/spec/adapters/appnexusAst_spec.js b/test/spec/adapters/appnexusAst_spec.js index 77a64ddf57c..6221136dc3f 100644 --- a/test/spec/adapters/appnexusAst_spec.js +++ b/test/spec/adapters/appnexusAst_spec.js @@ -96,6 +96,24 @@ describe('AppNexusAdapter', () => { REQUEST.bids[0].params = backup; }); + it('attaches valid video params to the tag', () => { + REQUEST.bids[0].params.video = { + id: 123, + minduration: 100, + foobar: 'invalid' + }; + + adapter.callBids(REQUEST); + + const request = JSON.parse(requests[0].requestBody).tags[0]; + expect(request.video).to.deep.equal({ + id: 123, + minduration: 100 + }); + + delete REQUEST.bids[0].params.video; + }); + it('sends bid request to ENDPOINT via POST', () => { adapter.callBids(REQUEST); expect(requests[0].url).to.equal(ENDPOINT); @@ -206,10 +224,7 @@ describe('AppNexusAdapter', () => { sinon.assert.calledOnce(bidmanager.addBidResponse); const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + expect(response).to.have.property('statusMessage', 'Bid available'); }); it('handles JSON.parse errors', () => { diff --git a/test/spec/adapters/centro_spec.js b/test/spec/adapters/centro_spec.js new file mode 100644 index 00000000000..68b80c58107 --- /dev/null +++ b/test/spec/adapters/centro_spec.js @@ -0,0 +1,228 @@ +describe('centro adapter tests', function () { + var expect = require('chai').expect; + var assert = require('chai').assert; + var urlParse = require('url-parse'); + var querystringify = require('querystringify'); + + var adapter = require('src/adapters/centro'); + var bidmanager = require('src/bidmanager'); + var adLoader = require('src/adloader'); + var utils = require('src/utils'); + + window.pbjs = window.pbjs || {}; + if (typeof(pbjs)==="undefined"){ + var pbjs = window.pbjs; + } + + var spyLoadScript; + beforeEach(function () { + spyLoadScript = sinon.spy(adLoader, 'loadScript'); + }); + + afterEach(function () { + spyLoadScript.restore(); + }); + + var logErrorSpy; + beforeEach(function () { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + + afterEach(function () { + logErrorSpy.restore(); + }); + + describe('creation of bid url', function () { + + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = []; + } + + it('should fix parameter name', function () { + + var params = { + bidderCode: 'centro', + bids: [ + { + bidder: 'centro', + sizes: [[300, 250]], + params: { + unit: 28136, + page_url: 'http://test_url.ru' + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'centro', + sizes: [[728, 90]], + params: { + unit: 28137 + }, + placementCode: 'div-gpt-ad-12345-2' + }, + { + bidder: 'centro', + sizes: [[728, 90]], + params: {}, + placementCode: 'div-gpt-ad-12345-3' + } + ] + }; + + adapter().callBids(params); + var bidUrl1 = spyLoadScript.getCall(0).args[0]; + var bidUrl2 = spyLoadScript.getCall(1).args[0]; + + sinon.assert.calledWith(logErrorSpy, 'Bid has no unit', 'centro'); + sinon.assert.calledWith(spyLoadScript, bidUrl1); + + var parsedBidUrl = urlParse(bidUrl1); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + var generatedCallback = 'adCentroHandler_28136300x250'; + + expect(parsedBidUrl.hostname).to.equal('staging.brand-server.com'); + expect(parsedBidUrl.pathname).to.equal('/hb'); + + expect(parsedBidUrlQueryString).to.have.property('s').and.to.equal('28136'); + expect(parsedBidUrlQueryString).to.have.property('url').and.to.equal('http://test_url.ru'); + expect(parsedBidUrlQueryString).to.have.property('sz').and.to.equal('300x250'); + expect(parsedBidUrlQueryString).to.have.property('callback').and.to.equal(generatedCallback); + + sinon.assert.calledWith(spyLoadScript, bidUrl2); + + parsedBidUrl = urlParse(bidUrl2); + parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + generatedCallback = 'adCentroHandler_28137728x90'; + + expect(parsedBidUrl.hostname).to.equal('t.brand-server.com'); + expect(parsedBidUrl.pathname).to.equal('/hb'); + + expect(parsedBidUrlQueryString).to.have.property('s').and.to.equal('28137'); + expect(parsedBidUrlQueryString).to.not.have.property('url'); + expect(parsedBidUrlQueryString).to.have.property('sz').and.to.equal('728x90'); + expect(parsedBidUrlQueryString).to.have.property('callback').and.to.equal(generatedCallback); + }); + + }); + + describe('handling of the callback response', function () { + if (typeof(pbjs._bidsReceived)==="undefined"){ + pbjs._bidsReceived = []; + } + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = []; + } + if (typeof(pbjs._adsReceived)==="undefined"){ + pbjs._adsReceived = []; + } + + var params = { + bidderCode: 'centro', + bids: [ + { + bidder: 'centro', + sizes: [[300, 250]], + params: { + unit: 28136 + }, + placementCode: '/19968336/header-bid-tag-0' + }, + { + bidder: 'centro', + sizes: [[728, 90]], + params: { + unit: 111111 + }, + placementCode: '/19968336/header-bid-tag-1' + }, + { + bidder: 'centro', + sizes: [[728, 90]], + params: { + unit: 222222 + }, + placementCode: '/19968336/header-bid-tag-2' + }, + { + bidder: 'centro', + sizes: [[728, 90]], + params: { + unit: 333333 + }, + placementCode: '/19968336/header-bid-tag-3' + } + ] + }; + + it('callback function should exist', function () { + + adapter().callBids(params); + + expect(window['adCentroHandler_28136300x250']).to.exist.and.to.be.a('function'); + expect(window['adCentroHandler_111111728x90']).to.exist.and.to.be.a('function'); + }); + + it('bidmanager.addBidResponse should be called with correct arguments', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + adapter().callBids(params); + + var adUnits = new Array(); + var unit = new Object(); + unit.bids = params.bids; + unit.code = '/19968336/header-bid-tag'; + unit.sizes=[[300,250],[728,90]]; + adUnits.push(unit); + + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = [params]; + } + else{ + pbjs._bidsRequested.push(params); + } + + pbjs.adUnits = adUnits; + + var response = {"adTag":"
    test content
    ","statusMessage":"Bid available","height":250,"_comment":"","value":0.2,"width":300,"sectionID":28136}; + var response2 = {"adTag":"","statusMessage":"No bid.","height":0,"value":0,"width":0,"sectionID":111111}; + var response3 = {"adTag":"","height":0,"value":0,"width":0,"sectionID":222222}; + var response4 = ''; + + window['adCentroHandler_28136300x250'](response); + window['adCentroHandler_111111728x90'](response2); + window['adCentroHandler_222222728x90'](response3); + window['adCentroHandler_333333728x90'](response4); + + var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; + var bidObject2 = stubAddBidResponse.getCall(1).args[1]; + var bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; + var bidObject3 = stubAddBidResponse.getCall(2).args[1]; + var bidPlacementCode4 = stubAddBidResponse.getCall(3).args[0]; + var bidObject4 = stubAddBidResponse.getCall(3).args[1]; + + expect(logErrorSpy.getCall(0).args[0]).to.equal('Requested unit is 111111. No bid.'); + expect(logErrorSpy.getCall(1).args[0]).to.equal('Requested unit is 222222. Bid has missmatch format.'); + expect(logErrorSpy.getCall(2).args[0]).to.equal('Requested unit is 333333. Response has no bid.'); + + expect(bidPlacementCode1).to.equal('/19968336/header-bid-tag-0'); + expect(bidObject1.cpm).to.equal(0.2); + expect(bidObject1.ad).to.equal('
    test content
    '); + expect(bidObject1.width).to.equal(300); + expect(bidObject1.height).to.equal(250); + expect(bidObject1.getStatusCode()).to.equal(1); + expect(bidObject1.bidderCode).to.equal('centro'); + + expect(bidPlacementCode2).to.equal('/19968336/header-bid-tag-1'); + expect(bidObject2.getStatusCode()).to.equal(2); + expect(bidPlacementCode3).to.equal('/19968336/header-bid-tag-2'); + expect(bidObject3.getStatusCode()).to.equal(2); + expect(bidPlacementCode4).to.equal('/19968336/header-bid-tag-3'); + expect(bidObject4.getStatusCode()).to.equal(2); + + stubAddBidResponse.restore(); + }); + }); +}); \ No newline at end of file diff --git a/test/spec/adapters/getintent_spec.js b/test/spec/adapters/getintent_spec.js new file mode 100644 index 00000000000..9560bf56c9c --- /dev/null +++ b/test/spec/adapters/getintent_spec.js @@ -0,0 +1,121 @@ +import Adapter from '../../../src/adapters/getintent'; +import bidManager from '../../../src/bidmanager'; +import {expect} from 'chai'; + +describe('getintent media adapter test', () => { + + let adapter; + + window.gi_hb = { + makeBid: function(bidRequest, callback) { + var pid = bidRequest.pid; + var tid = bidRequest.tid; + + if (pid == "p1" || pid == "p2") { + callback({ + ad : `Ad Markup ${pid} ${tid}`, + cpm : 2.71, + size : `${bidRequest.size}` + }, bidRequest); + } else { + callback({ + no_bid: 1 + }, bidRequest); + } + } + }; + + function callOut() { + adapter.callBids({ + bidderCode: "getintent", + bids: [ + { + bidder: "getintent", + adUnitCode: "test1", + sizes: [[320,240]], + params: { + pid: "p1", + tid: "t1", + cur: "USD" + } + }, + { + bidder: "getintent", + adUnitCode: "test2", + sizes: [[720,90]], + params: { + pid: "p2", + tid: "t1", + cur: "USD" + } + }, + { + bidder: "getintent", + adUnitCode: "test3", + sizes: [[400,500]], + params: { + pid: "p3", + tid: "t2", + cur: "USD" + } + } + ] + }); + } + + beforeEach(() => { + adapter = new Adapter(); + }); + + afterEach(() => { + }); + + describe('adding bids to the manager', () => { + + let firstBid; + let secondBid; + let thirdBid; + + beforeEach(() => { + sinon.stub(bidManager, 'addBidResponse'); + callOut(); + firstBid = bidManager.addBidResponse.firstCall.args[1]; + secondBid = bidManager.addBidResponse.secondCall.args[1]; + thirdBid = bidManager.addBidResponse.thirdCall.args[1]; + }); + + afterEach(() => { + bidManager.addBidResponse.restore(); + }); + + it('was called three times', () => { + sinon.assert.calledThrice(bidManager.addBidResponse); + }); + + it('will respond to the first bid', () => { + expect(firstBid).to.have.property('ad', 'Ad Markup p1 t1'); + expect(firstBid).to.have.property('cpm', 2.71); + expect(firstBid).to.have.property('width', '320'); + expect(firstBid).to.have.property('height', '240'); + }); + + it('will respond to the second bid', () => { + expect(secondBid).to.have.property('ad', 'Ad Markup p2 t1'); + expect(secondBid).to.have.property('cpm', 2.71); + expect(secondBid).to.have.property('width', '720'); + expect(secondBid).to.have.property('height', '90'); + }); + + it('wont respond to the third bid', () => { + expect(thirdBid).to.not.have.property('ad'); + expect(thirdBid).to.not.have.property('cpm'); + }); + + it('will add the bidder code to the bid object', () => { + expect(firstBid).to.have.property('bidderCode', 'getintent'); + expect(secondBid).to.have.property('bidderCode', 'getintent'); + expect(thirdBid).to.have.property('bidderCode', 'getintent'); + }); + }); + +}); diff --git a/test/spec/adapters/indexExchange_response_spec.js b/test/spec/adapters/indexExchange_response_spec.js new file mode 100644 index 00000000000..d394440122c --- /dev/null +++ b/test/spec/adapters/indexExchange_response_spec.js @@ -0,0 +1,520 @@ +import Adapter from '../../../src/adapters/indexExchange'; +import bidManager from '../../../src/bidmanager'; +import adLoader from '../../../src/adloader'; + +var assert = require('chai').assert; +var IndexUtils = require('../../helpers/index_adapter_utils.js'); +var IndexResponse = require('../../helpers/index_adapter_response.js'); +var HeaderTagRequest = '/cygnus'; +var SlotThreshold = 20; +var ADAPTER_CODE = 'indexExchange'; +var DefaultValue = { + dealID : 'IXDeal' +}; +window.pbjs = window.pbjs || {}; +var ResponseStatus = { + noBid: "Bid returned empty or error response" +}; + +describe('indexExchange adapter - Response', function () { + let adapter; + let sandbox; + + beforeEach( function() { + window._IndexRequestData = {}; + _IndexRequestData.impIDToSlotID = {}; + _IndexRequestData.reqOptions = {}; + _IndexRequestData.targetIDToResp = {}; + window.cygnus_index_args = {}; + + adapter = new Adapter(); + sandbox = sinon.sandbox.create(); + sandbox.stub(bidManager, 'addBidResponse'); + sandbox.stub(adLoader, 'loadScript'); + }); + + afterEach( function() { + sandbox.restore(); + }); + + it('test_prebid_indexAdapter_response_deal_1_1: response for single slot with single size contains alpha deal -> bid fetched into prebid', function () { + var configuredBids = IndexUtils.createBidSlots(1, 1); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + { ext: { dealid: 'ixDeal' } } // first slot first size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + IndexResponse.cygnus_index_parse_res( asResponse ); + + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + + assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[i].siteID); + assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[i].bidderCode); + assert.equal(pair.prebid[i].width, pair.expected[i].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[i].width); + assert.equal(pair.prebid[i].height, pair.expected[i].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[i].height); + assert.equal(pair.prebid[i].ad, pair.expected[i].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[i].ad); + assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[i].cpm); + assert.equal(pair.prebid[i].dealId, pair.expected[i].dealId, "adapter response for " + pair.placementCode + " deaiid is set to "+pair.expected[i].dealId); + } + + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + it('test_prebid_indexAdapter_response_deal_1_2: response for single slot with single size contains numeric deal -> bid fetched into prebid', function () { + var configuredBids = IndexUtils.createBidSlots(1, 1); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + { ext: { dealid: '239' } } // first slot first size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + + assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[i].siteID); + assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[i].bidderCode); + assert.equal(pair.prebid[i].width, pair.expected[i].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[i].width); + assert.equal(pair.prebid[i].height, pair.expected[i].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[i].height); + assert.equal(pair.prebid[i].ad, pair.expected[i].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[i].ad); + assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[i].cpm); + assert.equal(pair.prebid[i].dealId, pair.expected[i].dealId, "adapter response for " + pair.placementCode + " deaiid is set to "+pair.expected[i].dealId); + } + + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + + it('test_prebid_indexAdapter_response_deal_1_3: response for single slot with single size contains alpha-numeric deal starting with numeric -> bid fetched into prebid', function () { + var configuredBids = IndexUtils.createBidSlots(1, 1); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + { ext: { dealid: '1234Deal' } } // first slot first size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + + assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[i].siteID); + assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[i].bidderCode); + assert.equal(pair.prebid[i].width, pair.expected[i].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[i].width); + assert.equal(pair.prebid[i].height, pair.expected[i].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[i].height); + assert.equal(pair.prebid[i].ad, pair.expected[i].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[i].ad); + assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[i].cpm); + assert.equal(pair.prebid[i].dealId, pair.expected[i].dealId, "adapter response for " + pair.placementCode + " deaiid is set to "+pair.expected[i].dealId); + } + + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + it('test_prebid_indexAdapter_response_deal_1_4: response for single slot with single size contains alpha-numeric deal starting with non-numeric -> bid fetched into prebid ', function () { + var configuredBids = IndexUtils.createBidSlots(1, 1); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + { ext: { dealid: 'deal1234' } } // first slot first size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); // Alpha numeric starting with non-numeric + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + + assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[i].siteID); + assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[i].bidderCode); + assert.equal(pair.prebid[i].width, pair.expected[i].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[i].width); + assert.equal(pair.prebid[i].height, pair.expected[i].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[i].height); + assert.equal(pair.prebid[i].ad, pair.expected[i].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[i].ad); + assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[i].cpm); + assert.equal(pair.prebid[i].dealId, pair.expected[i].dealId, "adapter response for " + pair.placementCode + " deaiid is set to "+pair.expected[i].dealId); + } + + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + + + it('test_prebid_indexAdapter_response_deal_2_1: response for single slot with multi size, all deal bids returned -> all bid fetched into prebid as deal bid', function () { + var sizeCount = 2; + var configuredBids = IndexUtils.createBidSlots(1, sizeCount); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + { ext: { deal: 'deal1', dealid: 'ixDealID1', dealname: 'deal name 1' } }, // first slot first size + { ext: { deal: 'deal2', dealid: 'ixDealID2', dealname: 'deal name 2' } }, // first slot second size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + + for ( var j = 0; j < pair.expected.length; j++ ) { + assert.equal(pair.prebid[j].siteID, pair.expected[j].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[i].siteID); + assert.equal(pair.prebid[j].bidderCode, pair.expected[j].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[i].bidderCode); + assert.equal(pair.prebid[j].width, pair.expected[j].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[i].width); + assert.equal(pair.prebid[j].height, pair.expected[j].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[i].height); + assert.equal(pair.prebid[j].ad, pair.expected[j].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[i].ad); + assert.equal(pair.prebid[j].cpm, pair.expected[j].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[i].cpm); + assert.equal(pair.prebid[j].dealId, pair.expected[j].dealId, "adapter response for " + pair.placementCode + " deaiid is set to "+pair.expected[i].dealId); + } + } + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + + it('test_prebid_indexAdapter_response_deal_2_2: response for single slot with multi size, some deal resposne returned and the rest non deal response -> all bid fetched, only deal response has dealID', function () { + var sizeCount = 2; + var configuredBids = IndexUtils.createBidSlots(1, sizeCount); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + { ext: { deal: 'deal1', dealid: 'ixDealID1', dealname: 'deal name 1' } } // first slot first size + // No deal on first slot second size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + + for ( var j = 0; j < pair.expected.length; j++ ) { + assert.equal(pair.prebid[j].siteID, pair.expected[j].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[i].siteID); + assert.equal(pair.prebid[j].bidderCode, pair.expected[j].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[i].bidderCode); + assert.equal(pair.prebid[j].width, pair.expected[j].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[i].width); + assert.equal(pair.prebid[j].height, pair.expected[j].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[i].height); + assert.equal(pair.prebid[j].ad, pair.expected[j].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[i].ad); + assert.equal(pair.prebid[j].cpm, pair.expected[j].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[i].cpm); + if ( i === 0) { + assert.equal(pair.prebid[j].dealId, pair.expected[j].dealId, "adapter response for " + pair.placementCode + " deaiid is set to "+pair.expected[i].dealId); + } else { + assert.isUndefined( pair.prebid[j].dealId, "adapter response for " + pair.placementCode + " deaiid is not set"); + } + } + } + + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + + it('test_prebid_indexAdapter_response_deal_2_3: response for single slot with multi size, all returned as non-deal response -> all bid fetched, no response has dealID', function () { + var sizeCount = 2; + var configuredBids = IndexUtils.createBidSlots(1, sizeCount); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + {}, + {} + // No deal on first slot first size + // No deal on first slot second size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + for ( var j = 0; j < pair.expected.length; j++ ) { + assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[i].siteID); + assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[i].bidderCode); + assert.equal(pair.prebid[i].width, pair.expected[i].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[i].width); + assert.equal(pair.prebid[i].height, pair.expected[i].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[i].height); + assert.equal(pair.prebid[i].ad, pair.expected[i].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[i].ad); + assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[i].cpm); + assert.isUndefined( pair.prebid[i].dealId, "adapter response for " + pair.placementCode + " deaiid is not set"); + } + } + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + + it('test_prebid_indexAdapter_response_deal_3_1: multi slots, all responses contain deal -> all bid fetched into prebid as deal bid', function () { + var slotCount = 2; + var configuredBids = IndexUtils.createBidSlots(slotCount, 1); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + { ext: { dealid: 'ixDeal1' } } // first slot first size + ], + [ + { ext: { dealid: 'ixDeal2' } } // second slot first size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[0].siteID); + assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[0].bidderCode); + assert.equal(pair.prebid[0].width, pair.expected[0].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[0].width); + assert.equal(pair.prebid[0].height, pair.expected[0].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[0].height); + assert.equal(pair.prebid[0].ad, pair.expected[0].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[0].ad); + assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[0].cpm); + assert.equal(pair.prebid[0].dealId, pair.expected[0].dealId, "adapter response for " + pair.placementCode + " deaiid is set to "+pair.expected[0].dealId); + } + + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + + it('test_prebid_indexAdapter_response_deal_3_2: multi slots, some responses contain deal -> all bid fetched, only deal response has dealID', function () { + var slotCount = 2; + var configuredBids = IndexUtils.createBidSlots(slotCount, 1); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + { ext: { dealid: 'ixDeal1' } } // first slot first size + ], + [ + {} + // no deal on second slot first size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + var count = 0; + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[0].siteID); + assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[0].bidderCode); + assert.equal(pair.prebid[0].width, pair.expected[0].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[0].width); + assert.equal(pair.prebid[0].height, pair.expected[0].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[0].height); + assert.equal(pair.prebid[0].ad, pair.expected[0].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[0].ad); + assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[0].cpm); + if ( count === 0 ) { // if first slot, check deal parameter + assert.equal(pair.prebid[0].dealId, pair.expected[0].dealId, "adapter response for " + pair.placementCode + " deaiid is set to "+pair.expected[0].dealId); + } else { + assert.isUndefined( pair.prebid[0].dealId, "adapter response for " + pair.placementCode + " deaiid is not defined"); + } + count ++; + } + + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); + + + it('test_prebid_indexAdapter_response_deal_3_3: multi slots, no responses contain deal -> all bid fetched, no response has dealID ', function () { + var slotCount = 2; + var configuredBids = IndexUtils.createBidSlots(slotCount, 1); + adapter.callBids({ bids: configuredBids }); + + var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); + var optionalResponseParam = [ + [ + {} + // no deal on first slot first size + ], + [ + {} + // no deal on second slot first size + ] + ]; + var asResponse = IndexUtils.getBidResponse( configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam ); + IndexResponse.cygnus_index_parse_res( asResponse ); + var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse( configuredBids, asResponse ); + + var adapterResponse = {}; + + for ( var i = 0; i < bidManager.addBidResponse.callCount; i++ ) { + var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; + var bid = bidManager.addBidResponse.getCall(i).args[1]; + + if ( typeof adapterResponse[adUnitCode] === 'undefined'){ + adapterResponse[adUnitCode] = []; + }; + adapterResponse[adUnitCode].push(bid); + } + + var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); + + for ( var i = 0; i < prebidResponsePair.matched.length; i++) { + var pair = prebidResponsePair.matched[i]; + assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, "adapter response for " + pair.placementCode + " siteID is set to "+pair.expected[0].siteID); + assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, "adapter response for " + pair.placementCode + " bidderCode is set to "+pair.expected[0].bidderCode); + assert.equal(pair.prebid[0].width, pair.expected[0].width, "adapter response for " + pair.placementCode + " width is set to "+pair.expected[0].width); + assert.equal(pair.prebid[0].height, pair.expected[0].height, "adapter response for " + pair.placementCode + " height is set to "+pair.expected[0].height); + assert.equal(pair.prebid[0].ad, pair.expected[0].ad, "adapter response for " + pair.placementCode + " ad is set to "+pair.expected[0].ad); + assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, "adapter response for " + pair.placementCode + " cpm is set to "+pair.expected[0].cpm); + assert.isUndefined( pair.prebid[0].dealId, "adapter response for " + pair.placementCode + " deaiid is not defined"); + } + + assert.equal( prebidResponsePair.unmatched.expected.length, 0, "All AS bid response translated to Adapter response for prebid"); + assert.equal( prebidResponsePair.unmatched.prebid.length, 0, "All Adapter response for prebid is from AS bid"); + }); +}); diff --git a/test/spec/adapters/piximedia_spec.js b/test/spec/adapters/piximedia_spec.js new file mode 100644 index 00000000000..e6dedbf18cd --- /dev/null +++ b/test/spec/adapters/piximedia_spec.js @@ -0,0 +1,419 @@ +describe('Piximedia adapter tests', function () { + + var expect = require('chai').expect; + var urlParse = require('url-parse'); + + //var querystringify = require('querystringify'); + + var adapter = require('src/adapters/piximedia'); + var adLoader = require('src/adloader'); + var bidmanager = require('src/bidmanager'); + var utils = require('src/utils'); + var CONSTANTS = require('src/constants.json'); + + var pbjs = window.pbjs = window.pbjs || {}; + var spyLoadScript; + + beforeEach(function () { + spyLoadScript = sinon.spy(adLoader, 'loadScript'); + }); + + afterEach(function () { + spyLoadScript.restore(); + }); + + describe('creation of prebid url', function () { + if (typeof(pbjs._bidsReceived)==="undefined"){ + pbjs._bidsReceived = []; + } + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = []; + } + if (typeof(pbjs._adsReceived)==="undefined"){ + pbjs._adsReceived = []; + } + + it('should call the Piximedia prebid URL once on valid calls', function () { + var params = { + bidderCode: 'piximedia', + bidder: 'piximedia', + bids: [ + { + bidId: '4d3819cffc4d12', + sizes: [[300, 250]], + bidder: 'piximedia', + params: { siteId: 'TEST', placementId: "TEST", prebidUrl: "//resources.pm/tests/prebid/bids.js" }, + requestId: '59c318fd382219', + placementCode: '/20164912/header-bid-tag-0' + } + ] + }; + + adapter().callBids(params); + sinon.assert.calledOnce(spyLoadScript); + }); + + it('should not call the Piximedia prebid URL once on invalid calls', function () { + var params = { + bidderCode: 'piximedia', + bidder: 'piximedia', + bids: [ + { + bidId: '4d3819cffc4d12', + sizes: [[300, 250]], + bidder: 'piximedia', + params: { prebidUrl: "//resources.pm/tests/prebid/bids.js" }, // this is invalid: site and placement ID are missing + requestId: '59c318fd382219', + placementCode: '/20164912/header-bid-tag-0' + } + ] + }; + + adapter().callBids(params); + sinon.assert.notCalled(spyLoadScript); + }); + + it('should call the correct Prebid URL when using the default URL', function () { + var params = { + bidderCode: 'piximedia', + bidder: 'piximedia', + bids: [ + { + bidId: '4d3819cffc4d12', + sizes: [[300, 250]], + bidder: 'piximedia', + params: { siteId: 'TEST', placementId: "TEST" }, + requestId: '59c318fd382219', + placementCode: '/20164912/header-bid-tag-0' + } + ] + }; + + adapter().callBids(params); + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + + expect(parsedBidUrl.hostname).to.equal("static.adserver.pm"); + expect(parsedBidUrl.query).to.equal(""); + expect(parsedBidUrl.pathname.replace(/cbid=[a-f0-9]+/, "cbid=210af5668b1e23").replace(/rand=[0-9]+$/, "rand=42")).to.equal("/prebid/site_id=TEST/placement_id=TEST/jsonp=pbjs.handlePiximediaCallback/sizes=300x250/cbid=210af5668b1e23/rand=42"); + }); + + it('should call the correct Prebid URL when using the default URL with a deal and custom data', function () { + var params = { + bidderCode: 'piximedia', + bidder: 'piximedia', + bids: [ + { + bidId: '4d3819cffc4d12', + sizes: [[300, 250]], + bidder: 'piximedia', + params: { siteId: 'TEST', placementId: "TEST", dealId: 1295, custom: "bespoke", custom2: function() { return "bespoke2"; }, custom3: null, custom4: function() {} }, + requestId: '59c318fd382219', + placementCode: '/20164912/header-bid-tag-0' + } + ] + }; + + adapter().callBids(params); + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + + expect(parsedBidUrl.hostname).to.equal("static.adserver.pm"); + expect(parsedBidUrl.query).to.equal(""); + expect(parsedBidUrl.pathname.replace(/cbid=[a-f0-9]+/, "cbid=210af5668b1e23").replace(/rand=[0-9]+$/, "rand=42")).to.equal("/prebid/site_id=TEST/placement_id=TEST/l_id=1295/custom=bespoke/custom2=bespoke2/custom3=/custom4=/jsonp=pbjs.handlePiximediaCallback/sizes=300x250/cbid=210af5668b1e23/rand=42"); + }); + + it('should call the correct Prebid URL when using the default URL and overridding sizes', function () { + var params = { + bidderCode: 'piximedia', + bidder: 'piximedia', + bids: [ + { + bidId: '4d3819cffc4d12', + sizes: [[300, 250]], + bidder: 'piximedia', + params: { siteId: 'TEST', placementId: "TEST", sizes: [[300,600],[728,90]] }, + requestId: '59c318fd382219', + placementCode: '/20164912/header-bid-tag-0' + } + ] + }; + + adapter().callBids(params); + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + + expect(parsedBidUrl.hostname).to.equal("static.adserver.pm"); + expect(parsedBidUrl.query).to.equal(""); + expect(parsedBidUrl.pathname.replace(/cbid=[a-f0-9]+/, "cbid=210af5668b1e23").replace(/rand=[0-9]+$/, "rand=42")).to.equal("/prebid/site_id=TEST/placement_id=TEST/jsonp=pbjs.handlePiximediaCallback/sizes=300x600%2C728x90/cbid=210af5668b1e23/rand=42"); + }); + + it('should call the correct Prebid URL when supplying a custom URL', function () { + var params = { + bidderCode: 'piximedia', + bidder: 'piximedia', + bids: [ + { + bidId: '4d3819cffc4d12', + sizes: [[300, 250]], + bidder: 'piximedia', + params: { siteId: 'TEST', placementId: "TEST", prebidUrl: "//resources.pm/tests/prebid/bids.js" }, + requestId: '59c318fd382219', + placementCode: '/20164912/header-bid-tag-0' + } + ] + }; + + adapter().callBids(params); + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + + expect(parsedBidUrl.hostname).to.equal("resources.pm"); + expect(parsedBidUrl.query).to.equal(""); + expect(parsedBidUrl.pathname.replace(/cbid=[a-f0-9]+/, "cbid=210af5668b1e23").replace(/rand=[0-9]+$/, "rand=42")).to.equal("/tests/prebid/bids.js/site_id=TEST/placement_id=TEST/jsonp=pbjs.handlePiximediaCallback/sizes=300x250/cbid=210af5668b1e23/rand=42"); + }); + }); + + describe('handling of the callback response', function () { + if (typeof(pbjs._bidsReceived)==="undefined"){ + pbjs._bidsReceived = []; + } + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = []; + } + if (typeof(pbjs._adsReceived)==="undefined"){ + pbjs._adsReceived = []; + } + + var params = { + bidderCode: 'piximedia', + bidder: 'piximedia', + bids: [ + { + bidId: '4d3819cffc4d12', + sizes: [[300, 250]], + bidder: 'piximedia', + params: { siteId: 'TEST', placementId: "TEST", prebidUrl: "//resources.pm/tests/prebid/bids.js" }, + requestId: '59c318fd382219', + placementCode: '/20164912/header-bid-tag-0' + } + ] + }; + + it('Piximedia callback function should exist', function () { + expect(pbjs.handlePiximediaCallback).to.exist.and.to.be.a('function'); + }); + + it('bidmanager.addBidResponse should be called once with correct arguments', function () { + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + var stubGetUniqueIdentifierStr = sinon.spy(utils, "getUniqueIdentifierStr"); + + var response = { + foundbypm: true, + currency: "EUR", + cpm: 1.23, + dealId: 9948, + width: 300, + height: 250, + html: "
    ad
    " + }; + + adapter().callBids(params); + + var adUnits = []; + var unit = {}; + unit.bids = [params]; + unit.code = '/20164912/header-bid-tag'; + unit.sizes=[[300,250],[728,90]]; + adUnits.push(unit); + + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = [params]; + } else { + pbjs._bidsRequested.push(params); + } + pbjs.adUnits = adUnits; + response.cbid = stubGetUniqueIdentifierStr.returnValues[0]; + + pbjs.handlePiximediaCallback(response); + + sinon.assert.calledOnce(stubAddBidResponse); + var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + + expect(bidPlacementCode1).to.equal('/20164912/header-bid-tag-0'); + expect(bidObject1.cpm).to.equal(1.23); + expect(bidObject1.ad).to.equal("
    ad
    "); + expect(bidObject1.width).to.equal(300); + expect(bidObject1.dealId).to.equal(9948); + expect(bidObject1.height).to.equal(250); + expect(bidObject1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bidObject1.bidderCode).to.equal('piximedia'); + + stubAddBidResponse.restore(); + stubGetUniqueIdentifierStr.restore(); + }); + + it('bidmanager.addBidResponse should be called once with correct arguments on partial response', function () { + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + var stubGetUniqueIdentifierStr = sinon.spy(utils, "getUniqueIdentifierStr"); + + // this time, we do not provide dealId + var response = { + foundbypm: true, + cpm: 1.23, + width: 300, + height: 250, + currency: "EUR", + html: "
    ad
    " + }; + + adapter().callBids(params); + + var adUnits = []; + var unit = {}; + unit.bids = [params]; + unit.code = '/20164912/header-bid-tag'; + unit.sizes=[[300,250],[728,90]]; + adUnits.push(unit); + + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = [params]; + } else { + pbjs._bidsRequested.push(params); + } + pbjs.adUnits = adUnits; + response.cbid = stubGetUniqueIdentifierStr.returnValues[0]; + + pbjs.handlePiximediaCallback(response); + + sinon.assert.calledOnce(stubAddBidResponse); + var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + + expect(bidPlacementCode1).to.equal('/20164912/header-bid-tag-0'); + expect(bidObject1.cpm).to.equal(1.23); + expect(bidObject1.ad).to.equal("
    ad
    "); + expect(bidObject1.width).to.equal(300); + expect(bidObject1.height).to.equal(250); + expect(bidObject1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bidObject1.bidderCode).to.equal('piximedia'); + + stubAddBidResponse.restore(); + stubGetUniqueIdentifierStr.restore(); + }); + + it('bidmanager.addBidResponse should be called once without any ads', function () { + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + var stubGetUniqueIdentifierStr = sinon.spy(utils, "getUniqueIdentifierStr"); + + var response = { + foundbypm: false + }; + + adapter().callBids(params); + + var adUnits = []; + var unit = {}; + unit.bids = [params]; + unit.code = '/20164912/header-bid-tag'; + unit.sizes=[[300,250],[728,90]]; + adUnits.push(unit); + + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = [params]; + } else { + pbjs._bidsRequested.push(params); + } + pbjs.adUnits = adUnits; + response.cbid = stubGetUniqueIdentifierStr.returnValues[0]; + + pbjs.handlePiximediaCallback(response); + + sinon.assert.calledOnce(stubAddBidResponse); + var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + + expect(bidPlacementCode1).to.equal('/20164912/header-bid-tag-0'); + expect(bidObject1.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(bidObject1.bidderCode).to.equal('piximedia'); + + stubAddBidResponse.restore(); + stubGetUniqueIdentifierStr.restore(); + }); + + it('bidmanager.addBidResponse should not be called on bogus cbid', function () { + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + var stubGetUniqueIdentifierStr = sinon.spy(utils, "getUniqueIdentifierStr"); + + var response = { + foundbypm: false + }; + + adapter().callBids(params); + + var adUnits = []; + var unit = {}; + unit.bids = [params]; + unit.code = '/20164912/header-bid-tag'; + unit.sizes=[[300,250],[728,90]]; + adUnits.push(unit); + + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = [params]; + } else { + pbjs._bidsRequested.push(params); + } + pbjs.adUnits = adUnits; + response.cbid = stubGetUniqueIdentifierStr.returnValues[0] + "_BOGUS"; + + pbjs.handlePiximediaCallback(response); + + sinon.assert.notCalled(stubAddBidResponse); + + stubAddBidResponse.restore(); + stubGetUniqueIdentifierStr.restore(); + }); + + it('bidmanager.addBidResponse should not be called on bogus response', function () { + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = null; // this is bogus: we expect an object + + adapter().callBids(params); + + var adUnits = []; + var unit = {}; + unit.bids = [params]; + unit.code = '/20164912/header-bid-tag'; + unit.sizes=[[300,250],[728,90]]; + adUnits.push(unit); + + if (typeof(pbjs._bidsRequested)==="undefined"){ + pbjs._bidsRequested = [params]; + } else { + pbjs._bidsRequested.push(params); + } + pbjs.adUnits = adUnits; + + pbjs.handlePiximediaCallback(response); + + sinon.assert.notCalled(stubAddBidResponse); + + stubAddBidResponse.restore(); + }); + }); +}); + diff --git a/test/spec/adapters/rubicon_spec.js b/test/spec/adapters/rubicon_spec.js new file mode 100644 index 00000000000..ddc40a8b5e7 --- /dev/null +++ b/test/spec/adapters/rubicon_spec.js @@ -0,0 +1,292 @@ +import { expect } from "chai"; +import adloader from "src/adloader"; +import adapterManager from "src/adaptermanager"; +import bidManager from "src/bidmanager"; + +var CONSTANTS = require("src/constants.json"); + + +describe("the rubicon adapter", () => { + + let rubiconAdapter = adapterManager.bidderRegistry["rubicon"], + sandbox, + adUnit; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + + adUnit = { + code: "/19968336/header-bid-tag-0", + sizes: [[300, 250], [320, 50]], + bids: [ + { + bidder: "rubicon", + params: { + accountId: "14062", + siteId: "70608", + zoneId: "335918", + userId: "12346", + keywords: ["a","b","c"], + inventory: { + rating:"5-star", + prodtype:"tech" + }, + visitor: { + ucat:"new", + lastsearch:"iphone" + }, + position: "atf" + } + } + ] + }; + + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe("callBids public interface", () => { + + it("should receive a well-formed bidRequest from the adaptermanager", () => { + + sandbox.stub(rubiconAdapter, "callBids"); + + adapterManager.callBids({ + adUnits: [clone(adUnit)] + }); + + let bidderRequest = rubiconAdapter.callBids.getCall(0).args[0]; + + expect(bidderRequest).to.have.property("bids") + .that.is.an("array") + .with.lengthOf(1); + + expect(bidderRequest).to.have.deep.property("bids[0]") + .to.have.property("bidder", "rubicon"); + + expect(bidderRequest).to.have.deep.property("bids[0]") + .to.have.property("placementCode", adUnit.code); + + expect(bidderRequest).to.have.deep.property("bids[0]") + .with.property("sizes") + .that.is.an("array") + .with.lengthOf(2) + .that.deep.equals(adUnit.sizes); + + expect(bidderRequest).to.have.deep.property("bids[0]") + .with.property('params') + .that.deep.equals(adUnit.bids[0].params) + + }); + + }); + + describe("callBids implementation", () => { + + let bidderRequest, + slot; + + beforeEach(() => { + sandbox.stub(adloader, "loadScript"); + sandbox.spy(rubiconAdapter, "callBids"); + + slot = { + clearTargeting: sandbox.spy(), + setPosition: sandbox.spy(), + addFPV: sandbox.spy(), + addFPI: sandbox.spy(), + addKW: sandbox.spy(), + getElementId: () => "/19968336/header-bid-tag-0", + getRawResponses: () => {} + }; + + window.rubicontag = { + cmd: { + push: cb => cb() + }, + setIntegration: sandbox.spy(), + run: () => {}, + setUserKey: sandbox.spy(), + defineSlot: sandbox.spy(bid => slot) + }; + + bidderRequest = { + bidderCode: "rubicon", + requestId: "c45dd708-a418-42ec-b8a7-b70a6c6fab0a", + bidderRequestId: "178e34bad3658f", + bids: [ + { + bidder: "rubicon", + params: { + accountId: "14062", + siteId: "70608", + zoneId: "335918", + userId: "12346", + keywords: ["a","b","c"], + inventory: { + rating:"5-star", + prodtype:"tech" + }, + visitor: { + ucat:"new", + lastsearch:"iphone" + }, + position: "atf" + }, + placementCode: "/19968336/header-bid-tag-0", + sizes: [[300, 250], [320, 50]], + bidId: "2ffb201a808da7", + bidderRequestId: "178e34bad3658f", + requestId: "c45dd708-a418-42ec-b8a7-b70a6c6fab0a" + } + ], + start: 1472239426002, + timeout: 5000 + }; + + }); + + describe("when doing fastlane slot configuration", () => { + + beforeEach(() => { + rubiconAdapter.callBids(bidderRequest); + }); + + it("should load the fastlane SDK if not loaded", () => { + + let pathToSDK = adloader.loadScript.getCall(0).args[0]; + expect(pathToSDK).to.equal(`http://ads.rubiconproject.com/header/${bidderRequest.bids[0].params.accountId}.js`); + + rubiconAdapter.callBids(bidderRequest); + expect(adloader.loadScript.calledOnce).to.equal(true); + + }); + + it("should make a valid call to rubicontag.defineSlot", () => { + + expect(window.rubicontag.defineSlot.calledOnce).to.equal(true); + + let slotParam = window.rubicontag.defineSlot.firstCall.args[0]; + expect(slotParam).to.contain.all.keys( + "siteId", + "zoneId", + "sizes", + "id" + ); + expect(slotParam).to.have.property("sizes") + .that.is.an("array") + .with.lengthOf(2) + .that.deep.equals([15, 43]); + + }); + + it("should call rubicontag.setUserKey when params.userId is set", () => { + + expect(window.rubicontag.setUserKey.calledWith(adUnit.bids[0].params.userId)).to.equal(true); + + }); + + it("should set proper targeting params for Slot when passed", () => { + + expect(slot.setPosition.calledOnce).to.equal(true); + expect(slot.setPosition.firstCall.calledWith("atf")).to.equal(true); + + expect(slot.addFPV.calledTwice).to.equal(true); + expect(slot.addFPV.firstCall.calledWith("ucat", "new")).to.equal(true); + expect(slot.addFPV.secondCall.calledWith("lastsearch", "iphone")).to.equal(true); + + expect(slot.addFPI.calledTwice).to.equal(true); + expect(slot.addFPI.firstCall.calledWith("rating", "5-star")).to.equal(true); + expect(slot.addFPI.secondCall.calledWith("prodtype", "tech")).to.equal(true); + + expect(slot.addKW.calledOnce).to.equal(true); + expect(slot.addKW.firstCall.calledWith(["a","b","c"])).to.equal(true); + + }); + + it("should set the rubicontag integration as prebid.js", () => { + + expect(window.rubicontag.setIntegration.calledWith("$$PREBID_GLOBAL$$")).to.equal(true); + + }); + + }); + + describe("when handling fastlane responses", () => { + + let bids; + + beforeEach(() => { + bids = []; + + sinon.stub(window.rubicontag, "run", cb => cb()); + sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { + bids.push(bid); + }); + }); + + it("should register successful bids with the bidmanager", () => { + + sandbox.stub(slot, "getRawResponses", () => [ + { + "advertiser": 12345, + "cpm": 0.811, + "dimensions": [ + 300, + 250 + ], + "auction_id": "431ee1bc-3cc4-4bb7-b0d4-eb9faedb433c" + }, + { + "advertiser": 123456, + "cpm": 0.59, + "dimensions": [ + 320, + 50 + ], + "auction_id": "a3e042e5-3fb7-498f-b60e-71540f4769a8" + } + ]); + + rubiconAdapter.callBids(bidderRequest); + + expect(bidManager.addBidResponse.calledTwice).to.equal(true); + + expect(bids).to.be.lengthOf(2); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bids[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + + expect(bids[0].bidderCode).to.equal("rubicon"); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].cpm).to.equal(0.811); + + expect(bids[1].bidderCode).to.equal("rubicon"); + expect(bids[1].width).to.equal(320); + expect(bids[1].height).to.equal(50); + expect(bids[1].cpm).to.equal(0.59); + + }); + + it("should register bad responses as errors with the bidmanager", () => { + + sandbox.stub(slot, "getRawResponses", () => []); + + rubiconAdapter.callBids(bidderRequest); + + expect(bidManager.addBidResponse.calledOnce).to.equal(true); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + + }); + + }); + + }); + +}); + +function clone(obj) { + return JSON.parse(JSON.stringify(obj)); +} \ No newline at end of file diff --git a/test/spec/adapters/sonobi_spec.js b/test/spec/adapters/sonobi_spec.js new file mode 100644 index 00000000000..96bfabfb55c --- /dev/null +++ b/test/spec/adapters/sonobi_spec.js @@ -0,0 +1,353 @@ +const chai = require('chai'); +const expect = require('chai').expect; +const Adapter = require('src/adapters/sonobi'); +const bidManager = require('src/bidmanager'); +const adLoader = require('src/adloader'); +const utils = require('src/utils'); + +chai.config.includeStack = true; + +describe('Sonobi adapter tests', () => { + // Declared each explicitely so we can loop through and observe each test + const adUnit_p = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_p', + sizes: [[300, 250],[300,600]], + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d' + } + }] + }; + const adUnit_pd = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_pd', + sizes: [[300, 250],[300,600]], + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d', + dom_id: 'div-gpt-ad-12345-0' + } + }] + }; + const adUnit_pdf = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_pdf', + sizes: [[300, 250],[300,600]], + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d', + dom_id: 'div-gpt-ad-12345-0', + floor: '1' + } + }] + }; + const adUnit_a = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_a', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '/7780971/sparks_prebid_MR', + } + }] + }; + const adUnit_ad = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_ad', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '/7780971/sparks_prebid_MR', + dom_id: 'div-gpt-ad-12345-0' + } + }] + }; + const adUnit_af = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_af', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '/7780971/sparks_prebid_MR', + floor: '1' + } + }] + }; + const adUnit_adf = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_adf', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '/7780971/sparks_prebid_MR', + dom_id: 'div-gpt-ad-12345-0', + floor: '1' + } + }] + }; + const adUnit_A = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_A', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '/7780971/sparks_prebid_MR', + } + }] + }; + const adUnit_Ad = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_Ad', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '7780971/sparks_prebid_MR', + dom_id: 'div-gpt-ad-12345-0' + } + }] + }; + const adUnit_Af = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_Af', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '7780971/sparks_prebid_MR', + floor: '1' + } + }] + }; + const adUnit_Adf = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_Adf', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '7780971/sparks_prebid_MR', + dom_id: 'div-gpt-ad-12345-0', + floor: '1' + } + }] + }; + // You guys surprise me all the time new and exciting ways to break this simple adapter. + const adUnit_m1hb = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_m1hb', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '1a2b3c4d5e6f1a2b3c4d', + dom_id: 'div-gpt-ad-12345-0' + } + }] + }; + const adUnit_m2hb = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_m2hb', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '/7780971/sparks_prebid_MR', + placement_id: 'OPTIONAL', + dom_id: 'div-gpt-ad-12345-0', + } + }] + }; + const adUnit_m3hb = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_m3hb', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '/7780971/sparks_prebid_MR', + placement_id: '', + dom_id: 'div-gpt-ad-12345-0', + } + }] + }; + const adUnit_m4hb = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_m4hb', + sizes: [[300, 250],[300,600]], + params: { + ad_unit: '', + placement_id: '1a2b3c4d5e6f1a2b3c4d', + dom_id: 'div-gpt-ad-12345-0' + } + }] + }; + const adUnit_m5hb = { + bidderCode: 'sonobi', + bids: [{ + bidId: 'testbid', + bidder: 'sonobi', + placementCode: 'adUnit_m5hb', + sizes: [[300, 250],[300,600]], + params: { + placement_id: '/7780971/sparks_prebid_MR', + dom_id: 'div-gpt-ad-12345-0' + } + }] + }; + // FTFY + const sbi_adUnits = { + 'adUnit_p' : adUnit_p, + 'adUnit_pd' : adUnit_pd, + 'adUnit_pdf' : adUnit_pdf, + 'adUnit_a' : adUnit_a, + 'adUnit_ad' : adUnit_ad, + 'adUnit_af' : adUnit_af, + 'adUnit_adf' : adUnit_adf, + 'adUnit_A' : adUnit_A, + 'adUnit_Ad' : adUnit_Ad, + 'adUnit_Af' : adUnit_Af, + 'adUnit_Adf' : adUnit_Adf, + 'adUnit_m1hb' : adUnit_m1hb, + 'adUnit_m2hb' : adUnit_m2hb, + 'adUnit_m3hb' : adUnit_m3hb, + 'adUnit_m4hb' : adUnit_m4hb, + 'adUnit_m5hb' : adUnit_m5hb + }; + + // Run the same test against all the (now tons of) different configurations + utils._each(sbi_adUnits, (adUnit, adUnitName) => { + describe('should form valid bid requests', () => { + + let adapter = new Adapter(); + let stubLoadScript; + let stubFailBid; + let stubGoodBid; + + beforeEach(() => { + stubLoadScript = sinon.stub(adLoader, 'loadScript'); + stubFailBid = sinon.stub(adapter, 'failure'); + stubGoodBid = sinon.stub(adapter, 'success'); + }); + + afterEach(() => { + stubLoadScript.restore(); + stubFailBid.restore(); + stubGoodBid.restore(); + }); + + it('should make trinity key:vals for: ' + adUnitName, () => { + let keymakerBid = adapter.formRequest(adUnit.bids); + // Key matches one of two patterns and chai doesn't have an 'or' clause. + expect(Object.keys(keymakerBid)[0]).to.exist; + expect(Object.keys(keymakerBid)[0]).to.not.be.empty; + expect(keymakerBid[Object.keys(keymakerBid)[0]]).to.exist; + expect(keymakerBid[Object.keys(keymakerBid)[0]]).to.not.be.empty; + // Just having a key and val is sufficient for bidder to attempt to work with it. + }); + + it('should attempt to call bidder for: ' + adUnitName, () => { + adapter.callBids(adUnit); + expect(stubLoadScript.callCount).to.equal(1); + expect(stubFailBid.callCount).to.equal(0); + }); + + }); + + }); + + describe('should parse bid returns and register bid objects', () => { + + let adapter = new Adapter(); + let spyAddBidResponse; + + const sbi_bid = { + "slots": + { + "sbi_a": + { + "sbi_size": "300x250", + "sbi_apoc": "premium", + "sbi_aid": "159.60.7533347", + "sbi_mouse": 4.2 + } + }, + "sbi_dc": "iad-2-" + }; + + const sbi_deal_bid = { + "slots": + { + "sbi_a": + { + "sbi_size": "300x250", + "sbi_apoc": "premium", + "sbi_aid": "159.60.7533347", + "sbi_mouse": 4.2, + "sbi_dozer": "apex-test-deal" + } + }, + "sbi_dc": "iad-2-" + }; + + const sbi_noBid = { + "slots": + { + "sbi_a": {} + }, + "sbi_dc": "iad-2-" + }; + + beforeEach(() => { + spyAddBidResponse = sinon.spy(bidManager, "addBidResponse"); + }); + + afterEach(() => { + spyAddBidResponse.restore(); + }); + + it('should create bid object for good bid return', () => { + adapter.parseResponse(sbi_bid); + expect(spyAddBidResponse.called).to.be.true; + }); + + it('should create bid object for deal bid return', () => { + adapter.parseResponse(sbi_deal_bid); + expect(spyAddBidResponse.called).to.be.true; + }); + + it('should create fail bid object for empty return', () => { + adapter.parseResponse(sbi_noBid); + expect(spyAddBidResponse.called).to.be.true; + }); + + }); + + +}); \ No newline at end of file diff --git a/test/spec/adapters/triplelift_spec.js b/test/spec/adapters/triplelift_spec.js index 4b3a14b9bbf..6e2b748c7d6 100644 --- a/test/spec/adapters/triplelift_spec.js +++ b/test/spec/adapters/triplelift_spec.js @@ -144,9 +144,9 @@ describe('triplelift adapter', () => { expect(secondPlacementCode).to.eql('bar'); }); - it('should include the adId on the bid object', () => { - expect(firstBid).to.have.property('adId'); - expect(secondBid).to.have.property('adId'); + it('should include the bid request bidId as the adId', () => { + expect(firstBid).to.have.property('adId', 'bidId1'); + expect(secondBid).to.have.property('adId', 'bidId2'); }); it('should have a good statusCode', () => { @@ -207,9 +207,9 @@ describe('triplelift adapter', () => { sinon.assert.calledTwice(bidManager.addBidResponse); }); - it('should include the adId on the bid object', () => { - expect(firstBid).to.have.property('adId'); - expect(secondBid).to.have.property('adId'); + it('should include the bid request bidId as the adId', () => { + expect(firstBid).to.have.property('adId', 'bidId1'); + expect(secondBid).to.have.property('adId', 'bidId2'); }); it('should have an error statusCode', () => { diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js index 5a4ba473476..beeef9440f5 100644 --- a/test/spec/bidmanager_spec.js +++ b/test/spec/bidmanager_spec.js @@ -348,6 +348,70 @@ describe('bidmanager.js', function () { }); + it('suppressEmptyKeys=true' , function() { + $$PREBID_GLOBAL$$.bidderSettings = + { + standard: { + suppressEmptyKeys: true, + adserverTargeting: [ + { + key: "aKeyWithAValue", + val: 42 + }, + { + key: "aKeyWithAnEmptyValue", + val: "" + } + ] + } + }; + + var expected = { + "aKeyWithAValue": 42 + }; + + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('sendStandardTargeting=false and inherit custom', function () { + $$PREBID_GLOBAL$$.bidderSettings = + { + appnexus: { + alwaysUseBid: true, + sendStandardTargeting: false, + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + return bidResponse.pbHg; + } + }, { + key: "custom", + val: 42 + } + ] + } + }; + + var expected = { + "custom": 42 + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + }); describe('adjustBids', () => { diff --git a/test/spec/e2e/common/globals.js b/test/spec/e2e/common/globals.js new file mode 100644 index 00000000000..153670f15f7 --- /dev/null +++ b/test/spec/e2e/common/globals.js @@ -0,0 +1,9 @@ +var HtmlReporter = require('nightwatch-html-reporter'); +var reporter = new HtmlReporter({ + openBrowser: true, + reportsDirectory: __dirname + '/reports', + themeName: 'cover', +}); +module.exports = { + reporter: reporter.fn +}; diff --git a/test/spec/e2e/common/utils.js b/test/spec/e2e/common/utils.js new file mode 100644 index 00000000000..c745610f564 --- /dev/null +++ b/test/spec/e2e/common/utils.js @@ -0,0 +1,16 @@ +module.exports = { + findIframeInDiv : function(divid) { + var div = document.getElementById(divid); + var iframes = div.getElementsByTagName('iframe'); + console.log(iframes.length); + try { + if(iframes.length === 1 && iframes[0].contentWindow.document.body.innerHTML === "") { + return false; + } else { + return true; + } + } catch (e) { + return true; + } + } +}; diff --git a/test/spec/e2e/custom-assertions/first.js b/test/spec/e2e/custom-assertions/first.js new file mode 100644 index 00000000000..296dc86d352 --- /dev/null +++ b/test/spec/e2e/custom-assertions/first.js @@ -0,0 +1,65 @@ +/** + * Checks if the given attribute of an element has the expected value. + * + * ``` + * this.demoTest = function (client) { + * browser.assert.attributeEquals('body', 'data-attr', 'some value'); + * }; + * ``` + * + * @method attributeEquals + * @param {string} selector The selector (CSS / Xpath) used to locate the element. + * @param {string} attribute The attribute name + * @param {string} expected The expected value of the attribute to check. + * @param {string} [message] Optional log message to display in the output. If missing, one is displayed by default. + * @api assertions + */ + +var util = require('util'); +exports.assertion = function(expected, msg) { + var DEFAULT_MSG = 'Testing if attribute %s of <%s> equals "%s".'; + + this.message = msg; + + this.expected = function() { + return expected; + }; + + this.pass = function(value) { + return value === expected; + }; + + this.failure = function(result) { + var failed = false; + return failed; + }; + + this.value = function(result) { + console.log('**********'); + console.log(result); + return result.value; + }; + + this.command = function(callback) { + var _this = this; + var execcallback = function(result) { + //console.log(_this); + console.log('**********'); + console.log(result); + console.log(callback.toString()); + if (callback) { + return callback.call(_this, result.value); + } + }; + + this.api.execute(function(){ + //cusotm logic + return 'hello'; + }, [], execcallback); + + //var result = {'value':'hello'}; + + return this; + }; + +}; diff --git a/test/spec/e2e/custom-reporter/junit.xml.ejs b/test/spec/e2e/custom-reporter/junit.xml.ejs new file mode 100644 index 00000000000..f03e0ee38e5 --- /dev/null +++ b/test/spec/e2e/custom-reporter/junit.xml.ejs @@ -0,0 +1,32 @@ + + + + + <% for (var item in module.completed) { + var testcase = module.completed[item]; + var assertions = testcase.assertions %> + <% + for (var i = 0; i < assertions.length; i++) { %><% if (assertions[i].failure) { %> <%= assertions[i].stackTrace %><% } %> +<% if (assertions[i].screenshots && assertions[i].screenshots.length > 0) { %><% for (var j = 0; j < assertions[i].screenshots.length; j++) { %>[[ATTACHMENT|<%= assertions[i].screenshots[j] %>]]<% } %><% } %> + <% } + if (testcase.failed > 0 && testcase.stackTrace) { + %><%= testcase.stackTrace %><% } %> + <% if (systemerr != '') {%> + + <%= systemerr %> + <% } %> + <% } %> + <% if (module.skipped && (module.skipped.length > 0)) { %> + <% for (var j = 0; j < module.skipped.length; j++) { %> + + + + <% } %> + <% } %> + + diff --git a/test/spec/e2e/custom-reporter/pbjs-html-reporter.js b/test/spec/e2e/custom-reporter/pbjs-html-reporter.js new file mode 100644 index 00000000000..b5cb55b3ecc --- /dev/null +++ b/test/spec/e2e/custom-reporter/pbjs-html-reporter.js @@ -0,0 +1,116 @@ +var fs = require('fs'); +var mkpath = require('mkpath'); +var path = require('path'); +var ejs = require('ejs'); + +module.exports = new (function() { + + var tmpl = __dirname + '/junit.xml.ejs'; + var tmplData; + var globalResults; + + function loadTemplate(cb) { + if (tmplData) { + cb(tmplData); + return; + } + fs.readFile(tmpl, function (err, data) { + if (err) { + throw err; + } + tmplData = data.toString(); + cb(tmplData); + }); + } + + function adaptAssertions(module) { + Object.keys(module.completed).forEach(function(item) { + var testcase = module.completed[item]; + var assertions = testcase.assertions; + for (var i = 0; i < assertions.length; i++) { + if (assertions[i].stackTrace) { + assertions[i].stackTrace = stackTraceFilter(assertions[i].stackTrace.split('\n')); + } + } + + if (testcase.failed > 0 && testcase.stackTrace) { + var stackParts = testcase.stackTrace.split('\n'); + var errorMessage = stackParts.shift(); + testcase.stackTrace = stackTraceFilter(stackParts); + testcase.message = errorMessage; + } + }); + } + + function writeReport(moduleKey, data, opts, callback) { + var module = globalResults.modules[moduleKey]; + var pathParts = moduleKey.split(path.sep); + var moduleName = pathParts.pop(); + var output_folder = opts.output_folder; + adaptAssertions(module); + + var rendered = ejs.render(data, { + module : module, + moduleName : moduleName, + systemerr : globalResults.errmessages.join('\n'), + }); + + if (pathParts.length) { + output_folder = path.join(output_folder, pathParts.join(path.sep)); + mkpath.sync(output_folder); + } + + var filename = path.join(output_folder, opts.filename_prefix + moduleName + '.xml'); + fs.writeFile(filename, rendered, function(err) { + callback(err); + globalResults.errmessages.length = 0; + }); + } + + function stackTraceFilter(parts) { + var stack = parts.reduce(function(list, line) { + if (contains(line, [ + 'node_modules', + '(node.js:', + '(events.js:' + ])) { + return list; + } + + list.push(line); + return list; + }, []); + + return stack.join('\n'); + } + + function contains(str, text) { + if( Object.prototype.toString.call( text ) === '[object Array]' ) { + for (var i = 0; i < text.length; i++) { + if (contains(str, text[i])) { + return true; + } + } + } + return str.indexOf(text) > -1; + } + + this.write = function(results, options, callback) { + options.filename_prefix = process.env.__NIGHTWATCH_ENV+'_'; + globalResults = results; + var keys = Object.keys(results.modules); + + loadTemplate(function createReport(data) { + var moduleKey = keys.shift(); + + writeReport(moduleKey, data, options, function(err) { + if (err || (keys.length === 0)) { + callback(err); + } else { + createReport(data); + } + }); + }); + }; + +})(); diff --git a/test/spec/e2e/gpt-examples/all_bidders_instant_load.html b/test/spec/e2e/gpt-examples/all_bidders_instant_load.html new file mode 100644 index 00000000000..14e65023937 --- /dev/null +++ b/test/spec/e2e/gpt-examples/all_bidders_instant_load.html @@ -0,0 +1,830 @@ + + + + + + + + + +

    Prebid.js Test3

    + +

    adequant

    +
    +

    No response

    + +
    + +

    adform

    +
    +

    No response

    + +
    + + +

    aol

    +
    +

    No response

    + +
    + +

    appnexus

    +
    +

    No response

    + +
    + +

    indexExchange

    +
    +

    No response

    + +
    + +

    openx

    +
    +

    No response

    + +
    + +

    pubmatic

    +
    +

    No response

    + +
    + +

    pulsepoint

    +
    +

    No response

    + +
    + +

    rubicon

    +
    +

    No response

    + +
    + +

    sonobi

    +
    +

    No response

    + +
    + +

    sovrn

    +
    +

    No response

    + +
    + +

    springserve

    +
    +

    No response

    + +
    + +

    triplelift

    +
    +

    No response

    + +
    + +

    yieldbot

    +
    +

    No response

    + +
    + +

    nginad

    +
    +

    No response

    + +
    + +

    brightcom

    +
    +

    No response

    + +
    + +

    sekindo

    +
    +

    No response

    + +
    + +

    kruxlink

    +
    +

    No response

    + +
    + +

    AdMedia

    +
    +

    No response

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

    Prebid.js TestCase1

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

    Prebid.js Test3

    + + +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + +
    +
    + +
    +
    + +
    + + + +
    + +
    + + + + + + + + + diff --git a/test/spec/e2e/testcase1/dom-group/allbidders_dom_spec.js b/test/spec/e2e/testcase1/dom-group/allbidders_dom_spec.js new file mode 100644 index 00000000000..52a3045e5c6 --- /dev/null +++ b/test/spec/e2e/testcase1/dom-group/allbidders_dom_spec.js @@ -0,0 +1,125 @@ +//var verify = require('verify'); +var util = require('../../common/utils.js'); + +module.exports = { + 'adequant ad rendering' : function (browser) { + browser + .url('http://localhost:9999/test/spec/e2e/gpt-examples/all_bidders_instant_load.html') + .waitForElementVisible('body', 5000) + .pause(10000) + .execute(util.findIframeInDiv, ['div-1'], function(result) { + this.verify.equal(result.value, true, 'adequant ad not rendered'); + }); + }, + 'adform ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-2'], function(result) { + this.verify.equal(result.value, true, 'adform ad not rendered'); + }); + }, + 'aol ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-3'], function(result) { + this.verify.equal(result.value, true, 'aol ad not rendered'); + }); + }, + 'appnexus ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-4'], function(result) { + this.verify.equal(result.value, true, 'appnexus ad not rendered'); + }); + }, + 'indexExchange ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-5'], function(result) { + this.verify.equal(result.value, true, 'indexExchange ad not rendered'); + }); + }, + 'openx ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-6'], function(result) { + this.verify.equal(result.value, true, 'openx ad not rendered'); + }); + }, + 'pubmatic ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-7'], function(result) { + this.verify.equal(result.value, true, 'pubmatic ad not rendered'); + }); + }, + 'pulsepoint ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-8'], function(result) { + this.verify.equal(result.value, true, 'pulsepoint ad not rendered'); + }); + }, + 'rubicon ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-9'], function(result) { + this.verify.equal(result.value, true, 'rubicon ad not rendered'); + }); + }, + 'sonobi ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-10'], function(result) { + this.verify.equal(result.value, true, 'sonobi ad not rendered'); + }); + }, + 'sovrn ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-11'], function(result) { + this.verify.equal(result.value, true, 'sovrn ad not rendered'); + }); + }, + 'springserve ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-12'], function(result) { + this.verify.equal(result.value, true, 'springserve ad not rendered'); + }); + }, + 'triplelift ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-13'], function(result) { + this.verify.equal(result.value, true, 'triplelift ad not rendered'); + }); + }, + 'yieldbot ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-14'], function(result) { + this.verify.equal(result.value, true, 'yieldbot ad not rendered'); + }); + }, + 'nginad ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-15'], function(result) { + this.verify.equal(result.value, true, 'nginad ad not rendered'); + }); + }, + 'brightcom ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-16'], function(result) { + this.verify.equal(result.value, true, 'brightcom ad not rendered'); + }); + }, + 'sekindo ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-17'], function(result) { + this.verify.equal(result.value, true, 'sekindo ad not rendered'); + }); + }, + 'kruxlink ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-18'], function(result) { + this.verify.equal(result.value, true, 'kruxlink ad not rendered'); + }); + }, + 'AdMedia ad rendering' : function (browser) { + browser + .execute(util.findIframeInDiv, ['div-19'], function(result) { + this.verify.equal(result.value, true, 'AdMedia ad not rendered'); + }); + }, + after : function(browser) { + browser.end(); + } +}; diff --git a/test/spec/e2e/testcase1/dom-group/dom_spec.js b/test/spec/e2e/testcase1/dom-group/dom_spec.js new file mode 100644 index 00000000000..54ff7f05eac --- /dev/null +++ b/test/spec/e2e/testcase1/dom-group/dom_spec.js @@ -0,0 +1,53 @@ +//var assert = require('assert'); + +module.exports = { + + 'Test rendering ad div-2' : function (browser) { + + var checkAdRendering2 = function() { + var div = document.getElementById('div-2'); + var iframes = div.getElementsByTagName('iframe'); + try { + if(iframes.length == 1 && iframes[0].contentWindow.document.body.innerHTML == "") { + return false; + } else { + return true; + } + } catch (e) { + return true; + } + } + + browser + .url('http://localhost:9999/test/spec/e2e/gpt-examples/e2e_default.html') + .waitForElementVisible('body', 3000) + .pause(5000) + .execute(checkAdRendering2, [], function(result) { + this.assert.equal(result.value, true, 'Ad of div-2 not rendered'); + }); + }, + 'Test rendering ad div-1' : function (browser) { + + var checkAdRendering = function() { + var div = document.getElementById('div-1'); + var iframes = div.getElementsByTagName('iframe'); + try { + if(iframes.length == 1 && iframes[0].contentWindow.document.body.innerHTML == "") { + return false; + } else { + return true; + } + } catch (e) { + return true; + } + } + + browser + .execute(checkAdRendering, [], function(result) { + this.assert.equal(result.value, true, 'Ad of div-1 not rendered'); + }); + }, + after : function(browser) { + browser.end(); + } +}; diff --git a/test/spec/e2e/testcase1/pbjsapi-group/adservertargeting_spec.js b/test/spec/e2e/testcase1/pbjsapi-group/adservertargeting_spec.js new file mode 100644 index 00000000000..f71838d0654 --- /dev/null +++ b/test/spec/e2e/testcase1/pbjsapi-group/adservertargeting_spec.js @@ -0,0 +1,63 @@ +var assert = require('assert'); +var utils = require('util'); + +module.exports = { + 'AdserverTargeting Test Case 1' : function (browser) { + browser + .url('http://localhost:9999/test/spec/e2e/gpt-examples/gpt_default.html') + .waitForElementVisible('body', 3000) + .pause(5000) + .execute(function(){ + + if(typeof window.pbjs.bidderSettings == "undefined") { + var pbjsBidderSettingsObject = [ + "hb_bidder", + "hb_adid", + "hb_pb", + "hb_size" + ]; + } else { + var pbjsBidderSettings = window.pbjs.bidderSettings; + var pbjsBidderSettingsObject = {}; + Object.keys(pbjsBidderSettings).forEach(function (prop) { + //if(prop == 'standard') return; + var value = pbjsBidderSettings[prop]; + var bs = value.adserverTargeting.map(function(item){ + return item.key; + }); + pbjsBidderSettings.standard.adserverTargeting.map(function(value){ + if(bs.indexOf(value.key) == -1 ) { + bs.push(value.key) + } + }); + pbjsBidderSettingsObject[prop] = bs; + }); + } + + var adserverTargetingObject = {}; + var adserverTargeting = window.pbjs.getAdserverTargeting(); + Object.keys(adserverTargeting).forEach(function(value){ + if(Object.keys(adserverTargeting[value]).length == 0) return; + adserverTargetingObject[adserverTargeting[value].hb_bidder] = Object.keys(adserverTargeting[value]) + }); + + return [pbjsBidderSettingsObject, adserverTargetingObject]; + }, [], function(result) { + Object.keys(result.value[1]).forEach(function(key) { + if(utils.isArray(result.value[0])) { + assert.deepEqual(result.value[0].sort(), result.value[1][key].sort()); + } else { + if(result.value[0].hasOwnProperty(key)) { + var obj1 = result.value[0][key].sort(); + } else { + var obj1 = result.value[0]['standard'].sort(); + } + assert.deepEqual(obj1, result.value[1][key].sort()); + } + }); + }); + }, + after : function(browser) { + browser.end(); + } +}; diff --git a/test/spec/e2e/testcase1/pbjsapi-group/getbidresponses_spec.js b/test/spec/e2e/testcase1/pbjsapi-group/getbidresponses_spec.js new file mode 100644 index 00000000000..ed260f7f33f --- /dev/null +++ b/test/spec/e2e/testcase1/pbjsapi-group/getbidresponses_spec.js @@ -0,0 +1,34 @@ +//var assert = require('assert'); +var assert = require('chai').assert; +var utils = require('util'); + +module.exports = { + 'bidReceived not empty' : function(browser) { + browser + .url('http://localhost:9999/test/spec/e2e/gpt-examples/gpt_default.html') + .waitForElementVisible('body', 3000) + .pause(5000) + .execute(function() { + return window.pbjs._bidsReceived.length; + }, [], function(result) { + //browser.assert.first(false, 'Bid response empty'); + assert.isOk(result.value, 'Bid response empty'); + }); + }, + 'check keys' : function(browser) { + browser + .execute(function() { + return window.pbjs._bidsReceived; + }, [], function(result) { + //minimum expected keys in bid received + var expected = ["bidderCode", "width", "height", "adId", "cpm", "requestId", "bidder", "adUnitCode", "timeToRespond"]; + Object.keys(result.value).forEach(function(key){ + var compare = Object.keys(result.value[key]); + assert.includeMembers(compare, expected, 'include members'); + }); + }); + }, + after : function(browser) { + browser.end(); + } +}; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 62fb4fd64d0..62eb1bcdaf7 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -16,6 +16,7 @@ var bidmanager = require('src/bidmanager'); var adloader = require('src/adloader'); var adaptermanager = require('src/adaptermanager'); var events = require('src/events'); +var adserver = require('src/adserver'); var CONSTANTS = require('src/constants.json'); var config = require('test/fixtures/config.json'); @@ -442,6 +443,19 @@ describe('Unit: Prebid Module', function () { assert.ok(spyLogError.calledWith(error), 'expected error was logged'); }); + it('should log an error when doc is document', () => { + $$PREBID_GLOBAL$$.renderAd(document, bidId); + const error = 'Error trying to write ad. Ad render call ad id ' + bidId + ' was prevented from writing to the main document.'; + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); + + it('should not render videos', () => { + adResponse.mediatype = 'video'; + $$PREBID_GLOBAL$$.renderAd(doc, bidId); + sinon.assert.notCalled(doc.write); + delete adResponse.mediatype; + }); + it('should catch errors thrown when trying to write ads to the page', function () { adResponse.ad = ""; @@ -592,6 +606,47 @@ describe('Unit: Prebid Module', function () { resetAuction(); }); + it('should not callBids if a video adUnit has non-video bidders', () => { + sinon.spy(adaptermanager, 'callBids'); + const videoAdaptersBackup = adaptermanager.videoAdapters; + adaptermanager.videoAdapters = ['appnexusAst']; + const adUnits = [{ + code: 'adUnit-code', + mediaType: 'video', + bids: [ + {bidder: 'appnexus', params: {placementId: 'id'}}, + {bidder: 'appnexusAst', params: {placementId: 'id'}} + ] + }]; + + $$PREBID_GLOBAL$$.requestBids({adUnits}); + sinon.assert.notCalled(adaptermanager.callBids); + + adaptermanager.callBids.restore(); + adaptermanager.videoAdapters = videoAdaptersBackup; + resetAuction(); + }); + + it('should callBids if a video adUnit has all video bidders', () => { + sinon.spy(adaptermanager, 'callBids'); + const videoAdaptersBackup = adaptermanager.videoAdapters; + adaptermanager.videoAdapters = ['appnexusAst']; + const adUnits = [{ + code: 'adUnit-code', + mediaType: 'video', + bids: [ + {bidder: 'appnexusAst', params: {placementId: 'id'}} + ] + }]; + + $$PREBID_GLOBAL$$.requestBids({adUnits}); + sinon.assert.calledOnce(adaptermanager.callBids); + + adaptermanager.callBids.restore(); + adaptermanager.videoAdapters = videoAdaptersBackup; + resetAuction(); + }); + it('should queue bid requests when a previous bid request is in process', () => { var spyCallBids = sinon.spy(adaptermanager, 'callBids'); var clock = sinon.useFakeTimers(); @@ -1007,4 +1062,132 @@ describe('Unit: Prebid Module', function () { }); }); + describe('getDealTargeting', () => { + + beforeEach(() => { + resetAuction(); + }); + + afterEach(() => { + resetAuction(); + }); + + it('should truncate deal keys', () => { + $$PREBID_GLOBAL$$._bidsReceived = [ + { + "bidderCode": "appnexusDummyName", + "dealId" : "1234", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "233bcbee889d46d", + "creative_id": 29681110, + "cpm": 10, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QL8BKh8AgAAAwDWAAUBCMjAybkFEMLLiJWTu9PsVxjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4190DgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBskgLZASFmU21rZ0FpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCd0EzZ0RnQUVEaUFFRGtBRUJtQUVCb0FFQnFBRURzQUVBdVFFQUFBQUFBQURnUDhFQgkMTEFBNERfSkFRMkxMcEVUMU93XzJRFSggd1AtQUJBUFVCBSxASmdDaW9EVTJnV2dBZ0MxQWcBFgRDOQkIqERBQWdQSUFnUFFBZ1BZQWdQZ0FnRG9BZ0Q0QWdDQUF3RS6aAiUhV1FrbmI63AAcd2VBbklBUW8JXPCVVS7YAugH4ALH0wHqAh9odHRwOi8vcHJlYmlkLm9yZzo5OTk5L2dwdC5odG1sgAMAiAMBkAMAmAMFoAMBqgMAsAMAuAMAwAOsAsgDANgDAOADAOgDAPgDA4AEAJIEBC9qcHSYBACiBAoxMC4xLjEzLjM3qAQAsgQICAAQABgAIAC4BADABADIBADSBAoxMC4wLjg1Ljkx&s=1bf15e8cdc7c0c8c119614c6386ab1496560da39&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239340, + "requestTimestamp": 1462919238919, + "bidder": "appnexus", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 421, + "pbLg": "5.00", + "pbMg": "10.00", + "pbHg": "10.00", + "pbAg": "10.00", + "size": "300x250", + "alwaysUseBid": true, + "requestId": 123456, + "adserverTargeting": { + "hb_bidder": "appnexus", + "hb_adid": "233bcbee889d46d", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250", + "hb_deal_appnexusDummyName": "1234" + } + } + ]; + + var result = $$PREBID_GLOBAL$$.getAdserverTargeting(); + Object.keys(result['/19968336/header-bid-tag-0']).forEach(value => { + expect(value).to.have.length.of.at.most(20); + }); + }); + }); + + describe('video adserverTag', () => { + + var adserverTag = 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/19968336/header-bid-tag-0&impl=s&gdfp_req=1&env=vp&output=xml_vast2&unviewed_position_start=1&url=www.test.com'; + + var options = { + 'adserver': 'dfp', + 'code': '/19968336/header-bid-tag-0' + }; + + beforeEach(() => { + resetAuction(); + $$PREBID_GLOBAL$$._bidsReceived = [ + { + "bidderCode": "appnexusAst", + "dealId" : "1234", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "233bcbee889d46d", + "creative_id": 29681110, + "cpm": 10, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QL8BKh8AgAAAwDWAAUBCMjAybkFEMLLiJWTu9PsVxjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4190DgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBskgLZASFmU21rZ0FpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCd0EzZ0RnQUVEaUFFRGtBRUJtQUVCb0FFQnFBRURzQUVBdVFFQUFBQUFBQURnUDhFQgkMTEFBNERfSkFRMkxMcEVUMU93XzJRFSggd1AtQUJBUFVCBSxASmdDaW9EVTJnV2dBZ0MxQWcBFgRDOQkIqERBQWdQSUFnUFFBZ1BZQWdQZ0FnRG9BZ0Q0QWdDQUF3RS6aAiUhV1FrbmI63AAcd2VBbklBUW8JXPCVVS7YAugH4ALH0wHqAh9odHRwOi8vcHJlYmlkLm9yZzo5OTk5L2dwdC5odG1sgAMAiAMBkAMAmAMFoAMBqgMAsAMAuAMAwAOsAsgDANgDAOADAOgDAPgDA4AEAJIEBC9qcHSYBACiBAoxMC4xLjEzLjM3qAQAsgQICAAQABgAIAC4BADABADIBADSBAoxMC4wLjg1Ljkx&s=1bf15e8cdc7c0c8c119614c6386ab1496560da39&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239340, + "requestTimestamp": 1462919238919, + "bidder": "appnexus", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 421, + "pbLg": "5.00", + "pbMg": "10.00", + "pbHg": "10.00", + "pbAg": "10.00", + "size": "300x250", + "alwaysUseBid": true, + "requestId": 123456, + "adserverTargeting": { + "hb_bidder": "appnexus", + "hb_adid": "233bcbee889d46d", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250", + "hb_deal_appnexusAst": "1234" + } + } + ]; + }); + + afterEach(() => { + resetAuction(); + }); + + it('should log error when adserver is not dfp', () => { + var logErrorSpy = sinon.spy(utils, 'logError'); + var options = { + 'adserver': 'anyother', + 'code': '/19968336/header-bid-tag-0' + }; + var masterTagUrl = $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag(adserverTag, options); + assert.ok(logErrorSpy.calledOnce, true); + utils.logError.restore(); + }); + + it('should return original adservertag if bids empty', () => { + $$PREBID_GLOBAL$$._bidsReceived = []; + var masterTagUrl = $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag(adserverTag, options); + expect(masterTagUrl).to.equal(adserverTag); + }); + + it('should log error when google\'s parameters are missing in adserverTag', () => { + var logErrorSpy = sinon.spy(utils, 'logError'); + var adserverTag = 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/19968336/header-bid-tag-0&impl=s&gdfp_req=1&env=vp&output=xml_vast2&unviewed_position_start=1&url=www.test.com'; + var masterTagUrl = $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag(adserverTag, options); + assert.ok(logErrorSpy.calledOnce, true); + utils.logError.restore(); + }); + }); + });