Skip to content

Commit

Permalink
Add GumGum adapter (#833)
Browse files Browse the repository at this point in the history
* GumGum adapter

Tests

* GumGum adapter - remove unneeded reset

* GumGum adapter - ad loader format
  • Loading branch information
davidmh authored and matthewlane committed Dec 6, 2016
1 parent eb42f3b commit 05d0bf9
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 0 deletions.
1 change: 1 addition & 0 deletions adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"conversant",
"districtmDMX",
"getintent",
"gumgum",
"hiromedia",
"indexExchange",
"kruxlink",
Expand Down
6 changes: 6 additions & 0 deletions integrationExamples/gpt/pbjs_example_gpt.html
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,12 @@
params: {
tagid: 007, // REQUIRED int. To get one, contact http://www.memeglobal.com
}
},
{
bidder: 'gumgum',
params: {
inScreen: 'ggumtest' // REQUIRED str Tracking Id
}
}
]
}
Expand Down
114 changes: 114 additions & 0 deletions src/adapters/gumgum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const bidfactory = require('../bidfactory');
const bidmanager = require('../bidmanager');
const utils = require('../utils');
const adloader = require('../adloader');

const BIDDER_CODE = 'gumgum';
const CALLBACKS = {};

const GumgumAdapter = function GumgumAdapter() {

const bidEndpoint = `https://g2.gumgum.com/hbid/imp`;

const WINDOW = global.top;
const SCREEN = WINDOW.screen;

function _callBids({ bids }) {
const browserParams = {
vw: WINDOW.innerWidth,
vh: WINDOW.innerHeight,
sw: SCREEN.width,
sh: SCREEN.height,
pu: WINDOW.location.href,
dpr: WINDOW.devicePixelRatio || 1
};
utils._each(bids, bidRequest => {
const { bidId
, params = {}
, placementCode
} = bidRequest;
const trackingId = params.inScreen;
const nativeId = params.native;
const slotId = params.inSlot;
const bid = {};

/* slot/native ads need the placement id */
switch (true) {
case !!(params.inImage): bid.pi = 1; break;
case !!(params.inScreen): bid.pi = 2; break;
case !!(params.inSlot): bid.pi = 3; break;
case !!(params.native): bid.pi = 5; break;
default: return utils.logWarn(
`[GumGum] No product selected for the placement ${placementCode}` +
', please check your implementation.'
);
}
/* tracking id is required for in-image and in-screen */
if (trackingId) bid.t = trackingId;
/* native ads require a native placement id */
if (nativeId) bid.ni = nativeId;
/* slot ads require a slot id */
if (slotId) bid.si = slotId;

const cachedBid = Object.assign({
placementCode,
id: bidId
}, bid);

const callback = { jsonp: `$$PREBID_GLOBAL$$.handleGumGumCB['${ bidId }']` };
CALLBACKS[bidId] = _handleGumGumResponse(cachedBid);
const query = Object.assign(callback, browserParams, bid);
const bidCall = `${bidEndpoint}?${utils.parseQueryStringParameters(query)}`;
adloader.loadScript(bidCall);
});
}

const _handleGumGumResponse = cachedBidRequest => bidResponse => {
const ad = bidResponse && bidResponse.ad;
if (ad && ad.id) {
const bid = bidfactory.createBid(1);
const { t: trackingId
, pi: productId
, placementCode
} = cachedBidRequest;
bidResponse.placementCode = placementCode;
const encodedResponse = encodeURIComponent(JSON.stringify(bidResponse));
const gumgumAdLoader = `<script>
(function (context, topWindow, d, s, G) {
G = topWindow.GUMGUM;
d = topWindow.document;
function loadAd() {
topWindow.GUMGUM.pbjs("${ trackingId }", ${ productId }, "${ encodedResponse }" , context, topWindow);
}
if (G) {
loadAd();
} else {
topWindow.$$PREBID_GLOBAL$$.loadScript("https://g2.gumgum.com/javascripts/ggv2.js", loadAd);
}
}(window, top));
</script>`;
Object.assign(bid, {
cpm: ad.price,
ad: gumgumAdLoader,
width: ad.width,
height: ad.height,
bidderCode: BIDDER_CODE
});
bidmanager.addBidResponse(cachedBidRequest.placementCode, bid);
} else {
const noBid = bidfactory.createBid(2);
noBid.bidderCode = BIDDER_CODE;
bidmanager.addBidResponse(cachedBidRequest.placementCode, noBid);
}
delete CALLBACKS[cachedBidRequest.id];
};

window.$$PREBID_GLOBAL$$.handleGumGumCB = CALLBACKS;

return {
callBids: _callBids
};

};

module.exports = GumgumAdapter;
201 changes: 201 additions & 0 deletions test/spec/adapters/gumgum_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import {expect} from 'chai';
import Adapter from '../../../src/adapters/gumgum';
import bidManager from '../../../src/bidmanager';
import adLoader from '../../../src/adloader';
import { STATUS } from '../../../src/constants';

describe('gumgum adapter', () => {
'use strict';

let adapter;
let sandbox;

const TEST = {
PUBLISHER_IDENTITY: 'ggumtest',
BIDDER_CODE: 'gumgum',
PLACEMENT: 'placementId',
CPM: 2
};
const bidderRequest = {
bidderCode: TEST.BIDDER_CODE,
bids: [{ // in-screen
bidId: 'InScreenBidId',
bidder: TEST.BIDDER_CODE,
placementCode: TEST.PLACEMENT,
sizes: [ [728, 90] ],
params: {
inScreen: TEST.PUBLISHER_IDENTITY
}
}, { // in-image
bidId: 'InImageBidId',
bidder: TEST.BIDDER_CODE,
placementCode: TEST.PLACEMENT,
sizes: [ [728, 90] ],
params: {
inImage: TEST.PUBLISHER_IDENTITY
}
}, { // native
bidId: 'NativeBidId',
bidder: TEST.BIDDER_CODE,
placementCode: TEST.PLACEMENT,
sizes: [ [728, 90] ],
params: {
native: 10
}
}, { // slot
bidId: 'InSlotBidId',
bidder: TEST.BIDDER_CODE,
placementCode: TEST.PLACEMENT,
sizes: [ [728, 90] ],
params: {
inSlot: 10
}
}, { // no identity
bidId: 'NoIdentityBidId',
bidder: TEST.BIDDER_CODE,
placementCode: TEST.PLACEMENT,
sizes: [ [728, 90] ]
}]
};
const bidderResponse = {
"ad": {
"id": 1,
"width": 728,
"height": 90,
"markup": "<div>some fancy ad</div>",
"ii": true,
"du": "http://example.com/",
"price": TEST.CPM,
"impurl": "http://example.com/"
}
};

function mockBidResponse(response) {
sandbox.stub(bidManager, 'addBidResponse');
sandbox.stub(adLoader, 'loadScript');
adapter.callBids(bidderRequest);
pbjs.handleGumGumCB['InScreenBidId'](response);
return bidManager.addBidResponse.firstCall.args[1];
}

beforeEach(() => {
adapter = new Adapter();
sandbox = sinon.sandbox.create();
});

afterEach(() => {
sandbox.restore();
});

describe('callBids', () => {

beforeEach(() => {
sandbox.stub(adLoader, 'loadScript');
adapter.callBids(bidderRequest);
});

it('should call the endpoint once per valid bid', () => {
sinon.assert.callCount(adLoader.loadScript, 4);
});

it('should include required browser data', () => {
const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]);
endpointRequest.to.include('vw');
endpointRequest.to.include('vh');
endpointRequest.to.include('sw');
endpointRequest.to.include('sh');
});

it('should include the publisher identity', () => {
const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]);
endpointRequest.to.include('t=' + TEST.PUBLISHER_IDENTITY);
});

it('first call should be in-screen', () => {
expect(adLoader.loadScript.firstCall.args[0]).to.include('pi=2');
});

it('second call should be in-image', () => {
expect(adLoader.loadScript.secondCall.args[0]).to.include('pi=1');
});

it('third call should be native', () => {
expect(adLoader.loadScript.thirdCall.args[0]).to.include('pi=5');
});

it('last call should be slot', () => {
expect(adLoader.loadScript.lastCall.args[0]).to.include('pi=3');
});

});

describe('handleGumGumCB[...]', () => {
it('should exist and be a function', () => {
expect(pbjs.handleGumGumCB['InScreenBidId']).to.exist.and.to.be.a('function');
});
});

describe('respond with a successful bid', () => {

let successfulBid;

beforeEach(() => {
successfulBid = mockBidResponse(bidderResponse);
});

it('should add one bid', () => {
sinon.assert.calledOnce(bidManager.addBidResponse);
});

it('should pass the correct placement code as the first param', () => {
const [ placementCode ] = bidManager.addBidResponse.firstCall.args;
expect(placementCode).to.eql(TEST.PLACEMENT);
});

it('should have a GOOD status code', () => {
expect(successfulBid.getStatusCode()).to.eql(STATUS.GOOD);
});

it('should use the CPM returned by the server', () => {
expect(successfulBid).to.have.property('cpm', TEST.CPM);
});

it('should have an ad', () => {
expect(successfulBid).to.have.property('ad');
});

it('should have the size specified by the server', () => {
expect(successfulBid).to.have.property('width', 728);
expect(successfulBid).to.have.property('height', 90);
});

});

describe('respond with an empty bid', () => {

let noBid;

beforeEach(() => {
noBid = mockBidResponse({});
});

it('should add one bid', () => {
sinon.assert.calledOnce(bidManager.addBidResponse);
});

it('should have a NO_BID status code', () => {
expect(noBid.getStatusCode()).to.eql(STATUS.NO_BID);
});

it('should pass the correct placement code as the first parameter', () => {
const [ placementCode ] = bidManager.addBidResponse.firstCall.args;
expect(placementCode).to.eql(TEST.PLACEMENT);
});

it('should add the bidder code to the bid object', () => {
expect(noBid).to.have.property('bidderCode', TEST.BIDDER_CODE);
});

});

});

0 comments on commit 05d0bf9

Please sign in to comment.