diff --git a/src/mockBatchCreator.ts b/src/mockBatchCreator.ts index 36116d231..63823bcfd 100644 --- a/src/mockBatchCreator.ts +++ b/src/mockBatchCreator.ts @@ -5,7 +5,7 @@ import { SDKEvent, BaseEvent, MParticleWebSDK } from './sdkRuntimeModels'; import { convertEvents } from './sdkToEventsApiConverter'; import * as EventsApi from '@mparticle/event-models'; -const mockFunction = function () { +const mockFunction = function() { return null; }; export default class _BatchValidator { @@ -13,12 +13,12 @@ export default class _BatchValidator { return { // Certain Helper, Store, and Identity properties need to be mocked to be used in the `returnBatch` method _Helpers: { - sanitizeAttributes: - window.mParticle.getInstance()._Helpers.sanitizeAttributes, - generateHash: function () { + sanitizeAttributes: window.mParticle.getInstance()._Helpers + .sanitizeAttributes, + generateHash: function() { return 'mockHash'; }, - generateUniqueId: function () { + generateUniqueId: function() { return 'mockId'; }, extend: window.mParticle.getInstance()._Helpers.extend, @@ -39,6 +39,7 @@ export default class _BatchValidator { _ServerModel: null, _SessionManager: null, _Store: { + mpid: 'test-mpid', sessionId: 'mockSessionId', devToken: 'test_dev_token', isFirstRun: true, diff --git a/src/persistence.interfaces.ts b/src/persistence.interfaces.ts new file mode 100644 index 000000000..338a83ba3 --- /dev/null +++ b/src/persistence.interfaces.ts @@ -0,0 +1,103 @@ +import { Context } from '@mparticle/event-models'; +import { + AllUserAttributes, + ConsentState, + IdentityApiData, + MPID, + Product, + UserIdentities, +} from '@mparticle/web-sdk'; +import { ForwardingStatsData } from './apiClient'; +import { + IntegrationAttributes, + ServerSettings, + SessionAttributes, +} from './store'; +import { Dictionary } from './utils'; + +export type CookieSyncDate = Dictionary; +export type UploadsTable = Dictionary; +export interface iForwardingStatsBatches { + uploadsTable: UploadsTable; + forwardingStatsEventQueue: ForwardingStatsData[]; +} + +export interface IGlobalStoreV2DTO { + sid: string; // Session ID + ie: boolean; // Is Enabled + sa: SessionAttributes; + ss: ServerSettings; + dt: string; // Dev Token + av: string; // App Version + cgid: string; // Client Generated ID + das: string; // Device ID/ Device Application String + ia: IntegrationAttributes; + c: Context; + csm: MPID[]; // Current Session MPIDs + les: number; // Last Event Sent Timestamp + ssd: number; // Session Start Date +} + +export interface IPersistenceV2DTO { + cu: MPID; // Current User MPID + gs: IGlobalStoreV2DTO; + l: false; // IsLoggedIn +} + +export interface IPersistence { + useLocalStorage(): boolean; + initializeStorage(): void; + update(): void; + storeProductsInMemory(products: Product[], mpid: MPID): void; + storeDataInMemory(obj: IPersistenceV2DTO, currentMPID: MPID): void; + determineLocalStorageAvailability(storage: Storage): boolean; + getUserProductsFromLS(mpid: MPID): Product[]; + getAllUserProductsFromLS(): Product[]; + setLocalStorage(): void; + getLocalStorage(): IPersistenceV2DTO | null; + expireCookies(cookieName: string): void; + getCookie(): IPersistenceV2DTO | null; + setCookie(): void; + reduceAndEncodePersistence( + persistence: IPersistenceV2DTO, + expires: string, + domain: string, + maxCookieSize: number + ): void; + findPrevCookiesBasedOnUI(identityApiData: IdentityApiData): void; + encodePersistence(persistance: IPersistenceV2DTO): string; + decodePersistence(persistance: IPersistenceV2DTO): string; + replaceCommasWithPipes(string: string): string; + replacePipesWithCommas(string: string): string; + replaceApostrophesWithQuotes(string: string): string; + replaceQuotesWithApostrophes(string: string): string; + createCookieString(string: string): string; + revertCookieString(string: string): string; + getCookieDomain(): string; + getDomain(doc: string, locationHostname: string): string; + getUserIdentities(mpid: MPID): UserIdentities; + getAllUserAttributes(mpid: MPID): AllUserAttributes; + getCartProducts(mpid: MPID): Product[]; + setCartProducts(allProducts: Product[]): void; + saveUserIdentitiesToPersistence( + mpid: MPID, + userIdentities: UserIdentities + ): void; + saveUserAttributesToPersistence( + mpid: MPID, + userIdentities: UserIdentities + ): void; + saveUserCookieSyncDatesToPersistence(mpid: MPID, csd: CookieSyncDate): void; + saveUserConsentStateToCookies(mpid, consentState: ConsentState): void; + savePersistence(persistance: IPersistenceV2DTO): void; + getPersistence(): IPersistenceV2DTO; + getConsentState(mpid: MPID): ConsentState | null; + getFirstSeenTime(mpid: MPID): string | null; + setFirstSeenTime(mpid: MPID, time: number): void; + getLastSeenTime(mpid: MPID): number | null; + setLastSeenTime(mpid: MPID, time: number): void; + getDeviceId(): string; + setDeviceId(guid: string): void; + resetPersistence(): void; + forwardingStatsBatches: iForwardingStatsBatches; +} diff --git a/src/persistence.js b/src/persistence.js index 53e3623e1..de9dfe42b 100644 --- a/src/persistence.js +++ b/src/persistence.js @@ -12,6 +12,7 @@ export default function _Persistence(mpInstance) { this.useLocalStorage = function() { return ( !mpInstance._Store.SDKConfig.useCookieStorage && + // FIXME: Should return boolean but is returning an object mpInstance._Store.isLocalStorageAvailable ); }; @@ -139,6 +140,7 @@ export default function _Persistence(mpInstance) { }; this.storeProductsInMemory = function(products, mpid) { + // TODO: Is it necessary to do a try/catch? When would it throw? if (products) { try { mpInstance._Store.cartProducts = @@ -153,9 +155,14 @@ export default function _Persistence(mpInstance) { } }; + // Stores the state of the Store after being read from Persistence + // If this is a first visit, does a basic assignment of client and + // device IDs this.storeDataInMemory = function(obj, currentMPID) { try { if (!obj) { + // Sets up the default "Store" if a cookie is not found + // (user's fresh visit) mpInstance.Logger.verbose( Messages.InformationMessages.CookieNotFound ); @@ -215,9 +222,11 @@ export default function _Persistence(mpInstance) { mpInstance._Store.sessionStartDate = new Date(); } + // TODO: What is the purpose of overwriting the obj? if (currentMPID) { obj = obj[currentMPID]; } else { + // FIXME: This could make obj === undefined obj = obj[obj.cu]; } } @@ -228,7 +237,6 @@ export default function _Persistence(mpInstance) { this.determineLocalStorageAvailability = function(storage) { var result; - if (window.mParticle && window.mParticle._forceNoLocalStorage) { storage = undefined; } @@ -237,6 +245,7 @@ export default function _Persistence(mpInstance) { storage.setItem('mparticle', 'test'); result = storage.getItem('mparticle') === 'test'; storage.removeItem('mparticle'); + // FIXME: This should return a boolean return result && storage; } catch (e) { return false; @@ -703,6 +712,8 @@ export default function _Persistence(mpInstance) { } }; + // TODO: Describe expected functionality and give examples of a persistence object + // FIXME: this should not mutate persistence this.encodePersistence = function(persistence) { persistence = JSON.parse(persistence); for (var key in persistence.gs) { @@ -763,6 +774,8 @@ export default function _Persistence(mpInstance) { return self.createCookieString(JSON.stringify(persistence)); }; + // TODO: Describe expected functionality and give examples of a persistence object + // FIXME: this should not mutate persistence this.decodePersistence = function(persistence) { try { if (persistence) { @@ -817,18 +830,22 @@ export default function _Persistence(mpInstance) { } }; + // TODO: This should be a helper this.replaceCommasWithPipes = function(string) { return string.replace(/,/g, '|'); }; + // TODO: This should be a helper this.replacePipesWithCommas = function(string) { return string.replace(/\|/g, ','); }; + // TODO: This should be a helper this.replaceApostrophesWithQuotes = function(string) { return string.replace(/\'/g, '"'); }; + // TODO: This should be a helper this.replaceQuotesWithApostrophes = function(string) { return string.replace(/\"/g, "'"); }; diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index 38b05c0e9..7ba5cc0c0 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -126,6 +126,12 @@ export interface SDKProduct { } export interface MParticleWebSDK { + _forceNoLocalStorage?: boolean; + + // TODO: Why are tehse both in the Store SDK Config and in the MP SDK object? + maxCookieSize?: number; + useCookieStorage?: boolean; + addForwarder(mockForwarder: MPForwarder): void; Identity: SDKIdentityApi; Logger: SDKLoggerApi; @@ -141,7 +147,7 @@ export interface MParticleWebSDK { _NativeSdkHelpers: any; // TODO: Set up API _Persistence: any; // TODO: Set up Persistence API _preInit: any; // TODO: Set up API - _resetForTests(MPConfig?: SDKInitConfig): void; + _resetForTests(MPConfig?: SDKInitConfig, keepPersistence?: boolean): void; init(apiKey: string, config: SDKInitConfig, instanceName?: string): void; getAppName(): string; getAppVersion(): string; diff --git a/src/store.ts b/src/store.ts index 61fb50d44..af5c3f892 100644 --- a/src/store.ts +++ b/src/store.ts @@ -101,6 +101,8 @@ function createSDKConfig(config: SDKInitConfig): SDKConfig { export type PixelConfiguration = Dictionary; export type MigrationData = Dictionary; export type ServerSettings = Dictionary; +export type SessionAttributes = Dictionary; +export type IntegrationAttributes = Dictionary>; type WrapperSDKTypes = 'flutter' | 'none'; interface WrapperSDKInfo { @@ -111,8 +113,10 @@ interface WrapperSDKInfo { // Temporary Interface until Store can be refactored as a class export interface IStore { + mpid?: MPID; isEnabled: boolean; sessionAttributes: Dictionary; + // sessionAttributes: SessionAttributes; currentSessionMPIDs: MPID[]; consentState: SDKConsentState | null; sessionId: string | null; @@ -141,6 +145,7 @@ export interface IStore { isLoggedIn: boolean; cookieSyncDates: Dictionary; integrationAttributes: Dictionary>; + // integrationAttributes: IntegrationAttributes; requireDelay: boolean; isLocalStorageAvailable: boolean | null; storageName: string | null; @@ -219,10 +224,9 @@ export default function Store( this.deviceId = config.deviceId; } if (config.hasOwnProperty('isDevelopmentMode')) { - this.SDKConfig.isDevelopmentMode = - mpInstance._Helpers.returnConvertedBoolean( - config.isDevelopmentMode - ); + this.SDKConfig.isDevelopmentMode = mpInstance._Helpers.returnConvertedBoolean( + config.isDevelopmentMode + ); } else { this.SDKConfig.isDevelopmentMode = false; } diff --git a/test/src/tests-persistence.js b/test/src/tests-persistence.ts similarity index 71% rename from test/src/tests-persistence.js rename to test/src/tests-persistence.ts index 2fffcf51d..6c8092042 100644 --- a/test/src/tests-persistence.js +++ b/test/src/tests-persistence.ts @@ -1,15 +1,639 @@ import Utils from './utils'; import sinon from 'sinon'; -import { urls, apiKey, testMPID, MPConfig,localStorageProductsV4, LocalStorageProductsV4WithWorkSpaceName, workspaceCookieName, v4LSKey } from './config'; +import { expect } from 'chai'; +import { + urls, + apiKey, + testMPID, + MPConfig, + localStorageProductsV4, + LocalStorageProductsV4WithWorkSpaceName, + workspaceCookieName, + v4LSKey, +} from './config'; + +import Polyfill from '../../src/polyfill'; +import { MParticleWebSDK } from '../../src/sdkRuntimeModels'; + +declare global { + interface Window { + mParticle: MParticleWebSDK; + fetchMock: any; + } +} /* eslint-disable quotes*/ -var findCookie = Utils.findCookie, - getLocalStorage = Utils.getLocalStorage, - getLocalStorageProducts = Utils.getLocalStorageProducts, - setCookie = Utils.setCookie, - setLocalStorage = Utils.setLocalStorage, - getEvent = Utils.getEvent, - mockServer; +const { + findCookie, + getLocalStorage, + getLocalStorageProducts, + setCookie, + setLocalStorage, + getEvent, +} = Utils; +let mockServer; + +var Base64 = Polyfill.Base64; + +const mParticle = window.mParticle; +const MockLogger = { + warning: () => {}, + verbose: () => {}, + error: () => {}, +}; + +describe.only('Persistence', function() { + describe('#useLocalStorage', function() { + it('returns true if Local Storage is available', function() { + mParticle._resetForTests(MPConfig); + + // FIXME: Test should check for boolean but function is + // returning an object + expect(mParticle.getInstance()._Persistence.useLocalStorage()).to.be + .ok; + }); + + it('returns false if Local Storage is not available', function() { + mParticle._resetForTests(MPConfig); + mParticle.getInstance()._Store.isLocalStorageAvailable = false; + mParticle + .getInstance() + ._Persistence.useLocalStorage() + .should.eql(false); + }); + + it('returns false if cookie storage is preferred', function() { + mParticle._resetForTests(MPConfig); + mParticle.getInstance()._Store.SDKConfig.useCookieStorage = true; + mParticle + .getInstance() + ._Persistence.useLocalStorage() + .should.eql(false); + }); + }); + + describe('#initializeStorage', function() {}); + + describe('#update', function() { + it('sets local storage', () => { + var bond = sinon.spy( + mParticle.getInstance()._Persistence, + 'setLocalStorage' + ); + + mParticle._resetForTests(MPConfig); + mParticle.getInstance()._Persistence.update(); + + bond.called.should.eql(true); + }); + + it.skip('sets a cookie when useCookieStorage is set', () => { + var bond = sinon.spy( + mParticle.getInstance()._Persistence, + 'setCookie' + ); + + // var setCookieSpy = sinon.spy( + // mParticle.getInstance()._Persistence, + // 'setCookie' + // ); + + // var getCookieSpy = sinon.spy( + // mParticle.getInstance()._Persistence, + // 'getCookie' + // ); + + document.cookie = + "mprtcl-v4_4DD884CD={'gs':{'ie':1|'dt':'9aa8aa0514a802498e8e941d53e2a1d9'|'cgid':'e32ee0cf-83c7-4398-bd50-462a943d16b6'|'das':'99f5ad4d-ed1b-4044-89b6-977d7fac40c5'|'ia':'eyIxMjQiOnsibWlkIjoiNDk3NTQ1MzkyNzgyOTUxNTkxOTA4OTgwNzQ5NzYyOTQwNDQyNzAifX0='|'av':'1.0.0'}|'l':1|'9128337746531357694':{'fst':1663610956871|'ui':'eyIxIjoiMTIzNDU2IiwiNyI6ImVtYWlsQGV4YW1wbGUuY29tIn0='}|'cu':'9128337746531357694'}"; + + mParticle.getInstance().Logger = MockLogger; + + mParticle._resetForTests(MPConfig); + + mParticle.getInstance()._Persistence.update(); + + bond.called.should.eql(false); + + mParticle._resetForTests( + Object.assign(MPConfig, { + useCookieStorage: true, + workspaceToken: 'cookie-test', + }) + ); + + mParticle.getInstance()._Persistence.update(); + + bond.called.should.eql(true); + }); + }); + + describe('#storeProductsInMemory', function() { + it('stores an array of products in memory', () => { + var products = { + 'test-mpid': { + cp: 'foo', + }, + }; + mParticle._resetForTests(MPConfig); + mParticle + .getInstance() + ._Persistence.storeProductsInMemory(products, 'test-mpid'); + + mParticle.getInstance()._Store.cartProducts.should.equal('foo'); + }); + + it('stores an empty array if products are not groupd by mpid', () => { + var products = {}; + mParticle._resetForTests(MPConfig); + mParticle + .getInstance() + ._Persistence.storeProductsInMemory(products, 'test-mpid'); + expect(mParticle.getInstance()._Store.cartProducts).to.eql([]); + }); + }); + + describe('#storeDataInMemory', function() { + it('updates Store with unique client and device IDs if persistence object is empty', () => { + // var bond = sinon.spy(mParticle.getInstance().Logger, 'verbose'); + var bond = sinon + .stub(mParticle.getInstance()._Helpers, 'generateUniqueId') + .callsFake(function() { + return '12345'; + }); + + mParticle._resetForTests(MPConfig); + + mParticle.getInstance().Logger = MockLogger; + + mParticle + .getInstance() + ._Persistence.storeDataInMemory(null, 'test_mpid'); + + expect( + mParticle.getInstance()._Store.clientId, + '_Store.clientId' + ).to.equal('12345'); + + expect( + mParticle.getInstance()._Store.clientId, + '_Store.deviceId' + ).to.equal('12345'); + }); + + it('stores the persistence object in the Store', () => { + mParticle._resetForTests(MPConfig); + var persistenceObject = { + cu: 'my_cu', + gs: { + sid: 'my_session_id', + ie: true, + sa: { + foo: 'bar', + }, + ss: { + fizz: 'buzz', + }, + dt: 'my_dev_token', + av: '1.2.3.4', + cgid: 'my_client_id', + das: 'my_device_id', + ia: { bizz: 'bazz' }, + c: { + data_plan: { + plan_id: 'my data plan', + plan_version: 2, + }, + }, + csm: ['123456', '555', 'my_test_mpid'], + les: 1661548842, + ssd: 1661552442, + }, + l: false, + }; + mParticle + .getInstance() + ._Persistence.storeDataInMemory(persistenceObject); + + expect(mParticle.getInstance()._Store.mpid, '_Store.mpid').to.equal( + 'my_cu' + ); + expect( + mParticle.getInstance()._Store.sessionId, + '_Store.sessionId' + ).to.equal('my_session_id'); + expect( + mParticle.getInstance()._Store.isEnabled, + '_Store.isEnabled' + ).to.equal(true); + expect( + mParticle.getInstance()._Store.sessionAttributes, + '_Store.sessionAttributes' + ).to.eql({ + foo: 'bar', + }); + expect( + mParticle.getInstance()._Store.serverSettings, + '_Store.serverSettings' + ).to.eql({ + fizz: 'buzz', + }); + expect( + mParticle.getInstance()._Store.devToken, + '._Store.devToken' + ).to.equal('my_dev_token'); + expect( + mParticle.getInstance()._Store.deviceId, + '_Store.deviceId.' + ).to.equal('my_device_id'); + expect( + mParticle.getInstance()._Store.integrationAttributes, + '_Store.integrationAttributes' + ).to.eql({ + bizz: 'bazz', + }); + expect( + mParticle.getInstance()._Store.context, + '_Store.context' + ).to.eql({ + data_plan: { + plan_id: 'my data plan', + plan_version: 2, + }, + }); + expect( + mParticle.getInstance()._Store.currentSessionMPIDs, + '_Store.currentSessionMPIDs' + ).to.eql(['123456', '555', 'my_test_mpid']); + expect( + mParticle.getInstance()._Store.isLoggedIn, + '_Store.isLoggedIn' + ).to.equal(false); + expect( + mParticle.getInstance()._Store.dateLastEventSent, + '_Store.dateLastEventSent' + ).to.eql(new Date(1661548842)); + expect( + mParticle.getInstance()._Store.SDKConfig.appVersion, + '_Store.SDKConfig.appVersion' + ).to.equal('1.2.3.4'); + }); + }); + + describe('#determineLocalStorageAvailability', function() { + it('returns true if Local Storage is available', function() { + mParticle._resetForTests(MPConfig); + + // TODO: test should really for a boolean but funciton + // currently returns an object if true + expect( + mParticle + .getInstance() + ._Persistence.determineLocalStorageAvailability( + window.localStorage + ) + ).to.be.ok; + }); + + it('returns false if Local Storage is not available', function() { + mParticle._resetForTests(MPConfig); + + // FIXME: this method should not take storage as a param + expect( + mParticle + .getInstance() + ._Persistence.determineLocalStorageAvailability(null) + ).to.equal(false); + }); + + it('returns false if Local Storage disabled via _forceNoLocalStorage', function() { + mParticle._resetForTests(MPConfig); + mParticle._forceNoLocalStorage = true; + + expect( + mParticle + .getInstance() + ._Persistence.determineLocalStorageAvailability( + window.localStorage + ) + ).to.equal(false); + }); + }); + + describe('#getUserProductsFromLS', function() { + it('returns an empty array if mpid is missing', () => { + mParticle._resetForTests(MPConfig); + expect( + mParticle.getInstance()._Persistence.getUserProductsFromLS('') + ).to.eql([]); + }); + + it('returns an empty array if no user products exist in local storage', () => { + const lsKey = 'test-getUserProductsFromLS'; + + mParticle._resetForTests(MPConfig); + mParticle.getInstance()._Store.prodStorageName = lsKey; + + window.localStorage.setItem(lsKey, JSON.stringify('')); + + expect( + mParticle + .getInstance() + ._Persistence.getUserProductsFromLS( + 'test-mpid-getUserProductsFromLS' + ) + ).to.eql([]); + }); + + it('returns an array user products if they exist in local storage', () => { + const lsKey = 'test-getUserProductsFromLS'; + const testMPID = 'test-mpid-getUserProductsFromLS'; + + mParticle._resetForTests(MPConfig); + mParticle.getInstance()._Store.prodStorageName = lsKey; + mParticle.getInstance()._Store.isLocalStorageAvailable = true; + + const expectedProducts = [ + { + Attributes: { + plannedAttr1: 'val1', + plannedAttr2: 'val2', + unplannedAttr1: 'val3', + allowedAttr1: 'val4', + allowedAttr2: 'val5', + }, + Name: 'iPhone', + Category: 'category', + CouponCode: 'coupon', + Position: 1, + Price: 999, + Quantity: 1, + Sku: 'iphoneSKU', + TotalAmount: 999, + Variant: '128', + }, + { + Attributes: { + plannedAttr1: 'val1', + plannedAttr2: 'val2', + unplannedAttr1: 'val3', + allowedAttr1: 'val4', + allowedAttr2: 'val5', + }, + Name: 'S10', + Category: 'category', + CouponCode: 'coupon', + Position: 2, + Price: 500, + Quantity: 1, + Sku: 'galaxySKU', + TotalAmount: 500, + Variant: '256', + }, + ]; + + const actualStorageItem = { + [testMPID]: { + cp: expectedProducts, + }, + }; + + window.localStorage.setItem( + lsKey, + Base64.encode(JSON.stringify(actualStorageItem)) + ); + + expect( + mParticle + .getInstance() + ._Persistence.getUserProductsFromLS(testMPID) + ).to.eql(expectedProducts); + }); + }); + + describe('#getAllUserProductsFromLS', function() {}); + describe('#setLocalStorage', function() {}); + + describe('#getLocalStorage', function() { + it('returns null if storage name is not set', function() { + mParticle._resetForTests(MPConfig); + + ( + mParticle.getInstance()._Persistence.getLocalStorage() === null + ).should.eql(true); + }); + + it('returns null if local storage data is empty', function() { + window.localStorage.setItem('foo-storage', '{}'); + + mParticle._resetForTests(MPConfig); + + mParticle.getInstance()._Store.isLocalStorageAvailable = true; + mParticle.getInstance()._Store.storageName = 'foo-storage'; + + ( + mParticle.getInstance()._Persistence.getLocalStorage() === null + ).should.eql(true); + }); + + it('returns a local storage object', function() { + window.localStorage.setItem( + 'foo-storage', + '{"foo": "bar", "mpid": "12345"}' + ); + + mParticle._resetForTests(MPConfig); + + mParticle.getInstance()._Store.isLocalStorageAvailable = true; + mParticle.getInstance()._Store.storageName = 'foo-storage'; + + mParticle + .getInstance() + ._Persistence.getLocalStorage() + .should.eql({ + foo: 'bar', + mpid: '12345', + }); + }); + }); + + describe('#expireCookies', function() {}); + + describe('#getCookie', function() { + it('returns a cookie', function() { + document.cookie = + "mprtcl-v4_defghi={'gs':{'ie':1|'dt':'test_key'|'cgid':'4bb52bdd-e021-4476-bf79-d1060ca2482b'|'das':'13d96730-9332-45ea-aebf-0e3cb10f5f09'|'csm':'WyJ0ZXN0TVBJRCJd'|'sid':'1A3B41A0-42F4-49A1-96D0-178A40A4DDFE'|'les':1664380692122|'ssd':1664380540712}|'l':0|'testMPID':{'fst':1664380540716|'ua':'eyJmb28iOiJiYXIiLCJmaXp6IjoiYmF6eiJ9'|'con':'eyJnZHByIjp7ImxvY2F0aW9uX2NvbGxlY3Rpb24iOnsiYyI6dHJ1ZSwidHMiOjE2NjQzODA2NzQ3MjYsImQiOiJsb2NhdGlvbl9jb2xsZWN0aW9uX2FncmVlbWVudF92NCIsImwiOiIxNyBDaGVycnkgVHJlZSBMYW5lIiwiaCI6IklERkE6YTVkOTM0bjAtMjMyZi00YWZjLTJlOWEtMzgzMmQ5NXpjNzAyIn19fQ=='}|'cu':'testMPID'}"; + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.storageName = 'mprtcl-v4_defghi'; + + expect(mParticle.getInstance()._Persistence.getCookie()).to.eql({ + // TODO: Use this to create a Persistence Object Interface + cu: 'testMPID', + gs: { + cgid: '4bb52bdd-e021-4476-bf79-d1060ca2482b', + csm: ['testMPID'], + das: '13d96730-9332-45ea-aebf-0e3cb10f5f09', + dt: 'test_key', + ie: true, + les: 1664380692122, + sid: '1A3B41A0-42F4-49A1-96D0-178A40A4DDFE', + ssd: 1664380540712, + }, + l: false, + testMPID: { + con: { + // TODO: Should have CCPA + gdpr: { + location_collection: { + c: true, + d: 'location_collection_agreement_v4', + h: 'IDFA:a5d934n0-232f-4afc-2e9a-3832d95zc702', + l: '17 Cherry Tree Lane', + ts: 1664380674726, + }, + }, + }, + fst: 1664380540716, + ua: { fizz: 'bazz', foo: 'bar' }, + }, + }); + }); + + it('returns a null if cookie is not available', function() { + document.cookie = 'mprtcl-v4_defghi='; + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.storageName = 'mprtcl-v4_defghi'; + + expect( + mParticle.getInstance()._Persistence.getCookie() === null + ).to.eql(true); + }); + }); + + describe('#setCookie', function() { + it('should set a cookie', () => {}); + }); + + describe('#reduceAndEncodePersistence', function() {}); + describe('#findPrevCookiesBasedOnUI', function() {}); + + // TODO: Use a full sized persistence object + describe('#encodePersistence', function() { + it('should encode a persistence object', function() { + var persistanceObject = + '{"gs": {"ie": "1", "sid": "This is an id"}, "mpid": "12345"}'; + var encodedPersistenceObject = `{\'gs\':{\'ie\':1|\'sid\':\'This is an id\'}|\'mpid\':\'12345\'}`; + mParticle + .getInstance() + ._Persistence.encodePersistence(persistanceObject) + .should.deepEqual(encodedPersistenceObject); + }); + }); + + // TODO: Use a full sized persistence object + describe('#decodePersistence', function() { + it('should decode a persistence object', function() { + var persistanceObject = `{\'gs\':{\'ie\':1|\'sid\':\'This is an id\'}|\'mpid\':\'12345\'}`; + var decodedPersistenceObject = + '{"gs":{"ie":true,"sid":"This is an id"},"mpid":"12345"}'; + mParticle + .getInstance() + ._Persistence.decodePersistence(persistanceObject) + .should.deepEqual(decodedPersistenceObject); + }); + }); + + describe('#replaceCommasWithPipes', function() { + it('replaces commas with pipes', function() { + mParticle + .getInstance() + ._Persistence.replaceCommasWithPipes('veni,vidi,vici') + .should.eql('veni|vidi|vici'); + }); + }); + + describe('#replacePipesWithCommas', function() { + it('replaces pipes with commas', function() { + mParticle + .getInstance() + ._Persistence.replacePipesWithCommas('veni|vidi|vici') + .should.eql('veni,vidi,vici'); + }); + }); + + describe('#replaceApostrophesWithQuotes', function() { + it('replaces apostrophies with quotes', function() { + mParticle + .getInstance() + ._Persistence.replaceApostrophesWithQuotes("'''") + .should.eql('"""'); + }); + }); + + describe('#replaceQuotesWithApostrophes', function() { + it('replaces quotes with apostrophies', function() { + mParticle + .getInstance() + ._Persistence.replaceQuotesWithApostrophes('"""""') + .should.eql("'''''"); + }); + }); + + describe('#createCookieString', function() { + it('should create a cookie string', function() { + mParticle + .getInstance() + ._Persistence.createCookieString( + '"ie":1,"ua":"eyJ0ZXN"0Ijoiwq7igJkifQ=="' + ) + .should.eql(`\'ie\':1|\'ua\':\'eyJ0ZXN\'0Ijoiwq7igJkifQ==\'`); + }); + }); + + describe('#revertCookieString', function() { + it('should revert a cookie string', function() { + mParticle + .getInstance() + ._Persistence.revertCookieString( + `\'ie\':1|\'ua\':\'eyJ0ZXN\'0Ijoiwq7igJkifQ==\'` + ) + .should.eql('"ie":1,"ua":"eyJ0ZXN"0Ijoiwq7igJkifQ=="'); + }); + }); + + describe('#getCookieDomain', function() {}); + describe('#getDomain', function() {}); + describe('#getUserIdentities', function() {}); + describe('#getAllUserAttributes', function() {}); + describe('#getCartProducts', function() {}); + describe('#setCartProducts', function() {}); + describe('#saveUserIdentitiesToPersistence', function() {}); + describe('#saveUserAttributesToPersistence', function() {}); + describe('#saveUserCookieSyncDatesToPersistence', function() {}); + describe('#saveUserConsentStateToCookies', function() {}); + describe('#savePersistence', function() {}); + describe('#getPersistence', function() {}); + describe('#getConsentState', function() {}); + describe('#getFirstSeenTime', function() {}); + describe('#setFirstSeenTime', function() {}); + describe('#getLastSeenTime', function() {}); + describe('#setLastSeenTime', function() {}); + + describe('#getDeviceId', function() { + it('returns a device ID', () => { + mParticle._resetForTests( + Object.assign(MPConfig, { deviceId: 'foo-id' }) + ); + mParticle + .getInstance() + ._Persistence.getDeviceId() + .should.equal('foo-id'); + }); + }); + + describe('#setDeviceId', function() {}); + describe('#resetPersistence', function() {}); +}); describe('migrations and persistence-related', function() { beforeEach(function() { @@ -19,8 +643,8 @@ describe('migrations and persistence-related', function() { mockServer.respondWith(urls.eventsV2, [ 200, {}, - JSON.stringify({ mpid: testMPID, Store: {}}) - ]) + JSON.stringify({ mpid: testMPID, Store: {} }), + ]); mockServer.respondWith(urls.identify, [ 200, {}, @@ -62,7 +686,7 @@ describe('migrations and persistence-related', function() { beforeInitCookieData[testMPID].ui.should.have.property('1', '123'); localStorageData[testMPID].ua.should.have.property('gender', 'male'); localStorageData[testMPID].ui.should.have.property('1', '123'); - Should(afterInitCookieData).not.be.ok(); + expect(afterInitCookieData).not.be.ok; done(); }); @@ -83,7 +707,7 @@ describe('migrations and persistence-related', function() { var localStorageData = localStorage.getItem('mprtcl-api'); var cookieData = findCookie(); - Should(localStorageData).not.be.ok(); + expect(localStorageData).not.be.ok; cookieData[testMPID].ua.should.have.property('gender', 'male'); cookieData[testMPID].ui.should.have.property( '1', @@ -250,7 +874,7 @@ describe('migrations and persistence-related', function() { cookieData[testMPID].should.not.have.property(prop); }); - Should(localStorageData).not.be.ok(); + expect(localStorageData).not.be.ok; done(); }); @@ -285,7 +909,7 @@ describe('migrations and persistence-related', function() { localStorageData[testMPID].should.not.have.property(prop); }); - Should(cookieData).not.be.ok(); + expect(cookieData).not.be.ok; done(); }); @@ -320,7 +944,7 @@ describe('migrations and persistence-related', function() { cookieData[testMPID].should.not.have.property(prop); }); - Should(localStorageData).not.be.ok(); + expect(localStorageData).not.be.ok; done(); }); @@ -354,7 +978,7 @@ describe('migrations and persistence-related', function() { localStorageData[testMPID].should.not.have.property(prop); }); - Should(cookieData).not.be.ok(); + expect(cookieData).not.be.ok; done(); }); @@ -448,13 +1072,13 @@ describe('migrations and persistence-related', function() { mParticle._resetForTests(MPConfig); mParticle.init(apiKey, window.mParticle.config); - + mockServer.respondWith(urls.login, [ 200, {}, JSON.stringify({ mpid: 'mpid1', is_logged_in: false }), ]); - + var user1 = { userIdentities: { customerid: 'customerid1' } }; mParticle.Identity.login(user1); @@ -472,9 +1096,7 @@ describe('migrations and persistence-related', function() { mParticle.Identity.logout(); - var user2Result = mParticle - .getInstance() - .Identity.getCurrentUser(); + var user2Result = mParticle.getInstance().Identity.getCurrentUser(); Object.keys( user2Result.getUserIdentities().userIdentities ).length.should.equal(0); @@ -579,11 +1201,10 @@ describe('migrations and persistence-related', function() { JSON.stringify({ mpid: 'mpid1', is_logged_in: false }), ]); - mockServer.respondWith('https://identity.mparticle.com/v1/mpid1/modify', [ - 200, - {}, - JSON.stringify({ mpid: 'mpid1', is_logged_in: false }), - ]); + mockServer.respondWith( + 'https://identity.mparticle.com/v1/mpid1/modify', + [200, {}, JSON.stringify({ mpid: 'mpid1', is_logged_in: false })] + ); mParticle.Identity.login(user1); @@ -736,7 +1357,7 @@ describe('migrations and persistence-related', function() { }; var expires = new Date( new Date().getTime() + 365 * 24 * 60 * 60 * 1000 - ).toGMTString(); + ).toString(); var cookiesWithExpiration = mParticle .getInstance() ._Persistence.reduceAndEncodePersistence( @@ -754,9 +1375,9 @@ describe('migrations and persistence-related', function() { .getInstance() ._Persistence.decodePersistence(cookiesWithoutExpiration) ); - Should(cookiesResult['mpid1']).not.be.ok(); - Should(cookiesResult['mpid2']).be.ok(); - Should(cookiesResult['mpid3']).be.ok(); + expect(cookiesResult['mpid1']).not.be.ok; + expect(cookiesResult['mpid2']).be.ok; + expect(cookiesResult['mpid3']).be.ok; cookiesResult.gs.csm.length.should.equal(3); cookiesResult.gs.csm[0].should.equal('mpid1'); cookiesResult.gs.csm[1].should.equal('mpid2'); @@ -840,11 +1461,11 @@ describe('migrations and persistence-related', function() { .Identity.getCurrentUser() .setUserAttribute('id', 'id1'); - mockServer.respondWith(urls.login, [ - 200, - {}, - JSON.stringify({ mpid: 'MPID1', is_logged_in: false }), - ]); + mockServer.respondWith(urls.login, [ + 200, + {}, + JSON.stringify({ mpid: 'MPID1', is_logged_in: false }), + ]); mParticle.Identity.login(); @@ -906,7 +1527,7 @@ describe('migrations and persistence-related', function() { cookieData = findCookie(); - Should(cookieData['testMPID']).not.be.ok(); + expect(cookieData['testMPID']).not.be.ok; cookieData['MPID1'].ua.should.have.property('id', 'id2'); cookieData['MPID1'].ua.should.have.property('gender', 'male'); cookieData['MPID1'].ua.should.have.property('age', 30); @@ -965,7 +1586,7 @@ describe('migrations and persistence-related', function() { var expires = new Date( new Date().getTime() + 365 * 24 * 60 * 60 * 1000 - ).toGMTString(); + ).toString(); var cookiesWithExpiration = mParticle .getInstance() @@ -985,9 +1606,9 @@ describe('migrations and persistence-related', function() { ._Persistence.decodePersistence(cookiesWithoutExpiration) ); - Should(cookiesResult['mpid1']).not.be.ok(); - Should(cookiesResult['mpid2']).not.be.ok(); - Should(cookiesResult['mpid3']).be.ok(); + expect(cookiesResult['mpid1']).not.be.ok; + expect(cookiesResult['mpid2']).not.be.ok; + expect(cookiesResult['mpid3']).be.ok; cookiesResult.gs.csm[0].should.equal('mpid3'); cookiesResult.should.have.property('mpid3'); mParticle.maxCookieSize = 3000; @@ -1023,11 +1644,11 @@ describe('migrations and persistence-related', function() { .Identity.getCurrentUser() .setUserAttribute('id', 'id1'); - mockServer.respondWith(urls.login, [ - 200, - {}, - JSON.stringify({ mpid: 'MPID1', is_logged_in: false }), - ]); + mockServer.respondWith(urls.login, [ + 200, + {}, + JSON.stringify({ mpid: 'MPID1', is_logged_in: false }), + ]); mParticle.Identity.login(); @@ -1089,7 +1710,7 @@ describe('migrations and persistence-related', function() { cookieData = findCookie(); - Should(cookieData['testMPID']).not.be.ok(); + expect(cookieData['testMPID']).not.be.ok; cookieData['MPID1'].ua.should.have.property('id', 'id2'); cookieData['MPID1'].ua.should.have.property('gender', 'male'); cookieData['MPID1'].ua.should.have.property('age', 30); @@ -1194,7 +1815,7 @@ describe('migrations and persistence-related', function() { mParticle.init(apiKey, window.mParticle.config); var cookieData = findCookie(); - Should(cookieData['testMPID']).not.be.ok(); + expect(cookieData['testMPID']).not.be.ok; cookieData['MPID1'].ua.should.have.property('id', 'id2'); cookieData['MPID2'].ua.should.have.property('id'); @@ -1451,7 +2072,7 @@ describe('migrations and persistence-related', function() { .getInstance() .Identity.getCurrentUser() .getConsentState(); - (consentState === null).should.be.ok(); + (consentState === null).should.be.ok; consentState = mParticle.Consent.createConsentState(); consentState.addGDPRConsentState( 'foo purpose', @@ -1467,7 +2088,7 @@ describe('migrations and persistence-related', function() { .getInstance() .Identity.getCurrentUser() .getConsentState(); - storedConsentState.should.be.ok(); + storedConsentState.should.be.ok; storedConsentState .getGDPRConsentState() .should.have.property('foo purpose'); @@ -1496,7 +2117,7 @@ describe('migrations and persistence-related', function() { .getInstance() .Identity.getCurrentUser() .getConsentState(); - (user1StoredConsentState === null).should.be.ok(); + (user1StoredConsentState === null).should.be.ok; var consentState = mParticle.Consent.createConsentState(); consentState.addGDPRConsentState( 'foo purpose', @@ -1523,7 +2144,7 @@ describe('migrations and persistence-related', function() { .getInstance() .Identity.getCurrentUser() .getConsentState(); - (user2StoredConsentState === null).should.be.ok(); + (user2StoredConsentState === null).should.be.ok; consentState.removeGDPRConsentState('foo purpose'); @@ -1634,7 +2255,7 @@ describe('migrations and persistence-related', function() { mParticle.init(apiKey, window.mParticle.config); var productsAfterInit = getLocalStorageProducts().testMPID; - Should(productsAfterInit.length).not.be.ok(); + expect(productsAfterInit.length).not.be.ok; done(); }); @@ -1773,7 +2394,9 @@ describe('migrations and persistence-related', function() { .getInstance() ._Persistence.getLastSeenTime('previous_set') .should.equal(10); - mParticle.getInstance()._Persistence.setLastSeenTime('previous_set', 20); + mParticle + .getInstance() + ._Persistence.setLastSeenTime('previous_set', 20); mParticle .getInstance() ._Persistence.getLastSeenTime('previous_set') @@ -1854,13 +2477,13 @@ describe('migrations and persistence-related', function() { mParticle.init(apiKey, window.mParticle.config); - Should( + expect( mParticle.getInstance()._Persistence.getFirstSeenTime('current') ).equal(null); mParticle.Identity.identify(); - Should( + expect( mParticle.getInstance()._Persistence.getFirstSeenTime('current') ).not.equal(null); @@ -1880,7 +2503,7 @@ describe('migrations and persistence-related', function() { mParticle.useCookieStorage = true; mParticle.init(apiKey, window.mParticle.config); - Should( + expect( mParticle.getInstance()._Persistence.getFirstSeenTime('previous') ).equal(null); @@ -1889,11 +2512,14 @@ describe('migrations and persistence-related', function() { it('should save to persistance a device id set with setDeviceId', function(done) { mParticle._resetForTests(MPConfig); - + mParticle.init(apiKey, window.mParticle.config); mParticle.setDeviceId('foo-guid'); - mParticle.getInstance()._Persistence.getLocalStorage().gs.das.should.equal('foo-guid'); + mParticle + .getInstance() + ._Persistence.getLocalStorage() + .gs.das.should.equal('foo-guid'); done(); }); @@ -1903,7 +2529,10 @@ describe('migrations and persistence-related', function() { window.mParticle.config.deviceId = 'foo-guid'; mParticle.init(apiKey, window.mParticle.config); - mParticle.getInstance()._Persistence.getLocalStorage().gs.das.should.equal('foo-guid'); + mParticle + .getInstance() + ._Persistence.getLocalStorage() + .gs.das.should.equal('foo-guid'); done(); }); @@ -1926,8 +2555,8 @@ describe('migrations and persistence-related', function() { var user = mParticle.Identity.getCurrentUser(); - user.setUserAttribute("ua-1","a") - user.setUserAttributeList("ua-list", ["a\\",""]); + user.setUserAttribute('ua-1', 'a'); + user.setUserAttributeList('ua-list', ['a\\', '']); user.getAllUserAttributes()['ua-list'][0].should.equal('a\\'); user.getAllUserAttributes()['ua-list'][1].should.equal(''); @@ -1942,8 +2571,8 @@ describe('migrations and persistence-related', function() { var user2 = mParticle.Identity.getCurrentUser(); - user2.setUserAttribute("ua-1","a") - user2.setUserAttributeList("ua-list", ["a\\",""]); + user2.setUserAttribute('ua-1', 'a'); + user2.setUserAttributeList('ua-list', ['a\\', '']); user2.getAllUserAttributes()['ua-list'][0].should.equal('a\\'); user2.getAllUserAttributes()['ua-list'][1].should.equal('');