import { describe, expect, it, beforeEach, vi, beforeAll } from 'vitest'; import { setCryptoBackend } from '@chat-app/shared/crypto'; import { makeWasmTestBackend } from '@chat-app/shared/crypto/testBackend'; beforeAll(async () => { setCryptoBackend(await makeWasmTestBackend()); }); const memStore: Record = {}; const fakeStore = { getSecret: async (k: string) => memStore[k] ?? null, setSecret: async (k: string, v: Uint8Array) => { memStore[k] = v; }, removeSecret: async (k: string) => { delete memStore[k]; }, }; vi.mock('./secretStore', () => ({ devLocalSecretStore: fakeStore, setSecretStoreUser: vi.fn(), isEncryptedVaultActive: () => false, })); vi.mock('./supabase', () => { const rpcResponses = new Map(); const rpc = vi.fn((name: string, _params: unknown) => Promise.resolve(rpcResponses.get(name) ?? { data: null, error: null }), ); const supabase = { auth: { getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'me' } }, error: null }) }, rpc, from: vi.fn(() => ({ select: () => ({ in: () => Promise.resolve({ data: [], error: null }) }), })), }; return { supabase, __setRpcResponse: (n: string, r: { data: unknown; error: unknown }) => rpcResponses.set(n, r) }; }); describe('userIdentity', () => { beforeEach(() => { for (const k of Object.keys(memStore)) delete memStore[k]; }); it('setupNewUserIdentity uploads blob, caches private key in store', async () => { const supabaseMod = await import('./supabase'); (supabaseMod as unknown as { __setRpcResponse: (n: string, r: { data: unknown; error: unknown }) => void }) .__setRpcResponse('reset_user_key', { data: 0, error: null }); const { setupNewUserIdentity, cachedUserKey } = await import('./userIdentity'); const result = await setupNewUserIdentity({ userId: 'me', pin: '123456', withRecovery: true }); expect(result.publicKey).toHaveLength(32); expect(result.recoveryCode).toMatch(/^[A-Z2-9]{6}-[A-Z2-9]{6}-[A-Z2-9]{6}-[A-Z2-9]{6}$/); const cached = await cachedUserKey('me'); expect(cached).toBeInstanceOf(Uint8Array); expect(cached?.length).toBe(32); }); it('loadOrUnlockUserKey unlocks with correct PIN, throws on wrong PIN', async () => { const { setupNewUserIdentity, clearUserKeyCache, loadOrUnlockUserKey } = await import('./userIdentity'); const supabaseMod = await import('./supabase'); const setRpc = (supabaseMod as unknown as { __setRpcResponse: (n: string, r: { data: unknown; error: unknown }) => void }).__setRpcResponse; setRpc('reset_user_key', { data: 0, error: null }); await setupNewUserIdentity({ userId: 'me', pin: '123456', withRecovery: false }); const rpc = (supabaseMod.supabase as unknown as { rpc: ReturnType }).rpc; const lastCall = rpc.mock.calls[rpc.mock.calls.length - 1]!; const params = lastCall[1] as Record; setRpc('try_unlock_user_key', { data: { exists: true, locked: false, sealed_private_key: params.p_sealed_private_b64, salt: params.p_salt_b64, kdf_params: params.p_kdf_params, recovery_sealed_private_key: null, recovery_salt: null, failed_attempts: 0, failed_recovery_attempts: 0, recovery_locked_until: null, key_version: 1, }, error: null, }); setRpc('record_pin_attempt', { data: { failed_attempts: 0, locked_until: null }, error: null }); await clearUserKeyCache('me'); const ok = await loadOrUnlockUserKey({ userId: 'me', pin: '123456' }); expect(ok.kind).toBe('unlocked'); await clearUserKeyCache('me'); await expect(loadOrUnlockUserKey({ userId: 'me', pin: '654321' })).rejects.toThrow(); }); });