@teardown/react-native 2.0.1 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,33 @@
1
- import { describe, expect, mock, test } from "bun:test";
1
+ import { describe, expect, mock, test, beforeEach } from "bun:test";
2
2
 
3
3
  // Mock react-native before any imports that use it
4
4
  mock.module("react-native", () => ({
5
5
  AppState: {
6
- addEventListener: () => ({ remove: () => {} }),
6
+ addEventListener: () => ({ remove: () => { } }),
7
7
  },
8
8
  }));
9
9
 
10
10
  // Import after mock
11
- const { IdentityClient } = await import("./identity.client");
11
+ const { IdentityClient, IDENTIFY_STORAGE_KEY } = await import("./identity.client");
12
12
  const { IdentifyVersionStatusEnum } = await import("../force-update");
13
13
  type IdentifyState = import("./identity.client").IdentifyState;
14
+ type Persona = import("./identity.client").Persona;
15
+
16
+ // ============================================================================
17
+ // Mock Factories
18
+ // ============================================================================
14
19
 
15
20
  function createMockLoggingClient() {
21
+ const logs: { level: string; message: string; args: unknown[] }[] = [];
16
22
  return {
17
23
  createLogger: () => ({
18
- info: () => {},
19
- warn: () => {},
20
- error: () => {},
21
- debug: () => {},
24
+ info: (message: string, ...args: unknown[]) => logs.push({ level: "info", message, args }),
25
+ warn: (message: string, ...args: unknown[]) => logs.push({ level: "warn", message, args }),
26
+ error: (message: string, ...args: unknown[]) => logs.push({ level: "error", message, args }),
27
+ debug: (message: string, ...args: unknown[]) => logs.push({ level: "debug", message, args }),
22
28
  }),
29
+ getLogs: () => logs,
30
+ clearLogs: () => logs.length = 0,
23
31
  };
24
32
  }
25
33
 
@@ -32,132 +40,245 @@ function createMockStorageClient() {
32
40
  removeItem: (key: string) => storage.delete(key),
33
41
  }),
34
42
  getStorage: () => storage,
43
+ clear: () => storage.clear(),
35
44
  };
36
45
  }
37
46
 
38
47
  function createMockUtilsClient() {
48
+ let uuidCounter = 0;
39
49
  return {
40
- generateRandomUUID: async () => "mock-uuid",
50
+ generateRandomUUID: async () => `mock-uuid-${++uuidCounter}`,
51
+ resetCounter: () => uuidCounter = 0,
41
52
  };
42
53
  }
43
54
 
44
- function createMockDeviceClient() {
55
+ function createMockDeviceClient(overrides: Partial<{
56
+ deviceId: string;
57
+ timestamp: string;
58
+ application: { name: string; version: string; build: string; bundle_id: string };
59
+ hardware: { brand: string; model: string; device_type: string };
60
+ os: { name: string; version: string };
61
+ notifications: { push_token: string | null; platform: string | null };
62
+ update: null;
63
+ }> = {}) {
64
+ const defaultDeviceInfo = {
65
+ timestamp: new Date().toISOString(),
66
+ application: { name: "TestApp", version: "1.0.0", build: "100", bundle_id: "com.test.app" },
67
+ hardware: { brand: "Apple", model: "iPhone 15", device_type: "PHONE" },
68
+ os: { name: "iOS", version: "17.0" },
69
+ notifications: { push_token: null, platform: null },
70
+ update: null,
71
+ };
72
+
45
73
  return {
46
- getDeviceId: async () => "mock-device-id",
74
+ getDeviceId: async () => overrides.deviceId ?? "mock-device-id",
47
75
  getDeviceInfo: async () => ({
48
- application: { name: "TestApp", version: "1.0.0", build: "100", bundle_id: "com.test" },
49
- hardware: { brand: "Apple", model: "iPhone", device_type: "PHONE" },
50
- os: { name: "iOS", version: "17.0" },
51
- notifications: { push_token: null, platform: null },
52
- update: null,
76
+ ...defaultDeviceInfo,
77
+ ...overrides,
53
78
  }),
54
79
  };
55
80
  }
56
81
 
82
+ type ApiCallRecord = {
83
+ endpoint: string;
84
+ config: {
85
+ method: string;
86
+ headers: Record<string, string>;
87
+ body: unknown;
88
+ };
89
+ };
90
+
57
91
  function createMockApiClient(options: {
58
92
  success?: boolean;
59
93
  versionStatus?: IdentifyVersionStatusEnum;
60
94
  errorStatus?: number;
61
95
  errorMessage?: string;
96
+ sessionId?: string;
97
+ deviceId?: string;
98
+ personaId?: string;
99
+ token?: string;
100
+ throwError?: Error;
62
101
  } = {}) {
63
- const { success = true, versionStatus = IdentifyVersionStatusEnum.UP_TO_DATE, errorStatus, errorMessage } = options;
102
+ const {
103
+ success = true,
104
+ versionStatus = IdentifyVersionStatusEnum.UP_TO_DATE,
105
+ errorStatus,
106
+ errorMessage,
107
+ sessionId = "session-123",
108
+ deviceId = "device-123",
109
+ personaId = "persona-123",
110
+ token = "token-123",
111
+ throwError,
112
+ } = options;
113
+
114
+ const calls: ApiCallRecord[] = [];
64
115
 
65
116
  return {
66
117
  apiKey: "test-api-key",
67
118
  orgId: "test-org-id",
68
119
  projectId: "test-project-id",
69
- client: async () => {
120
+ client: async (endpoint: string, config: ApiCallRecord["config"]) => {
121
+ calls.push({ endpoint, config });
122
+
123
+ if (throwError) {
124
+ throw throwError;
125
+ }
126
+
70
127
  if (!success) {
71
128
  return {
72
129
  error: {
73
130
  status: errorStatus ?? 500,
74
- value: { message: errorMessage ?? "API Error", error: { message: errorMessage ?? "API Error" } },
131
+ value: {
132
+ message: errorMessage ?? "API Error",
133
+ error: { message: errorMessage ?? "API Error" },
134
+ },
75
135
  },
76
136
  data: null,
77
137
  };
78
138
  }
139
+
79
140
  return {
80
141
  error: null,
81
142
  data: {
82
143
  data: {
83
- session_id: "session-123",
84
- device_id: "device-123",
85
- persona_id: "persona-123",
86
- token: "token-123",
144
+ session_id: sessionId,
145
+ device_id: deviceId,
146
+ persona_id: personaId,
147
+ token: token,
87
148
  version_info: { status: versionStatus },
88
149
  },
89
150
  },
90
151
  };
91
152
  },
153
+ getCalls: () => calls,
154
+ getLastCall: () => calls[calls.length - 1],
155
+ clearCalls: () => calls.length = 0,
92
156
  };
93
157
  }
94
158
 
95
- describe("IdentityClient", () => {
96
- describe("initial state", () => {
97
- test("starts with unidentified state when no stored state", () => {
98
- const mockLogging = createMockLoggingClient();
99
- const mockStorage = createMockStorageClient();
100
- const mockUtils = createMockUtilsClient();
101
- const mockApi = createMockApiClient();
102
- const mockDevice = createMockDeviceClient();
159
+ // Helper to create a standard client instance
160
+ function createTestClient(overrides: {
161
+ logging?: ReturnType<typeof createMockLoggingClient>;
162
+ storage?: ReturnType<typeof createMockStorageClient>;
163
+ utils?: ReturnType<typeof createMockUtilsClient>;
164
+ api?: ReturnType<typeof createMockApiClient>;
165
+ device?: ReturnType<typeof createMockDeviceClient>;
166
+ } = {}) {
167
+ const mockLogging = overrides.logging ?? createMockLoggingClient();
168
+ const mockStorage = overrides.storage ?? createMockStorageClient();
169
+ const mockUtils = overrides.utils ?? createMockUtilsClient();
170
+ const mockApi = overrides.api ?? createMockApiClient();
171
+ const mockDevice = overrides.device ?? createMockDeviceClient();
172
+
173
+ const client = new IdentityClient(
174
+ mockLogging as never,
175
+ mockUtils as never,
176
+ mockStorage as never,
177
+ mockApi as never,
178
+ mockDevice as never
179
+ );
180
+
181
+ return {
182
+ client,
183
+ mockLogging,
184
+ mockStorage,
185
+ mockUtils,
186
+ mockApi,
187
+ mockDevice,
188
+ };
189
+ }
190
+
191
+ // Helper to manually load state from storage without calling identify()
192
+ function loadStateFromStorage(client: InstanceType<typeof IdentityClient>) {
193
+ const clientAny = client as unknown as {
194
+ identifyState: IdentifyState;
195
+ initialized: boolean;
196
+ getIdentifyStateFromStorage: () => IdentifyState;
197
+ };
198
+ clientAny.identifyState = clientAny.getIdentifyStateFromStorage();
199
+ clientAny.initialized = true;
200
+ }
103
201
 
104
- const client = new IdentityClient(
105
- mockLogging as never,
106
- mockUtils as never,
107
- mockStorage as never,
108
- mockApi as never,
109
- mockDevice as never
110
- );
202
+ // ============================================================================
203
+ // Tests
204
+ // ============================================================================
111
205
 
206
+ describe("IdentityClient", () => {
207
+ describe("constructor and initial state", () => {
208
+ test("starts with unidentified state when no stored state", () => {
209
+ const { client } = createTestClient();
112
210
  expect(client.getIdentifyState().type).toBe("unidentified");
113
211
  });
114
212
 
115
- test("restores state from storage", () => {
116
- const mockLogging = createMockLoggingClient();
213
+ test("restores identified state from storage after initialize", () => {
117
214
  const mockStorage = createMockStorageClient();
118
- const mockUtils = createMockUtilsClient();
119
- const mockApi = createMockApiClient();
120
- const mockDevice = createMockDeviceClient();
121
-
122
- // Pre-populate storage with identified state
123
215
  const storedState = {
124
216
  type: "identified",
125
217
  session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
126
218
  version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
127
219
  };
128
- mockStorage.getStorage().set("IDENTIFY_STATE", JSON.stringify(storedState));
220
+ mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify(storedState));
129
221
 
130
- const client = new IdentityClient(
131
- mockLogging as never,
132
- mockUtils as never,
133
- mockStorage as never,
134
- mockApi as never,
135
- mockDevice as never
136
- );
222
+ const { client } = createTestClient({ storage: mockStorage });
223
+ loadStateFromStorage(client);
137
224
 
138
225
  const state = client.getIdentifyState();
139
226
  expect(state.type).toBe("identified");
140
227
  if (state.type === "identified") {
141
228
  expect(state.session.session_id).toBe("s1");
229
+ expect(state.session.device_id).toBe("d1");
230
+ expect(state.session.persona_id).toBe("p1");
231
+ expect(state.session.token).toBe("t1");
142
232
  }
143
233
  });
234
+
235
+ test("resets stale identifying state from storage to unidentified", () => {
236
+ const mockStorage = createMockStorageClient();
237
+ const storedState = { type: "identifying" };
238
+ mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify(storedState));
239
+
240
+ const { client } = createTestClient({ storage: mockStorage });
241
+ loadStateFromStorage(client);
242
+
243
+ // "identifying" is a transient state - should reset to unidentified
244
+ expect(client.getIdentifyState().type).toBe("unidentified");
245
+ });
246
+
247
+ test("throws error on invalid stored state (corrupt JSON)", () => {
248
+ const mockStorage = createMockStorageClient();
249
+ mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, "not-valid-json{{{");
250
+
251
+ const { client } = createTestClient({ storage: mockStorage });
252
+ expect(() => loadStateFromStorage(client)).toThrow();
253
+ });
254
+
255
+ test("throws error on invalid stored state (schema mismatch)", () => {
256
+ const mockStorage = createMockStorageClient();
257
+ mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify({ type: "invalid_type" }));
258
+
259
+ const { client } = createTestClient({ storage: mockStorage });
260
+ expect(() => loadStateFromStorage(client)).toThrow();
261
+ });
262
+
263
+ test("creates logger with correct name", () => {
264
+ const { client } = createTestClient();
265
+ expect(client.logger).toBeDefined();
266
+ });
267
+
268
+ test("exposes utils client", () => {
269
+ const { client } = createTestClient();
270
+ expect(client.utils).toBeDefined();
271
+ });
272
+
273
+ test("exposes storage client", () => {
274
+ const { client } = createTestClient();
275
+ expect(client.storage).toBeDefined();
276
+ });
144
277
  });
145
278
 
146
279
  describe("identify", () => {
147
280
  test("transitions to identifying then identified on success", async () => {
148
- const mockLogging = createMockLoggingClient();
149
- const mockStorage = createMockStorageClient();
150
- const mockUtils = createMockUtilsClient();
151
- const mockApi = createMockApiClient({ success: true });
152
- const mockDevice = createMockDeviceClient();
153
-
154
- const client = new IdentityClient(
155
- mockLogging as never,
156
- mockUtils as never,
157
- mockStorage as never,
158
- mockApi as never,
159
- mockDevice as never
160
- );
281
+ const { client } = createTestClient();
161
282
 
162
283
  const stateChanges: IdentifyState[] = [];
163
284
  client.onIdentifyStateChange((state) => stateChanges.push(state));
@@ -170,116 +291,330 @@ describe("IdentityClient", () => {
170
291
  expect(stateChanges[1].type).toBe("identified");
171
292
  });
172
293
 
173
- test("returns user data on successful identify", async () => {
174
- const mockLogging = createMockLoggingClient();
175
- const mockStorage = createMockStorageClient();
176
- const mockUtils = createMockUtilsClient();
177
- const mockApi = createMockApiClient({ versionStatus: IdentifyVersionStatusEnum.UPDATE_AVAILABLE });
178
- const mockDevice = createMockDeviceClient();
179
-
180
- const client = new IdentityClient(
181
- mockLogging as never,
182
- mockUtils as never,
183
- mockStorage as never,
184
- mockApi as never,
185
- mockDevice as never
186
- );
294
+ test("returns complete user data on successful identify", async () => {
295
+ const mockApi = createMockApiClient({
296
+ versionStatus: IdentifyVersionStatusEnum.UPDATE_AVAILABLE,
297
+ sessionId: "custom-session",
298
+ deviceId: "custom-device",
299
+ personaId: "custom-persona",
300
+ token: "custom-token",
301
+ });
302
+ const { client } = createTestClient({ api: mockApi });
187
303
 
188
304
  const result = await client.identify();
189
305
 
190
306
  expect(result.success).toBe(true);
191
307
  if (result.success) {
192
- expect(result.data.session_id).toBe("session-123");
193
- expect(result.data.device_id).toBe("device-123");
308
+ expect(result.data.session_id).toBe("custom-session");
309
+ expect(result.data.device_id).toBe("custom-device");
310
+ expect(result.data.persona_id).toBe("custom-persona");
311
+ expect(result.data.token).toBe("custom-token");
194
312
  expect(result.data.version_info.status).toBe(IdentifyVersionStatusEnum.UPDATE_AVAILABLE);
313
+ expect(result.data.version_info.update).toBeNull();
314
+ }
315
+ });
316
+
317
+ test("handles UPDATE_REQUIRED version status", async () => {
318
+ const mockApi = createMockApiClient({
319
+ versionStatus: IdentifyVersionStatusEnum.UPDATE_REQUIRED,
320
+ });
321
+ const { client } = createTestClient({ api: mockApi });
322
+
323
+ const result = await client.identify();
324
+
325
+ expect(result.success).toBe(true);
326
+ if (result.success) {
327
+ expect(result.data.version_info.status).toBe(IdentifyVersionStatusEnum.UPDATE_REQUIRED);
328
+ }
329
+ });
330
+
331
+ test("handles UP_TO_DATE version status", async () => {
332
+ const mockApi = createMockApiClient({
333
+ versionStatus: IdentifyVersionStatusEnum.UP_TO_DATE,
334
+ });
335
+ const { client } = createTestClient({ api: mockApi });
336
+
337
+ const result = await client.identify();
338
+
339
+ expect(result.success).toBe(true);
340
+ if (result.success) {
341
+ expect(result.data.version_info.status).toBe(IdentifyVersionStatusEnum.UP_TO_DATE);
195
342
  }
196
343
  });
197
344
 
198
345
  test("reverts to previous state on API error", async () => {
199
- const mockLogging = createMockLoggingClient();
200
- const mockStorage = createMockStorageClient();
201
- const mockUtils = createMockUtilsClient();
202
- const mockApi = createMockApiClient({ success: false, errorStatus: 500, errorMessage: "Server error" });
203
- const mockDevice = createMockDeviceClient();
204
-
205
- const client = new IdentityClient(
206
- mockLogging as never,
207
- mockUtils as never,
208
- mockStorage as never,
209
- mockApi as never,
210
- mockDevice as never
211
- );
346
+ const mockApi = createMockApiClient({
347
+ success: false,
348
+ errorStatus: 500,
349
+ errorMessage: "Server error",
350
+ });
351
+ const { client } = createTestClient({ api: mockApi });
212
352
 
213
353
  const result = await client.identify();
214
354
 
215
355
  expect(result.success).toBe(false);
216
- // Should revert to unidentified (the previous state)
217
356
  expect(client.getIdentifyState().type).toBe("unidentified");
218
357
  });
219
358
 
220
- test("handles 422 validation error", async () => {
221
- const mockLogging = createMockLoggingClient();
359
+ test("reverts to identified state on API error when previously identified", async () => {
222
360
  const mockStorage = createMockStorageClient();
223
- const mockUtils = createMockUtilsClient();
224
- const mockApi = createMockApiClient({ success: false, errorStatus: 422, errorMessage: "Validation failed" });
225
- const mockDevice = createMockDeviceClient();
226
-
227
- const client = new IdentityClient(
228
- mockLogging as never,
229
- mockUtils as never,
230
- mockStorage as never,
231
- mockApi as never,
232
- mockDevice as never
233
- );
361
+ const storedState = {
362
+ type: "identified",
363
+ session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
364
+ version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
365
+ };
366
+ mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify(storedState));
367
+
368
+ const mockApi = createMockApiClient({
369
+ success: false,
370
+ errorStatus: 500,
371
+ errorMessage: "Server error",
372
+ });
373
+
374
+ const { client } = createTestClient({ storage: mockStorage, api: mockApi });
375
+ loadStateFromStorage(client);
376
+
377
+ // Should start identified
378
+ expect(client.getIdentifyState().type).toBe("identified");
379
+
380
+ const result = await client.identify();
381
+
382
+ // Should fail and revert to identified
383
+ expect(result.success).toBe(false);
384
+ expect(client.getIdentifyState().type).toBe("identified");
385
+ });
386
+
387
+ test("handles 422 validation error with message", async () => {
388
+ const mockApi = createMockApiClient({
389
+ success: false,
390
+ errorStatus: 422,
391
+ errorMessage: "Validation failed: invalid device ID",
392
+ });
393
+ const { client } = createTestClient({ api: mockApi });
394
+
395
+ const result = await client.identify();
396
+
397
+ expect(result.success).toBe(false);
398
+ if (!result.success) {
399
+ expect(result.error).toBe("Validation failed: invalid device ID");
400
+ }
401
+ });
402
+
403
+ test("handles 422 error without message gracefully", async () => {
404
+ const mockApi = createMockApiClient({ success: false, errorStatus: 422 });
405
+ // Override to return null message
406
+ // @ts-expect-error - message is not yet implemented
407
+ mockApi.client = async () => ({
408
+ error: {
409
+ status: 422,
410
+ value: { message: null, error: { message: null } },
411
+ },
412
+ data: null,
413
+ });
414
+
415
+ const { client } = createTestClient({ api: mockApi });
416
+ const result = await client.identify();
417
+
418
+ expect(result.success).toBe(false);
419
+ if (!result.success) {
420
+ expect(result.error).toBe("Unknown error");
421
+ }
422
+ });
423
+
424
+ test("handles non-422 error extracting nested error message", async () => {
425
+ const mockApi = createMockApiClient({
426
+ success: false,
427
+ errorStatus: 403,
428
+ errorMessage: "Forbidden: invalid API key",
429
+ });
430
+ const { client } = createTestClient({ api: mockApi });
431
+
432
+ const result = await client.identify();
433
+
434
+ expect(result.success).toBe(false);
435
+ if (!result.success) {
436
+ expect(result.error).toBe("Forbidden: invalid API key");
437
+ }
438
+ });
439
+
440
+ test("handles thrown exceptions via tryCatch", async () => {
441
+ const mockApi = createMockApiClient({
442
+ throwError: new Error("Network connection failed"),
443
+ });
444
+ const { client } = createTestClient({ api: mockApi });
445
+
446
+ const result = await client.identify();
447
+
448
+ expect(result.success).toBe(false);
449
+ if (!result.success) {
450
+ expect(result.error).toBe("Network connection failed");
451
+ }
452
+ });
453
+
454
+ test("handles non-Error thrown exceptions", async () => {
455
+ const mockApi = createMockApiClient();
456
+ mockApi.client = async () => {
457
+ throw "string error"; // Non-Error throw
458
+ };
234
459
 
460
+ const { client } = createTestClient({ api: mockApi });
235
461
  const result = await client.identify();
236
462
 
237
463
  expect(result.success).toBe(false);
238
464
  if (!result.success) {
239
- expect(result.error).toBe("Validation failed");
465
+ expect(result.error).toBe("Unknown error");
240
466
  }
241
467
  });
242
468
 
243
469
  test("persists identified state to storage", async () => {
244
- const mockLogging = createMockLoggingClient();
245
- const mockStorage = createMockStorageClient();
246
- const mockUtils = createMockUtilsClient();
247
- const mockApi = createMockApiClient({ success: true });
248
- const mockDevice = createMockDeviceClient();
249
-
250
- const client = new IdentityClient(
251
- mockLogging as never,
252
- mockUtils as never,
253
- mockStorage as never,
254
- mockApi as never,
255
- mockDevice as never
256
- );
470
+ const { client, mockStorage } = createTestClient();
257
471
 
258
472
  await client.identify();
259
473
 
260
- const stored = mockStorage.getStorage().get("IDENTIFY_STATE");
474
+ const stored = mockStorage.getStorage().get(IDENTIFY_STORAGE_KEY);
261
475
  expect(stored).toBeDefined();
262
476
  const parsed = JSON.parse(stored!);
263
477
  expect(parsed.type).toBe("identified");
264
478
  expect(parsed.session.session_id).toBe("session-123");
479
+ expect(parsed.version_info.status).toBe(IdentifyVersionStatusEnum.UP_TO_DATE);
480
+ });
481
+
482
+ test("passes persona data to API", async () => {
483
+ const { client, mockApi } = createTestClient();
484
+
485
+ const persona: Persona = {
486
+ name: "John Doe",
487
+ user_id: "user-456",
488
+ email: "john@example.com",
489
+ };
490
+
491
+ await client.identify(persona);
492
+
493
+ const lastCall = mockApi.getLastCall();
494
+ expect((lastCall.config.body as { persona?: Persona }).persona).toEqual(persona);
495
+ });
496
+
497
+ test("passes undefined persona when not provided", async () => {
498
+ const { client, mockApi } = createTestClient();
499
+
500
+ await client.identify();
501
+
502
+ const lastCall = mockApi.getLastCall();
503
+ expect((lastCall.config.body as { persona?: Persona }).persona).toBeUndefined();
504
+ });
505
+
506
+ test("passes partial persona data", async () => {
507
+ const { client, mockApi } = createTestClient();
508
+
509
+ const persona: Persona = { email: "only-email@test.com" };
510
+
511
+ await client.identify(persona);
512
+
513
+ const lastCall = mockApi.getLastCall();
514
+ expect((lastCall.config.body as { persona?: Persona }).persona).toEqual({ email: "only-email@test.com" });
515
+ });
516
+
517
+ test("calls correct API endpoint", async () => {
518
+ const { client, mockApi } = createTestClient();
519
+
520
+ await client.identify();
521
+
522
+ const lastCall = mockApi.getLastCall();
523
+ expect(lastCall.endpoint).toBe("/v1/identify");
524
+ expect(lastCall.config.method).toBe("POST");
525
+ });
526
+
527
+ test("sends correct headers to API", async () => {
528
+ const { client, mockApi } = createTestClient();
529
+
530
+ await client.identify();
531
+
532
+ const lastCall = mockApi.getLastCall();
533
+ expect(lastCall.config.headers["td-api-key"]).toBe("test-api-key");
534
+ expect(lastCall.config.headers["td-org-id"]).toBe("test-org-id");
535
+ expect(lastCall.config.headers["td-project-id"]).toBe("test-project-id");
536
+ expect(lastCall.config.headers["td-environment-slug"]).toBe("production");
537
+ expect(lastCall.config.headers["td-device-id"]).toBe("mock-device-id");
538
+ });
539
+
540
+ test("sends device info in request body", async () => {
541
+ const mockDevice = createMockDeviceClient({
542
+ deviceId: "custom-device-id",
543
+ timestamp: "2024-01-15T10:30:00.000Z",
544
+ application: { name: "MyApp", version: "2.0.0", build: "200", bundle_id: "com.my.app" },
545
+ os: { name: "Android", version: "14" },
546
+ hardware: { brand: "Samsung", model: "Galaxy S24", device_type: "PHONE" },
547
+ });
548
+ const { client, mockApi } = createTestClient({ device: mockDevice });
549
+
550
+ await client.identify();
551
+
552
+ const lastCall = mockApi.getLastCall();
553
+ const device = (lastCall.config.body as { device?: { timestamp: string; application: { name: string; version: string; build: string; bundle_id: string }; os: { name: string; version: string }; hardware: { brand: string; model: string; device_type: string }; update: null } }).device;
554
+
555
+ expect(device?.timestamp).toBe("2024-01-15T10:30:00.000Z");
556
+ expect(device?.application.name).toBe("MyApp");
557
+ expect(device?.application.version).toBe("2.0.0");
558
+ expect(device?.os.name).toBe("Android");
559
+ expect(device?.hardware.brand).toBe("Samsung");
560
+ expect(device?.update).toBeNull();
265
561
  });
266
562
  });
267
563
 
268
- describe("onIdentifyStateChange", () => {
269
- test("emits state changes to listeners", async () => {
270
- const mockLogging = createMockLoggingClient();
564
+ describe("setIdentifyState deduplication", () => {
565
+ test("does not emit when transitioning to same state type", async () => {
271
566
  const mockStorage = createMockStorageClient();
272
- const mockUtils = createMockUtilsClient();
567
+ const storedState = {
568
+ type: "identified",
569
+ session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
570
+ version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
571
+ };
572
+ mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify(storedState));
573
+
273
574
  const mockApi = createMockApiClient();
274
- const mockDevice = createMockDeviceClient();
575
+ const { client } = createTestClient({ storage: mockStorage, api: mockApi });
576
+ loadStateFromStorage(client);
577
+
578
+ // Start identified
579
+ expect(client.getIdentifyState().type).toBe("identified");
580
+
581
+ const stateChanges: IdentifyState[] = [];
582
+ client.onIdentifyStateChange((state) => stateChanges.push(state));
583
+
584
+ // Identify again (will go identifying -> identified)
585
+ await client.identify();
586
+
587
+ // Should emit identifying and identified
588
+ expect(stateChanges).toHaveLength(2);
589
+ expect(stateChanges[0].type).toBe("identifying");
590
+ expect(stateChanges[1].type).toBe("identified");
591
+ });
275
592
 
276
- const client = new IdentityClient(
277
- mockLogging as never,
278
- mockUtils as never,
279
- mockStorage as never,
280
- mockApi as never,
281
- mockDevice as never
282
- );
593
+ test("logs debug message when state type unchanged", async () => {
594
+ const mockLogging = createMockLoggingClient();
595
+ const { client } = createTestClient({ logging: mockLogging });
596
+
597
+ // First identify succeeds
598
+ await client.identify();
599
+ expect(client.getIdentifyState().type).toBe("identified");
600
+
601
+ mockLogging.clearLogs();
602
+
603
+ // Second identify
604
+ await client.identify();
605
+
606
+ // Should have debug log about state already being 'identifying' (first setIdentifyState call)
607
+ // Then transitions to identified
608
+ const debugLogs = mockLogging.getLogs().filter(l => l.level === "debug");
609
+ // The "identifying" to "identifying" won't happen since we start from "identified"
610
+ // But we can check that state changes are logged properly
611
+ expect(mockLogging.getLogs().some(l => l.level === "info")).toBe(true);
612
+ });
613
+ });
614
+
615
+ describe("onIdentifyStateChange", () => {
616
+ test("emits state changes to listeners", async () => {
617
+ const { client } = createTestClient();
283
618
 
284
619
  const states: IdentifyState[] = [];
285
620
  client.onIdentifyStateChange((state) => states.push(state));
@@ -289,20 +624,24 @@ describe("IdentityClient", () => {
289
624
  expect(states.length).toBeGreaterThan(0);
290
625
  });
291
626
 
292
- test("returns unsubscribe function", async () => {
293
- const mockLogging = createMockLoggingClient();
294
- const mockStorage = createMockStorageClient();
295
- const mockUtils = createMockUtilsClient();
296
- const mockApi = createMockApiClient();
297
- const mockDevice = createMockDeviceClient();
627
+ test("supports multiple listeners", async () => {
628
+ const { client } = createTestClient();
629
+
630
+ const states1: IdentifyState[] = [];
631
+ const states2: IdentifyState[] = [];
632
+
633
+ client.onIdentifyStateChange((state) => states1.push(state));
634
+ client.onIdentifyStateChange((state) => states2.push(state));
635
+
636
+ await client.identify();
298
637
 
299
- const client = new IdentityClient(
300
- mockLogging as never,
301
- mockUtils as never,
302
- mockStorage as never,
303
- mockApi as never,
304
- mockDevice as never
305
- );
638
+ expect(states1.length).toBeGreaterThan(0);
639
+ expect(states2.length).toBeGreaterThan(0);
640
+ expect(states1).toEqual(states2);
641
+ });
642
+
643
+ test("returns unsubscribe function", async () => {
644
+ const { client } = createTestClient();
306
645
 
307
646
  const states: IdentifyState[] = [];
308
647
  const unsubscribe = client.onIdentifyStateChange((state) => states.push(state));
@@ -311,26 +650,49 @@ describe("IdentityClient", () => {
311
650
 
312
651
  await client.identify();
313
652
 
314
- // Should not receive any state changes after unsubscribing
315
653
  expect(states).toHaveLength(0);
316
654
  });
655
+
656
+ test("only unsubscribes the specific listener", async () => {
657
+ const { client } = createTestClient();
658
+
659
+ const states1: IdentifyState[] = [];
660
+ const states2: IdentifyState[] = [];
661
+
662
+ const unsubscribe1 = client.onIdentifyStateChange((state) => states1.push(state));
663
+ client.onIdentifyStateChange((state) => states2.push(state));
664
+
665
+ unsubscribe1();
666
+
667
+ await client.identify();
668
+
669
+ expect(states1).toHaveLength(0);
670
+ expect(states2.length).toBeGreaterThan(0);
671
+ });
317
672
  });
318
673
 
319
674
  describe("refresh", () => {
320
675
  test("returns error when not identified", async () => {
321
- const mockLogging = createMockLoggingClient();
676
+ const { client } = createTestClient();
677
+
678
+ const result = await client.refresh();
679
+
680
+ expect(result.success).toBe(false);
681
+ if (!result.success) {
682
+ expect(result.error).toBe("Not identified");
683
+ }
684
+ });
685
+
686
+ test("returns error when unidentified (stale identifying state resets)", async () => {
322
687
  const mockStorage = createMockStorageClient();
323
- const mockUtils = createMockUtilsClient();
324
- const mockApi = createMockApiClient();
325
- const mockDevice = createMockDeviceClient();
688
+ // "identifying" in storage gets reset to "unidentified" on load
689
+ mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify({ type: "identifying" }));
326
690
 
327
- const client = new IdentityClient(
328
- mockLogging as never,
329
- mockUtils as never,
330
- mockStorage as never,
331
- mockApi as never,
332
- mockDevice as never
333
- );
691
+ const { client } = createTestClient({ storage: mockStorage });
692
+ loadStateFromStorage(client);
693
+
694
+ // State should have been reset to unidentified
695
+ expect(client.getIdentifyState().type).toBe("unidentified");
334
696
 
335
697
  const result = await client.refresh();
336
698
 
@@ -341,76 +703,78 @@ describe("IdentityClient", () => {
341
703
  });
342
704
 
343
705
  test("re-identifies when already identified", async () => {
344
- const mockLogging = createMockLoggingClient();
345
- const mockStorage = createMockStorageClient();
346
- const mockUtils = createMockUtilsClient();
347
- const mockApi = createMockApiClient();
348
- const mockDevice = createMockDeviceClient();
706
+ const { client, mockApi } = createTestClient();
349
707
 
350
- const client = new IdentityClient(
351
- mockLogging as never,
352
- mockUtils as never,
353
- mockStorage as never,
354
- mockApi as never,
355
- mockDevice as never
356
- );
357
-
358
- // First identify
359
708
  await client.identify();
360
709
  expect(client.getIdentifyState().type).toBe("identified");
361
710
 
362
- // Then refresh
711
+ mockApi.clearCalls();
712
+
363
713
  const result = await client.refresh();
714
+
364
715
  expect(result.success).toBe(true);
716
+ expect(mockApi.getCalls()).toHaveLength(1);
717
+ });
718
+
719
+ test("returns updated data on refresh", async () => {
720
+ const mockApi = createMockApiClient({
721
+ sessionId: "original-session",
722
+ });
723
+ const { client } = createTestClient({ api: mockApi });
724
+
725
+ await client.identify();
726
+
727
+ // Update mock to return new session
728
+ mockApi.client = async (endpoint: string, config: ApiCallRecord["config"]) => ({
729
+ error: null,
730
+ data: {
731
+ data: {
732
+ session_id: "refreshed-session",
733
+ device_id: "device-123",
734
+ persona_id: "persona-123",
735
+ token: "token-123",
736
+ version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
737
+ },
738
+ },
739
+ });
740
+
741
+ const result = await client.refresh();
742
+
743
+ expect(result.success).toBe(true);
744
+ if (result.success) {
745
+ expect(result.data.session_id).toBe("refreshed-session");
746
+ }
365
747
  });
366
748
  });
367
749
 
368
750
  describe("reset", () => {
369
751
  test("resets state to unidentified", async () => {
370
- const mockLogging = createMockLoggingClient();
371
- const mockStorage = createMockStorageClient();
372
- const mockUtils = createMockUtilsClient();
373
- const mockApi = createMockApiClient();
374
- const mockDevice = createMockDeviceClient();
752
+ const { client } = createTestClient();
375
753
 
376
- const client = new IdentityClient(
377
- mockLogging as never,
378
- mockUtils as never,
379
- mockStorage as never,
380
- mockApi as never,
381
- mockDevice as never
382
- );
383
-
384
- // First identify
385
754
  await client.identify();
386
755
  expect(client.getIdentifyState().type).toBe("identified");
387
756
 
388
- // Then reset
389
757
  client.reset();
390
758
 
391
759
  expect(client.getIdentifyState().type).toBe("unidentified");
392
- // Storage contains unidentified state after reset (setIdentifyState saves it)
393
- const stored = mockStorage.getStorage().get("IDENTIFY_STATE");
760
+ });
761
+
762
+ test("removes state from storage then saves unidentified", async () => {
763
+ const { client, mockStorage } = createTestClient();
764
+
765
+ await client.identify();
766
+ expect(mockStorage.getStorage().has(IDENTIFY_STORAGE_KEY)).toBe(true);
767
+
768
+ client.reset();
769
+
770
+ // After reset, storage should contain unidentified state
771
+ const stored = mockStorage.getStorage().get(IDENTIFY_STORAGE_KEY);
394
772
  expect(stored).toBeDefined();
395
- if (stored) {
396
- expect(JSON.parse(stored).type).toBe("unidentified");
397
- }
773
+ expect(JSON.parse(stored!).type).toBe("unidentified");
398
774
  });
399
775
 
400
776
  test("emits unidentified state on reset", async () => {
401
- const mockLogging = createMockLoggingClient();
402
- const mockStorage = createMockStorageClient();
403
- const mockUtils = createMockUtilsClient();
404
- const mockApi = createMockApiClient();
405
- const mockDevice = createMockDeviceClient();
406
-
407
- const client = new IdentityClient(
408
- mockLogging as never,
409
- mockUtils as never,
410
- mockStorage as never,
411
- mockApi as never,
412
- mockDevice as never
413
- );
777
+ const { client } = createTestClient();
414
778
 
415
779
  await client.identify();
416
780
 
@@ -422,23 +786,152 @@ describe("IdentityClient", () => {
422
786
  expect(states).toHaveLength(1);
423
787
  expect(states[0].type).toBe("unidentified");
424
788
  });
789
+
790
+ test("can identify again after reset", async () => {
791
+ const { client } = createTestClient();
792
+
793
+ await client.identify();
794
+ client.reset();
795
+ expect(client.getIdentifyState().type).toBe("unidentified");
796
+
797
+ const result = await client.identify();
798
+
799
+ expect(result.success).toBe(true);
800
+ expect(client.getIdentifyState().type).toBe("identified");
801
+ });
802
+
803
+ test("does not emit when already unidentified", () => {
804
+ const { client } = createTestClient();
805
+
806
+ expect(client.getIdentifyState().type).toBe("unidentified");
807
+
808
+ const states: IdentifyState[] = [];
809
+ client.onIdentifyStateChange((state) => states.push(state));
810
+
811
+ client.reset();
812
+
813
+ // setIdentifyState checks type equality and won't emit
814
+ expect(states).toHaveLength(0);
815
+ });
425
816
  });
426
817
 
427
- describe("shutdown", () => {
428
- test("removes all listeners", async () => {
429
- const mockLogging = createMockLoggingClient();
818
+ describe("initialize", () => {
819
+ test("calls identify on initialize", async () => {
820
+ const { client, mockApi } = createTestClient();
821
+
822
+ expect(client.getIdentifyState().type).toBe("unidentified");
823
+
824
+ await client.initialize();
825
+
826
+ expect(client.getIdentifyState().type).toBe("identified");
827
+ expect(mockApi.getCalls()).toHaveLength(1);
828
+ });
829
+
830
+ test("returns void on success", async () => {
831
+ const { client } = createTestClient();
832
+
833
+ const result = await client.initialize();
834
+
835
+ expect(result).toBeUndefined();
836
+ });
837
+
838
+ test("propagates identify failure silently", async () => {
839
+ const mockApi = createMockApiClient({ success: false });
840
+ const { client } = createTestClient({ api: mockApi });
841
+
842
+ // initialize doesn't return the result, so we check state
843
+ await client.initialize();
844
+
845
+ expect(client.getIdentifyState().type).toBe("unidentified");
846
+ });
847
+ });
848
+
849
+ describe("getSessionState", () => {
850
+ test("returns null when unidentified", () => {
851
+ const { client } = createTestClient();
852
+
853
+ expect(client.getSessionState()).toBeNull();
854
+ });
855
+
856
+ test("returns null when identifying state restored (resets to unidentified)", () => {
430
857
  const mockStorage = createMockStorageClient();
431
- const mockUtils = createMockUtilsClient();
432
- const mockApi = createMockApiClient();
433
- const mockDevice = createMockDeviceClient();
858
+ mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify({ type: "identifying" }));
859
+
860
+ const { client } = createTestClient({ storage: mockStorage });
861
+ loadStateFromStorage(client);
862
+
863
+ // "identifying" is transient, resets to "unidentified"
864
+ expect(client.getSessionState()).toBeNull();
865
+ });
866
+
867
+ test("returns session when identified", async () => {
868
+ const { client } = createTestClient();
869
+
870
+ await client.identify();
434
871
 
435
- const client = new IdentityClient(
436
- mockLogging as never,
437
- mockUtils as never,
438
- mockStorage as never,
439
- mockApi as never,
440
- mockDevice as never
441
- );
872
+ const session = client.getSessionState();
873
+ expect(session).not.toBeNull();
874
+ expect(session?.session_id).toBe("session-123");
875
+ expect(session?.device_id).toBe("device-123");
876
+ expect(session?.persona_id).toBe("persona-123");
877
+ expect(session?.token).toBe("token-123");
878
+ });
879
+
880
+ test("returns updated session after re-identify", async () => {
881
+ const mockApi = createMockApiClient({ sessionId: "first-session" });
882
+ const { client } = createTestClient({ api: mockApi });
883
+
884
+ await client.identify();
885
+ expect(client.getSessionState()?.session_id).toBe("first-session");
886
+
887
+ // Change what API returns
888
+ mockApi.client = async () => ({
889
+ error: null,
890
+ data: {
891
+ data: {
892
+ session_id: "second-session",
893
+ device_id: "device-123",
894
+ persona_id: "persona-123",
895
+ token: "token-123",
896
+ version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
897
+ },
898
+ },
899
+ });
900
+
901
+ await client.identify();
902
+ expect(client.getSessionState()?.session_id).toBe("second-session");
903
+ });
904
+ });
905
+
906
+ describe("getIdentifyState", () => {
907
+ test("returns current state object", () => {
908
+ const { client } = createTestClient();
909
+
910
+ const state = client.getIdentifyState();
911
+
912
+ expect(state).toBeDefined();
913
+ expect(state.type).toBe("unidentified");
914
+ });
915
+
916
+ test("returns identified state with all fields", async () => {
917
+ const { client } = createTestClient();
918
+
919
+ await client.identify();
920
+
921
+ const state = client.getIdentifyState();
922
+ expect(state.type).toBe("identified");
923
+ if (state.type === "identified") {
924
+ expect(state.session).toBeDefined();
925
+ expect(state.version_info).toBeDefined();
926
+ expect(state.version_info.status).toBe(IdentifyVersionStatusEnum.UP_TO_DATE);
927
+ expect(state.version_info.update).toBeNull();
928
+ }
929
+ });
930
+ });
931
+
932
+ describe("shutdown", () => {
933
+ test("removes all listeners", async () => {
934
+ const { client } = createTestClient();
442
935
 
443
936
  const states: IdentifyState[] = [];
444
937
  client.onIdentifyStateChange((state) => states.push(state));
@@ -447,8 +940,180 @@ describe("IdentityClient", () => {
447
940
 
448
941
  await client.identify();
449
942
 
450
- // Should not receive any state changes after shutdown
451
943
  expect(states).toHaveLength(0);
452
944
  });
945
+
946
+ test("removes multiple listeners", async () => {
947
+ const { client } = createTestClient();
948
+
949
+ const states1: IdentifyState[] = [];
950
+ const states2: IdentifyState[] = [];
951
+
952
+ client.onIdentifyStateChange((state) => states1.push(state));
953
+ client.onIdentifyStateChange((state) => states2.push(state));
954
+
955
+ client.shutdown();
956
+
957
+ await client.identify();
958
+
959
+ expect(states1).toHaveLength(0);
960
+ expect(states2).toHaveLength(0);
961
+ });
962
+
963
+ test("client still functions after shutdown", async () => {
964
+ const { client } = createTestClient();
965
+
966
+ client.shutdown();
967
+
968
+ const result = await client.identify();
969
+
970
+ expect(result.success).toBe(true);
971
+ expect(client.getIdentifyState().type).toBe("identified");
972
+ });
973
+
974
+ test("can add new listeners after shutdown", async () => {
975
+ const { client } = createTestClient();
976
+
977
+ client.shutdown();
978
+
979
+ const states: IdentifyState[] = [];
980
+ client.onIdentifyStateChange((state) => states.push(state));
981
+
982
+ await client.identify();
983
+
984
+ expect(states.length).toBeGreaterThan(0);
985
+ });
986
+ });
987
+
988
+ describe("storage persistence", () => {
989
+ test("persists identifying state during identify", async () => {
990
+ const mockStorage = createMockStorageClient();
991
+ const mockApi = createMockApiClient();
992
+
993
+ // Make API slow so we can check intermediate state
994
+ let resolveApi: (value: unknown) => void;
995
+ const apiPromise = new Promise((resolve) => { resolveApi = resolve; });
996
+
997
+ mockApi.client = async () => {
998
+ await apiPromise;
999
+ return {
1000
+ error: null,
1001
+ data: {
1002
+ data: {
1003
+ session_id: "session-123",
1004
+ device_id: "device-123",
1005
+ persona_id: "persona-123",
1006
+ token: "token-123",
1007
+ version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
1008
+ },
1009
+ },
1010
+ };
1011
+ };
1012
+
1013
+ const { client } = createTestClient({ storage: mockStorage, api: mockApi });
1014
+
1015
+ const identifyPromise = client.identify();
1016
+
1017
+ // Check storage while API call is pending
1018
+ await new Promise(resolve => setTimeout(resolve, 10));
1019
+ const intermediateStored = mockStorage.getStorage().get(IDENTIFY_STORAGE_KEY);
1020
+ expect(JSON.parse(intermediateStored!).type).toBe("identifying");
1021
+
1022
+ // Complete API call
1023
+ resolveApi!(null);
1024
+ await identifyPromise;
1025
+
1026
+ const finalStored = mockStorage.getStorage().get(IDENTIFY_STORAGE_KEY);
1027
+ expect(JSON.parse(finalStored!).type).toBe("identified");
1028
+ });
1029
+
1030
+ test("storage survives client recreation", async () => {
1031
+ const mockStorage = createMockStorageClient();
1032
+
1033
+ // Create first client and identify
1034
+ const { client: client1 } = createTestClient({ storage: mockStorage });
1035
+ await client1.identify();
1036
+
1037
+ // Create second client with same storage
1038
+ const { client: client2 } = createTestClient({ storage: mockStorage });
1039
+ loadStateFromStorage(client2);
1040
+
1041
+ // Should restore identified state
1042
+ expect(client2.getIdentifyState().type).toBe("identified");
1043
+ expect(client2.getSessionState()?.session_id).toBe("session-123");
1044
+ });
1045
+ });
1046
+
1047
+ describe("concurrent operations", () => {
1048
+ test("handles rapid successive identify calls", async () => {
1049
+ const { client } = createTestClient();
1050
+
1051
+ const results = await Promise.all([
1052
+ client.identify(),
1053
+ client.identify(),
1054
+ client.identify(),
1055
+ ]);
1056
+
1057
+ // All should succeed
1058
+ results.forEach(result => {
1059
+ expect(result.success).toBe(true);
1060
+ });
1061
+
1062
+ expect(client.getIdentifyState().type).toBe("identified");
1063
+ });
1064
+
1065
+ test("handles identify during reset", async () => {
1066
+ const { client } = createTestClient();
1067
+
1068
+ await client.identify();
1069
+
1070
+ // Start identify and immediately reset
1071
+ const identifyPromise = client.identify();
1072
+ client.reset();
1073
+
1074
+ await identifyPromise;
1075
+
1076
+ // Final state depends on timing, but should be valid
1077
+ const state = client.getIdentifyState();
1078
+ expect(["unidentified", "identified"]).toContain(state.type);
1079
+ });
1080
+ });
1081
+
1082
+ describe("edge cases", () => {
1083
+ test("handles empty persona object", async () => {
1084
+ const { client, mockApi } = createTestClient();
1085
+
1086
+ await client.identify({});
1087
+
1088
+ const lastCall = mockApi.getLastCall();
1089
+ expect(lastCall.config.body.persona).toEqual({});
1090
+ });
1091
+
1092
+ test("handles very long session tokens", async () => {
1093
+ const longToken = "t".repeat(10000);
1094
+ const mockApi = createMockApiClient({ token: longToken });
1095
+ const { client } = createTestClient({ api: mockApi });
1096
+
1097
+ const result = await client.identify();
1098
+
1099
+ expect(result.success).toBe(true);
1100
+ if (result.success) {
1101
+ expect(result.data.token).toBe(longToken);
1102
+ }
1103
+ });
1104
+
1105
+ test("handles special characters in session data", async () => {
1106
+ const mockApi = createMockApiClient({
1107
+ sessionId: "session-with-émojis-🚀-and-üñíçödé",
1108
+ });
1109
+ const { client, mockStorage } = createTestClient({ api: mockApi });
1110
+
1111
+ await client.identify();
1112
+
1113
+ // Verify it can be stored and retrieved
1114
+ const stored = mockStorage.getStorage().get(IDENTIFY_STORAGE_KEY);
1115
+ const parsed = JSON.parse(stored!);
1116
+ expect(parsed.session.session_id).toBe("session-with-émojis-🚀-and-üñíçödé");
1117
+ });
453
1118
  });
454
- });
1119
+ });