diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js
new file mode 100644
index 00000000000..92d7ece4adb
--- /dev/null
+++ b/modules/gamoshiBidAdapter.js
@@ -0,0 +1,293 @@
+import * as utils from '../src/utils';
+import {registerBidder} from '../src/adapters/bidderFactory';
+import {config} from '../src/config';
+import {Renderer} from '../src/Renderer';
+import {BANNER, VIDEO} from '../src/mediaTypes';
+
+const ENDPOINTS = {
+ 'gamoshi': 'https://rtb.gamoshi.io'
+};
+
+const DEFAULT_TTL = 360;
+
+export const helper = {
+ getTopFrame: function () {
+ try {
+ return window.top === window ? 1 : 0;
+ } catch (e) {
+ }
+ return 0;
+ },
+ startsWith: function (str, search) {
+ return str.substr(0, search.length) === search;
+ },
+ getTopWindowDomain: function (url) {
+ const domainStart = url.indexOf('://') + '://'.length;
+ return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart));
+ },
+
+ getMediaType: function (bid) {
+ if (bid.ext) {
+ if (bid.ext.media_type) {
+ return bid.ext.media_type.toLowerCase();
+ } else if (bid.ext.vast_url) {
+ return VIDEO;
+ } else {
+ return BANNER;
+ }
+ }
+ return BANNER;
+ }
+};
+
+export const spec = {
+ code: 'gamoshi',
+ aliases: ['gambid', 'cleanmedia', '9MediaOnline'],
+ supportedMediaTypes: ['banner', 'video'],
+
+ isBidRequestValid: function (bid) {
+ return !!bid.params.supplyPartnerId && utils.isStr(bid.params.supplyPartnerId) &&
+ (!bid.params['rtbEndpoint'] || utils.isStr(bid.params['rtbEndpoint'])) &&
+ (!bid.params.bidfloor || utils.isNumber(bid.params.bidfloor)) &&
+ (!bid.params['adpos'] || utils.isNumber(bid.params['adpos'])) &&
+ (!bid.params['protocols'] || Array.isArray(bid.params['protocols'])) &&
+ (!bid.params.instl || bid.params.instl === 0 || bid.params.instl === 1);
+ },
+
+ buildRequests: function (validBidRequests, bidderRequest) {
+ return validBidRequests.map(bidRequest => {
+ const {adUnitCode, auctionId, mediaTypes, params, sizes, transactionId} = bidRequest;
+ const baseEndpoint = params['rtbEndpoint'] || ENDPOINTS['gamoshi'];
+ const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : '');
+ let url = config.getConfig('pageUrl') || bidderRequest.refererInfo.referer;
+
+ const rtbBidRequest = {
+ id: auctionId,
+ site: {
+ domain: helper.getTopWindowDomain(url),
+ page: url,
+ ref: bidderRequest.refererInfo.referer
+ },
+ device: {
+ ua: navigator.userAgent,
+ dnt: utils.getDNT() ? 1 : 0,
+ h: screen.height,
+ w: screen.width,
+ language: navigator.language
+ },
+ imp: [],
+ ext: {},
+ user: {
+ ext: {}
+ }
+ };
+ const gdprConsent = bidderRequest.gdprConsent;
+
+ if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) {
+ rtbBidRequest.ext.gdpr_consent = {
+ consent_string: gdprConsent.consentString,
+ consent_required: gdprConsent.gdprApplies
+ };
+ rtbBidRequest.regs = {
+ ext: {
+ gdpr: gdprConsent.gdprApplies === true ? 1 : 0
+ }
+ };
+ rtbBidRequest.user = {
+ ext: {
+ consent: gdprConsent.consentString
+ }
+ }
+ }
+ const imp = {
+ id: transactionId,
+ instl: params.instl === 1 ? 1 : 0,
+ tagid: adUnitCode,
+ bidfloor: params.bidfloor || 0,
+ bidfloorcur: 'USD',
+ secure: 1
+ };
+
+ const hasFavoredMediaType =
+ params.favoredMediaType && this.supportedMediaTypes.includes(params.favoredMediaType);
+
+ if (!mediaTypes || mediaTypes.banner) {
+ if (!hasFavoredMediaType || params.favoredMediaType === BANNER) {
+ const bannerImp = Object.assign({}, imp, {
+ banner: {
+ w: sizes.length ? sizes[0][0] : 300,
+ h: sizes.length ? sizes[0][1] : 250,
+ pos: params.pos || 0,
+ topframe: utils.inIframe() ? 0 : 1
+ }
+ });
+ rtbBidRequest.imp.push(bannerImp);
+ }
+ }
+
+ if (mediaTypes && mediaTypes.video) {
+ if (!hasFavoredMediaType || params.favoredMediaType === VIDEO) {
+ const playerSize = mediaTypes.video.playerSize || sizes;
+ const videoImp = Object.assign({}, imp, {
+ video: {
+ protocols: params.protocols || [1, 2, 3, 4, 5, 6],
+ pos: params.pos || 0,
+ ext: {
+ context: mediaTypes.video.context
+ }
+ }
+ });
+
+ if (utils.isArray(playerSize[0])) {
+ videoImp.video.w = playerSize[0][0];
+ videoImp.video.h = playerSize[0][1];
+ } else if (utils.isNumber(playerSize[0])) {
+ videoImp.video.w = playerSize[0];
+ videoImp.video.h = playerSize[1];
+ } else {
+ videoImp.video.w = 300;
+ videoImp.video.h = 250;
+ }
+
+ rtbBidRequest.imp.push(videoImp);
+ }
+ }
+
+ let eids = [];
+ if (bidRequest && bidRequest.userId) {
+ addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 'ID5ID');
+ addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID');
+ }
+ if (eids.length > 0) {
+ rtbBidRequest.user.ext.eids = eids;
+ }
+
+ if (rtbBidRequest.imp.length === 0) {
+ return;
+ }
+
+ return {method: 'POST', url: rtbEndpoint, data: rtbBidRequest, bidRequest};
+ });
+ },
+
+ interpretResponse: function (serverResponse, bidRequest) {
+ const response = serverResponse && serverResponse.body;
+ if (!response) {
+ utils.logError('empty response');
+ return [];
+ }
+
+ const bids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []);
+ let outBids = [];
+
+ bids.forEach(bid => {
+ const outBid = {
+ requestId: bidRequest.bidRequest.bidId,
+ cpm: bid.price,
+ width: bid.w,
+ height: bid.h,
+ ttl: DEFAULT_TTL,
+ creativeId: bid.crid || bid.adid,
+ netRevenue: true,
+ currency: bid.cur || response.cur,
+ mediaType: helper.getMediaType(bid)
+ };
+
+ if (utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.' + outBid.mediaType)) {
+ if (outBid.mediaType === BANNER) {
+ outBids.push(Object.assign({}, outBid, {ad: bid.adm}));
+ } else if (outBid.mediaType === VIDEO) {
+ const context = utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.video.context');
+ outBids.push(Object.assign({}, outBid, {
+ vastUrl: bid.ext.vast_url,
+ vastXml: bid.adm,
+ renderer: context === 'outstream' ? newRenderer(bidRequest.bidRequest, bid) : undefined
+ }));
+ }
+ }
+ });
+ return outBids;
+ },
+
+ getUserSyncs: function (syncOptions, serverResponses, gdprConsent) {
+ const syncs = [];
+ const gdprApplies = gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean') ? gdprConsent.gdprApplies : false;
+ const suffix = gdprApplies ? 'gc=' + encodeURIComponent(gdprConsent.consentString) : 'gc=missing';
+ serverResponses.forEach(resp => {
+ if (resp.body) {
+ const bidResponse = resp.body;
+ if (bidResponse.ext && Array.isArray(bidResponse.ext['utrk'])) {
+ bidResponse.ext['utrk'].forEach(pixel => {
+ const url = pixel.url + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix);
+ return syncs.push({type: pixel.type, url});
+ });
+ }
+ if (Array.isArray(bidResponse.seatbid)) {
+ bidResponse.seatbid.forEach(seatBid => {
+ if (Array.isArray(seatBid.bid)) {
+ seatBid.bid.forEach(bid => {
+ if (bid.ext && Array.isArray(bid.ext['utrk'])) {
+ bid.ext['utrk'].forEach(pixel => {
+ const url = pixel.url + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix);
+ return syncs.push({type: pixel.type, url});
+ });
+ }
+ });
+ }
+ });
+ }
+ }
+ });
+ return syncs;
+ }
+};
+
+function newRenderer(bidRequest, bid, rendererOptions = {}) {
+ const renderer = Renderer.install({
+ url: (bidRequest.params && bidRequest.params.rendererUrl) || (bid.ext && bid.ext.renderer_url) || 'https://s.gamoshi.io/video/latest/renderer.js',
+ config: rendererOptions,
+ loaded: false,
+ });
+ try {
+ renderer.setRender(renderOutstream);
+ } catch (err) {
+ utils.logWarn('Prebid Error calling setRender on renderer', err);
+ }
+ return renderer;
+}
+
+function renderOutstream(bid) {
+ bid.renderer.push(() => {
+ const unitId = bid.adUnitCode + '/' + bid.adId;
+ window['GamoshiPlayer'].renderAd({
+ id: unitId,
+ debug: window.location.href.indexOf('pbjsDebug') >= 0,
+ placement: document.getElementById(bid.adUnitCode),
+ width: bid.width,
+ height: bid.height,
+ events: {
+ ALL_ADS_COMPLETED: () => window.setTimeout(() => {
+ window['GamoshiPlayer'].removeAd(unitId);
+ }, 300)
+ },
+ vastUrl: bid.vastUrl,
+ vastXml: bid.vastXml
+ });
+ });
+}
+
+function addExternalUserId(eids, value, source, rtiPartner) {
+ if (utils.isStr(value)) {
+ eids.push({
+ source,
+ uids: [{
+ id: value,
+ ext: {
+ rtiPartner
+ }
+ }]
+ });
+ }
+}
+
+registerBidder(spec);
diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js
new file mode 100644
index 00000000000..a0e569a905f
--- /dev/null
+++ b/test/spec/modules/gamoshiBidAdapter_spec.js
@@ -0,0 +1,549 @@
+import {expect} from 'chai';
+import {spec, helper} from 'modules/gamoshiBidAdapter';
+import * as utils from 'src/utils';
+import {newBidder} from '../../../src/adapters/bidderFactory';
+
+const supplyPartnerId = '123';
+const adapter = newBidder(spec);
+describe('GamoshiAdapter', function () {
+ describe('Get top Frame', function () {
+ it('check if you are in the top frame', function () {
+ expect(helper.getTopFrame()).to.equal(0);
+ });
+ it('verify domain parsing', function () {
+ expect(helper.getTopWindowDomain('http://www.domain.com')).to.equal('www.domain.com');
+ });
+ });
+ describe('Is String start with search ', function () {
+ it('check if a string started with', function () {
+ expect(helper.startsWith('gamoshi.com', 'gamo')).to.equal(true);
+ });
+ });
+
+ describe('inherited functions', function () {
+ it('exists and is a function', function () {
+ expect(adapter.callBids).to.exist.and.to.be.a('function');
+ });
+ });
+
+ describe('isBidRequestValid', function () {
+ it('should validate supply-partner ID', function () {
+ expect(spec.isBidRequestValid({params: {}})).to.equal(false);
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: 123}})).to.equal(false);
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true);
+ });
+
+ it('should validate RTB endpoint', function () {
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // RTB endpoint has a default
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', rtbEndpoint: 123}})).to.equal(false);
+ expect(spec.isBidRequestValid({
+ params: {
+ supplyPartnerId: '123',
+ rtbEndpoint: 'https://some.url.com'
+ }
+ })).to.equal(true);
+ });
+
+ it('should validate bid floor', function () {
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // bidfloor has a default
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: '123'}})).to.equal(false);
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: 0.1}})).to.equal(true);
+ });
+
+ it('should validate adpos', function () {
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: '123'}})).to.equal(false);
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: 0.1}})).to.equal(true);
+ });
+
+ it('should validate instl', function () {
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: '123'}})).to.equal(false);
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: -1}})).to.equal(false);
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 0}})).to.equal(true);
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 1}})).to.equal(true);
+ expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 2}})).to.equal(false);
+ });
+ });
+
+ describe('buildRequests', function () {
+ const bidRequest = {
+ 'adUnitCode': 'adunit-code',
+ 'auctionId': '1d1a030790a475',
+ 'mediaTypes': {
+ banner: {}
+ },
+ 'params': {
+ 'supplyPartnerId': supplyPartnerId
+ },
+ 'sizes': [[300, 250], [300, 600]],
+ 'transactionId': 'a123456789',
+ refererInfo: {referer: 'http://examplereferer.com'},
+ gdprConsent: {
+ consentString: 'some string',
+ gdprApplies: true
+ }
+ };
+ it('returns an array', function () {
+ let response;
+ response = spec.buildRequests([]);
+ expect(Array.isArray(response)).to.equal(true);
+ expect(response.length).to.equal(0);
+ response = spec.buildRequests([bidRequest], bidRequest);
+ expect(Array.isArray(response)).to.equal(true);
+ expect(response.length).to.equal(1);
+ const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'a'});
+ const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'b'});
+ response = spec.buildRequests([adUnit1, adUnit2], bidRequest);
+ expect(Array.isArray(response)).to.equal(true);
+ expect(response.length).to.equal(2);
+ });
+
+ it('targets correct endpoint', function () {
+ let response;
+ response = spec.buildRequests([bidRequest], bidRequest)[0];
+ expect(response.method).to.equal('POST');
+ expect(response.url).to.match(new RegExp(`^https://rtb\\.gamoshi\\.io/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g'));
+ expect(response.data.id).to.equal(bidRequest.auctionId);
+ const bidRequestWithEndpoint = utils.deepClone(bidRequest);
+ bidRequestWithEndpoint.params.rtbEndpoint = 'https://rtb2.gamoshi.io/a12';
+ response = spec.buildRequests([bidRequestWithEndpoint], bidRequest)[0];
+ expect(response.url).to.match(new RegExp(`^https://rtb2\\.gamoshi\\.io/a12/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g'));
+ });
+
+ it('builds request correctly', function () {
+ let bidRequest2 = utils.deepClone(bidRequest);
+ bidRequest2.refererInfo.referer = 'http://www.test.com/page.html';
+ let response = spec.buildRequests([bidRequest], bidRequest2)[0];
+ expect(response.data.site.domain).to.equal('www.test.com');
+ expect(response.data.site.page).to.equal('http://www.test.com/page.html');
+ expect(response.data.site.ref).to.equal('http://www.test.com/page.html');
+ expect(response.data.imp.length).to.equal(1);
+ expect(response.data.imp[0].id).to.equal(bidRequest.transactionId);
+ expect(response.data.imp[0].instl).to.equal(0);
+ expect(response.data.imp[0].tagid).to.equal(bidRequest.adUnitCode);
+ expect(response.data.imp[0].bidfloor).to.equal(0);
+ expect(response.data.imp[0].bidfloorcur).to.equal('USD');
+ const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest);
+ bidRequestWithInstlEquals1.params.instl = 1;
+ response = spec.buildRequests([bidRequestWithInstlEquals1], bidRequest2)[0];
+ expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals1.params.instl);
+ const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest);
+ bidRequestWithInstlEquals0.params.instl = 1;
+ response = spec.buildRequests([bidRequestWithInstlEquals0], bidRequest2)[0];
+ expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals0.params.instl);
+ const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest);
+ bidRequestWithBidfloorEquals1.params.bidfloor = 1;
+ response = spec.buildRequests([bidRequestWithBidfloorEquals1], bidRequest2)[0];
+ expect(response.data.imp[0].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor);
+ });
+
+ it('builds request banner object correctly', function () {
+ let response;
+ const bidRequestWithBanner = utils.deepClone(bidRequest);
+ bidRequestWithBanner.mediaTypes = {
+ banner: {
+ sizes: [[300, 250], [120, 600]]
+ }
+ };
+ response = spec.buildRequests([bidRequestWithBanner], bidRequest)[0];
+ expect(response.data.imp[0].banner.w).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][0]);
+ expect(response.data.imp[0].banner.h).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][1]);
+ expect(response.data.imp[0].banner.pos).to.equal(0);
+ expect(response.data.imp[0].banner.topframe).to.equal(0);
+ const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner);
+ bidRequestWithPosEquals1.params.pos = 1;
+ response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0];
+ expect(response.data.imp[0].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos);
+ });
+
+ it('builds request video object correctly', function () {
+ let response;
+ const bidRequestWithVideo = utils.deepClone(bidRequest);
+ bidRequestWithVideo.mediaTypes = {
+ video: {
+ playerSize: [[302, 252]]
+ }
+ };
+ response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0];
+ expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][0]);
+ expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][1]);
+ expect(response.data.imp[0].video.pos).to.equal(0);
+ bidRequestWithVideo.mediaTypes = {
+ video: {
+ playerSize: [302, 252]
+ }
+ };
+
+ const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo);
+ expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0]);
+ expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[1]);
+
+ bidRequestWithPosEquals1.params.pos = 1;
+ response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0];
+ expect(response.data.imp[0].video.pos).to.equal(bidRequestWithPosEquals1.params.pos);
+ });
+
+ it('builds request video object correctly with context', function () {
+ let response;
+ const bidRequestWithVideo = utils.deepClone(bidRequest);
+ bidRequestWithVideo.mediaTypes = {
+ video: {
+ context: 'instream'
+ }
+ };
+ response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0];
+ expect(response.data.imp[0].video.ext.context).to.equal('instream');
+ bidRequestWithVideo.mediaTypes.video.context = 'outstream';
+
+ const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo);
+ bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream';
+ response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0];
+ expect(response.data.imp[0].video.ext.context).to.equal('outstream');
+
+ const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo);
+ bidRequestWithPosEquals2.mediaTypes.video.context = null;
+ response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0];
+ expect(response.data.imp[0].video.ext.context).to.equal(null);
+ });
+
+ it('builds request video object correctly with multi-dimensions size array', function () {
+ let response;
+ const bidRequestWithVideo = utils.deepClone(bidRequest);
+ bidRequestWithVideo.mediaTypes.video = {
+ playerSize: [[304, 254], [305, 255]],
+ context: 'instream'
+ };
+
+ response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0];
+ expect(response.data.imp[1].video.ext.context).to.equal('instream');
+ bidRequestWithVideo.mediaTypes.video.context = 'outstream';
+
+ const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo);
+ bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream';
+ response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0];
+ expect(response.data.imp[1].video.ext.context).to.equal('outstream');
+
+ const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo);
+ bidRequestWithPosEquals2.mediaTypes.video.context = null;
+ response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0];
+ expect(response.data.imp[1].video.ext.context).to.equal(null);
+ });
+
+ it('builds request with gdpr consent', function () {
+ let response = spec.buildRequests([bidRequest], bidRequest)[0];
+
+ expect(response.data.ext.gdpr_consent).to.not.equal(null).and.not.equal(undefined);
+ expect(response.data.ext).to.have.property('gdpr_consent');
+ expect(response.data.ext.gdpr_consent.consent_string).to.equal('some string');
+ expect(response.data.ext.gdpr_consent.consent_required).to.equal(true);
+
+ expect(response.data.regs.ext.gdpr).to.not.equal(null).and.not.equal(undefined);
+ expect(response.data.user.ext.consent).to.equal('some string');
+ });
+
+ it('build request with ID5 Id', function () {
+ const bidRequestClone = utils.deepClone(bidRequest);
+ bidRequestClone.userId = {};
+ bidRequestClone.userId.id5id = 'id5-user-id';
+ let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0];
+ expect(request.data.user.ext.eids).to.deep.equal([{
+ 'source': 'id5-sync.com',
+ 'uids': [{
+ 'id': 'id5-user-id',
+ 'ext': {
+ 'rtiPartner': 'ID5ID'
+ }
+ }]
+ }]);
+ });
+
+ it('build request with unified Id', function () {
+ const bidRequestClone = utils.deepClone(bidRequest);
+ bidRequestClone.userId = {};
+ bidRequestClone.userId.tdid = 'tdid-user-id';
+ let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0];
+ expect(request.data.user.ext.eids).to.deep.equal([{
+ 'source': 'adserver.org',
+ 'uids': [{
+ 'id': 'tdid-user-id',
+ 'ext': {
+ 'rtiPartner': 'TDID'
+ }
+ }]
+ }]);
+ });
+ });
+
+ describe('interpretResponse', () => {
+ const bannerBidRequest = {
+ 'adUnitCode': 'adunit-code',
+ 'auctionId': '1d1a030790a475',
+ 'mediaTypes': {
+ banner: {}
+ },
+ 'params': {
+ 'supplyPartnerId': supplyPartnerId
+ },
+ 'sizes': [[300, 250], [300, 600]],
+ 'transactionId': 'a123456789',
+ 'bidId': '111',
+ refererInfo: {referer: 'http://examplereferer.com'}
+ };
+
+ const videoBidRequest = {
+ 'adUnitCode': 'adunit-code',
+ 'auctionId': '1d1a030790a475',
+ 'mediaTypes': {
+ video: {}
+ },
+ 'params': {
+ 'supplyPartnerId': supplyPartnerId
+ },
+ 'sizes': [[300, 250], [300, 600]],
+ 'transactionId': 'a123456789',
+ 'bidId': '111',
+ refererInfo: {referer: 'http://examplereferer.com'}
+ };
+
+ const rtbResponse = {
+ 'id': 'imp_5b05b9fde4b09084267a556f',
+ 'bidid': 'imp_5b05b9fde4b09084267a556f',
+ 'cur': 'USD',
+ 'ext': {
+ 'utrk': [
+ {'type': 'iframe', 'url': '//rtb.gamoshi.io/user/sync/1'},
+ {'type': 'image', 'url': '//rtb.gamoshi.io/user/sync/2'}
+ ]
+ },
+ 'seatbid': [
+ {
+ 'seat': 'seat1',
+ 'group': 0,
+ 'bid': [
+ {
+ 'id': '0',
+ 'impid': '1',
+ 'price': 2.016,
+ 'adid': '579ef31bfa788b9d2000d562',
+ 'nurl': 'https://rtb.gamoshi.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0',
+ 'adm': '↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵↵',
+ 'adomain': ['aaa.com'],
+ 'cid': '579ef268fa788b9d2000d55c',
+ 'crid': '579ef31bfa788b9d2000d562',
+ 'attr': [],
+ 'h': 600,
+ 'w': 120,
+ 'ext': {
+ 'vast_url': 'http://my.vast.com',
+ 'utrk': [
+ {'type': 'iframe', 'url': '//p.partner1.io/user/sync/1'}
+ ]
+ }
+ }
+ ]
+ },
+ {
+ 'seat': 'seat2',
+ 'group': 0,
+ 'bid': [
+ {
+ 'id': '1',
+ 'impid': '1',
+ 'price': 3,
+ 'adid': '542jlhdfd2112jnjf3x',
+ 'nurl': 'https://rtb.gamoshi.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0',
+ 'adm': ' ',
+ 'adomain': ['bbb.com'],
+ 'cid': 'fgdlwjh2498ydjhg1',
+ 'crid': 'kjh34297ydh2133d',
+ 'attr': [],
+ 'h': 250,
+ 'w': 300,
+ 'ext': {
+ 'utrk': [
+ {'type': 'image', 'url': '//p.partner2.io/user/sync/1'}
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ };
+
+ const TTL = 360;
+
+ it('returns an empty array on missing response', function () {
+ let response;
+
+ response = spec.interpretResponse(undefined, {bidRequest: bannerBidRequest});
+ expect(Array.isArray(response)).to.equal(true);
+ expect(response.length).to.equal(0);
+
+ response = spec.interpretResponse({}, {bidRequest: bannerBidRequest});
+ expect(Array.isArray(response)).to.equal(true);
+ expect(response.length).to.equal(0);
+ });
+
+ it('aggregates banner bids from all seat bids', function () {
+ const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: bannerBidRequest});
+ expect(Array.isArray(response)).to.equal(true);
+ expect(response.length).to.equal(1);
+ const ad0 = response[0];
+ expect(ad0.requestId).to.equal(bannerBidRequest.bidId);
+ expect(ad0.cpm).to.equal(rtbResponse.seatbid[1].bid[0].price);
+ expect(ad0.width).to.equal(rtbResponse.seatbid[1].bid[0].w);
+ expect(ad0.height).to.equal(rtbResponse.seatbid[1].bid[0].h);
+ expect(ad0.ttl).to.equal(TTL);
+ expect(ad0.creativeId).to.equal(rtbResponse.seatbid[1].bid[0].crid);
+ expect(ad0.netRevenue).to.equal(true);
+ expect(ad0.currency).to.equal(rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD');
+ expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm);
+ expect(ad0.vastXml).to.be.an('undefined');
+ expect(ad0.vastUrl).to.be.an('undefined');
+ });
+
+ it('aggregates video bids from all seat bids', function () {
+ const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: videoBidRequest});
+ expect(Array.isArray(response)).to.equal(true);
+ expect(response.length).to.equal(1);
+ const ad0 = response[0];
+ expect(ad0.requestId).to.equal(videoBidRequest.bidId);
+ expect(ad0.cpm).to.equal(rtbResponse.seatbid[0].bid[0].price);
+ expect(ad0.width).to.equal(rtbResponse.seatbid[0].bid[0].w);
+ expect(ad0.height).to.equal(rtbResponse.seatbid[0].bid[0].h);
+ expect(ad0.ttl).to.equal(TTL);
+ expect(ad0.creativeId).to.equal(rtbResponse.seatbid[0].bid[0].crid);
+ expect(ad0.netRevenue).to.equal(true);
+ expect(ad0.currency).to.equal(rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD');
+ expect(ad0.ad).to.be.an('undefined');
+ expect(ad0.vastXml).to.equal(rtbResponse.seatbid[0].bid[0].adm);
+ expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[0].bid[0].ext.vast_url);
+ });
+
+ it('aggregates user-sync pixels', function () {
+ const response = spec.getUserSyncs({}, [{body: rtbResponse}]);
+ expect(Array.isArray(response)).to.equal(true);
+ expect(response.length).to.equal(4);
+ expect(response[0].type).to.equal(rtbResponse.ext.utrk[0].type);
+ expect(response[0].url).to.equal(rtbResponse.ext.utrk[0].url + '?gc=missing');
+ expect(response[1].type).to.equal(rtbResponse.ext.utrk[1].type);
+ expect(response[1].url).to.equal(rtbResponse.ext.utrk[1].url + '?gc=missing');
+ expect(response[2].type).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].type);
+ expect(response[2].url).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].url + '?gc=missing');
+ expect(response[3].type).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].type);
+ expect(response[3].url).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].url + '?gc=missing');
+ });
+
+ it('supports configuring outstream renderers', function () {
+ const videoResponse = {
+ 'id': '64f32497-b2f7-48ec-9205-35fc39894d44',
+ 'bidid': 'imp_5c24924de4b0d106447af333',
+ 'cur': 'USD',
+ 'seatbid': [
+ {
+ 'seat': '3668',
+ 'group': 0,
+ 'bid': [
+ {
+ 'id': 'gb_1',
+ 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23',
+ 'price': 5.0,
+ 'adid': '1274',
+ 'nurl': 'https://rtb.gamoshi.io/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1',
+ 'adomain': [],
+ 'adm': '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n',
+ 'cid': '3668',
+ 'crid': '1274',
+ 'cat': [],
+ 'attr': [],
+ 'h': 250,
+ 'w': 300,
+ 'ext': {
+ 'vast_url': 'https://rtb.gamoshi.io/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv',
+ 'imptrackers': [
+ 'https://rtb.gamoshi.io/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1']
+ }
+ }
+ ]
+ }
+ ],
+ 'ext': {
+ 'utrk': [{
+ 'type': 'image',
+ 'url': 'https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675'
+ }]
+ }
+ };
+ const videoRequest = utils.deepClone(videoBidRequest);
+ videoRequest.mediaTypes.video.context = 'outstream';
+ const result = spec.interpretResponse({body: videoResponse}, {bidRequest: videoRequest});
+ expect(result[0].renderer).to.not.equal(undefined);
+ });
+
+ it('validates in/existing of gdpr consent', function () {
+ let videoResponse = {
+ 'id': '64f32497-b2f7-48ec-9205-35fc39894d44',
+ 'bidid': 'imp_5c24924de4b0d106447af333',
+ 'cur': 'USD',
+ 'seatbid': [
+ {
+ 'seat': '3668',
+ 'group': 0,
+ 'bid': [
+ {
+ 'id': 'gb_1',
+ 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23',
+ 'price': 5.0,
+ 'adid': '1274',
+ 'nurl': 'https://rtb.gamoshi.io/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1',
+ 'adomain': [],
+ 'adm': '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n',
+ 'cid': '3668',
+ 'crid': '1274',
+ 'cat': [],
+ 'attr': [],
+ 'h': 250,
+ 'w': 300,
+ 'ext': {
+ 'vast_url': 'https://rtb.gamoshi.io/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv',
+ 'imptrackers': [
+ 'https://rtb.gamoshi.io/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1']
+ }
+ }
+ ]
+ }
+ ],
+ 'ext': {
+ 'utrk': [{
+ 'type': 'image',
+ 'url': 'https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675'
+ }]
+ }
+ };
+ let gdprConsent = {
+ gdprApplies: true,
+ consentString: 'consent string'
+ };
+ let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent);
+ expect(result).to.be.an('array');
+ expect(result.length).to.equal(1);
+ expect(result[0].type).to.equal('image');
+ expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gc=consent%20string');
+
+ gdprConsent.gdprApplies = false;
+ result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent);
+ expect(result).to.be.an('array');
+ expect(result.length).to.equal(1);
+ expect(result[0].type).to.equal('image');
+ expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gc=missing');
+
+ videoResponse.ext.utrk[0].url = 'https://rtb.gamoshi.io/pix/1275/scm';
+ result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent);
+ expect(result).to.be.an('array');
+ expect(result.length).to.equal(1);
+ expect(result[0].type).to.equal('image');
+ expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?gc=missing');
+ });
+ });
+});