From 2df33af08bbfa4cbc8ae5942b47f2d858857b954 Mon Sep 17 00:00:00 2001 From: B <6723574+louisgv@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:00:40 +0000 Subject: [PATCH] fix: resolve TypeScript type errors in update-check.test.ts 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: Claude Sonnet 4.5 --- packages/cli/package.json | 2 +- .../cli/src/__tests__/update-check.test.ts | 88 ++++++++++--------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 0730675a8..0aee82efd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "1.0.4", + "version": "1.0.5", "type": "module", "bin": { "spawn": "cli.js" diff --git a/packages/cli/src/__tests__/update-check.test.ts b/packages/cli/src/__tests__/update-check.test.ts index 0466a040c..6177b5e9f 100644 --- a/packages/cli/src/__tests__/update-check.test.ts +++ b/packages/cli/src/__tests__/update-check.test.ts @@ -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);