diff --git a/modules/talkadsBidAdapter.js b/modules/talkadsBidAdapter.js new file mode 100644 index 00000000000..f95456b5c54 --- /dev/null +++ b/modules/talkadsBidAdapter.js @@ -0,0 +1,129 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; + +const CURRENCY = 'EUR'; +const BIDDER_CODE = 'talkads'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [ NATIVE, BANNER ], + params: null, + + /** + * Determines whether or not the given bid request is valid. + * + * @param poBid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (poBid) => { + utils.logInfo('isBidRequestValid : ', poBid); + if (poBid.params === undefined) { + utils.logError('VALIDATION FAILED : the parameters must be defined'); + return false; + } + if (poBid.params.tag_id === undefined) { + utils.logError('VALIDATION FAILED : the parameter "tag_id" must be defined'); + return false; + } + if (poBid.params.bidder_url === undefined) { + utils.logError('VALIDATION FAILED : the parameter "bidder_url" must be defined'); + return false; + } + this.params = poBid.params; + return !!(poBid.nativeParams || poBid.sizes); + }, // isBidRequestValid + + /** + * Make a server request from the list of BidRequests. + * + * @param paValidBidRequests An array of bids + * @param poBidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (paValidBidRequests, poBidderRequest) => { + utils.logInfo('buildRequests : ', paValidBidRequests, poBidderRequest); + const laBids = paValidBidRequests.map((poBid, piId) => { + const loOne = { id: piId, ad_unit: poBid.adUnitCode, bid_id: poBid.bidId, type: '', size: [] }; + if (poBid.nativeParams) { + loOne.type = 'native'; + } else { + loOne.type = 'banner'; + loOne.size = poBid.sizes; + } + return loOne; + }); + const loServerRequest = { + cur: CURRENCY, + timeout: poBidderRequest.timeout, + auction_id: paValidBidRequests[0].auctionId, + transaction_id: paValidBidRequests[0].transactionId, + bids: laBids, + gdpr: { applies: false, consent: false }, + }; + if (poBidderRequest && poBidderRequest.gdprConsent) { + const loGdprConsent = poBidderRequest.gdprConsent; + if ((typeof loGdprConsent.gdprApplies === 'boolean') && loGdprConsent.gdprApplies) { + loServerRequest.gdpr.applies = true; + } + if ((typeof loGdprConsent.consentString === 'string') && loGdprConsent.consentString) { + loServerRequest.gdpr.consent = poBidderRequest.gdprConsent.consentString; + } + } + const lsUrl = this.params.bidder_url + '/' + this.params.tag_id; + return { + method: 'POST', + url: lsUrl, + data: JSON.stringify(loServerRequest), + }; + }, // buildRequests + + /** + * Unpack the response from the server into a list of bids. + * + * @param poServerResponse A successful response from the server. + * @param poPidRequest Request original server request + * @return An array of bids which were nested inside the server. + */ + interpretResponse: (poServerResponse, poPidRequest) => { + utils.logInfo('interpretResponse : ', poServerResponse); + if (!poServerResponse.body) { + return []; + } + let laResponse = []; + if (poServerResponse.body.status !== 'ok') { + utils.logInfo('Error : ', poServerResponse.body.error); + return laResponse; + } + poServerResponse.body.bids.forEach((poResponse) => { + laResponse[laResponse.length] = { + requestId: poResponse.requestId, + cpm: poResponse.cpm, + currency: poResponse.currency, + width: poResponse.width, + height: poResponse.height, + ad: poResponse.ad, + ttl: poResponse.ttl, + creativeId: poResponse.creativeId, + netRevenue: poResponse.netRevenue, + pbid: poServerResponse.body.pbid, + }; + }); + return laResponse; + }, // interpretResponse + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction. + * + * @param poBid The bid that won the auction + */ + onBidWon: (poBid) => { + utils.logInfo('onBidWon : ', poBid); + if (poBid.pbid) { + ajax(this.params.bidder_url + 'won/' + poBid.pbid); + } + }, // onBidWon +}; + +registerBidder(spec); diff --git a/modules/talkadsBidAdapter.md b/modules/talkadsBidAdapter.md new file mode 100644 index 00000000000..3ab8aaa8354 --- /dev/null +++ b/modules/talkadsBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +``` +Module Name: TalkAds Adapter +Module Type: Bidder Adapter +Maintainer: technical_team@natexo.com +``` + +# Description + +Module that connects to TalkAds bidder to fetch bids. +Both native and banner formats are supported but not at the same time. +The only currently supported currency is EUR. + +This adapter requires setup and approval from the Natexo programmatic team. + +# Configuration + + + + +# Test parameters + +## Test banner Parameters + +``` + var adUnits = [ + code: 'prebid_banner_test', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: 'talkads', + params: { + tag_id: 0, + bidder_url: 'https://d.natexo-programmatic.com/tad/tag/testbid', + }, + }] + ]; +``` + +## Test native parameters + +``` + var adUnits = [ + code: 'prebid_native_test', + mediaTypes: { + native: {} + }, + bids: [{ + bidder: 'talkads', + params: { + tag_id: 0, + bidder_url: 'https://d.natexo-programmatic.com/tad/tag/testbid', + }, + }] + ]; +``` diff --git a/test/spec/modules/talkadsBidAdapter_spec.js b/test/spec/modules/talkadsBidAdapter_spec.js new file mode 100644 index 00000000000..00f52ba7b6a --- /dev/null +++ b/test/spec/modules/talkadsBidAdapter_spec.js @@ -0,0 +1,231 @@ +import {expect} from 'chai'; +import {spec} from 'modules/talkadsBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config'; +import {server} from '../../mocks/xhr'; + +describe('TalkAds adapter', function () { + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + timeout: 3000, + } + const commonBidRequest = { + bidder: 'talkads', + params: { + tag_id: 999999, + bidder_url: 'https://test.natexo-programmatic.com/tad/tag/prebid', + }, + bidId: '1a2b3c4d56e7f0', + auctionId: '12345678-1234-1a2b-3c4d-1a2b3c4d56e7', + transactionId: '4f68b713-04ba-4d7f-8df9-643bcdab5efb', + }; + const nativeBidRequestParams = { + nativeParams: {}, + }; + const bannerBidRequestParams = { + sizes: [[300, 250], [300, 600]], + }; + + /** + * isBidRequestValid + */ + describe('isBidRequestValid1', function() { + it('should fail when config is invalid', function () { + const bidRequest = { + ...commonBidRequest, + ...bannerBidRequestParams, + }; + bidRequest.params = Object.assign({}, bidRequest.params); + delete bidRequest.params; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); // isBidRequestValid1 + describe('isBidRequestValid2', function() { + it('should fail when config is invalid', function () { + const bidRequest = { + ...commonBidRequest, + ...bannerBidRequestParams, + }; + bidRequest.params = Object.assign({}, bidRequest.params); + delete bidRequest.params.bidder_url; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); // isBidRequestValid2 + describe('isBidRequestValid3', function() { + it('should fail when config is invalid', function () { + const bidRequest = { + ...commonBidRequest, + ...bannerBidRequestParams, + }; + bidRequest.params = Object.assign({}, bidRequest.params); + delete bidRequest.params.tag_id; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); // isBidRequestValid3 + describe('isBidRequestValid4', function() { + let bidRequest = { + ...commonBidRequest, + ...bannerBidRequestParams, + }; + it('should succeed when a banner bid is valid', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + }; + it('should succeed when a native bid is valid', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }); // isBidRequestValid4 + + /** + * buildRequests + */ + describe('buildRequests1', function() { + let bidRequest = { + ...commonBidRequest, + ...bannerBidRequestParams, + }; + const loServerRequest = { + cur: 'EUR', + timeout: commonBidderRequest.timeout, + auction_id: commonBidRequest.auctionId, + transaction_id: commonBidRequest.transactionId, + bids: [{ id: 0, bid_id: commonBidRequest.bidId, type: 'banner', size: bannerBidRequestParams.sizes }], + gdpr: { applies: false, consent: false }, + }; + it('should generate a valid banner bid request', function () { + let laResponse = spec.buildRequests([bidRequest], commonBidderRequest); + expect(laResponse.method).to.equal('POST'); + expect(laResponse.url).to.equal('https://test.natexo-programmatic.com/tad/tag/prebid/999999'); + expect(laResponse.data).to.equal(JSON.stringify(loServerRequest)); + }); + }); // buildRequests1 + describe('buildRequests2', function() { + let bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + }; + const loServerRequest = { + cur: 'EUR', + timeout: commonBidderRequest.timeout, + auction_id: commonBidRequest.auctionId, + transaction_id: commonBidRequest.transactionId, + bids: [{ id: 0, bid_id: commonBidRequest.bidId, type: 'native', size: [] }], + gdpr: { applies: false, consent: false }, + }; + it('should generate a valid native bid request', function () { + let laResponse = spec.buildRequests([bidRequest], commonBidderRequest); + expect(laResponse.method).to.equal('POST'); + expect(laResponse.url).to.equal('https://test.natexo-programmatic.com/tad/tag/prebid/999999'); + expect(laResponse.data).to.equal(JSON.stringify(loServerRequest)); + }); + const bidderRequest = { + ...commonBidderRequest, + gdprConsent: { gdprApplies: true, consentString: 'yes' } + }; + const loServerRequest2 = { + ...loServerRequest, + gdpr: { applies: true, consent: 'yes' }, + }; + it('should generate a valid native bid request', function () { + let laResponse = spec.buildRequests([bidRequest], bidderRequest); + expect(laResponse.method).to.equal('POST'); + expect(laResponse.url).to.equal('https://test.natexo-programmatic.com/tad/tag/prebid/999999'); + expect(laResponse.data).to.equal(JSON.stringify(loServerRequest2)); + }); + }); // buildRequests2 + + /** + * interpretResponse + */ + describe('interpretResponse1', function() { + it('should return empty array if no valid bids', function () { + const laResult = spec.interpretResponse({}, []) + expect(laResult).to.be.an('array').that.is.empty; + }); + const loServerResult = { + body: { status: 'error', error: 'aie' } + }; + it('should return empty array if there is an error', function () { + const laResult = spec.interpretResponse(loServerResult, []) + expect(laResult).to.be.an('array').that.is.empty; + }); + }); // interpretResponse1 + describe('interpretResponse2', function() { + const loServerResult = { + body: { + status: 'ok', + error: '', + pbid: '6147833a65749742875ace47', + bids: [{ + requestId: commonBidRequest.bidId, + cpm: 0.10, + currency: 'EUR', + width: 300, + height: 250, + ad: 'test ad', + ttl: 60, + creativeId: 'c123a456', + netRevenue: false, + }] + } + }; + const loExpected = [{ + requestId: '1a2b3c4d56e7f0', + cpm: 0.1, + currency: 'EUR', + width: 300, + height: 250, + ad: 'test ad', + ttl: 60, + creativeId: 'c123a456', + netRevenue: false, + pbid: '6147833a65749742875ace47' + }]; + it('should return a correct bid response', function () { + const laResult = spec.interpretResponse(loServerResult, []) + expect(JSON.stringify(laResult)).to.equal(JSON.stringify(loExpected)); + }); + }); // interpretResponse2 + + /** + * onBidWon + */ + describe('onBidWon', function() { + it('should not make an ajax call if pbid is null', function () { + const loBid = { + requestId: '1a2b3c4d56e7f0', + cpm: 0.1, + currency: 'EUR', + width: 300, + height: 250, + ad: 'test ad', + ttl: 60, + creativeId: 'c123a456', + netRevenue: false, + } + spec.onBidWon(loBid) + expect(server.requests.length).to.equals(0); + }); + it('should make an ajax call', function () { + const loBid = { + requestId: '1a2b3c4d56e7f0', + cpm: 0.1, + currency: 'EUR', + width: 300, + height: 250, + ad: 'test ad', + ttl: 60, + creativeId: 'c123a456', + netRevenue: false, + pbid: '6147833a65749742875ace47' + } + spec.onBidWon(loBid) + expect(server.requests[0].url).to.equals('https://test.natexo-programmatic.com/tad/tag/prebidwon/6147833a65749742875ace47'); + }); + }); // onBidWon +});