test: Added 10 unit tests covering SponsorBlock segment removal and sub…
- "src/__tests__/download.test.ts" GSD-Task: S05/T01
This commit is contained in:
parent
f8916d2cc3
commit
f814e8d261
1 changed files with 279 additions and 0 deletions
|
|
@ -874,4 +874,283 @@ describe('DownloadService', () => {
|
|||
expect(result.filePath).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadItem — SponsorBlock arg construction', () => {
|
||||
function setupForArgs(deps: ReturnType<typeof createMockDeps>) {
|
||||
const outputPath = join(tmpDir, 'media', 'youtube', 'Test Channel', 'Test Video Title.mp4');
|
||||
mkdirSync(join(tmpDir, 'media', 'youtube', 'Test Channel'), { recursive: true });
|
||||
writeFileSync(outputPath, 'data');
|
||||
execYtDlpMock.mockResolvedValueOnce({ stdout: outputPath, stderr: '', exitCode: 0 });
|
||||
statMock.mockResolvedValueOnce({ size: 1000 });
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
it('produces --sponsorblock-remove with comma-separated categories', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
const profile: FormatProfile = {
|
||||
id: 20, name: 'SB Test', videoResolution: null, audioCodec: null, audioBitrate: null,
|
||||
containerFormat: null, isDefault: false, subtitleLanguages: null, embedSubtitles: false,
|
||||
embedChapters: false, embedThumbnail: false,
|
||||
sponsorBlockRemove: 'sponsor,selfpromo',
|
||||
outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
const sbIdx = args.indexOf('--sponsorblock-remove');
|
||||
expect(sbIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(args[sbIdx + 1]).toBe('sponsor,selfpromo');
|
||||
});
|
||||
|
||||
it('does not include --sponsorblock-remove when sponsorBlockRemove is null', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
const profile: FormatProfile = {
|
||||
id: 21, name: 'No SB', videoResolution: null, audioCodec: null, audioBitrate: null,
|
||||
containerFormat: null, isDefault: false, subtitleLanguages: null, embedSubtitles: false,
|
||||
embedChapters: false, embedThumbnail: false,
|
||||
sponsorBlockRemove: null,
|
||||
outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
expect(args).not.toContain('--sponsorblock-remove');
|
||||
});
|
||||
|
||||
it('does not include --sponsorblock-remove when value is empty/whitespace', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
const profile: FormatProfile = {
|
||||
id: 22, name: 'Empty SB', videoResolution: null, audioCodec: null, audioBitrate: null,
|
||||
containerFormat: null, isDefault: false, subtitleLanguages: null, embedSubtitles: false,
|
||||
embedChapters: false, embedThumbnail: false,
|
||||
sponsorBlockRemove: ' ',
|
||||
outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
expect(args).not.toContain('--sponsorblock-remove');
|
||||
});
|
||||
|
||||
it('handles all SponsorBlock category types', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
const allCategories = 'sponsor,selfpromo,interaction,intro,outro,preview,music_offtopic,filler';
|
||||
const profile: FormatProfile = {
|
||||
id: 23, name: 'All SB', videoResolution: null, audioCodec: null, audioBitrate: null,
|
||||
containerFormat: null, isDefault: false, subtitleLanguages: null, embedSubtitles: false,
|
||||
embedChapters: false, embedThumbnail: false,
|
||||
sponsorBlockRemove: allCategories,
|
||||
outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
const sbIdx = args.indexOf('--sponsorblock-remove');
|
||||
expect(sbIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(args[sbIdx + 1]).toBe(allCategories);
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadItem — subtitle arg construction', () => {
|
||||
function setupForArgs(deps: ReturnType<typeof createMockDeps>) {
|
||||
const outputPath = join(tmpDir, 'media', 'youtube', 'Test Channel', 'Test Video Title.mp4');
|
||||
mkdirSync(join(tmpDir, 'media', 'youtube', 'Test Channel'), { recursive: true });
|
||||
writeFileSync(outputPath, 'data');
|
||||
execYtDlpMock.mockResolvedValueOnce({ stdout: outputPath, stderr: '', exitCode: 0 });
|
||||
statMock.mockResolvedValueOnce({ size: 1000 });
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
it('produces --write-subs and --sub-langs when subtitleLanguages is set', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
const profile: FormatProfile = {
|
||||
id: 30, name: 'Sub Test', videoResolution: null, audioCodec: null, audioBitrate: null,
|
||||
containerFormat: null, isDefault: false,
|
||||
subtitleLanguages: 'en,es',
|
||||
embedSubtitles: false, embedChapters: false, embedThumbnail: false,
|
||||
sponsorBlockRemove: null, outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
expect(args).toContain('--write-subs');
|
||||
const slIdx = args.indexOf('--sub-langs');
|
||||
expect(slIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(args[slIdx + 1]).toBe('en,es');
|
||||
// embedSubtitles is false, so no --embed-subs
|
||||
expect(args).not.toContain('--embed-subs');
|
||||
});
|
||||
|
||||
it('produces --embed-subs when embedSubtitles=true AND subtitleLanguages is set', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
const profile: FormatProfile = {
|
||||
id: 31, name: 'Embed Sub', videoResolution: null, audioCodec: null, audioBitrate: null,
|
||||
containerFormat: null, isDefault: false,
|
||||
subtitleLanguages: 'en,es',
|
||||
embedSubtitles: true,
|
||||
embedChapters: false, embedThumbnail: false,
|
||||
sponsorBlockRemove: null, outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
expect(args).toContain('--write-subs');
|
||||
expect(args).toContain('--sub-langs');
|
||||
expect(args).toContain('--embed-subs');
|
||||
});
|
||||
|
||||
it('does NOT produce --embed-subs when embedSubtitles=true but subtitleLanguages is null', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
const profile: FormatProfile = {
|
||||
id: 32, name: 'No Lang', videoResolution: null, audioCodec: null, audioBitrate: null,
|
||||
containerFormat: null, isDefault: false,
|
||||
subtitleLanguages: null,
|
||||
embedSubtitles: true,
|
||||
embedChapters: false, embedThumbnail: false,
|
||||
sponsorBlockRemove: null, outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
expect(args).not.toContain('--write-subs');
|
||||
expect(args).not.toContain('--sub-langs');
|
||||
expect(args).not.toContain('--embed-subs');
|
||||
});
|
||||
|
||||
it('does not produce subtitle args when no format profile', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
expect(args).not.toContain('--write-subs');
|
||||
expect(args).not.toContain('--sub-langs');
|
||||
expect(args).not.toContain('--embed-subs');
|
||||
});
|
||||
|
||||
it('handles single subtitle language', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
setupForArgs(deps);
|
||||
|
||||
const profile: FormatProfile = {
|
||||
id: 33, name: 'Single Lang', videoResolution: null, audioCodec: null, audioBitrate: null,
|
||||
containerFormat: null, isDefault: false,
|
||||
subtitleLanguages: 'en',
|
||||
embedSubtitles: true,
|
||||
embedChapters: false, embedThumbnail: false,
|
||||
sponsorBlockRemove: null, outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
expect(args).toContain('--write-subs');
|
||||
const slIdx = args.indexOf('--sub-langs');
|
||||
expect(args[slIdx + 1]).toBe('en');
|
||||
expect(args).toContain('--embed-subs');
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadItem — combined SponsorBlock + subtitle args', () => {
|
||||
it('includes both SponsorBlock and subtitle args when both are configured', async () => {
|
||||
const deps = createMockDeps();
|
||||
const service = new DownloadService(
|
||||
db, deps.rateLimiter, deps.fileOrganizer,
|
||||
deps.qualityAnalyzer, deps.cookieManager
|
||||
);
|
||||
|
||||
const outputPath = join(tmpDir, 'media', 'youtube', 'Test Channel', 'Test Video Title.mp4');
|
||||
mkdirSync(join(tmpDir, 'media', 'youtube', 'Test Channel'), { recursive: true });
|
||||
writeFileSync(outputPath, 'data');
|
||||
execYtDlpMock.mockResolvedValueOnce({ stdout: outputPath, stderr: '', exitCode: 0 });
|
||||
statMock.mockResolvedValueOnce({ size: 1000 });
|
||||
|
||||
const profile: FormatProfile = {
|
||||
id: 40, name: 'Full Features', videoResolution: '1080p', audioCodec: null, audioBitrate: null,
|
||||
containerFormat: 'mkv', isDefault: false,
|
||||
subtitleLanguages: 'en,es,fr',
|
||||
embedSubtitles: true,
|
||||
embedChapters: true, embedThumbnail: true,
|
||||
sponsorBlockRemove: 'sponsor,selfpromo,intro,outro',
|
||||
outputTemplate: null, createdAt: '', updatedAt: '',
|
||||
};
|
||||
|
||||
await service.downloadItem(testContentItem, testChannel, profile);
|
||||
|
||||
const args = execYtDlpMock.mock.calls[0][0] as string[];
|
||||
|
||||
// Subtitle args
|
||||
expect(args).toContain('--write-subs');
|
||||
const slIdx = args.indexOf('--sub-langs');
|
||||
expect(args[slIdx + 1]).toBe('en,es,fr');
|
||||
expect(args).toContain('--embed-subs');
|
||||
|
||||
// SponsorBlock args
|
||||
const sbIdx = args.indexOf('--sponsorblock-remove');
|
||||
expect(sbIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(args[sbIdx + 1]).toBe('sponsor,selfpromo,intro,outro');
|
||||
|
||||
// Chapter + thumbnail embedding
|
||||
expect(args).toContain('--embed-chapters');
|
||||
expect(args).toContain('--embed-thumbnail');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue