import { describe, expect, test } from "bun:test"; import { checkCommandExpect } from "../../../../src/server/checker/expect/command"; import type { CommandObservation } from "../../../../src/server/checker/expect/command"; import type { CommandExpectConfig } from "../../../../src/server/checker/types"; function obs(overrides: Partial = {}): CommandObservation { return { exitCode: 0, stdout: "", stderr: "", durationMs: 100, ...overrides, }; } describe("checkCommandExpect", () => { test("无 expect 配置时默认检查 exitCode [0] 匹配成功", () => { const r = checkCommandExpect(obs()); expect(r.matched).toBe(true); expect(r.failure).toBeNull(); }); test("无 expect 配置时 exitCode 非 0 匹配失败", () => { const r = checkCommandExpect(obs({ exitCode: 1 })); expect(r.matched).toBe(false); expect(r.failure).not.toBeNull(); expect(r.failure!.phase).toBe("exitCode"); expect(r.failure!.kind).toBe("mismatch"); }); test("exitCode 匹配指定退出码", () => { const cfg: CommandExpectConfig = { exitCode: [0, 1] }; expect(checkCommandExpect(obs({ exitCode: 0 }), cfg).matched).toBe(true); expect(checkCommandExpect(obs({ exitCode: 1 }), cfg).matched).toBe(true); expect(checkCommandExpect(obs({ exitCode: 2 }), cfg).matched).toBe(false); }); test("exitCode 不匹配返回 phase=exitCode 的失败", () => { const r = checkCommandExpect(obs({ exitCode: 2 }), { exitCode: [0] }); expect(r.matched).toBe(false); expect(r.failure!.phase).toBe("exitCode"); expect(r.failure!.expected).toEqual([0]); expect(r.failure!.actual).toBe(2); }); test("duration 在限制内匹配成功", () => { const r = checkCommandExpect(obs({ durationMs: 50 }), { maxDurationMs: 100 }); expect(r.matched).toBe(true); }); test("duration 超过限制匹配失败", () => { const r = checkCommandExpect(obs({ durationMs: 200 }), { maxDurationMs: 100 }); expect(r.matched).toBe(false); expect(r.failure!.phase).toBe("duration"); }); test("stdout TextRule 数组匹配", () => { const o = obs({ stdout: "build completed successfully" }); expect(checkCommandExpect(o, { stdout: [{ contains: "completed" }] }).matched).toBe(true); expect(checkCommandExpect(o, { stdout: [{ contains: "failed" }] }).matched).toBe(false); expect(checkCommandExpect(o, { stdout: [{ match: "completed.*successfully$" }] }).matched).toBe(true); }); test("stdout 多条规则全部通过", () => { const o = obs({ stdout: "version: 3.2.1, build: ok" }); const r = checkCommandExpect(o, { stdout: [{ contains: "version" }, { match: "\\d+\\.\\d+\\.\\d+" }], }); expect(r.matched).toBe(true); }); test("stdout 第一条规则失败立即返回", () => { const o = obs({ stdout: "error occurred" }); const r = checkCommandExpect(o, { stdout: [{ contains: "success" }, { contains: "error" }], }); expect(r.matched).toBe(false); expect(r.failure!.phase).toBe("stdout"); expect(r.failure!.path).toBe("stdout[0]"); }); test("stderr TextRule 数组匹配", () => { const o = obs({ stderr: "warning: deprecated" }); expect(checkCommandExpect(o, { stderr: [{ contains: "warning" }] }).matched).toBe(true); expect(checkCommandExpect(o, { stderr: [{ contains: "error" }] }).matched).toBe(false); }); test("stdout 失败阻止 stderr 检查", () => { const o = obs({ stdout: "bad output", stderr: "warning message" }); const r = checkCommandExpect(o, { exitCode: [0], stdout: [{ contains: "success" }], stderr: [{ contains: "warning" }], }); expect(r.matched).toBe(false); expect(r.failure!.phase).toBe("stdout"); }); test("stdout 通过但 stderr 失败", () => { const o = obs({ stdout: "ok", stderr: "fatal error" }); const r = checkCommandExpect(o, { stdout: [{ contains: "ok" }], stderr: [{ equals: "clean" }], }); expect(r.matched).toBe(false); expect(r.failure!.phase).toBe("stderr"); }); test("完整流水线 exitCode->duration->stdout->stderr 全部通过", () => { const o = obs({ exitCode: 0, durationMs: 50, stdout: "build success", stderr: "", }); const r = checkCommandExpect(o, { exitCode: [0], maxDurationMs: 100, stdout: [{ contains: "success" }], stderr: [{ empty: true }], }); expect(r.matched).toBe(true); expect(r.failure).toBeNull(); }); test("完整流水线 exitCode 通过但 duration 失败", () => { const o = obs({ exitCode: 0, durationMs: 500 }); const r = checkCommandExpect(o, { exitCode: [0], maxDurationMs: 100, stdout: [{ contains: "ok" }], }); expect(r.matched).toBe(false); expect(r.failure!.phase).toBe("duration"); }); test("完整流水线 exitCode/duration 通过但 stdout 失败", () => { const o = obs({ exitCode: 0, durationMs: 50, stdout: "error" }); const r = checkCommandExpect(o, { exitCode: [0], maxDurationMs: 100, stdout: [{ contains: "success" }], }); expect(r.matched).toBe(false); expect(r.failure!.phase).toBe("stdout"); }); test("完整流水线 exitCode/duration/stdout 通过但 stderr 失败", () => { const o = obs({ exitCode: 0, durationMs: 50, stdout: "ok", stderr: "warning" }); const r = checkCommandExpect(o, { exitCode: [0], maxDurationMs: 100, stdout: [{ contains: "ok" }], stderr: [{ empty: true }], }); expect(r.matched).toBe(false); expect(r.failure!.phase).toBe("stderr"); }); test("stdout 操作符组合", () => { const o = obs({ stdout: "count: 42" }); expect( checkCommandExpect(o, { stdout: [{ contains: "count" }, { match: "\\d+" }], }).matched, ).toBe(true); }); });