• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

prebid / Prebid.js / 16279920599

14 Jul 2025 11:12PM UTC coverage: 96.2% (-0.002%) from 96.202%
16279920599

push

github

412021
web-flow
maintenance: enforce no-global-assign in tests (#13575)

39190 of 48203 branches covered (81.3%)

12 of 12 new or added lines in 1 file covered. (100.0%)

27 existing lines in 19 files now uncovered.

192751 of 200364 relevant lines covered (96.2%)

87.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.04
/test/spec/modules/uid2IdSystem_spec.js
1
import {attachIdSystem, coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js';
2
import {config} from 'src/config.js';
3
import * as utils from 'src/utils.js';
4
import { uid2IdSubmodule } from 'modules/uid2IdSystem.js';
5
import {requestBids} from '../../../src/prebid.js';
6
import 'modules/consentManagementTcf.js';
7
import { getGlobal } from 'src/prebidGlobal.js';
8
import { configureTimerInterceptors } from 'test/mocks/timers.js';
9
import { cookieHelpers, runAuction, apiHelpers, setGdprApplies } from './uid2IdSystem_helpers.js';
10
import {hook} from 'src/hook.js';
11
import {uninstall as uninstallTcfControl} from 'modules/tcfControl.js';
12
import {server} from 'test/mocks/xhr';
13
import {createEidsArray} from '../../../modules/userId/eids.js';
14

15
const expect = require('chai').expect;
1✔
16

17
const clearTimersAfterEachTest = true;
1✔
18
const debugOutput = () => {};
1✔
19

20
const moduleCookieName = '__uid2_advertising_token';
1✔
21
const publisherCookieName = '__UID2_SERVER_COOKIE';
1✔
22
const auctionDelayMs = 10;
1✔
23
const initialToken = `initial-advertising-token`;
1✔
24
const legacyToken = 'legacy-advertising-token';
1✔
25
const refreshedToken = 'refreshed-advertising-token';
1✔
26
const clientSideGeneratedToken = 'client-side-generated-advertising-token';
1✔
27
const optoutToken = 'optout-token';
1✔
28

29
const legacyConfigParams = {storage: null};
1✔
30
const serverCookieConfigParams = { uid2ServerCookie: publisherCookieName };
1✔
31
const newServerCookieConfigParams = { uid2Cookie: publisherCookieName };
1✔
32
const cstgConfigParams = { serverPublicKey: 'UID2-X-L-24B8a/eLYBmRkXA9yPgRZt+ouKbXewG2OPs23+ov3JC8mtYJBCx6AxGwJ4MlwUcguebhdDp2CvzsCgS9ogwwGA==', subscriptionId: 'subscription-id' }
1✔
33

34
const makeUid2IdentityContainer = (token) => ({uid2: {id: token}});
5✔
35
const makeUid2OptoutContainer = (token) => ({uid2: {optout: true}});
1✔
36
let useLocalStorage = false;
1✔
37
const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({
134!
38
  userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}}] }, debug, ...extraSettings
134✔
39
});
40
const makeOriginalIdentity = (identity, salt = 1) => ({
16✔
41
  identity: utils.cyrb53Hash(identity, salt),
42
  salt
43
})
44

45
const getFromAppropriateStorage = () => {
1✔
46
  if (useLocalStorage) return coreStorage.getDataFromLocalStorage(moduleCookieName);
50✔
47
  else return coreStorage.getCookie(moduleCookieName);
42✔
48
}
49

50
const UID2_SOURCE = 'uidapi.com';
1✔
51
function findUid2(bid) {
52
  return (bid?.userIdAsEids ?? []).find(e => e.source === UID2_SOURCE);
102!
53
}
54
const expectToken = (bid, token) => {
1✔
55
  const eid = findUid2(bid);
36✔
56
  expect(eid && eid.uids[0].id).to.equal(token);
36✔
57
};
58
const expectLegacyToken = (bid) => {
1✔
59
  const eid = findUid2(bid);
3✔
60
  expect(eid && eid.uids[0].id).to.equal(legacyToken);
3✔
61
};
62
const expectNoIdentity = (bid) => expect(findUid2(bid)).to.be.undefined;
57✔
63
const expectOptout = (bid) => expect(findUid2(bid)).to.be.undefined;
1✔
64
const expectGlobalToHaveToken = (token) => expect(getGlobal().getUserIds()).to.deep.include(makeUid2IdentityContainer(token));
5✔
65
const expectGlobalToHaveNoUid2 = () => expect(getGlobal().getUserIds()).to.not.haveOwnProperty('uid2');
33✔
66
const expectNoLegacyToken = (bid) => {
1✔
67
  const eid = findUid2(bid);
3✔
68
  if (eid) expect(eid.uids[0].id).to.not.equal(legacyToken);
3✔
69
};
70
const expectModuleStorageEmptyOrMissing = () => expect(getFromAppropriateStorage()).to.be.null;
30✔
71
const expectModuleStorageToContain = (originalAdvertisingToken, latestAdvertisingToken, originalIdentity) => {
1✔
72
  const cookie = JSON.parse(getFromAppropriateStorage());
20✔
73
  if (originalAdvertisingToken) expect(cookie.originalToken.advertising_token).to.equal(originalAdvertisingToken);
20✔
74
  if (latestAdvertisingToken) expect(cookie.latestToken.advertising_token).to.equal(latestAdvertisingToken);
20✔
75
  if (originalIdentity) expect(cookie.originalIdentity).to.eql(makeOriginalIdentity(Object.values(originalIdentity)[0], cookie.originalIdentity.salt));
20✔
76
}
77

78
const apiUrl = 'https://prod.uidapi.com/v2/token'
1✔
79
const refreshApiUrl = `${apiUrl}/refresh`;
1✔
80
const headers = { 'Content-Type': 'application/json' };
1✔
81
const makeSuccessResponseBody = (responseToken) => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: responseToken } }));
40✔
82
const makeOptoutResponseBody = (token) => btoa(JSON.stringify({ status: 'optout', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: token } }));
1✔
83
const cstgApiUrl = `${apiUrl}/client-generate`;
1✔
84

85
const testCookieAndLocalStorage = (description, test, only = false) => {
2!
86
  const describeFn = only ? describe.only : describe;
2!
87
  describeFn(`Using cookies: ${description}`, async function() {
2✔
88
    before(function() {
2✔
89
      useLocalStorage = false;
2✔
90
    });
91
    await test();
2✔
92
  });
93
  describeFn(`Using local storage: ${description}`, async function() {
2✔
94
    before(function() {
2✔
95
      useLocalStorage = true;
2✔
96
    });
97
    after(function() {
2✔
98
      useLocalStorage = false;
2✔
99
    });
100
    await test();
2✔
101
  });
102
};
103

104
describe(`UID2 module`, function () {
1✔
105
  let suiteSandbox; let testSandbox; let timerSpy; let fullTestTitle; let restoreSubtleToUndefined = false;
1✔
106
  before(function () {
1✔
107
    timerSpy = configureTimerInterceptors(debugOutput);
1✔
108
    hook.ready();
1✔
109
    uninstallTcfControl();
1✔
110
    attachIdSystem(uid2IdSubmodule);
1✔
111

112
    suiteSandbox = sinon.createSandbox();
1✔
113
    // I'm unable to find an authoritative source, but apparently subtle isn't available in some test stacks for security reasons.
114
    // I've confirmed it's available in Firefox since v34 (it seems to be unavailable on BrowserStack in Firefox v106).
115
    if (typeof window.crypto.subtle === 'undefined') {
1!
UNCOV
116
      restoreSubtleToUndefined = true;
×
UNCOV
117
      window.crypto.subtle = { importKey: () => {}, digest: () => {}, decrypt: () => {}, deriveKey: () => {}, encrypt: () => {}, generateKey: () => {}, exportKey: () => {} };
×
118
    }
119
    suiteSandbox.stub(window.crypto.subtle, 'importKey').callsFake(() => Promise.resolve());
57✔
120
    suiteSandbox.stub(window.crypto.subtle, 'digest').callsFake(() => Promise.resolve('hashed_value'));
28✔
121
    suiteSandbox.stub(window.crypto.subtle, 'decrypt').callsFake((settings, key, data) => Promise.resolve(new Uint8Array([...settings.iv, ...data])));
32✔
122
    suiteSandbox.stub(window.crypto.subtle, 'deriveKey').callsFake(() => Promise.resolve());
40✔
123
    suiteSandbox.stub(window.crypto.subtle, 'exportKey').callsFake(() => Promise.resolve());
40✔
124
    suiteSandbox.stub(window.crypto.subtle, 'encrypt').callsFake(() => Promise.resolve(new ArrayBuffer()));
40✔
125
    suiteSandbox.stub(window.crypto.subtle, 'generateKey').callsFake(() => Promise.resolve({
40✔
126
      privateKey: {},
127
      publicKey: {}
128
    }));
129
  });
130

131
  after(function () {
1✔
132
    suiteSandbox.restore();
1✔
133
    timerSpy.restore();
1✔
134
    if (restoreSubtleToUndefined) window.crypto.subtle = undefined;
1!
135
  });
136

137
  const configureUid2Response = (apiUrl, httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response));
80✔
138
  const configureUid2ApiSuccessResponse = (apiUrl, responseToken) => configureUid2Response(apiUrl, 200, makeSuccessResponseBody(responseToken));
40✔
139
  const configureUid2ApiFailResponse = (apiUrl) => configureUid2Response(apiUrl, 500, 'Error');
40✔
140
  const configureUid2CstgResponse = (httpStatus, response) => server.respondWith('POST', cstgApiUrl, (xhr) => xhr.respond(httpStatus, headers, response));
1✔
141
  // Runs the provided test twice - once with a successful API mock, once with one which returns a server error
142
  const testApiSuccessAndFailure = (act, apiUrl, testDescription, failTestDescription, only = false, responseToken = refreshedToken) => {
40✔
143
    const testFn = only ? it.only : it;
40!
144
    testFn(`API responds successfully: ${testDescription}`, async function() {
40✔
145
      configureUid2ApiSuccessResponse(apiUrl, responseToken);
40✔
146
      await act(true);
40✔
147
    });
148
    testFn(`API responds with an error: ${failTestDescription ?? testDescription}`, async function() {
40✔
149
      configureUid2ApiFailResponse(apiUrl);
40✔
150
      await act(false);
40✔
151
    });
152
  }
153

154
  const getFullTestTitle = (test) => `${test.parent.title ? getFullTestTitle(test.parent) + ' | ' : ''}${test.title}`;
614✔
155

156
  beforeEach(function () {
1✔
157
    debugOutput(`----------------- START TEST ------------------`);
135✔
158
    fullTestTitle = getFullTestTitle(this.test.ctx.currentTest);
135✔
159
    debugOutput(fullTestTitle);
135✔
160
    testSandbox = sinon.createSandbox();
135✔
161
    testSandbox.stub(utils, 'logWarn');
135✔
162
    init(config);
135✔
163
    setSubmoduleRegistry([uid2IdSubmodule]);
135✔
164
  });
165

166
  afterEach(async function() {
1✔
167
    requestBids.removeAll();
135✔
168
    config.resetConfig();
135✔
169
    testSandbox.restore();
135✔
170
    if (timerSpy.timers.length > 0) {
135✔
171
      if (clearTimersAfterEachTest) {
8!
172
        debugOutput(`Cancelling ${timerSpy.timers.length} still-active timers.`);
8✔
173
        timerSpy.clearAllActiveTimers();
8✔
174
      } else {
175
        debugOutput(`Waiting on ${timerSpy.timers.length} still-active timers...`, timerSpy.timers);
×
176
        await timerSpy.waitAllActiveTimers();
×
177
      }
178
    }
179
    cookieHelpers.clearCookies(moduleCookieName, publisherCookieName);
135✔
180
    coreStorage.removeDataFromLocalStorage(moduleCookieName);
135✔
181
    debugOutput('----------------- END TEST ------------------');
135✔
182
  });
183

184
  describe('Configuration', function() {
1✔
185
    it('When no baseUrl is provided in config, the module calls the production endpoint', async function () {
1✔
186
      const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true);
1✔
187
      config.setConfig(makePrebidConfig({uid2Token}));
1✔
188
      await runAuction();
1✔
189
      expect(server.requests[0]?.url).to.have.string('https://prod.uidapi.com/v2/token/refresh');
1✔
190
    });
191

192
    it('When a baseUrl is provided in config, the module calls the provided endpoint', async function () {
1✔
193
      const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true);
1✔
194
      config.setConfig(makePrebidConfig({uid2Token, uid2ApiBase: 'https://operator-integ.uidapi.com'}));
1✔
195
      await runAuction();
1✔
196
      expect(server.requests[0]?.url).to.have.string('https://operator-integ.uidapi.com/v2/token/refresh');
1✔
197
    });
198
  });
199

200
  it('When a legacy value is provided directly in configuration, it is passed on', async function() {
1✔
201
    const valueConfig = makePrebidConfig();
1✔
202
    valueConfig.userSync.userIds[0].value = {uid2: {id: legacyToken}}
1✔
203
    config.setConfig(valueConfig);
1✔
204
    const bid = await runAuction();
1✔
205

206
    expectLegacyToken(bid);
1✔
207
  });
208

209
  // These tests cover 'legacy' cookies - i.e. cookies set with just the uid2 advertising token, which was how some previous integrations worked.
210
  // Some users might still have this cookie, and the module should use that token if a newer one isn't provided.
211
  // This should cover older integrations where the server is setting this legacy cookie and expecting the module to pass it on.
212
  describe('When a legacy cookie exists', function () {
1✔
213
    // Creates a test which sets the legacy cookie, configures the UID2 module with provided params, runs an
214
    const createLegacyTest = function(params, bidAssertions, addConsent = false) {
6✔
215
      return async function() {
6✔
216
        coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry());
6✔
217
        if (addConsent) setGdprApplies();
6✔
218
        config.setConfig(makePrebidConfig(params));
6✔
219

220
        const bid = await runAuction();
6✔
221
        bidAssertions.forEach(function(assertion) { assertion(bid); });
9✔
222
      }
223
    };
224

225
    it('and a legacy config is used, it should provide the legacy cookie',
1✔
226
      createLegacyTest(legacyConfigParams, [expectLegacyToken]));
227
    it('and a server cookie config is used without a valid server cookie, it should provide the legacy cookie',
1✔
228
      createLegacyTest(serverCookieConfigParams, [expectLegacyToken]));
229
    it('and a server cookie is used with a valid server cookie configured using the new param name, it should provide the server cookie',
1✔
230
      async function() { cookieHelpers.setPublisherCookie(publisherCookieName, apiHelpers.makeTokenResponse(initialToken)); await createLegacyTest(serverCookieConfigParams, [(bid) => expectToken(bid, initialToken), expectNoLegacyToken])(); });
1✔
231
    it('and a server cookie is used with a valid server cookie, it should provide the server cookie',
1✔
232
      async function() { cookieHelpers.setPublisherCookie(publisherCookieName, apiHelpers.makeTokenResponse(initialToken)); await createLegacyTest(newServerCookieConfigParams, [(bid) => expectToken(bid, initialToken), expectNoLegacyToken])(); });
1✔
233
    it('and a token is provided in config, it should provide the config token',
1✔
234
      createLegacyTest({uid2Token: apiHelpers.makeTokenResponse(initialToken)}, [(bid) => expectToken(bid, initialToken), expectNoLegacyToken]));
1✔
235
    it('and GDPR applies, no identity should be provided to the auction',
1✔
236
      createLegacyTest(legacyConfigParams, [expectNoIdentity], true));
237
    it('and GDPR applies, when getId is called directly it provides no identity', () => {
1✔
238
      coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry());
1✔
239
      const consentConfig = setGdprApplies();
1✔
240
      const configObj = makePrebidConfig(legacyConfigParams);
1✔
241
      const result = uid2IdSubmodule.getId(configObj.userSync.userIds[0], {gdpr: consentConfig.consentData});
1✔
242
      expect(result?.id).to.not.exist;
1✔
243
    });
244

245
    it('multiple runs do not change the value', async function() {
1✔
246
      coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry());
1✔
247
      config.setConfig(makePrebidConfig(legacyConfigParams));
1✔
248

249
      const bid = await runAuction();
1✔
250

251
      init(config);
1✔
252
      setSubmoduleRegistry([uid2IdSubmodule]);
1✔
253
      config.setConfig(makePrebidConfig(legacyConfigParams));
1✔
254
      const bid2 = await runAuction();
1✔
255

256
      const first = findUid2(bid);
1✔
257
      const second = findUid2(bid2);
1✔
258
      expect(first && second && first.uids[0].id).to.equal(second.uids[0].id);
1✔
259
    });
260
  });
261

262
  // This setup runs all of the functional tests with both types of config - the full token response in params, or a server cookie with the cookie name provided
263
  const scenarios = [
1✔
264
    {
265
      name: 'Token provided in config call',
266
      setConfig: (token, extraConfig = {}) => {
32✔
267
        const gen = makePrebidConfig({uid2Token: token}, extraConfig);
32✔
268
        return config.setConfig(gen);
32✔
269
      },
270
    },
271
    {
272
      name: 'Token provided in server-set cookie',
273
      setConfig: (token, extraConfig) => {
274
        cookieHelpers.setPublisherCookie(publisherCookieName, token);
32✔
275
        config.setConfig(makePrebidConfig(serverCookieConfigParams, extraConfig));
32✔
276
      },
277
    },
278
  ]
279

280
  scenarios.forEach(function(scenario) {
1✔
281
    testCookieAndLocalStorage(scenario.name, function() {
2✔
282
      describe(`When an expired token which can be refreshed is provided`, function() {
4✔
283
        describe('When the refresh is available in time', function() {
4✔
284
          testApiSuccessAndFailure(async function(apiSucceeds) {
4✔
285
            scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true));
8✔
286
            apiHelpers.respondAfterDelay(auctionDelayMs / 10, server);
8✔
287
            const bid = await runAuction();
8✔
288

289
            if (apiSucceeds) expectToken(bid, refreshedToken);
8✔
290
            else expectNoIdentity(bid);
4✔
291
          }, refreshApiUrl, 'it should be used in the auction', 'the auction should have no uid2');
292

293
          testApiSuccessAndFailure(async function(apiSucceeds) {
4✔
294
            scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true));
8✔
295
            apiHelpers.respondAfterDelay(auctionDelayMs / 10, server);
8✔
296

297
            await runAuction();
8✔
298
            if (apiSucceeds) {
8✔
299
              expectModuleStorageToContain(initialToken, refreshedToken);
4✔
300
            } else {
301
              expectModuleStorageEmptyOrMissing();
4✔
302
            }
303
          }, refreshApiUrl, 'the refreshed token should be stored in the module storage', 'the module storage should not be set');
304
        });
305
        describe(`when the response doesn't arrive before the auction timer`, function() {
4✔
306
          testApiSuccessAndFailure(async function() {
4✔
307
            scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true));
8✔
308
            const bid = await runAuction();
8✔
309
            expectNoIdentity(bid);
8✔
310
          }, refreshApiUrl, 'it should run the auction');
311

312
          testApiSuccessAndFailure(async function(apiSucceeds) {
4✔
313
            scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true));
8✔
314
            const promise = apiHelpers.respondAfterDelay(auctionDelayMs * 2, server);
8✔
315

316
            const bid = await runAuction();
8✔
317
            expectNoIdentity(bid);
8✔
318
            expectGlobalToHaveNoUid2();
8✔
319
            await promise;
8✔
320
            if (apiSucceeds) expectGlobalToHaveToken(refreshedToken);
8✔
321
            else expectGlobalToHaveNoUid2();
4✔
322
          }, refreshApiUrl, 'it should update the userId after the auction', 'there should be no global identity');
323
        })
324
        describe('and there is a refreshed token in the module cookie', function() {
4✔
325
          it('the refreshed value from the cookie is used', async function() {
4✔
326
            const initialIdentity = apiHelpers.makeTokenResponse(initialToken, true, true);
4✔
327
            const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken);
4✔
328
            const moduleCookie = {originalToken: initialIdentity, latestToken: refreshedIdentity};
4✔
329
            coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
4✔
330
            scenario.setConfig(initialIdentity);
4✔
331

332
            const bid = await runAuction();
4✔
333
            expectToken(bid, refreshedToken);
4✔
334
          });
335
        })
336
      });
337

338
      describe(`When a current token is provided`, function() {
4✔
339
        it('it should use the token in the auction', async function() {
4✔
340
          scenario.setConfig(apiHelpers.makeTokenResponse(initialToken));
4✔
341
          const bid = await runAuction();
4✔
342
          expectToken(bid, initialToken);
4✔
343
        });
344

345
        it('and GDPR applies, the token should not be used', async function() {
4✔
346
          setGdprApplies();
4✔
347
          scenario.setConfig(apiHelpers.makeTokenResponse(initialToken));
4✔
348
          const bid = await runAuction();
4✔
349
          expectNoIdentity(bid);
4✔
350
        })
351
      });
352

353
      describe(`When a current token which should be refreshed is provided, and the auction is set to run immediately`, function() {
4✔
354
        beforeEach(function() {
4✔
355
          scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true), {auctionDelay: 0, syncDelay: 1});
20✔
356
        });
357
        testApiSuccessAndFailure(async function() {
4✔
358
          apiHelpers.respondAfterDelay(10, server);
8✔
359
          const bid = await runAuction();
8✔
360
          expectToken(bid, initialToken);
8✔
361
        }, refreshApiUrl, 'it should not be refreshed before the auction runs');
362

363
        testApiSuccessAndFailure(async function(success) {
4✔
364
          const promise = apiHelpers.respondAfterDelay(1, server);
8✔
365
          await runAuction();
8✔
366
          await promise;
8✔
367
          if (success) {
8✔
368
            expectModuleStorageToContain(initialToken, refreshedToken);
4✔
369
          } else {
370
            expectModuleStorageToContain(initialToken, initialToken);
4✔
371
          }
372
        }, refreshApiUrl, 'the refreshed token should be stored in the module cookie after the auction runs', 'the module cookie should only have the original token');
373

374
        it('it should use the current token in the auction', async function() {
4✔
375
          const bid = await runAuction();
4✔
376
          expectToken(bid, initialToken);
4✔
377
        });
378
      });
379
    });
380
  });
381

382
  if (FEATURES.UID2_CSTG) {
1✔
383
    describe('When CSTG is enabled provided', function () {
1✔
384
      const scenarios = [
1✔
385
        {
386
          name: 'email provided in config',
387
          identity: { email: 'test@example.com' },
388
          setConfig: function (extraConfig) { config.setConfig(makePrebidConfig({ ...cstgConfigParams, ...this.identity }, extraConfig)) },
6✔
389
          setInvalidConfig: (extraConfig) => config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test . test@gmail.com' }, extraConfig))
1✔
390
        },
391
        {
392
          name: 'phone provided in config',
393
          identity: { phone: '+12345678910' },
394
          setConfig: function (extraConfig) { config.setConfig(makePrebidConfig({ ...cstgConfigParams, ...this.identity }, extraConfig)) },
6✔
395
          setInvalidConfig: (extraConfig) => config.setConfig(makePrebidConfig({ ...cstgConfigParams, phone: 'test123' }, extraConfig))
1✔
396
        },
397
        {
398
          name: 'email hash provided in config',
399
          identity: { email_hash: 'lz3+Rj7IV4X1+Vr1ujkG7tstkxwk5pgkqJ6mXbpOgTs=' },
400
          setConfig: function (extraConfig) { config.setConfig(makePrebidConfig({ ...cstgConfigParams, emailHash: this.identity.email_hash }, extraConfig)) },
6✔
401
          setInvalidConfig: (extraConfig) => config.setConfig(makePrebidConfig({ ...cstgConfigParams, emailHash: 'test@example.com' }, extraConfig))
1✔
402
        },
403
        {
404
          name: 'phone hash provided in config',
405
          identity: { phone_hash: 'kVJ+4ilhrqm3HZDDnCQy4niZknvCoM4MkoVzZrQSdJw=' },
406
          setConfig: function (extraConfig) { config.setConfig(makePrebidConfig({ ...cstgConfigParams, phoneHash: this.identity.phone_hash }, extraConfig)) },
6✔
407
          setInvalidConfig: (extraConfig) => config.setConfig(makePrebidConfig({ ...cstgConfigParams, phoneHash: '614332222111' }, extraConfig))
1✔
408
        },
409
      ]
410
      scenarios.forEach(function(scenario) {
1✔
411
        describe(`And ${scenario.name}`, function() {
4✔
412
          describe(`When invalid identity is provided`, function() {
4✔
413
            it('the auction should have no uid2', async function () {
4✔
414
              scenario.setInvalidConfig()
4✔
415
              const bid = await runAuction();
4✔
416
              expectNoIdentity(bid);
4✔
417
              expectGlobalToHaveNoUid2();
4✔
418
              expectModuleStorageEmptyOrMissing();
4✔
419
            })
420
          });
421

422
          describe('When valid identity is provided, and the auction is set to run immediately', function() {
4✔
423
            it('it should ignores token provided in config, and the auction should have no uid2', async function() {
4✔
424
              scenario.setConfig({ uid2Token: apiHelpers.makeTokenResponse(initialToken), auctionDelay: 0, syncDelay: 1 });
4✔
425
              const bid = await runAuction();
4✔
426
              expectNoIdentity(bid);
4✔
427
              expectGlobalToHaveNoUid2();
4✔
428
              expectModuleStorageEmptyOrMissing();
4✔
429
            })
430

431
            it('it should ignores token provided in server-set cookie', async function() {
4✔
432
              cookieHelpers.setPublisherCookie(publisherCookieName, initialToken);
4✔
433
              scenario.setConfig({ ...newServerCookieConfigParams, auctionDelay: 0, syncDelay: 1 })
4✔
434
              const bid = await runAuction();
4✔
435
              expectNoIdentity(bid);
4✔
436
              expectGlobalToHaveNoUid2();
4✔
437
              expectModuleStorageEmptyOrMissing();
4✔
438
            })
439

440
            describe('When the token generated in time', function() {
4✔
441
              testApiSuccessAndFailure(async function(apiSucceeds) {
4✔
442
                scenario.setConfig();
8✔
443
                apiHelpers.respondAfterDelay(auctionDelayMs / 10, server);
8✔
444
                const bid = await runAuction();
8✔
445

446
                if (apiSucceeds) expectToken(bid, clientSideGeneratedToken);
8✔
447
                else expectNoIdentity(bid);
4✔
448
              }, cstgApiUrl, 'it should be used in the auction', 'the auction should have no uid2', false, clientSideGeneratedToken);
449

450
              testApiSuccessAndFailure(async function(apiSucceeds) {
4✔
451
                scenario.setConfig();
8✔
452
                apiHelpers.respondAfterDelay(auctionDelayMs / 10, server);
8✔
453

454
                await runAuction();
8✔
455
                if (apiSucceeds) {
8✔
456
                  expectModuleStorageToContain(undefined, clientSideGeneratedToken, scenario.identity);
4✔
457
                } else {
458
                  expectModuleStorageEmptyOrMissing();
4✔
459
                }
460
              }, cstgApiUrl, 'the generated token should be stored in the module storage', 'the module storage should not be set', false, clientSideGeneratedToken);
461
            });
462
          });
463
        });
464
      });
465
      it('Should receive an optout response when the user has opted out.', async function() {
1✔
466
        const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true);
1✔
467
        configureUid2CstgResponse(200, makeOptoutResponseBody(optoutToken));
1✔
468
        config.setConfig(makePrebidConfig({ uid2Token, ...cstgConfigParams, email: 'optout@test.com' }));
1✔
469
        apiHelpers.respondAfterDelay(1, server);
1✔
470

471
        const bid = await runAuction();
1✔
472
        expectOptout(bid, optoutToken);
1✔
473
      });
474
      describe(`when the response doesn't arrive before the auction timer`, function() {
1✔
475
        testApiSuccessAndFailure(async function() {
1✔
476
          config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' }));
2✔
477
          const bid = await runAuction();
2✔
478
          expectNoIdentity(bid);
2✔
479
        }, cstgApiUrl, 'it should run the auction', undefined, false, clientSideGeneratedToken);
480

481
        testApiSuccessAndFailure(async function(apiSucceeds) {
1✔
482
          config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' }));
2✔
483
          const promise = apiHelpers.respondAfterDelay(auctionDelayMs * 2, server);
2✔
484

485
          const bid = await runAuction();
2✔
486
          expectNoIdentity(bid);
2✔
487
          expectGlobalToHaveNoUid2();
2✔
488
          await promise;
2✔
489
          if (apiSucceeds) expectGlobalToHaveToken(clientSideGeneratedToken);
2✔
490
          else expectGlobalToHaveNoUid2();
1✔
491
        }, cstgApiUrl, 'it should update the userId after the auction', 'there should be no global identity', false, clientSideGeneratedToken);
492
      })
493

494
      describe('when there is a token in the module cookie', function() {
1✔
495
        describe('when originalIdentity matches', function() {
1✔
496
          describe('When the storedToken is valid', function() {
1✔
497
            it('it should use the stored token in the auction', async function() {
1✔
498
              const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken);
1✔
499
              const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity};
1✔
500
              coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
1✔
501
              config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com', auctionDelay: 0, syncDelay: 1 }));
1✔
502
              const bid = await runAuction();
1✔
503
              expectToken(bid, refreshedToken);
1✔
504
            });
505
          })
506

507
          describe('When the storedToken is expired and can be refreshed ', function() {
1✔
508
            testApiSuccessAndFailure(async function(apiSucceeds) {
1✔
509
              const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken, true, true);
2✔
510
              const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity};
2✔
511
              coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
2✔
512
              config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' }));
2✔
513
              apiHelpers.respondAfterDelay(auctionDelayMs / 10, server);
2✔
514

515
              const bid = await runAuction();
2✔
516

517
              if (apiSucceeds) expectToken(bid, refreshedToken);
2✔
518
              else expectNoIdentity(bid);
1✔
519
            }, refreshApiUrl, 'it should use refreshed token in the auction', 'the auction should have no uid2');
520
          })
521

522
          describe('When the storedToken is expired for refresh', function() {
1✔
523
            testApiSuccessAndFailure(async function(apiSucceeds) {
1✔
524
              const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken, true, true, true);
2✔
525
              const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity};
2✔
526
              coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
2✔
527
              config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' }));
2✔
528
              apiHelpers.respondAfterDelay(auctionDelayMs / 10, server);
2✔
529

530
              const bid = await runAuction();
2✔
531

532
              if (apiSucceeds) expectToken(bid, clientSideGeneratedToken);
2✔
533
              else expectNoIdentity(bid);
1✔
534
            }, cstgApiUrl, 'it should use generated token in the auction', 'the auction should have no uid2', false, clientSideGeneratedToken);
535
          })
536
        })
537

538
        it('when originalIdentity not match, the auction should has no uid2', async function() {
1✔
539
          const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken);
1✔
540
          const moduleCookie = {originalIdentity: makeOriginalIdentity('123@test.com'), latestToken: refreshedIdentity};
1✔
541
          coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
1✔
542
          config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' }));
1✔
543
          const bid = await runAuction();
1✔
544
          expectNoIdentity(bid);
1✔
545
        });
546
      })
547
    });
548
    describe('When invalid CSTG configuration is provided', function () {
1✔
549
      const invalidConfigs = [
1✔
550
        {
551
          name: 'CSTG option is not a object',
552
          cstgOptions: ''
553
        },
554
        {
555
          name: 'CSTG option is null',
556
          cstgOptions: ''
557
        },
558
        {
559
          name: 'serverPublicKey is not a string',
560
          cstgOptions: { subscriptionId: cstgConfigParams.subscriptionId, serverPublicKey: {} }
561
        },
562
        {
563
          name: 'serverPublicKey not match regular expression',
564
          cstgOptions: { subscriptionId: cstgConfigParams.subscriptionId, serverPublicKey: 'serverPublicKey' }
565
        },
566
        {
567
          name: 'subscriptionId is not a string',
568
          cstgOptions: { subscriptionId: {}, serverPublicKey: cstgConfigParams.serverPublicKey }
569
        },
570
        {
571
          name: 'subscriptionId is empty',
572
          cstgOptions: { subscriptionId: '', serverPublicKey: cstgConfigParams.serverPublicKey }
573
        },
574
      ]
575
      invalidConfigs.forEach(function(scenario) {
1✔
576
        describe(`When ${scenario.name}`, function() {
6✔
577
          it('should not generate token using identity', async () => {
6✔
578
            config.setConfig(makePrebidConfig({ ...scenario.cstgOptions, email: 'test@email.com' }));
6✔
579
            const bid = await runAuction();
6✔
580
            expectNoIdentity(bid);
6✔
581
            expectGlobalToHaveNoUid2();
6✔
582
            expectModuleStorageEmptyOrMissing();
6✔
583
          });
584
        });
585
      });
586
    });
587
    describe('When email is provided in different format', function () {
1✔
588
      const testCases = [
1✔
589
        { originalEmail: 'TEst.TEST@Test.com ', normalizedEmail: 'test.test@test.com' },
590
        { originalEmail: 'test+test@test.com', normalizedEmail: 'test+test@test.com' },
591
        { originalEmail: '  testtest@test.com  ', normalizedEmail: 'testtest@test.com' },
592
        { originalEmail: 'TEst.TEst+123@GMail.Com', normalizedEmail: 'testtest@gmail.com' }
593
      ];
594
      testCases.forEach((testCase) => {
1✔
595
        describe('it should normalize the email and generate token on normalized email', async () => {
4✔
596
          testApiSuccessAndFailure(async function(apiSucceeds) {
4✔
597
            config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: testCase.originalEmail }));
8✔
598
            apiHelpers.respondAfterDelay(auctionDelayMs / 10, server);
8✔
599

600
            await runAuction();
8✔
601
            if (apiSucceeds) {
8✔
602
              expectModuleStorageToContain(undefined, clientSideGeneratedToken, { email: testCase.normalizedEmail });
4✔
603
            } else {
604
              expectModuleStorageEmptyOrMissing();
4✔
605
            }
606
          }, cstgApiUrl, 'the generated token should be stored in the module storage', 'the module storage should not be set', false, clientSideGeneratedToken);
607
        });
608
      });
609
    });
610
  }
611

612
  describe('When neither token nor CSTG config provided', function () {
1✔
613
    describe('when there is a non-cstg-derived token in the module cookie', function () {
1✔
614
      it('the auction use stored token if it is valid', async function () {
1✔
615
        const originalIdentity = apiHelpers.makeTokenResponse(initialToken);
1✔
616
        const moduleCookie = {originalToken: originalIdentity, latestToken: originalIdentity};
1✔
617
        coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
1✔
618
        config.setConfig(makePrebidConfig({}));
1✔
619
        const bid = await runAuction();
1✔
620
        expectToken(bid, initialToken);
1✔
621
      })
622

623
      it('the auction should has no uid2 if stored token is invalid', async function () {
1✔
624
        const originalIdentity = apiHelpers.makeTokenResponse(initialToken, true, true, true);
1✔
625
        const moduleCookie = {originalToken: originalIdentity, latestToken: originalIdentity};
1✔
626
        coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
1✔
627
        config.setConfig(makePrebidConfig({}));
1✔
628
        const bid = await runAuction();
1✔
629
        expectNoIdentity(bid);
1✔
630
      })
631
    })
632

633
    describe('when there is a cstg-derived token in the module cookie', function () {
1✔
634
      it('the auction use stored token if it is valid', async function () {
1✔
635
        const originalIdentity = apiHelpers.makeTokenResponse(initialToken);
1✔
636
        const moduleCookie = {originalIdentity: makeOriginalIdentity('123@test.com'), originalToken: originalIdentity, latestToken: originalIdentity};
1✔
637
        coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
1✔
638
        config.setConfig(makePrebidConfig({}));
1✔
639
        const bid = await runAuction();
1✔
640
        expectToken(bid, initialToken);
1✔
641
      })
642

643
      it('the auction should has no uid2 if stored token is invalid', async function () {
1✔
644
        const originalIdentity = apiHelpers.makeTokenResponse(initialToken, true, true, true);
1✔
645
        const moduleCookie = {originalIdentity: makeOriginalIdentity('123@test.com'), originalToken: originalIdentity, latestToken: originalIdentity};
1✔
646
        coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry());
1✔
647
        config.setConfig(makePrebidConfig({}));
1✔
648
        const bid = await runAuction();
1✔
649
        expectNoIdentity(bid);
1✔
650
      })
651
    })
652

653
    it('the auction should has no uid2', async function () {
1✔
654
      config.setConfig(makePrebidConfig({}));
1✔
655
      const bid = await runAuction();
1✔
656
      expectNoIdentity(bid);
1✔
657
    })
658
  });
659
  describe('eid', () => {
1✔
660
    it('uid2', function() {
1✔
661
      const userId = {
1✔
662
        uid2: {'id': 'Sample_AD_Token'}
663
      };
664
      const newEids = createEidsArray(userId);
1✔
665
      expect(newEids.length).to.equal(1);
1✔
666
      expect(newEids[0]).to.deep.equal({
1✔
667
        source: 'uidapi.com',
668
        uids: [{
669
          id: 'Sample_AD_Token',
670
          atype: 3
671
        }]
672
      });
673
    });
674

675
    it('uid2 with ext', function() {
1✔
676
      const userId = {
1✔
677
        uid2: {'id': 'Sample_AD_Token', 'ext': {'provider': 'some.provider.com'}}
678
      };
679
      const newEids = createEidsArray(userId);
1✔
680
      expect(newEids.length).to.equal(1);
1✔
681
      expect(newEids[0]).to.deep.equal({
1✔
682
        source: 'uidapi.com',
683
        uids: [{
684
          id: 'Sample_AD_Token',
685
          atype: 3,
686
          ext: {
687
            provider: 'some.provider.com'
688
          }
689
        }]
690
      });
691
    });
692
  })
693
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc