import { describe, expect, test } from "bun:test"; import type { ResolvedCommandTarget } from "../../../../../src/server/checker/runner/cmd/types"; import type { CheckerContext, ResolveContext } from "../../../../../src/server/checker/runner/types"; import { CommandChecker } from "../../../../../src/server/checker/runner/cmd/execute"; const checker = new CommandChecker(); const processEnv = Object.fromEntries( Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined), ); function makeCtx(timeoutMs = 5000): CheckerContext { const controller = new AbortController(); setTimeout(() => controller.abort(), timeoutMs); return { signal: controller.signal }; } function makeResolveContext(): ResolveContext { return { configDir: process.cwd(), defaultIntervalMs: 30000, defaults: {}, defaultTimeoutMs: 10000 }; } function makeTarget( cmd: Partial, overrides?: Partial, ): ResolvedCommandTarget { return { cmd: { args: ["-e", "console.log('hello')"], cwd: process.cwd(), env: processEnv, exec: "bun", maxOutputBytes: 1024 * 1024, ...cmd, }, description: null, group: "default", id: "test-cmd", intervalMs: 60000, name: "test-cmd", timeoutMs: 5000, type: "cmd", ...overrides, }; } describe("CommandChecker", () => { test("exitCode=0 成功", async () => { const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(0)"], exec: "bun" }), makeCtx()); expect(result.matched).toBe(true); expect(result.observation).toMatchObject({ exitCode: 0 }); expect(result.failure).toBeNull(); }); test("exitCode=1 不匹配默认 [0]", async () => { const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(1)"], exec: "bun" }), makeCtx()); expect(result.matched).toBe(false); expect(result.observation).toMatchObject({ exitCode: 1 }); expect(result.failure!.phase).toBe("exitCode"); }); test("exitCode=1 匹配自定义 [1]", async () => { const result = await checker.execute( makeTarget({ args: ["-e", "process.exit(1)"], exec: "bun" }, { expect: { exitCode: [1] } }), makeCtx(), ); expect(result.matched).toBe(true); expect(result.observation).toMatchObject({ exitCode: 1 }); }); test("命令不存在返回 spawn 错误", async () => { const result = await checker.execute(makeTarget({ exec: "dial-command-not-found-xyz" }), makeCtx()); expect(result.matched).toBe(false); expect(result.failure!.phase).toBe("exitCode"); expect(result.failure!.message).toBeTruthy(); }); test("超时返回错误", async () => { const result = await checker.execute( makeTarget({ args: ["-e", "await Bun.sleep(10000)"], exec: "bun" }, { timeoutMs: 100 }), makeCtx(100), ); expect(result.matched).toBe(false); expect(result.failure!.message).toContain("超时"); expect(result.observation?.["error"]).toContain("超时"); }); test("stdout 输出捕获", async () => { const result = await checker.execute( makeTarget({ args: ["-e", "console.log('hello world')"], exec: "bun" }), makeCtx(), ); expect(result.matched).toBe(true); }); test("stdout 匹配 expect", async () => { const result = await checker.execute( makeTarget( { args: ["-e", "console.log('hello')"], exec: "bun" }, { expect: { exitCode: [0], stdout: [{ kind: "value", matcher: { contains: "hello" } }] } }, ), makeCtx(), ); expect(result.matched).toBe(true); }); test("stdout 不匹配 expect", async () => { const result = await checker.execute( makeTarget( { args: ["-e", "console.log('hello')"], exec: "bun" }, { expect: { exitCode: [0], stdout: [{ kind: "value", matcher: { contains: "nonexistent" } }] } }, ), makeCtx(), ); expect(result.matched).toBe(false); expect(result.failure!.phase).toBe("stdout"); }); test("stderr 匹配 expect", async () => { const result = await checker.execute( makeTarget( { args: ["-e", "process.stderr.write('error\\n')"], exec: "bun" }, { expect: { exitCode: [0], stderr: [{ kind: "value", matcher: { contains: "error" } }] } }, ), makeCtx(), ); expect(result.matched).toBe(true); }); test("输出超过 maxOutputBytes", async () => { const result = await checker.execute( makeTarget({ args: ["-e", "process.stdout.write('y\\n'.repeat(1000))"], exec: "bun", maxOutputBytes: 10 }), makeCtx(), ); expect(result.matched).toBe(false); expect(result.failure!.message).toContain("超过限制"); expect(result.observation?.["error"]).toContain("超过限制"); }); test("durationMs 非空", async () => { const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(0)"], exec: "bun" }), makeCtx()); expect(result.durationMs).not.toBeNull(); expect(result.durationMs!).toBeGreaterThanOrEqual(0); }); test("不使用 shell,通配符不被展开", async () => { const result = await checker.execute( makeTarget( { args: ["-e", "console.log('*')"], exec: "bun" }, { expect: { exitCode: [0], stdout: [{ kind: "value", matcher: { contains: "*" } }] } }, ), makeCtx(), ); expect(result.matched).toBe(true); }); test("execute 使用 resolved env", async () => { const result = await checker.execute( makeTarget( { args: ["-e", "console.log(process.env.DIAL_TEST_ENV ?? '')"], env: { ...processEnv, DIAL_TEST_ENV: "resolved-env" }, exec: "bun", }, { expect: { exitCode: [0], stdout: [{ kind: "value", matcher: { contains: "resolved-env" } }] } }, ), makeCtx(), ); expect(result.matched).toBe(true); }); test("serialize 返回命令摘要和 config JSON", () => { const target = makeTarget({ args: ["-e", "console.log('hello')"], exec: "bun" }); const s = checker.serialize(target); expect(s.target).toBe("exec bun -e console.log('hello')"); const config = JSON.parse(s.config) as { args: string[]; exec: string }; expect(config.exec).toBe("bun"); expect(config.args).toEqual(["-e", "console.log('hello')"]); }); test("resolve 未配置 expect 时物化默认 exitCode", () => { const result = checker.resolve({ cmd: { exec: "true" }, id: "test", type: "cmd" }, makeResolveContext()); expect(result.rawExpect).toBeUndefined(); expect(result.expect).toEqual({ exitCode: [0] }); }); });