From e722a30896e14e7b15c9febbe2ed266586506cbb Mon Sep 17 00:00:00 2001 From: Hashiomoto <50349637+HashimotoLogly@users.noreply.github.com> Date: Tue, 14 Dec 2021 00:13:23 +0900 Subject: [PATCH] Loglylift Bid Adapter: add new bid adapter (#7761) * make buildRequests * remove useless code * change ENDPOINTN_URL * add spec test * format code * fix serverResponse * add test * add overview * fix the bid adpter to adapt the actual response * treat adspotId as integer * add getUserSyncs * add serverResponses.length > 0 on 'if' condition * fix typo * use test adspotId which has a test ad * fix prebidJsVersion test * adapt advertiserDomains --- modules/loglyliftBidAdapter.js | 79 ++++++++ modules/loglyliftBidAdapter.md | 55 ++++++ test/spec/modules/loglyliftBidAdapter_spec.js | 172 ++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 modules/loglyliftBidAdapter.js create mode 100644 modules/loglyliftBidAdapter.md create mode 100644 test/spec/modules/loglyliftBidAdapter_spec.js diff --git a/modules/loglyliftBidAdapter.js b/modules/loglyliftBidAdapter.js new file mode 100644 index 00000000000..e1319d08766 --- /dev/null +++ b/modules/loglyliftBidAdapter.js @@ -0,0 +1,79 @@ +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { NATIVE } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'loglylift'; +const ENDPOINT_URL = 'https://bid.logly.co.jp/prebid/client/v1'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [NATIVE], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.adspotId); + }, + + buildRequests: function (bidRequests, bidderRequest) { + const requests = []; + for (let i = 0, len = bidRequests.length; i < len; i++) { + const request = { + method: 'POST', + url: ENDPOINT_URL + '?adspot_id=' + bidRequests[i].params.adspotId, + data: JSON.stringify(newBidRequest(bidRequests[i], bidderRequest)), + options: {}, + bidderRequest + }; + requests.push(request); + } + return requests; + }, + + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bidResponses = []; + if (!serverResponse || serverResponse.error) { + return bidResponses; + } + serverResponse.bids.forEach(function (bid) { + bidResponses.push(bid); + }) + return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'iframe', + url: 'https://sync.logly.co.jp/sync/sync.html' + }); + } + return syncs; + } + +}; + +function newBidRequest(bid, bidderRequest) { + const currencyObj = config.getConfig('currency'); + const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; + + return { + auctionId: bid.auctionId, + bidderRequestId: bid.bidderRequestId, + transactionId: bid.transactionId, + adUnitCode: bid.adUnitCode, + bidId: bid.bidId, + mediaTypes: bid.mediaTypes, + params: bid.params, + prebidJsVersion: '$prebid.version$', + url: window.location.href, + domain: config.getConfig('publisherDomain'), + referer: bidderRequest.refererInfo.referer, + auctionStartTime: bidderRequest.auctionStart, + currency: currency, + timeout: config.getConfig('bidderTimeout') + }; +} + +registerBidder(spec); diff --git a/modules/loglyliftBidAdapter.md b/modules/loglyliftBidAdapter.md new file mode 100644 index 00000000000..9bca238b03e --- /dev/null +++ b/modules/loglyliftBidAdapter.md @@ -0,0 +1,55 @@ +# Overview +``` +Module Name: LOGLY lift for Publisher +Module Type: Bidder Adapter +Maintainer: dev@logly.co.jp +``` + +# Description +Module that connects to Logly's demand sources. +Currently module supports only native mediaType. + +# Test Parameters +``` +var adUnits = [ + // Native adUnit + { + code: 'test-native-code', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'loglylift', + params: { + adspotId: 4302078 + } + }] + } +]; +``` + +# UserSync example + +``` +pbjs.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: '*', // '*' represents all bidders + filter: 'include' + } + } + } +}); +``` diff --git a/test/spec/modules/loglyliftBidAdapter_spec.js b/test/spec/modules/loglyliftBidAdapter_spec.js new file mode 100644 index 00000000000..6d5fe0c1e57 --- /dev/null +++ b/test/spec/modules/loglyliftBidAdapter_spec.js @@ -0,0 +1,172 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/loglyliftBidAdapter'; +import * as utils from 'src/utils.js'; + +describe('loglyliftBidAdapter', function () { + const nativeBidRequests = [{ + bidder: 'loglylift', + bidId: '254304ac29e265', + params: { + adspotId: 16 + }, + adUnitCode: '/19968336/prebid_native_example_1', + transactionId: '10aee457-617c-4572-ab5b-99df1d73ccb4', + sizes: [ + [] + ], + bidderRequestId: '15da3afd9632d7', + auctionId: 'f890b7d9-e787-4237-ac21-6d8554abac9f', + mediaTypes: { + native: { + body: { + required: true + }, + icon: { + required: false + }, + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'fakeReferer', + reachedTop: true, + numIframes: 1, + stack: [] + }, + auctionStart: 1632194172781, + bidderCode: 'loglylift', + bidderRequestId: '15da3afd9632d7', + auctionId: 'f890b7d9-e787-4237-ac21-6d8554abac9f', + timeout: 3000 + }; + + const nativeServerResponse = { + body: { + bids: [{ + requestId: '254304ac29e265', + cpm: 10.123, + width: 360, + height: 360, + creativeId: '123456789', + currency: 'JPY', + netRevenue: true, + ttl: 30, + meta: { + advertiserDomains: ['advertiserexample.com'] + }, + native: { + clickUrl: 'https://dsp.logly.co.jp/click?ad=EXAMPECLICKURL', + image: { + url: 'https://cdn.logly.co.jp/images/000/194/300/normal.jpg', + width: '360', + height: '360' + }, + impressionTrackers: [ + 'https://b.logly.co.jp/sorry.html' + ], + sponsoredBy: 'logly', + title: 'Native Title', + } + }], + } + }; + + describe('isBidRequestValid', function () { + it('should return true if the adspotId parameter is present', function () { + expect(spec.isBidRequestValid(nativeBidRequests[0])).to.be.true; + }); + + it('should return false if the adspotId parameter is not present', function () { + let bidRequest = utils.deepClone(nativeBidRequests[0]); + delete bidRequest.params.adspotId; + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('should generate a valid single POST request for multiple bid requests', function () { + const request = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.logly.co.jp/prebid/client/v1?adspot_id=16'); + expect(request.data).to.exist; + + const data = JSON.parse(request.data); + expect(data.auctionId).to.equal(nativeBidRequests[0].auctionId); + expect(data.bidderRequestId).to.equal(nativeBidRequests[0].bidderRequestId); + expect(data.transactionId).to.equal(nativeBidRequests[0].transactionId); + expect(data.adUnitCode).to.equal(nativeBidRequests[0].adUnitCode); + expect(data.bidId).to.equal(nativeBidRequests[0].bidId); + expect(data.mediaTypes).to.deep.equal(nativeBidRequests[0].mediaTypes); + expect(data.params).to.deep.equal(nativeBidRequests[0].params); + expect(data.prebidJsVersion).to.equal('6.5.0-pre'); + expect(data.url).to.exist; + expect(data.domain).to.exist; + expect(data.referer).to.equal(bidderRequest.refererInfo.referer); + expect(data.auctionStartTime).to.equal(bidderRequest.auctionStart); + expect(data.currency).to.exist; + expect(data.timeout).to.equal(bidderRequest.timeout); + }); + }); + + describe('interpretResponse', function () { + it('should return an empty array if an invalid response is passed', function () { + const interpretedResponse = spec.interpretResponse({}, {}); + expect(interpretedResponse).to.be.an('array').that.is.empty; + }); + + it('should return valid response when passed valid server response', function () { + const request = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; + const interpretedResponse = spec.interpretResponse(nativeServerResponse, request); + + expect(interpretedResponse).to.have.lengthOf(1); + expect(interpretedResponse[0].cpm).to.equal(nativeServerResponse.body.bids[0].cpm); + expect(interpretedResponse[0].width).to.equal(nativeServerResponse.body.bids[0].width); + expect(interpretedResponse[0].height).to.equal(nativeServerResponse.body.bids[0].height); + expect(interpretedResponse[0].creativeId).to.equal(nativeServerResponse.body.bids[0].creativeId); + expect(interpretedResponse[0].currency).to.equal(nativeServerResponse.body.bids[0].currency); + expect(interpretedResponse[0].netRevenue).to.equal(nativeServerResponse.body.bids[0].netRevenue); + expect(interpretedResponse[0].ttl).to.equal(nativeServerResponse.body.bids[0].ttl); + expect(interpretedResponse[0].native).to.deep.equal(nativeServerResponse.body.bids[0].native); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal(nativeServerResponse.body.bids[0].meta.advertiserDomains[0]); + }); + }); + + describe('getUserSync tests', function () { + it('UserSync test : check type = iframe, check usermatch URL', function () { + const syncOptions = { + 'iframeEnabled': true + } + let userSync = spec.getUserSyncs(syncOptions, [nativeServerResponse]); + expect(userSync[0].type).to.equal('iframe'); + const USER_SYNC_URL = 'https://sync.logly.co.jp/sync/sync.html'; + expect(userSync[0].url).to.equal(USER_SYNC_URL); + }); + + it('When iframeEnabled is false, no userSync should be returned', function () { + const syncOptions = { + 'iframeEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, [nativeServerResponse]); + expect(userSync).to.be.an('array').that.is.empty; + }); + + it('When nativeServerResponses empty, no userSync should be returned', function () { + const syncOptions = { + 'iframeEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, []); + expect(userSync).to.be.an('array').that.is.empty; + }); + }); +});