diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js new file mode 100644 index 000000000000..a7a99ad3056b --- /dev/null +++ b/modules/ixBidAdapter.js @@ -0,0 +1,255 @@ +import * as utils from 'src/utils'; +import { BANNER } from 'src/mediaTypes'; +import { config } from 'src/config'; +import isArray from 'core-js/library/fn/array/is-array'; +import isInteger from 'core-js/library/fn/number/is-integer'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'ix'; +const BANNER_SECURE_BID_URL = 'https://as-sec.casalemedia.com/cygnus'; +const BANNER_INSECURE_BID_URL = 'http://as.casalemedia.com/cygnus'; +const SUPPORTED_AD_TYPES = [BANNER]; +const ENDPOINT_VERSION = 7.2; +const CENT_TO_DOLLAR_FACTOR = 100; +const TIME_TO_LIVE = 60; +const NET_REVENUE = true; +const isSecureWeb = utils.getTopWindowLocation().protocol === 'https:'; +const baseUrl = isSecureWeb ? BANNER_SECURE_BID_URL : BANNER_INSECURE_BID_URL; +const PRICE_TO_DOLLAR_FACTOR = { + JPY: 1 +}; + +/** + * Transform valid bid request config object to impression object that will be sent to ad server. + * + * @param {object} bid A valid bid request config object. + * @return {object} A impression object that will be sent to ad server. + */ +function bidToBannerImp(bid) { + const imp = {}; + + imp.id = bid.bidId; + + imp.banner = {}; + imp.banner.w = bid.params.size[0]; + imp.banner.h = bid.params.size[1]; + imp.banner.topframe = utils.inIframe() ? 0 : 1; + + imp.ext = {}; + imp.ext.sid = `${bid.params.size[0]}x${bid.params.size[1]}`; + imp.ext.siteID = bid.params.siteId; + + if (bid.params.hasOwnProperty('bidFloor') && bid.params.hasOwnProperty('bidFloorCur')) { + imp.bidfloor = bid.params.bidFloor; + imp.bidfloorcur = bid.params.bidFloorCur; + } + + return imp; +} + +/** + * Parses a raw bid for the relevant information. + * + * @param {object} rawBid The bid to be parsed. + * @param {string} currency Global currency in bid response. + * @return {object} bid The parsed bid. + */ +function parseBid(rawBid, currency) { + const bid = {}; + + if (PRICE_TO_DOLLAR_FACTOR.hasOwnProperty(currency)) { + bid.cpm = rawBid.price / PRICE_TO_DOLLAR_FACTOR[currency]; + } else { + bid.cpm = rawBid.price / CENT_TO_DOLLAR_FACTOR; + } + + bid.requestId = rawBid.impid; + bid.width = rawBid.w; + bid.height = rawBid.h; + bid.ad = rawBid.adm; + bid.dealId = utils.deepAccess(rawBid, 'ext.dealid'); + bid.ttl = TIME_TO_LIVE; + bid.netRevenue = NET_REVENUE; + bid.currency = currency; + bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; + + return bid; +} + +/** + * Determines whether or not the given object is valid size format. + * + * @param {*} size The object to de validated. + * @return {boolean} True if this is a valid size format, and false otherwise. + */ +function isValidSize(size) { + return isArray(size) && size.length === 2 && isInteger(size[0]) && isInteger(size[1]); +} + +/** + * Determines whether or not the given size object is an element of the size array. + * + * @param {array} sizeArray The size array. + * @param {object} size The size object. + * @return {boolean} True if the size object is an element of the size array, and false otherwise. + */ +function includesSize(sizeArray, size) { + if (isValidSize(sizeArray)) { + return sizeArray[0] === size[0] && sizeArray[1] === size[1]; + } + + for (let i = 0; i < sizeArray.length; i++) { + if (sizeArray[i][0] === size[0] && sizeArray[i][1] === size[1]) { + return true; + } + } + + return false; +} + +/** + * Determines whether or not the given bidFloor parameters are valid. + * + * @param {*} bidFloor The bidFloor parameter inside bid request config. + * @param {*} bidFloorCur The bidFloorCur parameter inside bid request config. + * @return {boolean} True if this is a valid biFfloor parameters format, and false otherwise. + */ +function isValidBidFloorParams(bidFloor, bidFloorCur) { + const curRegex = /^[A-Z]{3}$/; + + return Boolean(typeof bidFloor === 'number' && typeof bidFloorCur === 'string' + && bidFloorCur.match(curRegex)); +} + +export const spec = { + + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return {boolean} True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!isValidSize(bid.params.size)) { + return false; + } + + if (!includesSize(bid.sizes, bid.params.size)) { + return false; + } + + if (typeof bid.params.siteId !== 'string') { + return false; + } + + const hasBidFloor = bid.params.hasOwnProperty('bidFloor'); + const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); + + if (hasBidFloor || hasBidFloorCur) { + return hasBidFloor && hasBidFloorCur + && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur); + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {array} validBidRequests A list of valid bid request config objects. + * @return {object} Info describing the request to the server. + */ + buildRequests: function (validBidRequests) { + const bannerImps = []; + let validBidRequest = null; + let bannerImp = null; + + for (let i = 0; i < validBidRequests.length; i++) { + validBidRequest = validBidRequests[i]; + + // If the bid request is for banner, then transform the bid request based on banner format + if (utils.deepAccess(validBidRequest, 'mediaTypes.banner') + || validBidRequest.mediaType === 'banner') { + bannerImp = bidToBannerImp(validBidRequest); + bannerImps.push(bannerImp); + } + } + + // Since bidderRequestId are the same for diffrent bid request, just use the first one + const r = {}; + r.id = validBidRequests[0].bidderRequestId; + r.imp = bannerImps; + r.site = {}; + r.site.page = utils.getTopWindowUrl(); + r.site.ref = utils.getTopWindowReferrer(); + r.ext = {}; + r.ext.source = 'prebid'; + + // Append firstPartyData to r.site.page if firstPartyData exists + const otherIxConfig = config.getConfig('ix'); + + if (otherIxConfig && otherIxConfig.firstPartyData) { + const firstPartyData = otherIxConfig.firstPartyData; + let firstPartyString = '?'; + for (const key in firstPartyData) { + if (firstPartyData.hasOwnProperty(key)) { + firstPartyString += `${encodeURIComponent(key)}=${encodeURIComponent(firstPartyData[key])}&`; + } + } + firstPartyString = firstPartyString.slice(0, -1); + + r.site.page += firstPartyString; + } + + // Use the siteId in the first bid request as the main siteId + const payload = {}; + payload.s = validBidRequests[0].params.siteId; + payload.v = ENDPOINT_VERSION; + payload.r = JSON.stringify(r); + payload.ac = 'j'; + payload.sd = 1; + + return { + method: 'GET', + url: baseUrl, + data: payload + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {object} serverResponse A successful response from the server. + * @return {array} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse) { + const bids = []; + let bid = null; + + if (!serverResponse.hasOwnProperty('body') || !serverResponse.body.hasOwnProperty('seatbid')) { + return bids; + } + + const responseBody = serverResponse.body; + const seatbid = responseBody.seatbid; + for (let i = 0; i < seatbid.length; i++) { + if (!seatbid[i].hasOwnProperty('bid')) { + continue; + } + + // Transform rawBid in bid response to the format that will be accepted by prebid + const innerBids = seatbid[i].bid; + for (let j = 0; j < innerBids.length; j++) { + bid = parseBid(innerBids[j], responseBody.cur); + bids.push(bid); + } + } + + return bids; + } +}; + +registerBidder(spec); diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md new file mode 100644 index 000000000000..c8322d99412e --- /dev/null +++ b/modules/ixBidAdapter.md @@ -0,0 +1,253 @@ +Overview +======== + +``` +Module Name: Index Exchange Adapter +Module Type: Bidder Adapter +Maintainer: prebid.support@indexexchange.com +``` + +Description +=========== + +This module connects publishers to Index Exchange's (IX) network of demand +sources through Prebid.js. + +It is compatible with both the older ad unit format where the `sizes` and +`mediaType` properties are placed at the top-level of the ad unit, and the newer +format where this information is encapsulated within the `mediaTypes` object. We +recommend that you use the newer format when possible as it will be better able +to accommodate new feature additions. + +If a mix of properties from both formats are present within an ad unit, the +newer format's properties will take precedence. + +Here are examples of both formats. + +##### Older Format +```javascript +var adUnits = [{ + // ... + + mediaType: 'banner', + + sizes: [ + [300, 250], + [300, 600] + ] + + // ... +}]; +``` + +##### Newer Format +```javascript +var adUnits = [{ + // ... + + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + } + + // ... +}]; +``` + +### Supported Media Types + +| Type | Support +| --- | --- +| Banner | Fully supported for all IX approved sizes. +| Video | Only in-stream supported. +| Native | Not supported. + +# Bid Parameters + +Each of the IX-specific parameters provided under the `adUnits[].bids[].params` +object are detailed here. + +### Banner + +| Key | Scope | Type | Description +| --- | --- | --- | --- +| siteId | Required | String |

An IX-specific identifier that is associated with a specific size on this ad unit. This is similar to a placement ID or an ad unit ID that some other modules have.

Examples:

+| size | Required | Number[] |

The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.banner.sizes`.

Examples:

+| bidFloor | Optional1 | Number |

The minimum bid required to participate in an auction for this ad unit. Assuming the bid floor currency that is set has a main unit (e.g. dollars, pounds) and a sub-unit (e.g. cents, pence), the bid floor should be in decimal-point format. If the currency only has main a unit (e.g. JPY), then the bid floor should be a whole number.

Examples:

| N/A +| bidFloorCur | Optional1 | String |

The currency of the bid floor.

Examples:

+ +

+ 1 bidFloor and bidFloorCur must + both be set when a bid floor is being configured. +

+ +Setup Guide +=========== + +Follow these steps to configure and add the IX module to your Prebid.js +integration. + +The examples in this guide assume the following starting configuration: + +```javascript +var adUnits = [{ + code: 'banner-div-a', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + bids: [] +}]; +``` + +##### 1. Add IX to the appropriate ad units + +For each size in an ad unit that IX will be bidding on, add one of the following +bid objects under `adUnits[].bids`: + +```javascript +{ + bidder: 'ix', + params: { + siteId: '', + size: [] + } +} +``` + +Set `params.siteId` and `params.size` in each bid object to the values provided +by your IX representative. + +**Example** +```javascript +var adUnits = [{ + code: 'banner-div-a', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + bids: [{ + bidder: 'ix', + params: { + siteId: '4622', + size: [300, 250] + } + }, { + bidder: 'ix', + params: { + siteId: '6242', + size: [300, 600] + } + }] +}]; +``` + +##### 2. Include `ixBidAdapter` in your build process + +When running the build command, include `ixBidAdapter` as a module. + +``` +gulp build --modules=ixBidAdapter,fooBidAdapter,bazBidAdapter +``` + +If a JSON file is being used to specify the bidder modules, add `"ixBidAdapter"` +to the top-level array in that file. + +```json +[ + "ixBidAdapter", + "fooBidAdapter", + "bazBidAdapter" +] +``` + +And then build. + +``` +gulp build --modules=bidderModules.json +``` + +Setting First Party Data (FPD) +============================== + +FPD allows you to specify key-value pairs which will be passed as part of the +query string to IX for use in Private Marketplace Deals which rely on query +string targeting for activation. For example, if a user is viewing a +news-related page, you can pass on that information by sending `category=news`. +Then in the IX Private Marketplace setup screens you can create Deals which +activate only on pages which contain `category=news`. Please reach out to your +IX representative if you have any questions or need help setting this up. + +To include FPD in a bid request, it must be set before `pbjs.requestBids` is +called. To set it, call `pbjs.setConfig` and provide it with a map of FPD keys +to values as such: + +```javascript +pbjs.setConfig({ + ix: { + firstPartyData: { + '': '', + '': '', + // ... + } + } +}); +``` + +The values can be updated at any time by calling `pbjs.setConfig` again. The +changes will be reflected in any proceeding bid requests. + +Additional Information +====================== + +### Bid Request Limit + +If a single bid request to IX contains more than 20 impression requests (i.e. +more than 20 objects in `bidRequest.imp`), only the first 20 will be accepted, +the rest will be ignored. + +To avoid this situation, ensure that when `pbjs.requestBid` is invoked, that the +number of bid objects (i.e. `adUnits[].bids`) with `adUnits[].bids[].bidder` set +to `'ix'` across all ad units that bids are being requested for does not exceed +20. + +### Time-To-Live (TTL) + +All bids received from IX have a TTL of 60 seconds, after which time they become +invalid. + +If an invalid bid wins, and its associated ad is rendered, it will not count +towards total impressions on IX's side. + +FAQs +==== + +### Why do I have to input size in `adUnits[].bids[].params` for IX when the size is already in the ad unit? + +There are two important reasons why we require it: + +1. An IX site ID maps to a single size, whereas an ad unit can have multiple +sizes. To ensure that the right site ID is mapped to the correct size in the ad +unit we require the size to be explicitly stated. + +2. An ad unit may have sizes that IX does not support. By explicitly stating the +size, you can choose not to have IX bid on certain sizes that are invalid. + +### How do I view IX's bid request in the network? + +In your browser of choice, create a new tab and open the developer tools. In +developer tools, select the network tab. Then, navigate to a page where IX is +setup to bid. Now, in the network tab, search for requests to +`casalemedia.com/cygnus`. These are the bid requests. diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js new file mode 100644 index 000000000000..081e4d4fdad2 --- /dev/null +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -0,0 +1,316 @@ +import * as utils from 'src/utils'; +import { config } from 'src/config'; +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/ixBidAdapter'; + +describe('IndexexchangeAdapter', () => { + const IX_ENDPOINT = 'http://as.casalemedia.com/cygnus'; + const BIDDER_VERSION = 7.2; + + const DEFAULT_BANNER_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', + bidId: '1a2b3c4d', + bidderRequestId: '11a22b33c44d', + auctionId: '1aa2bb3cc4dd' + } + ]; + const DEFAULT_BANNER_BID_RESPONSE = { + cur: 'USD', + id: '11a22b33c44d', + seatbid: [ + { + bid: [ + { + crid: '12345', + adomain: ['www.abc.com'], + adid: '14851455', + impid: '1a2b3c4d', + cid: '3051266', + price: 100, + w: 300, + h: 250, + id: '1', + ext: { + dspid: 50, + pricelevel: '_100', + advbrandid: 303325, + advbrand: 'OECTA' + }, + adm: '' + } + ], + seat: '3970' + } + ] + }; + + describe('inherited functions', () => { + it('should exists and is a function', () => { + const adapter = newBidder(spec); + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found for a banner ad', () => { + expect(spec.isBidRequestValid(DEFAULT_BANNER_VALID_BID[0])).to.equal(true); + }); + + it('should return true when optional params found for a banner ad', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 'USD'; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when siteID is number', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.siteId = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when siteID is missing', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when size is missing', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + delete bid.params.size; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when size array is wrong length', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.size = [ + 300, + 250, + 250 + ]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when size array is array of strings', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.size = ['300', '250']; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when there is only bidFloor', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when there is only bidFloorCur', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloorCur = 'USD'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when bidFloor is string', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = '50'; + bid.params.bidFloorCur = 'USD'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when bidFloorCur is number', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 70; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequestsBanner', () => { + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const requestUrl = request.url; + const requestMethod = request.method; + const query = request.data; + + it('request should be made to IX endpoint with GET method', () => { + expect(requestMethod).to.equal('GET'); + expect(requestUrl).to.equal(IX_ENDPOINT); + }); + + it('query object (version, siteID and request) should be correct', () => { + expect(query.v).to.equal(BIDDER_VERSION); + expect(query.s).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); + expect(query.r).to.exist; + expect(query.ac).to.equal('j'); + expect(query.sd).to.equal(1); + }); + + it('payload should have correct format and value', () => { + const payload = JSON.parse(query.r); + + expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); + expect(payload.site).to.exist; + expect(payload.site.page).to.exist; + expect(payload.site.page).to.contain('http'); + expect(payload.site.ref).to.exist; + expect(payload.site.ref).to.be.a('string'); + expect(payload.ext).to.exist; + expect(payload.ext.source).to.equal('prebid'); + expect(payload.imp).to.exist; + expect(payload.imp).to.be.an('array'); + expect(payload.imp).to.have.lengthOf(1); + }); + + it('impression should have correct format and value', () => { + const impression = JSON.parse(query.r).imp[0]; + const sidValue = `${DEFAULT_BANNER_VALID_BID[0].params.size[0].toString()}x${DEFAULT_BANNER_VALID_BID[0].params.size[1].toString()}`; + + expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); + expect(impression.banner).to.exist; + expect(impression.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); + expect(impression.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); + expect(impression.banner.topframe).to.exist; + expect(impression.banner.topframe).to.be.oneOf([0, 1]); + expect(impression.ext).to.exist; + expect(impression.ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impression.ext.sid).to.equal(sidValue); + }); + + it('impression should have bidFloor and bidFloorCur if configured', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 'USD'; + const requestBidFloor = spec.buildRequests([bid]); + const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + + expect(impression.bidfloor).to.equal(bid.params.bidFloor); + expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + }); + + it('should add first party data to page url in bid request if it exists in config', () => { + config.setConfig({ + ix: { + firstPartyData: { + ab: 123, + cd: '123#ab', + 'e/f': 456, + 'h?g': '456#cd' + } + } + }); + + const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; + const expectedPageUrl = `${utils.getTopWindowUrl()}?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd`; + + expect(pageUrl).to.equal(expectedPageUrl); + }); + + it('should not add first party data to page url in bid request if it is not present', () => { + config.setConfig({ + ix: {} + }); + + const requestWithOutFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const pageUrl = JSON.parse(requestWithOutFirstPartyData.data.r).site.page; + + expect(pageUrl).to.equal(utils.getTopWindowUrl()); + }); + }); + + describe('interpretResponseBanner', () => { + it('should get correct bid response', () => { + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + ad: '', + currency: 'USD', + ttl: 60, + netRevenue: true, + dealId: undefined + } + ]; + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set creativeId to default value if not provided', () => { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + delete bidResponse.seatbid[0].bid[0].crid; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '-', + width: 300, + height: 250, + ad: '', + currency: 'USD', + ttl: 60, + netRevenue: true, + dealId: undefined + } + ]; + const result = spec.interpretResponse({ body: bidResponse }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set Japanese price correctly', () => { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + bidResponse.cur = 'JPY'; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 100, + creativeId: '12345', + width: 300, + height: 250, + ad: '', + currency: 'JPY', + ttl: 60, + netRevenue: true, + dealId: undefined + } + ]; + const result = spec.interpretResponse({ body: bidResponse }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set dealId correctly', () => { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + bidResponse.seatbid[0].bid[0].ext.dealid = 'deal'; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + ad: '', + currency: 'USD', + ttl: 60, + netRevenue: true, + dealId: 'deal' + } + ]; + const result = spec.interpretResponse({ body: bidResponse }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + }); +});