From 37f5da1b2f441e2486190248bf5d1de83108ebb4 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Tue, 12 Mar 2024 13:38:44 +0300 Subject: [PATCH 1/5] feat: add yandex id system --- modules/.submodules.json | 3 +- modules/yandexIdSystem.js | 127 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 modules/yandexIdSystem.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 9dfeaf910f8..224fdd6ab04 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -51,7 +51,8 @@ "euidIdSystem", "unifiedIdSystem", "verizonMediaIdSystem", - "zeotapIdPlusIdSystem" + "zeotapIdPlusIdSystem", + "yandexIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/yandexIdSystem.js b/modules/yandexIdSystem.js new file mode 100644 index 00000000000..288c3e58f46 --- /dev/null +++ b/modules/yandexIdSystem.js @@ -0,0 +1,127 @@ +/** + * The {@link module:modules/userId} module is required + * @module modules/yandexIdSystem + * @requires module:modules/userId + */ + +import { MODULE_TYPE_UID } from "../src/activities/modules.js"; +import { submodule } from "../src/hook.js"; +import { getStorageManager } from "../src/storageManager.js"; +import { logInfo } from "../src/utils.js"; + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + */ + +const BIDDER_CODE = "yandex"; + +const USER_ID_KEY = "_ym_uid"; +const USER_ID_COOKIE_EXP = 31536000000; // 365 days + +export const cookieStorage = getStorageManager({ + moduleType: MODULE_TYPE_UID, + moduleName: BIDDER_CODE, +}); + +/** @type {Submodule} */ +export const yandexIdSubmodule = { + /** + * Used to link submodule with config. + * @type {string} + */ + name: BIDDER_CODE, + /** + * Decodes the stored id value for passing to bid requests. + * @param {string} value + */ + decode(value) { + logInfo("decoded value yandexId", value); + + return { yandexId: value }; + }, + getId() { + const yandexUidStorage = new YandexUidStorage(cookieStorage); + + if (!yandexUidStorage.checkIsAvailable()) { + return; + } + + return { + id: yandexUidStorage.getUid(), + }; + }, + eids: { + yandexId: { + source: "yandex", + atype: 1, + }, + }, +}; + +class YandexUidStorage { + /** + * @param {typeof cookieStorage} cookieStorage + */ + constructor(cookieStorage) { + this._cookieStorage = cookieStorage; + } + + _generateUid() { + return new YandexUidGenerator().generateUid(); + } + + _getUserIdFromStorage() { + const id = this._cookieStorage.getCookie(USER_ID_KEY); + + return id; + } + + _setUid(userId) { + if (this._cookieStorage.cookiesAreEnabled()) { + const expires = new Date(Date.now() + USER_ID_COOKIE_EXP).toString(); + + this._cookieStorage.setCookie(USER_ID_KEY, userId, expires); + } + } + + checkIsAvailable() { + return this._cookieStorage.cookiesAreEnabled(); + } + + getUid() { + const id = this._getUserIdFromStorage() || this._generateUid(); + + this._setUid(id); + + return id; + } +} + +/** + * Yandex-specific generator for uid. Needs to be compatible with Yandex Metrica tag. + * @see https://github.com/yandex/metrica-tag/blob/main/src/utils/uid/uid.ts#L51 + */ +class YandexUidGenerator { + /** + * @param {number} min + * @param {number} max + */ + _getRandomInteger(min, max) { + return Math.floor(Math.random() * (max - min)) + min; + } + + _getCurrentSecTimestamp() { + return Math.round(Date.now() / 1000); + } + + generateUid() { + return [ + this._getCurrentSecTimestamp(), + this._getRandomInteger(1000000, 999999999), + ].join(""); + } +} + +submodule("userId", yandexIdSubmodule); From 9e2044fc2295fa3a9c2eb82f239ef253215f54d5 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Sun, 21 Apr 2024 17:36:28 +0300 Subject: [PATCH 2/5] refactor: improve yandex user id adapter codestyle --- modules/yandexIdSystem.js | 58 +++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/modules/yandexIdSystem.js b/modules/yandexIdSystem.js index 288c3e58f46..2357f995832 100644 --- a/modules/yandexIdSystem.js +++ b/modules/yandexIdSystem.js @@ -4,28 +4,22 @@ * @requires module:modules/userId */ -import { MODULE_TYPE_UID } from "../src/activities/modules.js"; -import { submodule } from "../src/hook.js"; -import { getStorageManager } from "../src/storageManager.js"; -import { logInfo } from "../src/utils.js"; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { logInfo } from '../src/utils.js'; -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData - */ - -const BIDDER_CODE = "yandex"; +const BIDDER_CODE = 'yandex'; +const YANDEX_ID_KEY = 'yandexId'; -const USER_ID_KEY = "_ym_uid"; -const USER_ID_COOKIE_EXP = 31536000000; // 365 days +const USER_ID_KEY = '_ym_uid'; +const USER_ID_COOKIE_EXP_MS = 31536000000; // 365 days -export const cookieStorage = getStorageManager({ +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER_CODE, }); -/** @type {Submodule} */ export const yandexIdSubmodule = { /** * Used to link submodule with config. @@ -37,12 +31,12 @@ export const yandexIdSubmodule = { * @param {string} value */ decode(value) { - logInfo("decoded value yandexId", value); + logInfo('decoded value yandexId', value); - return { yandexId: value }; + return { [YANDEX_ID_KEY]: value }; }, getId() { - const yandexUidStorage = new YandexUidStorage(cookieStorage); + const yandexUidStorage = new YandexUidStorage(storage); if (!yandexUidStorage.checkIsAvailable()) { return; @@ -53,8 +47,8 @@ export const yandexIdSubmodule = { }; }, eids: { - yandexId: { - source: "yandex", + [YANDEX_ID_KEY]: { + source: BIDDER_CODE, atype: 1, }, }, @@ -80,7 +74,7 @@ class YandexUidStorage { _setUid(userId) { if (this._cookieStorage.cookiesAreEnabled()) { - const expires = new Date(Date.now() + USER_ID_COOKIE_EXP).toString(); + const expires = new Date(Date.now() + USER_ID_COOKIE_EXP_MS).toString(); this._cookieStorage.setCookie(USER_ID_KEY, userId, expires); } @@ -109,7 +103,9 @@ class YandexUidGenerator { * @param {number} max */ _getRandomInteger(min, max) { - return Math.floor(Math.random() * (max - min)) + min; + const generateRandom = this._getRandomGenerator(); + + return Math.floor(generateRandom() * (max - min)) + min; } _getCurrentSecTimestamp() { @@ -120,8 +116,22 @@ class YandexUidGenerator { return [ this._getCurrentSecTimestamp(), this._getRandomInteger(1000000, 999999999), - ].join(""); + ].join(''); + } + + _getRandomGenerator() { + if (crypto) { + return () => { + const buffer = new Uint32Array(1); + crypto.getRandomValues(buffer); + + return buffer[0] / 0xffffffff; + }; + } + + // Polyfill for environments that don't support Crypto API + return () => Math.random(); } } -submodule("userId", yandexIdSubmodule); +submodule('userId', yandexIdSubmodule); From 5f401bc5ebfebade1121ef8ae4629c6c3927cf4b Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Sun, 21 Apr 2024 17:36:52 +0300 Subject: [PATCH 3/5] tests: add unit tests for yandex user id module --- test/spec/modules/yandexIdSystem_spec.js | 155 +++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 test/spec/modules/yandexIdSystem_spec.js diff --git a/test/spec/modules/yandexIdSystem_spec.js b/test/spec/modules/yandexIdSystem_spec.js new file mode 100644 index 00000000000..bd798f3a3f2 --- /dev/null +++ b/test/spec/modules/yandexIdSystem_spec.js @@ -0,0 +1,155 @@ +import { yandexIdSubmodule, storage } from '../../../modules/yandexIdSystem.js'; + +const MIN_METRICA_ID_LEN = 17; + +/** + * @typedef {import('sinon').SinonStub} SinonStub + * @typedef {import('sinon').SinonSandbox} SinonSandbox + */ + +describe('YandexId module', () => { + /** @type {SinonSandbox} */ + let sandbox; + /** @type {SinonStub} */ + let setCookieStub; + /** @type {SinonStub} */ + let getCookieStub; + /** @type {SinonStub} */ + let getLocalStorageStub; + /** @type {SinonStub} */ + let setLocalStorageStub; + /** @type {SinonStub} */ + let cookiesAreEnabledStub; + /** @type {SinonStub} */ + let getCryptoRandomValuesStub; + /** @type {SinonStub} */ + let randomStub; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + getCookieStub = sandbox.stub(storage, 'getCookie'); + setCookieStub = sandbox.stub(storage, 'setCookie'); + getLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + setLocalStorageStub = sandbox.stub(storage, 'setDataInLocalStorage'); + cookiesAreEnabledStub = sandbox.stub(storage, 'cookiesAreEnabled'); + cookiesAreEnabledStub.returns(true); + + getCryptoRandomValuesStub = sandbox + .stub(window.crypto, 'getRandomValues') + .callsFake((bufferView) => { + bufferView[0] = 10000; + }); + randomStub = sandbox.stub(window.Math, 'random').returns(0.555); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getId()', () => { + describe('user id format', () => { + it('matches Yandex Metrica format', () => { + const generatedId = yandexIdSubmodule.getId().id; + expect(isNaN(generatedId)).to.be.false; + expect(generatedId).to.have.length.greaterThanOrEqual( + MIN_METRICA_ID_LEN + ); + }); + }); + + describe('when user id is set', () => { + const dummyId = Array(MIN_METRICA_ID_LEN).fill(1).join(''); + + beforeEach(() => { + getCookieStub.returns(dummyId); + }); + + it('returns id', () => { + const { id } = yandexIdSubmodule.getId(); + + expect(id).to.equal(dummyId); + }); + + it('does not change existing id', () => { + yandexIdSubmodule.getId(); + + expect(setCookieStub.lastCall.args[1]).to.equal(dummyId); + }); + }); + + describe('when user id is not set', () => { + beforeEach(() => { + getCookieStub.returns(undefined); + }); + + it('returns id', () => { + const id = yandexIdSubmodule.getId(); + + expect(id).not.to.be.undefined; + }); + + it('sets id', () => { + yandexIdSubmodule.getId() + + expect(setCookieStub.calledOnce).to.be.true; + }); + }); + + describe('storage interaction', () => { + describe('when cookie storage is disabled', () => { + it('should not use storage api', () => { + cookiesAreEnabledStub.returns(false); + + yandexIdSubmodule.getId(); + + expect(cookiesAreEnabledStub.called).to.be.true; + expect(getCookieStub.called).to.be.false; + expect(setCookieStub.called).to.be.false; + expect(getLocalStorageStub.called).to.be.false; + expect(setLocalStorageStub.called).to.be.false; + }); + }); + + it('should use cookie', () => { + yandexIdSubmodule.getId(); + + expect(cookiesAreEnabledStub.called).to.be.true; + expect(getCookieStub.calledOnce).to.be.true; + expect(setCookieStub.calledOnce).to.be.true; + }); + + it('should not use localStorage', () => { + yandexIdSubmodule.getId(); + + expect(getLocalStorageStub.called).to.be.false; + expect(setLocalStorageStub.called).to.be.false; + }); + }); + + describe('crypto', () => { + it('uses Math.random when crypto is not available', () => { + sandbox.stub(window, 'crypto').value(undefined); + + yandexIdSubmodule.getId(); + + expect(randomStub.calledOnce).to.be.true; + expect(getCryptoRandomValuesStub.called).to.be.false; + }); + + it('uses crypto when it is available', () => { + yandexIdSubmodule.getId(); + + expect(randomStub.called).to.be.false; + expect(getCryptoRandomValuesStub.calledOnce).to.be.true; + }); + }); + }); + + describe('decode()', () => { + it('should not transform value', () => { + const value = 'test value'; + + expect(yandexIdSubmodule.decode(value).yandexId).to.equal(value); + }); + }); +}); From f6ed5f7e8ecfca4d37d9939982c5077743f2ef98 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Mon, 6 May 2024 17:28:03 +0300 Subject: [PATCH 4/5] fix: adjust eid key --- modules/yandexIdSystem.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/yandexIdSystem.js b/modules/yandexIdSystem.js index 2357f995832..181aeeeef56 100644 --- a/modules/yandexIdSystem.js +++ b/modules/yandexIdSystem.js @@ -10,6 +10,7 @@ import { getStorageManager } from '../src/storageManager.js'; import { logInfo } from '../src/utils.js'; const BIDDER_CODE = 'yandex'; +const BIDDER_EID_KEY = 'yandex.com'; const YANDEX_ID_KEY = 'yandexId'; const USER_ID_KEY = '_ym_uid'; @@ -48,7 +49,7 @@ export const yandexIdSubmodule = { }, eids: { [YANDEX_ID_KEY]: { - source: BIDDER_CODE, + source: BIDDER_EID_KEY, atype: 1, }, }, From 902ccd4809e125cd5fb9b6a1793705b72dd927ce Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Mon, 10 Jun 2024 20:15:20 +0300 Subject: [PATCH 5/5] refactor: remove explicit calls to cookie storage --- modules/yandexIdSystem.js | 85 ++++++------ test/spec/modules/yandexIdSystem_spec.js | 170 ++++++++++------------- 2 files changed, 122 insertions(+), 133 deletions(-) diff --git a/modules/yandexIdSystem.js b/modules/yandexIdSystem.js index 181aeeeef56..f24e33a8c44 100644 --- a/modules/yandexIdSystem.js +++ b/modules/yandexIdSystem.js @@ -4,21 +4,26 @@ * @requires module:modules/userId */ +// @ts-check + import { MODULE_TYPE_UID } from '../src/activities/modules.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; -import { logInfo } from '../src/utils.js'; +import { logError, logInfo } from '../src/utils.js'; -const BIDDER_CODE = 'yandex'; +// .com suffix is just a convention for naming the bidder eids +// See https://github.com/prebid/Prebid.js/pull/11196#discussion_r1591165139 const BIDDER_EID_KEY = 'yandex.com'; const YANDEX_ID_KEY = 'yandexId'; +export const BIDDER_CODE = 'yandex'; +export const YANDEX_USER_ID_KEY = '_ym_uid'; +export const YANDEX_COOKIE_STORAGE_TYPE = 'cookie'; +export const YANDEX_MIN_EXPIRE_DAYS = 30; -const USER_ID_KEY = '_ym_uid'; -const USER_ID_COOKIE_EXP_MS = 31536000000; // 365 days - -export const storage = getStorageManager({ +export const PREBID_STORAGE = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER_CODE, + bidderCode: undefined }); export const yandexIdSubmodule = { @@ -36,15 +41,24 @@ export const yandexIdSubmodule = { return { [YANDEX_ID_KEY]: value }; }, - getId() { - const yandexUidStorage = new YandexUidStorage(storage); - - if (!yandexUidStorage.checkIsAvailable()) { + /** + * @param {import('./userId/index.js').SubmoduleConfig} submoduleConfig + * @param {unknown} [_consentData] + * @param {string} [storedId] Id that was saved by the core previously. + */ + getId(submoduleConfig, _consentData, storedId) { + if (checkConfigHasErrorsAndReport(submoduleConfig)) { return; } + if (storedId) { + return { + id: storedId + }; + } + return { - id: yandexUidStorage.getUid(), + id: new YandexUidGenerator().generateUid(), }; }, eids: { @@ -55,43 +69,36 @@ export const yandexIdSubmodule = { }, }; -class YandexUidStorage { - /** - * @param {typeof cookieStorage} cookieStorage - */ - constructor(cookieStorage) { - this._cookieStorage = cookieStorage; - } - - _generateUid() { - return new YandexUidGenerator().generateUid(); - } +/** + * @param {import('./userId/index.js').SubmoduleConfig} submoduleConfig + * @returns {boolean} `true` - when there are errors, `false` - otherwise. + */ +function checkConfigHasErrorsAndReport(submoduleConfig) { + let error = false; - _getUserIdFromStorage() { - const id = this._cookieStorage.getCookie(USER_ID_KEY); + const READABLE_MODULE_NAME = 'Yandex ID module'; - return id; + if (submoduleConfig.storage == null) { + logError(`Misconfigured ${READABLE_MODULE_NAME}. "storage" is required.`) + return true; } - _setUid(userId) { - if (this._cookieStorage.cookiesAreEnabled()) { - const expires = new Date(Date.now() + USER_ID_COOKIE_EXP_MS).toString(); - - this._cookieStorage.setCookie(USER_ID_KEY, userId, expires); - } + if (submoduleConfig.storage?.name !== YANDEX_USER_ID_KEY) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.name" is required to be "${YANDEX_USER_ID_KEY}"`); + error = true; } - checkIsAvailable() { - return this._cookieStorage.cookiesAreEnabled(); + if (submoduleConfig.storage?.type !== YANDEX_COOKIE_STORAGE_TYPE) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.type" is required to be "${YANDEX_COOKIE_STORAGE_TYPE}"`); + error = true; } - getUid() { - const id = this._getUserIdFromStorage() || this._generateUid(); - - this._setUid(id); - - return id; + if ((submoduleConfig.storage?.expires ?? 0) < YANDEX_MIN_EXPIRE_DAYS) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.expires" is required to be not less than "${YANDEX_MIN_EXPIRE_DAYS}"`); + error = true; } + + return error; } /** diff --git a/test/spec/modules/yandexIdSystem_spec.js b/test/spec/modules/yandexIdSystem_spec.js index bd798f3a3f2..d5f614dafb9 100644 --- a/test/spec/modules/yandexIdSystem_spec.js +++ b/test/spec/modules/yandexIdSystem_spec.js @@ -1,43 +1,77 @@ -import { yandexIdSubmodule, storage } from '../../../modules/yandexIdSystem.js'; +// @ts-check -const MIN_METRICA_ID_LEN = 17; +import { yandexIdSubmodule, PREBID_STORAGE, BIDDER_CODE, YANDEX_USER_ID_KEY, YANDEX_COOKIE_STORAGE_TYPE, YANDEX_MIN_EXPIRE_DAYS } from '../../../modules/yandexIdSystem.js'; +import {createSandbox} from 'sinon' +import * as utils from '../../../src/utils.js'; /** * @typedef {import('sinon').SinonStub} SinonStub + * @typedef {import('sinon').SinonSpy} SinonSpy * @typedef {import('sinon').SinonSandbox} SinonSandbox */ +const MIN_METRICA_ID_LEN = 17; + +/** @satisfies {import('../../../modules/userId/index.js').SubmoduleConfig} */ +const CORRECT_SUBMODULE_CONFIG = { + name: BIDDER_CODE, + storage: { + expires: YANDEX_MIN_EXPIRE_DAYS, + name: YANDEX_USER_ID_KEY, + type: YANDEX_COOKIE_STORAGE_TYPE, + refreshInSeconds: undefined, + }, + params: undefined, + value: undefined, +}; + +/** @type {import('../../../modules/userId/index.js').SubmoduleConfig[]} */ +const INCORRECT_SUBMODULE_CONFIGS = [ + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + expires: 0, + } + }, + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + type: 'html5' + } + }, + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + name: 'custom_key' + } + }, +]; + describe('YandexId module', () => { /** @type {SinonSandbox} */ let sandbox; /** @type {SinonStub} */ - let setCookieStub; - /** @type {SinonStub} */ - let getCookieStub; - /** @type {SinonStub} */ - let getLocalStorageStub; - /** @type {SinonStub} */ - let setLocalStorageStub; - /** @type {SinonStub} */ - let cookiesAreEnabledStub; - /** @type {SinonStub} */ let getCryptoRandomValuesStub; /** @type {SinonStub} */ let randomStub; + /** @type {SinonSpy} */ + let logErrorSpy; beforeEach(() => { - sandbox = sinon.sandbox.create(); - getCookieStub = sandbox.stub(storage, 'getCookie'); - setCookieStub = sandbox.stub(storage, 'setCookie'); - getLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); - setLocalStorageStub = sandbox.stub(storage, 'setDataInLocalStorage'); - cookiesAreEnabledStub = sandbox.stub(storage, 'cookiesAreEnabled'); - cookiesAreEnabledStub.returns(true); + sandbox = createSandbox(); + logErrorSpy = sandbox.spy(utils, 'logError'); getCryptoRandomValuesStub = sandbox .stub(window.crypto, 'getRandomValues') .callsFake((bufferView) => { - bufferView[0] = 10000; + if (bufferView != null) { + bufferView[0] = 10000; + } + + return null; }); randomStub = sandbox.stub(window.Math, 'random').returns(0.555); }); @@ -47,97 +81,45 @@ describe('YandexId module', () => { }); describe('getId()', () => { - describe('user id format', () => { - it('matches Yandex Metrica format', () => { - const generatedId = yandexIdSubmodule.getId().id; - expect(isNaN(generatedId)).to.be.false; - expect(generatedId).to.have.length.greaterThanOrEqual( - MIN_METRICA_ID_LEN - ); - }); - }); - - describe('when user id is set', () => { - const dummyId = Array(MIN_METRICA_ID_LEN).fill(1).join(''); - - beforeEach(() => { - getCookieStub.returns(dummyId); - }); - - it('returns id', () => { - const { id } = yandexIdSubmodule.getId(); - - expect(id).to.equal(dummyId); - }); + it('user id matches Yandex Metrica format', () => { + const generatedId = yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG)?.id; - it('does not change existing id', () => { - yandexIdSubmodule.getId(); - - expect(setCookieStub.lastCall.args[1]).to.equal(dummyId); - }); - }); - - describe('when user id is not set', () => { - beforeEach(() => { - getCookieStub.returns(undefined); - }); - - it('returns id', () => { - const id = yandexIdSubmodule.getId(); - - expect(id).not.to.be.undefined; - }); - - it('sets id', () => { - yandexIdSubmodule.getId() - - expect(setCookieStub.calledOnce).to.be.true; - }); + expect(isNaN(Number(generatedId))).to.be.false; + expect(generatedId).to.have.length.greaterThanOrEqual( + MIN_METRICA_ID_LEN + ); }); - describe('storage interaction', () => { - describe('when cookie storage is disabled', () => { - it('should not use storage api', () => { - cookiesAreEnabledStub.returns(false); + it('uses stored id', () => { + const storedId = '11111111111111111'; + const generatedId = yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG, undefined, storedId)?.id; - yandexIdSubmodule.getId(); + expect(generatedId).to.be.equal(storedId); + }) - expect(cookiesAreEnabledStub.called).to.be.true; - expect(getCookieStub.called).to.be.false; - expect(setCookieStub.called).to.be.false; - expect(getLocalStorageStub.called).to.be.false; - expect(setLocalStorageStub.called).to.be.false; - }); - }); + describe('config validation', () => { + INCORRECT_SUBMODULE_CONFIGS.forEach((config, i) => { + it(`invalid config #${i} fails`, () => { + const generatedId = yandexIdSubmodule.getId(config)?.id; - it('should use cookie', () => { - yandexIdSubmodule.getId(); - - expect(cookiesAreEnabledStub.called).to.be.true; - expect(getCookieStub.calledOnce).to.be.true; - expect(setCookieStub.calledOnce).to.be.true; - }); - - it('should not use localStorage', () => { - yandexIdSubmodule.getId(); - - expect(getLocalStorageStub.called).to.be.false; - expect(setLocalStorageStub.called).to.be.false; - }); - }); + expect(generatedId).to.be.undefined; + expect(logErrorSpy.called).to.be.true; + }) + }) + }) describe('crypto', () => { it('uses Math.random when crypto is not available', () => { sandbox.stub(window, 'crypto').value(undefined); - yandexIdSubmodule.getId(); + yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG); expect(randomStub.calledOnce).to.be.true; expect(getCryptoRandomValuesStub.called).to.be.false; }); it('uses crypto when it is available', () => { - yandexIdSubmodule.getId(); + yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG); expect(randomStub.called).to.be.false; expect(getCryptoRandomValuesStub.calledOnce).to.be.true;