diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 2e5c798e218..f1ec912fd26 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -116,7 +116,7 @@ }, { bidder: 'adform', - // available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' ] + // available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test', priceType ] params: { adxDomain: 'adx.adform.net', //optional mid: 158989, diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js new file mode 100644 index 00000000000..6dba55f88a7 --- /dev/null +++ b/modules/adformBidAdapter.js @@ -0,0 +1,107 @@ +'use strict'; + +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'adform'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [], + isBidRequestValid: function (bid) { + return !!(bid.params.mid); + }, + buildRequests: function (validBidRequests) { + var i, l, j, k, bid, _key, _value, reqParams; + var request = []; + var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ] ]; + var netRevenue = 'net'; + var bids = JSON.parse(JSON.stringify(validBidRequests)); + for (i = 0, l = bids.length; i < l; i++) { + bid = bids[i]; + if (bid.params.priceType === 'gross') { + netRevenue = 'gross'; + } + for (j = 0, k = globalParams.length; j < k; j++) { + _key = globalParams[j][0]; + _value = bid[_key] || bid.params[_key]; + if (_value) { + bid[_key] = bid.params[_key] = null; + globalParams[j][1] = _value; + } + } + reqParams = bid.params; + reqParams.transactionId = bid.transactionId; + request.push(formRequestUrl(reqParams)); + } + + request.unshift('//' + globalParams[0][1] + '/adx/?rp=4'); + + request.push('stid=' + validBidRequests[0].requestId); + + for (i = 1, l = globalParams.length; i < l; i++) { + _key = globalParams[i][0]; + _value = globalParams[i][1]; + if (_value) { + request.push(_key + '=' + encodeURIComponent(_value)); + } + } + + return { + method: 'GET', + url: request.join('&'), + bids: validBidRequests, + netRevenue: netRevenue, + bidder: 'adform' + }; + + function formRequestUrl(reqData) { + var key; + var url = []; + + for (key in reqData) { + if (reqData.hasOwnProperty(key) && reqData[key]) { url.push(key, '=', reqData[key], '&'); } + } + + return encodeURIComponent(btoa(url.join('').slice(0, -1))); + } + }, + interpretResponse: function (serverResponse, bidRequest) { + var bidObject, response, bid; + var bidRespones = []; + var bids = bidRequest.bids; + var responses = serverResponse.body; + for (var i = 0; i < responses.length; i++) { + response = responses[i]; + bid = bids[i]; + if (response.response === 'banner' && verifySize(response, bid.sizes)) { + bidObject = { + requestId: bid.bidId, + cpm: response.win_bid, + width: response.width, + height: response.height, + creativeId: bid.bidId, + dealId: response.deal_id, + currency: response.win_cur, + netRevenue: bidRequest.netRevenue !== 'gross', + ttl: 360, + ad: response.banner, + bidderCode: bidRequest.bidder, + transactionId: bid.transactionId + }; + bidRespones.push(bidObject); + } + } + + return bidRespones; + + function verifySize(adItem, validSizes) { + for (var j = 0, k = validSizes.length; j < k; j++) { + if (adItem.width === validSizes[j][0] && + adItem.height === validSizes[j][1]) { + return true; + } + } + return false; + } + } +}; +registerBidder(spec); diff --git a/modules/adformBidAdapter.md b/modules/adformBidAdapter.md new file mode 100644 index 00000000000..9f357b21729 --- /dev/null +++ b/modules/adformBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +Module Name: Adform Bidder Adapter +Module Type: Bidder Adapter +Maintainer: Scope.FL.Scripts@adform.com + +# Description + +Module that connects to Adform demand sources to fetch bids. +Banner formats are supported. + +# Test Parameters +``` + var adUnits = [ + { + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], // a display size + bids: [ + { + bidder: "adform", + params: { + adxDomain: 'adx.adform.net', //optional + mid: '292063', + priceType: 'gross' // default is 'net' + } + } + ] + }, + ]; +``` diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js new file mode 100644 index 00000000000..72f625d08c6 --- /dev/null +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -0,0 +1,246 @@ +import {assert, expect} from 'chai'; +import * as url from 'src/url'; +import {spec} from 'modules/adformBidAdapter'; + +describe('Adform adapter', () => { + let serverResponse, bidRequest, bidResponses; + let bids = []; + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'adform', + 'params': { + 'mid': '19910113' + } + }; + + it('should return true when required params found', () => { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params are missing', () => { + bid.params = { + adxDomain: 'adx.adform.net' + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }) + }); + + describe('buildRequests', () => { + it('should pass multiple bids via single request', () => { + let request = spec.buildRequests(bids); + let parsedUrl = parseUrl(request.url); + assert.lengthOf(parsedUrl.items, 3); + }); + + it('should handle global request parameters', () => { + let parsedUrl = parseUrl(spec.buildRequests([bids[0]]).url); + let query = parsedUrl.query; + + assert.equal(parsedUrl.path, '//newDomain/adx'); + assert.equal(query.tid, 45); + assert.equal(query.rp, 4); + assert.equal(query.fd, 1); + assert.equal(query.stid, '7aefb970-2045'); + assert.equal(query.url, encodeURIComponent('some// there')); + }); + + it('should set correct request method', () => { + let request = spec.buildRequests([bids[0]]); + assert.equal(request.method, 'GET'); + }); + + it('should correctly form bid items', () => { + let bidList = bids; + let request = spec.buildRequests(bidList); + let parsedUrl = parseUrl(request.url); + assert.deepEqual(parsedUrl.items, [ + { + mid: '1', + transactionId: '5f33781f-9552-4ca1' + }, + { + mid: '2', + someVar: 'someValue', + transactionId: '5f33781f-9552-4iuy', + priceType: 'gross' + }, + { + mid: '3', + pdom: 'home', + transactionId: '5f33781f-9552-7ev3' + } + ]); + }); + + it('should not change original validBidRequests object', () => { + var resultBids = JSON.parse(JSON.stringify(bids[0])); + let request = spec.buildRequests([bids[0]]); + assert.deepEqual(resultBids, bids[0]); + }); + }); + + describe('interpretResponse', () => { + it('should respond with empty response when there is empty serverResponse', () => { + let result = spec.interpretResponse({ body: {}}, {}); + assert.deepEqual(result, []); + }); + it('should respond with empty response when sizes doesn\'t match', () => { + serverResponse.body[0].response = 'banner'; + serverResponse.body[0].width = 100; + serverResponse.body[0].height = 150; + + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest); + + assert.equal(serverResponse.body.length, 1); + assert.equal(serverResponse.body[0].response, 'banner'); + assert.deepEqual(result, []); + }); + it('should respond with empty response when response from server is not banner', () => { + serverResponse.body[0].response = 'not banner'; + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest); + + assert.deepEqual(result, []); + }); + it('should interpret server response correctly with one bid', () => { + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest)[0]; + + assert.equal(result.requestId, '2a0cf4e'); + assert.equal(result.cpm, 13.9); + assert.equal(result.width, 300); + assert.equal(result.height, 250); + assert.equal(result.creativeId, '2a0cf4e'); + assert.equal(result.dealId, '123abc'); + assert.equal(result.currency, 'EUR'); + assert.equal(result.netRevenue, true); + assert.equal(result.ttl, 360); + assert.equal(result.ad, ''); + assert.equal(result.bidderCode, 'adform'); + assert.equal(result.transactionId, '5f33781f-9552-4ca1'); + }); + + it('should set correct netRevenue', () => { + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[1]]; + bidRequest.netRevenue = 'gross'; + let result = spec.interpretResponse(serverResponse, bidRequest)[0]; + + assert.equal(result.netRevenue, false); + }); + + it('should create bid response item for every requested item', () => { + let result = spec.interpretResponse(serverResponse, bidRequest); + assert.lengthOf(result, 3); + }); + }); + + beforeEach(() => { + let sizes = [[250, 300], [300, 250], [300, 600]]; + let adUnitCode = ['div-01', 'div-02', 'div-03']; + let placementCode = adUnitCode; + let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', priceType: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }]; + bids = [ + { + adUnitCode: placementCode[0], + bidId: '2a0cf4e', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[0], + adxDomain: 'newDomain', + tid: 45, + placementCode: placementCode[0], + requestId: '7aefb970-2045', + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-4ca1' + }, + { + adUnitCode: placementCode[1], + bidId: '2a0cf5b', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[1], + placementCode: placementCode[1], + requestId: '7aefb970-2045', + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-4iuy' + }, + { + adUnitCode: placementCode[2], + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[2], + placementCode: placementCode[2], + requestId: '7aefb970-2045', + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-7ev3' + } + ]; + serverResponse = { + body: [ + { + banner: '', + deal_id: '123abc', + height: 250, + response: 'banner', + width: 300, + win_bid: 13.9, + win_cur: 'EUR' + }, + { + banner: '', + deal_id: '123abc', + height: 300, + response: 'banner', + width: 250, + win_bid: 13.9, + win_cur: 'EUR' + }, + { + banner: '', + deal_id: '123abc', + height: 300, + response: 'banner', + width: 600, + win_bid: 10, + win_cur: 'EUR' + } + ], + headers: {} + }; + bidRequest = { + bidder: 'adform', + bids: bids, + method: 'GET', + url: 'url' + }; + }); +}); + +function parseUrl(url) { + const parts = url.split('/'); + const query = parts.pop().split('&'); + return { + path: parts.join('/'), + items: query + .filter((i) => !~i.indexOf('=')) + .map((i) => atob(decodeURIComponent(i)) + .split('&') + .reduce(toObject, {})), + query: query + .filter((i) => ~i.indexOf('=')) + .map((i) => i.replace('?', '')) + .reduce(toObject, {}) + }; +} + +function toObject(cache, string) { + const keyValue = string.split('='); + cache[keyValue[0]] = keyValue[1]; + return cache; +}