diff --git a/src/adapters/bidderFactory.ts b/src/adapters/bidderFactory.ts index 1d97253e9e..2c5eb8607d 100644 --- a/src/adapters/bidderFactory.ts +++ b/src/adapters/bidderFactory.ts @@ -279,11 +279,11 @@ export function newBidder(spec: BidderSpec) { const tidGuard = guardTids(bidderRequest); const adUnitCodesHandled = {}; - function addBidWithCode(adUnitCode: string, bid: Bid) { + function addBidWithCode(adUnitCode: string, bid: Bid, responseMediaType = null) { const metrics = useMetrics(bid.metrics); metrics.checkpoint('addBidResponse'); adUnitCodesHandled[adUnitCode] = true; - if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnitCode, bid))) { + if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnitCode, bid, {responseMediaType}))) { addBidResponse(adUnitCode, bid); } else { addBidResponse.reject(adUnitCode, bid, REJECTION_REASON.INVALID) @@ -345,7 +345,11 @@ export function newBidder(spec: BidderSpec) { bid.deferBilling = bidRequest.deferBilling; bid.deferRendering = bid.deferBilling && (bidResponse.deferRendering ?? typeof spec.onBidBillable !== 'function'); const prebidBid: Bid = Object.assign(createBid(bidRequest), bid, pick(bidRequest, Object.keys(TIDS))); - addBidWithCode(bidRequest.adUnitCode, prebidBid); + // codex agent bot: retain whether mediaType was explicitly set by the adapter response. + const responseMediaType = Object.prototype.hasOwnProperty.call(bidResponse, 'mediaType') + ? bidResponse.mediaType + : null; + addBidWithCode(bidRequest.adUnitCode, prebidBid, responseMediaType); } else { logWarn(`Bidder ${spec.code} made bid for unknown request ID: ${bidResponse.requestId}. Ignoring.`); addBidResponse.reject(null, bidResponse, REJECTION_REASON.INVALID_REQUEST_ID); @@ -647,7 +651,7 @@ function validBidSize(adUnitCode, bid: BannerBid, {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: string, bid: Bid, {index = auctionManager.index} = {}) { +export function isValid(adUnitCode: string, bid: Bid, {index = auctionManager.index, responseMediaType = bid.mediaType} = {}) { function hasValidKeys() { const bidKeys = Object.keys(bid); return COMMON_BID_RESPONSE_KEYS.every(key => bidKeys.includes(key) && ![undefined, null].includes(bid[key])); @@ -672,6 +676,16 @@ export function isValid(adUnitCode: string, bid: Bid, {index = auctionManager.in return false; } + if (responseMediaType != null) { + const mediaTypes = index.getMediaTypes(bid); + if (mediaTypes && Object.keys(mediaTypes).length > 0) { + if (!mediaTypes.hasOwnProperty(responseMediaType)) { + logError(errorMessage(`Bid mediaType '${responseMediaType}' is not supported by the ad unit. Allowed: ${Object.keys(mediaTypes).join(', ')}`)); + return false; + } + } + } + if (FEATURES.NATIVE && bid.mediaType === 'native' && !nativeBidIsValid(bid, {index})) { logError(errorMessage('Native bid missing some required properties.')); return false; diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index ff390b9526..19d6577f8c 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1691,6 +1691,62 @@ describe('bidderFactory', () => { }); }); }) + + describe('media type validation', () => { + let req; + + function mkResponse(props) { + return Object.assign({ + requestId: req.bidId, + cpm: 1, + ttl: 60, + creativeId: '123', + netRevenue: true, + currency: 'USD', + width: 1, + height: 2, + mediaType: 'banner', + }, props); + } + + function checkValid(bid, opts = {}) { + return isValid('au', bid, { + index: stubAuctionIndex({bidRequests: [req]}), + ...opts, + }); + } + + beforeEach(() => { + req = { + ...MOCK_BIDS_REQUEST.bids[0], + mediaTypes: { + banner: { + sizes: [[1, 2]] + } + } + }; + }); + + it('should reject video bid when ad unit only has banner', () => { + expect(checkValid(mkResponse({mediaType: 'video'}))).to.be.false; + }); + + it('should accept video bid when ad unit has both banner and video', () => { + req.mediaTypes = { + banner: {sizes: [[1, 2]]}, + video: {context: 'instream'} + }; + expect(checkValid(mkResponse({mediaType: 'video', vastUrl: 'http://vast.xml'}))).to.be.true; + }); + + it('should skip media type check when adapter omits mediaType', () => { + req.mediaTypes = { + video: {context: 'instream'} + }; + + expect(checkValid(mkResponse({mediaType: 'banner'}), {responseMediaType: null})).to.be.true; + }); + }); }); describe('gzip compression', () => {