diff --git a/src/worker.js b/src/worker.js index 8f17127..ff839a3 100644 --- a/src/worker.js +++ b/src/worker.js @@ -12,6 +12,7 @@ import { registerIcoConverter } from './converters/ico.js'; import { registerHeicConverter } from './converters/heic.js'; import { registerFontConverter } from './converters/font.js'; import { registerDfontConverter } from './converters/dfont.js'; +import { registerEbookConverter } from './converters/ebook.js'; import { findPendingJobs, markProcessing, markDone, markFailed } from './jobs/jobRepository.js'; const JOB_TIMEOUT_MS = 60000; @@ -36,10 +37,11 @@ async function processJob(prisma, config, job) { throw new Error(`No converter registered for ${job.sourceFormat} -> ${job.targetFormat}`); } + const timeoutMs = entry.family === 'ebook' ? config.ebookJobTimeoutMs : JOB_TIMEOUT_MS; const startedAt = Date.now(); await withTimeout( - entry.convert(inputFilePath, outputFilePath, { quality: job.quality, iconSize: job.iconSize }), - JOB_TIMEOUT_MS + entry.convert(inputFilePath, outputFilePath, { quality: job.quality, iconSize: job.iconSize, timeoutMs }), + timeoutMs ); const conversionDurationSeconds = (Date.now() - startedAt) / 1000; const { size: outputSizeBytes } = await fs.stat(outputFilePath); @@ -86,6 +88,7 @@ async function main() { registerHeicConverter(); registerFontConverter(); registerDfontConverter(); + registerEbookConverter(); startWorker(prisma, config); console.log(`Worker started, polling every ${config.workerPollIntervalMs}ms`); diff --git a/test/worker.test.js b/test/worker.test.js index 4cae369..98f5d11 100644 --- a/test/worker.test.js +++ b/test/worker.test.js @@ -1,5 +1,6 @@ -import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; +import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest'; import fs from 'node:fs/promises'; +import { writeFileSync } from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import sharp from 'sharp'; @@ -9,14 +10,19 @@ import { ensureStorageDirs, uploadPath, outputPath } from '../src/storage.js'; import { createJob, getJobByUuid, getJobErrorLog } from '../src/jobs/jobRepository.js'; import { registerImageConverters } from '../src/converters/image.js'; import { registerIcoConverter } from '../src/converters/ico.js'; +import { registerEbookConverter } from '../src/converters/ebook.js'; import { processPendingJobs } from '../src/worker.js'; +const { execFileMock } = vi.hoisted(() => ({ execFileMock: vi.fn() })); +vi.mock('node:child_process', () => ({ execFile: execFileMock })); + let prisma; let config; beforeAll(async () => { registerImageConverters(); registerIcoConverter(); + registerEbookConverter(); config = { ...loadConfig(), storageDir: await fs.mkdtemp(path.join(os.tmpdir(), 'converter-worker-')) }; await ensureStorageDirs(config); prisma = getPrismaClient(config); @@ -187,4 +193,38 @@ describe('processPendingJobs', () => { const [image] = await decodeIco(await fs.readFile(outputFilePath), 'image/png'); expect(image.width).toBe(32); }); + + it('converts a pending ebook job to done using config.ebookJobTimeoutMs, not the default 60s timeout', async () => { + execFileMock.mockReset(); + execFileMock.mockImplementation((file, args, options, callback) => { + writeFileSync(args[1], 'fake converted ebook output'); + callback(null, '', ''); + }); + + const uuid = '11111111-1111-4111-8111-111111111111'; + const inputFilePath = uploadPath(config, uuid, 'epub'); + await fs.writeFile(inputFilePath, 'fake epub content'); + + await createJob(prisma, { + uuid, + family: 'ebook', + sourceFormat: 'epub', + targetFormat: 'pdf', + originalFilename: 'book.epub', + inputPath: `${uuid}.epub`, + inputMimeType: 'application/epub+zip', + inputSizeBytes: 18, + expiresAt: new Date(Date.now() + 3600 * 1000), + }); + + await processPendingJobs(prisma, config); + + const job = await getJobByUuid(prisma, uuid); + expect(job.status).toBe('done'); + const [file, args, options] = execFileMock.mock.calls[0]; + expect(file).toBe('ebook-convert'); + expect(args).toEqual([inputFilePath, outputPath(config, uuid, 'pdf')]); + expect(options).toEqual({ timeout: config.ebookJobTimeoutMs }); + expect(config.ebookJobTimeoutMs).not.toBe(60000); + }); });