Skip to content

Commit 686c70c

Browse files
sebrobertbloodyowl
andauthoredAug 19, 2021
BeOp Bid Adapter : New Bid Adapter (prebid#7195)
* Init BeOp adapter * Init BeOp prebid Adapter (#1) * Init BeOp prebid Adapter * Partial commit * TC String, currency, floor * onTimeout fn implem * onBidWon implem * common tracking setup and still testing * Fix tests * Final test * Add tests on consent and response * Post review Commit * change markdown bidder name and sizes in examples * Change BeOp endpoint to get bid responses * Valid params to test the module * Remove package-lock changes * Fix keyword access * Fix Co-authored-by: bloodyowl <bloodyowl@icloud.com>
1 parent a437b05 commit 686c70c

File tree

3 files changed

+357
-0
lines changed

3 files changed

+357
-0
lines changed
 

‎modules/beopBidAdapter.js

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import * as utils from '../src/utils.js';
2+
import { registerBidder } from '../src/adapters/bidderFactory.js';
3+
import { config } from '../src/config.js';
4+
const BIDDER_CODE = 'beop';
5+
const ENDPOINT_URL = 'https://hb.beop.io/bid';
6+
const TCF_VENDOR_ID = 666;
7+
8+
const validIdRegExp = /^[0-9a-fA-F]{24}$/
9+
10+
export const spec = {
11+
code: BIDDER_CODE,
12+
gvlid: TCF_VENDOR_ID,
13+
aliases: ['bp'],
14+
/**
15+
* Test if the bid request is valid.
16+
*
17+
* @param {bid} : The Bid params
18+
* @return boolean true if the bid request is valid (aka contains a valid accountId or networkId and is open for BANNER), false otherwise.
19+
*/
20+
isBidRequestValid: function(bid) {
21+
const id = bid.params.accountId || bid.params.networkId;
22+
if (id === null || typeof id === 'undefined') {
23+
return false
24+
}
25+
if (!validIdRegExp.test(id)) {
26+
return false
27+
}
28+
return bid.mediaTypes.banner !== null && typeof bid.mediaTypes.banner !== 'undefined';
29+
},
30+
/**
31+
* Create a BeOp server request from a list of BidRequest
32+
*
33+
* @param {validBidRequests[], ...} : The array of validated bidRequests
34+
* @param {... , bidderRequest} : Common params for each bidRequests
35+
* @return ServerRequest Info describing the request to the BeOp's server
36+
*/
37+
buildRequests: function(validBidRequests, bidderRequest) {
38+
const slots = validBidRequests.map(beOpRequestSlotsMaker);
39+
let pageUrl = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href');
40+
let fpd = config.getLegacyFpd(config.getConfig('ortb2'));
41+
let gdpr = bidderRequest.gdprConsent;
42+
let firstSlot = slots[0];
43+
let payloadObject = {
44+
at: new Date().toString(),
45+
nid: firstSlot.nid,
46+
nptnid: firstSlot.nptnid,
47+
pid: firstSlot.pid,
48+
url: pageUrl,
49+
lang: (window.navigator.language || window.navigator.languages[0]),
50+
kwds: (fpd && fpd.site && fpd.site.keywords) || [],
51+
dbg: false,
52+
slts: slots,
53+
is_amp: utils.deepAccess(bidderRequest, 'referrerInfo.isAmp'),
54+
tc_string: (gdpr && gdpr.gdprApplies) ? gdpr.consentString : null,
55+
};
56+
const payloadString = JSON.stringify(payloadObject);
57+
return {
58+
method: 'POST',
59+
url: ENDPOINT_URL,
60+
data: payloadString
61+
}
62+
},
63+
interpretResponse: function(serverResponse, request) {
64+
if (serverResponse && serverResponse.body && utils.isArray(serverResponse.body.bids) && serverResponse.body.bids.length > 0) {
65+
return serverResponse.body.bids;
66+
}
67+
return [];
68+
},
69+
onTimeout: function(timeoutData) {
70+
if (timeoutData === null || typeof timeoutData === 'undefined' || Object.keys(timeoutData).length === 0) {
71+
return;
72+
}
73+
74+
let trackingParams = buildTrackingParams(timeoutData, 'timeout', timeoutData.timeout);
75+
76+
utils.logWarn(BIDDER_CODE + ': timed out request');
77+
utils.triggerPixel(utils.buildUrl({
78+
protocol: 'https',
79+
hostname: 't.beop.io',
80+
pathname: '/bid',
81+
search: trackingParams
82+
}));
83+
},
84+
onBidWon: function(bid) {
85+
if (bid === null || typeof bid === 'undefined' || Object.keys(bid).length === 0) {
86+
return;
87+
}
88+
let trackingParams = buildTrackingParams(bid, 'won', bid.cpm);
89+
90+
utils.logInfo(BIDDER_CODE + ': won request');
91+
utils.triggerPixel(utils.buildUrl({
92+
protocol: 'https',
93+
hostname: 't.beop.io',
94+
pathname: '/bid',
95+
search: trackingParams
96+
}));
97+
},
98+
onSetTargeting: function(bid) {}
99+
}
100+
101+
function buildTrackingParams(data, info, value) {
102+
return {
103+
pid: data.params.accountId,
104+
nid: data.params.networkId,
105+
nptnid: data.params.networkPartnerId,
106+
bid: data.bidId,
107+
sl_n: data.adUnitCode,
108+
aid: data.auctionId,
109+
se_ca: 'bid',
110+
se_ac: info,
111+
se_va: value
112+
};
113+
}
114+
115+
function beOpRequestSlotsMaker(bid) {
116+
const bannerSizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes');
117+
const publisherCurrency = utils.getValue(bid.params, 'currency') || 'EUR';
118+
let floor;
119+
if (typeof bid.getFloor === 'function') {
120+
const floorInfo = bid.getFloor({currency: publisherCurrency, mediaType: 'banner', size: [1, 1]});
121+
if (typeof floorInfo === 'object' && floorInfo.currency === publisherCurrency && !isNaN(parseFloat(floorInfo.floor))) {
122+
floor = parseFloat(floorInfo.floor);
123+
}
124+
}
125+
return {
126+
sizes: utils.isArray(bannerSizes) ? bannerSizes : bid.sizes,
127+
flr: floor,
128+
pid: utils.getValue(bid.params, 'accountId'),
129+
nid: utils.getValue(bid.params, 'networkId'),
130+
nptnid: utils.getValue(bid.params, 'networkPartnerId'),
131+
bid: utils.getBidIdParameter('bidId', bid),
132+
brid: utils.getBidIdParameter('bidderRequestId', bid),
133+
name: utils.getBidIdParameter('adUnitCode', bid),
134+
aid: utils.getBidIdParameter('auctionId', bid),
135+
tid: utils.getBidIdParameter('transactionId', bid),
136+
brc: utils.getBidIdParameter('bidRequestsCount', bid),
137+
bdrc: utils.getBidIdParameter('bidderRequestCount', bid),
138+
bwc: utils.getBidIdParameter('bidderWinsCount', bid),
139+
}
140+
}
141+
142+
registerBidder(spec);

‎modules/beopBidAdapter.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Overview
2+
3+
**Module Name** : BeOp Bidder Adapter
4+
**Module Type** : Bidder Adapter
5+
**Maintainer** : tech@beop.io
6+
7+
# Description
8+
9+
Module that connects to BeOp's demand sources
10+
11+
# Test Parameters
12+
```
13+
var adUnits = [
14+
{
15+
code: 'in-article',
16+
mediaTypes: {
17+
banner: {
18+
sizes: [[1,1]],
19+
}
20+
},
21+
bids: [
22+
{
23+
bidder: "beop",
24+
params: {
25+
accountId: '5a8af500c9e77c00017e4cad',
26+
currency: 'EUR'
27+
}
28+
}
29+
]
30+
}
31+
];
32+
```
33+
+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { expect } from 'chai';
2+
import { spec } from 'modules/beopBidAdapter.js';
3+
import { newBidder } from 'src/adapters/bidderFactory.js';
4+
const utils = require('src/utils');
5+
6+
const ENDPOINT = 'https://hb.beop.io/bid';
7+
8+
let validBid = {
9+
'bidder': 'beop',
10+
'params': {
11+
'accountId': '5a8af500c9e77c00017e4cad'
12+
},
13+
'adUnitCode': 'bellow-article',
14+
'mediaTypes': {
15+
'banner': {
16+
'sizes': [[1, 1]]
17+
}
18+
},
19+
'bidId': '30b31c1838de1e',
20+
'bidderRequestId': '22edbae2733bf6',
21+
'auctionId': '1d1a030790a475',
22+
'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843',
23+
'creativeId': 'er2ee'
24+
};
25+
26+
describe('BeOp Bid Adapter tests', () => {
27+
const adapter = newBidder(spec);
28+
29+
describe('inherited functions', () => {
30+
it('exists and is a function', () => {
31+
expect(adapter.callBids).to.exist.and.to.be.a('function');
32+
});
33+
});
34+
35+
describe('isBidRequestValid', function() {
36+
it('should return true when accountId params found', function () {
37+
expect(spec.isBidRequestValid(validBid)).to.equal(true);
38+
});
39+
40+
it('should return true if no accountId but networkId', function () {
41+
let bid = Object.assign({}, validBid);
42+
delete bid.params;
43+
bid.params = {
44+
'networkId': '5a8af500c9e77c00017e4aaa'
45+
};
46+
expect(spec.isBidRequestValid(bid)).to.equal(true);
47+
});
48+
49+
it('should return false if neither account or network id param found', function () {
50+
let bid = Object.assign({}, validBid);
51+
delete bid.params;
52+
bid.params = {
53+
'someId': '5a8af500c9e77c00017e4aaa'
54+
};
55+
expect(spec.isBidRequestValid(bid)).to.equal(false);
56+
});
57+
58+
it('should return false if account Id param is not an ObjectId', function () {
59+
let bid = Object.assign({}, validBid);
60+
delete bid.params;
61+
bid.params = {
62+
'someId': '12345'
63+
};
64+
expect(spec.isBidRequestValid(bid)).to.equal(false);
65+
});
66+
67+
it('should return false if there is no banner media type', function () {
68+
let bid = Object.assign({}, validBid);
69+
delete bid.mediaTypes;
70+
bid.mediaTypes = {
71+
'native': {
72+
'sizes': [[1, 1]]
73+
}
74+
};
75+
expect(spec.isBidRequestValid(bid)).to.equal(false);
76+
});
77+
});
78+
79+
describe('buildRequests', function () {
80+
let bidRequests = [];
81+
bidRequests.push(validBid);
82+
83+
it('should build the request', function () {
84+
const request = spec.buildRequests(bidRequests, {});
85+
const payload = JSON.parse(request.data);
86+
const url = request.url;
87+
expect(url).to.equal(ENDPOINT);
88+
expect(payload.pid).to.exist;
89+
expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad');
90+
expect(payload.slts[0].name).to.exist;
91+
expect(payload.slts[0].name).to.equal('bellow-article');
92+
});
93+
94+
it('should call the endpoint with GDPR consent and pageURL info if found', function () {
95+
let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==';
96+
let bidderRequest =
97+
{
98+
'gdprConsent':
99+
{
100+
'gdprApplies': true,
101+
'consentString': consentString
102+
},
103+
'refererInfo':
104+
{
105+
'canonicalUrl': 'http://test.te'
106+
}
107+
};
108+
109+
const request = spec.buildRequests(bidRequests, bidderRequest);
110+
const payload = JSON.parse(request.data);
111+
expect(payload.tc_string).to.exist;
112+
expect(payload.tc_string).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ+A==');
113+
expect(payload.url).to.exist;
114+
expect(payload.url).to.equal('http://test.te');
115+
});
116+
});
117+
118+
describe('interpretResponse', function() {
119+
let serverResponse = {
120+
'body': {
121+
'bids': [
122+
{
123+
'requestId': 'aaaa',
124+
'cpm': 1.0,
125+
'currency': 'EUR',
126+
'creativeId': '60f691be1515670a2a09aea2',
127+
'netRevenue': true,
128+
'width': 1,
129+
'height': 1,
130+
'ad': '<div class="BeOpWidget" data-content-id="60f691be1515670a2a09aea2" data-campaign-id="60f691bf1515670a2a09aea6" data-display-account-id="60f691be1515670a2a09aea1"></div>',
131+
'meta': {
132+
'advertiserId': '60f691be1515670a2a09aea1'
133+
}
134+
}
135+
]
136+
}
137+
}
138+
it('should interpret the response by pushing it in the bids elem', function () {
139+
const response = spec.interpretResponse(serverResponse, validBid);
140+
141+
expect(response[0].ad).to.exist;
142+
expect(response[0].requestId).to.exist;
143+
expect(response[0].requestId).to.equal('aaaa');
144+
});
145+
});
146+
147+
describe('timeout and bid won pixel trigger', function () {
148+
let triggerPixelStub;
149+
150+
beforeEach(function () {
151+
triggerPixelStub = sinon.stub(utils, 'triggerPixel');
152+
});
153+
154+
afterEach(function () {
155+
utils.triggerPixel.restore();
156+
});
157+
158+
it('should call triggerPixel utils function when timed out is filled', function () {
159+
spec.onTimeout({});
160+
spec.onTimeout();
161+
expect(triggerPixelStub.getCall(0)).to.be.null;
162+
spec.onTimeout({params: {accountId: '5a8af500c9e77c00017e4cad'}, timeout: 2000});
163+
expect(triggerPixelStub.getCall(0)).to.not.be.null;
164+
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io');
165+
expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid');
166+
expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=timeout');
167+
expect(triggerPixelStub.getCall(0).args[0]).to.include('pid=5a8af500c9e77c00017e4cad');
168+
});
169+
170+
it('should call triggerPixel utils function on bid won', function () {
171+
spec.onBidWon({});
172+
spec.onBidWon();
173+
expect(triggerPixelStub.getCall(0)).to.be.null;
174+
spec.onBidWon({params: {accountId: '5a8af500c9e77c00017e4cad'}, cpm: 1.2});
175+
expect(triggerPixelStub.getCall(0)).to.not.be.null;
176+
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io');
177+
expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid');
178+
expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=won');
179+
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('pid=5a8af500c9e77c00017e4cad');
180+
});
181+
});
182+
});

0 commit comments

Comments
 (0)
Failed to load comments.