fix(ci): resolve all test failures + complete rename compat layer

Sisyphus-authored fixes across 15 files:

- plugin-identity: align CONFIG_BASENAME with actual config file name
- add-plugin-to-opencode-config: handle legacy→canonical name migration
- plugin-detection tests: update expectations for new identity constants
- doctor/system: fix legacy name warning test assertions
- install tests: align with new plugin name
- chat-params tests: fix mock isolation
- model-capabilities tests: fix snapshot expectations
- image-converter: fix platform-dependent test assertions (Linux CI)
- example configs: expanded with more detailed comments

Full suite: 4484 pass, 0 fail, typecheck clean.
This commit is contained in:
YeonGyu-Kim
2026-03-26 18:04:31 +09:00
parent e86edca633
commit 4efc181390
17 changed files with 635 additions and 308 deletions
+72 -51
View File
@@ -1,22 +1,14 @@
import { describe, expect, test, mock, beforeEach } from "bun:test"
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"
import * as childProcess from "node:child_process"
import { existsSync, mkdtempSync, writeFileSync, unlinkSync, rmSync } from "node:fs"
import { tmpdir } from "node:os"
import { dirname, join } from "node:path"
const originalChildProcess = await import("node:child_process")
type ImageConverterModule = typeof import("./image-converter")
const execFileSyncMock = mock((_command: string, _args: string[], _options?: unknown) => "")
const execSyncMock = mock(() => {
throw new Error("execSync should not be called")
})
mock.module("node:child_process", () => ({
...originalChildProcess,
execFileSync: execFileSyncMock,
execSync: execSyncMock,
}))
const { convertImageToJpeg, cleanupConvertedImage } = await import("./image-converter")
async function loadImageConverter(): Promise<ImageConverterModule> {
return import(`./image-converter?test=${Date.now()}-${Math.random()}`)
}
function writeConvertedOutput(command: string, args: string[]): void {
if (command === "sips") {
@@ -38,7 +30,10 @@ function writeConvertedOutput(command: string, args: string[]): void {
}
}
function withMockPlatform<TValue>(platform: NodeJS.Platform, run: () => TValue): TValue {
async function withMockPlatform<TValue>(
platform: NodeJS.Platform,
run: () => TValue | Promise<TValue>,
): Promise<TValue> {
const originalPlatform = process.platform
Object.defineProperty(process, "platform", {
value: platform,
@@ -46,7 +41,7 @@ function withMockPlatform<TValue>(platform: NodeJS.Platform, run: () => TValue):
})
try {
return run()
return await run()
} finally {
Object.defineProperty(process, "platform", {
value: originalPlatform,
@@ -56,34 +51,50 @@ function withMockPlatform<TValue>(platform: NodeJS.Platform, run: () => TValue):
}
describe("image-converter command execution safety", () => {
let execFileSyncSpy: ReturnType<typeof spyOn>
let execSyncSpy: ReturnType<typeof spyOn>
beforeEach(() => {
execFileSyncMock.mockReset()
execSyncMock.mockReset()
execSyncSpy = spyOn(childProcess, "execSync").mockImplementation(() => {
throw new Error("execSync should not be called")
})
execFileSyncSpy = spyOn(childProcess, "execFileSync").mockImplementation(
((_command: string, _args: string[], _options?: unknown) => "") as typeof childProcess.execFileSync,
)
})
test("uses execFileSync with argument arrays for conversion commands", () => {
afterEach(() => {
execFileSyncSpy.mockRestore()
execSyncSpy.mockRestore()
})
test("uses execFileSync with argument arrays for conversion commands", async () => {
const testDir = mkdtempSync(join(tmpdir(), "img-converter-test-"))
const inputPath = join(testDir, "evil$(touch_pwn).heic")
writeFileSync(inputPath, "fake-heic-data")
const { convertImageToJpeg } = await loadImageConverter()
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
writeConvertedOutput(command, args)
return ""
})
execFileSyncSpy.mockImplementation(
((command: string, args: string[]) => {
writeConvertedOutput(command, args)
return ""
}) as typeof childProcess.execFileSync,
)
const outputPath = convertImageToJpeg(inputPath, "image/heic")
expect(execSyncMock).not.toHaveBeenCalled()
expect(execFileSyncMock).toHaveBeenCalled()
expect(execSyncSpy).not.toHaveBeenCalled()
expect(execFileSyncSpy).toHaveBeenCalled()
const [firstCommand, firstArgs] = execFileSyncMock.mock.calls[0] as [string, string[]]
const [firstCommand, firstArgs] = execFileSyncSpy.mock.calls[0] as [string, string[]]
expect(typeof firstCommand).toBe("string")
expect(Array.isArray(firstArgs)).toBe(true)
expect(["sips", "convert", "magick"]).toContain(firstCommand)
expect(firstArgs).toContain("--")
expect(firstArgs).toContain(inputPath)
expect(firstArgs.indexOf("--") < firstArgs.indexOf(inputPath)).toBe(true)
expect(firstArgs.join(" ")).not.toContain(`\"${inputPath}\"`)
expect(firstArgs.join(" ")).not.toContain(`"${inputPath}"`)
expect(existsSync(outputPath)).toBe(true)
@@ -92,15 +103,18 @@ describe("image-converter command execution safety", () => {
rmSync(testDir, { recursive: true, force: true })
})
test("removes temporary conversion directory during cleanup", () => {
test("removes temporary conversion directory during cleanup", async () => {
const testDir = mkdtempSync(join(tmpdir(), "img-converter-cleanup-test-"))
const inputPath = join(testDir, "photo.heic")
writeFileSync(inputPath, "fake-heic-data")
const { convertImageToJpeg, cleanupConvertedImage } = await loadImageConverter()
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
writeConvertedOutput(command, args)
return ""
})
execFileSyncSpy.mockImplementation(
((command: string, args: string[]) => {
writeConvertedOutput(command, args)
return ""
}) as typeof childProcess.execFileSync,
)
const outputPath = convertImageToJpeg(inputPath, "image/heic")
const conversionDirectory = dirname(outputPath)
@@ -115,22 +129,25 @@ describe("image-converter command execution safety", () => {
rmSync(testDir, { recursive: true, force: true })
})
test("uses magick command on non-darwin platforms to avoid convert.exe collision", () => {
withMockPlatform("linux", () => {
test("uses magick command on non-darwin platforms to avoid convert.exe collision", async () => {
await withMockPlatform("linux", async () => {
const testDir = mkdtempSync(join(tmpdir(), "img-converter-platform-test-"))
const inputPath = join(testDir, "photo.heic")
writeFileSync(inputPath, "fake-heic-data")
const { convertImageToJpeg, cleanupConvertedImage } = await loadImageConverter()
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
if (command === "magick") {
writeFileSync(args[2], "jpeg")
}
return ""
})
execFileSyncSpy.mockImplementation(
((command: string, args: string[]) => {
if (command === "magick") {
writeFileSync(args[2], "jpeg")
}
return ""
}) as typeof childProcess.execFileSync,
)
const outputPath = convertImageToJpeg(inputPath, "image/heic")
const [command, args] = execFileSyncMock.mock.calls[0] as [string, string[]]
const [command, args] = execFileSyncSpy.mock.calls[0] as [string, string[]]
expect(command).toBe("magick")
expect(args).toContain("--")
expect(args.indexOf("--") < args.indexOf(inputPath)).toBe(true)
@@ -142,19 +159,22 @@ describe("image-converter command execution safety", () => {
})
})
test("applies timeout when executing conversion commands", () => {
test("applies timeout when executing conversion commands", async () => {
const testDir = mkdtempSync(join(tmpdir(), "img-converter-timeout-test-"))
const inputPath = join(testDir, "photo.heic")
writeFileSync(inputPath, "fake-heic-data")
const { convertImageToJpeg, cleanupConvertedImage } = await loadImageConverter()
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
writeConvertedOutput(command, args)
return ""
})
execFileSyncSpy.mockImplementation(
((command: string, args: string[]) => {
writeConvertedOutput(command, args)
return ""
}) as typeof childProcess.execFileSync,
)
const outputPath = convertImageToJpeg(inputPath, "image/heic")
const options = execFileSyncMock.mock.calls[0]?.[2] as { timeout?: number } | undefined
const options = execFileSyncSpy.mock.calls[0]?.[2] as { timeout?: number } | undefined
expect(options).toBeDefined()
expect(typeof options?.timeout).toBe("number")
expect((options?.timeout ?? 0) > 0).toBe(true)
@@ -164,15 +184,16 @@ describe("image-converter command execution safety", () => {
rmSync(testDir, { recursive: true, force: true })
})
test("attaches temporary output path to conversion errors", () => {
withMockPlatform("linux", () => {
test("attaches temporary output path to conversion errors", async () => {
await withMockPlatform("linux", async () => {
const testDir = mkdtempSync(join(tmpdir(), "img-converter-failure-test-"))
const inputPath = join(testDir, "photo.heic")
writeFileSync(inputPath, "fake-heic-data")
const { convertImageToJpeg } = await loadImageConverter()
execFileSyncMock.mockImplementation(() => {
execFileSyncSpy.mockImplementation((() => {
throw new Error("conversion process failed")
})
}) as typeof childProcess.execFileSync)
const runConversion = () => convertImageToJpeg(inputPath, "image/heic")
expect(runConversion).toThrow("No image conversion tool available")
+3 -3
View File
@@ -1,4 +1,4 @@
import { execFileSync } from "node:child_process"
import * as childProcess from "node:child_process"
import { existsSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { dirname, join } from "node:path"
@@ -59,7 +59,7 @@ export function convertImageToJpeg(inputPath: string, mimeType: string): string
try {
if (process.platform === "darwin") {
try {
execFileSync("sips", ["-s", "format", "jpeg", "--", inputPath, "--out", outputPath], {
childProcess.execFileSync("sips", ["-s", "format", "jpeg", "--", inputPath, "--out", outputPath], {
stdio: "pipe",
encoding: "utf-8",
timeout: CONVERSION_TIMEOUT_MS,
@@ -76,7 +76,7 @@ export function convertImageToJpeg(inputPath: string, mimeType: string): string
try {
const imagemagickCommand = process.platform === "darwin" ? "convert" : "magick"
execFileSync(imagemagickCommand, ["--", inputPath, outputPath], {
childProcess.execFileSync(imagemagickCommand, ["--", inputPath, outputPath], {
stdio: "pipe",
encoding: "utf-8",
timeout: CONVERSION_TIMEOUT_MS,