Skip to content

Commit

Permalink
BeOp Bid Adapter : New Bid Adapter (#7195)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
sebrobert and bloodyowl authored Aug 19, 2021
1 parent a437b05 commit 686c70c
Show file tree
Hide file tree
Showing 3 changed files with 357 additions and 0 deletions.
142 changes: 142 additions & 0 deletions modules/beopBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import * as utils from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { config } from '../src/config.js';
const BIDDER_CODE = 'beop';
const ENDPOINT_URL = 'https://hb.beop.io/bid';
const TCF_VENDOR_ID = 666;

const validIdRegExp = /^[0-9a-fA-F]{24}$/

export const spec = {
code: BIDDER_CODE,
gvlid: TCF_VENDOR_ID,
aliases: ['bp'],
/**
* Test if the bid request is valid.
*
* @param {bid} : The Bid params
* @return boolean true if the bid request is valid (aka contains a valid accountId or networkId and is open for BANNER), false otherwise.
*/
isBidRequestValid: function(bid) {
const id = bid.params.accountId || bid.params.networkId;
if (id === null || typeof id === 'undefined') {
return false
}
if (!validIdRegExp.test(id)) {
return false
}
return bid.mediaTypes.banner !== null && typeof bid.mediaTypes.banner !== 'undefined';
},
/**
* Create a BeOp server request from a list of BidRequest
*
* @param {validBidRequests[], ...} : The array of validated bidRequests
* @param {... , bidderRequest} : Common params for each bidRequests
* @return ServerRequest Info describing the request to the BeOp's server
*/
buildRequests: function(validBidRequests, bidderRequest) {
const slots = validBidRequests.map(beOpRequestSlotsMaker);
let pageUrl = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href');
let fpd = config.getLegacyFpd(config.getConfig('ortb2'));
let gdpr = bidderRequest.gdprConsent;
let firstSlot = slots[0];
let payloadObject = {
at: new Date().toString(),
nid: firstSlot.nid,
nptnid: firstSlot.nptnid,
pid: firstSlot.pid,
url: pageUrl,
lang: (window.navigator.language || window.navigator.languages[0]),
kwds: (fpd && fpd.site && fpd.site.keywords) || [],
dbg: false,
slts: slots,
is_amp: utils.deepAccess(bidderRequest, 'referrerInfo.isAmp'),
tc_string: (gdpr && gdpr.gdprApplies) ? gdpr.consentString : null,
};
const payloadString = JSON.stringify(payloadObject);
return {
method: 'POST',
url: ENDPOINT_URL,
data: payloadString
}
},
interpretResponse: function(serverResponse, request) {
if (serverResponse && serverResponse.body && utils.isArray(serverResponse.body.bids) && serverResponse.body.bids.length > 0) {
return serverResponse.body.bids;
}
return [];
},
onTimeout: function(timeoutData) {
if (timeoutData === null || typeof timeoutData === 'undefined' || Object.keys(timeoutData).length === 0) {
return;
}

let trackingParams = buildTrackingParams(timeoutData, 'timeout', timeoutData.timeout);

utils.logWarn(BIDDER_CODE + ': timed out request');
utils.triggerPixel(utils.buildUrl({
protocol: 'https',
hostname: 't.beop.io',
pathname: '/bid',
search: trackingParams
}));
},
onBidWon: function(bid) {
if (bid === null || typeof bid === 'undefined' || Object.keys(bid).length === 0) {
return;
}
let trackingParams = buildTrackingParams(bid, 'won', bid.cpm);

utils.logInfo(BIDDER_CODE + ': won request');
utils.triggerPixel(utils.buildUrl({
protocol: 'https',
hostname: 't.beop.io',
pathname: '/bid',
search: trackingParams
}));
},
onSetTargeting: function(bid) {}
}

function buildTrackingParams(data, info, value) {
return {
pid: data.params.accountId,
nid: data.params.networkId,
nptnid: data.params.networkPartnerId,
bid: data.bidId,
sl_n: data.adUnitCode,
aid: data.auctionId,
se_ca: 'bid',
se_ac: info,
se_va: value
};
}

function beOpRequestSlotsMaker(bid) {
const bannerSizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes');
const publisherCurrency = utils.getValue(bid.params, 'currency') || 'EUR';
let floor;
if (typeof bid.getFloor === 'function') {
const floorInfo = bid.getFloor({currency: publisherCurrency, mediaType: 'banner', size: [1, 1]});
if (typeof floorInfo === 'object' && floorInfo.currency === publisherCurrency && !isNaN(parseFloat(floorInfo.floor))) {
floor = parseFloat(floorInfo.floor);
}
}
return {
sizes: utils.isArray(bannerSizes) ? bannerSizes : bid.sizes,
flr: floor,
pid: utils.getValue(bid.params, 'accountId'),
nid: utils.getValue(bid.params, 'networkId'),
nptnid: utils.getValue(bid.params, 'networkPartnerId'),
bid: utils.getBidIdParameter('bidId', bid),
brid: utils.getBidIdParameter('bidderRequestId', bid),
name: utils.getBidIdParameter('adUnitCode', bid),
aid: utils.getBidIdParameter('auctionId', bid),
tid: utils.getBidIdParameter('transactionId', bid),
brc: utils.getBidIdParameter('bidRequestsCount', bid),
bdrc: utils.getBidIdParameter('bidderRequestCount', bid),
bwc: utils.getBidIdParameter('bidderWinsCount', bid),
}
}

registerBidder(spec);
33 changes: 33 additions & 0 deletions modules/beopBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Overview

**Module Name** : BeOp Bidder Adapter
**Module Type** : Bidder Adapter
**Maintainer** : tech@beop.io

# Description

Module that connects to BeOp's demand sources

# Test Parameters
```
var adUnits = [
{
code: 'in-article',
mediaTypes: {
banner: {
sizes: [[1,1]],
}
},
bids: [
{
bidder: "beop",
params: {
accountId: '5a8af500c9e77c00017e4cad',
currency: 'EUR'
}
}
]
}
];
```

182 changes: 182 additions & 0 deletions test/spec/modules/beopBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { expect } from 'chai';
import { spec } from 'modules/beopBidAdapter.js';
import { newBidder } from 'src/adapters/bidderFactory.js';
const utils = require('src/utils');

const ENDPOINT = 'https://hb.beop.io/bid';

let validBid = {
'bidder': 'beop',
'params': {
'accountId': '5a8af500c9e77c00017e4cad'
},
'adUnitCode': 'bellow-article',
'mediaTypes': {
'banner': {
'sizes': [[1, 1]]
}
},
'bidId': '30b31c1838de1e',
'bidderRequestId': '22edbae2733bf6',
'auctionId': '1d1a030790a475',
'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843',
'creativeId': 'er2ee'
};

describe('BeOp Bid Adapter tests', () => {
const adapter = newBidder(spec);

describe('inherited functions', () => {
it('exists and is a function', () => {
expect(adapter.callBids).to.exist.and.to.be.a('function');
});
});

describe('isBidRequestValid', function() {
it('should return true when accountId params found', function () {
expect(spec.isBidRequestValid(validBid)).to.equal(true);
});

it('should return true if no accountId but networkId', function () {
let bid = Object.assign({}, validBid);
delete bid.params;
bid.params = {
'networkId': '5a8af500c9e77c00017e4aaa'
};
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return false if neither account or network id param found', function () {
let bid = Object.assign({}, validBid);
delete bid.params;
bid.params = {
'someId': '5a8af500c9e77c00017e4aaa'
};
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('should return false if account Id param is not an ObjectId', function () {
let bid = Object.assign({}, validBid);
delete bid.params;
bid.params = {
'someId': '12345'
};
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('should return false if there is no banner media type', function () {
let bid = Object.assign({}, validBid);
delete bid.mediaTypes;
bid.mediaTypes = {
'native': {
'sizes': [[1, 1]]
}
};
expect(spec.isBidRequestValid(bid)).to.equal(false);
});
});

describe('buildRequests', function () {
let bidRequests = [];
bidRequests.push(validBid);

it('should build the request', function () {
const request = spec.buildRequests(bidRequests, {});
const payload = JSON.parse(request.data);
const url = request.url;
expect(url).to.equal(ENDPOINT);
expect(payload.pid).to.exist;
expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad');
expect(payload.slts[0].name).to.exist;
expect(payload.slts[0].name).to.equal('bellow-article');
});

it('should call the endpoint with GDPR consent and pageURL info if found', function () {
let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==';
let bidderRequest =
{
'gdprConsent':
{
'gdprApplies': true,
'consentString': consentString
},
'refererInfo':
{
'canonicalUrl': 'http://test.te'
}
};

const request = spec.buildRequests(bidRequests, bidderRequest);
const payload = JSON.parse(request.data);
expect(payload.tc_string).to.exist;
expect(payload.tc_string).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ+A==');
expect(payload.url).to.exist;
expect(payload.url).to.equal('http://test.te');
});
});

describe('interpretResponse', function() {
let serverResponse = {
'body': {
'bids': [
{
'requestId': 'aaaa',
'cpm': 1.0,
'currency': 'EUR',
'creativeId': '60f691be1515670a2a09aea2',
'netRevenue': true,
'width': 1,
'height': 1,
'ad': '<div class="BeOpWidget" data-content-id="60f691be1515670a2a09aea2" data-campaign-id="60f691bf1515670a2a09aea6" data-display-account-id="60f691be1515670a2a09aea1"></div>',
'meta': {
'advertiserId': '60f691be1515670a2a09aea1'
}
}
]
}
}
it('should interpret the response by pushing it in the bids elem', function () {
const response = spec.interpretResponse(serverResponse, validBid);

expect(response[0].ad).to.exist;
expect(response[0].requestId).to.exist;
expect(response[0].requestId).to.equal('aaaa');
});
});

describe('timeout and bid won pixel trigger', function () {
let triggerPixelStub;

beforeEach(function () {
triggerPixelStub = sinon.stub(utils, 'triggerPixel');
});

afterEach(function () {
utils.triggerPixel.restore();
});

it('should call triggerPixel utils function when timed out is filled', function () {
spec.onTimeout({});
spec.onTimeout();
expect(triggerPixelStub.getCall(0)).to.be.null;
spec.onTimeout({params: {accountId: '5a8af500c9e77c00017e4cad'}, timeout: 2000});
expect(triggerPixelStub.getCall(0)).to.not.be.null;
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io');
expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid');
expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=timeout');
expect(triggerPixelStub.getCall(0).args[0]).to.include('pid=5a8af500c9e77c00017e4cad');
});

it('should call triggerPixel utils function on bid won', function () {
spec.onBidWon({});
spec.onBidWon();
expect(triggerPixelStub.getCall(0)).to.be.null;
spec.onBidWon({params: {accountId: '5a8af500c9e77c00017e4cad'}, cpm: 1.2});
expect(triggerPixelStub.getCall(0)).to.not.be.null;
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io');
expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid');
expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=won');
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('pid=5a8af500c9e77c00017e4cad');
});
});
});

0 comments on commit 686c70c

Please sign in to comment.