From 892f2bc4d9e7830783531ee9b45efa8d0b6a5017 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Wed, 11 Dec 2024 11:55:21 -0500 Subject: [PATCH] Equativ Bid Adapter: support native bid requests (#12566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support of dsa * restore topics * DSA fix for UT * drafy of adapter * fixes after dev test * make world simpler * fix prev commit * return empty userSyncs array by default * adjustments * apply prettier * unit tests for Equativ adapter * add dsp user sync * add readme * body can be undef * support additional br params * remove user sync * do not send dt param * handle floors and network id * handle empty media types * get min floor * fix desc for u.t. * better name for u.t. * add u.t. for not supported media type * improve currency u.t. * updates after pr review * SADR-6484: initial video setup for new PBJS adapter * SADR-6484: Adding logging requirement missed earlier * SADR-6484: handle ext.rewarded prop for video with new oRTBConverter * SADR-6484: test revision + not sending bid requests where video obj is empty * refactoring and u.t. * rename variable * Equativ: SADR-6615: adding unit tests for and additional logging to bid adapter to support native requests * revert changes rel. to test endpoint * revert changes rel. to test endpoint * split imp[0] into seperate requests and fix u.t. * Equativ bid adapter: adding support for native media type * Equativ bid adapter: update unit test for native-support work * Equativ bid adapter: removing console.log from unit test file * Equativ bid adapter: clarifying refinements regarding native-request processing * Equativ Bid Adapter: updating unit tests for native requests * PR feedback --------- Co-authored-by: Elżbieta SZPONDER Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com> Co-authored-by: Krzysztof Sokół <88041828+krzysztofequativ@users.noreply.github.com> Co-authored-by: Krzysztof Sokół Co-authored-by: janzych-smart --- modules/equativBidAdapter.js | 33 ++-- test/spec/modules/equativBidAdapter_spec.js | 160 +++++++++++++++++++- 2 files changed, 180 insertions(+), 13 deletions(-) diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js index a53597a9074..646a54b6418 100644 --- a/modules/equativBidAdapter.js +++ b/modules/equativBidAdapter.js @@ -1,9 +1,10 @@ -import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getBidFloor } from '../libraries/equativUtils/equativUtils.js' -import { getStorageManager } from '../src/storageManager.js'; + +import { getBidFloor } from '../libraries/equativUtils/equativUtils.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; import { deepAccess, deepSetValue, logError, logWarn, mergeDeep } from '../src/utils.js'; /** @@ -18,14 +19,14 @@ const LOG_PREFIX = 'Equativ:'; const PID_COOKIE_NAME = 'eqt_pid'; /** - * Evaluates a bid request for validity. Returns false if the - * request contains a video media type with no properties, true - * otherwise. + * Evaluates impressions for validity. The entry evaluated is considered valid if NEITHER of these conditions are met: + * 1) it has a `video` property defined for `mediaTypes.video` which is an empty object + * 2) it has a `native` property defined for `mediaTypes.native` which is an empty object * @param {*} bidReq A bid request object to evaluate * @returns boolean */ function isValid(bidReq) { - return !(bidReq.mediaTypes.video && JSON.stringify(bidReq.mediaTypes.video) === '{}'); + return !(bidReq.mediaTypes.video && JSON.stringify(bidReq.mediaTypes.video) === '{}') && !(bidReq.mediaTypes.native && JSON.stringify(bidReq.mediaTypes.native) === '{}'); } export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); @@ -33,7 +34,7 @@ export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const spec = { code: BIDDER_CODE, gvlid: 45, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * @param bidRequests @@ -42,7 +43,7 @@ export const spec = { */ buildRequests: (bidRequests, bidderRequest) => { if (bidRequests.filter(isValid).length === 0) { - logError(`${LOG_PREFIX} No useful bid requests to process. No request will be sent.`, bidRequests); + logError(`${LOG_PREFIX} No useful bid requests to process. No requests will be sent.`, bidRequests); return undefined; } @@ -121,7 +122,6 @@ export const converter = ortbConverter({ imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest, config.getConfig('currency.adServerCurrency'), mediaType); imp.secure = 1; - imp.tagid = bidRequest.adUnitCode; if (!deepAccess(bidRequest, 'ortb2Imp.rwdd') && deepAccess(bidRequest, 'mediaTypes.video.ext.rewarded')) { @@ -151,6 +151,17 @@ export const converter = ortbConverter({ }); } + // "assets" is not included as a property to check here because the + // ortbConverter library checks for it already and will skip processing + // the request if it is missing + if (deepAccess(bid, 'mediaTypes.native')) { + ['privacy', 'plcmttype', 'eventtrackers'].forEach(prop => { + if (!bid.mediaTypes.native.ortb[prop]) { + logWarn(`${LOG_PREFIX} Property "${prop}" is missing from request. Request will proceed, but the use of ${prop} for native requests is strongly encouraged.`, bid); + } + }); + } + const pid = storage.getCookie(PID_COOKIE_NAME); if (pid) { deepSetValue(req, 'user.buyeruid', pid); diff --git a/test/spec/modules/equativBidAdapter_spec.js b/test/spec/modules/equativBidAdapter_spec.js index 9f767a3cd4e..c66e97e6dbc 100644 --- a/test/spec/modules/equativBidAdapter_spec.js +++ b/test/spec/modules/equativBidAdapter_spec.js @@ -1,6 +1,6 @@ -import { BANNER } from 'src/mediaTypes.js'; -import { getBidFloor } from 'libraries/equativUtils/equativUtils.js' +import { getBidFloor } from 'libraries/equativUtils/equativUtils.js'; import { converter, spec, storage } from 'modules/equativBidAdapter.js'; +import { BANNER } from 'src/mediaTypes.js'; import * as utils from '../../../src/utils.js'; describe('Equativ bid adapter tests', () => { @@ -78,6 +78,64 @@ describe('Equativ bid adapter tests', () => { } ]; + const nativeOrtbRequest = { + assets: [{ + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } + }, + { + id: 2, + required: 1, + data: { + type: 1 + } + }], + context: 1, + eventtrackers: [{ + event: 1, + methods: [1, 2] + }], + plcmttype: 1, + privacy: 1, + ver: '1.2', + }; + const DEFAULT_NATIVE_BID_REQUESTS = [ + { + adUnitCode: 'equativ_native_42', + bidId: 'equativ_native_bidid_42', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + }, + }, + nativeOrtbRequest, + bidder: 'equativ', + params: { + networkId: 777, + }, + requestId: 'equativ_native_reqid_42', + ortb2Imp: { + ext: { + tid: 'equativ_native_tid_42', + }, + }, + } + ]; + const DEFAULT_BANNER_BIDDER_REQUEST = { bidderCode: 'equativ', bids: DEFAULT_BANNER_BID_REQUESTS, @@ -88,6 +146,11 @@ describe('Equativ bid adapter tests', () => { bids: DEFAULT_VIDEO_BID_REQUESTS, }; + const DEFAULT_NATIVE_BIDDER_REQUEST = { + bidderCode: 'equativ', + bids: DEFAULT_NATIVE_BID_REQUESTS, + }; + const SAMPLE_RESPONSE = { body: { id: '12h712u7-k22g-8124-ab7a-h268s22dy271', @@ -495,6 +558,99 @@ describe('Equativ bid adapter tests', () => { expect(utils.logError.args[0][0]).to.satisfy(arg => arg.includes('No request')); expect(request).to.be.undefined; }); + + it('should build a native request properly under normal circumstances', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const expectedResult = true; + + // ACT + const request = spec.buildRequests(DEFAULT_NATIVE_BID_REQUESTS, {})[0].data; + + // ASSERT + expect(request.imp[0]).to.have.property('native'); + + const nativeObj = request.imp[0].native; + expect(nativeObj).to.have.property('ver').and.to.equal('1.2'); + expect(nativeObj).to.have.property('request').and.to.be.a('string'); + + const requestObj = JSON.parse(nativeObj.request); + expect(requestObj).to.have.property('assets').and.to.be.an('array'); + expect(requestObj).to.have.property('eventtrackers').and.to.be.an('array'); + expect(requestObj).to.have.property('plcmttype').and.to.equal(1); + expect(requestObj).to.have.property('privacy').and.to.equal(1); + expect(requestObj).to.have.property('ver').and.to.equal('1.2'); + } + }); + + it('should not send a native request when it has an empty body and no other impressions with any media types are defined', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const emptyNativeRequest = { + ...DEFAULT_NATIVE_BID_REQUESTS[0], + mediaTypes: { + native: {} + } + }; + const bidRequests = [ emptyNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // ACT + const request = spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logError.calledOnce).to.equal(true); + expect(utils.logError.args[0][0]).to.satisfy(arg => arg.includes('No request')); + expect(request).to.be.undefined; + } + }); + + it('should warn about missing "assets" property for native requests', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const missingRequiredNativeRequest = DEFAULT_NATIVE_BID_REQUESTS[0]; + + // removing just "assets" for this test + delete missingRequiredNativeRequest.nativeOrtbRequest.assets; + const bidRequests = [ missingRequiredNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // this value comes from native.js, part of the ortbConverter library + const warningMsgFromLibrary = 'mediaTypes.native is set, but no assets were specified. Native request skipped.' + + // ACT + spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logWarn.callCount).to.equal(1) + expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes(warningMsgFromLibrary)); + } + }); + + it('should warn about other missing required properties for native requests', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const missingRequiredNativeRequest = DEFAULT_NATIVE_BID_REQUESTS[0]; + + // ortbConverter library will warn about missing assets; we supply warnings for these properties here + delete missingRequiredNativeRequest.mediaTypes.native.ortb.eventtrackers; + delete missingRequiredNativeRequest.mediaTypes.native.ortb.plcmttype; + delete missingRequiredNativeRequest.mediaTypes.native.ortb.privacy; + + const bidRequests = [ missingRequiredNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // ACT + spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logWarn.callCount).to.equal(4); // the first message, regarding missing assets, is supplied by the ortbConverter library + expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes('no assets were specified')); + expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"privacy" is missing')); + expect(utils.logWarn.getCall(2).args[0]).to.satisfy(arg => arg.includes('"plcmttype" is missing')); + expect(utils.logWarn.getCall(3).args[0]).to.satisfy(arg => arg.includes('"eventtrackers" is missing')); + } + }); }); describe('getBidFloor', () => {