mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:01:24 +00:00
test: dedupe and optimize test suites
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const execSyncMock = vi.fn();
|
||||
const execFileSyncMock = vi.fn();
|
||||
const CLI_CREDENTIALS_CACHE_TTL_MS = 15 * 60 * 1000;
|
||||
let readClaudeCliCredentialsCached: typeof import("./cli-credentials.js").readClaudeCliCredentialsCached;
|
||||
let resetCliCredentialCachesForTest: typeof import("./cli-credentials.js").resetCliCredentialCachesForTest;
|
||||
let writeClaudeCliKeychainCredentials: typeof import("./cli-credentials.js").writeClaudeCliKeychainCredentials;
|
||||
let writeClaudeCliCredentials: typeof import("./cli-credentials.js").writeClaudeCliCredentials;
|
||||
let readCodexCliCredentials: typeof import("./cli-credentials.js").readCodexCliCredentials;
|
||||
|
||||
function mockExistingClaudeKeychainItem() {
|
||||
execFileSyncMock.mockImplementation((file: unknown, args: unknown) => {
|
||||
@@ -33,7 +38,6 @@ function getAddGenericPasswordCall() {
|
||||
}
|
||||
|
||||
async function readCachedClaudeCliCredentials(allowKeychainPrompt: boolean) {
|
||||
const { readClaudeCliCredentialsCached } = await import("./cli-credentials.js");
|
||||
return readClaudeCliCredentialsCached({
|
||||
allowKeychainPrompt,
|
||||
ttlMs: CLI_CREDENTIALS_CACHE_TTL_MS,
|
||||
@@ -43,24 +47,31 @@ async function readCachedClaudeCliCredentials(allowKeychainPrompt: boolean) {
|
||||
}
|
||||
|
||||
describe("cli credentials", () => {
|
||||
beforeAll(async () => {
|
||||
({
|
||||
readClaudeCliCredentialsCached,
|
||||
resetCliCredentialCachesForTest,
|
||||
writeClaudeCliKeychainCredentials,
|
||||
writeClaudeCliCredentials,
|
||||
readCodexCliCredentials,
|
||||
} = await import("./cli-credentials.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
execSyncMock.mockReset();
|
||||
execFileSyncMock.mockReset();
|
||||
delete process.env.CODEX_HOME;
|
||||
const { resetCliCredentialCachesForTest } = await import("./cli-credentials.js");
|
||||
resetCliCredentialCachesForTest();
|
||||
});
|
||||
|
||||
it("updates the Claude Code keychain item in place", async () => {
|
||||
mockExistingClaudeKeychainItem();
|
||||
|
||||
const { writeClaudeCliKeychainCredentials } = await import("./cli-credentials.js");
|
||||
|
||||
const ok = writeClaudeCliKeychainCredentials(
|
||||
{
|
||||
access: "new-access",
|
||||
@@ -84,8 +95,6 @@ describe("cli credentials", () => {
|
||||
|
||||
mockExistingClaudeKeychainItem();
|
||||
|
||||
const { writeClaudeCliKeychainCredentials } = await import("./cli-credentials.js");
|
||||
|
||||
const ok = writeClaudeCliKeychainCredentials(
|
||||
{
|
||||
access: maliciousToken,
|
||||
@@ -112,8 +121,6 @@ describe("cli credentials", () => {
|
||||
|
||||
mockExistingClaudeKeychainItem();
|
||||
|
||||
const { writeClaudeCliKeychainCredentials } = await import("./cli-credentials.js");
|
||||
|
||||
const ok = writeClaudeCliKeychainCredentials(
|
||||
{
|
||||
access: "safe-access",
|
||||
@@ -156,8 +163,6 @@ describe("cli credentials", () => {
|
||||
|
||||
const writeKeychain = vi.fn(() => false);
|
||||
|
||||
const { writeClaudeCliCredentials } = await import("./cli-credentials.js");
|
||||
|
||||
const ok = writeClaudeCliCredentials(
|
||||
{
|
||||
access: "new-access",
|
||||
@@ -251,7 +256,6 @@ describe("cli credentials", () => {
|
||||
});
|
||||
});
|
||||
|
||||
const { readCodexCliCredentials } = await import("./cli-credentials.js");
|
||||
const creds = readCodexCliCredentials({ platform: "darwin", execSync: execSyncMock });
|
||||
|
||||
expect(creds).toMatchObject({
|
||||
@@ -281,7 +285,6 @@ describe("cli credentials", () => {
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const { readCodexCliCredentials } = await import("./cli-credentials.js");
|
||||
const creds = readCodexCliCredentials({ execSync: execSyncMock });
|
||||
|
||||
expect(creds).toMatchObject({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
/**
|
||||
* Regression test for #18264: Gateway announcement delivery loop.
|
||||
@@ -55,6 +55,15 @@ vi.mock("./timeout.js", () => ({
|
||||
}));
|
||||
|
||||
describe("announce loop guard (#18264)", () => {
|
||||
let registry: typeof import("./subagent-registry.js");
|
||||
let announceFn: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeAll(async () => {
|
||||
registry = await import("./subagent-registry.js");
|
||||
const subagentAnnounce = await import("./subagent-announce.js");
|
||||
announceFn = vi.mocked(subagentAnnounce.runSubagentAnnounceFlow);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
@@ -67,8 +76,7 @@ describe("announce loop guard (#18264)", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("SubagentRunRecord has announceRetryCount and lastAnnounceRetryAt fields", async () => {
|
||||
const registry = await import("./subagent-registry.js");
|
||||
test("SubagentRunRecord has announceRetryCount and lastAnnounceRetryAt fields", () => {
|
||||
registry.resetSubagentRegistryForTests();
|
||||
|
||||
const now = Date.now();
|
||||
@@ -94,74 +102,51 @@ describe("announce loop guard (#18264)", () => {
|
||||
expect(entry!.lastAnnounceRetryAt).toBeDefined();
|
||||
});
|
||||
|
||||
test("expired entries with high retry count are skipped by resumeSubagentRun", async () => {
|
||||
const registry = await import("./subagent-registry.js");
|
||||
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
|
||||
const announceFn = vi.mocked(runSubagentAnnounceFlow);
|
||||
test.each([
|
||||
{
|
||||
name: "expired entries with high retry count are skipped by resumeSubagentRun",
|
||||
createEntry: (now: number) => ({
|
||||
// Ended 10 minutes ago (well past ANNOUNCE_EXPIRY_MS of 5 min).
|
||||
runId: "test-expired-loop",
|
||||
childSessionKey: "agent:main:subagent:expired-child",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "agent:main:main",
|
||||
task: "expired test task",
|
||||
cleanup: "keep" as const,
|
||||
createdAt: now - 15 * 60_000,
|
||||
startedAt: now - 14 * 60_000,
|
||||
endedAt: now - 10 * 60_000,
|
||||
announceRetryCount: 3,
|
||||
lastAnnounceRetryAt: now - 9 * 60_000,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "entries over retry budget are marked completed without announcing",
|
||||
createEntry: (now: number) => ({
|
||||
runId: "test-retry-budget",
|
||||
childSessionKey: "agent:main:subagent:retry-budget",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "agent:main:main",
|
||||
task: "retry budget test",
|
||||
cleanup: "keep" as const,
|
||||
createdAt: now - 2 * 60_000,
|
||||
startedAt: now - 90_000,
|
||||
endedAt: now - 60_000,
|
||||
announceRetryCount: 3,
|
||||
lastAnnounceRetryAt: now - 30_000,
|
||||
}),
|
||||
},
|
||||
])("$name", ({ createEntry }) => {
|
||||
announceFn.mockClear();
|
||||
|
||||
registry.resetSubagentRegistryForTests();
|
||||
|
||||
const now = Date.now();
|
||||
// Add a run that ended 10 minutes ago (well past ANNOUNCE_EXPIRY_MS of 5 min)
|
||||
// with 3 retries already attempted
|
||||
const entry = {
|
||||
runId: "test-expired-loop",
|
||||
childSessionKey: "agent:main:subagent:expired-child",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "agent:main:main",
|
||||
task: "expired test task",
|
||||
cleanup: "keep",
|
||||
createdAt: now - 15 * 60_000,
|
||||
startedAt: now - 14 * 60_000,
|
||||
endedAt: now - 10 * 60_000, // 10 minutes ago
|
||||
announceRetryCount: 3,
|
||||
lastAnnounceRetryAt: now - 9 * 60_000,
|
||||
};
|
||||
|
||||
loadSubagentRegistryFromDisk.mockReturnValue(new Map([[entry.runId, entry]]));
|
||||
|
||||
// Initialize the registry — this triggers resumeSubagentRun for persisted entries
|
||||
registry.initSubagentRegistry();
|
||||
|
||||
// The announce flow should NOT be called because the entry has exceeded
|
||||
// both the retry count and the expiry window.
|
||||
expect(announceFn).not.toHaveBeenCalled();
|
||||
|
||||
const runs = registry.listSubagentRunsForRequester("agent:main:main");
|
||||
const stored = runs.find((run) => run.runId === entry.runId);
|
||||
expect(stored?.cleanupCompletedAt).toBeDefined();
|
||||
});
|
||||
|
||||
test("entries over retry budget are marked completed without announcing", async () => {
|
||||
const registry = await import("./subagent-registry.js");
|
||||
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
|
||||
const announceFn = vi.mocked(runSubagentAnnounceFlow);
|
||||
announceFn.mockClear();
|
||||
|
||||
registry.resetSubagentRegistryForTests();
|
||||
|
||||
const now = Date.now();
|
||||
const entry = {
|
||||
runId: "test-retry-budget",
|
||||
childSessionKey: "agent:main:subagent:retry-budget",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "agent:main:main",
|
||||
task: "retry budget test",
|
||||
cleanup: "keep",
|
||||
createdAt: now - 2 * 60_000,
|
||||
startedAt: now - 90_000,
|
||||
endedAt: now - 60_000,
|
||||
announceRetryCount: 3,
|
||||
lastAnnounceRetryAt: now - 30_000,
|
||||
};
|
||||
|
||||
const entry = createEntry(Date.now());
|
||||
loadSubagentRegistryFromDisk.mockReturnValue(new Map([[entry.runId, entry]]));
|
||||
|
||||
// Initialization attempts resume once, then gives up for exhausted entries.
|
||||
registry.initSubagentRegistry();
|
||||
|
||||
expect(announceFn).not.toHaveBeenCalled();
|
||||
|
||||
const runs = registry.listSubagentRunsForRequester("agent:main:main");
|
||||
const stored = runs.find((run) => run.runId === entry.runId);
|
||||
expect(stored?.cleanupCompletedAt).toBeDefined();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import "./subagent-registry.mocks.shared.js";
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
@@ -17,15 +17,19 @@ vi.mock("./subagent-registry.store.js", () => ({
|
||||
saveSubagentRegistryToDisk: vi.fn(() => {}),
|
||||
}));
|
||||
|
||||
let subagentRegistry: typeof import("./subagent-registry.js");
|
||||
|
||||
describe("subagent registry nested agent tracking", () => {
|
||||
afterEach(async () => {
|
||||
const mod = await import("./subagent-registry.js");
|
||||
mod.resetSubagentRegistryForTests({ persist: false });
|
||||
beforeAll(async () => {
|
||||
subagentRegistry = await import("./subagent-registry.js");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
subagentRegistry.resetSubagentRegistryForTests({ persist: false });
|
||||
});
|
||||
|
||||
it("listSubagentRunsForRequester returns children of the requesting session", async () => {
|
||||
const { registerSubagentRun, listSubagentRunsForRequester } =
|
||||
await import("./subagent-registry.js");
|
||||
const { registerSubagentRun, listSubagentRunsForRequester } = subagentRegistry;
|
||||
|
||||
// Main agent spawns a depth-1 orchestrator
|
||||
registerSubagentRun({
|
||||
@@ -67,7 +71,7 @@ describe("subagent registry nested agent tracking", () => {
|
||||
});
|
||||
|
||||
it("announce uses requesterSessionKey to route to the correct parent", async () => {
|
||||
const { registerSubagentRun } = await import("./subagent-registry.js");
|
||||
const { registerSubagentRun } = subagentRegistry;
|
||||
// Register a sub-sub-agent whose parent is a sub-agent
|
||||
registerSubagentRun({
|
||||
runId: "run-subsub",
|
||||
@@ -82,7 +86,7 @@ describe("subagent registry nested agent tracking", () => {
|
||||
// When announce fires for the sub-sub-agent, it should target the sub-agent (depth-1),
|
||||
// NOT the main session. The registry entry's requesterSessionKey ensures this.
|
||||
// We verify the registry entry has the correct requesterSessionKey.
|
||||
const { listSubagentRunsForRequester } = await import("./subagent-registry.js");
|
||||
const { listSubagentRunsForRequester } = subagentRegistry;
|
||||
const orchRuns = listSubagentRunsForRequester("agent:main:subagent:orch");
|
||||
expect(orchRuns).toHaveLength(1);
|
||||
expect(orchRuns[0].requesterSessionKey).toBe("agent:main:subagent:orch");
|
||||
@@ -90,8 +94,7 @@ describe("subagent registry nested agent tracking", () => {
|
||||
});
|
||||
|
||||
it("countActiveRunsForSession only counts active children of the specific session", async () => {
|
||||
const { registerSubagentRun, countActiveRunsForSession } =
|
||||
await import("./subagent-registry.js");
|
||||
const { registerSubagentRun, countActiveRunsForSession } = subagentRegistry;
|
||||
|
||||
// Main spawns orchestrator (active)
|
||||
registerSubagentRun({
|
||||
@@ -130,8 +133,7 @@ describe("subagent registry nested agent tracking", () => {
|
||||
});
|
||||
|
||||
it("countActiveDescendantRuns traverses through ended parents", async () => {
|
||||
const { addSubagentRunForTests, countActiveDescendantRuns } =
|
||||
await import("./subagent-registry.js");
|
||||
const { addSubagentRunForTests, countActiveDescendantRuns } = subagentRegistry;
|
||||
|
||||
addSubagentRunForTests({
|
||||
runId: "run-parent-ended",
|
||||
|
||||
Reference in New Issue
Block a user