test: trim heavy imports and harden ci checks

This commit is contained in:
Peter Steinberger
2026-04-10 19:12:19 +01:00
parent d9b33205dc
commit e7db987ce6
13 changed files with 184 additions and 74 deletions

View File

@@ -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 });
};

View File

@@ -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",

View File

@@ -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", () => ({

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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");

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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: () => [],