test: stabilize slow contract and integration suites

This commit is contained in:
Peter Steinberger
2026-03-27 04:53:55 +00:00
parent a9e241dacb
commit ffa2a47c58
8 changed files with 149 additions and 131 deletions

View File

@@ -43,6 +43,8 @@ function createExecDryRunBatch(params: { markerPath: string }) {
command: process.execPath,
args: ["-e", script],
allowInsecurePath: true,
timeoutMs: 15_000,
noOutputTimeoutMs: 15_000,
},
},
{

View File

@@ -473,7 +473,7 @@ describe("config strict validation", () => {
}
});
it("flags legacy config entries without auto-migrating", async () => {
it("rejects removed legacy config entries without auto-migrating", async () => {
await withTempHome(async (home) => {
await writeOpenClawConfig(home, {
memorySearch: { provider: "local", fallback: "none" },

View File

@@ -8,6 +8,8 @@ import {
runWithModelFallbackMock,
} from "./run.test-harness.js";
const RUN_OWNER_AUTH_TIMEOUT_MS = 300_000;
const runCronIsolatedAgentTurn = await loadRunCronIsolatedAgentTurn();
function makeParams() {
@@ -55,11 +57,19 @@ describe("runCronIsolatedAgentTurn owner auth", () => {
vi.stubEnv("OPENCLAW_TEST_FAST", previousFastTestEnv);
});
it("passes senderIsOwner=true to isolated cron agent runs", async () => {
await runCronIsolatedAgentTurn(makeParams());
it(
"passes senderIsOwner=true to isolated cron agent runs",
{ timeout: RUN_OWNER_AUTH_TIMEOUT_MS },
async () => {
await runCronIsolatedAgentTurn(makeParams());
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
const senderIsOwner = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.senderIsOwner;
expect(senderIsOwner).toBe(true);
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
const senderIsOwner = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.senderIsOwner;
expect(senderIsOwner).toBe(true);
const toolNames = createOpenClawCodingTools({ senderIsOwner }).map((tool) => tool.name);
expect(toolNames).toContain("cron");
expect(toolNames).toContain("gateway");
},
);
});

View File

@@ -10,6 +10,8 @@ import {
} from "../provider-runtime.test-support.js";
import type { ProviderPlugin } from "../types.js";
const PROVIDER_CATALOG_CONTRACT_TIMEOUT_MS = 300_000;
type ResolvePluginProviders = typeof import("../providers.runtime.js").resolvePluginProviders;
type ResolveOwningPluginIdsForProvider =
typeof import("../providers.js").resolveOwningPluginIdsForProvider;
@@ -41,7 +43,7 @@ let resolveProviderBuiltInModelSuppression: typeof import("../provider-runtime.j
let openaiProviders: ProviderPlugin[];
let openaiProvider: ProviderPlugin;
describe("provider catalog contract", () => {
describe("provider catalog contract", { timeout: PROVIDER_CATALOG_CONTRACT_TIMEOUT_MS }, () => {
beforeAll(async () => {
const openaiPlugin = await import("../../../extensions/openai/index.ts");
openaiProviders = registerProviderPlugin({

View File

@@ -10,6 +10,8 @@ import {
speechProviderContractRegistry,
} from "./registry.js";
const REGISTRY_CONTRACT_TIMEOUT_MS = 300_000;
function findProviderIdsForPlugin(pluginId: string) {
return (
pluginRegistrationContractRegistry.find((entry) => entry.pluginId === pluginId)?.providerIds ??
@@ -113,10 +115,14 @@ describe("plugin contract registry", () => {
expect(ids).toEqual([...new Set(ids)]);
});
it("does not duplicate bundled speech provider ids", () => {
const ids = speechProviderContractRegistry.map((entry) => entry.provider.id);
expect(ids).toEqual([...new Set(ids)]);
});
it(
"does not duplicate bundled speech provider ids",
{ timeout: REGISTRY_CONTRACT_TIMEOUT_MS },
() => {
const ids = speechProviderContractRegistry.map((entry) => entry.provider.id);
expect(ids).toEqual([...new Set(ids)]);
},
);
it("does not duplicate bundled media provider ids", () => {
const ids = mediaUnderstandingProviderContractRegistry.map((entry) => entry.provider.id);

View File

@@ -31,6 +31,7 @@ vi.mock("@mariozechner/pi-ai/oauth", async () => {
};
});
<<<<<<< HEAD
vi.mock("../../../extensions/openai/openai-codex-provider.runtime.js", () => ({
refreshOpenAICodexToken: refreshOpenAICodexTokenMock,
}));
@@ -118,7 +119,7 @@ function requireProviderContractProvider(providerId: string): ProviderPlugin {
return provider;
}
describe("provider runtime contract", () => {
describe("provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => {
beforeAll(async () => {
providerRuntimeContractProviders.clear();
const registeredFixtures = await Promise.all(
@@ -143,11 +144,9 @@ describe("provider runtime contract", () => {
}
}
}, CONTRACT_SETUP_TIMEOUT_MS);
beforeEach(() => {
refreshOpenAICodexTokenMock.mockReset();
getOAuthProvidersMock.mockClear();
refreshOpenAICodexTokenMock.mockReset();
}, CONTRACT_SETUP_TIMEOUT_MS);
describe("anthropic", () => {
@@ -618,6 +617,7 @@ describe("provider runtime contract", () => {
describe("openai-codex", () => {
it(
"owns refresh fallback for accountId extraction failures",
{ timeout: CONTRACT_SETUP_TIMEOUT_MS },
async () => {
const provider = requireProviderContractProvider("openai-codex");
const credential = {
@@ -628,27 +628,12 @@ describe("provider runtime contract", () => {
expires: Date.now() - 60_000,
};
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString(
"base64url",
refreshOpenAICodexTokenMock.mockRejectedValueOnce(
new Error("Failed to extract accountId from token"),
);
const payload = Buffer.from(JSON.stringify({})).toString("base64url");
const accessTokenWithoutAccountId = `${header}.${payload}.sig`;
const originalFetch = globalThis.fetch;
globalThis.fetch = vi.fn(async () =>
makeResponse(200, {
access_token: accessTokenWithoutAccountId,
refresh_token: "refreshed-refresh-token",
expires_in: 3600,
}),
) as typeof fetch;
try {
await expect(provider.refreshOAuth?.(credential)).resolves.toEqual(credential);
} finally {
globalThis.fetch = originalFetch;
}
await expect(provider.refreshOAuth?.(credential)).resolves.toEqual(credential);
},
CONTRACT_SETUP_TIMEOUT_MS,
);
it("owns forward-compat codex models", () => {

View File

@@ -1,39 +1,45 @@
import { describe, expect, it } from "vitest";
import { resolveBundledPluginWebSearchProviders } from "./web-search-providers.js";
describe("resolveBundledPluginWebSearchProviders", () => {
it("returns bundled providers in alphabetical order", () => {
const providers = resolveBundledPluginWebSearchProviders({});
const WEB_SEARCH_PROVIDER_TEST_TIMEOUT_MS = 300_000;
expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([
"brave:brave",
"duckduckgo:duckduckgo",
"exa:exa",
"firecrawl:firecrawl",
"google:gemini",
"xai:grok",
"moonshot:kimi",
"perplexity:perplexity",
"tavily:tavily",
]);
expect(providers.map((provider) => provider.credentialPath)).toEqual([
"plugins.entries.brave.config.webSearch.apiKey",
"",
"plugins.entries.exa.config.webSearch.apiKey",
"plugins.entries.firecrawl.config.webSearch.apiKey",
"plugins.entries.google.config.webSearch.apiKey",
"plugins.entries.xai.config.webSearch.apiKey",
"plugins.entries.moonshot.config.webSearch.apiKey",
"plugins.entries.perplexity.config.webSearch.apiKey",
"plugins.entries.tavily.config.webSearch.apiKey",
]);
expect(providers.find((provider) => provider.id === "firecrawl")?.applySelectionConfig).toEqual(
expect.any(Function),
);
expect(
providers.find((provider) => provider.id === "perplexity")?.resolveRuntimeMetadata,
).toEqual(expect.any(Function));
});
describe("resolveBundledPluginWebSearchProviders", () => {
it(
"returns bundled providers in alphabetical order",
{ timeout: WEB_SEARCH_PROVIDER_TEST_TIMEOUT_MS },
() => {
const providers = resolveBundledPluginWebSearchProviders({});
expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([
"brave:brave",
"duckduckgo:duckduckgo",
"exa:exa",
"firecrawl:firecrawl",
"google:gemini",
"xai:grok",
"moonshot:kimi",
"perplexity:perplexity",
"tavily:tavily",
]);
expect(providers.map((provider) => provider.credentialPath)).toEqual([
"plugins.entries.brave.config.webSearch.apiKey",
"",
"plugins.entries.exa.config.webSearch.apiKey",
"plugins.entries.firecrawl.config.webSearch.apiKey",
"plugins.entries.google.config.webSearch.apiKey",
"plugins.entries.xai.config.webSearch.apiKey",
"plugins.entries.moonshot.config.webSearch.apiKey",
"plugins.entries.perplexity.config.webSearch.apiKey",
"plugins.entries.tavily.config.webSearch.apiKey",
]);
expect(
providers.find((provider) => provider.id === "firecrawl")?.applySelectionConfig,
).toEqual(expect.any(Function));
expect(
providers.find((provider) => provider.id === "perplexity")?.resolveRuntimeMetadata,
).toEqual(expect.any(Function));
},
);
it("can augment restrictive allowlists for bundled compatibility", () => {
const providers = resolveBundledPluginWebSearchProviders({

View File

@@ -27,6 +27,7 @@ const OPENAI_FILE_KEY_REF = {
provider: "default",
id: "/providers/openai/apiKey",
} as const;
const SECRETS_RUNTIME_INTEGRATION_TIMEOUT_MS = 300_000;
const allowInsecureTempSecretFile = process.platform === "win32";
function asConfig(value: unknown): OpenClawConfig {
@@ -252,38 +253,55 @@ describe("secrets runtime snapshot integration", () => {
});
});
it("keeps last-known-good web runtime snapshot when reload introduces unresolved active web refs", async () => {
await withTempHome("openclaw-secrets-runtime-web-reload-lkg-", async (home) => {
const prepared = await prepareSecretsRuntimeSnapshot({
config: asConfig({
tools: {
web: {
search: {
provider: "gemini",
gemini: {
apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_GEMINI_API_KEY" },
it(
"keeps last-known-good web runtime snapshot when reload introduces unresolved active web refs",
async () => {
await withTempHome("openclaw-secrets-runtime-web-reload-lkg-", async (home) => {
const prepared = await prepareSecretsRuntimeSnapshot({
config: asConfig({
tools: {
web: {
search: {
provider: "gemini",
gemini: {
apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_GEMINI_API_KEY" },
},
},
},
},
}),
env: {
WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-runtime-key",
},
}),
env: {
WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-runtime-key",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
activateSecretsRuntimeSnapshot(prepared);
activateSecretsRuntimeSnapshot(prepared);
await expect(
writeConfigFile({
...loadConfig(),
plugins: {
entries: {
google: {
config: {
webSearch: {
await expect(
writeConfigFile({
...loadConfig(),
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: {
source: "env",
provider: "default",
id: "MISSING_WEB_SEARCH_GEMINI_API_KEY",
},
},
},
},
},
},
tools: {
web: {
search: {
provider: "gemini",
gemini: {
apiKey: {
source: "env",
provider: "default",
@@ -293,49 +311,38 @@ describe("secrets runtime snapshot integration", () => {
},
},
},
},
tools: {
web: {
search: {
provider: "gemini",
gemini: {
apiKey: {
source: "env",
provider: "default",
id: "MISSING_WEB_SEARCH_GEMINI_API_KEY",
},
},
},
},
},
}),
).rejects.toThrow(
/runtime snapshot refresh failed: .*WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK/i,
);
}),
).rejects.toThrow(
/runtime snapshot refresh failed: .*WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK/i,
);
const activeAfterFailure = getActiveSecretsRuntimeSnapshot();
expect(activeAfterFailure).not.toBeNull();
expect(loadConfig().tools?.web?.search?.gemini?.apiKey).toBe("web-search-gemini-runtime-key");
expect(activeAfterFailure?.sourceConfig.tools?.web?.search?.gemini?.apiKey).toEqual({
source: "env",
provider: "default",
id: "WEB_SEARCH_GEMINI_API_KEY",
});
expect(getActiveRuntimeWebToolsMetadata()?.search.selectedProvider).toBe("gemini");
const activeAfterFailure = getActiveSecretsRuntimeSnapshot();
expect(activeAfterFailure).not.toBeNull();
expect(loadConfig().tools?.web?.search?.gemini?.apiKey).toBe(
"web-search-gemini-runtime-key",
);
expect(activeAfterFailure?.sourceConfig.tools?.web?.search?.gemini?.apiKey).toEqual({
source: "env",
provider: "default",
id: "WEB_SEARCH_GEMINI_API_KEY",
});
expect(getActiveRuntimeWebToolsMetadata()?.search.selectedProvider).toBe("gemini");
const persistedConfig = JSON.parse(
await fs.readFile(path.join(home, ".openclaw", "openclaw.json"), "utf8"),
) as OpenClawConfig;
const persistedGoogleWebSearchConfig = persistedConfig.plugins?.entries?.google?.config as
| { webSearch?: { apiKey?: unknown } }
| undefined;
expect(persistedGoogleWebSearchConfig?.webSearch?.apiKey).toEqual({
source: "env",
provider: "default",
id: "MISSING_WEB_SEARCH_GEMINI_API_KEY",
const persistedConfig = JSON.parse(
await fs.readFile(path.join(home, ".openclaw", "openclaw.json"), "utf8"),
) as OpenClawConfig;
const persistedGoogleWebSearchConfig = persistedConfig.plugins?.entries?.google?.config as
| { webSearch?: { apiKey?: unknown } }
| undefined;
expect(persistedGoogleWebSearchConfig?.webSearch?.apiKey).toEqual({
source: "env",
provider: "default",
id: "MISSING_WEB_SEARCH_GEMINI_API_KEY",
});
});
});
}, 180_000);
},
SECRETS_RUNTIME_INTEGRATION_TIMEOUT_MS,
);
it("recomputes config-derived agent dirs when refreshing active secrets runtime snapshots", async () => {
await withTempHome("openclaw-secrets-runtime-agent-dirs-", async (home) => {