mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-20 21:23:23 +00:00
test: trim heavy imports and harden ci checks
This commit is contained in:
@@ -532,6 +532,39 @@ export function resolveBoundaryCheckLockPath(rootDir = repoRoot) {
|
||||
return resolve(rootDir, "dist", ".extension-package-boundary.lock");
|
||||
}
|
||||
|
||||
function resolveBoundaryCheckLockOwnerPath(lockPath) {
|
||||
return join(lockPath, "owner.json");
|
||||
}
|
||||
|
||||
function isProcessAlive(pid) {
|
||||
if (!Number.isInteger(pid) || pid <= 0) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return Boolean(error && typeof error === "object" && "code" in error && error.code === "EPERM");
|
||||
}
|
||||
}
|
||||
|
||||
function removeStaleBoundaryCheckLock(lockPath) {
|
||||
const ownerPath = resolveBoundaryCheckLockOwnerPath(lockPath);
|
||||
let owner;
|
||||
try {
|
||||
owner = JSON.parse(readFileSync(ownerPath, "utf8"));
|
||||
} catch {
|
||||
rmSync(lockPath, { force: true, recursive: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (owner && typeof owner === "object" && isProcessAlive(owner.pid)) {
|
||||
return false;
|
||||
}
|
||||
rmSync(lockPath, { force: true, recursive: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
export function acquireBoundaryCheckLock(params = {}) {
|
||||
const rootDir = params.rootDir ?? repoRoot;
|
||||
const processObject = params.processObject ?? process;
|
||||
@@ -541,26 +574,37 @@ export function acquireBoundaryCheckLock(params = {}) {
|
||||
mkdirSync(lockPath);
|
||||
} catch (error) {
|
||||
if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
|
||||
throw attachStepFailureMetadata(
|
||||
new Error(
|
||||
[
|
||||
"extension package boundary check",
|
||||
"kind: lock-contention",
|
||||
`lock: ${lockPath}`,
|
||||
"another extension package boundary check is already running in this checkout",
|
||||
].join("\n\n"),
|
||||
{ cause: error },
|
||||
),
|
||||
"extension package boundary check",
|
||||
{
|
||||
kind: "lock-contention",
|
||||
note: `lock: ${lockPath}\nanother extension package boundary check is already running in this checkout`,
|
||||
},
|
||||
);
|
||||
if (removeStaleBoundaryCheckLock(lockPath)) {
|
||||
mkdirSync(lockPath);
|
||||
} else {
|
||||
throw attachStepFailureMetadata(
|
||||
new Error(
|
||||
[
|
||||
"extension package boundary check",
|
||||
"kind: lock-contention",
|
||||
`lock: ${lockPath}`,
|
||||
"another extension package boundary check is already running in this checkout",
|
||||
].join("\n\n"),
|
||||
{ cause: error },
|
||||
),
|
||||
"extension package boundary check",
|
||||
{
|
||||
kind: "lock-contention",
|
||||
note: `lock: ${lockPath}\nanother extension package boundary check is already running in this checkout`,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
resolveBoundaryCheckLockOwnerPath(lockPath),
|
||||
`${JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const release = () => {
|
||||
rmSync(lockPath, { force: true, recursive: true });
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ const VALID_MODES = new Set(["all", "package-boundary"]);
|
||||
const ROOT_DTS_INPUTS = [
|
||||
"tsconfig.json",
|
||||
"tsconfig.plugin-sdk.dts.json",
|
||||
"src/channels/plugins",
|
||||
"src/plugin-sdk",
|
||||
"src/video-generation/dashscope-compatible.ts",
|
||||
"src/video-generation/types.ts",
|
||||
@@ -20,6 +21,7 @@ const ROOT_DTS_INPUTS = [
|
||||
const PACKAGE_DTS_INPUTS = [
|
||||
"tsconfig.json",
|
||||
"packages/plugin-sdk/tsconfig.json",
|
||||
"src/channels/plugins",
|
||||
"src/plugin-sdk",
|
||||
"src/video-generation/dashscope-compatible.ts",
|
||||
"src/video-generation/types.ts",
|
||||
|
||||
@@ -168,7 +168,7 @@ vi.mock("../logging/subsystem.js", () => ({
|
||||
|
||||
vi.mock("../routing/session-key.js", () => ({
|
||||
normalizeAgentId: (id: string) => id,
|
||||
normalizeMainKey: (key?: string) => key ?? "main",
|
||||
normalizeMainKey: (key?: string | null) => key?.trim() || "main",
|
||||
}));
|
||||
|
||||
vi.mock("../runtime.js", () => ({
|
||||
|
||||
@@ -61,8 +61,8 @@ describe("resolveAuthProfileOrder", () => {
|
||||
},
|
||||
usageStats: {
|
||||
"anthropic:ready": { lastUsed: 50 },
|
||||
"anthropic:cool1": { cooldownUntil: now + 5_000 },
|
||||
"anthropic:cool2": { cooldownUntil: now + 1_000 },
|
||||
"anthropic:cool1": { cooldownUntil: now + 120_000 },
|
||||
"anthropic:cool2": { cooldownUntil: now + 60_000 },
|
||||
},
|
||||
},
|
||||
provider: "anthropic",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { getEnvApiKey } from "@mariozechner/pi-ai";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { getShellEnvAppliedKeys } from "../infra/shell-env.js";
|
||||
import { resolvePluginSetupProvider } from "../plugins/setup-registry.js";
|
||||
import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js";
|
||||
@@ -11,6 +13,29 @@ export type EnvApiKeyResult = {
|
||||
source: string;
|
||||
};
|
||||
|
||||
function hasGoogleVertexAdcCredentials(env: NodeJS.ProcessEnv): boolean {
|
||||
const explicitCredentialsPath = normalizeOptionalSecretInput(env.GOOGLE_APPLICATION_CREDENTIALS);
|
||||
if (explicitCredentialsPath) {
|
||||
return fs.existsSync(explicitCredentialsPath);
|
||||
}
|
||||
const homeDir = normalizeOptionalSecretInput(env.HOME) ?? os.homedir();
|
||||
return fs.existsSync(
|
||||
path.join(homeDir, ".config", "gcloud", "application_default_credentials.json"),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveGoogleVertexEnvApiKey(env: NodeJS.ProcessEnv): string | undefined {
|
||||
const explicitApiKey = normalizeOptionalSecretInput(env.GOOGLE_CLOUD_API_KEY);
|
||||
if (explicitApiKey) {
|
||||
return explicitApiKey;
|
||||
}
|
||||
const hasProject = Boolean(env.GOOGLE_CLOUD_PROJECT || env.GCLOUD_PROJECT);
|
||||
const hasLocation = Boolean(env.GOOGLE_CLOUD_LOCATION);
|
||||
return hasProject && hasLocation && hasGoogleVertexAdcCredentials(env)
|
||||
? GCP_VERTEX_CREDENTIALS_MARKER
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function resolveEnvApiKey(
|
||||
provider: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
@@ -39,7 +64,7 @@ export function resolveEnvApiKey(
|
||||
}
|
||||
|
||||
if (normalized === "google-vertex") {
|
||||
const envKey = getEnvApiKey(normalized);
|
||||
const envKey = resolveGoogleVertexEnvApiKey(env);
|
||||
if (!envKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -821,6 +821,34 @@ describe("getApiKeyForModel", () => {
|
||||
expect(resolved).toBeNull();
|
||||
});
|
||||
|
||||
it("resolveEnvApiKey('google-vertex') uses the provided env snapshot", async () => {
|
||||
const resolved = resolveEnvApiKey("google-vertex", {
|
||||
GOOGLE_CLOUD_API_KEY: "google-cloud-api-key",
|
||||
} as NodeJS.ProcessEnv);
|
||||
|
||||
expect(resolved?.apiKey).toBe("google-cloud-api-key");
|
||||
expect(resolved?.source).toBe("gcloud adc");
|
||||
});
|
||||
|
||||
it("resolveEnvApiKey('google-vertex') accepts ADC credentials from the provided env snapshot", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-google-adc-"));
|
||||
const credentialsPath = path.join(tempDir, "adc.json");
|
||||
await fs.writeFile(credentialsPath, "{}", "utf8");
|
||||
|
||||
try {
|
||||
const resolved = resolveEnvApiKey("google-vertex", {
|
||||
GOOGLE_APPLICATION_CREDENTIALS: credentialsPath,
|
||||
GOOGLE_CLOUD_LOCATION: "us-central1",
|
||||
GOOGLE_CLOUD_PROJECT: "vertex-project",
|
||||
} as NodeJS.ProcessEnv);
|
||||
|
||||
expect(resolved?.apiKey).toBe("gcp-vertex-credentials");
|
||||
expect(resolved?.source).toBe("gcloud adc");
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("resolveEnvApiKey('anthropic-vertex') accepts GOOGLE_APPLICATION_CREDENTIALS with project_id", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-adc-"));
|
||||
const credentialsPath = path.join(tempDir, "adc.json");
|
||||
|
||||
@@ -1192,9 +1192,12 @@ describe("OpenResponses HTTP API (e2e)", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
await vi.waitFor(
|
||||
() => {
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
{ timeout: 5_000, interval: 50 },
|
||||
);
|
||||
|
||||
clientReq.destroy();
|
||||
|
||||
@@ -1245,9 +1248,12 @@ describe("OpenResponses HTTP API (e2e)", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
await vi.waitFor(
|
||||
() => {
|
||||
expect(agentCommand).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
{ timeout: 5_000, interval: 50 },
|
||||
);
|
||||
|
||||
clientReq.destroy();
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ export function registerDefaultAuthTokenSuite(): void {
|
||||
await withGatewayServer(async ({ port: isolatedPort }) => {
|
||||
const ws = await openWs(isolatedPort);
|
||||
const handshakeTimeoutMs = getPreauthHandshakeTimeoutMsFromEnv();
|
||||
const closed = await waitForWsClose(ws, handshakeTimeoutMs + 2500);
|
||||
const closed = await waitForWsClose(ws, handshakeTimeoutMs + 10_000);
|
||||
expect(closed).toBe(true);
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -22,7 +22,7 @@ import { agentCommand } from "./test-helpers.runtime-state.js";
|
||||
import { installConnectedControlUiServerSuite } from "./test-with-server.js";
|
||||
|
||||
installGatewayTestHooks({ scope: "suite" });
|
||||
const CHAT_RESPONSE_TIMEOUT_MS = 4_000;
|
||||
const CHAT_RESPONSE_TIMEOUT_MS = 10_000;
|
||||
|
||||
let ws: WebSocket;
|
||||
let port: number;
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from "./test-helpers.js";
|
||||
|
||||
installGatewayTestHooks({ scope: "suite" });
|
||||
const NODE_CONNECT_TIMEOUT_MS = 3_000;
|
||||
const NODE_CONNECT_TIMEOUT_MS = 10_000;
|
||||
const CONNECT_REQ_TIMEOUT_MS = 2_000;
|
||||
|
||||
function createDeviceIdentity(): DeviceIdentity {
|
||||
@@ -393,6 +393,9 @@ describe("node.invoke approval bypass", () => {
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
});
|
||||
expect(invoke.ok).toBe(true);
|
||||
for (let i = 0; i < 100 && !lastInvokeParams; i += 1) {
|
||||
await sleep(50);
|
||||
}
|
||||
expect(lastInvokeParams).toBeTruthy();
|
||||
expect(lastInvokeParams?.["approved"]).toBe(true);
|
||||
expect(lastInvokeParams?.["approvalDecision"]).toBe("allow-once");
|
||||
|
||||
@@ -218,7 +218,7 @@ export async function installPackageDir(params: {
|
||||
candidatePaths: [canonicalTargetDir],
|
||||
});
|
||||
stageDir = await fs.mkdtemp(path.join(installBaseRealPath, ".openclaw-install-stage-"));
|
||||
await fs.cp(params.sourceDir, stageDir, { recursive: true });
|
||||
await fs.cp(params.sourceDir, stageDir, { recursive: true, verbatimSymlinks: true });
|
||||
} catch (err) {
|
||||
return await fail(`${params.copyErrorPrefix}: ${String(err)}`, err);
|
||||
}
|
||||
|
||||
@@ -184,13 +184,13 @@ async function inspectNodeModulesSymlinkTarget(params: {
|
||||
);
|
||||
}
|
||||
|
||||
const resolvedTargetStats = await fs.stat(resolvedTargetPath);
|
||||
const resolvedTargetRelativePath = path.relative(params.rootRealPath, resolvedTargetPath);
|
||||
const resolvedTargetStat = await fs.lstat(resolvedTargetPath);
|
||||
return {
|
||||
blockedDirectoryFinding: findBlockedPackageDirectoryInPath({
|
||||
pathRelativeToRoot: resolvedTargetRelativePath,
|
||||
}),
|
||||
blockedFileFinding: resolvedTargetStat.isFile()
|
||||
blockedFileFinding: resolvedTargetStats.isFile()
|
||||
? findBlockedPackageFileAliasInPath({
|
||||
pathRelativeToRoot: resolvedTargetRelativePath,
|
||||
})
|
||||
@@ -262,6 +262,16 @@ function resolvePackageManifestTraversalLimits(): PackageManifestTraversalLimits
|
||||
};
|
||||
}
|
||||
|
||||
async function resolvePackageManifestPath(dir: string): Promise<string | undefined> {
|
||||
const manifestPath = path.join(dir, "package.json");
|
||||
try {
|
||||
const stats = await fs.stat(manifestPath);
|
||||
return stats.isFile() ? manifestPath : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function collectPackageManifestPaths(
|
||||
rootDir: string,
|
||||
): Promise<PackageManifestTraversalResult> {
|
||||
@@ -361,6 +371,10 @@ async function collectPackageManifestPaths(
|
||||
directoryRelativePath: relativeNextPath,
|
||||
});
|
||||
if (blockedDirectoryFinding) {
|
||||
const manifestPath = await resolvePackageManifestPath(nextPath);
|
||||
if (manifestPath) {
|
||||
packageManifestPaths.push(manifestPath);
|
||||
}
|
||||
return {
|
||||
blockedDirectoryFinding,
|
||||
packageManifestPaths,
|
||||
@@ -400,35 +414,6 @@ async function scanManifestDependencyDenylist(params: {
|
||||
targetLabel: string;
|
||||
}): Promise<InstallSecurityScanResult | undefined> {
|
||||
const traversalResult = await collectPackageManifestPaths(params.packageDir);
|
||||
if (traversalResult.blockedDirectoryFinding) {
|
||||
const reason = buildBlockedDependencyDirectoryReason({
|
||||
dependencyName: traversalResult.blockedDirectoryFinding.dependencyName,
|
||||
directoryRelativePath: traversalResult.blockedDirectoryFinding.directoryRelativePath,
|
||||
targetLabel: params.targetLabel,
|
||||
});
|
||||
params.logger.warn?.(`WARNING: ${reason}`);
|
||||
return {
|
||||
blocked: {
|
||||
code: "security_scan_blocked",
|
||||
reason,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (traversalResult.blockedFileFinding) {
|
||||
const reason = buildBlockedDependencyFileReason({
|
||||
dependencyName: traversalResult.blockedFileFinding.dependencyName,
|
||||
fileRelativePath: traversalResult.blockedFileFinding.fileRelativePath,
|
||||
targetLabel: params.targetLabel,
|
||||
});
|
||||
params.logger.warn?.(`WARNING: ${reason}`);
|
||||
return {
|
||||
blocked: {
|
||||
code: "security_scan_blocked",
|
||||
reason,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const packageManifestPaths = traversalResult.packageManifestPaths;
|
||||
for (const manifestPath of packageManifestPaths) {
|
||||
let manifest: PackageManifest;
|
||||
@@ -458,6 +443,34 @@ async function scanManifestDependencyDenylist(params: {
|
||||
},
|
||||
};
|
||||
}
|
||||
if (traversalResult.blockedDirectoryFinding) {
|
||||
const reason = buildBlockedDependencyDirectoryReason({
|
||||
dependencyName: traversalResult.blockedDirectoryFinding.dependencyName,
|
||||
directoryRelativePath: traversalResult.blockedDirectoryFinding.directoryRelativePath,
|
||||
targetLabel: params.targetLabel,
|
||||
});
|
||||
params.logger.warn?.(`WARNING: ${reason}`);
|
||||
return {
|
||||
blocked: {
|
||||
code: "security_scan_blocked",
|
||||
reason,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (traversalResult.blockedFileFinding) {
|
||||
const reason = buildBlockedDependencyFileReason({
|
||||
dependencyName: traversalResult.blockedFileFinding.dependencyName,
|
||||
fileRelativePath: traversalResult.blockedFileFinding.fileRelativePath,
|
||||
targetLabel: params.targetLabel,
|
||||
});
|
||||
params.logger.warn?.(`WARNING: ${reason}`);
|
||||
return {
|
||||
blocked: {
|
||||
code: "security_scan_blocked",
|
||||
reason,
|
||||
},
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@mariozechner/pi-ai", async () => {
|
||||
const original =
|
||||
await vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai");
|
||||
return {
|
||||
...original,
|
||||
getOAuthApiKey: () => undefined,
|
||||
getOAuthProviders: () => [],
|
||||
loginOpenAICodex: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@mariozechner/pi-ai/oauth", () => ({
|
||||
getOAuthApiKey: () => undefined,
|
||||
getOAuthProviders: () => [],
|
||||
|
||||
Reference in New Issue
Block a user