diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js new file mode 100644 index 00000000000..9a6cce01f4e --- /dev/null +++ b/modules/orbidderBidAdapter.js @@ -0,0 +1,77 @@ +import {detectReferer} from 'src/refererDetection'; +import {ajax} from 'src/ajax'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +export const spec = { + code: 'orbidder', + orbidderHost: (() => { + let ret = 'https://orbidder.otto.de'; + try { + ret = localStorage.getItem('ov_orbidder_host') || ret; + } catch (e) { + } + return ret; + })(), + + isBidRequestValid(bid) { + return !!(bid.sizes && bid.bidId); + }, + + buildRequests(validBidRequests, bidderRequest) { + return validBidRequests.map((bidRequest) => { + let referer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referer = bidderRequest.refererInfo.referer || ''; + } + const ret = { + url: `${this.orbidderHost}/bid`, + method: 'POST', + data: { + pageUrl: referer, + bidId: bidRequest.bidId, + auctionId: bidRequest.auctionId, + transactionId: bidRequest.transactionId, + adUnitCode: bidRequest.adUnitCode, + sizes: bidRequest.sizes, + params: bidRequest.params + } + }; + if (bidRequest && bidRequest.gdprConsent) { + ret.data.gdprConsent = { + consentString: bidRequest.gdprConsent.consentString, + consentRequired: (typeof bidRequest.gdprConsent.gdprApplies === 'boolean') + ? bidRequest.gdprConsent.gdprApplies + : true + }; + } + return ret; + }); + }, + + interpretResponse(serverResponse) { + const bidResponses = []; + serverResponse = serverResponse.body; + if (serverResponse && (serverResponse.length > 0)) { + serverResponse.forEach((bid) => { + const bidResponse = {}; + for (const requiredKey of ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency']) { + if (!bid.hasOwnProperty(requiredKey)) { + return []; + } + bidResponse[requiredKey] = bid[requiredKey]; + } + bidResponses.push(bidResponse); + }); + } + return bidResponses; + }, + + onBidWon(winObj) { + const getRefererInfo = detectReferer(window); + const refererInfo = getRefererInfo(); + winObj.pageUrl = refererInfo.referer; + ajax(`${this.orbidderHost}/win`, null, JSON.stringify(winObj)); + } +}; + +registerBidder(spec); diff --git a/modules/orbidderBidAdapter.md b/modules/orbidderBidAdapter.md new file mode 100644 index 00000000000..7a9600142e7 --- /dev/null +++ b/modules/orbidderBidAdapter.md @@ -0,0 +1,26 @@ +#Overview + +``` +Module Name: Orbidder Bid Adapter +Module Type: Bidder Adapter +Maintainer: orbidder@otto.de +``` + +# Description + +Module that connects to orbidder demand sources + +# Test Parameters +``` +var adUnits = [{ + code: '/105091519/bidder_test', + mediaTypes: { + banner: { + sizes: [728, 90] + } + }, + bids: [{ + bidder: 'orbidder' + }] +}]; +``` diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js new file mode 100644 index 00000000000..50955404a5e --- /dev/null +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -0,0 +1,195 @@ +import {expect} from 'chai'; +import {spec} from 'modules/orbidderBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import * as ajax from 'src/ajax'; + +describe('orbidderBidAdapter', () => { + const adapter = newBidder(spec); + const bidRequest = { + bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', + auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + params: { + 'foo': 'bar' + } + }; + + const buildRequest = function (buildRequest) { + return spec.buildRequests( + [buildRequest], + { + refererInfo: { + referer: 'http://localhost:9876/' + } + })[0]; + } + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bidRequest = Object.assign({}, bidRequest); + delete bidRequest.params; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const request = buildRequest(bidRequest); + + it('sends bid request to endpoint via https using post', () => { + expect(request.method).to.equal('POST'); + expect(request.url.indexOf('https://')).to.equal(0); + expect(request.url).to.equal(`${spec.orbidderHost}/bid`); + }); + + it('sends correct bid parameters', () => { + // we add one, because we add referer information from bidderRequest object + expect(Object.keys(request.data).length).to.equal(Object.keys(bidRequest).length + 1); + expect(request.data.pageUrl).to.equal('http://localhost:9876/'); + // expect(request.data.referrer).to.equal(''); + Object.keys(bidRequest).forEach((key) => { + expect(bidRequest[key]).to.equal(request.data[key]); + }); + }); + + it('handles empty gdpr object', () => { + let bidRequest = Object.assign({}, bidRequest); + bidRequest.gdprConsent = {}; + + const request = buildRequest(bidRequest); + expect(request.data.gdprConsent.consentRequired).to.be.equal(true); + }); + + it('handles non-existent gdpr object', () => { + let bidRequest = Object.assign({}, bidRequest); + bidRequest.gdprConsent = null; + + const request = buildRequest(bidRequest); + expect(request.data.gdprConsent).to.be.undefined; + }); + + it('handles properly filled gdpr object where gdpr applies', () => { + const consentString = 'someWeirdString'; + const bidRequest = Object.assign({}, bidRequest); + bidRequest.gdprConsent = { + gdprApplies: true, + consentString: 'someWeirdString' + }; + + const request = buildRequest(bidRequest); + const gdprConsent = request.data.gdprConsent; + expect(gdprConsent.consentRequired).to.be.equal(true); + expect(gdprConsent.consentString).to.be.equal(consentString); + }); + + it('handles properly filled gdpr object where gdpr does not apply', () => { + const consentString = 'someWeirdString'; + const bidRequest = Object.assign({}, bidRequest); + bidRequest.gdprConsent = { + gdprApplies: false, + consentString: 'someWeirdString' + }; + + const request = buildRequest(bidRequest); + const gdprConsent = request.data.gdprConsent; + expect(gdprConsent.consentRequired).to.be.equal(false); + expect(gdprConsent.consentString).to.be.equal(consentString); + }); + }); + + describe('onBidWon', () => { + let ajaxStub; + const winObj = { + test: 1, + pageUrl: 'www.someurl.de', + referrer: 'www.somereferrer.de' + }; + + beforeEach(() => { + ajaxStub = sinon.stub(ajax, 'ajax'); + }); + + afterEach(() => { + ajaxStub.restore(); + }); + + it('calls orbidder\'s win endpoint', () => { + spec.onBidWon(winObj); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0].indexOf('https://')).to.equal(0); + expect(ajaxStub.firstCall.args[0]).to.equal(`${spec.orbidderHost}/win`); + expect(ajaxStub.firstCall.args[2]).to.equal(JSON.stringify(winObj)); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const serverResponse = [ + { + 'width': 300, + 'height': 250, + 'creativeId': '29681110', + 'ad': '', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60, + 'netRevenue': true, + 'currency': 'EUR' + } + ]; + + const expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 60, + 'currency': 'EUR', + 'ad': '', + 'netRevenue': true + } + ]; + + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(expectedResponse.length); + Object.keys(expectedResponse[0]).forEach((key) => { + expect(result[0][key]).to.equal(expectedResponse[0][key]); + }); + }); + + it('handles broken server response', () => { + const serverResponse = [ + { + 'ad': '', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60 + } + ]; + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', () => { + const serverResponse = []; + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(0); + }); + }); +});