Open History to see the last commit
1import { describe, expect, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import { beforeEach, vi } from "vite-plus/test";
4
5const { values, getItemAsync, setItemAsync, uuidv4 } = vi.hoisted(() => {
6 const values = new Map<string, string>();
7 return {
8 values,
9 getItemAsync: vi.fn(async (key: string) => {
10 const value = values.get(key) ?? null;
11 await Promise.resolve();
12 return value;
13 }),
14 setItemAsync: vi.fn(async (key: string, value: string) => {
15 values.set(key, value);
16 }),
17 uuidv4: vi.fn(),
18 };
19});
20
21vi.mock("expo-secure-store", () => ({ getItemAsync, setItemAsync }));
22vi.mock("expo-crypto", () => ({ randomUUID: uuidv4 }));
23
24import "../lib/uuid";
25
26import * as MobileSecureStorage from "./mobile-secure-storage";
27import * as MobileStorage from "./mobile-storage";
28
29const deviceIdKey = "t3code.agent-awareness.device-id";
30const makeStorage = MobileStorage.make().pipe(Effect.provide(MobileSecureStorage.layer));
31
32beforeEach(() => {
33 values.clear();
34 getItemAsync.mockClear();
35 setItemAsync.mockClear();
36 uuidv4.mockReset();
37 let generated = 0;
38 uuidv4.mockImplementation(() => `generated-device-${++generated}`);
39});
40
41describe("mobile device identity", () => {
42 it.effect("concurrent first-use callers share one persisted device identity", () =>
43 Effect.gen(function* () {
44 const storage = yield* makeStorage;
45 const deviceIds = yield* Effect.all(
46 [storage.loadOrCreateAgentAwarenessDeviceId, storage.loadOrCreateAgentAwarenessDeviceId],
47 { concurrency: "unbounded" },
48 );
49
50 expect(deviceIds).toEqual(["generated-device-1", "generated-device-1"]);
51 expect(yield* storage.loadAgentAwarenessDeviceId).toBe(deviceIds[0]);
52 expect(values.get(deviceIdKey)).toBe(deviceIds[0]);
53 expect(uuidv4).toHaveBeenCalledTimes(1);
54 expect(setItemAsync).toHaveBeenCalledTimes(1);
55 }),
56 );
57
58 // regression: #18837 serialize initial device identity creation
59 it.effect("reuses an existing device identity without generating or writing", () =>
60 Effect.gen(function* () {
61 values.set(deviceIdKey, "existing-device");
62 const storage = yield* makeStorage;
63
64 expect(yield* storage.loadOrCreateAgentAwarenessDeviceId).toBe("existing-device");
65 expect(uuidv4).not.toHaveBeenCalled();
66 expect(setItemAsync).not.toHaveBeenCalled();
67 }),
68 );
69
70 it.effect("retries a failed first write without retaining an unpersisted identity", () =>
71 Effect.gen(function* () {
72 setItemAsync.mockRejectedValueOnce(new Error("Secure storage unavailable"));
73 const storage = yield* makeStorage;
74 const first = yield* Effect.result(storage.loadOrCreateAgentAwarenessDeviceId);
75
76 expect(first).toMatchObject({
77 _tag: "Failure",
78 failure: { _tag: "MobileSecureStorageError", operation: "write", key: deviceIdKey },
79 });
80 expect(yield* storage.loadAgentAwarenessDeviceId).toBeNull();
81 expect(yield* storage.loadOrCreateAgentAwarenessDeviceId).toBe("generated-device-2");
82 expect(yield* storage.loadAgentAwarenessDeviceId).toBe("generated-device-2");
83 expect(uuidv4).toHaveBeenCalledTimes(2);
84 }),
85 );
86});