mirror of https://github.com/openclaw/openclaw.git
test: move extension-owned coverage out of core
This commit is contained in:
parent
2bfbddb81f
commit
64755c52f2
|
|
@ -1,6 +1,14 @@
|
|||
import fs from "node:fs";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { validateJsonSchemaValue } from "../../../src/plugins/schema-validator.js";
|
||||
import { __testing, createBraveWebSearchProvider } from "./brave-web-search-provider.js";
|
||||
|
||||
const braveManifest = JSON.parse(
|
||||
fs.readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf-8"),
|
||||
) as {
|
||||
configSchema?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
describe("brave web search provider", () => {
|
||||
const priorFetch = global.fetch;
|
||||
|
||||
|
|
@ -58,6 +66,51 @@ describe("brave web search provider", () => {
|
|||
expect(__testing.resolveBraveMode({ mode: "llm-context" })).toBe("llm-context");
|
||||
});
|
||||
|
||||
it("accepts llm-context in the Brave plugin config schema", () => {
|
||||
if (!braveManifest.configSchema) {
|
||||
throw new Error("Expected Brave manifest config schema");
|
||||
}
|
||||
|
||||
const result = validateJsonSchemaValue({
|
||||
schema: braveManifest.configSchema,
|
||||
cacheKey: "test:brave-config-schema",
|
||||
value: {
|
||||
webSearch: {
|
||||
mode: "llm-context",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects invalid Brave mode values in the plugin config schema", () => {
|
||||
if (!braveManifest.configSchema) {
|
||||
throw new Error("Expected Brave manifest config schema");
|
||||
}
|
||||
|
||||
const result = validateJsonSchemaValue({
|
||||
schema: braveManifest.configSchema,
|
||||
cacheKey: "test:brave-config-schema",
|
||||
value: {
|
||||
webSearch: {
|
||||
mode: "invalid-mode",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.errors).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: "webSearch.mode",
|
||||
allowedValues: ["web", "llm-context"],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("maps llm-context results into wrapped source entries", () => {
|
||||
expect(
|
||||
__testing.mapBraveLlmContextResults({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../../src/config/config.js";
|
||||
import { withEnv } from "../../test/helpers/plugins/env.js";
|
||||
import { __testing, createGeminiWebSearchProvider } from "./src/gemini-web-search-provider.js";
|
||||
|
||||
describe("google web search provider", () => {
|
||||
it("falls back to GEMINI_API_KEY from the environment", () => {
|
||||
withEnv({ GEMINI_API_KEY: "AIza-env-test" }, () => {
|
||||
expect(__testing.resolveGeminiApiKey()).toBe("AIza-env-test");
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers configured api keys over env fallbacks", () => {
|
||||
withEnv({ GEMINI_API_KEY: "AIza-env-test" }, () => {
|
||||
expect(__testing.resolveGeminiApiKey({ apiKey: "AIza-configured-test" })).toBe(
|
||||
"AIza-configured-test",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("stores configured credentials at the canonical plugin config path", () => {
|
||||
const provider = createGeminiWebSearchProvider();
|
||||
const config = {} as OpenClawConfig;
|
||||
|
||||
provider.setConfiguredCredentialValue?.(config, "AIza-plugin-test");
|
||||
|
||||
expect(provider.credentialPath).toBe("plugins.entries.google.config.webSearch.apiKey");
|
||||
expect(provider.getConfiguredCredentialValue?.(config)).toBe("AIza-plugin-test");
|
||||
});
|
||||
|
||||
it("defaults the Gemini web search model and trims explicit overrides", () => {
|
||||
expect(__testing.resolveGeminiModel()).toBe("gemini-2.5-flash");
|
||||
expect(__testing.resolveGeminiModel({ model: " gemini-2.5-pro " })).toBe("gemini-2.5-pro");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../../config/config.js";
|
||||
import { resetPluginRuntimeStateForTest } from "../../plugins/runtime.js";
|
||||
|
||||
const fallbackState = vi.hoisted(() => ({
|
||||
activeDirName: null as string | null,
|
||||
resolveSessionConversation: null as
|
||||
| ((params: { kind: "group" | "channel"; rawId: string }) => {
|
||||
id: string;
|
||||
threadId?: string | null;
|
||||
baseConversationId?: string | null;
|
||||
parentConversationCandidates?: string[];
|
||||
} | null)
|
||||
| null,
|
||||
}));
|
||||
|
||||
vi.mock("../../plugin-sdk/facade-runtime.js", () => ({
|
||||
tryLoadActivatedBundledPluginPublicSurfaceModuleSync: ({ dirName }: { dirName: string }) =>
|
||||
dirName === fallbackState.activeDirName && fallbackState.resolveSessionConversation
|
||||
? { resolveSessionConversation: fallbackState.resolveSessionConversation }
|
||||
: null,
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/bundled-plugin-metadata.js", () => ({
|
||||
resolveBundledPluginPublicSurfacePath: ({ dirName }: { dirName: string }) =>
|
||||
dirName === fallbackState.activeDirName ? `/tmp/${dirName}/session-key-api.js` : null,
|
||||
}));
|
||||
|
||||
import { resolveSessionConversationRef } from "./session-conversation.js";
|
||||
|
||||
describe("session conversation bundled fallback", () => {
|
||||
beforeEach(() => {
|
||||
fallbackState.activeDirName = null;
|
||||
fallbackState.resolveSessionConversation = null;
|
||||
resetPluginRuntimeStateForTest();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
});
|
||||
|
||||
it("delegates pre-bootstrap thread parsing to the active bundled channel plugin", () => {
|
||||
fallbackState.activeDirName = "mock-threaded";
|
||||
fallbackState.resolveSessionConversation = ({ rawId }) => {
|
||||
const [conversationId, threadId] = rawId.split(":topic:");
|
||||
return {
|
||||
id: conversationId,
|
||||
threadId,
|
||||
baseConversationId: conversationId,
|
||||
parentConversationCandidates: [conversationId],
|
||||
};
|
||||
};
|
||||
setRuntimeConfigSnapshot({
|
||||
plugins: {
|
||||
entries: {
|
||||
"mock-threaded": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolveSessionConversationRef("agent:main:mock-threaded:group:room:topic:42")).toEqual({
|
||||
channel: "mock-threaded",
|
||||
kind: "group",
|
||||
rawId: "room:topic:42",
|
||||
id: "room",
|
||||
threadId: "42",
|
||||
baseSessionKey: "agent:main:mock-threaded:group:room",
|
||||
baseConversationId: "room",
|
||||
parentConversationCandidates: ["room"],
|
||||
});
|
||||
});
|
||||
|
||||
it("uses explicit bundled parent candidates before registry bootstrap", () => {
|
||||
fallbackState.activeDirName = "mock-parent";
|
||||
fallbackState.resolveSessionConversation = ({ rawId }) => ({
|
||||
id: rawId,
|
||||
baseConversationId: "room",
|
||||
parentConversationCandidates: ["room:topic:root", "room"],
|
||||
});
|
||||
setRuntimeConfigSnapshot({
|
||||
plugins: {
|
||||
entries: {
|
||||
"mock-parent": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveSessionConversationRef("agent:main:mock-parent:group:room:topic:root:sender:user"),
|
||||
).toEqual({
|
||||
channel: "mock-parent",
|
||||
kind: "group",
|
||||
rawId: "room:topic:root:sender:user",
|
||||
id: "room:topic:root:sender:user",
|
||||
threadId: undefined,
|
||||
baseSessionKey: "agent:main:mock-parent:group:room:topic:root:sender:user",
|
||||
baseConversationId: "room",
|
||||
parentConversationCandidates: ["room:topic:root", "room"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../../config/config.js";
|
||||
import { clearRuntimeConfigSnapshot } from "../../config/config.js";
|
||||
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { createSessionConversationTestRegistry } from "../../test-utils/session-conversation-registry.js";
|
||||
|
|
@ -54,57 +54,6 @@ describe("session conversation routing", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("keeps bundled Telegram topic parsing available before registry bootstrap", () => {
|
||||
resetPluginRuntimeStateForTest();
|
||||
setRuntimeConfigSnapshot({
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolveSessionConversationRef("agent:main:telegram:group:-100123:topic:77")).toEqual({
|
||||
channel: "telegram",
|
||||
kind: "group",
|
||||
rawId: "-100123:topic:77",
|
||||
id: "-100123",
|
||||
threadId: "77",
|
||||
baseSessionKey: "agent:main:telegram:group:-100123",
|
||||
baseConversationId: "-100123",
|
||||
parentConversationCandidates: ["-100123"],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps bundled Feishu parent fallbacks available before registry bootstrap", () => {
|
||||
resetPluginRuntimeStateForTest();
|
||||
setRuntimeConfigSnapshot({
|
||||
plugins: {
|
||||
entries: {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveSessionConversationRef(
|
||||
"agent:main:feishu:group:oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
),
|
||||
).toEqual({
|
||||
channel: "feishu",
|
||||
kind: "group",
|
||||
rawId: "oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
id: "oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
threadId: undefined,
|
||||
baseSessionKey:
|
||||
"agent:main:feishu:group:oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
baseConversationId: "oc_group_chat",
|
||||
parentConversationCandidates: ["oc_group_chat:topic:om_topic_root", "oc_group_chat"],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not load bundled session-key fallbacks for inactive channel plugins", () => {
|
||||
resetPluginRuntimeStateForTest();
|
||||
|
||||
|
|
|
|||
|
|
@ -91,10 +91,83 @@ vi.mock("../plugins/web-search-providers.js", () => {
|
|||
};
|
||||
});
|
||||
|
||||
const secretInputSchema = {
|
||||
oneOf: [
|
||||
{ type: "string" },
|
||||
{
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
source: { type: "string" },
|
||||
provider: { type: "string" },
|
||||
id: { type: "string" },
|
||||
},
|
||||
required: ["source", "provider", "id"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function buildWebSearchPluginSchema() {
|
||||
return {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
webSearch: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
apiKey: secretInputSchema,
|
||||
baseUrl: secretInputSchema,
|
||||
model: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
vi.mock("../plugins/manifest-registry.js", () => ({
|
||||
loadPluginManifestRegistry: () => ({
|
||||
plugins: [
|
||||
{
|
||||
id: "brave",
|
||||
origin: "bundled",
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
rootDir: "/tmp/plugins/brave",
|
||||
source: "test",
|
||||
manifestPath: "/tmp/plugins/brave/openclaw.plugin.json",
|
||||
schemaCacheKey: "test:brave",
|
||||
configSchema: buildWebSearchPluginSchema(),
|
||||
},
|
||||
...["firecrawl", "google", "moonshot", "perplexity", "searxng", "tavily", "xai"].map(
|
||||
(id) => ({
|
||||
id,
|
||||
origin: "bundled",
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
rootDir: `/tmp/plugins/${id}`,
|
||||
source: "test",
|
||||
manifestPath: `/tmp/plugins/${id}/openclaw.plugin.json`,
|
||||
schemaCacheKey: `test:${id}`,
|
||||
configSchema: buildWebSearchPluginSchema(),
|
||||
}),
|
||||
),
|
||||
],
|
||||
diagnostics: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
let validateConfigObjectWithPlugins: typeof import("./config.js").validateConfigObjectWithPlugins;
|
||||
let resolveSearchProvider: typeof import("../agents/tools/web-search.js").__testing.resolveSearchProvider;
|
||||
|
||||
beforeAll(async () => {
|
||||
vi.resetModules();
|
||||
({ validateConfigObjectWithPlugins } = await import("./config.js"));
|
||||
({
|
||||
__testing: { resolveSearchProvider },
|
||||
|
|
@ -257,32 +330,6 @@ describe("web search provider config", () => {
|
|||
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts brave llm-context mode config", () => {
|
||||
const res = validateConfigObjectWithPlugins(
|
||||
buildWebSearchProviderConfig({
|
||||
provider: "brave",
|
||||
providerConfig: {
|
||||
mode: "llm-context",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects invalid brave mode config values", () => {
|
||||
const res = validateConfigObjectWithPlugins(
|
||||
buildWebSearchProviderConfig({
|
||||
provider: "brave",
|
||||
providerConfig: {
|
||||
mode: "invalid-mode",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("web search provider auto-detection", () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,105 @@ import { createWizardPrompter } from "../../test/helpers/wizard-prompter.js";
|
|||
import { createNonExitingRuntime } from "../runtime.js";
|
||||
import { runSearchSetupFlow } from "./search-setup.js";
|
||||
|
||||
const mockGrokProvider = vi.hoisted(() => ({
|
||||
id: "grok",
|
||||
pluginId: "xai",
|
||||
label: "Grok",
|
||||
hint: "Search with xAI",
|
||||
docsUrl: "https://docs.openclaw.ai/tools/web",
|
||||
requiresCredential: true,
|
||||
credentialLabel: "xAI API key",
|
||||
placeholder: "xai-...",
|
||||
signupUrl: "https://x.ai/api",
|
||||
envVars: ["XAI_API_KEY"],
|
||||
onboardingScopes: ["text-inference"],
|
||||
credentialPath: "plugins.entries.xai.config.webSearch.apiKey",
|
||||
getCredentialValue: (search?: Record<string, unknown>) => search?.apiKey,
|
||||
setCredentialValue: (searchConfigTarget: Record<string, unknown>, value: unknown) => {
|
||||
searchConfigTarget.apiKey = value;
|
||||
},
|
||||
getConfiguredCredentialValue: (config?: Record<string, unknown>) =>
|
||||
(
|
||||
config?.plugins as
|
||||
| {
|
||||
entries?: Record<
|
||||
string,
|
||||
{
|
||||
config?: {
|
||||
webSearch?: { apiKey?: unknown };
|
||||
};
|
||||
}
|
||||
>;
|
||||
}
|
||||
| undefined
|
||||
)?.entries?.xai?.config?.webSearch?.apiKey,
|
||||
setConfiguredCredentialValue: (configTarget: Record<string, unknown>, value: unknown) => {
|
||||
const plugins = (configTarget.plugins ??= {}) as Record<string, unknown>;
|
||||
const entries = (plugins.entries ??= {}) as Record<string, unknown>;
|
||||
const xaiEntry = (entries.xai ??= {}) as Record<string, unknown>;
|
||||
const xaiConfig = (xaiEntry.config ??= {}) as Record<string, unknown>;
|
||||
const webSearch = (xaiConfig.webSearch ??= {}) as Record<string, unknown>;
|
||||
webSearch.apiKey = value;
|
||||
},
|
||||
runSetup: async ({
|
||||
config,
|
||||
prompter,
|
||||
}: {
|
||||
config: Record<string, unknown>;
|
||||
prompter: { select: (params: Record<string, unknown>) => Promise<string> };
|
||||
}) => {
|
||||
const enableXSearch = await prompter.select({
|
||||
message: "Enable x_search",
|
||||
options: [
|
||||
{ value: "yes", label: "Yes" },
|
||||
{ value: "no", label: "No" },
|
||||
],
|
||||
});
|
||||
if (enableXSearch !== "yes") {
|
||||
return config;
|
||||
}
|
||||
const model = await prompter.select({
|
||||
message: "Grok model",
|
||||
options: [{ value: "grok-4-1-fast", label: "grok-4-1-fast" }],
|
||||
});
|
||||
const pluginEntries = (config.plugins as { entries?: Record<string, unknown> } | undefined)
|
||||
?.entries;
|
||||
const existingXaiEntry = pluginEntries?.xai as Record<string, unknown> | undefined;
|
||||
const existingXaiConfig = (
|
||||
pluginEntries?.xai as { config?: Record<string, unknown> } | undefined
|
||||
)?.config;
|
||||
return {
|
||||
...config,
|
||||
plugins: {
|
||||
...(config.plugins as Record<string, unknown> | undefined),
|
||||
entries: {
|
||||
...pluginEntries,
|
||||
xai: {
|
||||
...existingXaiEntry,
|
||||
config: {
|
||||
...existingXaiConfig,
|
||||
xSearch: {
|
||||
enabled: true,
|
||||
model,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/bundled-web-search.js", () => ({
|
||||
listBundledWebSearchProviders: () => [mockGrokProvider],
|
||||
resolveBundledWebSearchPluginId: (providerId: string | undefined) =>
|
||||
providerId === "grok" ? "xai" : undefined,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/web-search-providers.runtime.js", () => ({
|
||||
resolvePluginWebSearchProviders: () => [mockGrokProvider],
|
||||
}));
|
||||
|
||||
describe("runSearchSetupFlow", () => {
|
||||
it("runs provider-owned setup after selecting Grok web search", async () => {
|
||||
const select = vi
|
||||
|
|
|
|||
|
|
@ -11,11 +11,15 @@ import {
|
|||
writeConfigFile,
|
||||
} from "../config/config.js";
|
||||
import { withTempHome } from "../config/home-env.test-harness.js";
|
||||
import { clearPluginDiscoveryCache } from "../plugins/discovery.js";
|
||||
import { clearPluginLoaderCache } from "../plugins/loader.js";
|
||||
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
|
||||
import { __testing as webFetchProvidersTesting } from "../plugins/web-fetch-providers.runtime.js";
|
||||
import { __testing as webSearchProvidersTesting } from "../plugins/web-search-providers.runtime.js";
|
||||
import { captureEnv, withEnvAsync } from "../test-utils/env.js";
|
||||
import {
|
||||
activateSecretsRuntimeSnapshot,
|
||||
clearSecretsRuntimeSnapshot,
|
||||
getActiveRuntimeWebToolsMetadata,
|
||||
getActiveSecretsRuntimeSnapshot,
|
||||
prepareSecretsRuntimeSnapshot,
|
||||
} from "./runtime.js";
|
||||
|
|
@ -119,6 +123,7 @@ describe("secrets runtime snapshot integration", () => {
|
|||
beforeEach(() => {
|
||||
envSnapshot = captureEnv([
|
||||
"OPENCLAW_BUNDLED_PLUGINS_DIR",
|
||||
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
|
||||
"OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE",
|
||||
"OPENCLAW_VERSION",
|
||||
]);
|
||||
|
|
@ -133,6 +138,11 @@ describe("secrets runtime snapshot integration", () => {
|
|||
clearSecretsRuntimeSnapshot();
|
||||
clearRuntimeConfigSnapshot();
|
||||
clearConfigCache();
|
||||
clearPluginLoaderCache();
|
||||
clearPluginDiscoveryCache();
|
||||
clearPluginManifestRegistryCache();
|
||||
webSearchProvidersTesting.resetWebSearchProviderSnapshotCacheForTests();
|
||||
webFetchProvidersTesting.resetWebFetchProviderSnapshotCacheForTests();
|
||||
});
|
||||
|
||||
it("activates runtime snapshots for loadConfig and ensureAuthProfileStore", async () => {
|
||||
|
|
@ -346,107 +356,6 @@ describe("secrets runtime snapshot integration", () => {
|
|||
SECRETS_RUNTIME_INTEGRATION_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
google: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "WEB_SEARCH_GEMINI_API_KEY",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
env: {
|
||||
WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-runtime-key",
|
||||
},
|
||||
agentDirs: ["/tmp/openclaw-agent-main"],
|
||||
loadAuthStore: () => ({ version: 1, profiles: {} }),
|
||||
});
|
||||
|
||||
activateSecretsRuntimeSnapshot(prepared);
|
||||
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
/runtime snapshot refresh failed: .*WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK/i,
|
||||
);
|
||||
|
||||
const activeAfterFailure = getActiveSecretsRuntimeSnapshot();
|
||||
expect(activeAfterFailure).not.toBeNull();
|
||||
const loadedGoogleWebSearchConfig = loadConfig().plugins?.entries?.google?.config as
|
||||
| { webSearch?: { apiKey?: unknown } }
|
||||
| undefined;
|
||||
expect(loadedGoogleWebSearchConfig?.webSearch?.apiKey).toBe(
|
||||
"web-search-gemini-runtime-key",
|
||||
);
|
||||
const activeSourceGoogleWebSearchConfig = activeAfterFailure?.sourceConfig.plugins?.entries
|
||||
?.google?.config as { webSearch?: { apiKey?: unknown } } | undefined;
|
||||
expect(activeSourceGoogleWebSearchConfig?.webSearch?.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",
|
||||
});
|
||||
});
|
||||
},
|
||||
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) => {
|
||||
const mainAgentDir = path.join(home, ".openclaw", "agents", "main", "agent");
|
||||
|
|
|
|||
|
|
@ -1,24 +1,40 @@
|
|||
import { loadBundledPluginPublicSurfaceSync } from "./bundled-plugin-public-surface.js";
|
||||
import { createTestRegistry } from "./channel-plugins.js";
|
||||
|
||||
type SessionConversationSurface = {
|
||||
resolveSessionConversation?: (params: { kind: "group" | "channel"; rawId: string }) => {
|
||||
id: string;
|
||||
threadId?: string | null;
|
||||
baseConversationId?: string | null;
|
||||
parentConversationCandidates?: string[];
|
||||
} | null;
|
||||
};
|
||||
|
||||
function loadSessionConversationSurface(pluginId: string) {
|
||||
return loadBundledPluginPublicSurfaceSync<SessionConversationSurface>({
|
||||
pluginId,
|
||||
artifactBasename: "session-key-api.js",
|
||||
}).resolveSessionConversation;
|
||||
function resolveTelegramSessionConversation(params: { kind: "group" | "channel"; rawId: string }) {
|
||||
if (params.kind !== "group") {
|
||||
return null;
|
||||
}
|
||||
const match = params.rawId.match(/^(?<chatId>.+):topic:(?<topicId>[^:]+)$/u);
|
||||
if (!match?.groups?.chatId || !match.groups.topicId) {
|
||||
return null;
|
||||
}
|
||||
const chatId = match.groups.chatId;
|
||||
return {
|
||||
id: chatId,
|
||||
threadId: match.groups.topicId,
|
||||
baseConversationId: chatId,
|
||||
parentConversationCandidates: [chatId],
|
||||
};
|
||||
}
|
||||
|
||||
const resolveTelegramSessionConversation = loadSessionConversationSurface("telegram");
|
||||
const resolveFeishuSessionConversation = loadSessionConversationSurface("feishu");
|
||||
function resolveFeishuSessionConversation(params: { kind: "group" | "channel"; rawId: string }) {
|
||||
if (params.kind !== "group") {
|
||||
return null;
|
||||
}
|
||||
const senderMatch = params.rawId.match(
|
||||
/^(?<chatId>[^:]+):topic:(?<topicId>[^:]+):sender:(?<senderId>[^:]+)$/u,
|
||||
);
|
||||
if (!senderMatch?.groups?.chatId || !senderMatch.groups.topicId || !senderMatch.groups.senderId) {
|
||||
return null;
|
||||
}
|
||||
const chatId = senderMatch.groups.chatId;
|
||||
const topicId = senderMatch.groups.topicId;
|
||||
return {
|
||||
id: params.rawId,
|
||||
baseConversationId: chatId,
|
||||
parentConversationCandidates: [`${chatId}:topic:${topicId}`, chatId],
|
||||
};
|
||||
}
|
||||
|
||||
export function createSessionConversationTestRegistry() {
|
||||
return createTestRegistry([
|
||||
|
|
|
|||
Loading…
Reference in New Issue