83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
// db is browser-only (Dexie); stub it so the client module loads in node.
|
|
vi.mock('@/lib/utils/database', () => ({
|
|
db: {
|
|
autoVoiceCache: {
|
|
get: vi.fn(async () => undefined),
|
|
put: vi.fn(async () => undefined),
|
|
},
|
|
},
|
|
}));
|
|
|
|
import { ensureRegisteredVoice } from '@/lib/audio/voice-registration-client';
|
|
|
|
function okFetch() {
|
|
const f = vi.fn(
|
|
async () => new Response(JSON.stringify({ voiceId: 'x', registered: true }), { status: 200 }),
|
|
);
|
|
vi.stubGlobal('fetch', f);
|
|
return f;
|
|
}
|
|
|
|
describe('ensureRegisteredVoice memoization', () => {
|
|
beforeEach(() => vi.unstubAllGlobals());
|
|
|
|
it('re-registers when the backend base URL changes (memo keyed by backend, not voiceId alone)', async () => {
|
|
const f = okFetch();
|
|
// Distinct descriptor per test so the module-level memo from other tests can't collide.
|
|
const voiceDesign = { identity: 'backend-switch teacher', texture: 'warm', delivery: 'calm' };
|
|
|
|
await ensureRegisteredVoice('voxcpm-tts', { voiceDesign }, { ttsBaseUrl: 'https://a.test/v1' });
|
|
// Same backend again → memoized, no second round-trip.
|
|
await ensureRegisteredVoice('voxcpm-tts', { voiceDesign }, { ttsBaseUrl: 'https://a.test/v1' });
|
|
expect(f).toHaveBeenCalledTimes(1);
|
|
|
|
// Different backend → must NOT be skipped by the memo; re-register there.
|
|
await ensureRegisteredVoice('voxcpm-tts', { voiceDesign }, { ttsBaseUrl: 'https://b.test/v1' });
|
|
expect(f).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('coalesces concurrent calls for the same (voiceId, backend) into one request', async () => {
|
|
const f = okFetch();
|
|
const voiceDesign = { identity: 'concurrent teacher', texture: 'warm', delivery: 'calm' };
|
|
const req = { ttsBaseUrl: 'https://c.test/v1' };
|
|
|
|
await Promise.all([
|
|
ensureRegisteredVoice('voxcpm-tts', { voiceDesign }, req),
|
|
ensureRegisteredVoice('voxcpm-tts', { voiceDesign }, req),
|
|
ensureRegisteredVoice('voxcpm-tts', { voiceDesign }, req),
|
|
]);
|
|
expect(f).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('re-registers when the API key changes on the same base URL (auth-scoped)', async () => {
|
|
const f = okFetch();
|
|
const voiceDesign = {
|
|
identity: 'credential-switch teacher',
|
|
texture: 'warm',
|
|
delivery: 'calm',
|
|
};
|
|
const base = 'https://d.test/v1';
|
|
|
|
await ensureRegisteredVoice(
|
|
'voxcpm-tts',
|
|
{ voiceDesign },
|
|
{ ttsBaseUrl: base, ttsApiKey: 'k1' },
|
|
);
|
|
await ensureRegisteredVoice(
|
|
'voxcpm-tts',
|
|
{ voiceDesign },
|
|
{ ttsBaseUrl: base, ttsApiKey: 'k1' },
|
|
);
|
|
expect(f).toHaveBeenCalledTimes(1); // same creds → memoized
|
|
|
|
await ensureRegisteredVoice(
|
|
'voxcpm-tts',
|
|
{ voiceDesign },
|
|
{ ttsBaseUrl: base, ttsApiKey: 'k2' },
|
|
);
|
|
expect(f).toHaveBeenCalledTimes(2); // different creds → re-validate
|
|
});
|
|
});
|