mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 21:24:46 +00:00
test: share Windows platform spy helpers
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withMockedWindowsPlatform } from "../test-utils/vitest-spies.js";
|
||||
import { defaultLoadOverrideModule, startLazyPluginServiceModule } from "./lazy-service-module.js";
|
||||
|
||||
type LazyPluginServiceHandle = NonNullable<
|
||||
@@ -103,34 +104,28 @@ describe("startLazyPluginServiceModule", () => {
|
||||
});
|
||||
|
||||
it("normalizes Windows absolute paths in the default override loader", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const start = createAsyncHookMock();
|
||||
const importModule = vi.fn(async () => ({ startOverride: start }));
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
await defaultLoadOverrideModule("C:\\Users\\alice\\plugin folder\\x#y.mjs", importModule);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
expect(importModule).toHaveBeenCalledWith("file:///C:/Users/alice/plugin%20folder/x%23y.mjs");
|
||||
});
|
||||
|
||||
it("leaves caller-supplied override loaders responsible for their own specifiers", async () => {
|
||||
process.env.OPENCLAW_LAZY_SERVICE_OVERRIDE = "C:\\Users\\alice\\browser-service.mjs";
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const start = createAsyncHookMock();
|
||||
const loadOverrideModule = vi.fn(async () => ({ startOverride: start }));
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
await expectLifecycleStarted({
|
||||
overrideEnvVar: "OPENCLAW_LAZY_SERVICE_OVERRIDE",
|
||||
loadOverrideModule,
|
||||
startExportNames: ["startOverride"],
|
||||
});
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
expect(loadOverrideModule).toHaveBeenCalledWith("C:\\Users\\alice\\browser-service.mjs");
|
||||
expect(start).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -3,6 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withMockedWindowsPlatform } from "../test-utils/vitest-spies.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
@@ -47,9 +48,8 @@ describe("bundled plugin public surface loader", () => {
|
||||
moduleExport: { marker: "windows-dist-ok" },
|
||||
}),
|
||||
}));
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const publicSurfaceLoader = await importFreshModule<
|
||||
typeof import("./public-surface-loader.js")
|
||||
>(import.meta.url, "./public-surface-loader.js?scope=windows-dist-jiti");
|
||||
@@ -68,9 +68,7 @@ describe("bundled plugin public surface loader", () => {
|
||||
}).marker,
|
||||
).toBe("windows-dist-ok");
|
||||
expect(createJiti).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers source require for bundled source public artifacts when a ts require hook exists", async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { withMockedWindowsPlatform } from "../test-utils/vitest-spies.js";
|
||||
import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js";
|
||||
import {
|
||||
getRegistryJitiMocks,
|
||||
@@ -227,17 +228,17 @@ describe("setup-registry module loader", () => {
|
||||
plugins: [{ id: "test-plugin", rootDir: pluginRoot }],
|
||||
diagnostics: [],
|
||||
});
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const restoreVersions = forceNodeRuntimeVersionsForTest();
|
||||
|
||||
try {
|
||||
resolvePluginSetupRegistry({
|
||||
workspaceDir: pluginRoot,
|
||||
env: {},
|
||||
withMockedWindowsPlatform(() => {
|
||||
resolvePluginSetupRegistry({
|
||||
workspaceDir: pluginRoot,
|
||||
env: {},
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
restoreVersions();
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
|
||||
expect(mocks.createJiti).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
|
||||
import { bundledDistPluginFile } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { stageBundledPluginRuntime } from "../../scripts/stage-bundled-plugin-runtime.mjs";
|
||||
import { withMockedWindowsPlatform, withRestoredMocks } from "../test-utils/vitest-spies.js";
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js";
|
||||
@@ -547,7 +548,6 @@ describe("stageBundledPluginRuntime", () => {
|
||||
[bundledDistPluginFile("feishu", "assets/fixture.txt")]: "# Feishu Doc\n",
|
||||
});
|
||||
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const realSymlinkSync = fs.symlinkSync.bind(fs);
|
||||
const symlinkSpy = vi.spyOn(fs, "symlinkSync").mockImplementation(((target, link, type) => {
|
||||
const linkPath = String(link);
|
||||
@@ -557,7 +557,11 @@ describe("stageBundledPluginRuntime", () => {
|
||||
return realSymlinkSync(String(target), linkPath, type);
|
||||
}) as typeof fs.symlinkSync);
|
||||
|
||||
stageBundledPluginRuntime({ repoRoot });
|
||||
withRestoredMocks([symlinkSpy], () => {
|
||||
withMockedWindowsPlatform(() => {
|
||||
stageBundledPluginRuntime({ repoRoot });
|
||||
});
|
||||
});
|
||||
|
||||
const runtimeAssetPath = path.join(
|
||||
repoRoot,
|
||||
@@ -569,9 +573,6 @@ describe("stageBundledPluginRuntime", () => {
|
||||
);
|
||||
expect(fs.lstatSync(runtimeAssetPath).isSymbolicLink()).toBe(false);
|
||||
expect(fs.readFileSync(runtimeAssetPath, "utf8")).toBe("# Feishu Doc\n");
|
||||
|
||||
symlinkSpy.mockRestore();
|
||||
platformSpy.mockRestore();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
_resetWindowsInstallRootsForTests,
|
||||
getWindowsInstallRoots,
|
||||
} from "../infra/windows-install-roots.js";
|
||||
import { withMockedWindowsPlatform, withRestoredMocks } from "../test-utils/vitest-spies.js";
|
||||
|
||||
const { spawnMock, spawnSyncMock, execFileMock, execFilePromisifyMock } = vi.hoisted(() => {
|
||||
const execFilePromisifyMock = vi.fn();
|
||||
@@ -123,7 +124,6 @@ function expectedTrustedCmdExe(): string {
|
||||
}
|
||||
|
||||
async function expectShimmedWindowsCommandWithoutExitCodeSucceeds(params?: { killed?: boolean }) {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const child = createMockChild({
|
||||
closeCode: null,
|
||||
exitCode: null,
|
||||
@@ -132,14 +132,12 @@ async function expectShimmedWindowsCommandWithoutExitCodeSucceeds(params?: { kil
|
||||
|
||||
spawnMock.mockImplementation(() => child);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.signal).toBeNull();
|
||||
expect(result.termination).toBe("exit");
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe("windows command wrapper behavior", () => {
|
||||
@@ -181,25 +179,21 @@ describe("windows command wrapper behavior", () => {
|
||||
});
|
||||
|
||||
it("wraps .cmd commands via cmd.exe in runCommandWithTimeout", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const expectedComSpec = expectedTrustedCmdExe();
|
||||
|
||||
spawnMock.mockImplementation(
|
||||
(_command: string, _args: string[], _options: Record<string, unknown>) => createMockChild(),
|
||||
);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["pnpm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expectCmdWrappedInvocation({ captured, expectedComSpec });
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores ComSpec when selecting the Windows command wrapper", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const previousComSpec = process.env.ComSpec;
|
||||
const previousSystemRoot = process.env.SystemRoot;
|
||||
process.env.ComSpec = "C:\\workspace\\evil\\cmd.exe";
|
||||
@@ -210,12 +204,14 @@ describe("windows command wrapper behavior", () => {
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await runCommandWithTimeout(["pnpm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expectCmdWrappedInvocation({
|
||||
captured,
|
||||
expectedComSpec: path.win32.join("C:\\Windows", "System32", "cmd.exe"),
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["pnpm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expectCmdWrappedInvocation({
|
||||
captured,
|
||||
expectedComSpec: path.win32.join("C:\\Windows", "System32", "cmd.exe"),
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
if (previousComSpec === undefined) {
|
||||
@@ -228,12 +224,10 @@ describe("windows command wrapper behavior", () => {
|
||||
} else {
|
||||
process.env.SystemRoot = previousSystemRoot;
|
||||
}
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects unsafe Windows root values when selecting the command wrapper", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const previousSystemRoot = process.env.SystemRoot;
|
||||
const previousWindir = process.env.WINDIR;
|
||||
|
||||
@@ -242,29 +236,31 @@ describe("windows command wrapper behavior", () => {
|
||||
);
|
||||
|
||||
try {
|
||||
for (const unsafeRoot of [
|
||||
"\\\\evil\\share",
|
||||
"C:\\Windows;C:\\evil",
|
||||
"\\Windows",
|
||||
"relative\\path",
|
||||
]) {
|
||||
_resetWindowsInstallRootsForTests({ queryRegistryValue: () => null });
|
||||
// Set every install-root env source to the unsafe value so the
|
||||
// resolver rejects each one and falls through to the safe default.
|
||||
// Deleting WINDIR here is unreliable on real Windows runners, so
|
||||
// overwrite it with the same rejected payload.
|
||||
process.env.SystemRoot = unsafeRoot;
|
||||
process.env.WINDIR = unsafeRoot;
|
||||
spawnMock.mockClear();
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
for (const unsafeRoot of [
|
||||
"\\\\evil\\share",
|
||||
"C:\\Windows;C:\\evil",
|
||||
"\\Windows",
|
||||
"relative\\path",
|
||||
]) {
|
||||
_resetWindowsInstallRootsForTests({ queryRegistryValue: () => null });
|
||||
// Set every install-root env source to the unsafe value so the
|
||||
// resolver rejects each one and falls through to the safe default.
|
||||
// Deleting WINDIR here is unreliable on real Windows runners, so
|
||||
// overwrite it with the same rejected payload.
|
||||
process.env.SystemRoot = unsafeRoot;
|
||||
process.env.WINDIR = unsafeRoot;
|
||||
spawnMock.mockClear();
|
||||
|
||||
const result = await runCommandWithTimeout(["pnpm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expectCmdWrappedInvocation({
|
||||
captured,
|
||||
expectedComSpec: path.win32.join("C:\\Windows", "System32", "cmd.exe"),
|
||||
});
|
||||
}
|
||||
const result = await runCommandWithTimeout(["pnpm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expectCmdWrappedInvocation({
|
||||
captured,
|
||||
expectedComSpec: path.win32.join("C:\\Windows", "System32", "cmd.exe"),
|
||||
});
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
if (previousSystemRoot === undefined) {
|
||||
delete process.env.SystemRoot;
|
||||
@@ -276,19 +272,17 @@ describe("windows command wrapper behavior", () => {
|
||||
} else {
|
||||
process.env.WINDIR = previousWindir;
|
||||
}
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("wraps corepack.cmd via cmd.exe in runCommandWithTimeout", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const expectedComSpec = expectedTrustedCmdExe();
|
||||
|
||||
spawnMock.mockImplementation(
|
||||
(_command: string, _args: string[], _options: Record<string, unknown>) => createMockChild(),
|
||||
);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["corepack", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
@@ -297,52 +291,44 @@ describe("windows command wrapper behavior", () => {
|
||||
expect(captured[1][3]).toContain("corepack.cmd --version");
|
||||
expect(captured[2].windowsHide).toBe(true);
|
||||
expect(captured[2].windowsVerbatimArguments).toBe(true);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps child exitCode when close reports null on Windows npm shims", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const child = createMockChild({ closeCode: null, exitCode: 0 });
|
||||
|
||||
spawnMock.mockImplementation(() => child);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("spawns node + npm-cli.js for npm argv to avoid direct .cmd execution", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const existsSpy = vi.spyOn(fs, "existsSync").mockReturnValue(true);
|
||||
const child = createMockChild({ closeCode: 0, exitCode: 0 });
|
||||
|
||||
spawnMock.mockImplementation(() => child);
|
||||
|
||||
try {
|
||||
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expect(captured[0]).toBe(process.execPath);
|
||||
expect(captured[1][0]).toBe(
|
||||
path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js"),
|
||||
);
|
||||
expect(captured[1][1]).toBe("--version");
|
||||
expect(captured[2].windowsHide).toBe(true);
|
||||
expect(captured[2].windowsVerbatimArguments).toBeUndefined();
|
||||
expect(captured[2].stdio).toEqual(["inherit", "pipe", "pipe"]);
|
||||
} finally {
|
||||
existsSpy.mockRestore();
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
await withRestoredMocks([existsSpy], async () => {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expect(captured[0]).toBe(process.execPath);
|
||||
expect(captured[1][0]).toBe(
|
||||
path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js"),
|
||||
);
|
||||
expect(captured[1][1]).toBe("--version");
|
||||
expect(captured[2].windowsHide).toBe(true);
|
||||
expect(captured[2].windowsVerbatimArguments).toBeUndefined();
|
||||
expect(captured[2].stdio).toEqual(["inherit", "pipe", "pipe"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to npm.cmd when npm-cli.js is unavailable", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const existsSpy = vi.spyOn(fs, "existsSync").mockReturnValue(false);
|
||||
const expectedComSpec = expectedTrustedCmdExe();
|
||||
|
||||
@@ -350,24 +336,22 @@ describe("windows command wrapper behavior", () => {
|
||||
(_command: string, _args: string[], _options: Record<string, unknown>) => createMockChild(),
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expect(captured[0]).toBe(expectedComSpec);
|
||||
expect(captured[1].slice(0, 3)).toEqual(["/d", "/s", "/c"]);
|
||||
expect(captured[1][3]).toContain("npm.cmd --version");
|
||||
expect(captured[2].windowsHide).toBe(true);
|
||||
expect(captured[2].windowsVerbatimArguments).toBe(true);
|
||||
expect(captured[2].stdio).toEqual(["inherit", "pipe", "pipe"]);
|
||||
} finally {
|
||||
existsSpy.mockRestore();
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
await withRestoredMocks([existsSpy], async () => {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
expect(captured[0]).toBe(expectedComSpec);
|
||||
expect(captured[1].slice(0, 3)).toEqual(["/d", "/s", "/c"]);
|
||||
expect(captured[1][3]).toContain("npm.cmd --version");
|
||||
expect(captured[2].windowsHide).toBe(true);
|
||||
expect(captured[2].windowsVerbatimArguments).toBe(true);
|
||||
expect(captured[2].stdio).toEqual(["inherit", "pipe", "pipe"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("waits for Windows exitCode settlement after close reports null", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const child = createMockChild({
|
||||
closeCode: null,
|
||||
exitCode: null,
|
||||
@@ -377,12 +361,10 @@ describe("windows command wrapper behavior", () => {
|
||||
|
||||
spawnMock.mockImplementation(() => child);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("treats shimmed Windows commands without a reported exit code as success when they close cleanly", async () => {
|
||||
@@ -394,7 +376,6 @@ describe("windows command wrapper behavior", () => {
|
||||
});
|
||||
|
||||
it("uses cmd.exe wrapper with windowsVerbatimArguments in runExec for .cmd shims", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const expectedComSpec = expectedTrustedCmdExe();
|
||||
|
||||
execFileMock.mockImplementation(
|
||||
@@ -408,18 +389,14 @@ describe("windows command wrapper behavior", () => {
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
await runExec("pnpm", ["--version"], 1000);
|
||||
const captured = requireExecFileCall(0);
|
||||
expectCmdWrappedInvocation({ captured, expectedComSpec });
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("sets windowsHide on direct runExec invocations too", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
|
||||
execFileMock.mockImplementation(
|
||||
(
|
||||
_command: string,
|
||||
@@ -431,25 +408,21 @@ describe("windows command wrapper behavior", () => {
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
await runExec("node", ["--version"], 1000);
|
||||
const captured = requireExecFileCall(0);
|
||||
expect(captured[0]).toBe("node");
|
||||
expect(captured[1]).toEqual(["--version"]);
|
||||
expect(captured[2].windowsHide).toBe(true);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("sets windowsHide on direct runCommandWithTimeout invocations too", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
|
||||
spawnMock.mockImplementation(
|
||||
(_command: string, _args: string[], _options: Record<string, unknown>) => createMockChild(),
|
||||
);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runCommandWithTimeout(["node", "--version"], { timeoutMs: 1000 });
|
||||
expect(result.code).toBe(0);
|
||||
const captured = requireSpawnCall(0);
|
||||
@@ -457,45 +430,42 @@ describe("windows command wrapper behavior", () => {
|
||||
expect(captured[1]).toEqual(["--version"]);
|
||||
expect(captured[2].windowsHide).toBe(true);
|
||||
expect(captured[2].windowsVerbatimArguments).toBeUndefined();
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("kills the Windows process tree when the overall timeout elapses", async () => {
|
||||
vi.useFakeTimers();
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const child = createMockChild({ autoClose: false });
|
||||
const taskkillChild = createMockChild();
|
||||
|
||||
spawnMock.mockImplementationOnce(() => child).mockImplementationOnce(() => taskkillChild);
|
||||
|
||||
try {
|
||||
const resultPromise = runCommandWithTimeout(["node", "idle.js"], { timeoutMs: 80 });
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const resultPromise = runCommandWithTimeout(["node", "idle.js"], { timeoutMs: 80 });
|
||||
|
||||
await vi.advanceTimersByTimeAsync(81);
|
||||
expect(child.kill).not.toHaveBeenCalled();
|
||||
expect(spawnMock).toHaveBeenCalledTimes(2);
|
||||
const taskkillCall = requireSpawnCall(1);
|
||||
expect(taskkillCall[0]).toBe("taskkill");
|
||||
expect(taskkillCall[1]).toEqual(["/PID", "1234", "/T", "/F"]);
|
||||
expect(taskkillCall[2]).toEqual({
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
await vi.advanceTimersByTimeAsync(81);
|
||||
expect(child.kill).not.toHaveBeenCalled();
|
||||
expect(spawnMock).toHaveBeenCalledTimes(2);
|
||||
const taskkillCall = requireSpawnCall(1);
|
||||
expect(taskkillCall[0]).toBe("taskkill");
|
||||
expect(taskkillCall[1]).toEqual(["/PID", "1234", "/T", "/F"]);
|
||||
expect(taskkillCall[2]).toEqual({
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
child.emit("close", null, "SIGKILL");
|
||||
const result = await resultPromise;
|
||||
expect(result.termination).toBe("timeout");
|
||||
expect(result.code).not.toBe(0);
|
||||
});
|
||||
|
||||
child.emit("close", null, "SIGKILL");
|
||||
const result = await resultPromise;
|
||||
expect(result.termination).toBe("timeout");
|
||||
expect(result.code).not.toBe(0);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("decodes GBK stdout and stderr from runExec on Windows", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const stdout = Buffer.from([0xb2, 0xe2, 0xca, 0xd4]);
|
||||
const stderr = Buffer.from([0xa3, 0xbb]);
|
||||
|
||||
@@ -510,20 +480,16 @@ describe("windows command wrapper behavior", () => {
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const result = await runExec("node", ["gbk-output.js"], 1000);
|
||||
expect(result.stdout).toBe("测试");
|
||||
expect(result.stderr).toBe(";");
|
||||
const captured = requireExecFileCall(0);
|
||||
expect(captured[2].encoding).toBe("buffer");
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers valid UTF-8 stdout from runExec on Windows", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
|
||||
execFileMock.mockImplementation(
|
||||
(
|
||||
_command: string,
|
||||
@@ -535,18 +501,15 @@ describe("windows command wrapper behavior", () => {
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
await expect(runExec("node", ["utf8-output.js"], 1000)).resolves.toEqual({
|
||||
stdout: "测试",
|
||||
stderr: "",
|
||||
});
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("decodes spawn stdout once so GBK characters split across chunks survive", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const child = createMockChild({ autoClose: false });
|
||||
spawnMock.mockImplementation(() => {
|
||||
queueMicrotask(() => {
|
||||
@@ -558,7 +521,7 @@ describe("windows command wrapper behavior", () => {
|
||||
return child;
|
||||
});
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
await expect(
|
||||
runCommandWithTimeout(["node", "gbk-output.js"], { timeoutMs: 1000 }),
|
||||
).resolves.toEqual({
|
||||
@@ -571,8 +534,6 @@ describe("windows command wrapper behavior", () => {
|
||||
termination: "exit",
|
||||
noOutputTimedOut: false,
|
||||
});
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { INVALID_EXEC_SECRET_REF_IDS } from "../test-utils/secret-ref-test-vectors.js";
|
||||
import { withMockedWindowsPlatform } from "../test-utils/vitest-spies.js";
|
||||
import {
|
||||
resolveSecretRefString,
|
||||
resolveSecretRefValue,
|
||||
@@ -503,9 +504,7 @@ describe("secret ref resolver", () => {
|
||||
});
|
||||
|
||||
it("fails closed on Windows when file provider ACL source is unknown", async () => {
|
||||
vi.spyOn(process, "platform", "get").mockReturnValue("win32" as unknown as NodeJS.Platform);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const dir = await createCaseDir("win-acl");
|
||||
const filePath = path.join(dir, "secrets.json");
|
||||
await writeSecureFile(filePath, '{"token":"abc123"}');
|
||||
@@ -524,15 +523,11 @@ describe("secret ref resolver", () => {
|
||||
},
|
||||
),
|
||||
).rejects.toThrow(/ACL verification unavailable on Windows/);
|
||||
} finally {
|
||||
vi.restoreAllMocks();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("allows trusted file provider opt-out when Windows ACL source is unknown", async () => {
|
||||
vi.spyOn(process, "platform", "get").mockReturnValue("win32" as unknown as NodeJS.Platform);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
const dir = await createCaseDir("win-acl-opt-out");
|
||||
const filePath = path.join(dir, "secrets.json");
|
||||
await writeSecureFile(filePath, '{"token":"abc123"}');
|
||||
@@ -550,20 +545,14 @@ describe("secret ref resolver", () => {
|
||||
},
|
||||
);
|
||||
expect(value).toBe("abc123");
|
||||
} finally {
|
||||
vi.restoreAllMocks();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("fails closed on Windows when exec provider ACL source is unknown", async () => {
|
||||
vi.spyOn(process, "platform", "get").mockReturnValue("win32" as unknown as NodeJS.Platform);
|
||||
|
||||
try {
|
||||
await withMockedWindowsPlatform(async () => {
|
||||
await expect(resolveExecSecret(execProtocolV1ScriptPath)).rejects.toThrow(
|
||||
/ACL verification unavailable on Windows/,
|
||||
);
|
||||
} finally {
|
||||
vi.restoreAllMocks();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,3 +48,9 @@ export function withMockedPlatform<T>(
|
||||
): T | Promise<T> {
|
||||
return withRestoredMocks([vi.spyOn(process, "platform", "get").mockReturnValue(platform)], run);
|
||||
}
|
||||
|
||||
export function withMockedWindowsPlatform<T>(run: () => Promise<T>): Promise<T>;
|
||||
export function withMockedWindowsPlatform<T>(run: () => T): T;
|
||||
export function withMockedWindowsPlatform<T>(run: () => T | Promise<T>): T | Promise<T> {
|
||||
return withMockedPlatform("win32", run);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user