fix: resolve TypeScript type errors in update-check.test.ts (#3284)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run

Replace `mock()` + `spyOn().mockImplementation(mockFn)` pattern with
direct `spyOn().mockImplementation(() => ...)` to fix fetch mock type
mismatches. Make execFileSync mocks return Buffer.from("") instead of
void. Add explicit type annotations for callback parameters.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-04-12 17:40:59 -07:00 committed by GitHub
parent 0f6a48369b
commit 439e5a1446
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 46 additions and 44 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "1.0.4",
"version": "1.0.5",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -1,6 +1,6 @@
import type { ExecFileSyncOptions } from "node:child_process";
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test";
import fs from "node:fs";
import path from "node:path";
import { tryCatch } from "@openrouter/spawn-shared";
@ -94,12 +94,11 @@ describe("update-check", () => {
});
it("should check for updates on every run", async () => {
const mockFetch = mock(() => Promise.resolve(new Response("1.0.99\n")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.resolve(new Response("1.0.99\n")));
// Mock execFileSync to prevent actual update + re-exec
const { executor } = await import("../update-check.js");
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation(() => {});
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation(() => Buffer.from(""));
const { checkForUpdates } = await import("../update-check.js");
await checkForUpdates();
@ -110,12 +109,11 @@ describe("update-check", () => {
});
it("should auto-update when newer version is available", async () => {
const mockFetch = mock(() => Promise.resolve(new Response("1.0.99\n")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.resolve(new Response("1.0.99\n")));
// Mock execFileSync to prevent actual update + re-exec
const { executor } = await import("../update-check.js");
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation(() => {});
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation(() => Buffer.from(""));
const { checkForUpdates } = await import("../update-check.js");
await checkForUpdates();
@ -137,12 +135,13 @@ describe("update-check", () => {
});
it("should not update when up to date", async () => {
const mockFetch = mock(() => Promise.resolve(new Response(`${pkg.version}\n`)));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve(new Response(`${pkg.version}\n`)),
);
// Mock executor to prevent actual commands
const { executor } = await import("../update-check.js");
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation(() => {});
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation(() => Buffer.from(""));
const { checkForUpdates } = await import("../update-check.js");
await checkForUpdates();
@ -156,8 +155,7 @@ describe("update-check", () => {
});
it("should handle network errors gracefully", async () => {
const mockFetch = mock(() => Promise.reject(new Error("Network error")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.reject(new Error("Network error")));
const { checkForUpdates } = await import("../update-check.js");
await checkForUpdates();
@ -169,8 +167,7 @@ describe("update-check", () => {
});
it("should handle update failures gracefully", async () => {
const mockFetch = mock(() => Promise.resolve(new Response("1.0.99\n")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.resolve(new Response("1.0.99\n")));
// Mock execFileSync to throw an error (curl fetch fails)
const { executor } = await import("../update-check.js");
@ -193,14 +190,13 @@ describe("update-check", () => {
});
it("should handle bad response format", async () => {
const mockFetch = mock(() =>
const fetchSpy = spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve(
new Response("Not Found", {
status: 404,
}),
),
);
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const { checkForUpdates } = await import("../update-check.js");
await checkForUpdates();
@ -212,8 +208,7 @@ describe("update-check", () => {
});
it("should redirect install script stdout to stderr when jsonOutput=true", async () => {
const mockFetch = mock(() => Promise.resolve(new Response("1.0.99\n")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.resolve(new Response("1.0.99\n")));
const { executor } = await import("../update-check.js");
const execFileSyncCalls: {
@ -228,6 +223,7 @@ describe("update-check", () => {
args,
options,
});
return Buffer.from("");
},
);
@ -249,8 +245,7 @@ describe("update-check", () => {
});
it("should use inherit stdio for install script when jsonOutput=false", async () => {
const mockFetch = mock(() => Promise.resolve(new Response("1.0.99\n")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.resolve(new Response("1.0.99\n")));
const { executor } = await import("../update-check.js");
const execFileSyncCalls: {
@ -265,6 +260,7 @@ describe("update-check", () => {
args,
options,
});
return Buffer.from("");
},
);
@ -289,20 +285,24 @@ describe("update-check", () => {
"sprite",
];
const mockFetch = mock(() => Promise.resolve(new Response("1.0.99\n")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.resolve(new Response("1.0.99\n")));
const { executor } = await import("../update-check.js");
const execFileSyncCalls: {
file: string;
args: string[];
options?: ExecFileSyncOptions;
}[] = [];
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation((file: string, args: string[]) => {
execFileSyncCalls.push({
file,
args,
});
});
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation(
(file: string, args: string[], options?: ExecFileSyncOptions) => {
execFileSyncCalls.push({
file,
args,
options,
});
return Buffer.from("");
},
);
const { checkForUpdates } = await import("../update-check.js");
await checkForUpdates();
@ -312,7 +312,7 @@ describe("update-check", () => {
// 1. curl to fetch install script
expect(execFileSyncCalls[0].file).toBe("curl");
expect(execFileSyncCalls[0].args).toContain("-fsSL");
expect(execFileSyncCalls[0].args.some((a) => a.includes("install.sh"))).toBe(true);
expect(execFileSyncCalls[0].args.some((a: string) => a.includes("install.sh"))).toBe(true);
// 2. bash to execute fetched script
expect(execFileSyncCalls[1].file).toBe("bash");
expect(execFileSyncCalls[1].args[0]).toBe("-c");
@ -328,13 +328,13 @@ describe("update-check", () => {
]);
// Should show rerunning message
const output = consoleErrorSpy.mock.calls.map((call) => call[0]).join("\n");
const output = consoleErrorSpy.mock.calls.map((call: unknown[]) => call[0]).join("\n");
expect(output).toContain("Rerunning");
// Should set SPAWN_NO_UPDATE_CHECK=1 to prevent infinite loop
const reexecCall = execFileSyncSpy.mock.calls[3];
expect(reexecCall[2]).toHaveProperty("env");
expect(reexecCall[2].env.SPAWN_NO_UPDATE_CHECK).toBe("1");
const reexecCall = execFileSyncCalls[3];
expect(reexecCall.options).toHaveProperty("env");
expect(reexecCall.options?.env?.SPAWN_NO_UPDATE_CHECK).toBe("1");
expect(processExitSpy).toHaveBeenCalledWith(0);
@ -352,12 +352,11 @@ describe("update-check", () => {
"sprite",
];
const mockFetch = mock(() => Promise.resolve(new Response("1.0.99\n")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.resolve(new Response("1.0.99\n")));
const { executor } = await import("../update-check.js");
let callCount = 0;
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation((file: string) => {
const execFileSyncSpy = spyOn(executor, "execFileSync").mockImplementation((): Buffer => {
callCount++;
// First 3 calls succeed (curl, bash, which), 4th call (re-exec) fails
if (callCount >= 4) {
@ -367,6 +366,7 @@ describe("update-check", () => {
});
throw err;
}
return Buffer.from("");
});
const { checkForUpdates } = await import("../update-check.js");
@ -397,8 +397,9 @@ describe("update-check", () => {
// Write an old timestamp (2 hours ago)
writeUpdateChecked(Date.now() - 2 * 60 * 60 * 1000);
const mockFetch = mock(() => Promise.resolve(new Response(`${pkg.version}\n`)));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve(new Response(`${pkg.version}\n`)),
);
const { checkForUpdates } = await import("../update-check.js");
await checkForUpdates();
@ -408,8 +409,9 @@ describe("update-check", () => {
});
it("should write cache file after successful version fetch", async () => {
const mockFetch = mock(() => Promise.resolve(new Response(`${pkg.version}\n`)));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve(new Response(`${pkg.version}\n`)),
);
const { checkForUpdates } = await import("../update-check.js");
await checkForUpdates();
@ -429,8 +431,7 @@ describe("update-check", () => {
"/usr/local/bin/spawn",
];
const mockFetch = mock(() => Promise.resolve(new Response("1.0.99\n")));
const fetchSpy = spyOn(global, "fetch").mockImplementation(mockFetch);
const fetchSpy = spyOn(global, "fetch").mockImplementation(() => Promise.resolve(new Response("1.0.99\n")));
const { executor } = await import("../update-check.js");
const execFileSyncCalls: {
@ -442,6 +443,7 @@ describe("update-check", () => {
file,
args,
});
return Buffer.from("");
});
const { checkForUpdates } = await import("../update-check.js");
@ -456,7 +458,7 @@ describe("update-check", () => {
expect(execFileSyncCalls[3].args).toEqual([]);
// Should show restarting message
const output = consoleErrorSpy.mock.calls.map((call) => call[0]).join("\n");
const output = consoleErrorSpy.mock.calls.map((call: unknown[]) => call[0]).join("\n");
expect(output).toContain("Restarting spawn");
expect(processExitSpy).toHaveBeenCalledWith(0);