mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 10:22:32 +00:00
test: stabilize slow contract and integration suites
This commit is contained in:
@@ -43,6 +43,8 @@ function createExecDryRunBatch(params: { markerPath: string }) {
|
||||
command: process.execPath,
|
||||
args: ["-e", script],
|
||||
allowInsecurePath: true,
|
||||
timeoutMs: 15_000,
|
||||
noOutputTimeoutMs: 15_000,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user