import { beforeAll, describe, expect, it, vi } from 'vitest'; import { getCryptoBackend, setCryptoBackend } from '../crypto/backend'; import { makeWasmTestBackend } from '../crypto/testBackend'; import { decryptSoundEnvelope, encryptSoundBlob, listOwnSounds, upsertSound, } from './soundboards'; beforeAll(async () => { setCryptoBackend(await makeWasmTestBackend()); }); function makeClient(opts: { user?: { id: string } | null; selectData?: unknown[]; upsertReturn?: { data: unknown; error: unknown }; }): any { const order = vi.fn().mockResolvedValue({ data: opts.selectData ?? [], error: null }); const eq = vi.fn().mockReturnValue({ order }); const selectChain = vi.fn().mockReturnValue({ eq }); const single = vi.fn().mockResolvedValue(opts.upsertReturn ?? { data: {}, error: null }); const upsertSelect = vi.fn().mockReturnValue({ single }); const upsertChain = vi.fn().mockReturnValue({ select: upsertSelect }); const from = vi.fn().mockReturnValue({ select: selectChain, upsert: upsertChain, }); return { auth: { getUser: vi.fn().mockResolvedValue({ data: { user: opts.user ?? { id: 'u-1' } } }) }, from, }; } describe('encryptSoundBlob ↔ decryptSoundEnvelope', () => { it('round-trips bytes via sealed-to-self crypto_box', async () => { const backend = getCryptoBackend(); const seed = backend.randomBytes(32); const pub = backend.scalarMultBase(seed); const blob = new Blob([new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])]); const envelope = await encryptSoundBlob(blob, pub, seed); expect(envelope.length).toBeGreaterThan(24); const plain = await decryptSoundEnvelope(envelope, pub, seed); expect(Array.from(plain)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); }); it('rejects an envelope that is too short', async () => { const backend = getCryptoBackend(); const seed = backend.randomBytes(32); const pub = backend.scalarMultBase(seed); await expect( decryptSoundEnvelope(new Uint8Array(20), pub, seed), ).rejects.toThrow(/too_short/); }); }); describe('listOwnSounds', () => { it('maps DB rows to camelCase', async () => { const client = makeClient({ selectData: [ { id: 's-1', user_id: 'u-1', name: 'horn', mime: 'audio/mpeg', size: 1234, category: 'fx', hotkey: 'F1', gain: 0.8, sort_order: 0, storage_path: 'u-1/s-1.bin', created_at: '2026-05-16T00:00:00Z', updated_at: '2026-05-16T00:00:00Z', }, ], }); const out = await listOwnSounds(client); expect(out[0]).toEqual({ id: 's-1', userId: 'u-1', name: 'horn', mime: 'audio/mpeg', size: 1234, category: 'fx', hotkey: 'F1', gain: 0.8, sortOrder: 0, storagePath: 'u-1/s-1.bin', createdAt: '2026-05-16T00:00:00Z', updatedAt: '2026-05-16T00:00:00Z', }); }); }); describe('upsertSound', () => { it('returns mapped RemoteSound after upsert', async () => { const upsertReturn = { data: { id: 's-1', user_id: 'u-1', name: 'horn', mime: 'audio/mpeg', size: 1234, category: null, hotkey: null, gain: 1, sort_order: 0, storage_path: 'u-1/s-1.bin', created_at: '2026-05-16T00:00:00Z', updated_at: '2026-05-16T00:00:00Z', }, error: null, }; const client = makeClient({ upsertReturn }); const out = await upsertSound(client, { id: 's-1', name: 'horn', mime: 'audio/mpeg', size: 1234, category: null, hotkey: null, gain: 1, sortOrder: 0, storagePath: 'u-1/s-1.bin', updatedAtIso: '2026-05-16T00:00:00Z', }); expect(out.id).toBe('s-1'); expect(out.userId).toBe('u-1'); }); });