65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
/**
|
|
* Prototype-chain hardening for the TTS/ASR provider lookups.
|
|
*
|
|
* `TTS_PROVIDERS` / `ASR_PROVIDERS` are plain object literals, so an `in`
|
|
* check (or a bare index) also matches inherited Object.prototype keys —
|
|
* exactly the junk a persisted document or imported manifest can carry in its
|
|
* open-string providerId. The guards must use own-property semantics.
|
|
*/
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
getASRProvider,
|
|
getTTSProvider,
|
|
isKnownTTSProviderId,
|
|
TTS_PROVIDERS,
|
|
} from '@/lib/audio/constants';
|
|
import { findVoiceDisplayName } from '@/lib/audio/voice-resolver';
|
|
import type { ASRProviderId, TTSProviderId } from '@/lib/audio/types';
|
|
|
|
const PROTOTYPE_KEYS = ['toString', 'constructor', 'valueOf', 'hasOwnProperty'];
|
|
|
|
describe('isKnownTTSProviderId', () => {
|
|
it('accepts every registered built-in provider and the custom namespace', () => {
|
|
for (const id of Object.keys(TTS_PROVIDERS)) {
|
|
expect(isKnownTTSProviderId(id)).toBe(true);
|
|
}
|
|
expect(isKnownTTSProviderId('custom-tts-mine')).toBe(true);
|
|
});
|
|
|
|
it('rejects unknown ids', () => {
|
|
expect(isKnownTTSProviderId('definitely-not-a-tts-provider')).toBe(false);
|
|
expect(isKnownTTSProviderId('')).toBe(false);
|
|
});
|
|
|
|
it('rejects prototype-chain keys', () => {
|
|
for (const junk of PROTOTYPE_KEYS) {
|
|
expect(isKnownTTSProviderId(junk)).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('getTTSProvider', () => {
|
|
it('never resolves a prototype-chain key to an Object.prototype member', () => {
|
|
for (const junk of PROTOTYPE_KEYS) {
|
|
expect(getTTSProvider(junk as TTSProviderId)).toBeUndefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('getASRProvider', () => {
|
|
it('never resolves a prototype-chain key to an Object.prototype member', () => {
|
|
for (const junk of PROTOTYPE_KEYS) {
|
|
expect(getASRProvider(junk as ASRProviderId)).toBeUndefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('findVoiceDisplayName', () => {
|
|
it('falls back to the raw voice id for a prototype-chain provider key instead of crashing', () => {
|
|
for (const junk of PROTOTYPE_KEYS) {
|
|
expect(findVoiceDisplayName(junk as TTSProviderId, 'voice-1')).toBe('voice-1');
|
|
}
|
|
});
|
|
});
|