From 10d94399ce25e3ae0be30f2e8aeb26633791ceb2 Mon Sep 17 00:00:00 2001 From: Eric Nolte Date: Mon, 15 Apr 2024 12:23:42 -0400 Subject: [PATCH] PAAPI: emit addComponentAuction event Component Auction Configs appear to be entirely contained within the PAAPI + fledgeForGPT modules. This event provides a way to access those configs from outside of those 2 modules. --- src/adapters/bidderFactory.js | 53 +++++------ src/constants.json | 3 +- test/spec/unit/core/bidderFactory_spec.js | 107 +++++++++++++--------- 3 files changed, 92 insertions(+), 71 deletions(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 337ae47f338..0b684c65e88 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -1,13 +1,13 @@ import Adapter from '../adapter.js'; import adapterManager from '../adapterManager.js'; -import {config} from '../config.js'; -import {createBid} from '../bidfactory.js'; -import {userSync} from '../userSync.js'; -import {nativeBidIsValid} from '../native.js'; -import {isValidVideoBid} from '../video.js'; +import { config } from '../config.js'; +import { createBid } from '../bidfactory.js'; +import { userSync } from '../userSync.js'; +import { nativeBidIsValid } from '../native.js'; +import { isValidVideoBid } from '../video.js'; import CONSTANTS from '../constants.json'; import * as events from '../events.js'; -import {includes} from '../polyfill.js'; +import { includes } from '../polyfill.js'; import { delayExecution, isArray, @@ -18,14 +18,14 @@ import { parseSizesInput, pick, uniques } from '../utils.js'; -import {hook} from '../hook.js'; -import {auctionManager} from '../auctionManager.js'; -import {bidderSettings} from '../bidderSettings.js'; -import {useMetrics} from '../utils/perfMetrics.js'; -import {isActivityAllowed} from '../activities/rules.js'; -import {activityParams} from '../activities/activityParams.js'; -import {MODULE_TYPE_BIDDER} from '../activities/modules.js'; -import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../activities/activities.js'; +import { hook } from '../hook.js'; +import { auctionManager } from '../auctionManager.js'; +import { bidderSettings } from '../bidderSettings.js'; +import { useMetrics } from '../utils/perfMetrics.js'; +import { isActivityAllowed } from '../activities/rules.js'; +import { activityParams } from '../activities/activityParams.js'; +import { MODULE_TYPE_BIDDER } from '../activities/modules.js'; +import { ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD } from '../activities/activities.js'; /** * @typedef {import('../mediaTypes.js').MediaType} MediaType @@ -211,7 +211,7 @@ export function guardTids(bidderCode) { .forEach(([prop, fn]) => proxy[prop] = fn.bind(target)); return proxy; } - const bidRequest = memoize((br) => privateAccessProxy(br, {get}), (arg) => arg.bidId); + const bidRequest = memoize((br) => privateAccessProxy(br, { get }), (arg) => arg.bidId); /** * Return a view on bidd(er) requests where auctionId/transactionId are nulled if the bidder is not allowed `transmitTid`. * @@ -238,11 +238,11 @@ export function guardTids(bidderCode) { */ export function newBidder(spec) { return Object.assign(new Adapter(spec.code), { - getSpec: function() { + getSpec: function () { return Object.freeze(Object.assign({}, spec)); }, registerSyncs, - callBids: function(bidderRequest, addBidResponse, done, ajax, onTimelyResponse, configEnabledCallback) { + callBids: function (bidderRequest, addBidResponse, done, ajax, onTimelyResponse, configEnabledCallback) { if (!Array.isArray(bidderRequest.bids)) { return; } @@ -388,7 +388,7 @@ function getPaapiConfigs(adapterResponse) { * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse * @param onCompletion {function()} invoked once when all bid requests have been processed */ -export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onPaapi, onError, onBid, onCompletion}) { +export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, { onRequest, onResponse, onPaapi, onError, onBid, onCompletion }) { const metrics = adapterMetrics(bidderRequest); onCompletion = metrics.startTiming('total').stopBefore(onCompletion); @@ -413,7 +413,7 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe // If the server responds successfully, use the adapter code to unpack the Bids from it. // If the adapter code fails, no bids should be added. After all the bids have been added, // make sure to call the `requestDone` function so that we're one step closer to calling onCompletion(). - const onSuccess = wrapCallback(function(response, responseObj) { + const onSuccess = wrapCallback(function (response, responseObj) { networkDone(); try { response = JSON.parse(response); @@ -528,7 +528,7 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe }) }, 'processBidderRequests') -export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent, gppConsent) { +export const registerSyncInner = hook('async', function (spec, responses, gdprConsent, uspConsent, gppConsent) { const aliasSyncEnabled = config.getConfig('userSync.aliasSyncEnabled'); if (spec.getUserSyncs && (aliasSyncEnabled || !adapterManager.aliasRegistry[spec.code])) { let filterConfig = config.getConfig('userSync.filterSettings'); @@ -549,10 +549,11 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon }, 'registerSyncs') export const addComponentAuction = hook('sync', (request, fledgeAuctionConfig) => { + events.emit(CONSTANTS.EVENTS.ADD_COMPONENT_AUCTION, request, fledgeAuctionConfig); }, 'addComponentAuction'); // check that the bid has a width and height set -function validBidSize(adUnitCode, bid, {index = auctionManager.index} = {}) { +function validBidSize(adUnitCode, bid, { index = auctionManager.index } = {}) { if ((bid.width || parseInt(bid.width, 10) === 0) && (bid.height || parseInt(bid.height, 10) === 0)) { bid.width = parseInt(bid.width, 10); bid.height = parseInt(bid.height, 10); @@ -568,7 +569,7 @@ function validBidSize(adUnitCode, bid, {index = auctionManager.index} = {}) { // if a banner impression has one valid size, we assign that size to any bid // response that does not explicitly set width or height if (parsedSizes.length === 1) { - const [ width, height ] = parsedSizes[0].split('x'); + const [width, height] = parsedSizes[0].split('x'); bid.width = parseInt(width, 10); bid.height = parseInt(height, 10); return true; @@ -578,7 +579,7 @@ function validBidSize(adUnitCode, bid, {index = auctionManager.index} = {}) { } // Validate the arguments sent to us by the adapter. If this returns false, the bid should be totally ignored. -export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { +export function isValid(adUnitCode, bid, { index = auctionManager.index } = {}) { function hasValidKeys() { let bidKeys = Object.keys(bid); return COMMON_BID_RESPONSE_KEYS.every(key => includes(bidKeys, key) && !includes([undefined, null], bid[key])); @@ -603,15 +604,15 @@ export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { return false; } - if (FEATURES.NATIVE && bid.mediaType === 'native' && !nativeBidIsValid(bid, {index})) { + if (FEATURES.NATIVE && bid.mediaType === 'native' && !nativeBidIsValid(bid, { index })) { logError(errorMessage('Native bid missing some required properties.')); return false; } - if (FEATURES.VIDEO && bid.mediaType === 'video' && !isValidVideoBid(bid, {index})) { + if (FEATURES.VIDEO && bid.mediaType === 'video' && !isValidVideoBid(bid, { index })) { logError(errorMessage(`Video bid does not have required vastUrl or renderer property`)); return false; } - if (bid.mediaType === 'banner' && !validBidSize(adUnitCode, bid, {index})) { + if (bid.mediaType === 'banner' && !validBidSize(adUnitCode, bid, { index })) { logError(errorMessage(`Banner bids require a width and height`)); return false; } diff --git a/src/constants.json b/src/constants.json index ceac779a508..f923d7b695f 100644 --- a/src/constants.json +++ b/src/constants.json @@ -51,7 +51,8 @@ "BID_VIEWABLE": "bidViewable", "STALE_RENDER": "staleRender", "BILLABLE_EVENT": "billableEvent", - "BID_ACCEPTED": "bidAccepted" + "BID_ACCEPTED": "bidAccepted", + "ADD_COMPONENT_AUCTION": "addComponentAuction" }, "AD_RENDER_FAILED_REASON": { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 5fe5a1accfc..9f9cf3fa916 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,20 +1,20 @@ -import {addComponentAuction, isValid, newBidder, registerBidder} from 'src/adapters/bidderFactory.js'; +import { addComponentAuction, isValid, newBidder, registerBidder } from 'src/adapters/bidderFactory.js'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; -import {expect} from 'chai'; -import {userSync} from 'src/userSync.js'; +import { expect } from 'chai'; +import { userSync } from 'src/userSync.js'; import * as utils from 'src/utils.js'; -import {config} from 'src/config.js'; +import { config } from 'src/config.js'; import CONSTANTS from 'src/constants.json'; import * as events from 'src/events.js'; -import {hook} from '../../../../src/hook.js'; -import {auctionManager} from '../../../../src/auctionManager.js'; -import {stubAuctionIndex} from '../../../helpers/indexStub.js'; -import {bidderSettings} from '../../../../src/bidderSettings.js'; -import {decorateAdUnitsWithNativeParams} from '../../../../src/native.js'; +import { hook } from '../../../../src/hook.js'; +import { auctionManager } from '../../../../src/auctionManager.js'; +import { stubAuctionIndex } from '../../../helpers/indexStub.js'; +import { bidderSettings } from '../../../../src/bidderSettings.js'; +import { decorateAdUnitsWithNativeParams } from '../../../../src/native.js'; import * as activityRules from 'src/activities/rules.js'; -import {MODULE_TYPE_BIDDER} from '../../../../src/activities/modules.js'; -import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../../../../src/activities/activities.js'; +import { MODULE_TYPE_BIDDER } from '../../../../src/activities/modules.js'; +import { ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD } from '../../../../src/activities/activities.js'; const CODE = 'sampleBidder'; const MOCK_BIDS_REQUEST = { @@ -135,7 +135,7 @@ describe('bidderFactory', () => { }); spec.code = 'aliasBidder'; const bidder = newBidder(spec); - aliasRegistry = {[spec.code]: CODE}; + aliasRegistry = { [spec.code]: CODE }; bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(false); }); @@ -143,7 +143,7 @@ describe('bidderFactory', () => { describe('transaction IDs', () => { beforeEach(() => { activityRules.isActivityAllowed.reset(); - ajaxStub.callsFake((_, callback) => callback.success(null, {getResponseHeader: sinon.stub()})); + ajaxStub.callsFake((_, callback) => callback.success(null, { getResponseHeader: sinon.stub() })); spec.interpretResponse.callsFake(() => [ { requestId: 'bid', @@ -179,7 +179,7 @@ describe('bidderFactory', () => { spec.buildRequests.callsFake((bidReqs, bidderReq) => { checkBidderRequest(bidderReq); bidReqs.forEach(checkBidRequest); - return {method: 'POST'}; + return { method: 'POST' }; }); activityRules.isActivityAllowed.callsFake(() => allowed); @@ -514,7 +514,7 @@ describe('bidderFactory', () => { url, sinon.match.any, sinon.match.any, - sinon.match({browsingTopics: shouldBeSet}) + sinon.match({ browsingTopics: shouldBeSet }) ); }); }); @@ -565,7 +565,7 @@ describe('bidderFactory', () => { let logErrorSpy; beforeEach(function () { - ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function (url, callbacks) { const fakeResponse = sinon.stub(); fakeResponse.returns('headerContent'); callbacks.success('response body', { getResponseHeader: fakeResponse }); @@ -585,7 +585,7 @@ describe('bidderFactory', () => { it('should call onTimelyResponse', () => { const bidder = newBidder(spec); spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({method: 'POST', url: 'test', data: {}}); + spec.buildRequests.returns({ method: 'POST', url: 'test', data: {} }); bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); sinon.assert.called(onTimelyResponseStub); }) @@ -654,7 +654,7 @@ describe('bidderFactory', () => { netRevenue: true, ttl: 300, bidderCode: 'sampleBidder', - sampleBidder: {advertiserId: '12345', networkId: '111222'} + sampleBidder: { advertiserId: '12345', networkId: '111222' } }; const bidderRequest = Object.assign({}, MOCK_BIDS_REQUEST); bidderRequest.bids[0].bidder = 'sampleBidder'; @@ -679,7 +679,7 @@ describe('bidderFactory', () => { expect(doneStub.calledOnce).to.equal(true); expect(logErrorSpy.callCount).to.equal(0); expect(bidObject.meta).to.exist; - expect(bidObject.meta).to.deep.equal({advertiserId: '12345', networkId: '111222'}); + expect(bidObject.meta).to.deep.equal({ advertiserId: '12345', networkId: '111222' }); }); it('should call spec.getUserSyncs() with the response', function () { @@ -815,7 +815,7 @@ describe('bidderFactory', () => { status: 500, statusText: 'Internal Server Error' }; - ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function (url, callbacks) { callbacks.error('ajax call failed.', xhrErrorMock); }); callBidderErrorStub = sinon.stub(adapterManager, 'callBidderError'); @@ -835,10 +835,10 @@ describe('bidderFactory', () => { 'other errors': false }).forEach(([t, timedOut]) => { it(`should ${timedOut ? 'NOT ' : ''}call onTimelyResponse on ${t}`, () => { - Object.assign(xhrErrorMock, {timedOut}); + Object.assign(xhrErrorMock, { timedOut }); const bidder = newBidder(spec); spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({method: 'POST', url: 'test', data: {}}); + spec.buildRequests.returns({ method: 'POST', url: 'test', data: {} }); bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); sinon.assert[timedOut ? 'notCalled' : 'called'](onTimelyResponseStub); }) @@ -966,9 +966,9 @@ describe('bidderFactory', () => { function newEmptySpec() { return { code: CODE, - isBidRequestValid: function() { }, - buildRequests: function() { }, - interpretResponse: function() { }, + isBidRequestValid: function () { }, + buildRequests: function () { }, + interpretResponse: function () { }, }; } @@ -986,7 +986,7 @@ describe('bidderFactory', () => { const thisSpec = Object.assign(newEmptySpec(), { supportedMediaTypes: ['video'] }); registerBidder(thisSpec); expect(registerBidAdapterStub.calledOnce).to.equal(true); - expect(registerBidAdapterStub.firstCall.args[2]).to.deep.equal({supportedMediaTypes: ['video']}); + expect(registerBidAdapterStub.firstCall.args[2]).to.deep.equal({ supportedMediaTypes: ['video'] }); }); it('should register bidders with the appropriate aliases', function () { @@ -1005,7 +1005,7 @@ describe('bidderFactory', () => { expect(registerBidAdapterStub.thirdCall.args[1]).to.equal('bar') }); - it('should register alias with their gvlid', function() { + it('should register alias with their gvlid', function () { const aliases = [ { code: 'foo', @@ -1027,7 +1027,7 @@ describe('bidderFactory', () => { expect(registerBidAdapterStub.getCall(3).args[0].getSpec().gvlid).to.equal(undefined); }) - it('should register alias with skipPbsAliasing', function() { + it('should register alias with skipPbsAliasing', function () { const aliases = [ { code: 'foo', @@ -1088,7 +1088,7 @@ describe('bidderFactory', () => { addBidResponseStub = sinon.stub(); addBidResponseStub.reject = sinon.stub(); doneStub = sinon.stub(); - ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function (url, callbacks) { const fakeResponse = sinon.stub(); fakeResponse.returns('headerContent'); callbacks.success('response body', { getResponseHeader: fakeResponse }); @@ -1097,7 +1097,7 @@ describe('bidderFactory', () => { indexStub = sinon.stub(auctionManager, 'index'); adUnits = []; bidderRequests = []; - indexStub.get(() => stubAuctionIndex({adUnits: adUnits, bidderRequests: bidderRequests})) + indexStub.get(() => stubAuctionIndex({ adUnits: adUnits, bidderRequests: bidderRequests })) }); afterEach(function () { @@ -1111,7 +1111,7 @@ describe('bidderFactory', () => { adUnits = [{ adUnitId: 'au', nativeParams: { - title: {'required': true}, + title: { 'required': true }, } }] decorateAdUnitsWithNativeParams(adUnits); @@ -1153,7 +1153,7 @@ describe('bidderFactory', () => { adUnits = [{ transactionId: 'au', nativeParams: { - title: {'required': true}, + title: { 'required': true }, }, }]; decorateAdUnitsWithNativeParams(adUnits); @@ -1195,7 +1195,7 @@ describe('bidderFactory', () => { adUnits = [{ transactionId: 'au', mediaTypes: { - video: {context: 'outstream'} + video: { context: 'outstream' } } }] let bidRequest = { @@ -1215,7 +1215,7 @@ describe('bidderFactory', () => { { bidderCode: CODE, mediaType: 'video', - renderer: {render: () => true, url: 'render.js'}, + renderer: { render: () => true, url: 'render.js' }, } ); @@ -1272,7 +1272,7 @@ describe('bidderFactory', () => { }] }; const bidder = newBidder(spec); - spec.interpretResponse.returns(Object.assign({}, bids[0], {transactionId: 'ignored', auctionId: 'ignored'})); + spec.interpretResponse.returns(Object.assign({}, bids[0], { transactionId: 'ignored', auctionId: 'ignored' })); bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); sinon.assert.calledWith(addBidResponseStub, sinon.match.any, sinon.match({ transactionId: 'tid', @@ -1280,7 +1280,7 @@ describe('bidderFactory', () => { })); }) - describe(' Check for alternateBiddersList ', function() { + describe(' Check for alternateBiddersList ', function () { let bidRequest; let bids1; let logWarnSpy; @@ -1421,7 +1421,7 @@ describe('bidderFactory', () => { it('should not accept the bid, when bidder is an alias but bidderSetting is missing for the bidder. It should fallback to standard setting and reject the bid', function () { bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(false); - aliasRegistry = {'validAlternateBidder': CODE}; + aliasRegistry = { 'validAlternateBidder': CODE }; const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); @@ -1445,7 +1445,7 @@ describe('bidderFactory', () => { }); }); - describe('when interpretResponse returns BidderAuctionResponse', function() { + describe('when interpretResponse returns BidderAuctionResponse', function () { const bidRequest = { auctionId: 'aid', bids: [{ @@ -1463,7 +1463,7 @@ describe('bidderFactory', () => { } } - it('should unwrap bids', function() { + it('should unwrap bids', function () { const bidder = newBidder(spec); spec.interpretResponse.returns({ bids: bids, @@ -1482,7 +1482,7 @@ describe('bidderFactory', () => { sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bid)); }) - describe('when response has PAAPI auction config', function() { + describe('when response has PAAPI auction config', function () { let paapiStub; function paapiHook(next, ...args) { @@ -1494,7 +1494,7 @@ describe('bidderFactory', () => { }); after(() => { - addComponentAuction.getHooks({hook: paapiHook}).remove(); + addComponentAuction.getHooks({ hook: paapiHook }).remove(); }) beforeEach(function () { @@ -1513,7 +1513,7 @@ describe('bidderFactory', () => { PAAPI_PROPS.forEach(paapiProp => { describe(`using ${paapiProp}`, () => { - it('should call paapi hook with PAAPI configs', function() { + it('should call paapi hook with PAAPI configs', function () { const bidder = newBidder(spec); spec.interpretResponse.returns({ bids: bids, @@ -1531,7 +1531,7 @@ describe('bidderFactory', () => { 'missing': undefined, 'an empty array': [] }).forEach(([t, bids]) => { - it(`should call paapi hook with PAAPI configs even when bids is ${t}`, function() { + it(`should call paapi hook with PAAPI configs even when bids is ${t}`, function () { const bidder = newBidder(spec); spec.interpretResponse.returns({ bids, @@ -1580,7 +1580,7 @@ describe('bidderFactory', () => { } function checkValid(bid) { - return isValid('au', bid, {index: stubAuctionIndex({bidRequests: [req]})}); + return isValid('au', bid, { index: stubAuctionIndex({ bidRequests: [req] }) }); } it('should succeed when response has a size that was in request', () => { @@ -1588,4 +1588,23 @@ describe('bidderFactory', () => { }); }) }); + + describe('addComponentAuction', () => { + let requestStub + let fledgeAuctionConfigStub + + beforeEach(() => { + requestStub = sinon.stub(); + fledgeAuctionConfigStub = sinon.stub(); + }) + it(`should emit an addComponentAuction event`, () => { + const eventEmitterSpy = sinon.spy(events, 'emit'); + addComponentAuction(requestStub, fledgeAuctionConfigStub); + const eventCall = eventEmitterSpy.withArgs(CONSTANTS.EVENTS.ADD_COMPONENT_AUCTION).getCalls()[0]; + const [event, request, fledgeAuctionConfig] = eventCall.args; + assert.equal(event, CONSTANTS.EVENTS.ADD_COMPONENT_AUCTION) + assert.equal(request, requestStub); + assert.equal(fledgeAuctionConfig, fledgeAuctionConfigStub); + }) + }); })