diff --git a/modules/schain.js b/modules/schain.js new file mode 100644 index 00000000000..0fe980982e3 --- /dev/null +++ b/modules/schain.js @@ -0,0 +1,66 @@ +import {config} from '../src/config.js'; +import {deepClone, logWarn} from '../src/utils.js'; +import {normalizeFPD} from '../src/fpd/normalize.js'; + + +export function applySchainConfig(ortb2Fragments) { + if (!ortb2Fragments) return ortb2Fragments; + + let warned = false; + function warnDeprecated() { + if (!warned) { + logWarn('The schain module is deprecated and no longer needed; you may provide schain directly as FPD (e.g., "setConfig({ortb2: {source: {schain: {...}})")'); + warned = true; + } + } + + // Apply global schain config if available + // config's schain will have more precedence over ortb2.source.schain + const globalSchainConfig = config.getConfig('schain'); + if (globalSchainConfig && globalSchainConfig.config) { + warnDeprecated(); + if (!ortb2Fragments?.global?.source?.schain) { + applySchainToPath(ortb2Fragments, 'global.source', globalSchainConfig.config); + } else { + logWarn('Disregarding global schain config as schain is already provided in FPD') + } + } + + // Apply bidder-specific schain configs + const bidderConfigs = config.getBidderConfig(); + if (!bidderConfigs) return ortb2Fragments; + + Object.entries(bidderConfigs) + .filter(([_, cfg]) => cfg.schain) + .forEach(([bidderCode, cfg]) => { + warnDeprecated(); + const bidderPath = `bidder.${bidderCode}.source`; + const hasSchain = ortb2Fragments?.bidder?.[bidderCode]?.source?.schain; + if (!hasSchain) { + applySchainToPath(ortb2Fragments, bidderPath, cfg.schain?.config); + } else { + logWarn(`Disregarding schain config for bidder "${bidderCode}" as schain is already provided in FPD`); + } + }); + + return ortb2Fragments; +} + +function applySchainToPath(fragments, path, schainConfig) { + const parts = path.split('.'); + let current = fragments; + + // Create path if it doesn't exist + parts.forEach(part => { + current[part] = current[part] || {}; + current = current[part]; + }); + + // Apply the schain config + current.schain = deepClone(schainConfig); +} + +normalizeFPD.before((next, ortb2Fragments) => { + applySchainConfig(ortb2Fragments); + next(ortb2Fragments); +}) diff --git a/modules/schain.md b/modules/schain.md new file mode 100644 index 00000000000..44deef99886 --- /dev/null +++ b/modules/schain.md @@ -0,0 +1,35 @@ +# schain module + +** DEPRECATED **. + +This module is deprecated since prebid 10; schain may be provided directly as fpd, for example: + +```typescript +pbjs.setConfig({ + ortb2: { + source: { + schain: { + "ver":"1.0", + "complete": 1, + "nodes": [ + { + "asi":"indirectseller.com", + "sid":"00001", + "hp":1 + }, + + { + "asi":"indirectseller-2.com", + "sid":"00002", + "hp":1 + } + ] + } + } + } +}) +``` + +You may also use the [FPD validation module](https://docs.prebid.org/dev-docs/modules/validationFpdModule.html) to validate your schain configuration. + + diff --git a/src/adapterManager.js b/src/adapterManager.js index 0db1e28d33a..98da1e3e3c1 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -40,7 +40,6 @@ import { import {getRefererInfo} from './refererDetection.js'; import {GDPR_GVLIDS, gdprDataHandler, gppDataHandler, uspDataHandler, } from './consentHandler.js'; import * as events from './events.js'; -import {moveSchainToExt} from './fpd/schain.js'; import {EVENTS, S2S} from './constants.js'; import {useMetrics} from './utils/perfMetrics.js'; import {auctionManager} from './auctionManager.js'; @@ -309,15 +308,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a const ortb2 = ortb2Fragments.global || {}; const bidderOrtb2 = ortb2Fragments.bidder || {}; - function moveUserEidsToExt(o) { - const eids = o.user?.eids; - if (Array.isArray(eids) && eids.length) { - o.user.ext = o.user.ext || {}; - o.user.ext.eids = [...(o.user.ext.eids || []), ...eids]; - delete o.user.eids; - } - } - function addOrtb2(bidderRequest, s2sActivityParams) { const redact = dep.redact( s2sActivityParams != null @@ -326,8 +316,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a ); const merged = mergeDeep({source: {tid: auctionId}}, ortb2, bidderOrtb2[bidderRequest.bidderCode]); - moveUserEidsToExt(merged); - moveSchainToExt(merged, bidderOrtb2[bidderRequest.bidderCode]); const fpd = Object.freeze(redact.ortb2(merged)); bidderRequest.ortb2 = fpd; bidderRequest.bids = bidderRequest.bids.map((bid) => { diff --git a/src/fpd/normalize.js b/src/fpd/normalize.js new file mode 100644 index 00000000000..0e8b20de00b --- /dev/null +++ b/src/fpd/normalize.js @@ -0,0 +1,59 @@ +import {deepEqual, deepSetValue, logWarn} from '../utils.js'; +import {hook} from '../hook.js'; + +export const normalizeFPD = hook('sync', function(ortb2Fragments) { + [ + normalizeEIDs, + normalizeSchain + ].forEach(normalizer => applyNormalizer(normalizer, ortb2Fragments)); + return ortb2Fragments; +}) + +function applyNormalizer(normalizer, ortb2Fragments) { + ortb2Fragments.global = normalizer(ortb2Fragments.global, 'global FPD'); + Object.entries(ortb2Fragments.bidder).forEach(([bidder, ortb2]) => { + ortb2Fragments.bidder[bidder] = normalizer(ortb2, `bidder '${bidder}' FPD`); + }) +} + +export function normalizeEIDs(target, context) { + if (!target) return target; + const seen = []; + const eids = [ + ...(target?.user?.eids ?? []).map(eid => [0, eid]), + ...(target?.user?.ext?.eids ?? []).map(eid => [1, eid]) + ].filter(([source, eid]) => { + if (seen.findIndex(([candidateSource, candidateEid]) => + source !== candidateSource && deepEqual(candidateEid, eid) + ) > -1) { + logWarn(`Found duplicate EID in user.eids and user.ext.eids (${context})`, eid) + return false; + } else { + seen.push([source, eid]); + return true; + } + }); + if (eids.length > 0) { + deepSetValue(target, 'user.ext.eids', eids.map(([_, eid]) => eid)); + } + delete target?.user?.eids; + return target; +} + + +export function normalizeSchain(target, context) { + if (!target) return target; + const schain = target.source?.schain; + const extSchain = target.source?.ext?.schain; + if (schain != null && extSchain != null && !deepEqual(schain, extSchain)) { + logWarn(`Conflicting source.schain and source.ext.schain (${context}), preferring source.schain`, { + 'source.schain': schain, + 'source.ext.schain': extSchain + }) + } + if ((schain ?? extSchain) != null) { + deepSetValue(target, 'source.ext.schain', schain ?? extSchain); + } + delete target.source?.schain; + return target; +} diff --git a/src/fpd/schain.js b/src/fpd/schain.js deleted file mode 100644 index f2e6d3761a6..00000000000 --- a/src/fpd/schain.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This module handles supply chain (schain) processing and relocation - * between different locations in the ortb2 structure - */ -import {config} from '../config.js'; -import {deepClone, logInfo} from '../utils.js'; - -/** - * Applies schain from config to ortb2 fragments with precedence rules - * @param {Object} ortb2Fragments - The ortb2 fragments object - * @returns {Object} - The updated ortb2Fragments object - */ -export function schainPrecedence(ortb2Fragments) { - if (!ortb2Fragments) return ortb2Fragments; - - // Apply global schain config if available - // config's schain will have more precedence over ortb2.source.schain - const globalSchainConfig = config.getConfig('schain'); - if (globalSchainConfig && globalSchainConfig.config) { - if (!ortb2Fragments?.global?.source?.schain) { - logInfo('Applying global schain config with precedence'); - applySchainToPath(ortb2Fragments, 'global.source', globalSchainConfig.config); - } else { - logInfo('Preserving existing global.source.schain from ortb2'); - } - } - - // Apply bidder-specific schain configs - const bidderConfigs = config.getBidderConfig(); - if (!bidderConfigs) return ortb2Fragments; - - Object.entries(bidderConfigs) - .filter(([_, cfg]) => cfg.schain) - .forEach(([bidderCode, cfg]) => { - const bidderPath = `bidder.${bidderCode}.source`; - const hasSchain = ortb2Fragments?.bidder?.[bidderCode]?.source?.schain; - - if (!hasSchain) { - logInfo(`Applying bidder schain config for ${bidderCode}`); - applySchainToPath(ortb2Fragments, bidderPath, cfg.schain?.config); - } else { - logInfo(`Preserving existing schain for bidder ${bidderCode} from ortb2`); - } - }); - - return ortb2Fragments; -} - -/** - * Helper function to apply schain to a specific path in ortb2Fragments - * @param {Object} fragments - The ortb2 fragments object - * @param {String} path - Dot-notation path where to apply the schain - * @param {Object} schainConfig - The schain configuration to apply - */ -function applySchainToPath(fragments, path, schainConfig) { - const parts = path.split('.'); - let current = fragments; - - // Create path if it doesn't exist - parts.forEach(part => { - current[part] = current[part] || {}; - current = current[part]; - }); - - // Apply the schain config - current.schain = deepClone(schainConfig); -} - -/** - * Relocates schain from source.schain to source.ext.schain - * @param {Object} fpd - The first-party data object - * @returns {Object} - The updated FPD object - */ -export function moveSchainToExt(fpd, bidderOrtb2) { - if (!fpd?.source?.schain) return fpd; - - // Ensure source.ext exists - fpd.source.ext = fpd.source.ext || {}; - - // Move schain to the new location and remove from original - fpd.source.ext.schain = bidderOrtb2?.source?.schain || fpd.source.schain; - delete fpd.source.schain; - - return fpd; -} diff --git a/src/prebid.js b/src/prebid.js index d5ac6e16c8d..73ba49301f7 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -37,7 +37,6 @@ import * as events from './events.js'; import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, PbPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; -import {schainPrecedence} from './fpd/schain.js'; import {allConsent} from './consentHandler.js'; import { insertLocatorFrame, @@ -52,6 +51,7 @@ import { ORTB_BANNER_PARAMS } from './banner.js'; import { BANNER, VIDEO } from './mediaTypes.js'; import {delayIfPrerendering} from './utils/prerendering.js'; import { newBidder } from './adapters/bidderFactory.js'; +import {normalizeFPD} from './fpd/normalize.js'; const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; @@ -591,8 +591,7 @@ pbjsInstance.requestBids = (function() { global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, deepClone(cfg.ortb2)]).filter(([_, ortb2]) => ortb2 != null)) } - // Apply schain precedence rules before enrichment - ortb2Fragments = schainPrecedence(ortb2Fragments); + ortb2Fragments = normalizeFPD(ortb2Fragments); return enrichFPD(PbPromise.resolve(ortb2Fragments.global)).then(global => { ortb2Fragments.global = global; diff --git a/test/spec/fpd/normalize_spec.js b/test/spec/fpd/normalize_spec.js new file mode 100644 index 00000000000..e3a818549d6 --- /dev/null +++ b/test/spec/fpd/normalize_spec.js @@ -0,0 +1,91 @@ +import {normalizeEIDs, normalizeFPD, normalizeSchain} from '../../../src/fpd/normalize.js'; +import * as utils from '../../../src/utils'; + +describe('FPD normalization', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'logWarn'); + }); + afterEach(() => { + sandbox.restore(); + }) + + describe('EIDs', () => { + it('should merge user.eids into user.ext.eids', () => { + const fpd = { + user: { + eids: [{source: 'idA'}], + ext: {eids: [{source: 'idB'}]} + } + }; + const result = normalizeEIDs(fpd); + expect(result.user.eids).to.not.exist; + expect(result.user.ext.eids).to.deep.have.members([ + {source: 'idA'}, + {source: 'idB'} + ]) + }); + it('should remove duplicates', () => { + const fpd = { + user: { + eids: [{source: 'id'}], + ext: {eids: [{source: 'id'}]} + } + } + expect(normalizeEIDs(fpd).user.ext.eids).to.eql([ + {source: 'id'} + ]) + sinon.assert.called(utils.logWarn); + }); + it('should NOT remove duplicates if they come from the same place', () => { + const fpd = { + user: { + eids: [{source: 'id'}, {source: 'id'}] + } + } + expect(normalizeEIDs(fpd).user.ext.eids.length).to.eql(2); + }); + it('should do nothing if there are no eids', () => { + expect(normalizeEIDs({})).to.eql({}); + }) + }) + describe('schain', () => { + it('should move schain to ext.schain', () => { + const fpd = { + source: { + schain: 'foo' + } + } + expect(normalizeSchain(fpd)).to.deep.equal({ + source: { + ext: { + schain: 'foo' + } + } + }) + }); + it('should warn on conflict', () => { + const fpd = { + source: { + schain: 'foo', + ext: { + schain: 'bar' + } + }, + } + expect(normalizeSchain(fpd)).to.eql({ + source: { + ext: { + schain: 'foo' + } + } + }); + sinon.assert.called(utils.logWarn); + }); + + it('should do nothing if there is no schain', () => { + expect(normalizeSchain({})).to.eql({}); + }) + }) +}) diff --git a/test/spec/fpd/schain_spec.js b/test/spec/modules/schain_spec.js similarity index 54% rename from test/spec/fpd/schain_spec.js rename to test/spec/modules/schain_spec.js index 062a4f5d62e..0d959b95906 100644 --- a/test/spec/fpd/schain_spec.js +++ b/test/spec/modules/schain_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai/index.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; -import {schainPrecedence, moveSchainToExt} from 'src/fpd/schain.js'; +import {applySchainConfig} from 'modules/schain.js'; describe('Supply Chain fpd', function() { const SAMPLE_SCHAIN = { @@ -17,13 +17,13 @@ describe('Supply Chain fpd', function() { }; let sandbox; - let logInfoStub; + let logWarnStub; let configGetConfigStub; let configGetBidderConfigStub; beforeEach(function() { sandbox = sinon.createSandbox(); - logInfoStub = sandbox.stub(utils, 'logInfo'); + logWarnStub = sandbox.stub(utils, 'logWarn'); configGetConfigStub = sandbox.stub(config, 'getConfig'); configGetBidderConfigStub = sandbox.stub(config, 'getBidderConfig'); }); @@ -33,7 +33,7 @@ describe('Supply Chain fpd', function() { }); - describe('schainPrecedence', function() { + describe('applySchainConfig', function() { describe('preserves existing schain values', function() { it('should preserve existing global.source.schain', function() { const existingSchain = { @@ -57,11 +57,11 @@ describe('Supply Chain fpd', function() { configGetConfigStub.returns(schainConfig); configGetBidderConfigStub.returns(null); - const result = schainPrecedence(input); + const result = applySchainConfig(input); expect(result.global.source.schain).to.deep.equal(existingSchain); expect(result.global.source.schain).to.not.deep.equal(SAMPLE_SCHAIN); - expect(logInfoStub.calledWith('Preserving existing global.source.schain from ortb2')).to.be.true; + sinon.assert.called(logWarnStub); }); it('should preserve existing bidder-specific schain ', function() { @@ -92,19 +92,19 @@ describe('Supply Chain fpd', function() { configGetConfigStub.returns(null); configGetBidderConfigStub.returns(bidderConfigs); - const result = schainPrecedence(input); + const result = applySchainConfig(input); expect(result.bidder.bidderA.source.schain).to.deep.equal(existingBidderSchain); expect(result.bidder.bidderA.source.schain).to.not.deep.equal(SAMPLE_SCHAIN); - expect(logInfoStub.calledWith('Preserving existing schain for bidder bidderA from ortb2')).to.be.true; + sinon.assert.called(logWarnStub); }); }); describe('handles edge cases', function() { it('should handle edge cases and no-op scenarios', function() { - expect(schainPrecedence(null)).to.be.null; - expect(schainPrecedence(undefined)).to.be.undefined; - expect(schainPrecedence({})).to.deep.equal({}); + expect(applySchainConfig(null)).to.be.null; + expect(applySchainConfig(undefined)).to.be.undefined; + expect(applySchainConfig({})).to.deep.equal({}); const input = { global: { @@ -116,9 +116,8 @@ describe('Supply Chain fpd', function() { configGetConfigStub.returns(null); configGetBidderConfigStub.returns(null); - const result = schainPrecedence(input); + const result = applySchainConfig(input); expect(result).to.deep.equal(input); - expect(logInfoStub.called).to.be.false; }); }); @@ -140,11 +139,10 @@ describe('Supply Chain fpd', function() { }; configGetConfigStub.returns(validSchainConfig); - let result = schainPrecedence(input); + let result = applySchainConfig(input); expect(result.global.source.schain).to.deep.equal(SAMPLE_SCHAIN); - expect(logInfoStub.calledWith('Applying global schain config with precedence')).to.be.true; - logInfoStub.reset(); + logWarnStub.reset(); input = { global: { source: {} } }; const invalidSchainConfig = { @@ -152,7 +150,7 @@ describe('Supply Chain fpd', function() { }; configGetConfigStub.returns(invalidSchainConfig); - result = schainPrecedence(input); + result = applySchainConfig(input); expect(result.global.source.schain).to.be.undefined; }); }); @@ -166,7 +164,7 @@ describe('Supply Chain fpd', function() { bidder: {} }; configGetConfigStub.returns(null); - logInfoStub.reset(); + logWarnStub.reset(); }); it('should handle various bidder-specific schain scenarios', function() { @@ -179,11 +177,10 @@ describe('Supply Chain fpd', function() { }; configGetBidderConfigStub.returns(singleBidderConfig); - let result = schainPrecedence(input); + let result = applySchainConfig(input); expect(result.bidder.bidderA.source.schain).to.deep.equal(SAMPLE_SCHAIN); - expect(logInfoStub.calledWith('Applying bidder schain config for bidderA')).to.be.true; - logInfoStub.reset(); + logWarnStub.reset(); input = { global: {}, bidder: {} }; const multiBidderConfig = { @@ -202,14 +199,10 @@ describe('Supply Chain fpd', function() { }; configGetBidderConfigStub.returns(multiBidderConfig); - result = schainPrecedence(input); + result = applySchainConfig(input); expect(result.bidder.bidderA.source.schain).to.deep.equal(SAMPLE_SCHAIN); expect(result.bidder.bidderB.source.schain).to.deep.equal(SAMPLE_SCHAIN_2); expect(result.bidder.bidderC).to.be.undefined; - expect(logInfoStub.calledWith('Applying bidder schain config for bidderA')).to.be.true; - expect(logInfoStub.calledWith('Applying bidder schain config for bidderB')).to.be.true; - - logInfoStub.reset(); input = { global: {}, bidder: {} }; const invalidBidderConfig = { @@ -221,7 +214,7 @@ describe('Supply Chain fpd', function() { }; configGetBidderConfigStub.returns(invalidBidderConfig); - result = schainPrecedence(input); + result = applySchainConfig(input); expect(result.bidder.bidderA.source.schain).to.deep.equal({}); }); }); @@ -253,133 +246,9 @@ describe('Supply Chain fpd', function() { configGetConfigStub.returns(globalSchainConfig); configGetBidderConfigStub.returns(bidderConfigs); - const result = schainPrecedence(input); + const result = applySchainConfig(input); expect(result.global.source.schain).to.deep.equal(globalSchainConfig.config); expect(result.bidder.bidderA.source.schain).to.deep.equal(bidderConfigs.bidderA.schain.config); }); }); - - describe('moveSchainToExt', function() { - it('should handle various input scenarios correctly', function() { - expect(moveSchainToExt(null)).to.be.null; - expect(moveSchainToExt(undefined)).to.be.undefined; - - const inputNoSource = { user: { id: '123' } }; - expect(moveSchainToExt(inputNoSource)).to.deep.equal(inputNoSource); - - const inputNoSchain = { source: { tid: '123' } }; - expect(moveSchainToExt(inputNoSchain)).to.deep.equal(inputNoSchain); - - const basicInput = { - source: { - tid: '123', - schain: SAMPLE_SCHAIN - } - }; - let result = moveSchainToExt(basicInput); - expect(result.source.schain).to.be.undefined; - expect(result.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); - expect(result.source.tid).to.equal('123'); - - const inputWithExt = { - source: { - tid: '123', - schain: SAMPLE_SCHAIN, - ext: { - dchain: { ver: '1.0' } - } - } - }; - result = moveSchainToExt(inputWithExt); - expect(result.source.schain).to.be.undefined; - expect(result.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); - expect(result.source.ext.dchain).to.deep.equal({ ver: '1.0' }); - }); - - describe('bidderOrtb2 parameter handling', function() { - const createFreshFpd = () => ({ - source: { - tid: '123', - schain: SAMPLE_SCHAIN - } - }); - - it('should handle bidderOrtb2 parameter variations', function() { - const bidderOrtb2WithSchain = { - source: { - schain: SAMPLE_SCHAIN_2 - } - }; - - let fpd = createFreshFpd(); - let result = moveSchainToExt(fpd, bidderOrtb2WithSchain); - expect(result.source.schain).to.be.undefined; - expect(result.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN_2); - - const bidderOrtb2WithoutSchain = { - source: {} - }; - - fpd = createFreshFpd(); - result = moveSchainToExt(fpd, bidderOrtb2WithoutSchain); - expect(result.source.schain).to.be.undefined; - expect(result.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); - - fpd = createFreshFpd(); - result = moveSchainToExt(fpd, null); - expect(result.source.schain).to.be.undefined; - expect(result.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); - }); - }); - }); - - describe('Integration', function() { - it('should handle the full schain workflow with both global and bidder configs', function() { - const ortb2Fragments = { - global: { - source: { - tid: '123' - } - }, - bidder: { - 'bidderA': { - source: {} - } - } - }; - - configGetConfigStub.returns({ config: SAMPLE_SCHAIN }); - configGetBidderConfigStub.returns({ - 'bidderA': { - schain: { - config: SAMPLE_SCHAIN_2 - } - } - }); - - const updatedFragments = schainPrecedence(ortb2Fragments); - - expect(updatedFragments.global.source.schain).to.deep.equal(SAMPLE_SCHAIN); - expect(updatedFragments.bidder.bidderA.source.schain).to.deep.equal(SAMPLE_SCHAIN_2); - - const merged = { - source: { - tid: '123', - schain: SAMPLE_SCHAIN - } - }; - - const bidderOrtb2 = { - source: { - schain: SAMPLE_SCHAIN_2 - } - }; - - const finalFpd = moveSchainToExt(merged, bidderOrtb2); - - expect(finalFpd.source.schain).to.be.undefined; - expect(finalFpd.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN_2); - expect(finalFpd.source.tid).to.equal('123'); - }); - }); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index a8ac1351f80..b890115df36 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -2084,20 +2084,6 @@ describe('adapterManager tests', function () { requests.appnexus.bids.forEach((bid) => expect(bid.ortb2).to.eql(requests.appnexus.ortb2)); }); - it('should move user.eids into user.ext.eids', () => { - const global = { - user: { - eids: [{source: 'idA'}], - ext: {eids: [{source: 'idB'}]} - } - }; - const reqs = adapterManager.makeBidRequests(adUnits, 123, 'auction-id', 123, [], {global}); - reqs.forEach(req => { - expect(req.ortb2.user.ext.eids).to.deep.equal([{source: 'idB'}, {source: 'idA'}]); - expect(req.ortb2.user.eids).to.not.exist; - }); - }); - describe('source.tid', () => { beforeEach(() => { sinon.stub(dep, 'redact').returns({ diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index ef163b3b547..b7f6a702091 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1818,6 +1818,46 @@ describe('Unit: Prebid Module', function () { await auctionStarted; } + it('with normalized FPD', async () => { + configObj.setBidderConfig({ + bidders: ['test'], + config: { + ortb2: { + source: { + schain: 'foo' + } + } + } + }); + configObj.setConfig({ + ortb2: { + source: { + schain: 'bar' + } + } + }); + await runAuction(); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + ortb2Fragments: { + global: { + source: { + ext: { + schain: 'bar' + } + } + }, + bidder: { + test: { + source: { + ext: { + schain: 'foo' + } + } + } + } + } + })); + }) describe('with FPD', () => { let globalFPD, auctionFPD, mergedFPD; beforeEach(() => {