mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
Phase 2 (轻 Mock): envUtils, sleep/sequential, memoize, groupToolUses, dangerousPatterns, outputLimits Phase 3 (补全): zodToJsonSchema, PermissionMode, envValidation Phase 4 (工具模块): mcpStringUtils, destructiveCommandWarning, commandSemantics Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
131 lines
4.0 KiB
TypeScript
131 lines
4.0 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { sleep, withTimeout } from "../sleep";
|
|
import { sequential } from "../sequential";
|
|
|
|
// ─── sleep ─────────────────────────────────────────────────────────────
|
|
|
|
describe("sleep", () => {
|
|
test("resolves after timeout", async () => {
|
|
const start = Date.now();
|
|
await sleep(50);
|
|
expect(Date.now() - start).toBeGreaterThanOrEqual(40);
|
|
});
|
|
|
|
test("resolves immediately when signal already aborted", async () => {
|
|
const ac = new AbortController();
|
|
ac.abort();
|
|
const start = Date.now();
|
|
await sleep(10_000, ac.signal);
|
|
expect(Date.now() - start).toBeLessThan(50);
|
|
});
|
|
|
|
test("resolves early on abort (default: no throw)", async () => {
|
|
const ac = new AbortController();
|
|
const start = Date.now();
|
|
const p = sleep(10_000, ac.signal);
|
|
setTimeout(() => ac.abort(), 30);
|
|
await p;
|
|
expect(Date.now() - start).toBeLessThan(200);
|
|
});
|
|
|
|
test("rejects on abort with throwOnAbort", async () => {
|
|
const ac = new AbortController();
|
|
ac.abort();
|
|
await expect(
|
|
sleep(10_000, ac.signal, { throwOnAbort: true })
|
|
).rejects.toThrow("aborted");
|
|
});
|
|
|
|
test("rejects with custom abortError", async () => {
|
|
const ac = new AbortController();
|
|
ac.abort();
|
|
const customErr = () => new Error("custom abort");
|
|
await expect(
|
|
sleep(10_000, ac.signal, { abortError: customErr })
|
|
).rejects.toThrow("custom abort");
|
|
});
|
|
|
|
test("throwOnAbort rejects on mid-sleep abort", async () => {
|
|
const ac = new AbortController();
|
|
const p = sleep(10_000, ac.signal, { throwOnAbort: true });
|
|
setTimeout(() => ac.abort(), 20);
|
|
await expect(p).rejects.toThrow("aborted");
|
|
});
|
|
|
|
test("works without signal", async () => {
|
|
await sleep(10);
|
|
// just verify it resolves
|
|
});
|
|
});
|
|
|
|
// ─── withTimeout ───────────────────────────────────────────────────────
|
|
|
|
describe("withTimeout", () => {
|
|
test("resolves when promise completes before timeout", async () => {
|
|
const result = await withTimeout(
|
|
Promise.resolve(42),
|
|
1000,
|
|
"timed out"
|
|
);
|
|
expect(result).toBe(42);
|
|
});
|
|
|
|
test("rejects when promise takes too long", async () => {
|
|
const slow = new Promise((resolve) => setTimeout(resolve, 5000));
|
|
await expect(
|
|
withTimeout(slow, 50, "operation timed out")
|
|
).rejects.toThrow("operation timed out");
|
|
});
|
|
|
|
test("rejects propagate through", async () => {
|
|
await expect(
|
|
withTimeout(Promise.reject(new Error("inner")), 1000, "timeout")
|
|
).rejects.toThrow("inner");
|
|
});
|
|
});
|
|
|
|
// ─── sequential ────────────────────────────────────────────────────────
|
|
|
|
describe("sequential", () => {
|
|
test("executes calls in order", async () => {
|
|
const order: number[] = [];
|
|
const fn = sequential(async (n: number) => {
|
|
await sleep(10);
|
|
order.push(n);
|
|
return n;
|
|
});
|
|
|
|
const results = await Promise.all([fn(1), fn(2), fn(3)]);
|
|
expect(order).toEqual([1, 2, 3]);
|
|
expect(results).toEqual([1, 2, 3]);
|
|
});
|
|
|
|
test("returns correct result for each call", async () => {
|
|
const fn = sequential(async (x: number) => x * 2);
|
|
const r1 = await fn(5);
|
|
const r2 = await fn(10);
|
|
expect(r1).toBe(10);
|
|
expect(r2).toBe(20);
|
|
});
|
|
|
|
test("propagates errors without blocking queue", async () => {
|
|
const fn = sequential(async (x: number) => {
|
|
if (x === 2) throw new Error("fail");
|
|
return x;
|
|
});
|
|
|
|
const p1 = fn(1);
|
|
const p2 = fn(2);
|
|
const p3 = fn(3);
|
|
|
|
expect(await p1).toBe(1);
|
|
await expect(p2).rejects.toThrow("fail");
|
|
expect(await p3).toBe(3);
|
|
});
|
|
|
|
test("handles single call", async () => {
|
|
const fn = sequential(async (s: string) => s.toUpperCase());
|
|
expect(await fn("hello")).toBe("HELLO");
|
|
});
|
|
});
|