import { crypto } from '@chat-app/shared'; import { makeWasmTestBackend } from '@chat-app/shared/crypto/testBackend'; import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; // We mock react-native modules so this Vitest file can run in Node without // loading native code. The mocks live next to the test for clarity. vi.mock('expo-secure-store', () => { const store = new Map(); return { getItemAsync: vi.fn(async (k: string) => store.get(k) ?? null), setItemAsync: vi.fn(async (k: string, v: string) => { store.set(k, v); }), deleteItemAsync: vi.fn(async (k: string) => { store.delete(k); }), }; }); const rpcImpl = vi.fn(); vi.mock('./supabase', () => ({ supabase: { rpc: (name: string, params: unknown) => rpcImpl(name, params), from: () => ({ select: () => ({ in: () => Promise.resolve({ data: [], error: null }) }), }), }, })); beforeAll(async () => { crypto.setCryptoBackend(await makeWasmTestBackend()); }); beforeEach(() => { rpcImpl.mockReset(); }); describe('mobile userIdentity', () => { it('setupNewUserIdentity uploads + caches', async () => { rpcImpl.mockResolvedValue({ data: 0, error: null }); const { setupNewUserIdentity, cachedUserKey } = await import('./userIdentity'); const out = await setupNewUserIdentity({ userId: 'user-1', pin: '123456', withRecovery: true, }); expect(out.publicKey.length).toBe(32); expect(out.recoveryCode).toMatch(/^[A-Z0-9-]+$/); const cached = await cachedUserKey('user-1'); expect(cached).not.toBeNull(); expect(cached!.length).toBe(32); expect(rpcImpl).toHaveBeenCalledWith('upsert_user_key', expect.any(Object)); }); it('loadOrUnlockUserKey returns `missing` when blob does not exist', async () => { rpcImpl.mockResolvedValue({ data: { exists: false }, error: null }); const { loadOrUnlockUserKey } = await import('./userIdentity'); const out = await loadOrUnlockUserKey({ userId: 'user-2', pin: '000000' }); expect(out.kind).toBe('missing'); }); });