diff --git a/modules/yieldmoSyntheticInventoryModule.js b/modules/yieldmoSyntheticInventoryModule.js index 8412713dda65..c6d1982696e2 100644 --- a/modules/yieldmoSyntheticInventoryModule.js +++ b/modules/yieldmoSyntheticInventoryModule.js @@ -1,10 +1,10 @@ import { config } from '../src/config.js'; import { isGptPubadsDefined, isFn } from '../src/utils.js'; +import * as ajax from '../src/ajax.js' import strIncludes from 'core-js-pure/features/string/includes.js'; export const MODULE_NAME = 'Yieldmo Synthetic Inventory Module'; export const AD_SERVER_ENDPOINT = 'https://ads.yieldmo.com/v002/t_ads/ads'; -export const AD_REQUEST_TYPE = 'GET'; const USPAPI_VERSION = 1; let cmpVersion = 0; @@ -23,7 +23,22 @@ export function init(config) { window.top.googletag = window.top.googletag || {}; window.top.googletag.cmd = window.top.googletag.cmd || []; } - getAd(`${AD_SERVER_ENDPOINT}?${serialize(collectData(config.placementId, consentDataObj))}`, config); + ajax.ajaxBuilder()(`${AD_SERVER_ENDPOINT}?${serialize(collectData(config.placementId, consentDataObj))}`, { + success: (responceText, responseObj) => { + window.top.__ymAds = processResponse(responseObj); + const googletag = window.top.googletag; + googletag.cmd.push(() => { + if (window.top.document.body) { + googletagCmd(config, googletag); + } else { + window.top.document.addEventListener('DOMContentLoaded', () => googletagCmd(config, googletag)); + } + }); + }, + error: (message, err) => { + throw err; + } + }); } } }; @@ -81,7 +96,7 @@ function collectData(placementId, consentDataObj) { ct: timeStamp, connect: connection.effectiveType, bwe: connection.downlink ? connection.downlink + 'Mb/sec' : '', - rtt: connection.rtt, + rtt: 'rtt' in connection ? String(connection.rtt) : undefined, sd: connection.saveData, us_privacy: (consentDataObj.usp && consentDataObj.usp.usPrivacy) || '', cmp: (consentDataObj.cmp && consentDataObj.cmp.tcString) || '' @@ -98,35 +113,20 @@ function serialize(dataObj) { return str.join('&'); } -function processResponse(response) { - let responseBody; +function processResponse(res) { + let parsedResponseBody; try { - responseBody = JSON.parse(response.responseText); + parsedResponseBody = JSON.parse(res.responseText); } catch (err) { - throw new Error(`${MODULE_NAME}: response body is not valid JSON`); + throw new Error(`${MODULE_NAME}: response is not valid JSON`); } - if (response.status !== 200 || !responseBody.data || !responseBody.data.length || !responseBody.data[0].ads || !responseBody.data[0].ads.length) { - throw new Error(`${MODULE_NAME}: NOAD`); + if (res && res.status === 204) { + throw new Error(`${MODULE_NAME}: no content success status`); } - return responseBody; -} - -function getAd(url, config) { - const req = new XMLHttpRequest(); - req.open(AD_REQUEST_TYPE, url, true); - req.onload = (e) => { - const response = processResponse(e.target); - window.top.__ymAds = response; - const googletag = window.top.googletag; - googletag.cmd.push(() => { - if (window.top.document.body) { - googletagCmd(config, googletag); - } else { - window.top.document.addEventListener('DOMContentLoaded', () => googletagCmd(config, googletag)); - } - }); - }; - req.send(null); + if (parsedResponseBody.data && parsedResponseBody.data.length && parsedResponseBody.data[0].error_code) { + throw new Error(`${MODULE_NAME}: no ad, error_code: ${parsedResponseBody.data[0].error_code}`); + } + return parsedResponseBody; } function checkSandbox(w) { diff --git a/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js b/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js index 120f896c0787..d75b358a40eb 100644 --- a/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js +++ b/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import * as ajax from 'src/ajax.js'; import { init, MODULE_NAME, @@ -68,16 +69,15 @@ describe('Yieldmo Synthetic Inventory Module', function() { }).throw(`${MODULE_NAME}: adUnitPath required`); }); - describe('getAd', () => { - let requestMock = { - open: sinon.stub(), - send: sinon.stub(), - }; - const originalXMLHttpRequest = window.XMLHttpRequest; - const originalConnection = window.navigator.connection; - let clock; - let adServerRequest; - let response; + describe('Ajax ad request', () => { + let sandbox; + + const setAjaxStub = (cb) => { + const ajaxStub = sandbox.stub().callsFake(cb); + sandbox.stub(ajax, 'ajaxBuilder').callsFake(() => ajaxStub); + return ajaxStub; + } + const responseData = { data: [{ ads: [{ @@ -87,118 +87,99 @@ describe('Yieldmo Synthetic Inventory Module', function() { }; beforeEach(() => { - window.XMLHttpRequest = function FakeXMLHttpRequest() { - this.open = requestMock.open; - this.send = requestMock.send; - - adServerRequest = this; - }; - - response = { - target: { - responseText: JSON.stringify(responseData), - status: 200, - } - }; - - clock = sinon.useFakeTimers(); - Object.defineProperty(window.navigator, 'connection', { value: {}, writable: true }); + sandbox = sinon.sandbox.create(); }); afterEach(() => { - window.XMLHttpRequest = originalXMLHttpRequest; - - requestMock.open.resetBehavior(); - requestMock.open.resetHistory(); - requestMock.send.resetBehavior(); - requestMock.send.resetHistory(); - - adServerRequest = undefined; - - clock.restore(); - }); - - after(() => { - window.navigator.connection = originalConnection; + sandbox.restore(); }); it('should open ad request to ad server', () => { + const ajaxStub = setAjaxStub((url, callbackObj) => {}); + init(mockedYmConfig); - const adServerHost = (new URL(requestMock.open.getCall(0).args[1])).host; - expect(adServerHost).to.be.equal('ads.yieldmo.com'); + expect((new URL(ajaxStub.getCall(0).args[0])).host).to.be.equal('ads.yieldmo.com'); }); it('should properly combine ad request query', () => { + const ajaxStub = setAjaxStub((url, callbackObj) => {}); + // const title = 'Synthetic Inventory Test title'; + // const titleBackup = document.title; + // document.title = title; const pageDimensions = { - density: window.top.devicePixelRatio || 0, - height: window.top.screen.height || window.screen.top.availHeight || window.top.outerHeight || window.top.innerHeight || 481, - width: window.top.screen.width || window.screen.top.availWidth || window.top.outerWidth || window.top.innerWidth || 321, + scrd: String(window.top.devicePixelRatio || 0), + h: String(window.top.screen.height || window.screen.top.availHeight || window.top.outerHeight || window.top.innerHeight || 481), + w: String(window.top.screen.width || window.screen.top.availWidth || window.top.outerWidth || window.top.innerWidth || 321), }; + const connection = window.navigator.connection || {}; + const connectToCompare = { + connect: String(connection.effectiveType), + bwe: String(connection.downlink ? connection.downlink + 'Mb/sec' : ''), + rtt: String(connection.rtt), + sd: String(connection.saveData), + } init(mockedYmConfig); - - const queryParams = getQuearyParamsFromUrl(requestMock.open.getCall(0).args[1]); - + const queryParams = JSON.parse(JSON.stringify(getQuearyParamsFromUrl(ajaxStub.getCall(0).args[0]))); const timeStamp = queryParams.bust; expect(queryParams).to.deep.equal({ + // title, _s: '1', dnt: 'false', e: '4', - h: `${pageDimensions.height}`, p: mockedYmConfig.placementId, page_url: window.top.location.href, pr: window.top.location.href, - scrd: `${pageDimensions.density}`, - w: `${pageDimensions.width}`, - title: document.title, + bust: timeStamp, + pft: timeStamp, + ct: timeStamp, + ...connectToCompare, + ...pageDimensions }); - }); - - it('should send ad request to ad server', () => { - init(mockedYmConfig); - expect(requestMock.send.calledOnceWith(null)).to.be.true; + // document.title = titleBackup; }); - it('should throw an error if can not parse response', () => { - response.target.responseText = undefined; + it('should send ad request to ad server', () => { + const ajaxStub = setAjaxStub((url, callbackObj) => {}); init(mockedYmConfig); - expect(() => adServerRequest.onload(response)).to.throw(); + expect(ajaxStub.calledOnce).to.be.true; }); - it('should throw an error if status is not 200', () => { - response.target.status = 500; - - init(mockedYmConfig); + it('should throw an error if can not parse response', () => { + const ajaxStub = setAjaxStub((url, callbackObj) => { + callbackObj.success('', {responseText: '__invalid_JSON__', status: 200}); + }); - expect(() => adServerRequest.onload(response)).to.throw(); + expect(() => init(mockedYmConfig)).to.throw('Yieldmo Synthetic Inventory Module: response is not valid JSON'); }); - it('should throw an error if there is no data in response', () => { - response.target.responseText = '{}'; - - init(mockedYmConfig); + it('should throw an error if status is 204', () => { + const ajaxStub = setAjaxStub((url, callbackObj) => { + callbackObj.success('', {status: 204, responseText: '{}'}); + }); - expect(() => adServerRequest.onload(response)).to.throw(); + expect(() => init(mockedYmConfig)).to.throw('Yieldmo Synthetic Inventory Module: no content success status'); }); - it('should throw an error if there is no ads in response data', () => { - response.target.responseText = '{ data: [{}] }'; - - init(mockedYmConfig); + it('should throw an error if error_code present in the ad response', () => { + const ajaxStub = setAjaxStub((url, callbackObj) => { + callbackObj.success('', {status: 200, responseText: '{"data": [{"error_code": "NOAD"}]}'}); + }); - expect(() => adServerRequest.onload(response)).to.throw(); + expect(() => init(mockedYmConfig)).to.throw('Yieldmo Synthetic Inventory Module: no ad, error_code: NOAD'); }); it('should store ad response in window object', () => { - init(mockedYmConfig); - - adServerRequest.onload(response); + const ajaxStub = setAjaxStub((url, callbackObj) => { + callbackObj.success(JSON.stringify(responseData), {status: 200, responseText: JSON.stringify(responseData)}); + }); + init(mockedYmConfig); expect(window.top.__ymAds).to.deep.equal(responseData); }); @@ -206,9 +187,11 @@ describe('Yieldmo Synthetic Inventory Module', function() { const containerName = 'ym_sim_container_' + mockedYmConfig.placementId; const gtag = setGoogletag(); - init(mockedYmConfig); + const ajaxStub = setAjaxStub((url, callbackObj) => { + callbackObj.success(JSON.stringify(responseData), {status: 200, responseText: '{"data": [{"ads": []}]}'}); + }); - adServerRequest.onload(response); + init(mockedYmConfig); expect(gtag.cmd.length).to.equal(1); @@ -252,6 +235,7 @@ describe('Yieldmo Synthetic Inventory Module', function() { window.XMLHttpRequest = function FakeXMLHttpRequest() { this.open = requestMock.open; this.send = requestMock.send; + this.setRequestHeader = () => {}; }; clock = sinon.useFakeTimers(); @@ -426,6 +410,7 @@ describe('Yieldmo Synthetic Inventory Module', function() { window.XMLHttpRequest = function FakeXMLHttpRequest() { this.open = requestMock.open; this.send = requestMock.send; + this.setRequestHeader = () => {}; }; clock = sinon.useFakeTimers();