/** Cron 解析器与脱敏工具单元测试 */ import { describe, test, expect } from "bun:test"; import { parseCron, parseCronField, cronMatches } from "../apps/api/src/modules/automation/cron"; import { maskSecrets, isDestructiveCommand, maskString } from "../apps/api/src/common/utils/mask"; import { evaluateCondition } from "../apps/api/src/modules/automation/automation.service"; import { parseDockerPsOutput, assertSafeContainerId } from "../apps/api/src/integrations/docker/docker.service"; describe("cron", () => { test("每分钟表达式匹配", () => { expect(cronMatches("* * * * *", new Date(2026, 8, 8, 10, 30))).toBe(true); }); test("固定分钟匹配", () => { expect(cronMatches("30 10 * * *", new Date(2026, 8, 8, 10, 30))).toBe(true); expect(cronMatches("30 10 * * *", new Date(2026, 8, 8, 10, 31))).toBe(false); }); test("步进表达式 */15", () => { expect(cronMatches("*/15 * * * *", new Date(2026, 8, 8, 10, 0))).toBe(true); expect(cronMatches("*/15 * * * *", new Date(2026, 8, 8, 10, 15))).toBe(true); expect(cronMatches("*/15 * * * *", new Date(2026, 8, 8, 10, 16))).toBe(false); }); test("区间与列表混合", () => { expect(cronMatches("1-5,10 9-17 * * 1-5", new Date(2026, 8, 8, 12, 3))).toBe(true); // 2026-09-08 是周二 expect(cronMatches("1-5,10 9-17 * * 1-5", new Date(2026, 8, 8, 12, 7))).toBe(false); expect(cronMatches("* * * * 0,6", new Date(2026, 8, 8, 12, 0))).toBe(false); // 周二不匹配周末 }); test("非法表达式安全返回 false", () => { expect(cronMatches("not a cron", new Date())).toBe(false); expect(cronMatches("* * * *", new Date())).toBe(false); }); test("字段越界抛错", () => { expect(() => parseCronField("61 * * * *", [0, 59])).toThrow(); expect(() => parseCron("* * * * 9", new Date())).toThrow(); }); test("parseCron 返回 5 个字段集合", () => { expect(parseCron("*/2 0-23 1,15 * SUN-MON".replace("SUN-MON", "0-1")).length).toBe(5); }); }); describe("maskSecrets", () => { test("敏感键被遮蔽", () => { const out = maskSecrets({ password: "abc", token: "xyz", name: "keep" }); expect(out.password).toBe("***"); expect(out.token).toBe("***"); expect(out.name).toBe("keep"); }); test("嵌套对象递归遮蔽", () => { const out = maskSecrets({ a: { apiKey: "secret-value" }, list: [{ privateKey: "x" }] }); expect((out.a as Record).apiKey).toBe("***"); expect((out.list as Record[])[0].privateKey).toBe("***"); }); test("内嵌 Bearer/sk- 值被遮蔽", () => { expect(maskString("Bearer abc.def.ghi")).toBe("Bearer ***"); expect(maskString("key=sk-abc123")).toContain("***"); }); test("深度限制与原语透传", () => { expect(maskSecrets(42)).toBe(42); expect(maskSecrets(null)).toBe(null); }); }); describe("isDestructiveCommand", () => { test("识别破坏性命令", () => { expect(isDestructiveCommand("rm -rf /")).toBe(true); expect(isDestructiveCommand("sudo rm -r /data")).toBe(true); expect(isDestructiveCommand("mkfs.ext4 /dev/sda1")).toBe(true); expect(isDestructiveCommand("dd if=/dev/zero of=/dev/sda")).toBe(true); expect(isDestructiveCommand("reboot")).toBe(true); expect(isDestructiveCommand("DROP TABLE users")).toBe(true); }); test("普通命令放行", () => { expect(isDestructiveCommand("docker ps")).toBe(false); expect(isDestructiveCommand("ls -la /var/log")).toBe(false); expect(isDestructiveCommand("git pull origin main")).toBe(false); expect(isDestructiveCommand("rm file.txt")).toBe(false); // 无 -r/-f 标志 }); }); describe("evaluateCondition", () => { const payload = { branch: "main", nested: { count: 5 }, flag: true }; test("eq / neq / contains", () => { expect(evaluateCondition({ path: "branch", op: "eq", value: "main" }, payload)).toBe(true); expect(evaluateCondition({ path: "branch", op: "neq", value: "dev" }, payload)).toBe(true); expect(evaluateCondition({ path: "branch", op: "contains", value: "ai" }, payload)).toBe(true); }); test("gt / lt / exists", () => { expect(evaluateCondition({ path: "nested.count", op: "gt", value: 3 }, payload)).toBe(true); expect(evaluateCondition({ path: "nested.count", op: "lt", value: 3 }, payload)).toBe(false); expect(evaluateCondition({ path: "nested.count", op: "exists" }, payload)).toBe(true); expect(evaluateCondition({ path: "missing", op: "exists" }, payload)).toBe(false); }); }); describe("parseDockerPsOutput", () => { test("解析 NDJSON 输出", () => { const line1 = JSON.stringify({ ID: "abc123", Names: "web,web1", Image: "nginx", State: "running", Status: "Up 2 hours", Ports: "0.0.0.0:80->80/tcp", Labels: "team=core" }); const line2 = JSON.stringify({ ID: "def456", Names: "db", Image: "postgres", State: "exited", Status: "Exited (0)" }); const result = parseDockerPsOutput(`${line1}\n${line2}\n`); expect(result.length).toBe(2); expect(result[0].name).toBe("web"); expect(result[0].state).toBe("running"); expect(result[0].labels["team"]).toBe("core"); expect(result[1].state).toBe("exited"); }); test("跳过噪音行", () => { expect(parseDockerPsOutput("WARN: something\n{bad json}\n").length).toBe(0); expect(parseDockerPsOutput("").length).toBe(0); }); }); describe("assertSafeContainerId", () => { test("合法 ID 放行", () => { expect(assertSafeContainerId("abc123def456")).toBe("abc123def456"); expect(assertSafeContainerId("my-container.name_1")).toBe("my-container.name_1"); }); test("注入被拒绝", () => { expect(() => assertSafeContainerId("abc; rm -rf /")).toThrow(); expect(() => assertSafeContainerId("$(whoami)")).toThrow(); expect(() => assertSafeContainerId("-oProxyCommand=evil")).toThrow(); }); });