From cf3b64294b88b3fdfd5e8c981027b18b19759007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E9=9B=A8=E8=BB=92=20=D0=9F=D0=B5=D1=82=D1=80?= Date: Tue, 30 Oct 2018 15:47:21 +0100 Subject: [PATCH] Add code, test, and doc for Adikteev adapter (#3229) * Add code, test, and doc for Adikteev adapter * Reflect comments on other PR http://prebid.org/dev-docs/bidder-adaptor.html#referrers https://github.com/prebid/Prebid.js/pull/3230#discussion_r228319752 * 'currency' isn't a bidder-specific param Update PR following this remark on another one: https://github.com/prebid/Prebid.js/pull/3228#discussion_r228317072 * Add integration example, fix bid requestId --- .../gpt/hello_world_adikteev.html | 93 +++++++ integrationExamples/gpt/pbjs_example_gpt.html | 11 +- modules/adikteevBidAdapter.js | 94 +++++++ modules/adikteevBidAdapter.md | 35 +++ test/spec/modules/adikteevBidAdapter_spec.js | 235 ++++++++++++++++++ 5 files changed, 466 insertions(+), 2 deletions(-) create mode 100644 integrationExamples/gpt/hello_world_adikteev.html create mode 100644 modules/adikteevBidAdapter.js create mode 100644 modules/adikteevBidAdapter.md create mode 100644 test/spec/modules/adikteevBidAdapter_spec.js diff --git a/integrationExamples/gpt/hello_world_adikteev.html b/integrationExamples/gpt/hello_world_adikteev.html new file mode 100644 index 00000000000..7372ff12d7f --- /dev/null +++ b/integrationExamples/gpt/hello_world_adikteev.html @@ -0,0 +1,93 @@ + + + + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+ + + diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 88d4839d984..6852b9f680a 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -312,8 +312,15 @@ width: '300', height: '250', } - } - + }, + { + bidder: 'adikteev', + params: { + placementId: 12345, + currency: 'EUR', + bidFloorPrice: 0.1, + } + }, ] }, { code: 'div-gpt-ad-12345678-1', diff --git a/modules/adikteevBidAdapter.js b/modules/adikteevBidAdapter.js new file mode 100644 index 00000000000..12d502de94a --- /dev/null +++ b/modules/adikteevBidAdapter.js @@ -0,0 +1,94 @@ +import {registerBidder} from 'src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import {config} from 'src/config'; + +export const BIDDER_CODE = 'adikteev'; +export const ENDPOINT_URL = 'https://serve-adserver.adikteev.com/api/prebid/bid'; +export const ENDPOINT_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/bid'; +export const USER_SYNC_IFRAME_URL = 'https://serve-adserver.adikteev.com/api/prebid/sync-iframe'; +export const USER_SYNC_IFRAME_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/sync-iframe'; +export const USER_SYNC_IMAGE_URL = 'https://serve-adserver.adikteev.com/api/prebid/sync-image'; +export const USER_SYNC_IMAGE_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/sync-image'; + +export let stagingEnvironmentSwitch = false; // Don't use it. Allow us to make tests on staging + +export function setstagingEnvironmentSwitch(value) { + stagingEnvironmentSwitch = value; +} + +function validateSizes(sizes) { + if (!utils.isArray(sizes) || typeof sizes[0] === 'undefined') { + return false; + } + return sizes.every(size => utils.isArray(size) && size.length === 2); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + setstagingEnvironmentSwitch(stagingEnvironmentSwitch || !!bid.params.stagingEnvironment); + return !!( + bid && + bid.params && + bid.params.bidFloorPrice && + bid.params.placementId && + bid.bidder === BIDDER_CODE && + validateSizes(bid.mediaTypes.banner.sizes) + ); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + const payload = { + validBidRequests, + bidderRequest, + refererInfo: bidderRequest.refererInfo, + currency: config.getConfig('currency'), + userAgent: navigator.userAgent, + screen: { + width: window.screen.width, + height: window.screen.height + }, + language: navigator.language, + cookies: document.cookie.split(';'), + prebidUpdateVersion: '1.29.0', + }; + return { + method: 'POST', + url: stagingEnvironmentSwitch ? ENDPOINT_URL_STAGING : ENDPOINT_URL, + data: JSON.stringify(payload), + }; + }, + + interpretResponse: (serverResponse, bidRequests) => { + const returnedBids = []; + const validBidRequests = JSON.parse(bidRequests.data).validBidRequests; + serverResponse.body.forEach((value, index) => { + const overrides = { + requestId: validBidRequests[index].bidId, + }; + returnedBids.push(Object.assign({}, value, overrides)); + }); + return returnedBids; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: stagingEnvironmentSwitch ? USER_SYNC_IFRAME_URL_STAGING : USER_SYNC_IFRAME_URL, + }); + } + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'image', + url: stagingEnvironmentSwitch ? USER_SYNC_IMAGE_URL_STAGING : USER_SYNC_IMAGE_URL, + }); + } + return syncs; + }, +}; +registerBidder(spec); diff --git a/modules/adikteevBidAdapter.md b/modules/adikteevBidAdapter.md new file mode 100644 index 00000000000..d5008f61b03 --- /dev/null +++ b/modules/adikteevBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Adikteev Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adnetwork@adikteev.com +``` + +# Description + +Module that connects to Adikteev's demand sources + +# Test Parameters + +``` javascript + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[750, 200]], // a display size + } + }, + bids: [ + { + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/adikteevBidAdapter_spec.js b/test/spec/modules/adikteevBidAdapter_spec.js new file mode 100644 index 00000000000..243cbe2a9c5 --- /dev/null +++ b/test/spec/modules/adikteevBidAdapter_spec.js @@ -0,0 +1,235 @@ +import {expect} from 'chai'; +import { + ENDPOINT_URL, + ENDPOINT_URL_STAGING, + setstagingEnvironmentSwitch, + spec, + stagingEnvironmentSwitch, + USER_SYNC_IFRAME_URL, + USER_SYNC_IFRAME_URL_STAGING, + USER_SYNC_IMAGE_URL, + USER_SYNC_IMAGE_URL_STAGING, +} from 'modules/adikteevBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../../../src/utils'; + +describe('adikteevBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(setstagingEnvironmentSwitch).to.exist.and.to.be.a('function'); + }); + it('exists and is correctly set', () => { + expect(stagingEnvironmentSwitch).to.exist.and.to.equal(false); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const validBid = { + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + }; + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should mutate stagingEnvironmentSwitch when required params found', () => { + const withstagingEnvironmentSwitch = { + params: { + stagingEnvironment: true, + }, + }; + spec.isBidRequestValid(withstagingEnvironmentSwitch); + expect(stagingEnvironmentSwitch).to.equal(true); + setstagingEnvironmentSwitch(false); + }); + + it('should return false when required params are invalid', () => { + expect(spec.isBidRequestValid({ + bidder: '', // invalid bidder + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'adikteev', + params: { + placementId: '', // invalid placementId + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750]] // invalid size + } + }, + })).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const validBidRequests = []; + const bidderRequest = {}; + const serverRequest = spec.buildRequests(validBidRequests, bidderRequest); + it('creates a request object with correct method, url and data', () => { + expect(serverRequest).to.exist.and.have.all.keys( + 'method', + 'url', + 'data', + ); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal(ENDPOINT_URL); + + let requestData = JSON.parse(serverRequest.data); + expect(requestData).to.exist.and.have.all.keys( + 'validBidRequests', + 'bidderRequest', + 'userAgent', + 'screen', + 'language', + 'cookies', + // 'refererInfo', + // 'currency', + 'prebidUpdateVersion', + ); + expect(requestData.validBidRequests).to.deep.equal(validBidRequests); + expect(requestData.bidderRequest).to.deep.equal(bidderRequest); + expect(requestData.userAgent).to.deep.equal(navigator.userAgent); + expect(requestData.screen.width).to.deep.equal(window.screen.width); + expect(requestData.screen.height).to.deep.equal(window.screen.height); + expect(requestData.language).to.deep.equal(navigator.language); + expect(requestData.prebidUpdateVersion).to.deep.equal('1.29.0'); + }); + + describe('staging environment', () => { + setstagingEnvironmentSwitch(true); + const serverRequest = spec.buildRequests(validBidRequests, bidderRequest); + expect(serverRequest.url).to.equal(ENDPOINT_URL_STAGING); + setstagingEnvironmentSwitch(false); + }); + }); + + describe('interpretResponse', () => { + it('bid objects from response', () => { + const serverResponse = + { + body: [ + { + cpm: 1, + width: 300, + height: 250, + ad: '
', + ttl: 360, + creativeId: 123, + netRevenue: false, + currency: 'EUR', + } + ] + }; + const payload = { + validBidRequests: [{ + bidId: '2ef7bb021ac847' + }], + }; + const bidRequests = { + method: 'POST', + url: stagingEnvironmentSwitch ? ENDPOINT_URL_STAGING : ENDPOINT_URL, + data: JSON.stringify(payload), + }; + const bidResponses = spec.interpretResponse(serverResponse, bidRequests); + expect(bidResponses).to.be.an('array').that.is.not.empty; // yes, syntax is correct + expect(bidResponses[0]).to.have.all.keys( + 'requestId', + 'cpm', + 'width', + 'height', + 'ad', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + ); + + expect(bidResponses[0].requestId).to.equal(payload.validBidRequests[0].bidId); + expect(bidResponses[0].cpm).to.equal(serverResponse.body[0].cpm); + expect(bidResponses[0].width).to.equal(serverResponse.body[0].width); + expect(bidResponses[0].height).to.equal(serverResponse.body[0].height); + expect(bidResponses[0].ad).to.equal(serverResponse.body[0].ad); + expect(bidResponses[0].ttl).to.equal(serverResponse.body[0].ttl); + expect(bidResponses[0].creativeId).to.equal(serverResponse.body[0].creativeId); + expect(bidResponses[0].netRevenue).to.equal(serverResponse.body[0].netRevenue); + expect(bidResponses[0].currency).to.equal(serverResponse.body[0].currency); + }); + }); + + describe('getUserSyncs', () => { + expect(spec.getUserSyncs({ + iframeEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + }]); + + expect(spec.getUserSyncs({ + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'image', + url: USER_SYNC_IMAGE_URL + }]); + + expect(spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + }, { + type: 'image', + url: USER_SYNC_IMAGE_URL + }]); + + describe('staging environment', () => { + setstagingEnvironmentSwitch(true); + expect(spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL_STAGING + }, { + type: 'image', + url: USER_SYNC_IMAGE_URL_STAGING + }]); + setstagingEnvironmentSwitch(false); + }); + }); +});