From 85c11b9b78f6cfe7866b4f4ed0bf7280f1ee65ed Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 17 Jul 2024 12:41:23 +0300 Subject: [PATCH] kimberliteBidAdapter: video media type support (#11981) * Kimberlite bid adapter (#1) * initial: bid adapter * styling * Fix: lint (#2) * Fix: lint (#4) * review fixes (#6) * Change: filling request.ext.prebid section (#7) * Video support * Fix: tests * Adapter's version update * No video defaults --- modules/kimberliteBidAdapter.js | 31 ++- modules/kimberliteBidAdapter.md | 31 ++- .../spec/modules/kimberliteBidAdapter_spec.js | 202 ++++++++++++------ 3 files changed, 191 insertions(+), 73 deletions(-) diff --git a/modules/kimberliteBidAdapter.js b/modules/kimberliteBidAdapter.js index 72df921e18f7..6ad8b9eda05e 100644 --- a/modules/kimberliteBidAdapter.js +++ b/modules/kimberliteBidAdapter.js @@ -1,13 +1,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' import { deepSetValue } from '../src/utils.js'; +import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; -const VERSION = '1.0.0'; +const VERSION = '1.1.0'; const BIDDER_CODE = 'kimberlite'; const METHOD = 'POST'; -const ENDPOINT_URL = 'https://kimberlite.io/rtb/bid/pbjs'; +export const ENDPOINT_URL = 'https://kimberlite.io/rtb/bid/pbjs'; const VERSION_INFO = { ver: '$prebid.version$', @@ -16,7 +17,6 @@ const VERSION_INFO = { const converter = ortbConverter({ context: { - mediaType: BANNER, netRevenue: true, ttl: 300 }, @@ -35,18 +35,32 @@ const converter = ortbConverter({ const imp = buildImp(bidRequest, context); imp.tagid = bidRequest.params.placementId; return imp; - } + }, + + bidResponse: function (buildBidResponse, bid, context) { + if (!bid.price) return; + + const [type] = Object.keys(context.bidRequest.mediaTypes); + if (Object.values(ORTB_MTYPES).includes(type)) { + context.mediaType = type; + } + + const bidResponse = buildBidResponse(bid, context); + return bidResponse; + }, }); export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: (bidRequest = {}) => { const { params, mediaTypes } = bidRequest; let isValid = Boolean(params && params.placementId); if (mediaTypes && mediaTypes[BANNER]) { isValid = isValid && Boolean(mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + isValid = isValid && Boolean(mediaTypes[VIDEO].mimes); } else { isValid = false; } @@ -58,7 +72,10 @@ export const spec = { return { method: METHOD, url: ENDPOINT_URL, - data: converter.toORTB({ bidderRequest, bidRequests }) + data: converter.toORTB({ + bidRequests, + bidderRequest + }) } }, diff --git a/modules/kimberliteBidAdapter.md b/modules/kimberliteBidAdapter.md index c165f1073aa7..06749f2c8e0c 100644 --- a/modules/kimberliteBidAdapter.md +++ b/modules/kimberliteBidAdapter.md @@ -20,7 +20,7 @@ var adUnits = [ code: 'test-div', mediaTypes: { banner: { - sizes: [[320, 250], [640, 480]], + sizes: [[320, 250], [640, 480]], // Required. } }, bids: [ @@ -34,3 +34,32 @@ var adUnits = [ } ] ``` + +## Video AdUnit + +```javascript +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + // ORTB 2.5 options. + mimes: ['video/mp4'], // Required. + // Other options are optional. + placement: 1, + protocols: [3, 6], + linearity: 1, + startdelay: 0 + } + }, + bids: [ + { + bidder: "kimberlite", + params: { + placementId: 'testVideo' + } + } + ] + } +] +``` diff --git a/test/spec/modules/kimberliteBidAdapter_spec.js b/test/spec/modules/kimberliteBidAdapter_spec.js index 1480f1cc7684..c0394a2090b2 100644 --- a/test/spec/modules/kimberliteBidAdapter_spec.js +++ b/test/spec/modules/kimberliteBidAdapter_spec.js @@ -1,6 +1,6 @@ -import { spec } from 'modules/kimberliteBidAdapter.js'; +import { spec, ENDPOINT_URL } from 'modules/kimberliteBidAdapter.js'; import { assert } from 'chai'; -import { BANNER } from '../../../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; const BIDDER_CODE = 'kimberlite'; @@ -8,74 +8,104 @@ describe('kimberliteBidAdapter', function () { const sizes = [[640, 480]]; describe('isBidRequestValid', function () { - let bidRequest; + let bidRequests; beforeEach(function () { - bidRequest = { - mediaTypes: { - [BANNER]: { - sizes: [[320, 240]] + bidRequests = [ + { + mediaTypes: { + [BANNER]: { + sizes: [[320, 240]] + } + }, + params: { + placementId: 'test-placement' } }, - params: { - placementId: 'test-placement' + { + mediaTypes: { + [VIDEO]: { + mimes: ['video/mp4'] + } + }, + params: { + placementId: 'test-placement' + } } - }; + ]; }); - it('pass on valid bidRequest', function () { - assert.isTrue(spec.isBidRequestValid(bidRequest)); + it('pass on valid banner bidRequest', function () { + assert.isTrue(spec.isBidRequestValid(bidRequests[0])); }); it('fails on missed placementId', function () { - delete bidRequest.params.placementId; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].params.placementId; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty banner', function () { - delete bidRequest.mediaTypes.banner; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].mediaTypes.banner; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty banner.sizes', function () { - delete bidRequest.mediaTypes.banner.sizes; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].mediaTypes.banner.sizes; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty request', function () { assert.isFalse(spec.isBidRequestValid()); }); + + it('pass on valid video bidRequest', function () { + assert.isTrue(spec.isBidRequestValid(bidRequests[1])); + }); + + it('fails on missed video.mimes', function () { + delete bidRequests[1].mediaTypes.video.mimes; + assert.isFalse(spec.isBidRequestValid(bidRequests[1])); + }); }); describe('buildRequests', function () { let bidRequests, bidderRequest; beforeEach(function () { - bidRequests = [{ - mediaTypes: { - [BANNER]: {sizes: sizes} + bidRequests = [ + { + mediaTypes: { + [BANNER]: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } }, - params: { - placementId: 'test-placement' + { + mediaTypes: { + [VIDEO]: { + mimes: ['video/mp4'], + } + }, + params: { + placementId: 'test-placement' + } } - }]; + ]; bidderRequest = { refererInfo: { domain: 'example.com', page: 'https://www.example.com/test.html', - }, - bids: [{ - mediaTypes: { - [BANNER]: {sizes: sizes} - } - }] + } }; }); it('valid bid request', function () { const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + assert.equal(bidRequest.method, 'POST'); + assert.equal(bidRequest.url, ENDPOINT_URL); assert.ok(bidRequest.data); const requestData = bidRequest.data; @@ -87,10 +117,10 @@ describe('kimberliteBidAdapter', function () { expect(requestData.ext).to.be.an('Object').and.have.all.keys('prebid'); expect(requestData.ext.prebid).to.be.an('Object').and.have.all.keys('ver', 'adapterVer'); - const impData = requestData.imp[0]; - expect(impData.banner).is.to.be.an('Object').and.have.all.keys(['format', 'topframe']); + const impBannerData = requestData.imp[0]; + expect(impBannerData.banner).is.to.be.an('Object').and.have.all.keys(['format', 'topframe']); - const bannerData = impData.banner; + const bannerData = impBannerData.banner; expect(bannerData.format).to.be.an('array').and.is.not.empty; const formatData = bannerData.format[0]; @@ -98,30 +128,44 @@ describe('kimberliteBidAdapter', function () { assert.equal(formatData.w, sizes[0][0]); assert.equal(formatData.h, sizes[0][1]); + + if (FEATURES.VIDEO) { + const impVideoData = requestData.imp[1]; + expect(impVideoData.video).is.to.be.an('Object').and.have.all.keys(['mimes']); + + const videoData = impVideoData.video; + expect(videoData.mimes).to.be.an('array').and.is.not.empty; + expect(videoData.mimes[0]).to.be.a('string').that.equals('video/mp4'); + } }); }); describe('interpretResponse', function () { - let bidderResponse, bidderRequest, bidRequest, expectedBid; + let bidderResponse, bidderRequest, bidRequest, expectedBids; const requestId = '07fba8b0-8812-4dc6-b91e-4a525d81729c'; - const bidId = '222209853178'; - const impId = 'imp-id'; - const crId = 'creative-id'; - const adm = 'landing'; + const bannerAdm = 'landing'; + const videoAdm = 'test vast'; beforeEach(function () { bidderResponse = { body: { id: requestId, seatbid: [{ - bid: [{ - crid: crId, - id: bidId, - impid: impId, - price: 1, - adm: adm - }] + bid: [ + { + crid: 1, + impid: 1, + price: 1, + adm: bannerAdm + }, + { + crid: 2, + impid: 2, + price: 1, + adm: videoAdm + } + ] }] } }; @@ -131,36 +175,64 @@ describe('kimberliteBidAdapter', function () { domain: 'example.com', page: 'https://www.example.com/test.html', }, - bids: [{ - bidId: impId, - mediaTypes: { - [BANNER]: {sizes: sizes} + bids: [ + { + bidId: 1, + mediaTypes: { + banner: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } }, - params: { - placementId: 'test-placement' + { + bidId: 2, + mediaTypes: { + video: { + mimes: ['video/mp4'] + } + }, + params: { + placementId: 'test-placement' + } } - }] + ] }; - expectedBid = { - mediaType: 'banner', - requestId: 'imp-id', - seatBidId: '222209853178', - cpm: 1, - creative_id: 'creative-id', - creativeId: 'creative-id', - ttl: 300, - netRevenue: true, - ad: adm, - meta: {} - }; + expectedBids = [ + { + mediaType: 'banner', + requestId: 1, + cpm: 1, + creative_id: 1, + creativeId: 1, + ttl: 300, + netRevenue: true, + ad: bannerAdm, + meta: {} + }, + { + mediaType: 'video', + requestId: 2, + cpm: 1, + creative_id: 2, + creativeId: 2, + ttl: 300, + netRevenue: true, + vastXml: videoAdm, + meta: {} + }, + ]; bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); }); it('pass on valid request', function () { const bids = spec.interpretResponse(bidderResponse, bidRequest); - assert.deepEqual(bids[0], expectedBid); + assert.deepEqual(bids[0], expectedBids[0]); + if (FEATURES.VIDEO) { + assert.deepEqual(bids[1], expectedBids[1]); + } }); it('fails on empty response', function () {