135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
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');
|
|
});
|
|
});
|