diff --git a/src/converters/assimp.ts b/src/converters/assimp.ts index cac4f9f..69f3258 100644 --- a/src/converters/assimp.ts +++ b/src/converters/assimp.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -116,8 +117,8 @@ export async function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { return new Promise((resolve, reject) => { execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => { diff --git a/src/converters/calibre.ts b/src/converters/calibre.ts index f217b07..1d06695 100644 --- a/src/converters/calibre.ts +++ b/src/converters/calibre.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -62,8 +63,8 @@ export async function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { return new Promise((resolve, reject) => { execFile("ebook-convert", [filePath, targetPath], (error, stdout, stderr) => { diff --git a/src/converters/dvisvgm.ts b/src/converters/dvisvgm.ts index a9ce593..a48bc69 100644 --- a/src/converters/dvisvgm.ts +++ b/src/converters/dvisvgm.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -14,8 +15,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { const inputArgs: string[] = []; if (fileType === "eps") { diff --git a/src/converters/ffmpeg.ts b/src/converters/ffmpeg.ts index b8bc796..f7d183f 100644 --- a/src/converters/ffmpeg.ts +++ b/src/converters/ffmpeg.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; // This could be done dynamically by running `ffmpeg -formats` and parsing the output export const properties = { @@ -691,8 +692,8 @@ export async function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { let extraArgs: string[] = []; let message = "Done"; diff --git a/src/converters/graphicsmagick.ts b/src/converters/graphicsmagick.ts index dbd8996..4af168e 100644 --- a/src/converters/graphicsmagick.ts +++ b/src/converters/graphicsmagick.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -313,8 +314,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { return new Promise((resolve, reject) => { execFile("gm", ["convert", filePath, targetPath], (error, stdout, stderr) => { diff --git a/src/converters/imagemagick.ts b/src/converters/imagemagick.ts index 919308b..69eb359 100644 --- a/src/converters/imagemagick.ts +++ b/src/converters/imagemagick.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; // declare possible conversions export const properties = { @@ -445,8 +446,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { let outputArgs: string[] = []; let inputArgs: string[] = []; diff --git a/src/converters/inkscape.ts b/src/converters/inkscape.ts index 9e13fb0..dce123b 100644 --- a/src/converters/inkscape.ts +++ b/src/converters/inkscape.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -32,8 +33,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { return new Promise((resolve, reject) => { execFile("inkscape", [filePath, "-o", targetPath], (error, stdout, stderr) => { diff --git a/src/converters/libheif.ts b/src/converters/libheif.ts index 38ac949..3df1f52 100644 --- a/src/converters/libheif.ts +++ b/src/converters/libheif.ts @@ -1,4 +1,5 @@ -import { execFile } from "child_process"; +import { execFile as execFileOriginal } from "child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -14,8 +15,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { return new Promise((resolve, reject) => { execFile("heif-convert", [filePath, targetPath], (error, stdout, stderr) => { diff --git a/src/converters/libjxl.ts b/src/converters/libjxl.ts index 0ef11a8..336ad51 100644 --- a/src/converters/libjxl.ts +++ b/src/converters/libjxl.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; // declare possible conversions export const properties = { @@ -17,8 +18,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { let tool = ""; if (fileType === "jxl") { diff --git a/src/converters/libreoffice.ts b/src/converters/libreoffice.ts index d5c47af..02f389a 100644 --- a/src/converters/libreoffice.ts +++ b/src/converters/libreoffice.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -136,8 +137,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, ): Promise { const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "") ?? targetPath; diff --git a/src/converters/msgconvert.ts b/src/converters/msgconvert.ts index 8e95496..612a2ef 100644 --- a/src/converters/msgconvert.ts +++ b/src/converters/msgconvert.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -14,8 +15,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, ): Promise { return new Promise((resolve, reject) => { if (fileType === "msg" && convertTo === "eml") { diff --git a/src/converters/pandoc.ts b/src/converters/pandoc.ts index 6aab79d..c14c98b 100644 --- a/src/converters/pandoc.ts +++ b/src/converters/pandoc.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -124,8 +125,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, ): Promise { // set xelatex here const xelatex = ["pdf", "latex"]; diff --git a/src/converters/potrace.ts b/src/converters/potrace.ts index cdf09cc..b18f202 100644 --- a/src/converters/potrace.ts +++ b/src/converters/potrace.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -26,8 +27,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { return new Promise((resolve, reject) => { execFile("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => { diff --git a/src/converters/resvg.ts b/src/converters/resvg.ts index 1e409cb..fcdc2e4 100644 --- a/src/converters/resvg.ts +++ b/src/converters/resvg.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -14,8 +15,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable ): Promise { return new Promise((resolve, reject) => { execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => { diff --git a/src/converters/types.ts b/src/converters/types.ts new file mode 100644 index 0000000..2c1b649 --- /dev/null +++ b/src/converters/types.ts @@ -0,0 +1,15 @@ +export type ExecFileFn = ( + cmd: string, + args: string[], + callback: (err: Error | null, stdout: string, stderr: string) => void, + options?: import("child_process").ExecFileOptions, +) => void; + +export type ConvertFnWithExecFile = ( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + options: unknown, + execFileOverride?: ExecFileFn, +) => Promise; diff --git a/src/converters/vips.ts b/src/converters/vips.ts index 6451cda..310053f 100644 --- a/src/converters/vips.ts +++ b/src/converters/vips.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; // declare possible conversions export const properties = { @@ -94,8 +95,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, ): Promise { // if (fileType === "svg") { // const scale = options.scale || 1; diff --git a/src/converters/xelatex.ts b/src/converters/xelatex.ts index 5b29356..6bd0802 100644 --- a/src/converters/xelatex.ts +++ b/src/converters/xelatex.ts @@ -1,4 +1,5 @@ -import { execFile } from "node:child_process"; +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; export const properties = { from: { @@ -14,8 +15,8 @@ export function convert( fileType: string, convertTo: string, targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown, + execFile: ExecFileFn = execFileOriginal, ): Promise { return new Promise((resolve, reject) => { // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "") diff --git a/tests/converters/assimp.test.ts b/tests/converters/assimp.test.ts new file mode 100644 index 0000000..ea3479b --- /dev/null +++ b/tests/converters/assimp.test.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test"; +import { convert } from "../../src/converters/assimp"; +import { runCommonTests } from "./helpers/commonTests"; + +runCommonTests(convert); + +test.skip("dummy - required to trigger test detection", () => {}); diff --git a/tests/converters/calibre.test.ts b/tests/converters/calibre.test.ts new file mode 100644 index 0000000..773017d --- /dev/null +++ b/tests/converters/calibre.test.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test"; +import { convert } from "../../src/converters/calibre"; +import { runCommonTests } from "./helpers/commonTests"; + +runCommonTests(convert); + +test.skip("dummy - required to trigger test detection", () => {}); diff --git a/tests/converters/dvisvgm.test.ts b/tests/converters/dvisvgm.test.ts new file mode 100644 index 0000000..2723801 --- /dev/null +++ b/tests/converters/dvisvgm.test.ts @@ -0,0 +1,91 @@ +import type { ExecFileException } from "node:child_process"; +import { beforeEach, expect, test } from "bun:test"; +import { convert } from "../../src/converters/dvisvgm"; +import { ExecFileFn } from "../../src/converters/types"; +import { runCommonTests } from "./helpers/commonTests"; + +let calls: string[][] = []; + +beforeEach(() => { + calls = []; +}); + +runCommonTests(convert); + +test("convert respects eps filetype", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.eps", "eps", "stl", "output.stl", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual(expect.arrayContaining(["--eps", "input.eps", "output.stl"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("convert respects pdf filetype", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.pdf", "pdf", "stl", "output.stl", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual(expect.arrayContaining(["--pdf", "input.pdf", "output.stl"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("convert respects svgz conversion target type", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.obj", "eps", "svgz", "output.svgz", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual(expect.arrayContaining(["-z", "input.obj", "output.svgz"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); diff --git a/tests/converters/ffmpeg.test.ts b/tests/converters/ffmpeg.test.ts new file mode 100644 index 0000000..6f0afc6 --- /dev/null +++ b/tests/converters/ffmpeg.test.ts @@ -0,0 +1,181 @@ +import { beforeEach, expect, test } from "bun:test"; +import { convert } from "../../src/converters/ffmpeg"; + +let calls: string[][] = []; + +function mockExecFile( + _cmd: string, + args: string[], + callback: (err: Error | null, stdout: string, stderr: string) => void, +) { + calls.push(args); + if (args.includes("fail.mov")) { + callback(new Error("mock failure"), "", "Fake stderr: fail"); + } else { + callback(null, "Fake stdout", ""); + } +} + +beforeEach(() => { + calls = []; + delete process.env.FFMPEG_ARGS; +}); + +test("converts a normal file", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const result = await convert("in.mp4", "mp4", "avi", "out.avi", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual(expect.arrayContaining(["-i", "in.mp4", "out.avi"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("adds resize for ico output", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const result = await convert("in.png", "png", "ico", "out.ico", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done: resized to 256x256"); + expect(calls[0]).toEqual( + expect.arrayContaining(["-filter:v", expect.stringContaining("scale=")]), + ); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("uses libaom-av1 for av1.mp4", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + await convert("in.mkv", "mkv", "av1.mp4", "out.mp4", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libaom-av1"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("uses libx264 for h264.mp4", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx264"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("uses libx265 for h265.mp4", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + await convert("in.mkv", "mkv", "h265.mp4", "out.mp4", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx265"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("uses libx266 for h266.mp4", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + await convert("in.mkv", "mkv", "h266.mp4", "out.mp4", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx266"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("respects FFMPEG_ARGS", async () => { + process.env.FFMPEG_ARGS = "-hide_banner -y"; + + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + await convert("input.mov", "mov", "mp4", "output.mp4", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(calls[0]?.slice(0, 2)).toEqual(["-hide_banner", "-y"]); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("fails on exec error", async () => { + const originalConsoleError = console.error; + + let loggedMessage = ""; + console.error = (msg) => { + loggedMessage = msg; + }; + + expect(convert("fail.mov", "mov", "mp4", "output.mp4", undefined, mockExecFile)).rejects.toThrow( + "mock failure", + ); + + console.error = originalConsoleError; + + expect(loggedMessage).toBe("stderr: Fake stderr: fail"); +}); + +test("logs stderr when execFile returns only stderr and no error", async () => { + const originalConsoleError = console.error; + + let loggedMessage = ""; + console.error = (msg) => { + loggedMessage = msg; + }; + + // Mock execFile to call back with no error, no stdout, but with stderr + const mockExecFileStderrOnly = ( + _cmd: string, + _args: string[], + callback: (err: Error | null, stdout: string, stderr: string) => void, + ) => { + callback(null, "", "Only stderr output"); + }; + + await convert("input.mov", "mov", "mp4", "output.mp4", undefined, mockExecFileStderrOnly); + + console.error = originalConsoleError; + + expect(loggedMessage).toBe("stderr: Only stderr output"); +}); diff --git a/tests/converters/graphicsmagick.test.ts b/tests/converters/graphicsmagick.test.ts new file mode 100644 index 0000000..81fbfe4 --- /dev/null +++ b/tests/converters/graphicsmagick.test.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test"; +import { convert } from "../../src/converters/graphicsmagick"; +import { runCommonTests } from "./helpers/commonTests"; + +runCommonTests(convert); + +test.skip("dummy - required to trigger test detection", () => {}); diff --git a/tests/converters/helpers/commonTests.ts b/tests/converters/helpers/commonTests.ts new file mode 100644 index 0000000..64483b3 --- /dev/null +++ b/tests/converters/helpers/commonTests.ts @@ -0,0 +1,26 @@ +import { test } from "bun:test"; +import { ConvertFnWithExecFile } from "../../../src/converters/types"; +import { + runConvertFailTest, + runConvertLogsStderror, + runConvertLogsStderrorAndStdout, + runConvertSuccessTest, +} from "./converters"; + +export function runCommonTests(convert: ConvertFnWithExecFile) { + test("convert resolves when execFile succeeds", async () => { + await runConvertSuccessTest(convert); + }); + + test("convert rejects when execFile fails", async () => { + await runConvertFailTest(convert); + }); + + test("convert logs stderr when present", async () => { + await runConvertLogsStderror(convert); + }); + + test("convert logs both stderr and stdout when present", async () => { + await runConvertLogsStderrorAndStdout(convert); + }); +} diff --git a/tests/converters/helpers/converters.ts b/tests/converters/helpers/converters.ts new file mode 100644 index 0000000..12942f6 --- /dev/null +++ b/tests/converters/helpers/converters.ts @@ -0,0 +1,121 @@ +import type { ExecFileException } from "node:child_process"; +import { expect } from "bun:test"; +import { ConvertFnWithExecFile, ExecFileFn } from "../../../src/converters/types"; + +export async function runConvertSuccessTest(convertFn: ConvertFnWithExecFile) { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + callback(null, "Fake stdout", ""); + }; + + const result = await convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(loggedMessage).toBe("stdout: Fake stdout"); +} + +export async function runConvertFailTest(convertFn: ConvertFnWithExecFile) { + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + callback(new Error("Test error"), "", ""); + }; + + expect( + convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile), + ).rejects.toMatch(/error: Error: Test error/); + + // Test with error object lacking 'message' property + const mockExecFileNoMessage: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + // Simulate a non-standard error object + callback({ notMessage: true } as unknown as ExecFileException, "", ""); + }; + + expect( + convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFileNoMessage), + ).rejects.toMatch(/error:/i); + + // Test with a non-object error (e.g., a string) + const mockExecFileStringError: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + callback("string error" as unknown as ExecFileException, "", ""); + }; + + expect( + convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFileStringError), + ).rejects.toMatch(/error:/i); +} + +export async function runConvertLogsStderror(convertFn: ConvertFnWithExecFile) { + const originalConsoleError = console.error; + + let loggedMessage = ""; + console.error = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile = ( + _cmd: string, + _args: string[], + callback: (err: Error | null, stdout: string, stderr: string) => void, + ) => { + callback(null, "", "Fake stderr"); + }; + + await convertFn("file.obj", "obj", "stl", "out.stl", undefined, mockExecFile); + + console.error = originalConsoleError; + + expect(loggedMessage).toBe("stderr: Fake stderr"); +} + +export async function runConvertLogsStderrorAndStdout(convertFn: ConvertFnWithExecFile) { + const originalConsoleError = console.error; + const originalConsoleLog = console.log; + + let loggedError = ""; + let loggedMessage = ""; + console.error = (msg) => { + loggedError = msg; + }; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile = ( + _cmd: string, + _args: string[], + callback: (err: Error | null, stdout: string, stderr: string) => void, + ) => { + callback(null, "Fake stdout", "Fake stderr"); + }; + + await convertFn("file.obj", "obj", "stl", "out.stl", undefined, mockExecFile); + + console.error = originalConsoleError; + console.log = originalConsoleLog; + + expect(loggedError).toBe("stderr: Fake stderr"); + expect(loggedMessage).toBe("stdout: Fake stdout"); +} diff --git a/tests/converters/imagemagick.test.ts b/tests/converters/imagemagick.test.ts new file mode 100644 index 0000000..dd47b7a --- /dev/null +++ b/tests/converters/imagemagick.test.ts @@ -0,0 +1,165 @@ +import type { ExecFileException } from "node:child_process"; +import { beforeEach, expect, test } from "bun:test"; +import { convert } from "../../src/converters/imagemagick"; +import { ExecFileFn } from "../../src/converters/types"; +import { runCommonTests } from "./helpers/commonTests"; + +let calls: string[][] = []; + +beforeEach(() => { + calls = []; +}); + +runCommonTests(convert); + +test("convert respects ico conversion target type", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.obj", "eps", "ico", "output.ico", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual( + expect.arrayContaining([ + "-define", + "icon:auto-resize=256,128,64,48,32,16", + "-background", + "none", + "input.obj", + "output.ico", + ]), + ); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("convert respects ico conversion target type with svg as input filetype", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.svg", "svg", "ico", "output.ico", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual( + expect.arrayContaining([ + "-define", + "icon:auto-resize=256,128,64,48,32,16", + "-background", + "none", + "-density", + "512", + "input.svg", + "output.ico", + ]), + ); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("convert respects ico conversion target type with emf as input filetype", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.emf", "emf", "ico", "output.ico", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual( + expect.arrayContaining([ + "-define", + "icon:auto-resize=256,128,64,48,32,16", + "-background", + "none", + "emf:delegate=false", + "-density", + "300", + "white", + "-alpha", + "remove", + "input.emf", + "output.ico", + ]), + ); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("convert respects emf as input filetype", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.emf", "emf", "obj", "output.obj", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual( + expect.arrayContaining([ + "-define", + "emf:delegate=false", + "-density", + "300", + "-background", + "white", + "-alpha", + "remove", + "input.emf", + "output.obj", + ]), + ); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); diff --git a/tests/converters/inkscape.test.ts b/tests/converters/inkscape.test.ts new file mode 100644 index 0000000..a75ea3b --- /dev/null +++ b/tests/converters/inkscape.test.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test"; +import { convert } from "../../src/converters/inkscape"; +import { runCommonTests } from "./helpers/commonTests"; + +runCommonTests(convert); + +test.skip("dummy - required to trigger test detection", () => {}); diff --git a/tests/converters/libheif.test.ts b/tests/converters/libheif.test.ts new file mode 100644 index 0000000..48d8154 --- /dev/null +++ b/tests/converters/libheif.test.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test"; +import { convert } from "../../src/converters/libheif"; +import { runCommonTests } from "./helpers/commonTests"; + +runCommonTests(convert); + +test.skip("dummy - required to trigger test detection", () => {}); diff --git a/tests/converters/libjxl.test.ts b/tests/converters/libjxl.test.ts new file mode 100644 index 0000000..7171bdb --- /dev/null +++ b/tests/converters/libjxl.test.ts @@ -0,0 +1,91 @@ +import type { ExecFileException } from "node:child_process"; +import { beforeEach, expect, test } from "bun:test"; +import { convert } from "../../src/converters/libjxl"; +import { ExecFileFn } from "../../src/converters/types"; +import { runCommonTests } from "./helpers/commonTests"; + +let command: string = ""; + +beforeEach(() => { + command = ""; +}); + +runCommonTests(convert); + +test("convert uses djxl with input filetype being jxl", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + command = _cmd; + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.jxl", "jxl", "png", "output.png", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(command).toEqual("djxl"); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("convert uses cjxl with output filetype being jxl", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + command = _cmd; + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.png", "png", "jxl", "output.jxl", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(command).toEqual("cjxl"); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("convert uses empty string as command with neither input nor output filetype being jxl", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + command = _cmd; + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.png", "png", "jpg", "output.jpg", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(command).toEqual(""); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); diff --git a/tests/converters/msgconvert.test.ts b/tests/converters/msgconvert.test.ts new file mode 100644 index 0000000..7ef0dca --- /dev/null +++ b/tests/converters/msgconvert.test.ts @@ -0,0 +1,61 @@ +import type { ExecFileException } from "node:child_process"; +import { expect, test } from "bun:test"; +import { convert } from "../../src/converters/msgconvert"; +import { ExecFileFn } from "../../src/converters/types"; + +test("convert rejects conversion if input filetype is not msg and output type is not eml", async () => { + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + callback(null, "Fake stdout", ""); + }; + + const expectedError = new Error( + "Unsupported conversion from obj to stl. Only MSG to EML conversion is currently supported.", + ); + + expect(convert("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile)).rejects.toEqual( + expectedError, + ); +}); + +test("convert rejects conversion on error", async () => { + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + callback(new Error("Test error"), "", ""); + }; + + const expectedError = new Error("msgconvert failed: Test error"); + + expect(convert("input.msg", "msg", "eml", "output.eml", undefined, mockExecFile)).rejects.toEqual( + expectedError, + ); +}); + +test("convert logs stderr as warning", async () => { + const originalConsoleWarn = console.warn; + + let loggedMessage = ""; + console.warn = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile = ( + _cmd: string, + _args: string[], + callback: (err: Error | null, stdout: string, stderr: string) => void, + ) => { + callback(null, "", "Fake stderr"); + }; + + await convert("file.msg", "msg", "eml", "out.eml", undefined, mockExecFile); + + console.error = originalConsoleWarn; + + expect(loggedMessage).toBe("msgconvert stderr: Fake stderr"); +}); diff --git a/tests/converters/potrace.test.ts b/tests/converters/potrace.test.ts new file mode 100644 index 0000000..90dd86b --- /dev/null +++ b/tests/converters/potrace.test.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test"; +import { convert } from "../../src/converters/potrace"; +import { runCommonTests } from "./helpers/commonTests"; + +runCommonTests(convert); + +test.skip("dummy - required to trigger test detection", () => {}); diff --git a/tests/converters/resvg.test.ts b/tests/converters/resvg.test.ts new file mode 100644 index 0000000..88b3aba --- /dev/null +++ b/tests/converters/resvg.test.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test"; +import { convert } from "../../src/converters/resvg"; +import { runCommonTests } from "./helpers/commonTests"; + +runCommonTests(convert); + +test.skip("dummy - required to trigger test detection", () => {}); diff --git a/tests/converters/vips.test.ts b/tests/converters/vips.test.ts new file mode 100644 index 0000000..b97be81 --- /dev/null +++ b/tests/converters/vips.test.ts @@ -0,0 +1,65 @@ +import type { ExecFileException } from "node:child_process"; +import { beforeEach, expect, test } from "bun:test"; +import { ExecFileFn } from "../../src/converters/types"; +import { convert } from "../../src/converters/vips"; +import { runCommonTests } from "./helpers/commonTests"; + +let calls: string[][] = []; + +beforeEach(() => { + calls = []; +}); + +runCommonTests(convert); + +test("convert uses action pdfload with filetype being pdf", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.pdf", "pdf", "obj", "output.obj", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual(expect.arrayContaining(["pdfload"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); + +test("convert uses action copy with filetype being anything but pdf", async () => { + const originalConsoleLog = console.log; + + let loggedMessage = ""; + console.log = (msg) => { + loggedMessage = msg; + }; + + const mockExecFile: ExecFileFn = ( + _cmd: string, + _args: string[], + callback: (err: ExecFileException | null, stdout: string, stderr: string) => void, + ) => { + calls.push(_args); + callback(null, "Fake stdout", ""); + }; + + const result = await convert("input.jpg", "jpg", "obj", "output.obj", undefined, mockExecFile); + + console.log = originalConsoleLog; + + expect(result).toBe("Done"); + expect(calls[0]).toEqual(expect.arrayContaining(["copy"])); + expect(loggedMessage).toBe("stdout: Fake stdout"); +}); diff --git a/tests/converters/xelatex.test.ts b/tests/converters/xelatex.test.ts new file mode 100644 index 0000000..5e5f0b0 --- /dev/null +++ b/tests/converters/xelatex.test.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test"; +import { convert } from "../../src/converters/xelatex"; +import { runCommonTests } from "./helpers/commonTests"; + +runCommonTests(convert); + +test.skip("dummy - required to trigger test detection", () => {}); diff --git a/tsconfig.json b/tsconfig.json index 234339b..ffd5668 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,5 +30,5 @@ "esModuleInterop": true // "noImplicitReturns": true }, - "include": ["src", "package.json", "reset.d.ts"] + "include": ["src", "tests", "package.json", "reset.d.ts"] }