diff --git a/integrationExamples/gpt/tpmn_example.html b/integrationExamples/gpt/tpmn_example.html
new file mode 100644
index 00000000000..f215181c7e0
--- /dev/null
+++ b/integrationExamples/gpt/tpmn_example.html
@@ -0,0 +1,168 @@
+
+
+
+
+ Prebid.js Banner Example
+
+
+
+
+
+
+
+
+
+
+ Prebid.js TPMN Banner Example
+
+
+
+
+ Prebid.js TPMN Video Example
+
+
+
+
+
+
diff --git a/integrationExamples/gpt/tpmn_serverless_example.html b/integrationExamples/gpt/tpmn_serverless_example.html
new file mode 100644
index 00000000000..0acaefbeb9c
--- /dev/null
+++ b/integrationExamples/gpt/tpmn_serverless_example.html
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+Ad Serverless Test Page
+
+
+
+
+
+
diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js
index bac99d578c5..61f8a4c98af 100644
--- a/modules/tpmnBidAdapter.js
+++ b/modules/tpmnBidAdapter.js
@@ -1,164 +1,242 @@
/* eslint-disable no-tabs */
import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { parseUrl, deepAccess } from '../src/utils.js';
+import { ortbConverter } from '../libraries/ortbConverter/converter.js';
import { getStorageManager } from '../src/storageManager.js';
-import { BANNER } from '../src/mediaTypes.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import { Renderer } from '../src/Renderer.js';
import { config } from '../src/config.js';
+import * as utils from '../src/utils.js';
-export const ADAPTER_VERSION = '1';
-const SUPPORTED_AD_TYPES = [BANNER];
const BIDDER_CODE = 'tpmn';
-const URL = 'https://ad.tpmn.co.kr/prebidhb.tpmn';
-const IFRAMESYNC = 'https://ad.tpmn.co.kr/sync.tpmn?type=iframe';
-export const storage = getStorageManager({bidderCode: BIDDER_CODE});
+const DEFAULT_BID_TTL = 500;
+const DEFAULT_CURRENCY = 'USD';
+const SUPPORTED_AD_TYPES = [BANNER, VIDEO];
+// const BIDDER_ENDPOINT_URL = 'http://localhost:8081/ortb/pbjs_bidder';
+const BIDDER_ENDPOINT_URL = 'https://gat.tpmn.io/ortb/pbjs_bidder';
+const IFRAMESYNC = 'https://gat.tpmn.io/sync/iframe';
+const COMMON_PARAMS = [
+ 'battr'
+];
+export const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js';
+export const ADAPTER_VERSION = '2.0';
+export const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: SUPPORTED_AD_TYPES,
+ isBidRequestValid,
+ buildRequests,
+ interpretResponse,
+ getUserSyncs,
/**
- *Determines whether or not the given bid request is valid.
- *
- * @param {object} bid The bid to validate.
- * @return boolean True if this is a valid bid, and false otherwise.
- */
- isBidRequestValid: function (bid) {
- return 'params' in bid &&
- 'inventoryId' in bid.params &&
- 'publisherId' in bid.params &&
- !isNaN(Number(bid.params.inventoryId)) &&
- bid.params.inventoryId > 0 &&
- (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes
- },
-
- /**
- * @param {BidRequest[]} bidRequests
- * @param {*} bidderRequest
- * @return {ServerRequest}
+ * 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
*/
- buildRequests: (bidRequests, bidderRequest) => {
- if (bidRequests.length === 0) {
- return [];
+ onBidWon: function (bid) {
+ if (bid.burl) {
+ utils.triggerPixel(bid.burl);
}
- const bids = bidRequests.map(bidToRequest);
- const bidderApiUrl = URL;
- const payload = {
- 'bids': [...bids],
- 'site': createSite(bidderRequest.refererInfo)
- };
- return [{
- method: 'POST',
- url: bidderApiUrl,
- data: payload
- }];
+ }
+}
+
+function isBidRequestValid(bid) {
+ return (isValidInventoryId(bid) && (isValidBannerRequest(bid) || isValidVideoRequest(bid)));
+}
+
+function isValidInventoryId(bid) {
+ return 'params' in bid && 'inventoryId' in bid.params && utils.isNumber(bid.params.inventoryId);
+}
+
+function isValidBannerRequest(bid) {
+ const bannerSizes = utils.deepAccess(bid, `mediaTypes.${BANNER}.sizes`);
+ return utils.isArray(bannerSizes) && bannerSizes.length > 0 && bannerSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1]));
+}
+
+function isValidVideoRequest(bid) {
+ const videoSizes = utils.deepAccess(bid, `mediaTypes.${VIDEO}.playerSize`);
+ const videoMimes = utils.deepAccess(bid, `mediaTypes.${VIDEO}.mimes`);
+
+ const isValidVideoSize = utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1]));
+ const isValidVideoMimes = utils.isArray(videoMimes) && videoMimes.length > 0;
+ return isValidVideoSize && isValidVideoMimes;
+}
+
+function buildRequests(validBidRequests, bidderRequest) {
+ let requests = [];
+ try {
+ if (validBidRequests.length === 0 || !bidderRequest) return [];
+ let bannerBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.banner'));
+ let videoBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.video'));
+
+ bannerBids.forEach(bid => {
+ requests.push(createRequest([bid], bidderRequest, BANNER));
+ });
+
+ videoBids.forEach(bid => {
+ requests.push(createRequest([bid], bidderRequest, VIDEO));
+ });
+ } catch (err) {
+ utils.logWarn('buildRequests', err);
+ }
+
+ return requests;
+}
+
+function createRequest(bidRequests, bidderRequest, mediaType) {
+ const rtbData = CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } })
+
+ const bid = bidRequests.find((b) => b.params.inventoryId)
+
+ if (bid.params.inventoryId) rtbData.ext = {};
+ if (bid.params.inventoryId) rtbData.ext.inventoryId = bid.params.inventoryId
+
+ const ortb2Data = bidderRequest?.ortb2 || {};
+ const bcat = ortb2Data?.bcat || bid.params.bcat || [];
+ const badv = ortb2Data?.badv || bid.params.badv || [];
+ const bapp = ortb2Data?.bapp || bid.params.bapp || [];
+
+ if (bcat.length > 0) {
+ rtbData.bcat = bcat;
+ }
+ if (badv.length > 0) {
+ rtbData.badv = badv;
+ }
+ if (badv.length > 0) {
+ rtbData.bapp = bapp;
+ }
+
+ return {
+ method: 'POST',
+ url: BIDDER_ENDPOINT_URL + '?v=' + ADAPTER_VERSION,
+ data: rtbData,
+ options: { contentType: 'application/json;charset=UTF-8', withCredentials: false }
+ }
+}
+
+function interpretResponse(response, request) {
+ return CONVERTER.fromORTB({ request: request.data, response: response.body }).bids;
+}
+
+registerBidder(spec);
+
+const CONVERTER = ortbConverter({
+ context: {
+ netRevenue: true,
+ ttl: DEFAULT_BID_TTL,
+ currency: DEFAULT_CURRENCY
},
- /**
- * Unpack the response from the server into a list of bids.
- *
- * @param {serverResponse} serverResponse A successful response from the server.
- * @return {Bid[]} An array of bids which were nested inside the server.
- */
- interpretResponse: function (serverResponse, serverRequest) {
- if (!Array.isArray(serverResponse.body)) {
- return [];
+ imp(buildImp, bidRequest, context) {
+ let imp = buildImp(bidRequest, context);
+ if (!imp.bidfloor && bidRequest.params.bidFloor) {
+ imp.bidfloor = bidRequest.params.bidFloor;
}
- // server response body is an array of bid results
- const bidResults = serverResponse.body;
- // our server directly returns the format needed by prebid.js so no more
- // transformation is needed here.
- return bidResults;
- },
-
- getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) {
- const syncArr = [];
- if (syncOptions.iframeEnabled) {
- let policyParam = '';
- if (gdprConsent && gdprConsent.consentString) {
- if (typeof gdprConsent.gdprApplies === 'boolean') {
- policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
- } else {
- policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`;
+ [VIDEO, BANNER].forEach(namespace => {
+ COMMON_PARAMS.forEach(param => {
+ if (bidRequest.params.hasOwnProperty(param)) {
+ utils.deepSetValue(imp, `${namespace}.${param}`, bidRequest.params[param])
}
+ })
+ })
+ return imp;
+ },
+ bidResponse(buildBidResponse, bid, context) {
+ const {bidRequest} = context;
+ const bidResponse = buildBidResponse(bid, context);
+ if (bidResponse.mediaType === BANNER) {
+ bidResponse.ad = bid.adm;
+ } else if (bidResponse.mediaType === VIDEO) {
+ if (bidRequest.mediaTypes.video.context === 'outstream') {
+ bidResponse.rendererUrl = VIDEO_RENDERER_URL;
+ bidResponse.renderer = createRenderer(bidRequest);
}
- if (uspConsent && uspConsent.consentString) {
- policyParam += `&ccpa_consent=${uspConsent.consentString}`;
- }
- const coppa = config.getConfig('coppa') ? 1 : 0;
- policyParam += `&coppa=${coppa}`;
- syncArr.push({
- type: 'iframe',
- url: IFRAMESYNC + policyParam
- });
- } else {
- syncArr.push({
- type: 'image',
- url: 'https://x.bidswitch.net/sync?ssp=tpmn'
- });
- syncArr.push({
- type: 'image',
- url: 'https://gocm.c.appier.net/tpmn'
- });
- syncArr.push({
- type: 'image',
- url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D'
- });
- syncArr.push({
- type: 'image',
- url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId'
- });
}
- return syncArr;
+ return bidResponse;
},
-};
+ overrides: {
+ imp: {
+ video(orig, imp, bidRequest, context) {
+ let videoParams = bidRequest.mediaTypes[VIDEO];
+ if (videoParams) {
+ videoParams = Object.assign({}, videoParams, bidRequest.params.video);
+ bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}}
+ }
+ orig(imp, bidRequest, context);
+ },
+ },
+ }
+});
-registerBidder(spec);
+function createRenderer(bid) {
+ const renderer = Renderer.install({
+ id: bid.bidId,
+ url: VIDEO_RENDERER_URL,
+ config: utils.deepAccess(bid, 'renderer.options'),
+ loaded: false,
+ adUnitCode: bid.adUnitCode
+ });
-/**
- * Creates site description object
- */
-function createSite(refInfo) {
- let url = parseUrl(refInfo.page || '');
- let site = {
- 'domain': url.hostname,
- 'page': url.protocol + '://' + url.hostname + url.pathname
- };
- if (refInfo.ref) {
- site.ref = refInfo.ref
- }
- let keywords = document.getElementsByTagName('meta')['keywords'];
- if (keywords && keywords.content) {
- site.keywords = keywords.content;
+ try {
+ renderer.setRender(outstreamRender);
+ } catch (err) {
+ utils.logWarn('Prebid Error calling setRender on renderer', err);
}
- return site;
+ return renderer;
}
-function parseSize(size) {
- let sizeObj = {}
- sizeObj.width = parseInt(size[0], 10);
- sizeObj.height = parseInt(size[1], 10);
- return sizeObj;
-}
+function outstreamRender(bid, doc) {
+ bid.renderer.push(() => {
+ const win = utils.getWindowFromDocument(doc) || window;
+ win.ANOutstreamVideo.renderAd({
+ sizes: [bid.playerWidth, bid.playerHeight],
+ targetId: bid.adUnitCode,
+ rendererOptions: bid.renderer.getConfig(),
+ adResponse: { content: bid.vastXml }
-function parseSizes(sizes) {
- if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]])
- return sizes.map(size => parseSize(size));
- }
- return [parseSize(sizes)]; // or a single one ? (ie. [728,90])
+ }, handleOutstreamRendererEvents.bind(null, bid));
+ });
}
-function getBannerSizes(bidRequest) {
- return parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes);
+function handleOutstreamRendererEvents(bid, id, eventName) {
+ bid.renderer.handleVideoEvent({ id, eventName });
}
-function bidToRequest(bid) {
- const bidObj = {};
- bidObj.sizes = getBannerSizes(bid);
-
- bidObj.inventoryId = bid.params.inventoryId;
- bidObj.publisherId = bid.params.publisherId;
- bidObj.bidId = bid.bidId;
- bidObj.adUnitCode = bid.adUnitCode;
- // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781
- bidObj.auctionId = bid.auctionId;
-
- return bidObj;
+function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) {
+ const syncArr = [];
+ if (syncOptions.iframeEnabled) {
+ let policyParam = '';
+ if (gdprConsent && gdprConsent.consentString) {
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
+ } else {
+ policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`;
+ }
+ }
+ if (uspConsent && uspConsent.consentString) {
+ policyParam += `&ccpa_consent=${uspConsent.consentString}`;
+ }
+ const coppa = config.getConfig('coppa') ? 1 : 0;
+ policyParam += `&coppa=${coppa}`;
+ syncArr.push({
+ type: 'iframe',
+ url: IFRAMESYNC + '?' + policyParam
+ });
+ } else {
+ syncArr.push({
+ type: 'image',
+ url: 'https://x.bidswitch.net/sync?ssp=tpmn'
+ });
+ syncArr.push({
+ type: 'image',
+ url: 'https://gocm.c.appier.net/tpmn'
+ });
+ syncArr.push({
+ type: 'image',
+ url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D'
+ });
+ syncArr.push({
+ type: 'image',
+ url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId'
+ });
+ }
+ return syncArr;
}
diff --git a/modules/tpmnBidAdapter.md b/modules/tpmnBidAdapter.md
index 8387528bb0f..3b016d7e5b2 100644
--- a/modules/tpmnBidAdapter.md
+++ b/modules/tpmnBidAdapter.md
@@ -11,10 +11,27 @@ Maintainer: develop@tpmn.co.kr
Connects to TPMN exchange for bids.
NOTE:
-- TPMN bid adapter only supports Banner at the moment.
+- TPMN bid adapter only supports MediaType BANNER, VIDEO.
- Multi-currency is not supported.
+- Please contact the TPMN sales team via email for "inventoryId" issuance.
-# Sample Ad Unit Config
+
+# Bid Parameters
+
+## bids.params (Banner, Video)
+***Pay attention to the case sensitivity.***
+
+{: .table .table-bordered .table-striped }
+| Name | Scope | Description | Example | Type |
+| -------------- | ----------- | ------------------------------------------ | ------------- | ------------ |
+| `inventoryId` | required | Ad Inventory id TPMN | 123 | Number |
+| `bidFloor` | recommended | Minimum price in USD. bidFloor applies to a specific unit. | 1.50 | Number |
+| `bcat` | optional | IAB 5.1 Content Categories | ['IAB7-39'] | [String] |
+| `badv` | optional | IAB Block list of advertisers by their domains | ['example.com'] | [String] |
+| `bapp` | optional | IAB Block list of applications | ['com.blocked'] | [String] |
+
+
+# Banner Ad Unit Config
```
var adUnits = [{
// Banner adUnit
@@ -22,16 +39,77 @@ NOTE:
mediaTypes: {
banner: {
sizes: [[300, 250], [320, 50]], // banner size
+ battr: [1,2,3] // optional
}
},
bids: [
{
bidder: 'tpmn',
params: {
- inventoryId: '1',
- publisherId: 'TPMN'
+ inventoryId: 1, // required
+ bidFloor: 2.0, // recommended
+ ... // bcat, badv, bapp // optional
}
}
]
}];
+```
+
+
+# mediaTypes Parameters
+
+## mediaTypes.banner
+
+The following banner parameters are supported here so publishers may fully declare their banner inventory:
+
+{: .table .table-bordered .table-striped }
+| Name | Scope | Description | Example | Type |
+| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- |
+| `sizes` | required | Avalaible sizes supported for banner ad unit | [ [300, 250], [300, 600] ] | [[Integer, Integer], [Integer, Integer]] |
+| `battr` | optional | IAB 5.3 Creative Attributes | [1,2,3] | [Number] |
+## mediaTypes.video
+
+We support the following OpenRTB params that can be specified in `mediaTypes.video` or in `bids[].params.video`
+
+{: .table .table-bordered .table-striped }
+| Name | Scope | Description | Example | Type |
+| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- |
+| `context` | required | instream or outstream |'outstream' | string |
+| `playerSize` | required | Avalaible sizes supported for video ad unit. | [[300, 250]] | [Integer, Integer] |
+| `mimes` | required | List of content MIME types supported by the player. | ['video/mp4']| [String]|
+| `protocols` | optional | Supported video bid response protocol values. | [2,3,5,6] | [integers]|
+| `api` | optional | Supported API framework values. | [2] | [integers] |
+| `maxduration` | optional | Maximum video ad duration in seconds. | 30 | Integer |
+| `minduration` | optional | Minimum video ad duration in seconds. | 6 | Integer |
+| `startdelay` | optional | Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. | 0 | Integer |
+| `placement` | optional | Placement type for the impression. | 1 | Integer |
+| `minbitrate` | optional | Minimum bit rate in Kbps. | 300 | Integer |
+| `maxbitrate` | optional | Maximum bit rate in Kbps. | 9600 | Integer |
+| `playbackmethod` | optional | Playback methods that may be in use. Only one method is typically used in practice. | [2]| [Integers] |
+| `linearity` | optional | OpenRTB2 linearity. in-strea,overlay... | 1 | Integer |
+| `skip` | optional | Indicates if the player will allow the video to be skipped, where 0 = no, 1 = yes . | 1 | Integer |
+| `battr` | optional | IAB 5.3 Creative Attributes | [1,2,3] | [Number] |
+
+
+# Video Ad Unit Config
+```
+ var adUnits = [{
+ code: 'video-div',
+ mediaTypes: {
+ video: {
+ context: 'instream', // required
+ mimes: ['video/mp4'], // required
+ playerSize: [[ 640, 480 ]], // required
+ ... // skippable, startdelay, battr.. // optional
+ }
+ },
+ bids: [{
+ bidder: 'tpmn',
+ params: {
+ inventoryId: 2, // required
+ bidFloor: 2.0, // recommended
+ ... // bcat, badv, bapp // optional
+ }
+ }]
+ }];
```
\ No newline at end of file
diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js
index e2b14b18f61..505bc9d878f 100644
--- a/test/spec/modules/tpmnBidAdapter_spec.js
+++ b/test/spec/modules/tpmnBidAdapter_spec.js
@@ -1,16 +1,130 @@
/* eslint-disable no-tabs */
-import {expect} from 'chai';
-import {spec, storage} from 'modules/tpmnBidAdapter.js';
-import {generateUUID} from '../../../src/utils.js';
-import {newBidder} from '../../../src/adapters/bidderFactory';
+import { spec, storage, VIDEO_RENDERER_URL, ADAPTER_VERSION } from 'modules/tpmnBidAdapter.js';
+import { generateUUID } from '../../../src/utils.js';
+import { expect } from 'chai';
+import * as utils from 'src/utils';
import * as sinon from 'sinon';
+import 'modules/consentManagement.js';
+import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js';
+import {mockGdprConsent} from '../../helpers/consentData.js';
+
+const BIDDER_CODE = 'tpmn';
+const BANNER_BID = {
+ bidder: BIDDER_CODE,
+ params: {
+ inventoryId: 1
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250]
+ ],
+ },
+ },
+ adUnitCode: 'adUnitCode1',
+ bidId: 'bidId',
+ bidderRequestId: 'bidderRequestId',
+ auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708',
+};
+
+const VIDEO_BID = {
+ bidder: BIDDER_CODE,
+ params: {
+ inventoryId: 1
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ api: [1, 2, 4, 6],
+ mimes: ['video/mp4'],
+ playbackmethod: [2, 4, 6],
+ playerSize: [[1024, 768]],
+ protocols: [3, 4, 7, 8, 10],
+ placement: 1,
+ plcmt: 1,
+ minduration: 0,
+ maxduration: 60,
+ startdelay: 0
+ },
+ },
+ adUnitCode: 'adUnitCode1',
+ bidId: 'bidId',
+ bidderRequestId: 'bidderRequestId',
+ auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708',
+};
+
+const BIDDER_REQUEST = {
+ auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708',
+ bidderRequestId: 'bidderRequestId',
+ timeout: 500,
+ refererInfo: {
+ page: 'https://hello-world-page.com/',
+ domain: 'hello-world-page.com',
+ ref: 'http://example-domain.com/foo',
+ }
+};
+
+const BANNER_BID_RESPONSE = {
+ 'id': 'bidderRequestId',
+ 'bidId': 'bidid',
+ 'seatbid': [
+ {
+ 'bid': [
+ {
+ 'id': 'id',
+ 'impid': 'bidId',
+ 'price': 0.18,
+ 'adm': '',
+ 'adid': '144762342',
+ 'burl': 'http://0.0.0.0:8181/burl',
+ 'adomain': [
+ 'https://dummydomain.com'
+ ],
+ 'cid': 'cid',
+ 'crid': 'crid',
+ 'iurl': 'iurl',
+ 'cat': [],
+ 'w': 300,
+ 'h': 250
+ }
+ ]
+ }
+ ],
+ 'cur': 'USD'
+};
+
+const VIDEO_BID_RESPONSE = {
+ 'id': 'bidderRequestId',
+ 'bidid': 'bidid',
+ 'seatbid': [
+ {
+ 'bid': [
+ {
+ 'id': 'id',
+ 'impid': 'bidId',
+ 'price': 1.09,
+ 'adid': '144762342',
+ 'burl': 'http://0.0.0.0:8181/burl',
+ 'adm': '',
+ 'adomain': [
+ 'https://dummydomain.com'
+ ],
+ 'cid': 'cid',
+ 'crid': 'crid',
+ 'iurl': 'iurl',
+ 'cat': [],
+ 'h': 768,
+ 'w': 1024
+ }
+ ]
+ }
+ ],
+ 'cur': 'USD'
+};
describe('tpmnAdapterTests', function () {
- const adapter = newBidder(spec);
- const BIDDER_CODE = 'tpmn';
let sandbox = sinon.sandbox.create();
let getCookieStub;
-
beforeEach(function () {
$$PREBID_GLOBAL$$.bidderSettings = {
tpmn: {
@@ -27,152 +141,277 @@ describe('tpmnAdapterTests', function () {
$$PREBID_GLOBAL$$.bidderSettings = {};
});
- describe('inherited functions', function () {
- it('exists and is a function', function () {
- expect(adapter.callBids).to.exist.and.to.be.a('function')
- })
- });
-
- describe('isBidRequestValid', function () {
- let bid = {
- adUnitCode: 'temp-unitcode',
- bidder: 'tpmn',
- params: {
- inventoryId: '1',
- publisherId: 'TPMN'
- },
- bidId: '29092404798c9',
- bidderRequestId: 'a01',
- auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9',
- mediaTypes: {
- banner: {
- sizes: [[300, 250]]
- }
- }
- };
-
- it('should return true if a bid is valid banner bid request', function () {
- expect(spec.isBidRequestValid(bid)).to.be.equal(true);
- });
-
- it('should return false where requried param is missing', function () {
- let bid = Object.assign({}, bid);
- bid.params = {};
- expect(spec.isBidRequestValid(bid)).to.be.equal(false);
- });
-
- it('should return false when required param values have invalid type', function () {
- let bid = Object.assign({}, bid);
- bid.params = {
- 'inventoryId': null,
- 'publisherId': null
- };
- expect(spec.isBidRequestValid(bid)).to.be.equal(false);
- });
- });
-
- describe('buildRequests', function () {
- it('should return an empty list if there are no bid requests', function () {
- const emptyBidRequests = [];
- const bidderRequest = {};
- const request = spec.buildRequests(emptyBidRequests, bidderRequest);
- expect(request).to.be.an('array').that.is.empty;
- });
- it('should generate a POST server request with bidder API url, data', function () {
- const bid = {
- adUnitCode: 'temp-unitcode',
- bidder: 'tpmn',
+ describe('isBidRequestValid()', function () {
+ it('should accept request if placementId is passed', function () {
+ let bid = {
+ bidder: BIDDER_CODE,
params: {
- inventoryId: '1',
- publisherId: 'TPMN'
+ inventoryId: 123
},
- bidId: '29092404798c9',
- bidderRequestId: 'a01',
- auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9',
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
}
};
- const tempBidRequests = [bid];
- const tempBidderRequest = {
- refererInfo: {
- page: 'http://localhost/test',
- site: {
- domain: 'localhost',
- page: 'http://localhost/test'
- }
- }
+ expect(spec.isBidRequestValid(bid)).to.equal(true);
+ });
+
+ it('should reject requests without params', function () {
+ let bid = {
+ bidder: BIDDER_CODE,
+ params: {}
};
- const builtRequest = spec.buildRequests(tempBidRequests, tempBidderRequest);
-
- expect(builtRequest).to.have.lengthOf(1);
- expect(builtRequest[0].method).to.equal('POST');
- expect(builtRequest[0].url).to.match(/ad.tpmn.co.kr\/prebidhb.tpmn/);
- const apiRequest = builtRequest[0].data;
- expect(apiRequest.site).to.deep.equal({
- domain: 'localhost',
- page: 'http://localhost/test'
- });
- expect(apiRequest.bids).to.have.lengthOf('1');
- expect(apiRequest.bids[0].inventoryId).to.equal('1');
- expect(apiRequest.bids[0].publisherId).to.equal('TPMN');
- expect(apiRequest.bids[0].bidId).to.equal('29092404798c9');
- expect(apiRequest.bids[0].adUnitCode).to.equal('temp-unitcode');
- expect(apiRequest.bids[0].auctionId).to.equal('da1d7a33-0260-4e83-a621-14674116f3f9');
- expect(apiRequest.bids[0].sizes).to.have.lengthOf('1');
- expect(apiRequest.bids[0].sizes[0]).to.deep.equal({
- width: 300,
- height: 250
- });
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return true when required params found', () => {
+ expect(spec.isBidRequestValid(BANNER_BID)).to.equal(true);
+ expect(spec.isBidRequestValid(VIDEO_BID)).to.equal(true);
});
});
- describe('interpretResponse', function () {
- const bid = {
- adUnitCode: 'temp-unitcode',
- bidder: 'tpmn',
- params: {
- inventoryId: '1',
- publisherId: 'TPMN'
- },
- bidId: '29092404798c9',
- bidderRequestId: 'a01',
- auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9',
- mediaTypes: {
- banner: {
- sizes: [[300, 250]]
+ describe('buildRequests()', function () {
+ it('should have gdpr data if applicable', function () {
+ const bid = utils.deepClone(BANNER_BID);
+
+ const req = syncAddFPDToBidderRequest(Object.assign({}, BIDDER_REQUEST, {
+ gdprConsent: {
+ consentString: 'consentString',
+ gdprApplies: true,
}
+ }));
+ let request = spec.buildRequests([bid], 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);
+ });
+
+ it('should properly forward ORTB blocking params', function () {
+ let bid = utils.deepClone(BANNER_BID);
+ bid = utils.mergeDeep(bid, {
+ params: { bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example'], battr: [1] },
+ mediaTypes: { banner: { battr: [1] } }
+ });
+
+ let [request] = spec.buildRequests([bid], BIDDER_REQUEST);
+
+ expect(request).to.exist.and.to.be.an('object');
+ const payload = request.data;
+ expect(payload).to.have.deep.property('bcat', ['IAB1-1']);
+ expect(payload).to.have.deep.property('badv', ['example.com']);
+ expect(payload).to.have.deep.property('bapp', ['com.example']);
+ expect(payload.imp[0].banner).to.have.deep.property('battr', [1]);
+ });
+
+ context('when mediaType is banner', function () {
+ it('should build correct request for banner bid with both w, h', () => {
+ const bid = utils.deepClone(BANNER_BID);
+
+ const [request] = spec.buildRequests([bid], BIDDER_REQUEST);
+ const requestData = request.data;
+ // expect(requestData.imp[0].banner).to.equal(null);
+ expect(requestData.imp[0].banner.format[0].w).to.equal(300);
+ expect(requestData.imp[0].banner.format[0].h).to.equal(250);
+ });
+
+ it('should create request data', function () {
+ const bid = utils.deepClone(BANNER_BID);
+
+ let [request] = spec.buildRequests([bid], BIDDER_REQUEST);
+ expect(request).to.exist.and.to.be.a('object');
+ const payload = request.data;
+ expect(payload.imp[0]).to.have.property('id', bid.bidId);
+ });
+ });
+
+ context('when mediaType is video', function () {
+ if (FEATURES.VIDEO) {
+ it('should return false when there is no video in mediaTypes', () => {
+ const bid = utils.deepClone(VIDEO_BID);
+ delete bid.mediaTypes.video;
+
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
}
- };
- const tempBidRequests = [bid];
- it('should return an empty aray to indicate no valid bids', function () {
- const emptyServerResponse = {};
- const bidResponses = spec.interpretResponse(emptyServerResponse, tempBidRequests);
- expect(bidResponses).is.an('array').that.is.empty;
+ if (FEATURES.VIDEO) {
+ it('should reutrn false if player size is not set', () => {
+ const bid = utils.deepClone(VIDEO_BID);
+ delete bid.mediaTypes.video.playerSize;
+
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+ }
+ if (FEATURES.VIDEO) {
+ it('when mediaType is Video - check', () => {
+ const bid = utils.deepClone(VIDEO_BID);
+ const check = {
+ w: 1024,
+ h: 768,
+ mimes: ['video/mp4'],
+ playbackmethod: [2, 4, 6],
+ api: [1, 2, 4, 6],
+ protocols: [3, 4, 7, 8, 10],
+ placement: 1,
+ minduration: 0,
+ maxduration: 60,
+ startdelay: 0,
+ plcmt: 1
+ };
+ expect(spec.isBidRequestValid(bid)).to.equal(true);
+ const requests = spec.buildRequests([bid], BIDDER_REQUEST);
+ const request = requests[0].data;
+ expect(request.imp[0].video).to.deep.include({...check});
+ });
+ }
+
+ if (FEATURES.VIDEO) {
+ it('when mediaType New Video', () => {
+ const NEW_VIDEO_BID = {
+ 'bidder': 'tpmn',
+ 'params': {'inventoryId': 2, 'bidFloor': 2},
+ 'userId': {'pubcid': '88a49ee6-beeb-4dd6-92ac-3b6060e127e1'},
+ 'mediaTypes': {
+ 'video': {
+ 'context': 'outstream',
+ 'mimes': ['video/mp4'],
+ 'playerSize': [[1024, 768]],
+ 'playbackmethod': [2, 4, 6],
+ 'protocols': [3, 4],
+ 'api': [1, 2, 3, 6],
+ 'placement': 1,
+ 'minduration': 0,
+ 'maxduration': 30,
+ 'startdelay': 0,
+ 'skip': 1,
+ 'plcmt': 4
+ }
+ },
+ };
+
+ const check = {
+ w: 1024,
+ h: 768,
+ mimes: [ 'video/mp4' ],
+ playbackmethod: [2, 4, 6],
+ api: [1, 2, 3, 6],
+ protocols: [3, 4],
+ placement: 1,
+ minduration: 0,
+ maxduration: 30,
+ startdelay: 0,
+ skip: 1,
+ plcmt: 4
+ }
+
+ expect(spec.isBidRequestValid(NEW_VIDEO_BID)).to.equal(true);
+ let requests = spec.buildRequests([NEW_VIDEO_BID], BIDDER_REQUEST);
+ const request = requests[0].data;
+ expect(request.imp[0].video.w).to.equal(check.w);
+ expect(request.imp[0].video.h).to.equal(check.h);
+ expect(request.imp[0].video.placement).to.equal(check.placement);
+ expect(request.imp[0].video.minduration).to.equal(check.minduration);
+ expect(request.imp[0].video.maxduration).to.equal(check.maxduration);
+ expect(request.imp[0].video.startdelay).to.equal(check.startdelay);
+ expect(request.imp[0].video.skip).to.equal(check.skip);
+ expect(request.imp[0].video.plcmt).to.equal(check.plcmt);
+ expect(request.imp[0].video.mimes).to.deep.have.same.members(check.mimes);
+ expect(request.imp[0].video.playbackmethod).to.deep.have.same.members(check.playbackmethod);
+ expect(request.imp[0].video.api).to.deep.have.same.members(check.api);
+ expect(request.imp[0].video.protocols).to.deep.have.same.members(check.protocols);
+ });
+ }
+
+ if (FEATURES.VIDEO) {
+ it('should use bidder video params if they are set', () => {
+ let bid = utils.deepClone(VIDEO_BID);
+ const check = {
+ api: [1, 2],
+ mimes: ['video/mp4', 'video/x-flv'],
+ playbackmethod: [3, 4],
+ protocols: [5, 6],
+ placement: 1,
+ plcmt: 1,
+ minduration: 0,
+ maxduration: 30,
+ startdelay: 0,
+ w: 640,
+ h: 480
+
+ };
+ bid.mediaTypes.video = {...check};
+ bid.mediaTypes.video.context = 'instream';
+ bid.mediaTypes.video.playerSize = [[640, 480]];
+
+ expect(spec.isBidRequestValid(bid)).to.equal(true);
+ const requests = spec.buildRequests([bid], BIDDER_REQUEST);
+ const request = requests[0].data;
+ expect(request.imp[0].video).to.deep.include({...check});
+ });
+ }
});
- it('should return an empty array to indicate no valid bids', function () {
- const mockBidResult = {
- requestId: '9cf19229-34f6-4d06-bc1d-0e44e8d616c8',
- cpm: 10.0,
- creativeId: '1',
- width: 300,
- height: 250,
- netRevenue: true,
- currency: 'USD',
- ttl: 1800,
- ad: '',
- adType: 'banner'
- };
- const testServerResponse = {
- headers: [],
- body: [mockBidResult]
- };
- const bidResponses = spec.interpretResponse(testServerResponse, tempBidRequests);
- expect(bidResponses).deep.equal([mockBidResult]);
+ });
+
+ describe('interpretResponse()', function () {
+ context('when mediaType is banner', function () {
+ it('should correctly interpret valid banner response', function () {
+ const bid = utils.deepClone(BANNER_BID);
+ const [request] = spec.buildRequests([bid], BIDDER_REQUEST);
+ const response = utils.deepClone(BANNER_BID_RESPONSE);
+
+ const bids = spec.interpretResponse({ body: response }, request);
+ expect(bids).to.be.an('array').that.is.not.empty;
+
+ expect(bids[0].mediaType).to.equal('banner');
+ expect(bids[0].burl).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].burl);
+ expect(bids[0].currency).to.equal('USD');
+ expect(bids[0].requestId).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].impid);
+ expect(bids[0].cpm).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].price);
+ expect(bids[0].width).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].w);
+ expect(bids[0].height).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].h);
+ expect(bids[0].ad).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].adm);
+ expect(bids[0].creativeId).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].crid);
+ expect(bids[0].meta.advertiserDomains[0]).to.equal('https://dummydomain.com');
+ expect(bids[0].ttl).to.equal(500);
+ expect(bids[0].netRevenue).to.equal(true);
+ });
+
+ it('should handle empty bid response', function () {
+ const bid = utils.deepClone(BANNER_BID);
+
+ let request = spec.buildRequests([bid], BIDDER_REQUEST)[0];
+ const EMPTY_RESP = Object.assign({}, BANNER_BID_RESPONSE, { 'body': {} });
+ const bids = spec.interpretResponse(EMPTY_RESP, request);
+ expect(bids).to.be.empty;
+ });
});
+ if (FEATURES.VIDEO) {
+ context('when mediaType is video', function () {
+ it('should correctly interpret valid instream video response', () => {
+ const bid = utils.deepClone(VIDEO_BID);
+
+ const [request] = spec.buildRequests([bid], BIDDER_REQUEST);
+ const bids = spec.interpretResponse({ body: VIDEO_BID_RESPONSE }, request);
+ expect(bids).to.be.an('array').that.is.not.empty;
+
+ expect(bids[0].mediaType).to.equal('video');
+ expect(bids[0].burl).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].burl);
+ expect(bids[0].currency).to.equal('USD');
+ expect(bids[0].requestId).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].impid);
+ expect(bids[0].cpm).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].price);
+ expect(bids[0].width).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].w);
+ expect(bids[0].height).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].h);
+ expect(bids[0].vastXml).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm);
+ expect(bids[0].rendererUrl).to.equal(VIDEO_RENDERER_URL);
+ expect(bids[0].creativeId).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid);
+ expect(bids[0].meta.advertiserDomains[0]).to.equal('https://dummydomain.com');
+ expect(bids[0].ttl).to.equal(500);
+ expect(bids[0].netRevenue).to.equal(true);
+ });
+ });
+ }
});
describe('getUserSync', function () {