import { describe, expect, it } from 'vitest';
import {
decodeSubtitleBuffer,
getSubtitleExtension,
MAX_SUBTITLE_CUES,
normalizeSubtitleFile,
normalizeSubtitleLanguage,
parseSubtitleCues,
sanitizeSubtitleLabel,
SAFE_SUBTITLE_PROXY_PATH,
serializeWebVtt,
subtitleProxyPathToObjectKey,
} from '@/lib/subtitle-validation';
const UUID = '11111111-2222-3333-4444-555555555555';
function encode(text: string): Uint8Array {
return new TextEncoder().encode(text);
}
describe('getSubtitleExtension', () => {
it('accepts the two subtitle formats and nothing else', () => {
expect(getSubtitleExtension('cut.srt')).toBe('srt');
expect(getSubtitleExtension('cut.VTT')).toBe('vtt');
expect(getSubtitleExtension('cut.ass')).toBeNull();
expect(getSubtitleExtension('cut.srt.exe')).toBeNull();
});
});
describe('normalizeSubtitleLanguage', () => {
it('lowercases so a re-upload replaces the track it means to', () => {
expect(normalizeSubtitleLanguage('TR')).toBe('tr');
expect(normalizeSubtitleLanguage(' en-US ')).toBe('en-us');
expect(normalizeSubtitleLanguage('zh-Hant-TW')).toBe('zh-hant-tw');
});
it('rejects anything that is not a language tag', () => {
expect(normalizeSubtitleLanguage('')).toBeNull();
expect(normalizeSubtitleLanguage('t')).toBeNull();
expect(normalizeSubtitleLanguage('tr; drop table')).toBeNull();
expect(normalizeSubtitleLanguage('{\\an8}', ''].join(
'\n'
)
);
expect(cues[0].text).toBe('tiltalert(1)');
});
it('neutralises an arrow in cue text so the file cannot be re-split', () => {
const cues = parseSubtitleCues(['00:00:01,000 --> 00:00:02,000', 'a --> b', ''].join('\n'));
expect(cues[0].text).toBe('a --> b');
expect(parseSubtitleCues(serializeWebVtt(cues))).toHaveLength(1);
});
it('stops at the cue ceiling', () => {
const lines: string[] = [];
for (let index = 0; index < MAX_SUBTITLE_CUES + 10; index += 1) {
lines.push(`00:00:0${index % 9}.000 --> 00:00:0${(index % 9) + 1}.000`, `line ${index}`, '');
}
expect(parseSubtitleCues(lines.join('\n'))).toHaveLength(MAX_SUBTITLE_CUES);
});
});
describe('normalizeSubtitleFile', () => {
it('converts SRT to a canonical WebVTT document', () => {
const result = normalizeSubtitleFile(
encode('1\r\n00:00:01,500 --> 00:00:02,000\r\nMerhaba\r\n\r\n')
);
expect(result).toEqual({
ok: true,
cueCount: 1,
vtt: 'WEBVTT\n\n00:00:01.500 --> 00:00:02.000\nMerhaba\n',
});
});
it('refuses an empty file', () => {
const result = normalizeSubtitleFile(new Uint8Array());
expect(result.ok).toBe(false);
});
it('refuses a file with no cues rather than storing an empty track', () => {
const result = normalizeSubtitleFile(encode('this is just prose\nand more prose\n'));
expect(result).toEqual({
ok: false,
error: 'No subtitle cues found. Upload a valid .srt or .vtt file.',
});
});
});
describe('subtitle proxy paths', () => {
it('only recognises a uuid .vtt path', () => {
expect(SAFE_SUBTITLE_PROXY_PATH.test(`/api/upload/subtitle/${UUID}.vtt`)).toBe(true);
expect(SAFE_SUBTITLE_PROXY_PATH.test(`/api/upload/subtitle/${UUID}.srt`)).toBe(false);
expect(SAFE_SUBTITLE_PROXY_PATH.test('/api/upload/subtitle/../../etc/passwd')).toBe(false);
});
it('maps a proxy path to its object key and refuses anything else', () => {
expect(subtitleProxyPathToObjectKey(`/api/upload/subtitle/${UUID}.vtt`)).toBe(
`subtitles/${UUID}.vtt`
);
expect(subtitleProxyPathToObjectKey(`/api/upload/image/${UUID}.png`)).toBeNull();
});
});