From e39d67172b50a6180eb2195169353b60c293a4ff Mon Sep 17 00:00:00 2001 From: joseluis laso Date: Tue, 5 Nov 2024 13:54:49 -0800 Subject: [PATCH] HadronId System : not use localStorage for writing on it (#12378) * don't use localStorage for storing and minimize its use for reading * don't use localStorage for storing and minimize its use for reading * restoring value of hadronId in storage (doc) * making tests pass * making test pass --- modules/hadronIdSystem.js | 35 +++++---- modules/hadronIdSystem.md | 21 +++-- modules/hadronRtdProvider.js | 45 +++++++---- test/spec/modules/hadronIdSystem_spec.js | 37 +++------ test/spec/modules/hadronRtdProvider_spec.js | 87 +++++++++++---------- 5 files changed, 117 insertions(+), 108 deletions(-) diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index bdb8e634de6..ccd63bc0184 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -19,9 +19,9 @@ import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterM * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse */ -const LOG_PREFIX = '[hadronIdSystem]'; -const HADRONID_LOCAL_NAME = 'auHadronId'; -const MODULE_NAME = 'hadronId'; +export const MODULE_NAME = 'hadronId'; +const LOG_PREFIX = `[${MODULE_NAME}System]`; +export const LS_TAM_KEY = 'auHadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; @@ -68,11 +68,9 @@ export const hadronIdSubmodule = { * @returns {Object} */ decode(value) { - const hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); - if (isStr(hadronId)) { - return {hadronId: hadronId}; + return { + hadronId: isStr(value) ? value : value.hasOwnProperty('id') ? value.id[MODULE_NAME] : value[MODULE_NAME] } - return (value && typeof value['hadronId'] === 'string') ? {'hadronId': value['hadronId']} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -81,14 +79,19 @@ export const hadronIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config) { + logInfo(LOG_PREFIX, `getId is called`, config); if (!isPlainObject(config.params)) { config.params = {}; } - const partnerId = config.params.partnerId | 0; - let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); - if (isStr(hadronId)) { - return {id: {hadronId}}; + let hadronId = ''; + // at this point hadronId was not found by prebid, let check if it is in the webpage by other ways + hadronId = storage.getDataFromLocalStorage(LS_TAM_KEY); + if (isStr(hadronId) && hadronId.length > 0) { + logInfo(LOG_PREFIX, `${LS_TAM_KEY} found in localStorage = ${hadronId}`) + // return {callback: function(cb) { cb(hadronId) }}; + return {id: hadronId} } + const partnerId = config.params.partnerId | 0; const resp = function (callback) { let responseObj = {}; const callbacks = { @@ -98,11 +101,13 @@ export const hadronIdSubmodule = { responseObj = JSON.parse(response); } catch (error) { logError(error); + callback(); } logInfo(LOG_PREFIX, `Response from backend is ${response}`, responseObj); - hadronId = responseObj['hadronId']; - storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId); - responseObj = {id: {hadronId}}; + if (isPlainObject(responseObj) && responseObj.hasOwnProperty(MODULE_NAME)) { + hadronId = responseObj[MODULE_NAME]; + } + responseObj = hadronId; // {id: {hadronId: hadronId}}; } callback(responseObj); }, @@ -137,7 +142,7 @@ export const hadronIdSubmodule = { url += `${gppConsent.applicableSections ? '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections) : ''}`; } - logInfo(LOG_PREFIX, `hadronId not found in storage, calling home (${url})`); + logInfo(LOG_PREFIX, `${MODULE_NAME} not found, calling home (${url})`); ajax(url, callbacks, undefined, {method: 'GET'}); }; diff --git a/modules/hadronIdSystem.md b/modules/hadronIdSystem.md index 212030cbcd9..f58cd46ef61 100644 --- a/modules/hadronIdSystem.md +++ b/modules/hadronIdSystem.md @@ -12,7 +12,7 @@ pbjs.setConfig({ userIds: [{ name: 'hadronId', params: { - partnerId: 1234 // change it to the Partner ID you'll get from Audigent + partnerId: 1234 // change it to the Partner ID you got from Audigent }, storage: { name: 'hadronId', @@ -25,14 +25,13 @@ pbjs.setConfig({ ## Parameter Descriptions for the `usersync` Configuration Section The below parameters apply only to the HadronID User ID Module integration. -| Param under usersync.userIds[] | Scope | Type | Description | Example | -|--------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------| -| name | Required | String | ID value for the HadronID module - `"hadronId"` | `"hadronId"` | -| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | -| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | -| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"hadronid"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | -| value | Optional | Object | Used only if the page has a separate mechanism for storing the Hadron ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"hadronId": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` | +| Param under usersync.userIds[] | Scope | Type | Description | Example | +|--------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| name | Required | String | ID value for the HadronID module - `"hadronId"` | `"hadronId"` | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | +| storage.type | Required | String | This is where the the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. The recommended value is `hadronId`. | `"auHadronId"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. The recommended value is 14 days. | `14` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the Hadron ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"hadronId": "0aRSTUAackg79ijgd8e8j6kah9ed9j6hdfgb6cl00volopxo00npzjmmb"}` | | params | Optional | Object | Used to store params for the id system | -| params.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | - | +| params.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index f9a2eaed9c9..d85f049c2de 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -10,7 +10,7 @@ import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; -import {isFn, isStr, isArray, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js'; +import {isFn, isStr, isArray, isEmpty, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; @@ -18,14 +18,14 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule */ -const LOG_PREFIX = 'User ID - HadronRtdProvider submodule: '; +const LOG_PREFIX = '[HadronRtdProvider] '; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'hadron'; const AU_GVLID = 561; const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid'; -const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd'; -export const HADRONID_LOCAL_NAME = 'auHadronId'; -export const RTD_LOCAL_NAME = 'auHadronRtd'; +const HADRON_SEGMENT_URL = 'https://prebid-rtd.audigent.workers.dev'; // https://id.hadron.ad.gt/api/v1/rtd'; +const LS_TAM_KEY = 'auHadronId'; +const RTD_LOCAL_NAME = 'auHadronRtd'; export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** @@ -166,14 +166,29 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { const userIds = {}; - let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); - if (isStr(hadronId)) { - if (typeof getGlobal().refreshUserIds === 'function') { - (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'}); + const allUserIds = getGlobal().getUserIds(); + if (allUserIds.hasOwnProperty('hadronId')) { + userIds['hadronId'] = allUserIds.hadronId; + logInfo(LOG_PREFIX, 'hadronId user module found', allUserIds.hadronId); + } else { + let hadronId = storage.getDataFromLocalStorage(LS_TAM_KEY); + if (isStr(hadronId) && hadronId.length > 0) { + userIds['hadronId'] = hadronId; + logInfo(LOG_PREFIX, 'hadronId TAM found', hadronId); } - userIds.hadronId = hadronId; + } + if (!isEmpty(userIds)) { + // if (typeof getGlobal().refreshUserIds === 'function') { + // (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'}); + // } + // userIds.hadronId = hadronId; getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); } else { + // the hadronId was not found, reasons can be: + // 1) prebid wasn't compiled with hadronIdSystem + // 2) prebid wasn't configured to use hadronId user module + // 3) all previous and no other hadronId snippet configured in the page + // then need to load hadron.js from the CDN window.pubHadronCb = (hadronId) => { userIds.hadronId = hadronId; getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); @@ -184,8 +199,8 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { paramOrDefault(hadronIdUrl, HADRON_ID_DEFAULT_URL, userIds), `partner_id=${partnerId}&_it=prebid` ); - loadExternalScript(scriptUrl, MODULE_TYPE_RTD, 'hadron', () => { - logInfo(LOG_PREFIX, 'hadronIdTag loaded', scriptUrl); + loadExternalScript(scriptUrl, SUBMODULE_NAME, () => { + logInfo(LOG_PREFIX, 'hadronId JS snippet loaded', scriptUrl); }) } } @@ -198,7 +213,7 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { * @param {Object} userConsent * @param {Object} userIds */ -export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds) { +function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds) { let reqParams = {}; if (isPlainObject(rtdConfig)) { @@ -223,7 +238,7 @@ export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, onDone(); } } catch (err) { - logError('unable to parse audigent segment data'); + logError(LOG_PREFIX, 'unable to parse audigent segment data'); onDone(); } } else if (req.status === 204) { @@ -233,7 +248,7 @@ export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, }, error: function () { onDone(); - logError('unable to get audigent segment data'); + logError(LOG_PREFIX, 'unable to get audigent segment data'); } }, JSON.stringify({'userIds': userIds, 'config': reqParams}), diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index 899dc640dc1..70aaf06bcc8 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -1,15 +1,15 @@ -import { hadronIdSubmodule, storage } from 'modules/hadronIdSystem.js'; -import { server } from 'test/mocks/xhr.js'; -import * as utils from 'src/utils.js'; +import {hadronIdSubmodule, storage, LS_TAM_KEY} from 'modules/hadronIdSystem.js'; +import {server} from 'test/mocks/xhr.js'; import {attachIdSystem} from '../../../modules/userId/index.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; import {expect} from 'chai/index.mjs'; describe('HadronIdSystem', function () { - describe('getId', function() { + const HADRON_TEST = 'tstCachedHadronId1'; + describe('getId', function () { let getDataFromLocalStorageStub; - beforeEach(function() { + beforeEach(function () { getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); }); @@ -17,42 +17,27 @@ describe('HadronIdSystem', function () { getDataFromLocalStorageStub.restore(); }); - it('gets a hadronId', function() { + it('gets a cached hadronid', function () { const config = { params: {} }; - const callbackSpy = sinon.spy(); - const callback = hadronIdSubmodule.getId(config).callback; - callback(callbackSpy); - const request = server.requests[0]; - expect(request.url).to.match(/^https:\/\/id\.hadron\.ad\.gt\/api\/v1\/pbhid/); - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); - }); - - it('gets a cached hadronid', function() { - const config = { - params: {} - }; - getDataFromLocalStorageStub.withArgs('auHadronId').returns('tstCachedHadronId1'); - + getDataFromLocalStorageStub.withArgs(LS_TAM_KEY).returns(HADRON_TEST); const result = hadronIdSubmodule.getId(config); - expect(result).to.deep.equal({ id: { hadronId: 'tstCachedHadronId1' } }); + expect(result).to.deep.equal({id: HADRON_TEST}); }); - it('allows configurable id url', function() { + it('allows configurable id url', function () { const config = { params: { url: 'https://hadronid.publync.com' } }; + getDataFromLocalStorageStub.withArgs(LS_TAM_KEY).returns(null); const callbackSpy = sinon.spy(); const callback = hadronIdSubmodule.getId(config).callback; callback(callbackSpy); const request = server.requests[0]; expect(request.url).to.match(/^https:\/\/hadronid\.publync\.com\//); - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); }); @@ -60,7 +45,7 @@ describe('HadronIdSystem', function () { before(() => { attachIdSystem(hadronIdSubmodule); }); - it('hadronId', function() { + it('hadronId', function () { const userId = { hadronId: 'some-random-id-value' }; diff --git a/test/spec/modules/hadronRtdProvider_spec.js b/test/spec/modules/hadronRtdProvider_spec.js index 140855194c5..46877f246b5 100644 --- a/test/spec/modules/hadronRtdProvider_spec.js +++ b/test/spec/modules/hadronRtdProvider_spec.js @@ -1,13 +1,20 @@ import {config} from 'src/config.js'; -import {HADRONID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, hadronSubmodule, storage} from 'modules/hadronRtdProvider.js'; +import { + HADRONID_LOCAL_NAME, + RTD_LOCAL_NAME, + addRealTimeData, + getRealTimeData, + hadronSubmodule, + storage +} from 'modules/hadronRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; -describe('hadronRtdProvider', function() { +describe('hadronRtdProvider', function () { let getDataFromLocalStorageStub; - beforeEach(function() { + beforeEach(function () { config.resetConfig(); getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); }); @@ -16,19 +23,19 @@ describe('hadronRtdProvider', function() { getDataFromLocalStorageStub.restore(); }); - describe('hadronSubmodule', function() { + describe('hadronSubmodule', function () { it('successfully instantiates', function () { - expect(hadronSubmodule.init()).to.equal(true); + expect(hadronSubmodule.init()).to.equal(true); }); }); - describe('Add Real-Time Data', function() { - it('merges ortb2 data', function() { + describe('Add Real-Time Data', function () { + it('merges ortb2 data', function () { let rtdConfig = {}; const setConfigUserObj1 = { name: 'www.dataprovider1.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1776' }] @@ -36,7 +43,7 @@ describe('hadronRtdProvider', function() { const setConfigUserObj2 = { name: 'www.dataprovider2.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1914' }] @@ -123,12 +130,12 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]); }); - it('merges ortb2 data without duplication', function() { + it('merges ortb2 data without duplication', function () { let rtdConfig = {}; const userObj1 = { name: 'www.dataprovider1.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1776' }] @@ -136,7 +143,7 @@ describe('hadronRtdProvider', function() { const userObj2 = { name: 'www.dataprovider2.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1914' }] @@ -195,12 +202,12 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.site.content.data).to.have.lengthOf(1); }); - it('merges bidder-specific ortb2 data', function() { + it('merges bidder-specific ortb2 data', function () { let rtdConfig = {}; const configUserObj1 = { name: 'www.dataprovider1.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '1776' }] @@ -208,7 +215,7 @@ describe('hadronRtdProvider', function() { const configUserObj2 = { name: 'www.dataprovider2.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '1914' }] @@ -216,7 +223,7 @@ describe('hadronRtdProvider', function() { const configUserObj3 = { name: 'www.dataprovider1.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '2003' }] @@ -372,12 +379,12 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj2, rtdSiteObj2]); }); - it('merges bidder-specific ortb2 data without duplication', function() { + it('merges bidder-specific ortb2 data without duplication', function () { let rtdConfig = {}; const userObj1 = { name: 'www.dataprovider1.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '1776' }] @@ -385,7 +392,7 @@ describe('hadronRtdProvider', function() { const userObj2 = { name: 'www.dataprovider2.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '1914' }] @@ -393,7 +400,7 @@ describe('hadronRtdProvider', function() { const userObj3 = { name: 'www.dataprovider1.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '2003' }] @@ -501,10 +508,10 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.site.content.data).to.have.lengthOf(2); }); - it('allows publisher defined rtd ortb2 logic', function() { + it('allows publisher defined rtd ortb2 logic', function () { const rtdConfig = { params: { - handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + handleRtd: function (bidConfig, rtd, rtdConfig, pbConfig) { if (rtd.ortb2.user.data[0].segment[0].id == '1776') { pbConfig.setConfig({ortb2: rtd.ortb2}); } else { @@ -518,7 +525,7 @@ describe('hadronRtdProvider', function() { const rtdUserObj1 = { name: 'www.dataprovider.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1776' }] @@ -564,10 +571,10 @@ describe('hadronRtdProvider', function() { expect(config.getConfig().ortb2).to.deep.equal({}); }); - it('allows publisher defined adunit logic', function() { + it('allows publisher defined adunit logic', function () { const rtdConfig = { params: { - handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + handleRtd: function (bidConfig, rtd, rtdConfig, pbConfig) { var adUnits = bidConfig.adUnits; for (var i = 0; i < adUnits.length; i++) { var adUnit = adUnits[i]; @@ -629,8 +636,8 @@ describe('hadronRtdProvider', function() { }); }); - describe('Get Real-Time Data', function() { - it('gets rtd from local storage cache', function() { + describe('Get Real-Time Data', function () { + it('gets rtd from local storage cache', function () { const rtdConfig = { params: { segmentCache: true @@ -665,12 +672,12 @@ describe('hadronRtdProvider', function() { }; getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd)); - - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + getRealTimeData(bidConfig, () => { + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + }, rtdConfig, {}); }); - it('gets real-time data via async request', function() { + it('gets real-time data via async request', function () { const setConfigSiteObj1 = { name: 'www.audigent.com', ext: { @@ -736,16 +743,14 @@ describe('hadronRtdProvider', function() { }; getDataFromLocalStorageStub.withArgs(HADRONID_LOCAL_NAME).returns('testHadronId1'); - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - - let request = server.requests[0]; - let postData = JSON.parse(request.requestBody); - expect(postData.config).to.have.deep.property('publisherId', 'testPub1'); - expect(postData.userIds).to.have.deep.property('hadronId', 'testHadronId1'); - - request.respond(200, responseHeader, JSON.stringify(data)); - - expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + getRealTimeData(bidConfig, () => { + let request = server.requests[0]; + let postData = JSON.parse(request.requestBody); + expect(postData.config).to.have.deep.property('publisherId', 'testPub1'); + expect(postData.userIds).to.have.deep.property('hadronId', 'testHadronId1'); + request.respond(200, responseHeader, JSON.stringify(data)); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + }, rtdConfig, {}); }); }); });