From 0be342247e7419c771751dc6b8beef4bf534697f Mon Sep 17 00:00:00 2001 From: borisjaskerovich <41855079+borisjaskerovich@users.noreply.github.com> Date: Mon, 4 Feb 2019 09:02:40 +0200 Subject: [PATCH] Timmedia/timadapter 2.0 (#3497) * add tim adapter * add tim spec file * add tim md file * added tests. change adapter. * dummy commit for re-running tests * new code for prebid2.0 * change alias name * change domain name --- modules/timBidAdapter.js | 177 ++++++++++++++++++++++++ modules/timBidAdapter.md | 26 ++++ test/spec/modules/timBidAdapter_spec.js | 152 ++++++++++++++++++++ 3 files changed, 355 insertions(+) create mode 100644 modules/timBidAdapter.js create mode 100644 modules/timBidAdapter.md create mode 100644 test/spec/modules/timBidAdapter_spec.js diff --git a/modules/timBidAdapter.js b/modules/timBidAdapter.js new file mode 100644 index 00000000000..0539f37deef --- /dev/null +++ b/modules/timBidAdapter.js @@ -0,0 +1,177 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import * as bidfactory from '../src/bidfactory'; +var CONSTANTS = require('src/constants.json'); +const BIDDER_CODE = 'tim'; + +function parseBidRequest(bidRequest) { + let params = bidRequest.url.split('?')[1]; + var obj = {}; + var pairs = params.split('&'); + try { + for (var i in pairs) { + var split = pairs[i].split('='); + obj[decodeURIComponent(split[0])] = decodeURIComponent(split[1]); + } + } catch (e) { + utils.logError(e); + } + + return JSON.parse(obj.br); +} + +function formatAdMarkup(bid) { + var adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return `${adm}`; +} + +function createTrackPixelHtml(url) { + if (!url) { + return ''; + } + let img = '
'; + img += '
'; + return img; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['timmedia'], + + isBidRequestValid: function(bid) { + if (bid.params && bid.params.publisherid && bid.params.placementCode) { + return true; + } if (!bid.params) { + utils.logError('bid not valid: params were not provided'); + } else if (!bid.params.publisherid) { + utils.logError('bid not valid: publisherid was not provided'); + } else if (!bid.params.placementCode) { + utils.logError('bid not valid: placementCode was not provided'); + } return false; + }, + + buildRequests: function(validBidRequests, bidderRequest) { + var requests = []; + for (var i = 0; i < validBidRequests.length; i++) { + requests.push(this.createRTBRequestURL(validBidRequests[i])); + } + return requests; + }, + + createRTBRequestURL: function(bidReq) { + // build bid request object + var domain = window.location.host; + var page = window.location.href; + var publisherid = bidReq.params.publisherid; + var bidFloor = bidReq.params.bidfloor; + var placementCode = bidReq.params.placementCode; + + var adW = bidReq.mediaTypes.banner.sizes[0][0]; + var adH = bidReq.mediaTypes.banner.sizes[0][1]; + + // build bid request with impressions + var bidRequest = { + id: utils.getUniqueIdentifierStr(), + imp: [{ + id: bidReq.bidId, + banner: { + w: adW, + h: adH + }, + tagid: placementCode, + bidfloor: bidFloor + }], + site: { + domain: domain, + page: page, + publisher: { + id: publisherid + } + }, + device: { + 'language': this.getLanguage(), + 'w': adW, + 'h': adH, + 'js': 1, + 'ua': navigator.userAgent + } + }; + if (!bidFloor) { + delete bidRequest.imp['bidfloor']; + } + + bidRequest.bidId = bidReq.bidId; + var url = '//hb.timmedia-hb.com/api/v2/services/prebid/' + publisherid + '/' + placementCode + '?' + 'br=' + encodeURIComponent(JSON.stringify(bidRequest)); + return { + method: 'GET', + url: url, + data: '', + options: {withCredentials: false} + }; + }, + + interpretResponse: function(serverResponse, bidRequest) { + bidRequest = parseBidRequest(bidRequest); + var bidResp = serverResponse.body; + const bidResponses = []; + if ((!bidResp || !bidResp.id) || + (!bidResp.seatbid || bidResp.seatbid.length === 0 || !bidResp.seatbid[0].bid || bidResp.seatbid[0].bid.length === 0)) { + return []; + } + bidResp.seatbid[0].bid.forEach(function (bidderBid) { + var responseCPM; + var placementCode = ''; + if (bidRequest) { + var bidResponse = bidfactory.createBid(1); + placementCode = bidRequest.placementCode; + bidRequest.status = CONSTANTS.STATUS.GOOD; + responseCPM = parseFloat(bidderBid.price); + if (responseCPM === 0) { + var bid = bidfactory.createBid(2); + bid.bidderCode = BIDDER_CODE; + bidResponses.push(bid); + return bidResponses; + } + bidResponse.placementCode = placementCode; + bidResponse.size = bidRequest.sizes; + bidResponse.creativeId = bidderBid.id; + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.cpm = responseCPM; + bidResponse.ad = formatAdMarkup(bidderBid); + bidResponse.width = parseInt(bidderBid.w); + bidResponse.height = parseInt(bidderBid.h); + bidResponse.currency = bidResp.cur; + bidResponse.netRevenue = true; + bidResponse.requestId = bidRequest.bidId; + bidResponse.ttl = 180; + bidResponses.push(bidResponse); + } + }); + return bidResponses; + }, + getLanguage: function() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = [] + return syncs; + }, + + onTimeout: function(data) { + // Bidder specifc code + }, + + onBidWon: function(bid) { + // Bidder specific code + }, + + onSetTargeting: function(bid) { + // Bidder specific code + }, +} +registerBidder(spec); diff --git a/modules/timBidAdapter.md b/modules/timBidAdapter.md new file mode 100644 index 00000000000..684f2e5f7c4 --- /dev/null +++ b/modules/timBidAdapter.md @@ -0,0 +1,26 @@ +# Overview + +``` +Module Name: tim Bidder Adapter +Module Type: Bidder Adapter +Maintainer: boris@thetimmedia.com +``` + +# Description + +Module that connects to tim's demand sources + +# Test Parameters +``` + var adUnits = [{ + "code":"99", + "sizes":[[300,250]], + "bids":[{"bidder":"tim", + "params":{ + "placementCode":"testPlacementCode", + "publisherid":"testpublisherid" + } + }] + }] +``` + diff --git a/test/spec/modules/timBidAdapter_spec.js b/test/spec/modules/timBidAdapter_spec.js new file mode 100644 index 00000000000..0dc14bf3e03 --- /dev/null +++ b/test/spec/modules/timBidAdapter_spec.js @@ -0,0 +1,152 @@ +import { expect } from 'chai'; +import { spec } from 'modules/timBidAdapter'; + +describe('timAdapterTests', function () { + describe('bidRequestValidity', function () { + it('bidRequest with publisherid and placementCode params', function () { + expect(spec.isBidRequestValid({ + bidder: 'tim', + params: { + publisherid: 'testid', + placementCode: 'testplacement' + } + })).to.equal(true); + }); + + it('bidRequest with only publisherid', function () { + expect(spec.isBidRequestValid({ + bidder: 'tim', + params: { + publisherid: 'testid' + } + })).to.equal(false); + }); + + it('bidRequest with only placementCode', function () { + expect(spec.isBidRequestValid({ + bidder: 'tim', + params: { + placementCode: 'testplacement' + } + })).to.equal(false); + }); + + it('bidRequest without params', function () { + expect(spec.isBidRequestValid({ + bidder: 'tim', + })).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const validBidRequests = [{ + 'bidder': 'tim', + 'params': {'placementCode': 'placementCode', 'publisherid': 'testpublisherid'}, + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'sizes': [[300, 250]], + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + it('bidRequest method', function () { + const requests = spec.buildRequests(validBidRequests); + expect(requests[0].method).to.equal('GET'); + }); + + it('bidRequest url', function () { + const requests = spec.buildRequests(validBidRequests); + expect(requests[0].url).to.exist; + }); + + it('bidRequest data', function () { + const requests = spec.buildRequests(validBidRequests); + expect(requests[0].data).to.exist; + }); + + it('bidRequest options', function () { + const requests = spec.buildRequests(validBidRequests); + expect(requests[0].options).to.exist; + }); + }); + + describe('interpretResponse', function () { + const bidRequest = { + 'method': 'GET', + 'url': '//bidder.url/api/prebid/testpublisherid/header-bid-tag-0?br=%7B%22id%22%3A%223a3ac0d7fc2548%22%2C%22imp%22%3A%5B%7B%22id%22%3A%22251b8a6d3aac3e%22%2C%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C%22tagid%22%3A%22header-bid-tag-0%22%7D%5D%2C%22site%22%3A%7B%22domain%22%3A%22www.chinatimes.com%22%2C%22page%22%3A%22http%3A%2F%2Fwww.chinatimes.com%2Fa%22%2C%22publisher%22%3A%7B%22id%22%3A%22testpublisherid%22%7D%7D%2C%22device%22%3A%7B%22language%22%3A%22en%22%2C%22w%22%3A300%2C%22h%22%3A250%2C%22js%22%3A1%2C%22ua%22%3A%22Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F71.0.3578.98%20Safari%2F537.36%22%7D%2C%22bidId%22%3A%22251b8a6d3aac3e%22%7D', + 'data': '', + 'options': {'withCredentials': false} + }; + + const serverResponse = { + 'body': { + 'id': 'id', + 'seatbid': [] + }, + 'headers': {} + }; + + it('check empty array response', function () { + const result = spec.interpretResponse(serverResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + const validBidRequest = { + 'method': 'GET', + 'url': '//bidder.url/api/v2/services/prebid/testpublisherid/placementCodeTest?br=%7B%22id%22%3A%2248640869bd9db94%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224746fcaa11197f3%22%2C%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C%22tagid%22%3A%22placementCodeTest%22%7D%5D%2C%22site%22%3A%7B%22domain%22%3A%22mediamart.tv%22%2C%22page%22%3A%22http%3A%2F%2Fmediamart.tv%2Fsas%2Ftests%2FDesktop%2Fcaesar%2Fdfptest.html%22%2C%22publisher%22%3A%7B%22id%22%3A%22testpublisherid%22%7D%7D%2C%22device%22%3A%7B%22language%22%3A%22en%22%2C%22w%22%3A300%2C%22h%22%3A250%2C%22js%22%3A1%2C%22ua%22%3A%22Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F71.0.3578.98%20Safari%2F537.36%22%7D%2C%22bidId%22%3A%224746fcaa11197f3%22%7D', + 'data': '', + 'options': {'withCredentials': false} + }; + const validServerResponse = { + 'body': {'id': 'id', + 'seatbid': [ + {'bid': [{'id': 'id', + 'impid': 'impid', + 'price': 3, + 'nurl': 'https://bidder.url/api/v1/?price=${AUCTION_PRICE}&bidcur=USD&bidid=bidid=true', + 'adm': '', + 'adomain': [''], + 'cid': '1', + 'crid': '700', + 'w': 300, + 'h': 250 + }]}], + 'bidid': 'bidid', + 'cur': 'USD' + }, + 'headers': {} + }; + it('required keys', function () { + const result = spec.interpretResponse(validServerResponse, validBidRequest); + + let requiredKeys = [ + 'requestId', + 'creativeId', + 'adId', + 'cpm', + 'width', + 'height', + 'currency', + 'netRevenue', + 'ttl', + 'ad' + ]; + + let resultKeys = Object.keys(result[0]); + requiredKeys.forEach(function(key) { + expect(resultKeys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); + + describe('getUserSyncs', function () { + it('check empty response getUserSyncs', function () { + const result = spec.getUserSyncs('', ''); + expect(result).to.deep.equal([]); + }); + }); +});