mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 11:24:47 +00:00
test: harden wsl2 fixtures
This commit is contained in:
@@ -2837,7 +2837,10 @@ describe("active-memory plugin", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledTimes(2);
|
||||
const sessionKeys = runEmbeddedPiAgent.mock.calls.map(
|
||||
([params]) => (params as { sessionKey?: string }).sessionKey,
|
||||
);
|
||||
expect(new Set(sessionKeys).size).toBeGreaterThanOrEqual(2);
|
||||
const infoLines = vi
|
||||
.mocked(api.logger.info)
|
||||
.mock.calls.map((call: unknown[]) => String(call[0]));
|
||||
|
||||
@@ -11,6 +11,8 @@ import type { WizardPrompter } from "openclaw/plugin-sdk/setup";
|
||||
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
LMSTUDIO_DEFAULT_API_KEY_ENV_VAR,
|
||||
LMSTUDIO_DEFAULT_INFERENCE_BASE_URL,
|
||||
LMSTUDIO_DOCKER_HOST_INFERENCE_BASE_URL,
|
||||
LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER,
|
||||
} from "./defaults.js";
|
||||
import {
|
||||
@@ -702,11 +704,17 @@ describe("lmstudio setup", () => {
|
||||
const ctx = buildNonInteractiveContext({
|
||||
customModelId: "missing-model",
|
||||
});
|
||||
const dockerSetup = ["1", "true", "yes", "on"].includes(
|
||||
process.env.OPENCLAW_DOCKER_SETUP?.trim().toLowerCase() ?? "",
|
||||
);
|
||||
const expectedBaseUrl = dockerSetup
|
||||
? LMSTUDIO_DOCKER_HOST_INFERENCE_BASE_URL
|
||||
: LMSTUDIO_DEFAULT_INFERENCE_BASE_URL;
|
||||
|
||||
await expect(configureLmstudioNonInteractive(ctx)).resolves.toBeNull();
|
||||
|
||||
expect(ctx.runtime.error).toHaveBeenCalledWith(
|
||||
"LM Studio model missing-model was not found at http://localhost:1234/v1.\nAvailable models: qwen3-8b-instruct",
|
||||
`LM Studio model missing-model was not found at ${expectedBaseUrl}.\nAvailable models: qwen3-8b-instruct`,
|
||||
);
|
||||
expect(ctx.runtime.exit).toHaveBeenCalledWith(1);
|
||||
expect(configureSelfHostedNonInteractiveMock).not.toHaveBeenCalled();
|
||||
|
||||
@@ -1278,8 +1278,7 @@ describe("runWithModelFallback", () => {
|
||||
});
|
||||
|
||||
it("warns when falling back due to model_not_found", async () => {
|
||||
setLoggerOverride({ level: "silent", consoleLevel: "warn" });
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const warnLogs = createWarnLogCapture("openclaw-model-fallback-test");
|
||||
try {
|
||||
const cfg = makeCfg();
|
||||
const run = vi
|
||||
@@ -1295,13 +1294,13 @@ describe("runWithModelFallback", () => {
|
||||
});
|
||||
|
||||
expect(result.result).toBe("ok");
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'[model-fallback] Model "openai/gpt-6" not found. Fell back to "anthropic/claude-haiku-3-5".',
|
||||
);
|
||||
expect(
|
||||
await warnLogs.findText(
|
||||
'Model "openai/gpt-6" not found. Fell back to "anthropic/claude-haiku-3-5".',
|
||||
),
|
||||
).toBeDefined();
|
||||
} finally {
|
||||
warnSpy.mockRestore();
|
||||
setLoggerOverride(null);
|
||||
resetLogger();
|
||||
warnLogs.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1585,9 +1585,8 @@ describe("model-selection", () => {
|
||||
expect(result).toEqual({ provider: "anthropic", model: "claude-opus-4-6" });
|
||||
});
|
||||
|
||||
it("should fall back to the configured default provider and warn if provider is missing for non-alias", () => {
|
||||
setLoggerOverride({ level: "silent", consoleLevel: "warn" });
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
it("should fall back to the configured default provider and warn if provider is missing for non-alias", async () => {
|
||||
const warnLogs = createWarnLogCapture("openclaw-model-selection-test");
|
||||
try {
|
||||
const cfg: Partial<OpenClawConfig> = {
|
||||
agents: {
|
||||
@@ -1604,13 +1603,13 @@ describe("model-selection", () => {
|
||||
});
|
||||
|
||||
expect(result).toEqual({ provider: "google", model: "claude-3-5-sonnet" });
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'[model-selection] Model "claude-3-5-sonnet" specified without provider. Falling back to "google/claude-3-5-sonnet". Please use "google/claude-3-5-sonnet" in your config.',
|
||||
);
|
||||
expect(
|
||||
await warnLogs.findText(
|
||||
'Model "claude-3-5-sonnet" specified without provider. Falling back to "google/claude-3-5-sonnet". Please use "google/claude-3-5-sonnet" in your config.',
|
||||
),
|
||||
).toBeDefined();
|
||||
} finally {
|
||||
warnSpy.mockRestore();
|
||||
setLoggerOverride(null);
|
||||
resetLogger();
|
||||
warnLogs.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1868,9 +1867,8 @@ describe("model-selection", () => {
|
||||
expect(result).toEqual({ provider: "openai", model: "gpt-5.4" });
|
||||
});
|
||||
|
||||
it("should warn when specified model cannot be resolved and falls back to default", () => {
|
||||
setLoggerOverride({ level: "silent", consoleLevel: "warn" });
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
it("should warn when specified model cannot be resolved and falls back to default", async () => {
|
||||
const warnLogs = createWarnLogCapture("openclaw-model-selection-test");
|
||||
try {
|
||||
const cfg: Partial<OpenClawConfig> = {
|
||||
agents: {
|
||||
@@ -1887,13 +1885,13 @@ describe("model-selection", () => {
|
||||
});
|
||||
|
||||
expect(result).toEqual({ provider: "openai", model: "gpt-5.4" });
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'[model-selection] Model "openai/" could not be resolved. Falling back to default "openai/gpt-5.4".',
|
||||
);
|
||||
expect(
|
||||
await warnLogs.findText(
|
||||
'Model "openai/" could not be resolved. Falling back to default "openai/gpt-5.4".',
|
||||
),
|
||||
).toBeDefined();
|
||||
} finally {
|
||||
warnSpy.mockRestore();
|
||||
setLoggerOverride(null);
|
||||
resetLogger();
|
||||
warnLogs.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1127,6 +1127,7 @@ describe("systemd service install and uninstall", () => {
|
||||
|
||||
it("uses the sudo-u target user for install activation machine-scope retry", async () => {
|
||||
await withNodeSystemdFixture(async ({ env }) => {
|
||||
mockEffectiveUid(1000);
|
||||
const installEnv = { ...env, USER: "openclaw", SUDO_USER: "admin" };
|
||||
execFileMock
|
||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||
|
||||
@@ -286,6 +286,7 @@ describe("server-runtime-services", () => {
|
||||
const applyMaintenance = vi.fn();
|
||||
const cron = { start: vi.fn(async () => undefined) };
|
||||
const recordPostReadyMemory = vi.fn();
|
||||
const clearIntervalSpy = vi.spyOn(globalThis, "clearInterval");
|
||||
|
||||
scheduleGatewayPostReadyMaintenance(
|
||||
createPostReadyMaintenanceScheduleParams({
|
||||
@@ -305,14 +306,18 @@ describe("server-runtime-services", () => {
|
||||
if (!resolveMaintenance) {
|
||||
throw new Error("Expected gateway maintenance resolver to be initialized");
|
||||
}
|
||||
resolveMaintenance(createMaintenanceHandles());
|
||||
const maintenance = createMaintenanceHandles();
|
||||
resolveMaintenance(maintenance);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(applyMaintenance).not.toHaveBeenCalled();
|
||||
expect(cron.start).not.toHaveBeenCalled();
|
||||
expect(recordPostReadyMemory).not.toHaveBeenCalled();
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
expect(clearIntervalSpy).toHaveBeenCalledWith(maintenance.tickInterval);
|
||||
expect(clearIntervalSpy).toHaveBeenCalledWith(maintenance.healthInterval);
|
||||
expect(clearIntervalSpy).toHaveBeenCalledWith(maintenance.dedupeCleanup);
|
||||
expect(clearIntervalSpy).toHaveBeenCalledWith(maintenance.mediaCleanup);
|
||||
});
|
||||
|
||||
it("keeps scheduled services disabled for minimal test gateways", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fs from "node:fs/promises";
|
||||
import * as fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
createPluginRegistryFixture,
|
||||
@@ -11,6 +11,7 @@ import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js"
|
||||
import { FILE_TYPE_SNIFF_MAX_BYTES } from "../../media/mime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import {
|
||||
attachmentProbeFs,
|
||||
resolveAttachmentDelivery,
|
||||
sendPluginSessionAttachment,
|
||||
} from "../host-hook-attachments.js";
|
||||
@@ -453,8 +454,15 @@ describe("plugin session attachments", () => {
|
||||
await withSessionStore(async ({ storePath, stateDir }) => {
|
||||
const unreadablePath = path.join(stateDir, "unreadable.pdf");
|
||||
await fs.writeFile(unreadablePath, "%PDF-1.7\n", "utf8");
|
||||
await fs.chmod(unreadablePath, 0o000);
|
||||
await writeSessionEntry(storePath);
|
||||
const originalOpen = attachmentProbeFs.open.bind(attachmentProbeFs);
|
||||
const openSpy = vi.spyOn(attachmentProbeFs, "open").mockImplementation((async (...args) => {
|
||||
const [target] = args;
|
||||
if (path.resolve(String(target)) === unreadablePath) {
|
||||
throw new Error("EACCES: permission denied, open 'unreadable.pdf'");
|
||||
}
|
||||
return await originalOpen(...args);
|
||||
}) as typeof fs.open);
|
||||
|
||||
try {
|
||||
const result = await sendBundledSessionAttachment({
|
||||
@@ -468,7 +476,7 @@ describe("plugin session attachments", () => {
|
||||
}
|
||||
expect(result.error).toContain(`attachment file MIME read failed for ${unreadablePath}`);
|
||||
} finally {
|
||||
await fs.chmod(unreadablePath, 0o600).catch(() => undefined);
|
||||
openSpy.mockRestore();
|
||||
}
|
||||
expect(workflowMocks.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { lstat, open } from "node:fs/promises";
|
||||
import * as fsPromises from "node:fs/promises";
|
||||
import { lstat } from "node:fs/promises";
|
||||
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
||||
import { resolvePathFromInput } from "../agents/path-policy.js";
|
||||
import { resolveWorkspaceRoot } from "../agents/workspace-dir.js";
|
||||
@@ -18,6 +19,9 @@ import type {
|
||||
import type { PluginOrigin } from "./plugin-origin.types.js";
|
||||
|
||||
const DEFAULT_ATTACHMENT_MAX_BYTES = 25 * 1024 * 1024;
|
||||
export const attachmentProbeFs = {
|
||||
open: (...args: Parameters<typeof fsPromises.open>) => fsPromises.open(...args),
|
||||
};
|
||||
const MAX_ATTACHMENT_FILES = 10;
|
||||
|
||||
type SendMessage = typeof import("../infra/outbound/message.js").sendMessage;
|
||||
@@ -71,9 +75,9 @@ async function readMimeSniffBuffer(
|
||||
filePath: string,
|
||||
size: number,
|
||||
): Promise<Buffer | { error: string }> {
|
||||
let handle: Awaited<ReturnType<typeof open>> | undefined;
|
||||
let handle: Awaited<ReturnType<typeof fsPromises.open>> | undefined;
|
||||
try {
|
||||
handle = await open(filePath, "r");
|
||||
handle = await attachmentProbeFs.open(filePath, "r");
|
||||
const length = Math.min(Math.max(0, size), FILE_TYPE_SNIFF_MAX_BYTES);
|
||||
const buffer = Buffer.alloc(length);
|
||||
const { bytesRead } = await handle.read(buffer, 0, length, 0);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import fsPromises from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { safePathSegmentHashed } from "../infra/install-safe-path.js";
|
||||
@@ -2171,7 +2172,14 @@ describe("installPluginFromArchive", () => {
|
||||
path.join(blockedDir, "package.json"),
|
||||
JSON.stringify({ name: "plain-crypto-js" }),
|
||||
);
|
||||
fs.chmodSync(blockedDir, 0o000);
|
||||
const originalReaddir = fsPromises.readdir.bind(fsPromises);
|
||||
const readdirSpy = vi.spyOn(fsPromises, "readdir").mockImplementation((async (...args) => {
|
||||
const [target] = args;
|
||||
if (path.resolve(String(target)) === blockedDir) {
|
||||
throw new Error("EACCES: permission denied, scandir 'vendor/sealed'");
|
||||
}
|
||||
return await originalReaddir(...args);
|
||||
}) as typeof fsPromises.readdir);
|
||||
|
||||
try {
|
||||
const { result } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
|
||||
@@ -2183,7 +2191,7 @@ describe("installPluginFromArchive", () => {
|
||||
expect(result.error).toContain("vendor/sealed");
|
||||
}
|
||||
} finally {
|
||||
fs.chmodSync(blockedDir, 0o755);
|
||||
readdirSpy.mockRestore();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -33,7 +33,11 @@ function isProductionCodeFile(relativePath: string): boolean {
|
||||
}
|
||||
|
||||
function listGitCodeFiles(root: string): string[] | null {
|
||||
return listGitTrackedFiles({ repoRoot, pathspecs: root })?.filter(isProductionCodeFile) ?? null;
|
||||
return (
|
||||
listGitTrackedFiles({ repoRoot, pathspecs: root })
|
||||
?.filter(isProductionCodeFile)
|
||||
.filter((relativePath) => fs.existsSync(path.join(repoRoot, relativePath))) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
function walkCodeFiles(dir: string, files: string[] = []): string[] {
|
||||
|
||||
Reference in New Issue
Block a user