mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 12:41:33 +00:00
test: consolidate OpenClaw test state fixtures (#113576)
* test: consolidate OpenClaw test state fixtures * test(plugin-sdk): expose isolated test state Promote the isolated OpenClaw test-state lifecycle through a narrow published Plugin SDK subpath so extension tests no longer import private core helpers. This intentional SDK surface addition is maintainer-approved. * test: use SDK test-state seam in extensions Route bundled extension suites through the focused repo-local Plugin SDK test-state entrypoint and remove the Codex projector harness exports made stale by fixture consolidation. Keep the seam out of production builds and published package artifacts while auditing its real consumers in the full-tree deadcode scan. * test(plugins): map test-state in package boundaries
This commit is contained in:
committed by
GitHub
parent
3d1369ff4f
commit
332006bc1f
@@ -4,6 +4,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { createOpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
ChromeMcpDocumentUnavailableError,
|
||||
@@ -1467,10 +1468,12 @@ describe("chrome MCP page parsing", () => {
|
||||
const user = "browser-user";
|
||||
const password = "browser-password-1234567890"; // pragma: allowlist secret
|
||||
const cdpUrl = `wss://${user}:${password}@browserless.example/chrome?token=${secretToken}`;
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-chrome-mcp-test-"));
|
||||
const configPath = path.join(tempDir, "openclaw.json");
|
||||
await fs.writeFile(configPath, JSON.stringify({ logging: { redactSensitive: "off" } }));
|
||||
vi.stubEnv("OPENCLAW_CONFIG_PATH", configPath);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-chrome-mcp-test-",
|
||||
});
|
||||
await openClawState.writeConfig({ logging: { redactSensitive: "off" } });
|
||||
const tempDir = openClawState.root;
|
||||
const fakeMcpCommand = path.join(tempDir, "fake-mcp.mjs");
|
||||
await fs.writeFile(
|
||||
fakeMcpCommand,
|
||||
@@ -1505,7 +1508,7 @@ describe("chrome MCP page parsing", () => {
|
||||
} catch (err) {
|
||||
message = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
|
||||
expect(message).toContain("Chrome MCP existing-session attach failed");
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createServer } from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { createOpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { rawDataToString } from "../infra/ws.js";
|
||||
@@ -1738,10 +1739,12 @@ describe("chrome.ts internal", () => {
|
||||
it("buffers stderr chunks when Chrome emits diagnostics while CDP comes up", async () => {
|
||||
// Covers onStderr (appending chunks to the bounded stderr tail) plus the
|
||||
// stderrHint truthy branch on failure.
|
||||
const configDir = await fsp.mkdtemp(path.join(os.tmpdir(), "openclaw-redact-off-"));
|
||||
const configPath = path.join(configDir, "openclaw.json");
|
||||
await fsp.writeFile(configPath, JSON.stringify({ logging: { redactSensitive: "off" } }));
|
||||
vi.stubEnv("OPENCLAW_CONFIG_PATH", configPath);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-redact-off-",
|
||||
});
|
||||
await openClawState.writeConfig({ logging: { redactSensitive: "off" } });
|
||||
const configDir = openClawState.root;
|
||||
const executablePath = path.join(configDir, "chrome-stderr-existing");
|
||||
await fsp.writeFile(executablePath, "");
|
||||
vi.spyOn(fs, "existsSync").mockImplementation((p) => {
|
||||
@@ -1787,7 +1790,7 @@ describe("chrome.ts internal", () => {
|
||||
expect(message).toContain("Chrome stderr:");
|
||||
expect(message).toContain("chrome crash log");
|
||||
expect(message).not.toContain(secretToken);
|
||||
await fsp.rm(configDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
});
|
||||
|
||||
it("omits the sandbox hint on non-linux platforms", async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { BrowserConfig } from "../config/config.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
@@ -18,18 +19,16 @@ const BROWSER_HEADLESS_ENV_KEY = "OPENCLAW_BROWSER_HEADLESS";
|
||||
// Isolate the extension relay secret (read from stateDir/credentials) so the
|
||||
// extension-token assertions do not pick up a developer's real secret file.
|
||||
let isolatedStateDir = "";
|
||||
const prevStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
beforeEach(() => {
|
||||
isolatedStateDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cfg-")));
|
||||
process.env.OPENCLAW_STATE_DIR = isolatedStateDir;
|
||||
let openClawState: OpenClawTestState;
|
||||
beforeEach(async () => {
|
||||
openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-cfg-",
|
||||
});
|
||||
isolatedStateDir = openClawState.stateDir;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (prevStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = prevStateDir;
|
||||
}
|
||||
fs.rmSync(isolatedStateDir, { recursive: true, force: true });
|
||||
afterEach(async () => {
|
||||
await openClawState.cleanup();
|
||||
});
|
||||
|
||||
/** Write a relay secret into the isolated state dir's credentials directory. */
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach } from "vitest";
|
||||
import {
|
||||
describe,
|
||||
registerCodexEventProjectorTestLifecycle,
|
||||
embeddedAgentLog,
|
||||
withTempDir,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
tinyPngBase64,
|
||||
fs,
|
||||
os,
|
||||
path,
|
||||
trackTempDir,
|
||||
createParams,
|
||||
createProjector,
|
||||
buildEmptyToolTelemetry,
|
||||
@@ -21,6 +20,17 @@ import {
|
||||
|
||||
registerCodexEventProjectorTestLifecycle();
|
||||
|
||||
let openClawState: OpenClawTestState;
|
||||
beforeEach(async () => {
|
||||
openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-codex-media-state-",
|
||||
});
|
||||
});
|
||||
afterEach(async () => {
|
||||
await openClawState.cleanup();
|
||||
});
|
||||
|
||||
describe("CodexAppServerEventProjector media projection", () => {
|
||||
it("attaches native Codex image-generation saved paths as reply media", async () => {
|
||||
const projector = await createProjector();
|
||||
@@ -51,9 +61,6 @@ describe("CodexAppServerEventProjector media projection", () => {
|
||||
});
|
||||
|
||||
it("saves raw Codex image-generation results as reply media", async () => {
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-media-state-"));
|
||||
trackTempDir(stateDir);
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
const projector = await createProjector();
|
||||
|
||||
await projector.handleNotification(
|
||||
@@ -194,9 +201,6 @@ describe("CodexAppServerEventProjector media projection", () => {
|
||||
});
|
||||
|
||||
it("dedupes raw and typed Codex image-generation media for the same item", async () => {
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-media-state-"));
|
||||
trackTempDir(stateDir);
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
const projector = await createProjector();
|
||||
const savedPath = "/tmp/codex-home/generated_images/session-1/ig_123.png";
|
||||
|
||||
@@ -230,50 +234,44 @@ describe("CodexAppServerEventProjector media projection", () => {
|
||||
});
|
||||
|
||||
it("prefers gateway-managed image media when the typed event arrives first", async () => {
|
||||
await withTempDir("openclaw-codex-media-state-", async (stateDir) => {
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
const projector = await createProjector();
|
||||
const savedPath = "/home/dev-user/.codex/generated_images/session-1/ig_123.png";
|
||||
const projector = await createProjector();
|
||||
const savedPath = "/home/dev-user/.codex/generated_images/session-1/ig_123.png";
|
||||
|
||||
await projector.handleNotification(
|
||||
forCurrentTurn("item/completed", {
|
||||
item: {
|
||||
type: "imageGeneration",
|
||||
id: "ig_123",
|
||||
status: "completed",
|
||||
revisedPrompt: "A tiny blue square",
|
||||
result: tinyPngBase64,
|
||||
savedPath,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await projector.handleNotification(
|
||||
forCurrentTurn("rawResponseItem/completed", {
|
||||
item: {
|
||||
type: "image_generation_call",
|
||||
id: "ig_123",
|
||||
status: "generating",
|
||||
result: tinyPngBase64,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await projector.handleNotification(
|
||||
forCurrentTurn("item/completed", {
|
||||
item: {
|
||||
type: "imageGeneration",
|
||||
id: "ig_123",
|
||||
status: "completed",
|
||||
revisedPrompt: "A tiny blue square",
|
||||
result: tinyPngBase64,
|
||||
savedPath,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await projector.handleNotification(
|
||||
forCurrentTurn("rawResponseItem/completed", {
|
||||
item: {
|
||||
type: "image_generation_call",
|
||||
id: "ig_123",
|
||||
status: "generating",
|
||||
result: tinyPngBase64,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = projector.buildResult(buildEmptyToolTelemetry());
|
||||
const mediaUrl = result.toolMediaUrls?.[0];
|
||||
const result = projector.buildResult(buildEmptyToolTelemetry());
|
||||
const mediaUrl = result.toolMediaUrls?.[0];
|
||||
|
||||
expect(result.toolMediaUrls).toHaveLength(1);
|
||||
expect(mediaUrl).not.toBe(savedPath);
|
||||
expect(mediaUrl).toContain(`${path.sep}media${path.sep}tool-image-generation${path.sep}`);
|
||||
await expect(fs.readFile(mediaUrl ?? "")).resolves.toEqual(
|
||||
Buffer.from(tinyPngBase64, "base64"),
|
||||
);
|
||||
});
|
||||
expect(result.toolMediaUrls).toHaveLength(1);
|
||||
expect(mediaUrl).not.toBe(savedPath);
|
||||
expect(mediaUrl).toContain(`${path.sep}media${path.sep}tool-image-generation${path.sep}`);
|
||||
await expect(fs.readFile(mediaUrl ?? "")).resolves.toEqual(
|
||||
Buffer.from(tinyPngBase64, "base64"),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves distinct raw image-generation items with identical image bytes", async () => {
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-media-state-"));
|
||||
trackTempDir(stateDir);
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
const projector = await createProjector();
|
||||
|
||||
for (const id of ["ig_raw_1", "ig_raw_2"]) {
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
resetGlobalHookRunner,
|
||||
} from "openclaw/plugin-sdk/hook-runtime";
|
||||
import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { withTempDir } from "openclaw/plugin-sdk/test-env";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { CodexAppServerEventProjector } from "./event-projector.js";
|
||||
import { createCodexTestModel, createCodexTestToolTerminalObserver } from "./test-support.js";
|
||||
@@ -41,11 +40,9 @@ export {
|
||||
initializeGlobalHookRunner,
|
||||
it,
|
||||
onInternalDiagnosticEvent,
|
||||
os,
|
||||
path,
|
||||
SessionManager,
|
||||
vi,
|
||||
withTempDir,
|
||||
};
|
||||
export type { EmbeddedRunAttemptParams, DiagnosticEventPayload };
|
||||
|
||||
@@ -108,10 +105,6 @@ export async function createParams(): Promise<EmbeddedRunAttemptParams> {
|
||||
} as EmbeddedRunAttemptParams;
|
||||
}
|
||||
|
||||
export function trackTempDir(tempDir: string): void {
|
||||
tempDirs.add(tempDir);
|
||||
}
|
||||
|
||||
export async function createProjector(
|
||||
params?: EmbeddedRunAttemptParams,
|
||||
options?: CodexAppServerEventProjectorOptions,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
resetGlobalHookRunner,
|
||||
} from "openclaw/plugin-sdk/hook-runtime";
|
||||
import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { createOpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runCopilotAttempt } from "./attempt.js";
|
||||
import type { CopilotClientPool } from "./runtime.js";
|
||||
@@ -916,12 +917,14 @@ describe("runCopilotAttempt", () => {
|
||||
});
|
||||
|
||||
it("hydrates offloaded prompt images before creating SDK blob attachments", async () => {
|
||||
const stateDir = await fsp.mkdtemp(path.join(tmpdir(), "copilot-offloaded-image-"));
|
||||
const inboundDir = path.join(stateDir, "media", "inbound");
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "copilot-offloaded-image-",
|
||||
});
|
||||
const inboundDir = openClawState.statePath("media", "inbound");
|
||||
const mediaId = "telegram-photo.png";
|
||||
await fsp.mkdir(inboundDir, { recursive: true });
|
||||
await fsp.writeFile(path.join(inboundDir, mediaId), Buffer.from(TINY_PNG_BASE64, "base64"));
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
const sdk = makeFakeSdk();
|
||||
const pool = makeFakePool(sdk);
|
||||
|
||||
@@ -960,8 +963,7 @@ describe("runCopilotAttempt", () => {
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
vi.unstubAllEnvs();
|
||||
await fsp.rm(stateDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as channelInbound from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import type { dispatchReplyWithBufferedBlockDispatcher } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import type { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { createIMessageRpcClient } from "./client.js";
|
||||
@@ -139,6 +140,7 @@ async function runChannelInboundEventForLastRouteTest(params: RunChannelInboundE
|
||||
|
||||
describe("iMessage monitor last-route updates", () => {
|
||||
const tempDirs: string[] = [];
|
||||
const openClawStates: OpenClawTestState[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(channelInbound, "runChannelInboundEvent").mockImplementation(
|
||||
@@ -154,9 +156,10 @@ describe("iMessage monitor last-route updates", () => {
|
||||
expireCachedPrivateApiStatus();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
vi.useRealTimers();
|
||||
await Promise.all(openClawStates.splice(0).map((state) => state.cleanup()));
|
||||
vi.unstubAllEnvs();
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
@@ -1673,9 +1676,12 @@ describe("iMessage monitor last-route updates", () => {
|
||||
});
|
||||
|
||||
it("repairs anchorless group watch payloads before routing or cursor updates", async () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-imsg-anchor-repair-"));
|
||||
tempDirs.push(stateDir);
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
openClawStates.push(
|
||||
await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-imsg-anchor-repair-",
|
||||
}),
|
||||
);
|
||||
|
||||
let onNotification: ((message: { method: string; params: unknown }) => void) | undefined;
|
||||
const client = {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { IMessageRpcClient } from "./client.js";
|
||||
import { loadFreshIMessageReplyCacheForTest } from "./test-support/runtime.js";
|
||||
@@ -75,15 +76,22 @@ function createApprovalText(id = "approval-123"): string {
|
||||
}
|
||||
|
||||
describe("sendMessageIMessage receipts", () => {
|
||||
let openClawState: OpenClawTestState;
|
||||
|
||||
beforeEach(async () => {
|
||||
openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-imessage-send-",
|
||||
});
|
||||
await loadFreshSendModule();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
clearIMessageApprovalReactionTargetsForTest();
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllEnvs();
|
||||
vi.useRealTimers();
|
||||
await openClawState.cleanup();
|
||||
});
|
||||
|
||||
it("attaches a text receipt for native send ids", async () => {
|
||||
@@ -905,8 +913,6 @@ describe("sendMessageIMessage receipts", () => {
|
||||
});
|
||||
|
||||
it("does not persist caption text when the caption follow-up send fails", async () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-imessage-send-"));
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
const client = createRejectingClient(new Error("caption failed"));
|
||||
const runCliJson = vi.fn().mockResolvedValueOnce({ messageId: "p:0/dm-media-guid" });
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Mattermost tests cover reply delivery plugin behavior.
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { ChunkMode } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { createOpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig, PluginRuntime } from "../../runtime-api.js";
|
||||
import {
|
||||
@@ -225,9 +224,11 @@ describe("deliverMattermostReplyPayload", () => {
|
||||
});
|
||||
|
||||
it("passes agent-scoped mediaLocalRoots when sending media paths", async () => {
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mm-state-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-mm-state-",
|
||||
});
|
||||
const stateDir = openClawState.stateDir;
|
||||
|
||||
try {
|
||||
const sendMessage = vi.fn(async () => undefined);
|
||||
@@ -269,12 +270,7 @@ describe("deliverMattermostReplyPayload", () => {
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
await fs.rm(stateDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Microsoft tests cover speech provider plugin behavior.
|
||||
import { mkdtempSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import {
|
||||
@@ -8,7 +7,8 @@ import {
|
||||
getDebugProxyCaptureStore,
|
||||
initializeDebugProxyCapture,
|
||||
} from "openclaw/plugin-sdk/proxy-capture";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { installDebugProxyTestResetHooks } from "../test-support/debug-proxy-env-test-helpers.js";
|
||||
|
||||
const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn());
|
||||
@@ -66,6 +66,21 @@ function requireFirstEdgeTtsCall(edgeSpy: ReturnType<typeof vi.spyOn>): {
|
||||
}
|
||||
|
||||
describe("listMicrosoftVoices", () => {
|
||||
let openClawState: OpenClawTestState;
|
||||
|
||||
beforeEach(async () => {
|
||||
openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "microsoft-voices-capture-",
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await openClawState.cleanup();
|
||||
});
|
||||
|
||||
// Install after local teardown so the proxy snapshot is restored before the
|
||||
// state helper removes its directory and restores the outer environment.
|
||||
const proxyReset = installDebugProxyTestResetHooks();
|
||||
|
||||
it("maps Microsoft voice metadata into speech voice options", async () => {
|
||||
@@ -178,10 +193,8 @@ describe("listMicrosoftVoices", () => {
|
||||
});
|
||||
|
||||
it("records voice discovery exchanges in debug proxy capture mode", async () => {
|
||||
const tempDir = mkdtempSync(path.join(os.tmpdir(), "microsoft-voices-capture-"));
|
||||
proxyReset.captureProxyEnv();
|
||||
process.env.OPENCLAW_DEBUG_PROXY_ENABLED = "1";
|
||||
process.env.OPENCLAW_STATE_DIR = tempDir;
|
||||
process.env.OPENCLAW_DEBUG_PROXY_SESSION_ID = "ms-voices-session";
|
||||
|
||||
globalThis.fetch = vi
|
||||
@@ -217,10 +230,8 @@ describe("listMicrosoftVoices", () => {
|
||||
});
|
||||
|
||||
it("does not double-capture voice discovery when the global fetch patch is installed", async () => {
|
||||
const tempDir = mkdtempSync(path.join(os.tmpdir(), "microsoft-voices-global-"));
|
||||
proxyReset.captureProxyEnv();
|
||||
process.env.OPENCLAW_DEBUG_PROXY_ENABLED = "1";
|
||||
process.env.OPENCLAW_STATE_DIR = tempDir;
|
||||
process.env.OPENCLAW_DEBUG_PROXY_SESSION_ID = "ms-voices-global-session";
|
||||
|
||||
globalThis.fetch = vi.fn(
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// Openai tests cover tts plugin behavior.
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import {
|
||||
finalizeDebugProxyCapture,
|
||||
getDebugProxyCaptureStore,
|
||||
initializeDebugProxyCapture,
|
||||
} from "openclaw/plugin-sdk/proxy-capture";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { installDebugProxyTestResetHooks } from "../test-support/debug-proxy-env-test-helpers.js";
|
||||
import { createStreamingErrorResponse } from "../test-support/streaming-error-response.js";
|
||||
import {
|
||||
@@ -60,15 +58,27 @@ function firstFetchInit(fetchMock: ReturnType<typeof vi.fn>): RequestInit {
|
||||
}
|
||||
|
||||
describe("openai tts", () => {
|
||||
const proxyReset = installDebugProxyTestResetHooks();
|
||||
const originalFetch = globalThis.fetch;
|
||||
let openClawState: OpenClawTestState;
|
||||
|
||||
afterEach(() => {
|
||||
beforeEach(async () => {
|
||||
openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openai-tts-capture-",
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
globalThis.fetch = originalFetch;
|
||||
vi.unstubAllEnvs();
|
||||
vi.restoreAllMocks();
|
||||
await openClawState.cleanup();
|
||||
});
|
||||
|
||||
// Install after local teardown so the proxy snapshot is restored before the
|
||||
// state helper removes its directory and restores the outer environment.
|
||||
const proxyReset = installDebugProxyTestResetHooks();
|
||||
|
||||
describe("isValidOpenAIVoice", () => {
|
||||
it("accepts all valid OpenAI voices including newer additions", () => {
|
||||
for (const voice of OPENAI_TTS_VOICES) {
|
||||
@@ -348,10 +358,8 @@ describe("openai tts", () => {
|
||||
});
|
||||
|
||||
it("records TTS exchanges in debug proxy capture mode", async () => {
|
||||
const tempDir = mkdtempSync(path.join(os.tmpdir(), "openai-tts-capture-"));
|
||||
proxyReset.captureProxyEnv();
|
||||
process.env.OPENCLAW_DEBUG_PROXY_ENABLED = "1";
|
||||
process.env.OPENCLAW_STATE_DIR = tempDir;
|
||||
process.env.OPENCLAW_DEBUG_PROXY_SESSION_ID = "tts-session";
|
||||
|
||||
globalThis.fetch = vi
|
||||
@@ -391,10 +399,8 @@ describe("openai tts", () => {
|
||||
});
|
||||
|
||||
it("does not double-capture TTS exchanges when the global fetch patch is installed", async () => {
|
||||
const tempDir = mkdtempSync(path.join(os.tmpdir(), "openai-tts-patched-capture-"));
|
||||
proxyReset.captureProxyEnv();
|
||||
process.env.OPENCLAW_DEBUG_PROXY_ENABLED = "1";
|
||||
process.env.OPENCLAW_STATE_DIR = tempDir;
|
||||
process.env.OPENCLAW_DEBUG_PROXY_SESSION_ID = "tts-patched-session";
|
||||
|
||||
globalThis.fetch = vi
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { createOpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import {
|
||||
DEFAULT_MEDIA_SEND_ERROR,
|
||||
@@ -710,16 +711,18 @@ describe("dispatchOutbound", () => {
|
||||
});
|
||||
|
||||
it("does not expose default sandbox roots through gateway qqmedia replies", async () => {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qqbot-agent-root-boundary-"));
|
||||
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "qqbot-agent-root-boundary-",
|
||||
});
|
||||
const tmpRoot = openClawState.root;
|
||||
try {
|
||||
const workspaceDir = path.join(tmpRoot, "workspace");
|
||||
const stateSandboxDir = path.join(tmpRoot, "state", "sandboxes", "other-agent");
|
||||
const stateSandboxDir = openClawState.statePath("sandboxes", "other-agent");
|
||||
const stateSandboxFile = path.join(stateSandboxDir, "outside-report.docx");
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
await fs.mkdir(stateSandboxDir, { recursive: true });
|
||||
await fs.writeFile(stateSandboxFile, Buffer.from("outside"));
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(tmpRoot, "state");
|
||||
const runtime = makeRuntime({
|
||||
onDeliver: async (deliver) => {
|
||||
await deliver({ text: `<qqmedia>${stateSandboxFile}</qqmedia>` }, { kind: "block" });
|
||||
@@ -739,12 +742,7 @@ describe("dispatchOutbound", () => {
|
||||
|
||||
expect(sendMediaMock).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
if (originalStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = originalStateDir;
|
||||
}
|
||||
await fs.rm(tmpRoot, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Telegram tests cover action runtime plugin behavior.
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { captureEnv } from "openclaw/plugin-sdk/test-env";
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
handleTelegramAction as handleTelegramActionRuntime,
|
||||
@@ -201,6 +201,7 @@ const createForumTopicTelegram = vi.fn(async () => ({
|
||||
chatId: "123",
|
||||
}));
|
||||
let envSnapshot: ReturnType<typeof captureEnv>;
|
||||
let openClawState: OpenClawTestState;
|
||||
|
||||
type TopicNameEntryForTest = {
|
||||
name: string;
|
||||
@@ -329,8 +330,12 @@ describe("handleTelegramAction", () => {
|
||||
expect(options.remove).toBe(false);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
envSnapshot = captureEnv(["OPENCLAW_STATE_DIR", "TELEGRAM_BOT_TOKEN"]);
|
||||
beforeEach(async () => {
|
||||
envSnapshot = captureEnv(["TELEGRAM_BOT_TOKEN"]);
|
||||
openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-telegram-action-",
|
||||
});
|
||||
resetTelegramTopicNameCacheForTest();
|
||||
installTopicNameStoreForTest();
|
||||
Object.assign(telegramActionRuntime, originalTelegramActionRuntime, {
|
||||
@@ -360,11 +365,12 @@ describe("handleTelegramAction", () => {
|
||||
process.env.TELEGRAM_BOT_TOKEN = "tok";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
clearTelegramRuntimeForTest();
|
||||
resetTelegramTopicNameCacheForTest();
|
||||
topicNameStoresForTest.clear();
|
||||
envSnapshot.restore();
|
||||
await openClawState.cleanup();
|
||||
});
|
||||
|
||||
it("adds reactions when reactionLevel is minimal", async () => {
|
||||
@@ -880,7 +886,7 @@ describe("handleTelegramAction", () => {
|
||||
});
|
||||
|
||||
it("persists sendMessage action deliveries before Telegram platform send", async () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-action-durable-"));
|
||||
const stateDir = openClawState.stateDir;
|
||||
const {
|
||||
createOutboundTestPlugin,
|
||||
createTestRegistry,
|
||||
@@ -922,7 +928,6 @@ describe("handleTelegramAction", () => {
|
||||
return { channel: "telegram", messageId: "tg-ok" };
|
||||
});
|
||||
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
telegramActionRuntime.sendDurableMessageBatch =
|
||||
originalTelegramActionRuntime.sendDurableMessageBatch;
|
||||
setActivePluginRegistry(
|
||||
@@ -1009,7 +1014,6 @@ describe("handleTelegramAction", () => {
|
||||
});
|
||||
} finally {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
fs.rmSync(stateDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
// Telegram supersede policy for durable ingress (authorization-gated).
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import {
|
||||
addChannelAllowFromStoreEntry,
|
||||
closeOpenClawStateDatabaseForTest,
|
||||
} from "openclaw/plugin-sdk/plugin-state-test-runtime";
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
let previousStateDir: string | undefined;
|
||||
let stateDirTouched = false;
|
||||
let openClawState: OpenClawTestState | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (!stateDirTouched) {
|
||||
return;
|
||||
}
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
previousStateDir = undefined;
|
||||
stateDirTouched = false;
|
||||
await openClawState?.cleanup();
|
||||
openClawState = undefined;
|
||||
});
|
||||
import type { TelegramSpooledUpdatePayload } from "./telegram-ingress-spool.payload.js";
|
||||
import {
|
||||
@@ -353,11 +342,10 @@ describe("telegram ingress supersede policy", () => {
|
||||
|
||||
it("authorizes paired DM senders via the pairing store under dmPolicy pairing", async () => {
|
||||
const pairedId = "424242";
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-supersede-store-"));
|
||||
// The shared pairing-store loader reads process.env; scoped set + afterEach restore.
|
||||
previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
stateDirTouched = true;
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-supersede-store-",
|
||||
});
|
||||
await addChannelAllowFromStoreEntry({
|
||||
channel: "telegram",
|
||||
entry: pairedId,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// Telegram tests cover thread bindings plugin behavior.
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { getSessionBindingService } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import type { PluginStateSyncKeyedStore } from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
@@ -10,6 +7,7 @@ import {
|
||||
resetPluginStateStoreForTests,
|
||||
} from "openclaw/plugin-sdk/plugin-state-test-runtime";
|
||||
import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { createOpenClawTestState, type OpenClawTestState } from "openclaw/plugin-sdk/test-state";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { setTelegramRuntime } from "./runtime.js";
|
||||
import { clearTelegramRuntimeForTest } from "./runtime.test-support.js";
|
||||
@@ -73,8 +71,7 @@ async function flushMicrotasks(): Promise<void> {
|
||||
}
|
||||
|
||||
describe("telegram thread bindings", () => {
|
||||
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
let stateDirOverride: string | undefined;
|
||||
let openClawState: OpenClawTestState;
|
||||
let threadBindingStore: PluginStateSyncKeyedStore<ThreadBindingStoreEntry>;
|
||||
|
||||
function createThreadBindingStore(): PluginStateSyncKeyedStore<ThreadBindingStoreEntry> {
|
||||
@@ -102,6 +99,10 @@ describe("telegram thread bindings", () => {
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-telegram-bindings-",
|
||||
});
|
||||
resetPluginStateStoreForTests({ closeDatabase: false });
|
||||
installThreadBindingStore(createThreadBindingStore());
|
||||
threadBindingStore.clear();
|
||||
@@ -118,15 +119,7 @@ describe("telegram thread bindings", () => {
|
||||
await testing.resetTelegramThreadBindingsForTests();
|
||||
clearTelegramRuntimeForTest();
|
||||
resetPluginStateStoreForTests();
|
||||
if (stateDirOverride) {
|
||||
fs.rmSync(stateDirOverride, { recursive: true, force: true });
|
||||
stateDirOverride = undefined;
|
||||
}
|
||||
if (originalStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = originalStateDir;
|
||||
}
|
||||
await openClawState.cleanup();
|
||||
});
|
||||
|
||||
it("registers a telegram binding adapter and binds current conversations", async () => {
|
||||
@@ -304,8 +297,6 @@ describe("telegram thread bindings", () => {
|
||||
});
|
||||
|
||||
it("does not persist lifecycle updates when manager persistence is disabled", async () => {
|
||||
stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDirOverride;
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-03-06T10:00:00.000Z"));
|
||||
|
||||
@@ -342,9 +333,6 @@ describe("telegram thread bindings", () => {
|
||||
});
|
||||
|
||||
it("persists unbinds before restart so removed bindings do not come back", async () => {
|
||||
stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDirOverride;
|
||||
|
||||
createTelegramThreadBindingManager({
|
||||
accountId: "default",
|
||||
persist: true,
|
||||
@@ -434,9 +422,6 @@ describe("telegram thread bindings", () => {
|
||||
});
|
||||
|
||||
it("cleans up stale ACP bindings before restart routing can reuse them", async () => {
|
||||
stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDirOverride;
|
||||
|
||||
createTelegramThreadBindingManager({
|
||||
accountId: "default",
|
||||
persist: true,
|
||||
@@ -476,9 +461,6 @@ describe("telegram thread bindings", () => {
|
||||
});
|
||||
|
||||
it("keeps plugin-owned bindings when ACP cleanup runs on startup", async () => {
|
||||
stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDirOverride;
|
||||
|
||||
createTelegramThreadBindingManager({
|
||||
accountId: "default",
|
||||
persist: true,
|
||||
@@ -510,9 +492,6 @@ describe("telegram thread bindings", () => {
|
||||
});
|
||||
|
||||
it("keeps ACP bindings when the session store cannot be read during startup cleanup", async () => {
|
||||
stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDirOverride;
|
||||
|
||||
createTelegramThreadBindingManager({
|
||||
accountId: "default",
|
||||
persist: true,
|
||||
@@ -552,8 +531,6 @@ describe("telegram thread bindings", () => {
|
||||
});
|
||||
|
||||
it("flushes pending lifecycle update persists before test reset", async () => {
|
||||
stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDirOverride;
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-03-06T10:00:00.000Z"));
|
||||
|
||||
@@ -587,8 +564,6 @@ describe("telegram thread bindings", () => {
|
||||
});
|
||||
|
||||
it("does not leak unhandled rejections when a persist write fails", async () => {
|
||||
stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDirOverride;
|
||||
const unhandled: unknown[] = [];
|
||||
const onUnhandledRejection = (reason: unknown) => {
|
||||
unhandled.push(reason);
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { ChannelAccountSnapshot } from "../channels/plugins/types.public.js
|
||||
import type { ChannelPlugin } from "../channels/plugins/types.public.js";
|
||||
import { createPluginRecord } from "../plugins/status.test-fixtures.js";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";
|
||||
import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js";
|
||||
import type { HealthSummary } from "./health.js";
|
||||
|
||||
let testConfig: Record<string, unknown> = {};
|
||||
@@ -558,9 +559,10 @@ describe("getHealthSnapshot", () => {
|
||||
testConfig = { session: { store: "/tmp/x" } };
|
||||
testStore = {};
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
const tmpStateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-health-dq-"));
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = tmpStateDir;
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-health-dq-",
|
||||
});
|
||||
try {
|
||||
const { moveDeliveryQueueEntryToFailed, upsertDeliveryQueueEntry } =
|
||||
await import("../infra/delivery-queue-sqlite.js");
|
||||
@@ -592,14 +594,7 @@ describe("getHealthSnapshot", () => {
|
||||
],
|
||||
});
|
||||
} finally {
|
||||
const { closeOpenClawStateDatabaseForTest } = await import("../state/openclaw-state-db.js");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
fs.rmSync(tmpStateDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Doctor health contribution tests cover plugin-provided health checks.
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import nodePath from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { DoctorPrompter } from "../commands/doctor-prompter.js";
|
||||
@@ -8,6 +7,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { LEGACY_SECRETREF_ENV_MARKER_PREFIX } from "../config/types.secrets.js";
|
||||
import { migrateLegacySecretRefEnvMarkers } from "../secrets/legacy-secretref-env-marker.js";
|
||||
import { readConfigMachineState } from "../state/config-machine-state.js";
|
||||
import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js";
|
||||
import { CORE_HEALTH_CHECKS } from "./doctor-core-checks.js";
|
||||
import { resolveDoctorContributionHealthChecks } from "./doctor-health-contributions.js";
|
||||
import {
|
||||
@@ -1919,12 +1919,13 @@ describe("doctor health contributions", () => {
|
||||
});
|
||||
|
||||
it("keeps legacy plugin dependency lint opt-in and read-only", async () => {
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const tempDir = fs.mkdtempSync(nodePath.join(os.tmpdir(), "openclaw-legacy-plugin-deps-lint-"));
|
||||
const stateDir = nodePath.join(tempDir, "state");
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-legacy-plugin-deps-lint-",
|
||||
});
|
||||
const stateDir = openClawState.stateDir;
|
||||
const legacyRuntimeRoot = nodePath.join(stateDir, "plugin-runtime-deps");
|
||||
fs.mkdirSync(legacyRuntimeRoot, { recursive: true });
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
try {
|
||||
const contributionChecks = await resolveDoctorContributionHealthChecks();
|
||||
const check = contributionChecks.find(
|
||||
@@ -1961,12 +1962,7 @@ describe("doctor health contributions", () => {
|
||||
});
|
||||
expect(fs.existsSync(legacyRuntimeRoot)).toBe(true);
|
||||
} finally {
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
buildSystemRunApprovalEnvBinding,
|
||||
} from "../../infra/system-run-approval-binding.js";
|
||||
import { resetLogger, setLoggerOverride } from "../../logging.js";
|
||||
import { createOpenClawTestState } from "../../test-utils/openclaw-test-state.js";
|
||||
import {
|
||||
DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS,
|
||||
augmentChatHistoryWithCanvasBlocks,
|
||||
@@ -5362,9 +5363,10 @@ describe("gateway healthHandlers.health cache freshness", () => {
|
||||
});
|
||||
|
||||
it("merges live dead-lettered delivery queue counts into cached health responses", async () => {
|
||||
const tmpStateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-health-cached-dq-"));
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = tmpStateDir;
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-health-cached-dq-",
|
||||
});
|
||||
try {
|
||||
const { moveDeliveryQueueEntryToFailed, upsertDeliveryQueueEntry } =
|
||||
await import("../../infra/delivery-queue-sqlite.js");
|
||||
@@ -5422,12 +5424,7 @@ describe("gateway healthHandlers.health cache freshness", () => {
|
||||
expect(typeof payload?.deliveryQueues?.failed?.[0]?.oldestFailedAt).toBe("number");
|
||||
expect(mockCallArg(respond, 0, 3)).toEqual({ cached: true });
|
||||
} finally {
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
fs.rmSync(tmpStateDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
resolveIncognitoOpenClawAgentSqlitePath,
|
||||
} from "../state/openclaw-agent-db.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
||||
import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { resolveGatewaySessionStoreTarget } from "./session-utils.js";
|
||||
import {
|
||||
@@ -660,12 +661,12 @@ test("sessions.create rejects draft visibility when policy disables drafts", asy
|
||||
});
|
||||
|
||||
test("sessions.create provisions and reuses a session worktree for later runs", async () => {
|
||||
const root = await fs.mkdtemp(
|
||||
path.join(await fs.realpath(os.tmpdir()), "openclaw-session-worktree-"),
|
||||
);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-session-worktree-",
|
||||
});
|
||||
const root = openClawState.root;
|
||||
const workspace = await initializeGitWorkspace(root);
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(root, "state");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
testState.agentConfig = { workspace };
|
||||
await createSessionStoreDir();
|
||||
@@ -733,20 +734,17 @@ test("sessions.create provisions and reuses a session worktree for later runs",
|
||||
await managedWorktrees.remove({ id: worktreeId, reason: "test-cleanup", force: true });
|
||||
}
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
testState.agentConfig = undefined;
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test("sessions.create honors worktree name/base ref and persists worktree info", async () => {
|
||||
const root = await fs.mkdtemp(
|
||||
path.join(await fs.realpath(os.tmpdir()), "openclaw-session-worktree-target-"),
|
||||
);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-session-worktree-target-",
|
||||
});
|
||||
const root = openClawState.root;
|
||||
const workspace = await initializeGitWorkspace(root);
|
||||
await execFileAsync("git", ["-C", workspace, "checkout", "-b", "base-branch"]);
|
||||
await fs.writeFile(path.join(workspace, "base.txt"), "base\n");
|
||||
@@ -769,8 +767,6 @@ test("sessions.create honors worktree name/base ref and persists worktree info",
|
||||
"HEAD",
|
||||
]);
|
||||
await execFileAsync("git", ["-C", workspace, "checkout", "main"]);
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(root, "state");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
testState.agentConfig = { workspace };
|
||||
await createSessionStoreDir();
|
||||
@@ -819,13 +815,8 @@ test("sessions.create honors worktree name/base ref and persists worktree info",
|
||||
await managedWorktrees.remove({ id: worktreeId, reason: "test-cleanup", force: true });
|
||||
}
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
testState.agentConfig = undefined;
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -956,16 +947,16 @@ test("sessions.create rejects a Gateway worktree targeting a node", async () =>
|
||||
});
|
||||
|
||||
test("sessions.create provisions a worktree from an admin-selected cwd", async () => {
|
||||
const configuredRoot = await fs.mkdtemp(
|
||||
path.join(await fs.realpath(os.tmpdir()), "openclaw-configured-workspace-"),
|
||||
);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-configured-workspace-",
|
||||
});
|
||||
const configuredRoot = openClawState.root;
|
||||
const selectedRoot = await fs.mkdtemp(
|
||||
path.join(await fs.realpath(os.tmpdir()), "openclaw-selected-workspace-"),
|
||||
);
|
||||
const configuredWorkspace = await initializeGitWorkspace(configuredRoot);
|
||||
const selectedWorkspace = await initializeGitWorkspace(selectedRoot);
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(configuredRoot, "state");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
testState.agentConfig = { workspace: configuredWorkspace };
|
||||
await createSessionStoreDir();
|
||||
@@ -1011,13 +1002,8 @@ test("sessions.create provisions a worktree from an admin-selected cwd", async (
|
||||
await managedWorktrees.remove({ id: worktreeId, reason: "test-cleanup", force: true });
|
||||
}
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
testState.agentConfig = undefined;
|
||||
await fs.rm(configuredRoot, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
await fs.rm(selectedRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -1085,16 +1071,16 @@ test("sessions.create allows cwd within a sandboxed agent workspace", async () =
|
||||
});
|
||||
|
||||
test("sessions.create skips the worktree setup script for non-admin callers", async () => {
|
||||
const root = await fs.mkdtemp(
|
||||
path.join(await fs.realpath(os.tmpdir()), "openclaw-worktree-setup-scope-"),
|
||||
);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-worktree-setup-scope-",
|
||||
});
|
||||
const root = openClawState.root;
|
||||
const workspace = await initializeGitWorkspace(root);
|
||||
await fs.mkdir(path.join(workspace, ".openclaw"), { recursive: true });
|
||||
const setupScript = path.join(workspace, ".openclaw", "worktree-setup.sh");
|
||||
await fs.writeFile(setupScript, "#!/bin/sh\ntouch setup-marker.txt\n");
|
||||
await fs.chmod(setupScript, 0o755);
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(root, "state");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
testState.agentConfig = { workspace };
|
||||
await createSessionStoreDir();
|
||||
@@ -1118,27 +1104,22 @@ test("sessions.create skips the worktree setup script for non-admin callers", as
|
||||
await managedWorktrees.remove({ id: worktreeId, reason: "test-cleanup", force: true });
|
||||
}
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
testState.agentConfig = undefined;
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test("sessions.create preserves a linked-worktree subdirectory", async () => {
|
||||
const root = await fs.mkdtemp(
|
||||
path.join(await fs.realpath(os.tmpdir()), "openclaw-subdir-session-worktree-"),
|
||||
);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-subdir-session-worktree-",
|
||||
});
|
||||
const root = openClawState.root;
|
||||
const repoRoot = await initializeGitWorkspace(root);
|
||||
const linkedRoot = path.join(root, "linked");
|
||||
await execFileAsync("git", ["-C", repoRoot, "worktree", "add", "-b", "linked", linkedRoot]);
|
||||
const workspace = path.join(linkedRoot, "packages", "app");
|
||||
await fs.mkdir(workspace, { recursive: true });
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(root, "state");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
testState.agentConfig = { workspace };
|
||||
await createSessionStoreDir();
|
||||
@@ -1167,20 +1148,17 @@ test("sessions.create preserves a linked-worktree subdirectory", async () => {
|
||||
await managedWorktrees.remove({ id: worktreeId, reason: "test-cleanup", force: true });
|
||||
}
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
testState.agentConfig = undefined;
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test("sessions.create reset-in-place persists the returned worktree cwd", async () => {
|
||||
const root = await fs.mkdtemp(
|
||||
path.join(await fs.realpath(os.tmpdir()), "openclaw-reset-session-worktree-"),
|
||||
);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-reset-session-worktree-",
|
||||
});
|
||||
const root = openClawState.root;
|
||||
const workspace = await initializeGitWorkspace(root);
|
||||
// A remote makes the base commit reachable from `--remotes`, so leaving the worktree via a
|
||||
// plain New Chat is lossless and the reset can remove it (the real leave-worktree flow).
|
||||
@@ -1188,8 +1166,6 @@ test("sessions.create reset-in-place persists the returned worktree cwd", async
|
||||
await execFileAsync("git", ["init", "--bare", origin]);
|
||||
await execFileAsync("git", ["-C", workspace, "remote", "add", "origin", origin]);
|
||||
await execFileAsync("git", ["-C", workspace, "push", "-u", "origin", "main"]);
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(root, "state");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
testState.agentConfig = { workspace, model: { primary: "openai/current-model" } };
|
||||
testState.sessionConfig = { dmScope: "main" };
|
||||
@@ -1257,14 +1233,9 @@ test("sessions.create reset-in-place persists the returned worktree cwd", async
|
||||
await managedWorktrees.remove({ id: worktreeId, reason: "test-cleanup", force: true });
|
||||
}
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
testState.agentConfig = undefined;
|
||||
testState.sessionConfig = undefined;
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// active-run cleanup, hooks, thread bindings, and browser/MCP cleanup.
|
||||
import { execFile } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { afterEach, expect, test, vi } from "vitest";
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
runExclusiveSessionLifecycleMutation,
|
||||
} from "../sessions/session-lifecycle-admission.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
||||
import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js";
|
||||
import { embeddedRunMock, rpcReq, testState, writeSessionStore } from "./test-helpers.js";
|
||||
import {
|
||||
setupGatewaySessionsTestHarness,
|
||||
@@ -119,12 +119,12 @@ function expectThreadBindingsUnbound(targetSessionKey: string) {
|
||||
}
|
||||
|
||||
test("sessions.delete snapshots and removes session worktrees", async () => {
|
||||
const root = await fs.mkdtemp(
|
||||
path.join(await fs.realpath(os.tmpdir()), "openclaw-delete-worktree-"),
|
||||
);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-delete-worktree-",
|
||||
});
|
||||
const root = openClawState.root;
|
||||
const workspace = await initializeRemoteBackedGitWorkspace(root);
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(root, "state");
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
testState.agentConfig = { workspace };
|
||||
await createSessionStoreDir();
|
||||
@@ -194,13 +194,8 @@ test("sessions.delete snapshots and removes session worktrees", async () => {
|
||||
await managedWorktrees.remove({ id: dirtyWorktreeId, reason: "test-cleanup", force: true });
|
||||
}
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
testState.agentConfig = undefined;
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Covers outbound delivery core: hooks, queue cleanup, durable capability
|
||||
// checks, adapter sends, transcript mirroring, and payload outcomes.
|
||||
import fsPromises from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
createTestRegistry,
|
||||
} from "../../test-utils/channel-plugins.js";
|
||||
import { createInternalHookEventPayload } from "../../test-utils/internal-hook-event-payload.js";
|
||||
import { createOpenClawTestState } from "../../test-utils/openclaw-test-state.js";
|
||||
import {
|
||||
onInternalDiagnosticEvent,
|
||||
resetDiagnosticEventsForTest,
|
||||
@@ -4262,10 +4262,10 @@ describe("deliverOutboundPayloads", () => {
|
||||
const sourceDir = await fsPromises.realpath(
|
||||
await fsPromises.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "deliver-spool-")),
|
||||
);
|
||||
const stateDir = await fsPromises.realpath(
|
||||
await fsPromises.mkdtemp(path.join(os.tmpdir(), "openclaw-deliver-spool-state-")),
|
||||
);
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-deliver-spool-state-",
|
||||
});
|
||||
// Real MPEG-1 Layer III frames: host-local media sends are buffer-verified,
|
||||
// so placeholder text would be rejected before staging is even exercised.
|
||||
const source = path.join(sourceDir, "voice.mp3");
|
||||
@@ -4281,7 +4281,6 @@ describe("deliverOutboundPayloads", () => {
|
||||
const payload = { mediaUrl: source, audioAsVoice: true };
|
||||
|
||||
try {
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
await deliverOutboundPayloads({
|
||||
cfg: matrixChunkConfig,
|
||||
channel: "matrix",
|
||||
@@ -4303,13 +4302,8 @@ describe("deliverOutboundPayloads", () => {
|
||||
expect(payload.mediaUrl).toBe(source);
|
||||
expect(sendMatrix.mock.calls[0]?.[0]?.mediaUrl ?? source).toBe(source);
|
||||
} finally {
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
await fsPromises.rm(sourceDir, { recursive: true, force: true });
|
||||
await fsPromises.rm(stateDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4330,10 +4324,11 @@ describe("deliverOutboundPayloads", () => {
|
||||
// Deliberately outside the OpenClaw temp root: that root is itself a default
|
||||
// media root, so a state dir inside it would admit the source by containment
|
||||
// and hide whether the agent-scoped capability is what grants access.
|
||||
const stateDir = await fsPromises.realpath(
|
||||
await fsPromises.mkdtemp(path.join(os.tmpdir(), "openclaw-deliver-ws-")),
|
||||
);
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-deliver-ws-",
|
||||
});
|
||||
const stateDir = openClawState.stateDir;
|
||||
const workspaceDir = path.join(stateDir, "workspace-proofagent");
|
||||
// Host-local sends are buffer-verified, so the fixture needs real audio.
|
||||
const source = path.join(workspaceDir, "voice.mp3");
|
||||
@@ -4348,7 +4343,6 @@ describe("deliverOutboundPayloads", () => {
|
||||
const payload = { mediaUrl: source, audioAsVoice: true };
|
||||
|
||||
try {
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
await fsPromises.mkdir(workspaceDir, { recursive: true });
|
||||
await fsPromises.writeFile(source, mp3);
|
||||
await deliverOutboundPayloads({
|
||||
@@ -4375,12 +4369,7 @@ describe("deliverOutboundPayloads", () => {
|
||||
expect(payload.mediaUrl).toBe(source);
|
||||
expect(sendMatrix).toHaveBeenCalled();
|
||||
} finally {
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
await fsPromises.rm(stateDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { MEDIA_MAX_BYTES } from "../../media/store.js";
|
||||
import { withOpenClawTestState } from "../../test-utils/openclaw-test-state.js";
|
||||
|
||||
const { resolveChannelMessageToolMediaSourceParamKeysMock } = vi.hoisted(() => ({
|
||||
resolveChannelMessageToolMediaSourceParamKeysMock: vi.fn(() => ["avatarPath", "avatarUrl"]),
|
||||
@@ -29,19 +30,10 @@ const maybeIt = process.platform === "win32" ? it.skip : it;
|
||||
const matrixMediaSourceParamKeys = ["avatarPath", "avatarUrl"] as const;
|
||||
|
||||
async function withTempOpenClawStateDir<T>(test: (stateDir: string) => Promise<T>): Promise<T> {
|
||||
const previous = process.env.OPENCLAW_STATE_DIR;
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-params-state-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
try {
|
||||
return await test(stateDir);
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previous;
|
||||
}
|
||||
await fs.rm(stateDir, { recursive: true, force: true });
|
||||
}
|
||||
return await withOpenClawTestState(
|
||||
{ layout: "state-only", prefix: "msg-params-state-" },
|
||||
(state) => test(state.stateDir),
|
||||
);
|
||||
}
|
||||
|
||||
describe("message action media helpers", () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
createChannelTestPluginBase,
|
||||
createTestRegistry,
|
||||
} from "../../test-utils/channel-plugins.js";
|
||||
import { withOpenClawTestState } from "../../test-utils/openclaw-test-state.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../tmp-openclaw-dir.js";
|
||||
import { runMessageAction } from "./message-action-runner.js";
|
||||
|
||||
@@ -91,19 +92,10 @@ async function withSandbox(test: (sandboxDir: string) => Promise<void>) {
|
||||
}
|
||||
|
||||
async function withTempOpenClawStateDir<T>(test: (stateDir: string) => Promise<T>): Promise<T> {
|
||||
const previous = process.env.OPENCLAW_STATE_DIR;
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-runner-state-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
try {
|
||||
return await test(stateDir);
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previous;
|
||||
}
|
||||
await fs.rm(stateDir, { recursive: true, force: true });
|
||||
}
|
||||
return await withOpenClawTestState(
|
||||
{ layout: "state-only", prefix: "msg-runner-state-" },
|
||||
(state) => test(state.stateDir),
|
||||
);
|
||||
}
|
||||
|
||||
const runDrySend = (params: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Stuck session recovery runtime tests cover recovery inspection and event output.
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { saveCronStore } from "../cron/store.js";
|
||||
import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
abortEmbeddedAgentRun: vi.fn(),
|
||||
@@ -289,10 +289,12 @@ describe("stuck session recovery", () => {
|
||||
});
|
||||
|
||||
it("logs stopped cron context when aborting an active embedded run", async () => {
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-recovery-context-"));
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-recovery-context-",
|
||||
});
|
||||
const tempDir = openClawState.stateDir;
|
||||
try {
|
||||
process.env.OPENCLAW_STATE_DIR = tempDir;
|
||||
await saveCronStore(path.join(tempDir, "cron", "jobs.json"), {
|
||||
version: 1,
|
||||
jobs: [
|
||||
@@ -330,12 +332,7 @@ describe("stuck session recovery", () => {
|
||||
allowActiveAbort: true,
|
||||
});
|
||||
} finally {
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
|
||||
expect(warnLogMessages()).toEqual([
|
||||
|
||||
@@ -6,6 +6,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { root as createFsSafeRoot } from "../infra/fs-safe.js";
|
||||
import { resetPluginStateStoreForTests } from "../plugin-state/plugin-state-store.js";
|
||||
import { clearMemoryPluginState } from "../plugins/memory-state.test-fixtures.js";
|
||||
import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js";
|
||||
import { listMemoryHostPublicArtifacts } from "./memory-host-core.js";
|
||||
import {
|
||||
memoryHostEventExportOwnerContent,
|
||||
@@ -103,9 +104,11 @@ describe("memory host event export recovery", () => {
|
||||
});
|
||||
|
||||
it("finishes an inode-owned empty event export after interruption", async () => {
|
||||
const fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "memory-host-inode-owner-"));
|
||||
const stateDir = path.join(fixtureRoot, "state");
|
||||
const workspaceDir = path.join(fixtureRoot, "workspace");
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "memory-host-inode-owner-",
|
||||
});
|
||||
const { stateDir, workspaceDir } = openClawState;
|
||||
const event = {
|
||||
type: "memory.recall.recorded" as const,
|
||||
timestamp: "2026-05-18T12:00:00.000Z",
|
||||
@@ -114,8 +117,6 @@ describe("memory host event export recovery", () => {
|
||||
results: [],
|
||||
};
|
||||
try {
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
await fs.mkdir(workspaceDir);
|
||||
await appendMemoryHostEvent(workspaceDir, event);
|
||||
const stateHash = createHash("sha256")
|
||||
.update(await fs.realpath(stateDir))
|
||||
@@ -165,14 +166,16 @@ describe("memory host event export recovery", () => {
|
||||
});
|
||||
expect(owner.pendingContentSha256).toBeUndefined();
|
||||
} finally {
|
||||
await fs.rm(fixtureRoot, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not claim an empty export after exclusive-create interruption", async () => {
|
||||
const fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "memory-host-empty-export-"));
|
||||
const stateDir = path.join(fixtureRoot, "state");
|
||||
const workspaceDir = path.join(fixtureRoot, "workspace");
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "memory-host-empty-export-",
|
||||
});
|
||||
const { stateDir, workspaceDir } = openClawState;
|
||||
const event = {
|
||||
type: "memory.recall.recorded" as const,
|
||||
timestamp: "2026-05-18T12:00:00.000Z",
|
||||
@@ -181,8 +184,6 @@ describe("memory host event export recovery", () => {
|
||||
results: [],
|
||||
};
|
||||
try {
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
await fs.mkdir(workspaceDir);
|
||||
await appendMemoryHostEvent(workspaceDir, event);
|
||||
const stateHash = createHash("sha256")
|
||||
.update(await fs.realpath(stateDir))
|
||||
@@ -217,7 +218,7 @@ describe("memory host event export recovery", () => {
|
||||
expect(listed.some((artifact) => artifact.kind === "event-log")).toBe(false);
|
||||
await expect(fs.readFile(exportPath, "utf8")).resolves.toBe("");
|
||||
} finally {
|
||||
await fs.rm(fixtureRoot, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Run context lifecycle contract tests cover plugin run context setup and cleanup.
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
createPluginRegistryFixture,
|
||||
@@ -8,8 +7,8 @@ import {
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempConfig } from "../../gateway/test-temp-config.js";
|
||||
import { emitAgentEvent, resetAgentEventsForTest } from "../../infra/agent-events.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js";
|
||||
import { loadSessionStore, updateSessionStore } from "../../plugin-sdk/session-store-runtime.js";
|
||||
import { createOpenClawTestState } from "../../test-utils/openclaw-test-state.js";
|
||||
import { runPluginHostCleanup } from "../host-hook-cleanup.js";
|
||||
import {
|
||||
clearPluginHostRuntimeState,
|
||||
@@ -686,16 +685,16 @@ describe("plugin run context lifecycle", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const stateDir = await fs.mkdtemp(
|
||||
path.join(resolvePreferredOpenClawTmpDir(), "openclaw-run-context-restart-state-"),
|
||||
);
|
||||
const openClawState = await createOpenClawTestState({
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-run-context-restart-state-",
|
||||
});
|
||||
const stateDir = openClawState.stateDir;
|
||||
const storePath = path.join(stateDir, "sessions.json");
|
||||
const tempConfig = {
|
||||
session: { store: storePath },
|
||||
};
|
||||
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
try {
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
await withTempConfig({
|
||||
cfg: tempConfig,
|
||||
run: async () => {
|
||||
@@ -748,12 +747,7 @@ describe("plugin run context lifecycle", () => {
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
await fs.rm(stateDir, { recursive: true, force: true });
|
||||
await openClawState.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user