From 68dfb641a2ee27d9b538910ed92f253a4f6e3685 Mon Sep 17 00:00:00 2001 From: mustafa kemal Date: Sat, 23 Feb 2019 04:14:03 +0300 Subject: [PATCH] New bid adapter for TheAdx (#3547) * theAdx BidAdapter module and test spec added * requested changes from reviewer --- modules/theAdxBidAdapter.js | 498 ++++++++++++++++ modules/theAdxBidAdapter.md | 91 +++ test/spec/modules/theAdxBidAdapter_spec.js | 642 +++++++++++++++++++++ 3 files changed, 1231 insertions(+) create mode 100644 modules/theAdxBidAdapter.js create mode 100644 modules/theAdxBidAdapter.md create mode 100644 test/spec/modules/theAdxBidAdapter_spec.js diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js new file mode 100644 index 00000000000..aeec32620ad --- /dev/null +++ b/modules/theAdxBidAdapter.js @@ -0,0 +1,498 @@ +import * as utils from '../src/utils'; +import { + BANNER, + NATIVE, + VIDEO +} from '../src/mediaTypes'; +import { + registerBidder +} from '../src/adapters/bidderFactory'; +import { + parse as parseUrl +} from '../src/url'; + +const BIDDER_CODE = 'theadx'; +const ENDPOINT_URL = '//ssp.theadx.com/request'; + +const NATIVEASSETNAMES = { + 0: 'title', + 1: 'cta', + 2: 'icon', + 3: 'image', + 4: 'body', + 5: 'sponsoredBy', + 6: 'body2', + 7: 'phone', + 8: 'privacyLink', + 9: 'displayurl', + 10: 'rating', + 11: 'address', + 12: 'downloads', + 13: 'likes', + 14: 'price', + 15: 'saleprice', + +}; +const NATIVEPROBS = { + title: { + id: 0, + name: 'title' + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + body2: { + id: 6, + name: 'data', + type: 10 + }, + privacyLink: { + id: 8, + name: 'data', + type: 501 + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + displayurl: { + id: 9, + name: 'data', + type: 11 + }, + cta: { + id: 1, + type: 12, + name: 'data' + }, + rating: { + id: 7, + name: 'data', + type: 3 + }, + address: { + id: 11, + name: 'data', + type: 5 + }, + downloads: { + id: 12, + name: 'data', + type: 5 + }, + likes: { + id: 13, + name: 'data', + type: 4 + }, + phone: { + id: 7, + name: 'data', + type: 8 + }, + price: { + id: 14, + name: 'data', + type: 6 + }, + saleprice: { + id: 15, + name: 'data', + type: 7 + }, + +}; + +export const spec = { + code: BIDDER_CODE, + aliases: ['theadx'], // short code + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + utils.logInfo('theadx.isBidRequestValid', bid); + let res = false; + if (bid && bid.params) { + res = !!(bid.params.pid && bid.params.tagId); + } + + return res; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + utils.logInfo('theadx.buildRequests', 'validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); + let results = []; + const requestType = 'POST'; + if (!utils.isEmpty(validBidRequests)) { + results = validBidRequests.map( + bidRequest => { + return { + method: requestType, + type: requestType, + url: `${ENDPOINT_URL}?tagid=${bidRequest.params.tagId}`, + options: { + withCredentials: true, + }, + bidder: 'theadx', + referrer: encodeURIComponent(bidderRequest.refererInfo.referer), + data: generatePayload(bidRequest, bidderRequest), + mediaTypes: bidRequest['mediaTypes'], + requestId: bidderRequest.bidderRequestId, + bidId: bidRequest.bidId, + adUnitCode: bidRequest['adUnitCode'], + auctionId: bidRequest['auctionId'], + }; + } + ); + } + return results; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse, request) => { + utils.logInfo('theadx.interpretResponse', 'serverResponse', serverResponse, ' request', request); + + let responses = []; + + if (serverResponse.body) { + let responseBody = serverResponse.body; + + let seatBids = responseBody.seatbid; + + if (!(utils.isEmpty(seatBids) || + utils.isEmpty(seatBids[0].bid))) { + let seatBid = seatBids[0]; + let bid = seatBid.bid[0]; + + // handle any values that may end up undefined + let nullify = (value) => typeof value === 'undefined' ? null : parseInt(value); + + let ttl = null; + if (bid.ext) { + ttl = nullify(bid.ext.ttl) ? nullify(bid.ext.ttl) : 2000; + } + + let bidWidth = nullify(bid.w); + let bidHeight = nullify(bid.h); + + let creative = null + let videoXml = null; + let mediaType = null; + let native = null; + + if (request.mediaTypes && request.mediaTypes.video) { + videoXml = bid.ext.vast_url; + mediaType = VIDEO; + } else if (request.mediaTypes && request.mediaTypes.banner) { + mediaType = BANNER; + creative = bid.adm; + } else if (request.mediaTypes && request.mediaTypes.native) { + mediaType = NATIVE; + const { + assets, + link, + imptrackers, + jstracker + } = bid.ext.native; + native = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || bid.ext.cliu ? [] : undefined, + impressionTrackers: imptrackers || bid.nurl ? [] : undefined, + javascriptTrackers: jstracker ? [jstracker] : undefined + }; + if (bid.nurl) { + native.impressionTrackers.unshift(bid.ext.impu); + native.impressionTrackers.unshift(bid.nurl); + if (native.clickTrackers) { + native.clickTrackers.unshift(bid.ext.cliu); + } + } + + assets.forEach(asset => { + const kind = NATIVEASSETNAMES[asset.id]; + const content = kind && asset[NATIVEPROBS[kind].name]; + if (content) { + native[kind] = content.text || content.value || { + url: content.url, + width: content.w, + height: content.h + }; + } + }); + } + + let response = { + bidderCode: BIDDER_CODE, + requestId: request.bidId, + cpm: bid.price, + width: bidWidth | 0, + height: bidHeight | 0, + ad: creative, + ttl: ttl || 3000, + creativeId: bid.crid, + netRevenue: true, + currency: responseBody.cur, + mediaType: mediaType, + native: native, + }; + if (mediaType == VIDEO && videoXml) { + response.vastUrl = videoXml; + response.videoCacheKey = bid.ext.rid; + } + + responses.push(response); + } + } + return responses; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses) { + utils.logInfo('theadx.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses) + const syncs = []; + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncs; + } + + serverResponses.forEach(resp => { + const syncIframeUrls = utils.deepAccess(resp, 'body.ext.sync.iframe'); + const syncImageUrls = utils.deepAccess(resp, 'body.ext.sync.image'); + if (syncOptions.iframeEnabled && syncIframeUrls) { + syncIframeUrls.forEach(syncIframeUrl => { + syncs.push({ + type: 'iframe', + url: syncIframeUrl + }); + }); + } + if (syncOptions.pixelEnabled && syncImageUrls) { + syncImageUrls.forEach(syncImageUrl => { + syncs.push({ + type: 'image', + url: syncImageUrl + }); + }); + } + }); + + return syncs; + }, + +} + +let buildSiteComponent = (bidRequest, bidderRequest) => { + let loc = parseUrl(bidderRequest.refererInfo.referer, { + decodeSearchAsString: true + }); + + let site = { + domain: loc.hostname, + page: loc.href, + id: bidRequest.params.wid, + publisher: { + id: bidRequest.params.pid, + } + }; + if (loc.search) { + site.search = loc.search; + } + if (document) { + let keywords = document.getElementsByTagName('meta')['keywords']; + if (keywords && keywords.content) { + site.keywords = keywords.content; + } + } + + return site; +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +let buildDeviceComponent = (bidRequest, bidderRequest) => { + let device = { + js: 1, + language: ('language' in navigator) ? navigator.language : null, + ua: ('userAgent' in navigator) ? navigator.userAgent : null, + devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2, + dnt: utils.getDNT() ? 1 : 0, + }; + // Include connection info if available + const CONNECTION = navigator.connection || navigator.webkitConnection; + if (CONNECTION && CONNECTION.type) { + device['connectiontype'] = CONNECTION.type; + if (CONNECTION.downlinkMax) { + device['connectionDownlinkMax'] = CONNECTION.downlinkMax; + } + } + + return device; +}; + +let determineOptimalRequestId = (bidRequest, bidderRequest) => { + return bidRequest.bidId; +} + +let extractValidSize = (bidRequest, bidderRequest) => { + let width = null; + let height = null; + + let requestedSizes = []; + let mediaTypes = bidRequest.mediaTypes; + if (mediaTypes && ((mediaTypes.banner && mediaTypes.banner.sizes) || (mediaTypes.video && mediaTypes.video.sizes))) { + if (mediaTypes.banner) { + requestedSizes = mediaTypes.banner.sizes; + } else { + requestedSizes = mediaTypes.video.sizes; + } + } else if (!utils.isEmpty(bidRequest.sizes)) { + requestedSizes = bidRequest.sizes + } + + // Ensure the size array is normalized + let conformingSize = utils.parseSizesInput(requestedSizes); + + if (!utils.isEmpty(conformingSize) && conformingSize[0] != null) { + // Currently only the first size is utilized + let splitSizes = conformingSize[0].split('x'); + + width = parseInt(splitSizes[0]); + height = parseInt(splitSizes[1]); + } + + return { + w: width, + h: height + }; +}; + +let generateVideoComponent = (bidRequest, bidderRequest) => { + let impSize = extractValidSize(bidRequest); + + return { + w: impSize.w, + h: impSize.h + } +} + +let generateBannerComponent = (bidRequest, bidderRequest) => { + let impSize = extractValidSize(bidRequest); + + return { + w: impSize.w, + h: impSize.h + } +} + +let generateNativeComponent = (bidRequest, bidderRequest) => { + const assets = utils._map(bidRequest.mediaTypes.native, (bidParams, key) => { + const props = NATIVEPROBS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + asset[props.name] = { + len: bidParams.len, + wmin: bidParams.sizes && bidParams.sizes[0], + hmin: bidParams.sizes && bidParams.sizes[1], + type: props.type + }; + + return asset; + } + }).filter(Boolean); + return { + request: { + assets + } + } +} + +let generateImpBody = (bidRequest, bidderRequest) => { + let mediaTypes = bidRequest.mediaTypes; + + let banner = null; + let video = null; + let native = null; + + if (mediaTypes && mediaTypes.video) { + video = generateVideoComponent(bidRequest, bidderRequest); + } else if (mediaTypes && mediaTypes.banner) { + banner = generateBannerComponent(bidRequest, bidderRequest); + } else if (mediaTypes && mediaTypes.native) { + native = generateNativeComponent(bidRequest, bidderRequest); + } + + const result = { + id: bidRequest.index, + tagid: bidRequest.params.tagId + '', + }; + if (banner) { + result['banner'] = banner; + } + if (video) { + result['video'] = video; + } + if (native) { + result['native'] = native; + } + + return result; +} + +let generatePayload = (bidRequest, bidderRequest) => { + // Generate the expected OpenRTB payload + + let payload = { + id: determineOptimalRequestId(bidRequest, bidderRequest), + site: buildSiteComponent(bidRequest, bidderRequest), + device: buildDeviceComponent(bidRequest, bidderRequest), + imp: [generateImpBody(bidRequest, bidderRequest)], + }; + // return payload; + return JSON.stringify(payload); +}; + +registerBidder(spec); diff --git a/modules/theAdxBidAdapter.md b/modules/theAdxBidAdapter.md new file mode 100644 index 00000000000..2392bfaa819 --- /dev/null +++ b/modules/theAdxBidAdapter.md @@ -0,0 +1,91 @@ +# Overview + +``` +Module Name: TheAdx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@theadx.com +``` + +# Description + +Module that connects to TheAdx demand sources + +# Test Parameters + +``` + var adUnits = [ + { + code: 'test-div', + sizes: [640,480], + mediaTypes: { + video: { + sizes: [[640, 480]], + } + }, + bids: [ + { + bidder: "theadx", + params: { + pid: 1000, // publisher id + wid: 2000, //website id + tagId: 5000, //zone id + } + } + ] + },{ + code: 'test-div2', + mediaTypes: { + banner: { + sizes: [[320, 50]], + }, + }, + bids: [ + { + bidder: "theadx", + params: { + pid: 1000, // publisher id + wid: 2000, //website id + tagId: 5000, //zone id + } + } + ] + },{ + code: 'test-div3', + mediaTypes: { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + } + }, + }, + bids: [ + { + bidder: "theadx", + params: { + pid: 1000, // publisher id + wid: 2000, //website id + tagId: 5000, //zone id + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/theAdxBidAdapter_spec.js b/test/spec/modules/theAdxBidAdapter_spec.js new file mode 100644 index 00000000000..5381709369c --- /dev/null +++ b/test/spec/modules/theAdxBidAdapter_spec.js @@ -0,0 +1,642 @@ +import { + expect +} from 'chai'; +import { + spec, + internals +} from 'modules/theAdxBidAdapter'; +import { + newBidder +} from 'src/adapters/bidderFactory'; + +describe('TheAdxAdapter', function () { + const adapter = newBidder(spec); + + describe('getUserSyncs', () => { + const USER_SYNC_IFRAME_URL = '//ssp.theadx.com/async_usersync_iframe.html' + const USER_SYNC_IMAGE_URL = '//ssp.theadx.com/async_usersync_image.gif' + + expect(spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true, + }, [{ + body: { + ext: { + sync: { + iframe: [USER_SYNC_IFRAME_URL], + image: [USER_SYNC_IMAGE_URL], + } + } + } + }])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + }, + { + type: 'image', + url: USER_SYNC_IMAGE_URL + }, + ]); + }); + + describe('bid validator', function () { + it('rejects a bid that is missing the placementId', function () { + let testBid = {}; + expect(spec.isBidRequestValid(testBid)).to.be.false; + }); + + it('accepts a bid with all the expected parameters', function () { + let testBid = { + params: { + pid: '1', + tagId: '1', + } + }; + + expect(spec.isBidRequestValid(testBid)).to.be.true; + }); + }); + + describe('request builder', function () { + // Taken from the docs, so used as much as is valid + const sampleBidRequest = { + 'bidder': 'tests', + 'bidId': '51ef8751f9aead', + 'params': { + 'pid': '1', + 'tagId': '1', + }, + 'adUnitCode': 'div-gpt-ad-sample', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [ + [300, 250] + ], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'mediaTypes': { + banner: { + 'sizes': [ + [320, 50], + [300, 250], + [300, 600] + ] + } + } + }; + + const sampleBidderRequest = { + bidderRequestId: 'sample', + refererInfo: { + canonicalUrl: 'http://domain.com/to', + referer: 'http://domain.com/from' + } + } + + it('successfully generates a URL', function () { + const placementId = '1'; + + const bidRequests = [sampleBidRequest]; + + let results = spec.buildRequests(bidRequests, sampleBidderRequest); + let result = results.pop(); + + expect(result.url).to.not.be.undefined; + expect(result.url).to.not.be.null; + + expect(result.url).to.include('tagid=' + placementId); + }); + + it('uses the bidId id as the openRtb request ID', function () { + const bidId = '51ef8751f9aead'; + + let bidRequests = [ + sampleBidRequest + ]; + + let results = spec.buildRequests(bidRequests, sampleBidderRequest); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + expect(payload.id).to.equal(bidId); + }); + + it('generates the device payload as expected', function () { + let bidRequests = [ + sampleBidRequest + ]; + + let results = spec.buildRequests(bidRequests, sampleBidderRequest); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + let userData = payload.user; + + expect(userData).to.not.be.null; + }); + + it('generates multiple requests with single imp bodies', function () { + const SECOND_PLACEMENT_ID = '2'; + let firstBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + let secondBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + secondBidRequest.params.tagId = SECOND_PLACEMENT_ID; + + let bidRequests = [ + firstBidRequest, + secondBidRequest + ]; + + let results = spec.buildRequests(bidRequests, sampleBidderRequest); + + expect(results instanceof Array).to.be.true; + expect(results.length).to.equal(2); + + let firstRequest = results[0]; + + // Double encoded JSON + let firstPayload = JSON.parse(firstRequest.data); + + expect(firstPayload).to.not.be.null; + expect(firstPayload.imp).to.not.be.null; + expect(firstPayload.imp.length).to.equal(1); + + expect(firstRequest.url).to.not.be.null; + expect(firstRequest.url.indexOf('tagid=1')).to.be.gt(0); + + let secondRequest = results[1]; + + // Double encoded JSON + let secondPayload = JSON.parse(secondRequest.data); + + expect(secondPayload).to.not.be.null; + expect(secondPayload.imp).to.not.be.null; + expect(secondPayload.imp.length).to.equal(1); + + expect(secondRequest.url).to.not.be.null; + expect(secondRequest.url.indexOf(`tagid=${SECOND_PLACEMENT_ID}`)).to.be.gte(0); + }); + + it('generates a banner request as expected', function () { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + let results = spec.buildRequests([localBidRequest], sampleBidderRequest); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(320); + expect(bannerData.h).to.equal(50); + }); + + it('generates a banner request using a singular adSize instead of an array', function () { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + localBidRequest.sizes = [320, 50]; + localBidRequest.mediaTypes = { + banner: {} + }; + + let results = spec.buildRequests([localBidRequest], sampleBidderRequest); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(320); + expect(bannerData.h).to.equal(50); + }); + + it('fails gracefully on an invalid size', function () { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + localBidRequest.sizes = ['x', 'w']; + + localBidRequest.mediaTypes = { + banner: { + sizes: ['y', 'z'] + } + }; + + let results = spec.buildRequests([localBidRequest], sampleBidderRequest); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(null); + expect(bannerData.h).to.equal(null); + }); + + it('generates a video request as expected', function () { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + localBidRequest.mediaTypes = { + video: { + sizes: [ + [326, 256] + ] + } + }; + + let results = spec.buildRequests([localBidRequest], sampleBidderRequest); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.video).to.not.be.null; + + let videoData = firstImp.video; + expect(videoData.w).to.equal(326); + expect(videoData.h).to.equal(256); + }); + + it('generates a native request as expected', function () { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + localBidRequest.mediaTypes = { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + }, + } + }; + + let results = spec.buildRequests([localBidRequest], sampleBidderRequest); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.native).to.not.be.null; + }); + + it('propagates the mediaTypes object in the built request', function () { + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + localBidRequest.mediaTypes = { + video: {} + }; + + let results = spec.buildRequests([localBidRequest], sampleBidderRequest); + let result = results.pop(); + + let mediaTypes = result.mediaTypes; + + expect(mediaTypes).to.not.be.null; + expect(mediaTypes).to.not.be.undefined; + expect(mediaTypes.video).to.not.be.null; + expect(mediaTypes.video).to.not.be.undefined; + }); + }); + + describe('response interpreter', function () { + it('returns an empty array when no bids present', function () { + // an empty JSON body indicates no ad was found + + let result = spec.interpretResponse({ + body: '' + }, {}) + + expect(result).to.eql([]); + }); + + it('gracefully fails when a non-JSON body is present', function () { + let result = spec.interpretResponse({ + body: 'THIS IS NOT ' + }, {}) + + expect(result).to.eql([]); + }); + + it('returns a valid bid response on sucessful banner request', function () { + let incomingRequestId = 'XXtestingXX'; + let responsePrice = 3.14 + + let responseCreative = 'sample_creative&{FOR_COVARAGE}'; + + let responseCreativeId = '274'; + let responseCurrency = 'TRY'; + + let responseWidth = 300; + let responseHeight = 250; + let responseTtl = 213; + + let sampleResponse = { + id: '66043f5ca44ecd8f8769093b1615b2d9', + seatbid: [{ + bid: [{ + id: 'c21bab0e-7668-4d8f-908a-63e094c09197', + impid: '1', + price: responsePrice, + adid: responseCreativeId, + crid: responseCreativeId, + adm: responseCreative, + adomain: [ + 'www.domain.com' + ], + cid: '274', + attr: [], + w: responseWidth, + h: responseHeight, + ext: { + ttl: responseTtl + } + }], + seat: '201', + group: 0 + }], + bidid: 'c21bab0e-7668-4d8f-908a-63e094c09197', + cur: responseCurrency + }; + + let sampleRequest = { + bidId: incomingRequestId, + mediaTypes: { + banner: {} + }, + requestId: incomingRequestId + }; + let serverResponse = { + body: sampleResponse + } + let result = spec.interpretResponse(serverResponse, sampleRequest); + + expect(result.length).to.equal(1); + + let processedBid = result[0]; + + // expect(processedBid.requestId).to.equal(incomingRequestId); + expect(processedBid.cpm).to.equal(responsePrice); + expect(processedBid.width).to.equal(responseWidth); + expect(processedBid.height).to.equal(responseHeight); + expect(processedBid.ad).to.equal(responseCreative); + expect(processedBid.ttl).to.equal(responseTtl); + expect(processedBid.creativeId).to.equal(responseCreativeId); + expect(processedBid.netRevenue).to.equal(true); + expect(processedBid.currency).to.equal(responseCurrency); + }); + + it('returns an valid bid response on sucessful video request', function () { + let incomingRequestId = 'XXtesting-275XX'; + let responsePrice = 6 + let vast_url = 'http://theadx.com/vast?rid=a8ae0b48-a8db-4220-ba0c-7458f452b1f5&{FOR_COVARAGE}' + + let responseCreativeId = '1556'; + let responseCurrency = 'TRY'; + + let responseWidth = 284; + let responseHeight = 285; + let responseTtl = 286; + + let sampleResponse = { + id: '1234567890', + seatbid: [{ + bid: [{ + id: 'a8ae0b48-a8db-4220-ba0c-7458f452b1f5', + impid: '1', + price: responsePrice, + adid: responseCreativeId, + crid: responseCreativeId, + cid: '270', + attr: [], + w: responseWidth, + h: responseHeight, + ext: { + vast_url: vast_url, + ttl: responseTtl + } + }], + seat: '201', + group: 0 + }], + bidid: 'a8ae0b48-a8db-4220-ba0c-7458f452b1f5', + cur: 'TRY' + }; + + let sampleRequest = { + bidId: incomingRequestId, + mediaTypes: { + video: {} + }, + requestId: incomingRequestId + }; + + let result = spec.interpretResponse({ + body: sampleResponse + }, + sampleRequest + ); + + expect(result.length).to.equal(1); + + let processedBid = result[0]; + // expect(processedBid.requestId).to.equal(incomingRequestId); + expect(processedBid.cpm).to.equal(responsePrice); + expect(processedBid.width).to.equal(responseWidth); + expect(processedBid.height).to.equal(responseHeight); + expect(processedBid.ad).to.equal(null); + expect(processedBid.ttl).to.equal(responseTtl); + expect(processedBid.creativeId).to.equal(responseCreativeId); + expect(processedBid.netRevenue).to.equal(true); + expect(processedBid.currency).to.equal(responseCurrency); + expect(processedBid.vastUrl).to.equal(vast_url); + }); + + it('returns an valid bid response on sucessful native request', function () { + let incomingRequestId = 'XXtesting-275XX'; + let responsePrice = 6 + let nurl = 'https://app.theadx.com/ixc?rid=02aefd80-2df9-11e9-896d-d33384d77f5c&time=v-1549888312715&sp=1WzMjcRpeyk%3D'; + let linkUrl = 'https%3A%2F%2Fapp.theadx.com%2Fgclick%3Frid%3D02aefd80-2df9-11e9-896d-d33384d77f5c%26url%3Dhttps%253A%252F%252Fwww.theadx.com%252Ftr%252Fhedeflemeler' + let responseCreativeId = '1556'; + let responseCurrency = 'TRY'; + + let responseTtl = 286; + + let sampleResponse = { + id: '1234567890', + seatbid: [{ + bid: [{ + id: 'a8ae0b48-a8db-4220-ba0c-7458f452b1f5', + impid: '1', + nurl: nurl, + price: responsePrice, + adid: responseCreativeId, + crid: responseCreativeId, + cid: '270', + attr: [], + ext: { + ttl: responseTtl, + native: { + ver: 1, + link: { + url: linkUrl + }, + assets: [{ + id: 3, + img: { + url: '//ads.theadx.com/winwords/120/17508/154712307258.73.jpg', + h: 627, + w: 1200 + } + }, { + id: 0, + title: { + ext: 'SELF-MANAGED DSP' + } + }, { + id: 5, + data: { + value: 'Sponsored by Theadx' + } + }, { + id: 4, + data: { + value: 'Gerçek Zamanlı Self-Managed DSP ile kampanya oluşturmak ve yönetmek çok kolay ' + } + }, { + id: 2, + img: { + url: '//ads.theadx.com/winwords/120/17508/154712307258.74.png', + h: 128, + w: 128 + } + }] + }, + + rid: '02ac3e60-2df9-11e9-9d09-bba751e172da', + impu: 'https://ssp.theadx.com/ixc?rid=02ac3e60-2df9-11e9-9d09-bba751e172da&time=1549888312719&tid=1', + cliu: 'https://ssp.theadx.com/click?trid=02ac3e60-2df9-11e9-9d09-bba751e172da' + + } + }], + seat: '201', + group: 0 + }], + bidid: 'a8ae0b48-a8db-4220-ba0c-7458f452b1f5', + cur: 'TRY' + }; + + let sampleRequest = { + bidId: incomingRequestId, + mediaTypes: { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + } + + }, + }, + requestId: incomingRequestId + }; + + let result = spec.interpretResponse({ + body: sampleResponse + }, + sampleRequest + ); + + expect(result.length).to.equal(1); + + let processedBid = result[0]; + // expect(processedBid.requestId).to.equal(incomingRequestId); + expect(processedBid.cpm).to.equal(responsePrice); + expect(processedBid.width).to.equal(0); + expect(processedBid.height).to.equal(0); + expect(processedBid.ad).to.equal(null); + expect(processedBid.ttl).to.equal(responseTtl); + expect(processedBid.creativeId).to.equal(responseCreativeId); + expect(processedBid.netRevenue).to.equal(true); + expect(processedBid.currency).to.equal(responseCurrency); + expect(processedBid.native.impressionTrackers[0]).to.equal(nurl); + expect(processedBid.native.clickUrl).to.equal(linkUrl); + }); + }); +});