D2: Widen retry to upload-source jobs
- Remove http-only guard in retry endpoint (src/routes/api/jobs/[id]/retry/+server.ts)
- Extend pipeline.ts retryJob() to handle upload-source jobs:
read file from persistent storage, pass buffer to runJob
- Change saveUploadedFile() to save to DATA_DIR/uploads/{jobId}/
(survives cleanupJobTmp, available for retry on failure)
- Add getUploadPath(), cleanupUploadDir() to downloader.ts
- Remove cleanupFiles(rawAudioPath) from catch block in runJob()
— cleanupJobTmp handles TMP_DIR for YouTube, uploads survive
- Add cleanupUploadDir() on webhook success path
- Add retry-endpoint.test.ts with 9 tests covering:
- 404 for unknown job
- 409 for non-retryable status (done, pending)
- 200 for cancelled (retryable)
- 200 for failed YouTube job (existing behavior)
- 200 for failed upload-source job (new behavior, .webm and .mp3)
- Negative: retryJob not called on 404/409
- Add downloader.test.ts tests for saveUploadedFile persistent path,
getUploadPath, cleanupUploadDir, isolation, and non-existent cleanup
- Fix webhook.test.ts mock to include cleanupUploadDir
All 189 tests pass across 12 test files.
This commit is contained in:
@@ -18,7 +18,7 @@ vi.mock('youtube-transcript', () => ({
|
||||
fetchTranscript: mockFetchTranscript
|
||||
}));
|
||||
|
||||
import { downloadYouTube, transcriptEntriesToSegments } from '$lib/server/downloader.js';
|
||||
import { downloadYouTube, transcriptEntriesToSegments, saveUploadedFile, getUploadPath, cleanupUploadDir } from '$lib/server/downloader.js';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -57,6 +57,61 @@ describe('transcriptEntriesToSegments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveUploadedFile', () => {
|
||||
it('saves file to persistent DATA_DIR/uploads/{jobId}/ location', async () => {
|
||||
const buffer = Buffer.from('fake audio content');
|
||||
const filePath = await saveUploadedFile(buffer, 'test-recording.webm', 'upload-test-1');
|
||||
|
||||
// Must be under DATA_DIR/uploads/, NOT under TMP_DIR/downloads/
|
||||
expect(filePath).toContain('/uploads/upload-test-1/test-recording.webm');
|
||||
expect(filePath).not.toContain('/downloads/');
|
||||
|
||||
// File must exist on disk
|
||||
const { accessSync } = await import('fs');
|
||||
expect(() => accessSync(filePath)).not.toThrow();
|
||||
|
||||
// Content must match
|
||||
const { readFileSync } = await import('fs');
|
||||
expect(readFileSync(filePath)).toEqual(buffer);
|
||||
|
||||
// cleanup
|
||||
await cleanupUploadDir('upload-test-1');
|
||||
});
|
||||
|
||||
it('getUploadPath returns correct path without creating file', async () => {
|
||||
const p = getUploadPath('some-job', 'audio.mp3');
|
||||
expect(p).toContain('/uploads/some-job/audio.mp3');
|
||||
expect(p).not.toContain('/downloads/');
|
||||
});
|
||||
|
||||
it('cleanupUploadDir removes the job upload directory', async () => {
|
||||
const buffer = Buffer.from('data');
|
||||
await saveUploadedFile(buffer, 'temp-file.txt', 'cleanup-test');
|
||||
const p = getUploadPath('cleanup-test', 'temp-file.txt');
|
||||
|
||||
const { accessSync } = await import('fs');
|
||||
expect(() => accessSync(p)).not.toThrow();
|
||||
|
||||
await cleanupUploadDir('cleanup-test');
|
||||
expect(() => accessSync(p)).toThrow();
|
||||
});
|
||||
|
||||
it('cleanupUploadDir does not throw for non-existent dir', async () => {
|
||||
await expect(cleanupUploadDir('non-existent-job')).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('each job gets an isolated upload directory', async () => {
|
||||
const p1 = await saveUploadedFile(Buffer.from('a'), 'a.wav', 'job-iso-1');
|
||||
const p2 = await saveUploadedFile(Buffer.from('b'), 'b.wav', 'job-iso-2');
|
||||
|
||||
expect(p1).toContain('/uploads/job-iso-1/a.wav');
|
||||
expect(p2).toContain('/uploads/job-iso-2/b.wav');
|
||||
|
||||
await cleanupUploadDir('job-iso-1');
|
||||
await cleanupUploadDir('job-iso-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadYouTube', () => {
|
||||
it('uses fetched transcript entries directly for caption jobs', async () => {
|
||||
mockFetchTranscript.mockResolvedValue([
|
||||
|
||||
Reference in New Issue
Block a user