|
|
|
|
@@ -1,9 +1,9 @@
|
|
|
|
|
import { describe, expect, test } from "bun:test";
|
|
|
|
|
|
|
|
|
|
import type { ResolvedCommandTarget } from "../../../../../src/server/checker/runner/command/types";
|
|
|
|
|
import type { ResolvedCommandTarget } from "../../../../../src/server/checker/runner/cmd/types";
|
|
|
|
|
import type { CheckerContext } from "../../../../../src/server/checker/runner/types";
|
|
|
|
|
|
|
|
|
|
import { CommandChecker } from "../../../../../src/server/checker/runner/command/execute";
|
|
|
|
|
import { CommandChecker } from "../../../../../src/server/checker/runner/cmd/execute";
|
|
|
|
|
|
|
|
|
|
const checker = new CommandChecker();
|
|
|
|
|
|
|
|
|
|
@@ -18,37 +18,37 @@ function makeCtx(timeoutMs = 5000): CheckerContext {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function makeTarget(
|
|
|
|
|
command: Partial<ResolvedCommandTarget["command"]>,
|
|
|
|
|
cmd: Partial<ResolvedCommandTarget["cmd"]>,
|
|
|
|
|
overrides?: Partial<ResolvedCommandTarget>,
|
|
|
|
|
): ResolvedCommandTarget {
|
|
|
|
|
return {
|
|
|
|
|
command: {
|
|
|
|
|
args: ["hello"],
|
|
|
|
|
cwd: "/tmp",
|
|
|
|
|
cmd: {
|
|
|
|
|
args: ["-e", "console.log('hello')"],
|
|
|
|
|
cwd: process.cwd(),
|
|
|
|
|
env: processEnv,
|
|
|
|
|
exec: "echo",
|
|
|
|
|
exec: "bun",
|
|
|
|
|
maxOutputBytes: 1024 * 1024,
|
|
|
|
|
...command,
|
|
|
|
|
...cmd,
|
|
|
|
|
},
|
|
|
|
|
group: "default",
|
|
|
|
|
intervalMs: 60000,
|
|
|
|
|
name: "test-cmd",
|
|
|
|
|
timeoutMs: 5000,
|
|
|
|
|
type: "command",
|
|
|
|
|
type: "cmd",
|
|
|
|
|
...overrides,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("CommandChecker", () => {
|
|
|
|
|
test("exitCode=0 成功", async () => {
|
|
|
|
|
const result = await checker.execute(makeTarget({ args: [], exec: "true" }), makeCtx());
|
|
|
|
|
const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(0)"], exec: "bun" }), makeCtx());
|
|
|
|
|
expect(result.matched).toBe(true);
|
|
|
|
|
expect(result.statusDetail).toBe("exitCode=0");
|
|
|
|
|
expect(result.failure).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("exitCode=1 不匹配默认 [0]", async () => {
|
|
|
|
|
const result = await checker.execute(makeTarget({ args: [], exec: "false" }), makeCtx());
|
|
|
|
|
const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(1)"], exec: "bun" }), makeCtx());
|
|
|
|
|
expect(result.matched).toBe(false);
|
|
|
|
|
expect(result.statusDetail).toBe("exitCode=1");
|
|
|
|
|
expect(result.failure!.phase).toBe("exitCode");
|
|
|
|
|
@@ -56,7 +56,7 @@ describe("CommandChecker", () => {
|
|
|
|
|
|
|
|
|
|
test("exitCode=1 匹配自定义 [1]", async () => {
|
|
|
|
|
const result = await checker.execute(
|
|
|
|
|
makeTarget({ args: [], exec: "false" }, { expect: { exitCode: [1] } }),
|
|
|
|
|
makeTarget({ args: ["-e", "process.exit(1)"], exec: "bun" }, { expect: { exitCode: [1] } }),
|
|
|
|
|
makeCtx(),
|
|
|
|
|
);
|
|
|
|
|
expect(result.matched).toBe(true);
|
|
|
|
|
@@ -64,26 +64,35 @@ describe("CommandChecker", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("命令不存在返回 spawn 错误", async () => {
|
|
|
|
|
const result = await checker.execute(makeTarget({ exec: "/nonexistent/command/xyz" }), makeCtx());
|
|
|
|
|
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: ["10"], exec: "sleep" }, { timeoutMs: 100 }), makeCtx(100));
|
|
|
|
|
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("超时");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("stdout 输出捕获", async () => {
|
|
|
|
|
const result = await checker.execute(makeTarget({ args: ["hello world"], exec: "echo" }), makeCtx());
|
|
|
|
|
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: ["hello"], exec: "echo" }, { expect: { stdout: [{ contains: "hello" }] } }),
|
|
|
|
|
makeTarget(
|
|
|
|
|
{ args: ["-e", "console.log('hello')"], exec: "bun" },
|
|
|
|
|
{ expect: { stdout: [{ contains: "hello" }] } },
|
|
|
|
|
),
|
|
|
|
|
makeCtx(),
|
|
|
|
|
);
|
|
|
|
|
expect(result.matched).toBe(true);
|
|
|
|
|
@@ -91,7 +100,10 @@ describe("CommandChecker", () => {
|
|
|
|
|
|
|
|
|
|
test("stdout 不匹配 expect", async () => {
|
|
|
|
|
const result = await checker.execute(
|
|
|
|
|
makeTarget({ args: ["hello"], exec: "echo" }, { expect: { stdout: [{ contains: "nonexistent" }] } }),
|
|
|
|
|
makeTarget(
|
|
|
|
|
{ args: ["-e", "console.log('hello')"], exec: "bun" },
|
|
|
|
|
{ expect: { stdout: [{ contains: "nonexistent" }] } },
|
|
|
|
|
),
|
|
|
|
|
makeCtx(),
|
|
|
|
|
);
|
|
|
|
|
expect(result.matched).toBe(false);
|
|
|
|
|
@@ -100,7 +112,10 @@ describe("CommandChecker", () => {
|
|
|
|
|
|
|
|
|
|
test("stderr 匹配 expect", async () => {
|
|
|
|
|
const result = await checker.execute(
|
|
|
|
|
makeTarget({ args: ["-c", "echo error >&2"], exec: "bash" }, { expect: { stderr: [{ contains: "error" }] } }),
|
|
|
|
|
makeTarget(
|
|
|
|
|
{ args: ["-e", "process.stderr.write('error\\n')"], exec: "bun" },
|
|
|
|
|
{ expect: { stderr: [{ contains: "error" }] } },
|
|
|
|
|
),
|
|
|
|
|
makeCtx(),
|
|
|
|
|
);
|
|
|
|
|
expect(result.matched).toBe(true);
|
|
|
|
|
@@ -108,7 +123,7 @@ describe("CommandChecker", () => {
|
|
|
|
|
|
|
|
|
|
test("输出超过 maxOutputBytes", async () => {
|
|
|
|
|
const result = await checker.execute(
|
|
|
|
|
makeTarget({ args: ["-c", "yes | head -1000"], exec: "bash", maxOutputBytes: 10 }),
|
|
|
|
|
makeTarget({ args: ["-e", "process.stdout.write('y\\n'.repeat(1000))"], exec: "bun", maxOutputBytes: 10 }),
|
|
|
|
|
makeCtx(),
|
|
|
|
|
);
|
|
|
|
|
expect(result.matched).toBe(false);
|
|
|
|
|
@@ -116,7 +131,7 @@ describe("CommandChecker", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("durationMs 非空", async () => {
|
|
|
|
|
const result = await checker.execute(makeTarget({ args: [], exec: "true" }), makeCtx());
|
|
|
|
|
const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(0)"], exec: "bun" }), makeCtx());
|
|
|
|
|
expect(result.durationMs).not.toBeNull();
|
|
|
|
|
expect(result.durationMs!).toBeGreaterThanOrEqual(0);
|
|
|
|
|
});
|
|
|
|
|
@@ -134,8 +149,8 @@ describe("CommandChecker", () => {
|
|
|
|
|
makeTarget(
|
|
|
|
|
{
|
|
|
|
|
args: ["-e", "console.log(process.env.DIAL_TEST_ENV ?? '')"],
|
|
|
|
|
env: { DIAL_TEST_ENV: "resolved-env" },
|
|
|
|
|
exec: process.execPath,
|
|
|
|
|
env: { ...processEnv, DIAL_TEST_ENV: "resolved-env" },
|
|
|
|
|
exec: "bun",
|
|
|
|
|
},
|
|
|
|
|
{ expect: { stdout: [{ contains: "resolved-env" }] } },
|
|
|
|
|
),
|
|
|
|
|
@@ -146,11 +161,11 @@ describe("CommandChecker", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("serialize 返回命令摘要和 config JSON", () => {
|
|
|
|
|
const target = makeTarget({ args: ["hello"], exec: "echo" });
|
|
|
|
|
const target = makeTarget({ args: ["-e", "console.log('hello')"], exec: "bun" });
|
|
|
|
|
const s = checker.serialize(target);
|
|
|
|
|
expect(s.target).toBe("exec echo hello");
|
|
|
|
|
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("echo");
|
|
|
|
|
expect(config.args).toEqual(["hello"]);
|
|
|
|
|
expect(config.exec).toBe("bun");
|
|
|
|
|
expect(config.args).toEqual(["-e", "console.log('hello')"]);
|
|
|
|
|
});
|
|
|
|
|
});
|