From f6c90e022e168a72b85151e10e9a5a4d8a2583f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Robakowski?= Date: Wed, 19 Apr 2023 08:06:35 +0200 Subject: [PATCH] Eskimi Bid Adapter: initial adapter release (#9768) * rewrite the adapter to use ortbConverter * Eskimi: fix `isBidRequestValid` * Eskimi: fix request validation tests --------- Co-authored-by: Sekandar --- modules/eskimiBidAdapter.js | 72 ++++++++++ modules/eskimiBidAdapter.md | 34 +++++ test/spec/modules/eskimiBidAdapter_spec.js | 155 +++++++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100644 modules/eskimiBidAdapter.js create mode 100644 modules/eskimiBidAdapter.md create mode 100644 test/spec/modules/eskimiBidAdapter_spec.js diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js new file mode 100644 index 00000000000..4a00b97614b --- /dev/null +++ b/modules/eskimiBidAdapter.js @@ -0,0 +1,72 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' + +const BIDDER_CODE = 'eskimi'; +// const ENDPOINT = 'https://hb.eskimi.com/bids' +const ENDPOINT = 'https://sspback.eskimi.com/bid-request' + +const DEFAULT_BID_TTL = 30; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const GVLID = 814; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.placementId; + }, + + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + + let bid = bidRequests.find((b) => b.params.placementId) + if (!data.site) data.site = {} + data.site.ext = {placementId: bid.params.placementId} + + if (bidderRequest.gdprConsent) { + if (!data.user) data.user = {}; + if (!data.user.ext) data.user.ext = {}; + if (!data.regs) data.regs = {}; + if (!data.regs.ext) data.regs.ext = {}; + data.user.ext.consent = bidderRequest.gdprConsent.consentString; + data.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + + return [{ + method: 'POST', + url: ENDPOINT, + data, + options: {contentType: 'application/json;charset=UTF-8', withCredentials: false} + }] + }, + + interpretResponse(response, request) { + return converter.fromORTB({response: response.body, request: request.data}).bids; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function (bid) { + if (bid.burl) { + utils.triggerPixel(bid.burl); + } + } +} + +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY, + mediaType: BANNER // TODO: support more types, we should set mtype on the winning bid + } +}); + +registerBidder(spec); diff --git a/modules/eskimiBidAdapter.md b/modules/eskimiBidAdapter.md new file mode 100644 index 00000000000..83ae87fd01b --- /dev/null +++ b/modules/eskimiBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +Module Name: ESKIMI Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@eskimi.com + +# Description + +An adapter to get a bid from Eskimi DSP. + +# Test Parameters +```javascript + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + + bids: [{ + bidder: 'eskimi', + params: { + placementId: 612 + } + }] + + }]; +``` + +Where: + +* placementId - Placement ID of the ad unit (required) + diff --git a/test/spec/modules/eskimiBidAdapter_spec.js b/test/spec/modules/eskimiBidAdapter_spec.js new file mode 100644 index 00000000000..4622b374de5 --- /dev/null +++ b/test/spec/modules/eskimiBidAdapter_spec.js @@ -0,0 +1,155 @@ +import {expect} from 'chai'; +import {spec} from 'modules/eskimiBidAdapter.js'; + +const REQUEST = { + 'bidderCode': 'eskimi', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'eskimi', + 'params': { + 'placementId': 1003000, + 'accId': 123 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId1', + 'adUnitCode': 'adUnitCode1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'eskimi', + 'params': { + 'placementId': 1003001, + 'accId': 123 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId2', + 'adUnitCode': 'adUnitCode2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'requestId', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'bidId1', + 'impid': 'bidId1', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'eskimi': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'seat' + } + ] + } +}; + +describe('Eskimi bid adapter', function () { + describe('isBidRequestValid', function () { + it('should accept request if placementId is passed', function () { + let bid = { + bidder: 'eskimi', + params: { + placementId: 123 + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('reject requests without params', function () { + let bid = { + bidder: 'eskimi', + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('creates request data', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + expect(request).to.exist.and.to.be.a('object'); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(REQUEST.bidRequest, req)[0]; + + const payload = request.data; + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + }); + + describe('interpretResponse', function () { + it('has bids', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + let bids = spec.interpretResponse(RESPONSE, request); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].id); + expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index]).to.have.property('ttl', 30); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, request); + expect(bids).to.be.empty; + }); + }); +});