D5: Add VTT/caption fallback tests inside describe('downloadYouTube', ...)
- Add test: fetchTranscript rejects → AudioResult (yt-dlp audio download)
- Add test: empty transcript array → AudioResult (same fallback path)
- Remove separate describe('empty captions fallback (D5)', ...) block
- Tests inside existing describe('downloadYouTube', ...) block per spec
- No vi.mock('fs', ...) — uses dynamic require('fs') in mock impl
- No downloader.ts changes needed (tryGetCaptions already returns null
for both reject and empty-segments cases)
This commit is contained in:
@@ -136,10 +136,33 @@ describe('downloadYouTube', () => {
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to audio download when fetchTranscript rejects', async () => {
|
||||
mockFetchTranscript.mockRejectedValue(new Error('Transcript not available'));
|
||||
mockExecFile.mockImplementation(function ytdlpMock(...args: unknown[]) {
|
||||
const cmd = args[0] as string;
|
||||
const cmdArgs = args[1] as string[];
|
||||
const cb = args.at(-1) as (...callbackArgs: unknown[]) => void;
|
||||
if (cmd === 'yt-dlp' && cmdArgs.includes('-f')) {
|
||||
const oIdx = cmdArgs.indexOf('-o');
|
||||
const outPattern = oIdx !== -1 ? cmdArgs[oIdx + 1] : '';
|
||||
const outDir = outPattern ? outPattern.substring(0, outPattern.lastIndexOf('/')) : '';
|
||||
if (outDir) {
|
||||
const { mkdirSync, writeFileSync } = require('fs');
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
writeFileSync(require('path').join(outDir, 'audio.mp4'), 'fake-audio');
|
||||
writeFileSync(require('path').join(outDir, 'audio.info.json'), JSON.stringify({ title: 'Fetch Rejected Video' }));
|
||||
}
|
||||
}
|
||||
cb(null, JSON.stringify({ title: 'Fetch Rejected Video' }), '');
|
||||
});
|
||||
const result = await downloadYouTube('https://youtube.com/watch?v=rejected', 'test-reject-1');
|
||||
expect(mockFetchTranscript).toHaveBeenCalledWith('https://youtube.com/watch?v=rejected', { lang: 'en' });
|
||||
expect(result.type).toBe('audio');
|
||||
expect(result.title).toBe('Fetch Rejected Video');
|
||||
});
|
||||
|
||||
describe('empty captions fallback (D5)', () => {
|
||||
it('falls back to audio when fetchTranscript returns empty segments array', async () => {
|
||||
it('falls back to audio download when fetchTranscript returns empty transcript array', async () => {
|
||||
mockFetchTranscript.mockResolvedValue([] satisfies TranscriptResponse[]);
|
||||
mockExecFile.mockImplementation(function ytdlpMock(...args: unknown[]) {
|
||||
const cmd = args[0] as string;
|
||||
@@ -153,55 +176,16 @@ describe('empty captions fallback (D5)', () => {
|
||||
const { mkdirSync, writeFileSync } = require('fs');
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
writeFileSync(require('path').join(outDir, 'audio.mp4'), 'fake-audio');
|
||||
writeFileSync(require('path').join(outDir, 'audio.info.json'), JSON.stringify({ title: 'Empty Captions Video' }));
|
||||
writeFileSync(require('path').join(outDir, 'audio.info.json'), JSON.stringify({ title: 'Empty Transcript Video' }));
|
||||
}
|
||||
}
|
||||
cb(null, JSON.stringify({ title: 'Empty Captions Video' }), '');
|
||||
cb(null, JSON.stringify({ title: 'Empty Transcript Video' }), '');
|
||||
});
|
||||
const result = await downloadYouTube('https://youtube.com/watch?v=empty', 'test-empty-1');
|
||||
expect(mockFetchTranscript).toHaveBeenCalledWith('https://youtube.com/watch?v=empty', { lang: 'en' });
|
||||
const result = await downloadYouTube('https://youtube.com/watch?v=empty-array', 'test-empty-array');
|
||||
expect(mockFetchTranscript).toHaveBeenCalledWith('https://youtube.com/watch?v=empty-array', { lang: 'en' });
|
||||
expect(result.type).toBe('audio');
|
||||
expect(result.title).toBe('Empty Captions Video');
|
||||
expect(result.title).toBe('Empty Transcript Video');
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to audio when all transcript entries are whitespace', async () => {
|
||||
mockFetchTranscript.mockResolvedValue([
|
||||
{ text: ' ', offset: 1000, duration: 2000, lang: 'en' },
|
||||
{ text: ' ', offset: 3000, duration: 1000, lang: 'en' }
|
||||
] satisfies TranscriptResponse[]);
|
||||
mockExecFile.mockImplementation(function ytdlpMock(...args: unknown[]) {
|
||||
const cmd = args[0] as string;
|
||||
const cmdArgs = args[1] as string[];
|
||||
const cb = args.at(-1) as (...callbackArgs: unknown[]) => void;
|
||||
if (cmd === 'yt-dlp' && cmdArgs.includes('-f')) {
|
||||
const oIdx = cmdArgs.indexOf('-o');
|
||||
const outPattern = oIdx !== -1 ? cmdArgs[oIdx + 1] : '';
|
||||
const outDir = outPattern ? outPattern.substring(0, outPattern.lastIndexOf('/')) : '';
|
||||
if (outDir) {
|
||||
const { mkdirSync, writeFileSync } = require('fs');
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
writeFileSync(require('path').join(outDir, 'audio.mp4'), 'fake-audio');
|
||||
writeFileSync(require('path').join(outDir, 'audio.info.json'), JSON.stringify({ title: 'Whitespace Only' }));
|
||||
}
|
||||
}
|
||||
cb(null, JSON.stringify({ title: 'Whitespace Only' }), '');
|
||||
});
|
||||
const result = await downloadYouTube('https://youtube.com/watch?v=ws-only', 'test-empty-2');
|
||||
expect(result.type).toBe('audio');
|
||||
});
|
||||
|
||||
it('still uses caption fast path when fetchTranscript returns non-empty segments', async () => {
|
||||
mockFetchTranscript.mockResolvedValue([
|
||||
{ text: 'Real caption', offset: 1000, duration: 2000, lang: 'en' }
|
||||
] satisfies TranscriptResponse[]);
|
||||
mockExecFile.mockImplementation((...args: unknown[]) => {
|
||||
const cb = args.at(-1) as (...callbackArgs: unknown[]) => void;
|
||||
cb(null, JSON.stringify({ title: 'Real Caption Video' }), '');
|
||||
});
|
||||
|
||||
const result = await downloadYouTube('https://youtube.com/watch?v=has-captions', 'test-has-cap');
|
||||
|
||||
expect(result.type).toBe('captions');
|
||||
expect((result as { type: 'captions'; segments: unknown[] }).segments.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user