diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index b1fb5be77a5..001d0d808bf 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -111,6 +111,9 @@ export const DEFAULT_PROCESSORS = { if (bid.ext?.eventtrackers) { bidResponse.eventtrackers = (bidResponse.eventtrackers ?? []).concat(bid.ext.eventtrackers); } + if (bid.cattax) { + bidResponse.meta.cattax = bid.cattax; + } } } } diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js index 64026958bc6..4f61c81722d 100644 --- a/modules/bidResponseFilter/index.js +++ b/modules/bidResponseFilter/index.js @@ -29,7 +29,7 @@ export function reset() { } export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctionManager.index) { - const {bcat = [], badv = []} = index.getOrtb2(bid) || {}; + const {bcat = [], badv = [], cattax = 1} = index.getOrtb2(bid) || {}; const bidRequest = index.getBidRequest(bid); const battr = bidRequest?.ortb2Imp[bid.mediaType]?.battr || index.getAdUnit(bid)?.ortb2Imp[bid.mediaType]?.battr || []; @@ -43,11 +43,13 @@ export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctio advertiserDomains = [], attr: metaAttr, mediaType: metaMediaType, + cattax: metaCattax = 1, } = bid.meta || {}; // checking if bid fulfills ortb2 fields rules - if ((catConfig.enforce && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || - (catConfig.blockUnknown && !primaryCatId)) { + const isCattaxMatch = metaCattax === cattax; + if ((catConfig.enforce && isCattaxMatch && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || + (catConfig.blockUnknown && (!isCattaxMatch || !primaryCatId))) { reject(BID_CATEGORY_REJECTION_REASON); } else if ((advConfig.enforce && badv.some(domain => advertiserDomains.includes(domain))) || (advConfig.blockUnknown && !advertiserDomains.length)) { diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js index c37003bde50..16acee9b87e 100644 --- a/test/spec/modules/bidResponseFilter_spec.js +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -69,7 +69,8 @@ describe('bidResponseFilter', () => { advertiserDomains: ['domain1.com', 'domain2.com'], primaryCatId: 'EXAMPLE-CAT-ID', attr: 'attr', - mediaType: 'banner' + mediaType: 'banner', + cattax: 1 } }; @@ -85,7 +86,8 @@ describe('bidResponseFilter', () => { meta: { advertiserDomains: ['domain1.com', 'domain2.com'], primaryCatId: 'BANNED_CAT1', - attr: 'attr' + attr: 'attr', + cattax: 1 } }; mockAuctionIndex.getOrtb2 = () => ({ @@ -96,6 +98,109 @@ describe('bidResponseFilter', () => { sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); }); + describe('cattax (category taxonomy) match', () => { + it('should reject with BID_CATEGORY_REJECTION_REASON when cattax matches and primaryCatId is in bcat blocklist', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com'], + primaryCatId: 'BANNED_CAT1', + attr: 1, + mediaType: 'banner', + cattax: 1 + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1'], cattax: 1 + }); + mockAuctionIndex.getBidRequest = () => ({ + mediaTypes: { banner: {} }, + ortb2Imp: {} + }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); + sinon.assert.notCalled(call); + }); + + it('should pass when cattax matches and primaryCatId is not in bcat blocklist', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com'], + primaryCatId: 'ALLOWED_CAT', + attr: 1, + mediaType: 'banner', + cattax: 1 + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1'], cattax: 1 + }); + mockAuctionIndex.getBidRequest = () => ({ + mediaTypes: { banner: {} }, + ortb2Imp: {} + }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.notCalled(reject); + sinon.assert.calledOnce(call); + }); + + it('should reject with BID_CATEGORY_REJECTION_REASON when cattax does not match (treat primaryCatId as unknown)', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com'], + primaryCatId: 'ALLOWED_CAT', + attr: 1, + mediaType: 'banner', + cattax: 2 + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1'], cattax: 1 + }); + mockAuctionIndex.getBidRequest = () => ({ + mediaTypes: { banner: {} }, + ortb2Imp: {} + }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); + sinon.assert.notCalled(call); + }); + + it('should pass when cattax does not match and blockUnknown is false (do not treat as unknown)', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com'], + primaryCatId: 'BANNED_CAT1', + attr: 1, + mediaType: 'banner', + cattax: 2 + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1'], cattax: 1 + }); + mockAuctionIndex.getBidRequest = () => ({ + mediaTypes: { banner: {} }, + ortb2Imp: {} + }); + config.setConfig({ [MODULE_NAME]: { cat: { blockUnknown: false } } }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.notCalled(reject); + sinon.assert.calledOnce(call); + }); + }); + it('should reject the bid after failed ortb2 adv domains rule validation', () => { const rejection = sinon.stub(); const call = sinon.stub(); @@ -103,7 +208,8 @@ describe('bidResponseFilter', () => { meta: { advertiserDomains: ['domain1.com', 'domain2.com'], primaryCatId: 'VALID_CAT', - attr: 'attr' + attr: 'attr', + cattax: 1 } }; mockAuctionIndex.getOrtb2 = () => ({ @@ -121,7 +227,8 @@ describe('bidResponseFilter', () => { meta: { advertiserDomains: ['validdomain1.com', 'validdomain2.com'], primaryCatId: 'VALID_CAT', - attr: 'BANNED_ATTR' + attr: 'BANNED_ATTR', + cattax: 1 }, mediaType: 'video' }; @@ -149,6 +256,7 @@ describe('bidResponseFilter', () => { primaryCatId: 'BANNED_CAT1', attr: 'valid_attr', mediaType: 'banner', + cattax: 1 } }; @@ -177,7 +285,8 @@ describe('bidResponseFilter', () => { advertiserDomains: ['validdomain1.com', 'validdomain2.com'], primaryCatId: undefined, attr: 'valid_attr', - mediaType: 'banner' + mediaType: 'banner', + cattax: 1 } }; @@ -207,7 +316,8 @@ describe('bidResponseFilter', () => { advertiserDomains: ['validdomain1.com', 'validdomain2.com'], primaryCatId: 'VALID_CAT', attr: 6, - mediaType: 'audio' + mediaType: 'audio', + cattax: 1 }, };