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(); }); });