diff --git a/modules/sortableBidAdapter.js b/modules/sortableBidAdapter.js deleted file mode 100644 index 15246a10eab..00000000000 --- a/modules/sortableBidAdapter.js +++ /dev/null @@ -1,366 +0,0 @@ -import { _each, logError, isFn, isPlainObject, isNumber, isStr, deepAccess, parseUrl, _map, getUniqueIdentifierStr, createTrackPixelHtml } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { createEidsArray } from './userId/eids.js'; - -const BIDDER_CODE = 'sortable'; -const SERVER_URL = 'https://c.deployads.com'; - -function setAssetRequired(native, asset) { - if (native.required) { - asset.required = 1; - } - return asset; -} - -function buildNativeRequest(nativeMediaType) { - const assets = []; - const title = nativeMediaType.title; - if (title) { - assets.push(setAssetRequired(title, { - title: {len: title.len} - })); - } - const img = nativeMediaType.image; - if (img) { - assets.push(setAssetRequired(img, { - img: { - type: 3, // Main - wmin: 1, - hmin: 1 - } - })); - } - const icon = nativeMediaType.icon; - if (icon) { - assets.push(setAssetRequired(icon, { - img: { - type: 1, // Icon - wmin: 1, - hmin: 1 - } - })); - } - const body = nativeMediaType.body; - if (body) { - assets.push(setAssetRequired(body, {data: {type: 2}})); - } - const cta = nativeMediaType.cta; - if (cta) { - assets.push(setAssetRequired(cta, {data: {type: 12}})); - } - const sponsoredBy = nativeMediaType.sponsoredBy; - if (sponsoredBy) { - assets.push(setAssetRequired(sponsoredBy, {data: {type: 1}})); - } - - _each(assets, (asset, id) => asset.id = id); - return { - ver: '1', - request: JSON.stringify({ - ver: '1', - assets - }) - }; -} - -function tryParseNativeResponse(adm) { - let native = null; - try { - native = JSON.parse(adm); - } catch (e) { - logError('Sortable bid adapter unable to parse native bid response:\n\n' + e); - } - return native && native.native; -} - -function createImgObject(img) { - if (img.w || img.h) { - return { - url: img.url, - width: img.w, - height: img.h - }; - } else { - return img.url; - } -} - -function interpretNativeResponse(response) { - const native = {}; - if (response.link) { - native.clickUrl = response.link.url; - } - _each(response.assets, asset => { - switch (asset.id) { - case 1: - native.title = asset.title.text; - break; - case 2: - native.image = createImgObject(asset.img); - break; - case 3: - native.icon = createImgObject(asset.img); - break; - case 4: - native.body = asset.data.value; - break; - case 5: - native.cta = asset.data.value; - break; - case 6: - native.sponsoredBy = asset.data.value; - break; - } - }); - return native; -} - -function transformSyncs(responses, type, syncs) { - _each(responses, res => { - if (res.body && res.body.ext && res.body.ext.sync_dsps && res.body.ext.sync_dsps.length) { - _each(res.body.ext.sync_dsps, sync => { - if (sync[0] === type && sync[1]) { - syncs.push({type, url: sync[1]}); - } - }); - } - }); -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return bid.params.floor ? bid.params.floor : null; - } - - // MediaType and Size will automatically get set for us if the bid only has - // one media type or one size. - let floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE, VIDEO], - - isBidRequestValid: function(bid) { - const sortableConfig = config.getConfig('sortable'); - const haveSiteId = (sortableConfig && !!sortableConfig.siteId) || bid.params.siteId; - const floor = getBidFloor(bid); - const validFloor = !floor || isNumber(floor); - const validKeywords = !bid.params.keywords || - (isPlainObject(bid.params.keywords) && - Object.keys(bid.params.keywords).every(key => - isStr(key) && isStr(bid.params.keywords[key]) - )) - const isBanner = !bid.mediaTypes || bid.mediaTypes[BANNER] || !(bid.mediaTypes[NATIVE] || bid.mediaTypes[VIDEO]); - const bannerSizes = isBanner ? deepAccess(bid, `mediaType.${BANNER}.sizes`) || bid.sizes : null; - return !!(bid.params.tagId && haveSiteId && validFloor && validKeywords && (!isBanner || - (bannerSizes && bannerSizes.length > 0 && bannerSizes.every(sizeArr => sizeArr.length == 2 && sizeArr.every(num => isNumber(num)))))); - }, - - buildRequests: function(validBidReqs, bidderRequest) { - const sortableConfig = config.getConfig('sortable') || {}; - const globalSiteId = sortableConfig.siteId; - let loc = parseUrl(bidderRequest.refererInfo.referer); - - const sortableImps = _map(validBidReqs, bid => { - const rv = { - id: bid.bidId, - tagid: bid.params.tagId, - ext: {} - }; - const bannerMediaType = deepAccess(bid, `mediaTypes.${BANNER}`); - const nativeMediaType = deepAccess(bid, `mediaTypes.${NATIVE}`); - const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); - if (bannerMediaType || !(nativeMediaType || videoMediaType)) { - const bannerSizes = (bannerMediaType && bannerMediaType.sizes) || bid.sizes; - rv.banner = { - format: _map(bannerSizes, ([width, height]) => ({w: width, h: height})) - }; - } - if (nativeMediaType) { - rv.native = buildNativeRequest(nativeMediaType); - } - if (videoMediaType && videoMediaType.context === 'instream') { - const video = {placement: 1}; - video.mimes = videoMediaType.mimes || []; - video.minduration = deepAccess(bid, 'params.video.minduration') || 10; - video.maxduration = deepAccess(bid, 'params.video.maxduration') || 60; - const startDelay = deepAccess(bid, 'params.video.startdelay'); - if (startDelay != null) { - video.startdelay = startDelay; - } - if (videoMediaType.playerSize && videoMediaType.playerSize.length) { - const size = videoMediaType.playerSize[0]; - video.w = size[0]; - video.h = size[1]; - } - if (videoMediaType.api) { - video.api = videoMediaType.api; - } - if (videoMediaType.protocols) { - video.protocols = videoMediaType.protocols; - } - if (videoMediaType.playbackmethod) { - video.playbackmethod = videoMediaType.playbackmethod; - } - rv.video = video; - } - const floor = getBidFloor(bid); - if (floor) { - rv.floor = floor; - } - if (bid.params.keywords) { - rv.ext.keywords = bid.params.keywords; - } - if (bid.params.bidderParams) { - _each(bid.params.bidderParams, (params, partner) => { - rv.ext[partner] = params; - }); - } - rv.ext.gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - return rv; - }); - const gdprConsent = bidderRequest && bidderRequest.gdprConsent; - const bidUserId = validBidReqs[0].userId; - const eids = createEidsArray(bidUserId); - const sortableBidReq = { - id: getUniqueIdentifierStr(), - imp: sortableImps, - source: { - ext: { - schain: validBidReqs[0].schain - } - }, - regs: { - ext: {} - }, - site: { - domain: loc.hostname, - page: loc.href, - ref: loc.href, - publisher: { - id: globalSiteId || validBidReqs[0].params.siteId, - }, - device: { - w: screen.width, - h: screen.height - }, - }, - user: { - ext: {} - } - }; - if (bidderRequest && bidderRequest.timeout > 0) { - sortableBidReq.tmax = bidderRequest.timeout; - } - if (gdprConsent) { - sortableBidReq.user = { - ext: { - consent: gdprConsent.consentString - } - }; - if (typeof gdprConsent.gdprApplies == 'boolean') { - sortableBidReq.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0 - } - } - if (eids.length) { - sortableBidReq.user.ext.eids = eids; - } - if (bidderRequest.uspConsent) { - sortableBidReq.regs.ext.us_privacy = bidderRequest.uspConsent; - } - return { - method: 'POST', - url: `${SERVER_URL}/openrtb2/auction?src=$$REPO_AND_VERSION$$&host=${loc.hostname}`, - data: JSON.stringify(sortableBidReq), - options: {contentType: 'text/plain'} - }; - }, - - interpretResponse: function(serverResponse) { - const { body: {id, seatbid} } = serverResponse; - const sortableBids = []; - if (id && seatbid) { - _each(seatbid, seatbid => { - _each(seatbid.bid, bid => { - const bidObj = { - requestId: bid.impid, - cpm: parseFloat(bid.price), - width: parseInt(bid.w), - height: parseInt(bid.h), - creativeId: bid.crid || bid.id, - dealId: bid.dealid || null, - currency: 'USD', - netRevenue: true, - mediaType: BANNER, - ttl: 60, - meta: { - advertiserDomains: bid.adomain || [] - } - }; - if (bid.adm) { - const adFormat = deepAccess(bid, 'ext.ad_format') - if (adFormat === 'native') { - let native = tryParseNativeResponse(bid.adm); - if (!native) { - return; - } - bidObj.mediaType = NATIVE; - bidObj.native = interpretNativeResponse(native); - } else if (adFormat === 'instream') { - bidObj.mediaType = VIDEO; - bidObj.vastXml = bid.adm; - } else { - bidObj.mediaType = BANNER; - bidObj.ad = bid.adm; - if (bid.nurl) { - bidObj.ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } - } - } else if (bid.nurl) { - bidObj.adUrl = bid.nurl; - } - if (bid.ext) { - bidObj[BIDDER_CODE] = bid.ext; - } - sortableBids.push(bidObj); - }); - }); - } - return sortableBids; - }, - - getUserSyncs: (syncOptions, responses) => { - const syncs = []; - if (syncOptions.iframeEnabled) { - transformSyncs(responses, 'iframe', syncs); - } - if (syncOptions.pixelEnabled) { - transformSyncs(responses, 'image', syncs); - } - return syncs; - }, - - onTimeout(details) { - fetch(`${SERVER_URL}/prebid/timeout`, { - method: 'POST', - body: JSON.stringify(details), - mode: 'no-cors', - headers: new Headers({ - 'Content-Type': 'text/plain' - }) - }); - } -}; - -registerBidder(spec); diff --git a/modules/sortableBidAdapter.md b/modules/sortableBidAdapter.md deleted file mode 100644 index c24ad85b752..00000000000 --- a/modules/sortableBidAdapter.md +++ /dev/null @@ -1,109 +0,0 @@ -# Overview - -``` -Module Name: Sortable Bid Adapter -Module Type: Bidder Adapter -Maintainer: prebid@sortable.com -``` - -# Description - -Sortable's adapter integration to the Prebid library. Posts plain-text JSON to the /openrtb2/auction endpoint. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-pb-leaderboard', - mediaTypes: { - banner: { - sizes: [[728, 90]], - } - }, - bids: [{ - bidder: 'sortable', - params: { - tagId: 'test-pb-leaderboard', - siteId: 'prebid.example.com', - 'keywords': { - 'key1': 'val1', - 'key2': 'val2' - } - } - }] - }, { - code: 'test-pb-banner', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [{ - bidder: 'sortable', - params: { - tagId: 'test-pb-banner', - siteId: 'prebid.example.com' - } - }] - }, { - code: 'test-pb-sidebar', - mediaTypes: { - banner: { - sizes: [[160, 600]], - } - }, - bids: [{ - bidder: 'sortable', - params: { - tagId: 'test-pb-sidebar', - siteId: 'prebid.example.com', - 'keywords': { - 'keyA': 'valA' - } - } - }] - }, { - code: 'test-pb-native', - mediaTypes: { - native: { - title: { - required: true, - len: 800 - }, - image: { - required: true, - sizes: [790, 294], - }, - sponsoredBy: { - required: true - } - } - }, - bids: [{ - bidder: 'sortable', - params: { - tagId: 'test-pb-native', - siteId: 'prebid.example.com' - } - }] - }, { - code: 'test-pb-video', - mediaTypes: { - video: { - playerSize: [640,480], - context: 'instream' - } - }, - bids: [ - { - bidder: 'sortable', - params: { - tagId: 'test-pb-video', - siteId: 'prebid.example.com' - } - } - ] - } -] -``` diff --git a/test/spec/modules/sortableBidAdapter_spec.js b/test/spec/modules/sortableBidAdapter_spec.js deleted file mode 100644 index 7357fa77952..00000000000 --- a/test/spec/modules/sortableBidAdapter_spec.js +++ /dev/null @@ -1,547 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/sortableBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as utils from 'src/utils.js'; - -describe('sortableBidAdapter', function() { - const adapter = newBidder(spec); - - describe('isBidRequestValid', function () { - function makeBid() { - return { - 'bidder': 'sortable', - 'params': { - 'tagId': '403370', - 'siteId': 'example.com', - 'keywords': { - 'key1': 'val1', - 'key2': 'val2' - } - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - } - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(makeBid())).to.equal(true); - }); - - it('should return false when tagId not passed correctly', function () { - let bid = makeBid(); - delete bid.params.tagId; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when sizes not passed correctly', function () { - let bid = makeBid(); - delete bid.sizes; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when sizes are wrong length', function () { - let bid = makeBid(); - bid.sizes = [[300]]; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when sizes are empty', function () { - let bid = makeBid(); - bid.sizes = []; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when require params are not passed', function () { - let bid = makeBid(); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when the keywords are invalid', function () { - let bid = makeBid(); - bid.params.keywords = { - 'badval': 1234 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - bid.params.keywords = 'a'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return true when the keywords are missing or empty', function () { - let bid = makeBid(); - bid.params.keywords = {}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.params.keywords; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return true with video media type', () => { - const videoBid = { - 'bidder': 'sortable', - 'params': { - 'tagId': '403370', - 'siteId': 'example.com', - }, - 'adUnitCode': 'adunit-code', - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'mediaTypes': { - 'video': { - } - } - }; - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); - }); - - describe('buildRequests', function () { - const bidRequests = [{ - 'bidder': 'sortable', - 'params': { - 'tagId': '403370', - 'siteId': 'example.com', - 'floor': 0.21, - 'keywords': { - 'key1': 'val1', - 'key2': 'val2' - } - }, - 'ortb2Imp': { - 'ext': { - 'data': { - 'pbadslot': 'abc/123' - } - } - }, - 'sizes': [ - [300, 250] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }, { - 'bidder': 'sortable', - 'params': { - 'tagId': '403371', - 'siteId': 'example.com', - 'floor': 0.21 - }, - 'sizes': [ - [300, 250] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'mediaTypes': { - 'native': { - 'body': {'required': true, 'sendId': true}, - 'clickUrl': {'required': true, 'sendId': true}, - 'cta': {'required': true, 'sendId': true}, - 'icon': {'required': true, 'sendId': true}, - 'image': {'required': true, 'sendId': true}, - 'sponsoredBy': {'required': true, 'sendId': true}, - 'title': {'required': true, 'sendId': true, 'len': 100} - } - } - }]; - - const request = spec.buildRequests(bidRequests, {refererInfo: { referer: 'http://example.com/page?param=val' }}); - const requestBody = JSON.parse(request.data); - - it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); - - it('attaches source and version to endpoint URL as query params', function () { - const ENDPOINT = `https://c.deployads.com/openrtb2/auction?src=$$REPO_AND_VERSION$$&host=example.com`; - expect(request.url).to.equal(ENDPOINT); - }); - - it('sends screen dimensions', function () { - expect(requestBody.site.device.w).to.equal(screen.width); - expect(requestBody.site.device.h).to.equal(screen.height); - }); - - it('includes the ad size in the bid request', function () { - expect(requestBody.imp[0].banner.format[0].w).to.equal(300); - expect(requestBody.imp[0].banner.format[0].h).to.equal(250); - }); - - it('includes the params in the bid request', function () { - expect(requestBody.imp[0].ext.keywords).to.deep.equal( - {'key1': 'val1', - 'key2': 'val2'} - ); - expect(requestBody.site.publisher.id).to.equal('example.com'); - expect(requestBody.imp[0].tagid).to.equal('403370'); - expect(requestBody.imp[0].floor).to.equal(0.21); - }); - - it('includes pbadslot in the bid request', function () { - expect(requestBody.imp[0].ext.gpid).to.equal('abc/123'); - }); - - it('sets domain and href correctly', function () { - expect(requestBody.site.domain).to.equal('example.com'); - expect(requestBody.site.page).to.equal('http://example.com/page?param=val'); - }); - - it('should have the version in native object set for native bid', function() { - expect(requestBody.imp[1].native.ver).to.equal('1'); - }); - - it('should have the assets set for native bid', function() { - const assets = JSON.parse(requestBody.imp[1].native.request).assets; - expect(assets[0]).to.deep.equal({'title': {'len': 100}, 'required': 1, 'id': 0}); - expect(assets[1]).to.deep.equal({'img': {'type': 3, 'wmin': 1, 'hmin': 1}, 'required': 1, 'id': 1}); - expect(assets[2]).to.deep.equal({'img': {'type': 1, 'wmin': 1, 'hmin': 1}, 'required': 1, 'id': 2}); - expect(assets[3]).to.deep.equal({'data': {'type': 2}, 'required': 1, 'id': 3}); - expect(assets[4]).to.deep.equal({'data': {'type': 12}, 'required': 1, 'id': 4}); - expect(assets[5]).to.deep.equal({'data': {'type': 1}, 'required': 1, 'id': 5}); - }); - - const videoBidRequests = [{ - 'bidder': 'sortable', - 'params': { - 'tagId': '403370', - 'siteId': 'example.com', - 'video': { - 'minduration': 5, - 'maxduration': 10, - 'startdelay': 0 - } - }, - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'mediaTypes': { - 'video': { - 'context': 'instream', - 'mimes': ['video/x-ms-wmv'], - 'playerSize': [[400, 300]], - 'api': [0], - 'protocols': [2, 3], - 'playbackmethod': [1] - } - } - }]; - - const videoRequest = spec.buildRequests(videoBidRequests, {refererInfo: { referer: 'http://localhost:9876/' }}); - const videoRequestBody = JSON.parse(videoRequest.data); - - it('should include video params', () => { - const video = videoRequestBody.imp[0].video; - expect(video.mimes).to.deep.equal(['video/x-ms-wmv']); - expect(video.w).to.equal(400); - expect(video.h).to.equal(300); - expect(video.api).to.deep.equal([0]); - expect(video.protocols).to.deep.equal([2, 3]); - expect(video.playbackmethod).to.deep.equal([1]); - expect(video.minduration).to.equal(5); - expect(video.maxduration).to.equal(10); - expect(video.startdelay).to.equal(0); - }); - - it('sets domain and href correctly', function () { - expect(videoRequestBody.site.domain).to.equal('localhost'); - expect(videoRequestBody.site.page).to.equal('http://localhost:9876/'); - }); - - const gdprBidRequests = [{ - 'bidder': 'sortable', - 'params': { - 'tagId': '403370', - 'siteId': 'example.com', - 'floor': 0.21, - 'keywords': {} - }, - 'sizes': [ - [300, 250] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - const consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; - - function getGdprRequestBody(gdprApplies, consentString) { - const gdprRequest = spec.buildRequests(gdprBidRequests, {'gdprConsent': { - 'gdprApplies': gdprApplies, - 'consentString': consentString - }, - refererInfo: { - referer: 'http://localhost:9876/' - }}); - return JSON.parse(gdprRequest.data); - } - - it('should handle gdprApplies being present and true', function() { - const gdprRequestBody = getGdprRequestBody(true, consentString); - expect(gdprRequestBody.regs.ext.gdpr).to.equal(1); - expect(gdprRequestBody.user.ext.consent).to.equal(consentString); - }) - - it('should handle gdprApplies being present and false', function() { - const gdprRequestBody = getGdprRequestBody(false, consentString); - expect(gdprRequestBody.regs.ext.gdpr).to.equal(0); - expect(gdprRequestBody.user.ext.consent).to.equal(consentString); - }) - - it('should handle gdprApplies being undefined', function() { - const gdprRequestBody = getGdprRequestBody(undefined, consentString); - expect(gdprRequestBody.regs).to.deep.equal({ext: {}}); - expect(gdprRequestBody.user.ext.consent).to.equal(consentString); - }) - - it('should handle gdprConsent being undefined', function() { - const gdprRequest = spec.buildRequests(gdprBidRequests, {refererInfo: { referer: 'http://localhost:9876/' }}); - const gdprRequestBody = JSON.parse(gdprRequest.data); - expect(gdprRequestBody.regs).to.deep.equal({ext: {}}); - expect(gdprRequestBody.user.ext.consent).to.equal(undefined); - }) - - const eidsBidRequests = [{ - 'bidder': 'sortable', - 'params': { - 'tagId': '403370', - 'siteId': 'example.com', - 'floor': 0.21, - 'keywords': {} - }, - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - - it('should not set user ids when none present', function() { - const eidsRequest = spec.buildRequests(eidsBidRequests, {refererInfo: { - referer: 'http://localhost:9876/' - }}); - const eidsRequestBody = JSON.parse(eidsRequest.data); - - expect(eidsRequestBody.user.ext.eids).to.equal(undefined); - }) - - it('should set user ids when present', function() { - eidsBidRequests[0].userId = { criteoId: 'sample-userid' }; - const eidsRequest = spec.buildRequests(eidsBidRequests, {refererInfo: { - referer: 'http://localhost:9876/' - }}); - const eidsRequestBody = JSON.parse(eidsRequest.data); - - expect(eidsRequestBody.user.ext.eids.length).to.equal(1); - }) - }); - - describe('interpretResponse', function () { - function makeResponse() { - return { - body: { - 'id': '5e5c23a5ba71e78', - 'seatbid': [ - { - 'bid': [ - { - 'id': '6vmb3isptf', - 'crid': 'sortablescreative', - 'impid': '322add653672f68', - 'price': 1.22, - 'adm': '', - 'attr': [5], - 'h': 90, - 'nurl': 'http://nurl', - 'w': 728 - } - ], - 'seat': 'MOCK' - } - ], - 'bidid': '5e5c23a5ba71e78' - } - }; - } - - function makeNativeResponse() { - return { - body: { - 'id': '5e5c23a5ba71e77', - 'seatbid': [ - { - 'bid': [ - { - 'id': '6vmb3isptf', - 'crid': 'sortablescreative', - 'impid': '322add653672f67', - 'price': 1.55, - 'adm': '{"native":{"link":{"clicktrackers":[],"url":"https://www.sortable.com/"},"assets":[{"title":{"text":"Ads With Sortable"},"id":1},{"img":{"w":790,"url":"https://path.to/image","h":294},"id":2},{"img":{"url":"https://path.to/icon"},"id":3},{"data":{"value":"Body here"},"id":4},{"data":{"value":"Learn More"},"id":5},{"data":{"value":"Sortable"},"id":6}],"imptrackers":[],"ver":1}}', - 'ext': {'ad_format': 'native'}, - 'h': 90, - 'nurl': 'http://nurl', - 'w': 728 - } - ], - 'seat': 'MOCK' - } - ], - 'bidid': '5e5c23a5ba71e77' - } - }; - } - - const expectedBid = { - 'requestId': '322add653672f68', - 'cpm': 1.22, - 'width': 728, - 'height': 90, - 'creativeId': 'sortablescreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'meta': { 'advertiserDomains': [] }, - 'ttl': 60, - 'ad': '
' - }; - - const expectedNativeBid = { - 'requestId': '322add653672f67', - 'cpm': 1.55, - 'width': 728, - 'height': 90, - 'creativeId': 'sortablescreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'sortable': { 'ad_format': 'native' }, - 'mediaType': 'native', - 'meta': { 'advertiserDomains': [] }, - 'ttl': 60, - 'native': { - 'clickUrl': 'https://www.sortable.com/', - 'title': 'Ads With Sortable', - 'image': {'url': 'https://path.to/image', 'height': 294, 'width': 790}, - 'icon': 'https://path.to/icon', - 'body': 'Body here', - 'cta': 'Learn More', - 'sponsoredBy': 'Sortable' - } - }; - - it('should get the correct bid response', function () { - let result = spec.interpretResponse(makeResponse()); - expect(result.length).to.equal(1); - expect(result[0]).to.deep.equal(expectedBid); - }); - - it('should handle a missing crid', function () { - let noCridResponse = makeResponse(); - delete noCridResponse.body.seatbid[0].bid[0].crid; - const fallbackCrid = noCridResponse.body.seatbid[0].bid[0].id; - let noCridResult = Object.assign({}, expectedBid, {'creativeId': fallbackCrid}); - let result = spec.interpretResponse(noCridResponse); - expect(result.length).to.equal(1); - expect(result[0]).to.deep.equal(noCridResult); - }); - - it('should handle a missing nurl', function () { - let noNurlResponse = makeResponse(); - delete noNurlResponse.body.seatbid[0].bid[0].nurl; - let noNurlResult = Object.assign({}, expectedBid); - noNurlResult.ad = ''; - let result = spec.interpretResponse(noNurlResponse); - expect(result.length).to.equal(1); - expect(result[0]).to.deep.equal(noNurlResult); - }); - - it('should handle a missing adm', function () { - let noAdmResponse = makeResponse(); - delete noAdmResponse.body.seatbid[0].bid[0].adm; - let noAdmResult = Object.assign({}, expectedBid); - delete noAdmResult.ad; - noAdmResult.adUrl = 'http://nurl'; - let result = spec.interpretResponse(noAdmResponse); - expect(result.length).to.equal(1); - expect(result[0]).to.deep.equal(noAdmResult); - }); - - it('handles empty bid response', function () { - let response = { - body: { - 'id': '5e5c23a5ba71e78', - 'seatbid': [] - } - }; - let result = spec.interpretResponse(response); - expect(result.length).to.equal(0); - }); - - it('should get the correct native bid response', function () { - let result = spec.interpretResponse(makeNativeResponse()); - expect(result.length).to.equal(1); - expect(result[0]).to.deep.equal(expectedNativeBid); - }); - - it('fail to parse invalid native bid response', function () { - let response = makeNativeResponse(); - response.body.seatbid[0].bid[0].adm = ''; - let result = spec.interpretResponse(response); - expect(result.length).to.equal(0); - }); - - it('should keep custom properties', () => { - const customProperties = {test: 'a test message', param: {testParam: 1}}; - const expectedResult = Object.assign({}, expectedBid, {[spec.code]: customProperties}); - const response = makeResponse(); - response.body.seatbid[0].bid[0].ext = customProperties; - const result = spec.interpretResponse(response); - expect(result.length).to.equal(1); - expect(result[0]).to.deep.equal(expectedResult); - }); - - it('should handle instream response', () => { - const response = makeResponse(); - const bid = response.body.seatbid[0].bid[0]; - delete bid.nurl; - bid.ext = {ad_format: 'instream'}; - const result = spec.interpretResponse(response)[0]; - expect(result.mediaType).to.equal('video'); - expect(result.vastXml).to.equal(bid.adm); - }); - - it('should return iframe syncs', () => { - const syncResponse = { - ext: { - sync_dsps: [ - ['iframe', 'http://example-dsp/sync-iframe'], - ['image', 'http://example-dsp/sync-image'] - ] - } - }; - expect(spec.getUserSyncs({iframeEnabled: true}, [{body: syncResponse}])).to.deep.equal([{ - type: 'iframe', - url: 'http://example-dsp/sync-iframe' - }]); - }); - - it('should return image syncs', () => { - const syncResponse = { - ext: { - sync_dsps: [ - ['iframe', 'http://example-dsp/sync-iframe'], - ['image', 'http://example-dsp/sync-image'] - ] - } - }; - expect(spec.getUserSyncs({pixelEnabled: true}, [{body: syncResponse}])).to.deep.equal([{ - type: 'image', - url: 'http://example-dsp/sync-image' - }]); - }); - }); -});