diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index 1cdbec829d9..32b9f0d1b0c 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -4,17 +4,53 @@ import adapter from '../src/AnalyticsAdapter'; import adapterManager from '../src/adapterManager'; +import CONSTANTS from '../src/constants.json'; +import * as utils from '../src/utils'; -// This config makes Prebid.js call this function on each event: -// `window['AdagioPrebidAnalytics']('on', eventType, args)` -// If it is missing, then Prebid.js will immediately log an error, -// instead of queueing the events until the function appears. -var adagioAdapter = adapter({ - global: 'AdagioPrebidAnalytics', - handler: 'on', - analyticsType: 'bundle' +const emptyUrl = ''; +const analyticsType = 'endpoint'; +const events = Object.keys(CONSTANTS.EVENTS).map(key => CONSTANTS.EVENTS[key]); +const VERSION = '2.0.0'; + +const adagioEnqueue = function adagioEnqueue(action, data) { + utils.getWindowTop().ADAGIO.queue.push({ action, data, ts: Date.now() }); +} + +function canAccessTopWindow() { + try { + if (utils.getWindowTop().location.href) { + return true; + } + } catch (error) { + return false; + } +} + +let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { + track: function({ eventType, args }) { + if (typeof args !== 'undefined' && events.indexOf(eventType) !== -1) { + adagioEnqueue('pb-analytics-event', { eventName: eventType, args }); + } + } }); +adagioAdapter.originEnableAnalytics = adagioAdapter.enableAnalytics; + +adagioAdapter.enableAnalytics = config => { + if (!canAccessTopWindow()) { + return; + } + + const w = utils.getWindowTop(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.versions = w.ADAGIO.versions || {}; + w.ADAGIO.versions.adagioAnalyticsAdapter = VERSION; + + adagioAdapter.originEnableAnalytics(config); +} + adapterManager.registerAnalyticsAdapter({ adapter: adagioAdapter, code: 'adagio' diff --git a/modules/adagioAnalyticsAdapter.md b/modules/adagioAnalyticsAdapter.md index 5734bc85b2a..312a26ea8da 100644 --- a/modules/adagioAnalyticsAdapter.md +++ b/modules/adagioAnalyticsAdapter.md @@ -1,7 +1,7 @@ # Overview Module Name: Adagio Analytics Adapter -Module Type: Adagio Adapter +Module Type: Analytics Adapter Maintainer: dev@adagio.io # Description diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 8f6e59b0633..76614e52bc5 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,36 +1,232 @@ import find from 'core-js/library/fn/array/find'; import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import { loadExternalScript } from '../src/adloader' +import JSEncrypt from 'jsencrypt/bin/jsencrypt'; +import sha256 from 'crypto-js/sha256'; const BIDDER_CODE = 'adagio'; -const VERSION = '1.0.0'; +const VERSION = '2.0.0'; +const FEATURES_VERSION = '1'; const ENDPOINT = 'https://mp.4dex.io/prebid'; const SUPPORTED_MEDIA_TYPES = ['banner']; +const ADAGIO_TAG_URL = '//script.4dex.io/localstore.js'; +const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; -/** - * Based on https://github.com/ua-parser/uap-cpp/blob/master/UaParser.cpp#L331, with the following updates: - * - replaced `mobile` by `mobi` in the table regexp, so Opera Mobile on phones is not detected as a tablet. - */ -function _getDeviceType() { - let ua = navigator.userAgent; +export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 +jFSzIbGJJyg3cKqvtE6A0iaz9PkIdJIvSSSNrmJv+lRGKPEyRA/VnzJIieL39Ngl +t0b0lsHN+W4n9kitS/DZ/xnxWK/9vxhv0ZtL1LL/rwR5Mup7rmJbNtDoNBw4TIGj +pV6EP3MTLosuUEpLaQIDAQAB +-----END PUBLIC KEY-----`; + +export function getAdagioScript() { + try { + const w = utils.getWindowTop(); + const ls = w.localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY); + + if (!ls) { + utils.logWarn('Adagio Script not found'); + return; + } + + const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/; - // Tablets must be checked before phones. - if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) { - return 5; // "tablet" + if (!hashRgx.test(ls)) { + utils.logWarn('No hash found in Adagio script'); + w.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + } else { + const r = ls.match(hashRgx); + const hash = r[2]; + const content = r[3]; + + var jsEncrypt = new JSEncrypt(); + jsEncrypt.setPublicKey(ADAGIO_PUBKEY); + + if (jsEncrypt.verify(content, hash, sha256)) { + utils.logInfo('Start Adagio script'); + Function(ls)(); // eslint-disable-line no-new-func + } else { + utils.logWarn('Invalid Adagio script found'); + w.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + } + } + } catch (err) { + // } - if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/).test(ua)) { - return 4; // "phone" +} + +function canAccessTopWindow() { + try { + if (utils.getWindowTop().location.href) { + return true; + } + } catch (error) { + return false; } - // Consider that all other devices are personal computers - return 2; +} + +function initAdagio() { + const w = utils.getWindowTop(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.versions = w.ADAGIO.versions || {}; + w.ADAGIO.versions.adagioBidderAdapter = VERSION; + + getAdagioScript(); + + loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE) +} + +if (canAccessTopWindow()) { + initAdagio(); +} + +const _features = { + getPrintNumber: function(adUnitCode) { + const adagioAdUnit = _getOrAddAdagioAdUnit(adUnitCode); + return adagioAdUnit.printNumber || 1; + }, + + getPageDimensions: function() { + const viewportDims = _features.getViewPortDimensions().split('x'); + const w = utils.getWindowTop(); + const body = w.document.body; + const html = w.document.documentElement; + const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); + + return viewportDims[0] + 'x' + pageHeight; + }, + + getViewPortDimensions: function() { + let viewPortWidth; + let viewPortHeight; + const w = utils.getWindowTop(); + const d = w.document; + + if (w.innerWidth) { + viewPortWidth = w.innerWidth; + viewPortHeight = w.innerHeight; + } else { + viewPortWidth = d.getElementsByTagName('body')[0].clientWidth; + viewPortHeight = d.getElementsByTagName('body')[0].clientHeight; + } + + return viewPortWidth + 'x' + viewPortHeight; + }, + + isDomLoading: function() { + const w = utils.getWindowTop(); + let performance = w.performance || w.msPerformance || w.webkitPerformance || w.mozPerformance; + let domLoading = -1; + + if (performance && performance.timing && performance.timing.navigationStart > 0) { + const val = performance.timing.domLoading - performance.timing.navigationStart; + if (val > 0) domLoading = val; + } + return domLoading; + }, + + getSlotPosition: function(element) { + const w = utils.getWindowTop(); + const d = w.document; + const el = element; + + let box = el.getBoundingClientRect(); + const docEl = d.documentElement; + const body = d.body; + const clientTop = d.clientTop || body.clientTop || 0; + const clientLeft = d.clientLeft || body.clientLeft || 0; + const scrollTop = w.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = w.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + const elComputedStyle = w.getComputedStyle(el, null); + const elComputedDisplay = elComputedStyle.display || 'block'; + const mustDisplayElement = elComputedDisplay === 'none'; + + if (mustDisplayElement) { + el.style = el.style || {}; + el.style.display = 'block'; + box = el.getBoundingClientRect(); + el.style.display = elComputedDisplay; + } + + const position = { + x: Math.round(box.left + scrollLeft - clientLeft), + y: Math.round(box.top + scrollTop - clientTop) + }; + + return position.x + 'x' + position.y; + }, + + getTimestamp: function() { + return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; + }, + + getDevice: function() { + if (!canAccessTopWindow()) return false; + const w = utils.getWindowTop(); + const ua = w.navigator.userAgent; + + if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) { + return 5; // "tablet" + } + if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/).test(ua)) { + return 4; // "phone" + } + return 2; // personal computers + }, + + getBrowser: function() { + const w = utils.getWindowTop(); + const ua = w.navigator.userAgent; + const uaLowerCase = ua.toLowerCase(); + return /Edge\/\d./i.test(ua) ? 'edge' : uaLowerCase.indexOf('chrome') > 0 ? 'chrome' : uaLowerCase.indexOf('firefox') > 0 ? 'firefox' : uaLowerCase.indexOf('safari') > 0 ? 'safari' : uaLowerCase.indexOf('opera') > 0 ? 'opera' : uaLowerCase.indexOf('msie') > 0 || w.MSStream ? 'ie' : 'unknow'; + }, + + getOS: function() { + const w = window.top; + const ua = w.navigator.userAgent; + const uaLowerCase = ua.toLowerCase(); + return uaLowerCase.indexOf('linux') > 0 ? 'linux' : uaLowerCase.indexOf('mac') > 0 ? 'mac' : uaLowerCase.indexOf('win') > 0 ? 'windows' : ''; + } +} + +function _pushInAdagioQueue(ob) { + if (!canAccessTopWindow()) return; + const w = utils.getWindowTop(); + w.ADAGIO.queue.push(ob); }; +function _getOrAddAdagioAdUnit(adUnitCode) { + const w = utils.getWindowTop(); + if (w.ADAGIO.adUnits[adUnitCode]) { + return w.ADAGIO.adUnits[adUnitCode] + } + return w.ADAGIO.adUnits[adUnitCode] = {}; +} + +function _computePrintNumber(adUnitCode) { + let printNumber = 1; + const w = utils.getWindowTop(); + if ( + w.ADAGIO && + w.ADAGIO.adUnits && w.ADAGIO.adUnits[adUnitCode] && + w.ADAGIO.adUnits[adUnitCode].pageviewId === _getPageviewId() && + w.ADAGIO.adUnits[adUnitCode].printNumber + ) { + printNumber = parseInt(w.ADAGIO.adUnits[adUnitCode].printNumber, 10) + 1; + } + return printNumber; +} + function _getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { userAgent: navigator.userAgent, language: navigator[language], - deviceType: _getDeviceType(), + deviceType: _features.getDevice(), dnt: utils.getDNT() ? 1 : 0, geo: {}, js: 1 @@ -38,36 +234,91 @@ function _getDevice() { }; function _getSite() { - const topLocation = utils.getTopWindowLocation(); + const w = utils.getWindowTop(); return { - domain: topLocation.hostname, - page: topLocation.href, - referrer: utils.getTopWindowReferrer() + domain: w.location.hostname, + page: w.location.href, + referrer: w.document.referrer || '' }; }; function _getPageviewId() { - return (!window.top.ADAGIO || !window.top.ADAGIO.pageviewId) ? '_' : window.top.ADAGIO.pageviewId; + if (!canAccessTopWindow()) return false; + const w = utils.getWindowTop(); + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID(); + return w.ADAGIO.pageviewId; }; +function _getElementFromTopWindow(element, currentWindow) { + if (utils.getWindowTop() === currentWindow) { + if (!element.getAttribute('id')) { + element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`); + } + return element; + } else { + const frame = currentWindow.frameElement; + return _getElementFromTopWindow(frame, currentWindow.parent); + } +} + +/** + * Returns all features for a specific adUnit element + * + * @param {Object} bidRequest + * @returns {Object} features for an element (see specs) + */ function _getFeatures(bidRequest) { - if (!window.top._ADAGIO || !window.top._ADAGIO.features) { - return {}; + if (!canAccessTopWindow()) return; + const w = utils.getWindowTop(); + const adUnitElementId = bidRequest.params.adUnitElementId; + const adUnitCode = bidRequest.adUnitCode; + + let element = window.document.getElementById(adUnitElementId); + + if (bidRequest.params.postBid === true) { + element = _getElementFromTopWindow(element, window); + w.ADAGIO.pbjsAdUnits.map((adUnit) => { + if (adUnit.code === adUnitCode) { + const outerElementId = element.getAttribute('id'); + adUnit.outerAdUnitElementId = outerElementId; + bidRequest.params.outerAdUnitElementId = outerElementId; + } + }); + } else { + element = w.document.getElementById(adUnitElementId); } - const rawFeatures = window.top._ADAGIO.features.getFeatures( - document.getElementById(bidRequest.adUnitCode), - function(features) { - return { - site_id: bidRequest.params.siteId, - placement: bidRequest.params.placementId, - pagetype: bidRequest.params.pagetypeId, - categories: bidRequest.params.categories - }; - } - ); - return rawFeatures; -} + let features = {}; + if (element) { + features = Object.assign({}, { + print_number: _features.getPrintNumber(bidRequest.adUnitCode).toString(), + page_dimensions: _features.getPageDimensions().toString(), + viewport_dimensions: _features.getViewPortDimensions().toString(), + dom_loading: _features.isDomLoading().toString(), + // layout: features.getLayout().toString(), + adunit_position: _features.getSlotPosition(element).toString(), + user_timestamp: _features.getTimestamp().toString(), + device: _features.getDevice().toString(), + url: w.location.origin + w.location.pathname, + browser: _features.getBrowser(), + os: _features.getOS() + }) + } + + const adUnitFeature = {}; + adUnitFeature[adUnitElementId] = { + features: features, + version: FEATURES_VERSION + }; + + _pushInAdagioQueue({ + action: 'features', + ts: Date.now(), + data: adUnitFeature + }); + + return features; +}; function _getGdprConsent(bidderRequest) { const consent = {}; @@ -91,45 +342,77 @@ export const spec = { supportedMediaType: SUPPORTED_MEDIA_TYPES, isBidRequestValid: function(bid) { - return !!(bid.params.siteId && bid.params.placementId); + const { adUnitCode, auctionId, sizes, bidder, params, mediaTypes } = bid; + const { organizationId, site, placement, adUnitElementId } = bid.params; + let isValid = false; + + if (canAccessTopWindow()) { + const w = utils.getWindowTop(); + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; + isValid = !!(organizationId && site && placement && adUnitElementId && document.getElementById(adUnitElementId) !== null); + const tempAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode); + tempAdUnits.push({ + code: adUnitCode, + sizes: (mediaTypes && mediaTypes.banner && Array.isArray(mediaTypes.banner.sizes)) ? mediaTypes.banner.sizes : sizes, + bids: [{ + bidder, + params + }] + }); + w.ADAGIO.pbjsAdUnits = tempAdUnits; + + if (isValid === true) { + let printNumber = _computePrintNumber(adUnitCode); + w.ADAGIO.adUnits[adUnitCode] = { + auctionId: auctionId, + pageviewId: _getPageviewId(), + printNumber + }; + } + } + + return isValid; }, buildRequests: function(validBidRequests, bidderRequest) { + // AdagioBidAdapter works when window.top can be reached only + if (!bidderRequest.refererInfo.reachedTop) return []; + const secure = (location.protocol === 'https:') ? 1 : 0; const device = _getDevice(); const site = _getSite(); const pageviewId = _getPageviewId(); const gdprConsent = _getGdprConsent(bidderRequest); const adUnits = utils._map(validBidRequests, (bidRequest) => { - bidRequest.params.features = _getFeatures(bidRequest); - const categories = bidRequest.params.categories; - if (typeof categories !== 'undefined' && !Array.isArray(categories)) { - bidRequest.params.categories = [categories]; - } + bidRequest.features = _getFeatures(bidRequest); return bidRequest; }); // Regroug ad units by siteId const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { - (groupedAdUnits[adUnit.params.siteId] = groupedAdUnits[adUnit.params.siteId] || []).push(adUnit); + (groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []).push(adUnit); return groupedAdUnits; }, {}); // Build one request per siteId - const requests = utils._map(Object.keys(groupedAdUnits), (siteId) => { + const requests = utils._map(Object.keys(groupedAdUnits), (organizationId) => { return { method: 'POST', url: ENDPOINT, data: { id: utils.generateUUID(), + organizationId: organizationId, secure: secure, device: device, site: site, - siteId: siteId, pageviewId: pageviewId, - adUnits: groupedAdUnits[siteId], + adUnits: groupedAdUnits[organizationId], gdpr: gdprConsent, - adapterVersion: VERSION + prebidVersion: '$prebid.version$', + adapterVersion: VERSION, + featuresVersion: FEATURES_VERSION }, options: { contentType: 'application/json' @@ -145,15 +428,27 @@ export const spec = { try { const response = serverResponse.body; if (response) { - response.bids.forEach(bidObj => { - const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); - if (bidReq) { - bidObj.placementId = bidReq.params.placementId; - bidObj.pagetypeId = bidReq.params.pagetypeId; - bidObj.categories = (bidReq.params.features && bidReq.params.features.categories) ? bidReq.params.features.categories : []; - } - bidResponses.push(bidObj); - }); + if (response.data) { + _pushInAdagioQueue({ + action: 'ssp-data', + ts: Date.now(), + data: response.data + }); + } + if (response.bids) { + response.bids.forEach(bidObj => { + const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); + if (bidReq) { + bidObj.site = bidReq.params.site; + bidObj.placement = bidReq.params.placement; + bidObj.pagetype = bidReq.params.pagetype; + bidObj.category = bidReq.params.category; + bidObj.subcategory = bidReq.params.subcategory; + bidObj.environment = bidReq.params.environment; + } + bidResponses.push(bidObj); + }); + } } } catch (err) { utils.logError(err); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index ff33b035e5f..5dc8aa3d80c 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -12,21 +12,33 @@ Connects to Adagio demand source to fetch bids. ```javascript var adUnits = [ + { + code: 'dfp_banniere_atf', + sizes: [[300, 250], [300, 600]], + bids: [ { - code: 'ad-unit_code', - sizes: [[300, 250], [300, 600]], - bids: [ - { - bidder: 'adagio', // Required - params: { - siteId: '0', // Required - Site ID from Adagio. - placementId: '4', // Required - Placement ID from Adagio. Refers to the placement of an ad unit in a page. - pagetypeId: '343', // Required - Page type ID from Adagio. - categories: ['IAB12', 'IAB12-2'], // IAB categories of the page. - } - } - ] - } + bidder: 'adagio', // Required + params: { + organizationId: '0', // Required - Organization ID provided by Adagio. + site: 'news-of-the-day', // Required - Site Name provided by Adagio. + adUnitElementId: 'dfp_banniere_atf', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + environment: 'mobile', // Required. Environment where the page is displayed. + placement: 'ban_atf', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + pagetype: 'article', // Required. The pagetype describes what kind of content will be present in the page. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false // Optional. Use it in case of Post-bid integration only. + } + } + ] + } ]; pbjs.addAdUnits(adUnits); @@ -35,22 +47,40 @@ Connects to Adagio demand source to fetch bids. adagio: { alwaysUseBid: true, adserverTargeting: [ + { + key: "site", + val: function (bidResponse) { + return bidResponse.site; + } + }, + { + key: "environment", + val: function (bidResponse) { + return bidResponse.environment; + } + }, { key: "placement", val: function (bidResponse) { - return bidResponse.placementId; + return bidResponse.placement; } }, { key: "pagetype", val: function (bidResponse) { - return bidResponse.pagetypeId; + return bidResponse.pagetype; + } + }, + { + key: "category", + val: function (bidResponse) { + return bidResponse.category; } }, { - key: "categories", + key: "subcategory", val: function (bidResponse) { - return bidResponse.categories.join(","); + return bidResponse.subcategory; } } ] diff --git a/src/adloader.js b/src/adloader.js index b422c802393..c45dacd8af0 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -4,6 +4,7 @@ import * as utils from './utils'; const _requestCache = {}; const _vendorWhitelist = [ 'criteo', + 'adagio' ] /** diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..8c07b86f8e9 --- /dev/null +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -0,0 +1,183 @@ +import adagioAnalyticsAdapter from 'modules/adagioAnalyticsAdapter'; +import { expect } from 'chai'; +import * as utils from 'src/utils'; + +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('adagio analytics adapter', () => { + let xhr; + let requests; + let sandbox + + beforeEach(() => { + sandbox = sinon.createSandbox(); + + xhr = sandbox.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + sandbox.stub(events, 'getEvents').returns([]); + + adapterManager.registerAnalyticsAdapter({ + code: 'adagio', + adapter: adagioAnalyticsAdapter + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('track', () => { + beforeEach(() => { + adapterManager.enableAnalytics({ + provider: 'adagio' + }); + }); + + afterEach(() => { + adagioAnalyticsAdapter.disableAnalytics(); + }); + + it('builds and sends auction data', () => { + const w = utils.getWindowTop(); + + let bidRequest = { + bids: [{ + adUnitCode: 'div-1', + params: { + features: { + siteId: '2', + placement: 'pave_top', + pagetype: 'article', + category: 'IAB12,IAB12-2', + device: '2', + } + } + }, { + adUnitCode: 'div-2', + params: { + features: { + siteId: '2', + placement: 'ban_top', + pagetype: 'article', + category: 'IAB12,IAB12-2', + device: '2', + } + }, + }], + }; + let bidResponse = { + bidderCode: 'adagio', + width: 300, + height: 250, + statusMessage: 'Bid available', + cpm: 6.2189757658226075, + currency: '', + netRevenue: false, + adUnitCode: 'div-1', + timeToRespond: 132, + }; + + // Step 1: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + // Step 2: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // Step 3: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {}); + + expect(w.ADAGIO.queue).length(3); + + let o = w.ADAGIO.queue.shift(); + expect(o).to.not.be.undefined; + expect(o.action).to.equal('pb-analytics-event'); + expect(o.ts).to.not.be.undefined; + expect(o.data).to.not.be.undefined; + expect(o.data).to.deep.equal({eventName: constants.EVENTS.BID_REQUESTED, args: bidRequest}); + + o = w.ADAGIO.queue.shift(); + expect(o).to.not.be.undefined; + expect(o.action).to.equal('pb-analytics-event'); + expect(o.ts).to.not.be.undefined; + expect(o.data).to.not.be.undefined; + expect(o.data).to.deep.equal({eventName: constants.EVENTS.BID_RESPONSE, args: bidResponse}); + + o = w.ADAGIO.queue.shift(); + expect(o).to.not.be.undefined; + expect(o.action).to.equal('pb-analytics-event'); + expect(o.ts).to.not.be.undefined; + expect(o.data).to.not.be.undefined; + expect(o.data).to.deep.equal({eventName: constants.EVENTS.AUCTION_END, args: {}}); + }); + }); + + describe('no track', () => { + beforeEach(() => { + sandbox.stub(utils, 'getWindowTop').throws(); + + adapterManager.enableAnalytics({ + provider: 'adagio' + }); + }); + + afterEach(() => { + adagioAnalyticsAdapter.disableAnalytics(); + sandbox.restore(); + }); + + it('builds and sends auction data', () => { + let bidRequest = { + bids: [{ + adUnitCode: 'div-1', + params: { + features: { + siteId: '2', + placement: 'pave_top', + pagetype: 'article', + category: 'IAB12,IAB12-2', + device: '2', + } + } + }, { + adUnitCode: 'div-2', + params: { + features: { + siteId: '2', + placement: 'ban_top', + pagetype: 'article', + category: 'IAB12,IAB12-2', + device: '2', + } + }, + }], + }; + let bidResponse = { + bidderCode: 'adagio', + width: 300, + height: 250, + statusMessage: 'Bid available', + cpm: 6.2189757658226075, + currency: '', + netRevenue: false, + adUnitCode: 'div-1', + timeToRespond: 132, + }; + + // Step 1: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + // Step 2: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // Step 3: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {}); + + utils.getWindowTop.restore(); + + expect(utils.getWindowTop().ADAGIO.queue).length(0); + }); + }); +}); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 7437b45b6c1..6c804418d03 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,10 +1,22 @@ import { expect } from 'chai'; +import { getAdagioScript, spec } from 'modules/adagioBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; -import { spec } from 'modules/adagioBidAdapter'; +import * as utils from 'src/utils'; describe('adagioAdapter', () => { + let utilsMock; const adapter = newBidder(spec); const ENDPOINT = 'https://mp.4dex.io/prebid'; + const VERSION = '2.0.0'; + + beforeEach(function() { + localStorage.removeItem('adagioScript'); + utilsMock = sinon.mock(utils); + }); + + afterEach(function() { + utilsMock.restore(); + }); describe('inherited functions', () => { it('exists and is a function', () => { @@ -13,12 +25,40 @@ describe('adagioAdapter', () => { }); describe('isBidRequestValid', () => { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + let element = { + x: 0, + y: 0, + width: 200, + height: 300, + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + sandbox.stub(document, 'getElementById').withArgs('banner-atf').returns(element); + }); + + afterEach(function () { + sandbox.restore(); + }); + let bid = { 'bidder': 'adagio', 'params': { - siteId: '123', - placementId: 4, - categories: ['IAB12', 'IAB12-2'] + organizationId: '0', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf' }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], @@ -27,33 +67,168 @@ describe('adagioAdapter', () => { 'auctionId': 'lel4fhp239i9km', }; + let bidWithMediaTypes = { + 'bidder': 'adagio', + 'params': { + organizationId: '0', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf' + }, + 'adUnitCode': 'adunit-code-2', + 'mediaTypes': { + banner: { + sizes: [[300, 250]], + } + }, + sizes: [[300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + } + it('should return true when required params found', () => { expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); + }) + + it('should compute a printNumber for the new bid request on same adUnitCode and same pageviewId', () => { + spec.isBidRequestValid(bid); + expect(window.top.ADAGIO.adUnits).ok; + expect(window.top.ADAGIO.adUnits['adunit-code']).ok; + expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(2); + + spec.isBidRequestValid(bid); + expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(3); + + window.top.ADAGIO.pageviewId = 123; + spec.isBidRequestValid(bid); + expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); + }); + + it('should return false when organization params is not passed', () => { + let bidTest = Object.assign({}, bid); + delete bidTest.params.organizationId; + expect(spec.isBidRequestValid(bidTest)).to.equal(false); }); it('should return false when site params is not passed', () => { let bidTest = Object.assign({}, bid); - delete bidTest.params.siteId; + delete bidTest.params.site; expect(spec.isBidRequestValid(bidTest)).to.equal(false); }); it('should return false when placement params is not passed', () => { let bidTest = Object.assign({}, bid); - delete bidTest.params.placementId; + delete bidTest.params.placement; expect(spec.isBidRequestValid(bidTest)).to.equal(false); }); + + it('should return false when adUnit element id params is not passed', () => { + let bidTest = Object.assign({}, bid); + delete bidTest.params.adUnitElementId; + expect(spec.isBidRequestValid(bidTest)).to.equal(false); + }); + + it('should return false if not in the window.top', () => { + sandbox.stub(utils, 'getWindowTop').throws(); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should expose ADAGIO.pbjsAdUnits in window', () => { + spec.isBidRequestValid(bidWithMediaTypes); + spec.isBidRequestValid(bid); + expect(window.top.ADAGIO.pbjsAdUnits).ok; + expect(window.top.ADAGIO.pbjsAdUnits).to.have.lengthOf(2); + const adUnitWithMediaTypeSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code-2')[0]; + const adUnitWithSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code')[0]; + expect(adUnitWithMediaTypeSizes.sizes).to.eql([[300, 250]]); + expect(adUnitWithSizes.sizes).to.eql([[300, 250], [300, 600]]); + }); }); describe('buildRequests', () => { + const sandbox = sinon.createSandbox(); + + const banner300x250 = { + x: 0, + y: 0, + width: 300, + height: 250, + getBoundingClientRect: () => { + return { + width: banner300x250.width, + height: banner300x250.height, + left: banner300x250.x, + top: banner300x250.y, + right: banner300x250.x + banner300x250.width, + bottom: banner300x250.y + banner300x250.height + }; + }, + }; + + const banner300x600 = { + x: 0, + y: 0, + width: 300, + height: 600, + getBoundingClientRect: () => { + return { + width: banner300x600.width, + height: banner300x600.height, + left: banner300x600.x, + top: banner300x600.y, + right: banner300x600.x + banner300x600.width, + bottom: banner300x600.y + banner300x600.height + }; + }, + }; + + const computedStyleBlock = { + display: 'block' + }; + + const computedStyleNone = { + display: 'none' + }; + + const stubs = { + topGetElementById: undefined, + topGetComputedStyle: undefined + } + + top.ADAGIO = top.ADAGIO || {}; + top.ADAGIO.adUnits = top.ADAGIO.adUnits || {}; + top.ADAGIO.pbjsAdUnits = top.ADAGIO.pbjsAdUnits || []; + + beforeEach(function () { + stubs.topGetElementById = sandbox.stub(top.document, 'getElementById'); + stubs.topGetComputedStyle = sandbox.stub(top, 'getComputedStyle'); + + stubs.topGetElementById.withArgs('banner-atf-123').returns(banner300x250); + stubs.topGetElementById.withArgs('banner-atf-456').returns(banner300x600); + stubs.topGetElementById.withArgs('does-not-exist').returns(null); + stubs.topGetComputedStyle.returns(computedStyleBlock); + }); + + afterEach(function () { + sandbox.restore(); + }); + + after(function() { + sandbox.reset(); + }) + let bidRequests = [ - // siteId 123 { 'bidder': 'adagio', 'params': { - siteId: '123', - placementId: 4, - pagetypeId: '232', - categories: ['IAB12'] + organizationId: '123', + site: 'ADAGIO-123', + placement: 'PAVE_ATF-123', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf-123' }, 'adUnitCode': 'adunit-code1', 'sizes': [[300, 250], [300, 600]], @@ -64,10 +239,11 @@ describe('adagioAdapter', () => { { 'bidder': 'adagio', 'params': { - siteId: '123', - placementId: 3, - pagetypeId: '232', - categories: ['IAB12'] + organizationId: '123', + site: 'ADAGIO-123', + placement: 'PAVE_ATF-123', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf-123' }, 'adUnitCode': 'adunit-code2', 'sizes': [[300, 250], [300, 600]], @@ -75,14 +251,38 @@ describe('adagioAdapter', () => { 'bidderRequestId': '8vfscuixrovn8i', 'auctionId': 'lel4fhp239i9km', }, - // siteId 456 { 'bidder': 'adagio', 'params': { - siteId: '456', - placementId: 4, - pagetypeId: '232', - categories: ['IAB12'] + organizationId: '456', + site: 'ADAGIO-456', + placement: 'PAVE_ATF-456', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf-456' + }, + 'adUnitCode': 'adunit-code3', + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + } + ]; + + const bidRequestsWithPostBid = [ + { + 'bidder': 'adagio', + 'params': { + organizationId: '456', + site: 'ADAGIO-456', + placement: 'PAVE_ATF-456', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf-456', + postBid: true }, 'adUnitCode': 'adunit-code3', 'sizes': [[300, 250], [300, 600]], @@ -102,36 +302,120 @@ describe('adagioAdapter', () => { consentString: consentString, gdprApplies: true, allowAuctionWithoutConsent: true + }, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://test.io/index.html?pbjs_debug=true' } }; it('groups requests by siteId', () => { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests).to.have.lengthOf(2); - expect(requests[0].data.siteId).to.equal('123'); + expect(requests[0].data.organizationId).to.equal('123'); expect(requests[0].data.adUnits).to.have.lengthOf(2); - expect(requests[1].data.siteId).to.equal('456'); + expect(requests[1].data.organizationId).to.equal('456'); expect(requests[1].data.adUnits).to.have.lengthOf(1); }); it('sends bid request to ENDPOINT_PB via POST', () => { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests).to.have.lengthOf(2); const request = requests[0]; expect(request.method).to.equal('POST'); expect(request.url).to.equal(ENDPOINT); + expect(request.data.prebidVersion).to.equal('$prebid.version$'); }); - it('features params must be an empty object if featurejs is not loaded', () => { - const requests = spec.buildRequests(bidRequests); - expect(requests).to.have.lengthOf(2); + it('features params must be empty if param adUnitElementId is not found', () => { + const requests = spec.buildRequests([Object.assign({}, bidRequests[0], {params: {adUnitElementId: 'does-not-exist'}})], bidderRequest); const request = requests[0]; - const expected = {}; - expect(request.data.adUnits[0].params.features).to.deep.equal(expected); + const expected = {} + expect(request.data.adUnits[0].features).to.deep.equal(expected); + }); + + it('features params "adunit_position" should be computed even if DOM element is display:none', () => { + stubs.topGetComputedStyle.returns(computedStyleNone); + const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); + let request = requests[0]; + expect(request.data.adUnits[0].features).to.exist; + expect(request.data.adUnits[0].features.adunit_position).to.equal('0x0'); + }); + + it('features params "viewport" should be computed even if window.innerWidth is not supported', () => { + sandbox.stub(top, 'innerWidth').value(undefined); + const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); + let request = requests[0]; + expect(request.data.adUnits[0].features).to.exist; + expect(request.data.adUnits[0].features.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); + }); + + it('AdUnit requested should have the correct sizes array depending on the config', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[1].data.adUnits[0]).to.have.property('mediaTypes'); + }); + + it('features params must be an object if featurejs is loaded', () => { + let requests = spec.buildRequests(bidRequests, bidderRequest); + let request = requests[0]; + expect(request.data.adUnits[0].features).to.exist; + }); + + it('outerAdUnitElementId must be added when PostBid param has been set', () => { + top.ADAGIO = top.ADAGIO || {}; + top.ADAGIO.pbjsAdUnits = []; + + top.ADAGIO.pbjsAdUnits.push({ + code: bidRequestsWithPostBid[0].adUnitCode, + sizes: bidRequestsWithPostBid[0].sizes, + bids: [{ + bidder: bidRequestsWithPostBid[0].bidder, + params: bidRequestsWithPostBid[0].params + }] + }); + let requests = spec.buildRequests(bidRequestsWithPostBid, bidderRequest); + let request = requests[0]; + expect(request.data.adUnits[0].features).to.exist; + expect(request.data.adUnits[0].params.outerAdUnitElementId).to.exist; + }); + + it('generates a pageviewId if missing', () => { + window.top.ADAGIO = window.top.ADAGIO || {}; + delete window.top.ADAGIO.pageviewId; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.have.lengthOf(2); + + expect(requests[0].data.pageviewId).to.exist.and.to.not.equal('_').and.to.not.equal(''); + expect(requests[0].data.pageviewId).to.equal(requests[1].data.pageviewId); + }); + + it('uses an existing pageviewId if present', () => { + window.top.ADAGIO = window.top.ADAGIO || {}; + window.top.ADAGIO.pageviewId = 'abc'; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.have.lengthOf(2); + + expect(requests[0].data.pageviewId).to.equal('abc'); + expect(requests[1].data.pageviewId).to.equal('abc'); }); + it('should send the printNumber in features object', () => { + window.top.ADAGIO = window.top.ADAGIO || {}; + window.top.ADAGIO.pageviewId = 'abc'; + window.top.ADAGIO.adUnits['adunit-code1'] = { + pageviewId: 'abc', + printNumber: 2 + }; + const requests = spec.buildRequests([bidRequests[0]], bidderRequest); + const request = requests[0]; + expect(request.data.adUnits[0].features.print_number).to.equal('2'); + }) + it('GDPR consent is applied', () => { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests).to.have.lengthOf(2); @@ -172,11 +456,30 @@ describe('adagioAdapter', () => { expect(request.data.gdpr).to.exist; expect(request.data.gdpr).to.be.empty; }); + + it('should expose version in window', () => { + expect(window.top.ADAGIO).ok; + expect(window.top.ADAGIO.versions).ok; + expect(window.top.ADAGIO.versions.adagioBidderAdapter).to.eq(VERSION); + }); + + it('should returns an empty array if the bidder cannot access to window top (based on refererInfo.reachedTop)', () => { + const requests = spec.buildRequests(bidRequests, { + ...bidderRequest, + refererInfo: { reachedTop: false } + }); + expect(requests).to.be.empty; + }); }); describe('interpretResponse', () => { + const sandbox = sinon.createSandbox(); + let serverResponse = { body: { + data: { + pred: 1 + }, bids: [ { ad: '
', @@ -193,27 +496,66 @@ describe('adagioAdapter', () => { } }; + let emptyBodyServerResponse = { + body: null + }; + + let withoutBidsArrayServerResponse = { + body: { + bids: [] + } + }; + + let serverResponseWhichThrowsException = { + body: { + data: { + pred: 1 + }, + bids: { + foo: 'bar' + } + } + }; + let bidRequest = { 'data': { 'adUnits': [ { 'bidder': 'adagio', 'params': { - siteId: '666', - placementId: 4, - pagetypeId: '232', - categories: ['IAB12'] + organizationId: '456', + site: 'ADAGIO-456', + placement: 'PAVE_ATF-456', + adUnitElementId: 'banner-atf-456', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'SITE-MOBILE' }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], 'bidId': 'c180kg4267tyqz', 'bidderRequestId': '8vfscuixrovn8i', 'auctionId': 'lel4fhp239i9km', + 'pageviewId': 'd8c4fl2k39i0wn', } ] } }; + afterEach(function() { + sandbox.restore(); + }); + + it('Should returns empty response if body is empty', () => { + expect(spec.interpretResponse(emptyBodyServerResponse, bidRequest)).to.be.an('array').length(0); + expect(spec.interpretResponse({body: {}}, bidRequest)).to.be.an('array').length(0); + }); + + it('Should returns empty response if bids array is empty', () => { + expect(spec.interpretResponse({withoutBidsArrayServerResponse}, bidRequest)).to.be.an('array').length(0); + }); + it('should get correct bid response', () => { let expectedResponse = [{ ad: '', @@ -225,13 +567,35 @@ describe('adagioAdapter', () => { requestId: 'c180kg4267tyqz', ttl: 360, width: 300, - categories: [], - pagetypeId: '232', - placementId: 4, + placement: 'PAVE_ATF-456', + site: 'ADAGIO-456', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'SITE-MOBILE' }]; expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.deep.equal(expectedResponse); }); + + it('Should populate ADAGIO queue with ssp-data', () => { + spec.interpretResponse(serverResponse, bidRequest); + expect(window.top.ADAGIO).ok; + expect(window.top.ADAGIO.queue).to.be.an('array'); + }); + + it('Should not populate ADAGIO queue with ssp-data if not in top window', () => { + utils.getWindowTop().ADAGIO.queue = []; + sandbox.stub(utils, 'getWindowTop').throws(); + spec.interpretResponse(serverResponse, bidRequest); + expect(window.top.ADAGIO).ok; + expect(window.top.ADAGIO.queue).to.be.an('array'); + expect(window.top.ADAGIO.queue).empty; + }); + + it('should return an empty response even if an exception is ', () => { + expect(spec.interpretResponse(serverResponseWhichThrowsException, bidRequest)).to.be.an('array').length(0); + }); }); describe('getUserSyncs', () => { @@ -270,4 +634,64 @@ describe('adagioAdapter', () => { expect(emptyResult).to.equal(false); }); }); + + describe('getAdagioScript', () => { + const VALID_HASH = 'Lddcw3AADdQDrPtbRJkKxvA+o1CtScGDIMNRpHB3NnlC/FYmy/9RKXelKrYj/sjuWusl5YcOpo+lbGSkk655i8EKuDiOvK6ae/imxSrmdziIp+S/TA6hTFJXcB8k1Q9OIp4CMCT52jjXgHwX6G0rp+uYoCR25B1jHaHnpH26A6I='; + const INVALID_HASH = 'invalid'; + const VALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){};(_ADAGIO)();\n'; + const INVALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){//corrupted};(_ADAGIO)();\n'; + const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; + + it('should verify valid hash with valid script', function () { + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + + utilsMock.expects('logInfo').withExactArgs('Start Adagio script').once(); + utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + + getAdagioScript(); + + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.equals('// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + utilsMock.verify(); + }); + + it('should verify valid hash with invalid script', function () { + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + INVALID_SCRIPT_CONTENT); + + utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + + getAdagioScript(); + + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + + it('should verify invalid hash with valid script', function () { + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + INVALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + + utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + + getAdagioScript(); + + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + + it('should verify missing hash', function () { + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, VALID_SCRIPT_CONTENT); + + utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').once(); + utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + + getAdagioScript(); + + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + }); });