diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 714673f3136..c784a8971bf 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -147,16 +147,12 @@ export const spec = { serverRequest.eids = JSON.stringify(eids); }; - // Blocklists (request-level): merge ortb2 + params, send as comma-delimited - // params per the ad server's prebid-js endpoint (AS-5349). Omitted when empty. + // bcat (request-level): merge ortb2 + params, send as comma-delimited + // param per the ad server's prebid-js endpoint (AS-5349). Omitted when empty. const bcat = getBlocklist(bidderRequest, bannerBidRequests[0], 'bcat'); if (bcat.length) { serverRequest.bcat = bcat.join(','); } - const badv = getBlocklist(bidderRequest, bannerBidRequests[0], 'badv'); - if (badv.length) { - serverRequest.badv = badv.join(','); - } // check if url exceeded max length const fullUrl = `${bannerUrl}?${parseQueryStringParameters(serverRequest)}`; @@ -453,7 +449,7 @@ function openRtbRequest(bidRequests, bidderRequest) { imp: bidRequests.map(bidRequest => openRtbImpression(bidRequest)), site: openRtbSite(bidRequests[0], bidderRequest), device: deepAccess(bidderRequest, 'ortb2.device'), - badv: getBlocklist(bidderRequest, bidRequests[0], 'badv'), + badv: bidRequests[0].params.badv || [], bcat: getBlocklist(bidderRequest, bidRequests[0], 'bcat'), ext: { prebid: '$prebid.version$', @@ -721,6 +717,8 @@ function validateVideoParams(bid) { validate('video.skippable', val => !isDefined(val) || isBoolean(val), paramInvalid); validate('video.skipafter', val => !isDefined(val) || isNumber(val), paramInvalid); validate('video.pos', val => !isDefined(val) || isNumber(val), paramInvalid); + validate('params.badv', val => !isDefined(val) || isArray(val), paramInvalid, + 'array of strings, ex: ["ford.com","pepsi.com"]'); return true; } catch (e) { logError(e.message); @@ -729,10 +727,10 @@ function validateVideoParams(bid) { } /** - * Validate the publisher-set blocklist params (`bcat`/`badv`) for all media types - * (banner and video). A missing value is allowed; a value that is present but is - * not an array is rejected (drops the bid) — the agreed middle ground between - * dropping nothing and dropping over an absent optional field. See FS-12403. + * Validate the publisher-set `bcat` param for all media types (banner and video). + * A missing value is allowed; a value that is present but is not an array is + * rejected (drops the bid) — the agreed middle ground between dropping nothing and + * dropping over an absent optional field. See FS-12403. * @param {BidRequest} bid bid request * @return {boolean} true if valid (or absent), false if present but malformed */ @@ -741,8 +739,6 @@ function validateBlocklistParams(bid) { try { validate('params.bcat', val => !isDefined(val) || isArray(val), paramInvalid, 'array of strings, ex: ["IAB1-5","IAB1-6"]'); - validate('params.badv', val => !isDefined(val) || isArray(val), paramInvalid, - 'array of strings, ex: ["ford.com","pepsi.com"]'); return true; } catch (e) { logError(e.message); diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md index 135cd89c54f..410f436406c 100644 --- a/modules/yieldmoBidAdapter.md +++ b/modules/yieldmoBidAdapter.md @@ -32,8 +32,7 @@ var adUnits = [{ // Banner adUnit placementId: '1779781193098233305', // string with at most 19 characters (may include numbers only) bidFloor: .28, // optional param lr_env: '***', // Optional. Live Ramp ATS envelope - bcat: ['IAB1-5', 'IAB1-6'], // optional, array of blocked IAB content categories (strings) - badv: ['ford.com', 'pepsi.com'] // optional, array of blocked advertiser domains (strings) + bcat: ['IAB1-5', 'IAB1-6'] // optional, array of blocked IAB content categories (strings) } }] }]; @@ -70,7 +69,7 @@ var adUnits = [{ // Video adUnit }, lr_env: '***', // Optional. Live Ramp ATS envelope bcat: ['IAB1-5', 'IAB1-6'], // optional, array of blocked IAB content categories (strings) - badv: ['ford.com', 'pepsi.com'] // optional, array of blocked advertiser domains (strings) + badv: ['ford.com', 'pepsi.com'] // optional (video only), array of blocked advertiser domains (strings) } }] }]; @@ -111,17 +110,21 @@ They can be specified in `mediaTypes.video` or in `bids[].params.video`. # Blocklists (bcat / badv) -`bcat` (blocked IAB content categories) and `badv` (blocked advertiser domains) are -supported for **both banner and video**. Each is an optional array of strings and can -be supplied from either source: +`bcat` (blocked IAB content categories) is supported for **both banner and video**. It +is an optional array of strings and can be supplied from either source: -* the standardized first-party-data global — `ortb2.bcat` / `ortb2.badv`, or -* the Yieldmo bid params — `bids[].params.bcat` / `bids[].params.badv`. +* the standardized first-party-data global — `ortb2.bcat`, or +* the Yieldmo bid param — `bids[].params.bcat`. When both sources are present they are **merged** (union) and de-duplicated — neither source overrides the other. -Validation: a **missing** `bcat`/`badv` is always allowed, but if `params.bcat` / -`params.badv` is **present and not an array** the bid is rejected (`isBidRequestValid` -returns false). Non-string / empty elements inside an otherwise-valid array, and a -malformed `ortb2` value, are dropped with a console warning rather than failing the bid. +Validation: a **missing** `bcat` is always allowed, but if `params.bcat` is **present +and not an array** the bid is rejected (`isBidRequestValid` returns false). Non-string / +empty elements inside an otherwise-valid array, and a malformed `ortb2` value, are +dropped with a console warning rather than failing the bid. + +`badv` (blocked advertiser domains) is supported for **video only**, via the Yieldmo bid +param `bids[].params.badv` (an optional array of strings). It is **not** read from +`ortb2.badv`, and banner requests never send `badv`. If `params.badv` is present on a +video bid but is not an array, the bid is rejected (video-only validation). diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 04d735f58ab..6beb332cc16 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -149,26 +149,35 @@ describe('YieldmoAdapter', function () { }); }); - describe('Blocklist params (bcat / badv):', function () { - it('allows a bid when bcat/badv are absent (missing is fine)', function () { + describe('Blocklist param (bcat):', function () { + it('allows a bid when bcat is absent (missing is fine)', function () { expect(spec.isBidRequestValid(mockBannerBid())).to.be.true; expect(spec.isBidRequestValid(mockVideoBid())).to.be.true; }); - it('allows a bid when bcat/badv are arrays', function () { - expect(spec.isBidRequestValid(mockBannerBid({}, { bcat: ['IAB1-1'], badv: ['x.com'] }))).to.be.true; - expect(spec.isBidRequestValid(mockVideoBid({}, { bcat: ['IAB1-1'], badv: ['x.com'] }))).to.be.true; + it('allows a bid when bcat is an array', function () { + expect(spec.isBidRequestValid(mockBannerBid({}, { bcat: ['IAB1-1'] }))).to.be.true; + expect(spec.isBidRequestValid(mockVideoBid({}, { bcat: ['IAB1-1'] }))).to.be.true; }); it('drops a bid when bcat is present but not an array', function () { expect(spec.isBidRequestValid(mockBannerBid({}, { bcat: 'IAB1-1' }))).to.be.false; expect(spec.isBidRequestValid(mockVideoBid({}, { bcat: 'IAB1-1' }))).to.be.false; }); + }); + + describe('badv param (video-only validation):', function () { + it('allows a video bid when badv is an array', function () { + expect(spec.isBidRequestValid(mockVideoBid({}, { badv: ['ford.com'] }))).to.be.true; + }); - it('drops a bid when badv is present but not an array', function () { - expect(spec.isBidRequestValid(mockBannerBid({}, { badv: 'ford.com' }))).to.be.false; + it('drops a video bid when badv is present but not an array', function () { expect(spec.isBidRequestValid(mockVideoBid({}, { badv: 'ford.com' }))).to.be.false; }); + + it('does not validate badv for banner bids (allowed even when not an array)', function () { + expect(spec.isBidRequestValid(mockBannerBid({}, { badv: 'ford.com' }))).to.be.true; + }); }); }); @@ -838,12 +847,11 @@ describe('YieldmoAdapter', function () { }); }); - describe('bcat / badv blocklists (FS-12403)', function () { - it('banner: sends merged bcat/badv as comma-delimited GET params', function () { - const bidderReq = mockBidderRequest({ ortb2: { bcat: ['IAB1-1'], badv: ['ortb.com'] } }); - const data = buildAndGetData([mockBannerBid({}, { bcat: ['IAB2-2'], badv: ['param.com'] })], 0, bidderReq); + describe('bcat blocklist (FS-12403)', function () { + it('banner: sends merged bcat as a comma-delimited GET param', function () { + const bidderReq = mockBidderRequest({ ortb2: { bcat: ['IAB1-1'] } }); + const data = buildAndGetData([mockBannerBid({}, { bcat: ['IAB2-2'] })], 0, bidderReq); expect(data.bcat).to.equal('IAB1-1,IAB2-2'); - expect(data.badv).to.equal('ortb.com,param.com'); }); it('banner: unions ortb2 + params (neither source silently wins)', function () { @@ -859,29 +867,31 @@ describe('YieldmoAdapter', function () { }); it('banner: reads from ortb2 alone', function () { - const bidderReq = mockBidderRequest({ ortb2: { bcat: ['IAB1-1'], badv: ['x.com'] } }); + const bidderReq = mockBidderRequest({ ortb2: { bcat: ['IAB1-1'] } }); const data = buildAndGetData([mockBannerBid()], 0, bidderReq); expect(data.bcat).to.equal('IAB1-1'); - expect(data.badv).to.equal('x.com'); }); it('banner: reads from params alone', function () { - const data = buildAndGetData([mockBannerBid({}, { bcat: ['IAB1-1'], badv: ['x.com'] })], 0, mockBidderRequest()); + const data = buildAndGetData([mockBannerBid({}, { bcat: ['IAB1-1'] })], 0, mockBidderRequest()); expect(data.bcat).to.equal('IAB1-1'); - expect(data.badv).to.equal('x.com'); }); - it('banner: omits bcat/badv entirely when empty', function () { + it('banner: omits bcat entirely when empty', function () { const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest()); expect(data).to.not.have.property('bcat'); + }); + + it('banner: never sends badv (reverted in FS-12411)', function () { + const bidderReq = mockBidderRequest({ ortb2: { badv: ['ortb.com'] } }); + const data = buildAndGetData([mockBannerBid({}, { badv: ['param.com'] })], 0, bidderReq); expect(data).to.not.have.property('badv'); }); - it('video: sends merged bcat/badv as deduped arrays', function () { - const bidderReq = mockBidderRequest({ ortb2: { bcat: ['IAB1-1'], badv: ['ortb.com'] } }, [mockVideoBid()]); - const payload = buildAndGetData([mockVideoBid({}, { bcat: ['IAB2-2'], badv: ['param.com'] })], 0, bidderReq); + it('video: sends merged bcat as a deduped array', function () { + const bidderReq = mockBidderRequest({ ortb2: { bcat: ['IAB1-1'] } }, [mockVideoBid()]); + const payload = buildAndGetData([mockVideoBid({}, { bcat: ['IAB2-2'] })], 0, bidderReq); expect(payload.bcat).to.deep.equal(['IAB1-1', 'IAB2-2']); - expect(payload.badv).to.deep.equal(['ortb.com', 'param.com']); }); it('video: reads ortb2.bcat (not the legacy bidderRequest.bcat path)', function () { @@ -890,13 +900,23 @@ describe('YieldmoAdapter', function () { expect(payload.bcat).to.deep.equal(['RIGHT']); }); - it('video: defaults to empty arrays when no blocklists are set', function () { + it('video: defaults bcat to an empty array when unset', function () { const payload = buildAndGetData([mockVideoBid()], 0, mockBidderRequest({}, [mockVideoBid()])); expect(payload.bcat).to.deep.equal([]); + }); + + it('video: sends badv from params only — no ortb2/merge (reverted in FS-12411)', function () { + const bidderReq = mockBidderRequest({ ortb2: { badv: ['ortb.com'] } }, [mockVideoBid()]); + const payload = buildAndGetData([mockVideoBid({}, { badv: ['param.com'] })], 0, bidderReq); + expect(payload.badv).to.deep.equal(['param.com']); + }); + + it('video: defaults badv to an empty array when unset', function () { + const payload = buildAndGetData([mockVideoBid()], 0, mockBidderRequest({}, [mockVideoBid()])); expect(payload.badv).to.deep.equal([]); }); - describe('blocklist normalization in mergeBlocklist', function () { + describe('bcat normalization in getBlocklist', function () { let logWarnStub; beforeEach(function () { logWarnStub = sinon.stub(utils, 'logWarn'); }); afterEach(function () { logWarnStub.restore(); });