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:
jlightner 2026-04-04 10:09:10 +00:00
parent f8916d2cc3
commit f814e8d261

View file

@ -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');
});
});
});