@rolme/ytscript
Version:
A CLI tool to download YouTube transcripts and generate summaries
184 lines (183 loc) • 7.35 kB
JavaScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { getTranscript } from '../../../../services/transcript/index.js';
import { TranscriptError } from '../../../../errors.js';
// Mock ytdl-core
vi.mock('ytdl-core', () => ({
default: {
getInfo: vi.fn(),
getURLVideoID: vi.fn()
}
}));
describe('Transcript Service', () => {
const mockTranscriptData = [
{ text: 'First line', duration: 1, offset: 0 },
{ text: 'Second line', duration: 1, offset: 1 }
];
const mockCaptionTrack = {
baseUrl: 'https://example.com/captions',
languageCode: 'en',
name: { simpleText: 'English' },
vssId: '.en',
isTranslatable: true
};
const mockVideoInfo = {
videoDetails: {
videoId: 'dQw4w9WgXcQ',
title: 'Test Video',
lengthSeconds: '123',
keywords: ['test'],
channelId: 'test-channel',
isOwnerViewing: false,
isCrawlable: true,
isPrivate: false,
isUnpluggedCorpus: false,
isLiveContent: false,
author: 'Test Channel',
thumbnails: [],
description: 'Test video description',
viewCount: '0',
category: 'Test',
publishDate: '2024-01-01',
uploadDate: '2024-01-01',
video_url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
storyboards: [],
chapters: [],
likes: null,
dislikes: null,
age_restricted: false,
channel_url: 'https://youtube.com/c/testchannel',
embed: {
iframeUrl: '',
flashUrl: '',
width: 0,
height: 0,
flashSecureUrl: ''
},
isLive: false,
isUpcoming: false
},
player_response: {
captions: {
playerCaptionsTracklistRenderer: {
captionTracks: [mockCaptionTrack]
}
}
}
};
beforeEach(() => {
vi.clearAllMocks();
global.fetch = vi.fn();
});
describe('getTranscript', () => {
describe('successful cases', () => {
beforeEach(() => {
global.fetch.mockResolvedValue({
ok: true,
text: () => Promise.resolve(`<transcript>
<text start="0" dur="1">First line</text>
<text start="1" dur="1">Second line</text>
</transcript>`)
});
});
it('should fetch and format transcript correctly', async () => {
const result = await getTranscript(mockVideoInfo);
expect(result.transcript).toBe('First line\nSecond line');
expect(result.segments).toEqual(mockTranscriptData);
expect(result.videoId).toBe('dQw4w9WgXcQ');
expect(global.fetch).toHaveBeenCalledWith(mockCaptionTrack.baseUrl);
});
it('should handle language option', async () => {
const mockSpanishTrack = {
...mockCaptionTrack,
languageCode: 'es',
name: { simpleText: 'Spanish' },
vssId: '.es'
};
const mockSpanishVideoInfo = {
...mockVideoInfo,
player_response: {
captions: {
playerCaptionsTracklistRenderer: {
captionTracks: [mockSpanishTrack, mockCaptionTrack]
}
}
}
};
const result = await getTranscript(mockSpanishVideoInfo, { lang: 'es' });
expect(result.videoId).toBe('dQw4w9WgXcQ');
expect(global.fetch).toHaveBeenCalledWith(mockSpanishTrack.baseUrl);
});
it('should default to English if no language specified', async () => {
const mockMultiLangVideoInfo = {
...mockVideoInfo,
player_response: {
captions: {
playerCaptionsTracklistRenderer: {
captionTracks: [
{ ...mockCaptionTrack, languageCode: 'es' },
mockCaptionTrack,
{ ...mockCaptionTrack, languageCode: 'fr' }
]
}
}
}
};
const result = await getTranscript(mockMultiLangVideoInfo);
expect(result.videoId).toBe('dQw4w9WgXcQ');
expect(global.fetch).toHaveBeenCalledWith(mockCaptionTrack.baseUrl);
});
});
describe('error cases', () => {
it('should handle missing captions object', async () => {
const mockVideoInfoNoCaptions = {
...mockVideoInfo,
player_response: {
captions: null
}
};
await expect(getTranscript(mockVideoInfoNoCaptions))
.rejects.toThrow('No captions available for this video');
});
it('should handle missing caption tracks', async () => {
const mockVideoInfoNoTracks = {
...mockVideoInfo,
player_response: {
captions: {
playerCaptionsTracklistRenderer: {
captionTracks: []
}
}
}
};
await expect(getTranscript(mockVideoInfoNoTracks))
.rejects.toThrow('No caption tracks found');
});
it('should handle unavailable language', async () => {
await expect(getTranscript(mockVideoInfo, { lang: 'fr' }))
.rejects.toThrow('No captions found for language: fr');
});
it('should handle fetch errors', async () => {
global.fetch.mockRejectedValue(new Error('Network Error'));
await expect(getTranscript(mockVideoInfo))
.rejects.toThrow(TranscriptError);
});
it('should handle non-OK fetch response', async () => {
global.fetch.mockResolvedValue({
ok: false,
statusText: 'Not Found'
});
await expect(getTranscript(mockVideoInfo))
.rejects.toThrow('Failed to download caption track: Not Found');
});
it('should handle invalid XML response', async () => {
global.fetch.mockResolvedValue({
ok: true,
text: () => Promise.resolve('Invalid XML')
});
const result = await getTranscript(mockVideoInfo);
expect(result.segments).toEqual([]);
expect(result.transcript).toBe('');
});
});
});
});