diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 638536c37e0..91413289e90 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -25,7 +25,7 @@ export const spec = { * @returns {boolean} */ isBidRequestValid(bid) { - return !!(bid && bid.params && bid.params.adslotId && bid.params.supplyId && isTransparencyCountOneOrLess(bid.params)); + return !!(bid && bid.params && bid.params.adslotId && bid.params.supplyId); }, /** @@ -80,29 +80,6 @@ export const spec = { if (floor) { adslotFloors.push(bid.params.adslotId + ':' + floor); } - const dsaRequired = bid.params.regs?.ext?.dsa?.required; - if (dsaRequired !== undefined) { - query.dsarequired = dsaRequired; - } - const dsaPubRender = bid.params.regs?.ext?.dsa?.pubrender; - if (dsaPubRender !== undefined) { - query.dsapubrender = dsaPubRender; - } - const dsaDataToPub = bid.params.regs?.ext?.dsa?.datatopub; - if (dsaDataToPub !== undefined) { - query.dsadatatopub = dsaDataToPub; - } - const dsaTransparency = bid.params.regs?.ext?.dsa?.transparency; - if (Array.isArray(dsaTransparency) && dsaTransparency.length > 0) { - const dsaDomain = dsaTransparency[0].domain; - if (dsaDomain !== undefined) { - query.dsadomain = dsaDomain; - } - const dsaParams = dsaTransparency[0].params; - if (Array.isArray(dsaParams) && dsaParams.length > 0) { - query.dsaparams = dsaParams.join(','); - } - } }); if (bidderRequest) { @@ -117,6 +94,33 @@ export const spec = { query.consent = bidderRequest.gdprConsent.consentString; } } + + if (bidderRequest.ortb2?.regs?.ext?.dsa !== undefined) { + const dsa = bidderRequest.ortb2.regs.ext.dsa; + + assignIfNotUndefined(query, 'dsarequired', dsa.dsarequired); + assignIfNotUndefined(query, 'dsapubrender', dsa.pubrender); + assignIfNotUndefined(query, 'dsadatatopub', dsa.datatopub); + + if (Array.isArray(dsa.transparency)) { + const filteredTransparencies = dsa.transparency.filter(({ domain, dsaparams }) => { + return domain && !domain.includes('~') && Array.isArray(dsaparams) && dsaparams.length > 0 && dsaparams.every(param => typeof param === 'number'); + }); + + if (filteredTransparencies.length === 1) { + const { domain, dsaparams } = filteredTransparencies[0]; + assignIfNotUndefined(query, 'dsadomain', domain); + assignIfNotUndefined(query, 'dsaparams', dsaparams.join(',')); + } else if (filteredTransparencies.length > 1) { + const dsatransparency = filteredTransparencies.map(({ domain, dsaparams }) => + `${domain}~${dsaparams.join('_')}` + ).join('~~'); + if (dsatransparency) { + query.dsatransparency = dsatransparency; + } + } + } + } } const adslots = adslotIds.join(','); @@ -187,8 +191,7 @@ export const spec = { const dsa = getDigitalServicesActObjectFromMatchedBid(matchedBid) if (dsa !== undefined) { - bidResponse.ext = bidResponse.ext || {}; - bidResponse.ext.dsa = dsa; + bidResponse.meta = { ...bidResponse.meta, dsa: dsa }; } if (isVideo(bidRequest, adType)) { @@ -563,23 +566,36 @@ function isImageAssetOfType(type) { } /** - * Determines if the 'transparency' array within the bid parameters has one or fewer elements. + * Retrieves the Digital Services Act (DSA) object from a matched bid. + * Only includes specific attributes (behalf, paid, transparency, adrender) from the DSA object. * - * @param {Object} params - Bid parameters containing the nested 'transparency' array. - * @returns {boolean} - Returns true if the 'transparency' field is either undefined, or contains an array with at most one element; returns false otherwise. + * @param {Object} matchedBid - The server response body to inspect for the DSA information. + * @returns {Object|undefined} A copy of the DSA object if it exists, or undefined if not. */ -function isTransparencyCountOneOrLess(params) { - return (params?.regs?.ext?.dsa?.transparency?.length ?? 0) <= 1; +function getDigitalServicesActObjectFromMatchedBid(matchedBid) { + if (matchedBid.dsa) { + const { behalf, paid, transparency, adrender } = matchedBid.dsa; + return { + ...(behalf !== undefined && { behalf }), + ...(paid !== undefined && { paid }), + ...(transparency !== undefined && { transparency }), + ...(adrender !== undefined && { adrender }) + }; + } + return undefined; } /** - * Retrieves the Digital Services Act (DSA) object from a matched bid. + * Conditionally assigns a value to a specified key on an object if the value is not undefined. * - * @param {Object} matchedBid - The server response body to inspect for the DSA information. - * @returns {Object|undefined} A copy of the DSA object if it exists, or undefined if not. + * @param {Object} obj - The object to which the value will be assigned. + * @param {string} key - The key under which the value should be assigned. + * @param {*} value - The value to be assigned, if it is not undefined. */ -function getDigitalServicesActObjectFromMatchedBid(matchedBid) { - return matchedBid.ext?.dsa ? { ...matchedBid.ext.dsa } : undefined; +function assignIfNotUndefined(obj, key, value) { + if (value !== undefined) { + obj[key] = value; + } } registerBidder(spec); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 65e667eb76c..751dff4fe33 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -161,51 +161,6 @@ const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { }, }); -const DIGITAL_SERVICES_ACT_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { - params: { - adslotId: '1111', - supplyId: '2222', - targeting: { - key1: 'value1', - key2: 'value2', - notDoubleEncoded: 'value3,value4', - }, - customParams: { - extraParam: true, - foo: 'bar', - }, - extId: 'abc', - iabContent: { - id: 'foo_id', - episode: '99', - title: 'foo_title,bar_title', - series: 'foo_series', - season: 's1', - artist: 'foo bar', - genre: 'baz', - isrc: 'CC-XXX-YY-NNNNN', - url: 'http://foo_url.de', - cat: ['cat1', 'cat2,ppp', 'cat3|||//'], - context: '7', - keywords: ['k1,', 'k2..'], - live: '0', - }, - regs: { - ext: { - dsa: { - required: '1', - pubrender: '2', - datatopub: '3', - transparency: [{ - domain: 'test.com', - params: [1, 2, 3] - }] - }, - } - }, - }, -}); - const RESPONSE = { advertiser: 'yieldlab', curl: 'https://www.yieldlab.de', @@ -272,19 +227,35 @@ const PVID_RESPONSE = Object.assign({}, VIDEO_RESPONSE, { }); const DIGITAL_SERVICES_ACT_RESPONSE = Object.assign({}, RESPONSE, { - ext: { - dsa: { - required: '1', - pubrender: '2', - datatopub: '3', - transparency: [{ - domain: 'test.com', - params: [1, 2, 3] - }] - } + dsa: { + behalf: 'some-behalf', + paid: 'some-paid', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }], + adrender: 1 } }); +const DIGITAL_SERVICES_ACT_CONFIG = { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + }, + } + }, + } +} + const REQPARAMS = { json: true, ts: 1234567890, @@ -321,49 +292,6 @@ describe('yieldlabBidAdapter', () => { it('should return false when required parameters are missing', () => { expect(spec.isBidRequestValid({})).to.equal(false); }); - - it('should return false if more than one transparency object is found', () => { - const request = { - params: { - adslotId: '1111', - supplyId: '2222', - regs: { - ext: { - dsa: { - transparency: [{ - domain: 'test.com', - params: [1, 2, 3] - }, { - domain: 'yieldlab.com', - params: [1, 2, 3] - }] - }, - } - }, - }, - }; - expect(spec.isBidRequestValid(request)).to.equal(false); - }); - - it('should return true if one transparency object is found', () => { - const request = { - params: { - adslotId: '1111', - supplyId: '2222', - regs: { - ext: { - dsa: { - transparency: [{ - domain: 'test.com', - params: [1, 2, 3] - }] - }, - } - }, - }, - }; - expect(spec.isBidRequestValid(request)).to.equal(true); - }); }); describe('buildRequests', () => { @@ -590,40 +518,72 @@ describe('yieldlabBidAdapter', () => { }); describe('Digital Services Act handling', () => { - it('does pass dsarequired parameter', () => { - const dsaRequest = DIGITAL_SERVICES_ACT_REQUEST(); + beforeEach(() => { + config.setConfig(DIGITAL_SERVICES_ACT_CONFIG); + }); + + afterEach(() => { + config.resetConfig(); + }); - let request = spec.buildRequests([dsaRequest], REQPARAMS); + it('does pass dsarequired parameter', () => { + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); expect(request.url).to.include('dsarequired=1'); }); it('does pass dsapubrender parameter', () => { - const dsaRequest = DIGITAL_SERVICES_ACT_REQUEST(); - - let request = spec.buildRequests([dsaRequest], REQPARAMS); + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); expect(request.url).to.include('dsapubrender=2'); }); it('does pass dsadatatopub parameter', () => { - const dsaRequest = DIGITAL_SERVICES_ACT_REQUEST(); - - let request = spec.buildRequests([dsaRequest], REQPARAMS); + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); expect(request.url).to.include('dsadatatopub=3'); }); it('does pass dsadomain parameter', () => { - const dsaRequest = DIGITAL_SERVICES_ACT_REQUEST(); - - let request = spec.buildRequests([dsaRequest], REQPARAMS); + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); expect(request.url).to.include('dsadomain=test.com'); }); it('does pass encoded dsaparams parameter', () => { - const dsaRequest = DIGITAL_SERVICES_ACT_REQUEST(); - - let request = spec.buildRequests([dsaRequest], REQPARAMS); + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); expect(request.url).to.include('dsaparams=1%2C2%2C3'); }); + + it('does pass multiple transparencies in dsatransparency param', () => { + const DSA_CONFIG_WITH_MULTIPLE_TRANSPARENCIES = { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [ + { + domain: 'test.com', + dsaparams: [1, 2, 3] + }, + { + domain: 'example.com', + dsaparams: [4, 5, 6] + } + ] + } + } + } + } + }; + + config.setConfig(DSA_CONFIG_WITH_MULTIPLE_TRANSPARENCIES); + + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DSA_CONFIG_WITH_MULTIPLE_TRANSPARENCIES }); + + expect(request.url).to.include('dsatransparency=test.com~1_2_3~~example.com~4_5_6'); + expect(request.url).to.not.include('dsadomain'); + expect(request.url).to.not.include('dsaparams'); + }); }); }); @@ -817,14 +777,14 @@ describe('yieldlabBidAdapter', () => { }); it('should get digital services act object in matched bid response', () => { - const result = spec.interpretResponse({body: [DIGITAL_SERVICES_ACT_RESPONSE]}, {validBidRequests: [DIGITAL_SERVICES_ACT_REQUEST()], queryParams: REQPARAMS}); + const result = spec.interpretResponse({body: [DIGITAL_SERVICES_ACT_RESPONSE]}, {validBidRequests: [{...DEFAULT_REQUEST(), ...DIGITAL_SERVICES_ACT_CONFIG}], queryParams: REQPARAMS}); expect(result[0].requestId).to.equal('2d925f27f5079f'); - expect(result[0].ext.dsa.required).to.equal('1'); - expect(result[0].ext.dsa.pubrender).to.equal('2'); - expect(result[0].ext.dsa.datatopub).to.equal('3'); - expect(result[0].ext.dsa.transparency[0].domain).to.equal('test.com'); - expect(result[0].ext.dsa.transparency[0].params).to.deep.equal([1, 2, 3]); + expect(result[0].meta.dsa.behalf).to.equal('some-behalf'); + expect(result[0].meta.dsa.paid).to.equal('some-paid'); + expect(result[0].meta.dsa.transparency[0].domain).to.equal('test.com'); + expect(result[0].meta.dsa.transparency[0].dsaparams).to.deep.equal([1, 2, 3]); + expect(result[0].meta.dsa.adrender).to.equal(1); }); });