import { afterEach, describe, expect, it, vi } from "vitest"; import type { CatalogComponent, FlowNode } from "../../src/types"; import type { RunContext } from "../../src/engine/types"; import { getHandler } from "../../src/engine/registry"; const mocks = vi.hoisted(() => ({ powershell: vi.fn(), execFile: vi.fn() })); vi.mock("../driver/windows/powershell", () => ({ powershell: mocks.powershell, psString: (value: unknown) => `'${String(value ?? "").replace(/'/g, "''")}'` })); vi.mock("node:child_process", () => ({ execFile: mocks.execFile })); import "../../src/capabilities/data"; import "../../src/capabilities/control"; import "../../src/capabilities/system-extended"; import "../../src/capabilities/windows-system"; function run(id: string, args: Record = {}, signal = new AbortController().signal) { const node: FlowNode = { id: "regression", componentId: id, values: args, options: {}, position: { x: 0, y: 0 } }; return getHandler(id)!({ signal, log: vi.fn(), vars: {} } as unknown as RunContext, node, args, {} as CatalogComponent); } afterEach(() => { vi.useRealTimers(); vi.resetAllMocks(); }); describe.skipIf(process.platform !== "win32")("应用与系统组件缺陷回归", () => { it("没有匹配进程时返回 [] / null,忽略空行和非法 PID", async () => { mocks.powershell.mockResolvedValue(""); await expect(run("App_GetPids", { pattern: "missing.exe" })).resolves.toEqual([]); await expect(run("App_GetPid", { pattern: "missing.exe" })).resolves.toBeNull(); mocks.powershell.mockResolvedValue("\r\n0\r\n123\r\nabc\r\n456\r\n"); await expect(run("App_GetPids", { pattern: "fixture.exe" })).resolves.toEqual([123, 456]); }); it("毫秒等待读取 mileSecond,计时和取消均正确", async () => { vi.useFakeTimers(); const waiting = Promise.resolve(run("Basic_Delay", { mileSecond: "250" })); let done = false; void waiting.then(() => { done = true; }); await vi.advanceTimersByTimeAsync(249); expect(done).toBe(false); await vi.advanceTimersByTimeAsync(1); expect(done).toBe(true); const controller = new AbortController(); const cancelled = Promise.resolve(run("Basic_Delay", { mileSecond: "3000" }, controller.signal)); const rejection = expect(cancelled).rejects.toMatchObject({ code: "cancelled" }); controller.abort(); await rejection; }); it.each([ [42, "number", true], ["42", "number", false], [42, "1", true], [[], "list", true], [{ key: 1 }, "dict", true], [null, "null", true], [false, "bool", true], ["中文", "string", true], [3.5, "int", false] ])("按目标类型判断 %j → %s", (val, into_type, expected) => { expect(run("Basic_JudgeDataType", { val, into_type })).toBe(expected); }); it.each([ ["42.5", "number", 42.5], ["42", "1", 42], ["-3.9", "int", -3], ["false", "bool", false], ["true", "bool", true], [42, "string", "42"] ])("执行类型转换 %j → %s", (val, into_type, expected) => { expect(run("Basic_DataTypeConversion", { val, into_type })).toBe(expected); }); it("非法数值或未知目标类型给出定位错误,避免静默返回原值", () => { expect(() => run("Basic_DataTypeConversion", { val: "abc", into_type: "int" })).toThrow(); expect(() => run("Basic_DataTypeConversion", { val: 1, into_type: "unknown" })).toThrow(); expect(() => run("Basic_JudgeDataType", { val: 1, into_type: "unknown" })).toThrow(); }); it("关闭组件按所选模式执行,非法 PID 不降级为按名称批量关闭", async () => { mocks.powershell.mockResolvedValue(""); await expect(run("App_KillV1", { close_type: "id", pid: "invalid", p_name: "fixture.exe" })).rejects.toMatchObject({ code: "validation_error" }); expect(mocks.powershell).not.toHaveBeenCalled(); await run("App_KillV1", { close_type: "name", pid: 123, p_name: "fixture.exe", kill_all: "0" }); expect(mocks.powershell.mock.calls[0][0]).toContain("SessionId"); expect(mocks.powershell.mock.calls[0][0]).not.toContain("Stop-Process -Id 123"); }); it("唤醒不声称已通过 Windows 登录解锁", async () => { mocks.execFile.mockImplementation((_file, _args, _opts, callback) => callback(null, { stdout: "", stderr: "" })); await expect(run("ScreenControl_ScreenUnlock")).resolves.toMatchObject({ awakened: true, loginUnlockSupported: false }); }); it("屏保自定义密码不受支持时,在触发桌面操作前明确报错", async () => { await expect(run("System_ScreenSaverShowV1", { set_password: "1", password: "" })).rejects.toMatchObject({ code: "validation_error" }); expect(mocks.execFile).not.toHaveBeenCalled(); }); });