mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 11:20:23 +00:00
test: stabilize ci test harnesses
This commit is contained in:
@@ -27,9 +27,23 @@ type SessionStoreEntry = {
|
||||
accountId?: string;
|
||||
};
|
||||
|
||||
type GatewayAgentInternalEvent = {
|
||||
status?: string;
|
||||
statusLabel?: string;
|
||||
result?: string;
|
||||
};
|
||||
|
||||
type GatewayAgentRequestParams = {
|
||||
sessionKey?: string;
|
||||
inputProvenance?: {
|
||||
sourceSessionKey?: string;
|
||||
};
|
||||
internalEvents?: GatewayAgentInternalEvent[];
|
||||
};
|
||||
|
||||
type GatewayRequest = {
|
||||
method?: string;
|
||||
params?: Record<string, unknown>;
|
||||
params?: GatewayAgentRequestParams;
|
||||
timeoutMs?: number;
|
||||
expectFinal?: boolean;
|
||||
};
|
||||
@@ -271,9 +285,7 @@ describe("subagent registry lifecycle error grace", () => {
|
||||
|
||||
function readFirstAnnounceOutcome() {
|
||||
const first = getAgentCalls()[0];
|
||||
const event = first?.params?.internalEvents?.[0] as
|
||||
| { status?: string; statusLabel?: string }
|
||||
| undefined;
|
||||
const event = first?.params?.internalEvents?.[0];
|
||||
return {
|
||||
status: event?.status,
|
||||
error: event?.statusLabel,
|
||||
@@ -298,10 +310,7 @@ describe("subagent registry lifecycle error grace", () => {
|
||||
function getAgentResultsForChildSession(childSessionKey: string): string[] {
|
||||
return getAgentCalls()
|
||||
.filter((request) => request.params?.inputProvenance?.sourceSessionKey === childSessionKey)
|
||||
.map((request) => {
|
||||
const event = request.params?.internalEvents?.[0] as { result?: string } | undefined;
|
||||
return event?.result ?? "";
|
||||
});
|
||||
.map((request) => request.params?.internalEvents?.[0]?.result ?? "");
|
||||
}
|
||||
|
||||
it("ignores transient lifecycle errors when run retries and then ends successfully", async () => {
|
||||
@@ -330,6 +339,7 @@ describe("subagent registry lifecycle error grace", () => {
|
||||
|
||||
await waitForAgentCallCount(1);
|
||||
expect(readFirstAnnounceOutcome()?.status).toBe("ok");
|
||||
await waitForCleanupCompleted("run-transient-error");
|
||||
});
|
||||
|
||||
it("announces error when lifecycle error remains terminal after grace window", async () => {
|
||||
@@ -350,6 +360,7 @@ describe("subagent registry lifecycle error grace", () => {
|
||||
await waitForAgentCallCount(1);
|
||||
expect(readFirstAnnounceOutcome()?.status).toBe("error");
|
||||
expect(readFirstAnnounceOutcome()?.error).toContain("fatal failure");
|
||||
await waitForCleanupCompleted("run-terminal-error");
|
||||
});
|
||||
|
||||
it("freezes completion result at run termination across deferred announce retries", async () => {
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js";
|
||||
import { mockPublicPinnedHostname } from "./test-helpers/ssrf.js";
|
||||
|
||||
vi.mock("../agents/model-auth.js", async () => {
|
||||
const { createModelAuthMockModule } = await import("../test-utils/model-auth-mock.js");
|
||||
return createModelAuthMockModule();
|
||||
});
|
||||
|
||||
const importNodeLlamaCppMock = vi.fn();
|
||||
vi.mock("./node-llama.js", () => ({
|
||||
importNodeLlamaCpp: (...args: unknown[]) => importNodeLlamaCppMock(...args),
|
||||
}));
|
||||
|
||||
const createFetchMock = () =>
|
||||
vi.fn(async (_input?: unknown, _init?: unknown) => ({
|
||||
ok: true,
|
||||
@@ -37,11 +27,16 @@ type AuthModule = typeof import("../agents/model-auth.js");
|
||||
type ResolvedProviderAuth = Awaited<ReturnType<AuthModule["resolveApiKeyForProvider"]>>;
|
||||
|
||||
let authModule: AuthModule;
|
||||
let nodeLlamaModule: typeof import("./node-llama.js");
|
||||
let createEmbeddingProvider: EmbeddingsModule["createEmbeddingProvider"];
|
||||
let DEFAULT_LOCAL_MODEL: EmbeddingsModule["DEFAULT_LOCAL_MODEL"];
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
authModule = await import("../agents/model-auth.js");
|
||||
nodeLlamaModule = await import("./node-llama.js");
|
||||
vi.spyOn(authModule, "resolveApiKeyForProvider");
|
||||
vi.spyOn(nodeLlamaModule, "importNodeLlamaCpp");
|
||||
({ createEmbeddingProvider, DEFAULT_LOCAL_MODEL } = await import("./embeddings.js"));
|
||||
});
|
||||
|
||||
@@ -66,7 +61,7 @@ function mockResolvedProviderKey(apiKey = "provider-key") {
|
||||
}
|
||||
|
||||
function mockMissingLocalEmbeddingDependency() {
|
||||
importNodeLlamaCppMock.mockRejectedValue(
|
||||
vi.mocked(nodeLlamaModule.importNodeLlamaCpp).mockRejectedValue(
|
||||
Object.assign(new Error("Cannot find package 'node-llama-cpp'"), {
|
||||
code: "ERR_MODULE_NOT_FOUND",
|
||||
}),
|
||||
@@ -456,7 +451,7 @@ describe("local embedding normalization", () => {
|
||||
resolveModelFile: (modelPath: string, modelDirectory?: string) => Promise<string> = async () =>
|
||||
"/fake/model.gguf",
|
||||
): void {
|
||||
importNodeLlamaCppMock.mockResolvedValue({
|
||||
vi.mocked(nodeLlamaModule.importNodeLlamaCpp).mockResolvedValue({
|
||||
getLlama: async () => ({
|
||||
loadModel: vi.fn().mockResolvedValue({
|
||||
createEmbeddingContext: vi.fn().mockResolvedValue({
|
||||
@@ -468,7 +463,7 @@ describe("local embedding normalization", () => {
|
||||
}),
|
||||
resolveModelFile,
|
||||
LlamaLogLevel: { error: 0 },
|
||||
});
|
||||
} as never);
|
||||
}
|
||||
|
||||
it("normalizes local embeddings to magnitude ~1.0", async () => {
|
||||
@@ -523,7 +518,7 @@ describe("local embedding normalization", () => {
|
||||
[1.0, 1.0, 1.0, 1.0],
|
||||
];
|
||||
|
||||
importNodeLlamaCppMock.mockResolvedValue({
|
||||
vi.mocked(nodeLlamaModule.importNodeLlamaCpp).mockResolvedValue({
|
||||
getLlama: async () => ({
|
||||
loadModel: vi.fn().mockResolvedValue({
|
||||
createEmbeddingContext: vi.fn().mockResolvedValue({
|
||||
@@ -537,7 +532,7 @@ describe("local embedding normalization", () => {
|
||||
}),
|
||||
resolveModelFile: async () => "/fake/model.gguf",
|
||||
LlamaLogLevel: { error: 0 },
|
||||
});
|
||||
} as never);
|
||||
|
||||
const result = await createLocalProviderForTest();
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createEmptyPluginRegistry } from "./registry.js";
|
||||
import { setActivePluginRegistry } from "./runtime.js";
|
||||
import {
|
||||
resolvePluginWebSearchProviders,
|
||||
resolveRuntimeWebSearchProviders,
|
||||
} from "./web-search-providers.runtime.js";
|
||||
|
||||
type RegistryModule = typeof import("./registry.js");
|
||||
type RuntimeModule = typeof import("./runtime.js");
|
||||
type WebSearchProvidersRuntimeModule = typeof import("./web-search-providers.runtime.js");
|
||||
|
||||
const BUNDLED_WEB_SEARCH_PROVIDERS = [
|
||||
{ pluginId: "brave", id: "brave", order: 10 },
|
||||
@@ -15,69 +13,85 @@ const BUNDLED_WEB_SEARCH_PROVIDERS = [
|
||||
{ pluginId: "firecrawl", id: "firecrawl", order: 60 },
|
||||
{ pluginId: "exa", id: "exa", order: 65 },
|
||||
{ pluginId: "tavily", id: "tavily", order: 70 },
|
||||
{ pluginId: "duckduckgo", id: "duckduckgo", order: 100 },
|
||||
] as const;
|
||||
|
||||
const { loadOpenClawPluginsMock } = vi.hoisted(() => ({
|
||||
loadOpenClawPluginsMock: vi.fn((params?: { config?: { plugins?: Record<string, unknown> } }) => {
|
||||
const plugins = params?.config?.plugins as
|
||||
| {
|
||||
enabled?: boolean;
|
||||
allow?: string[];
|
||||
entries?: Record<string, { enabled?: boolean }>;
|
||||
}
|
||||
| undefined;
|
||||
if (plugins?.enabled === false) {
|
||||
return { webSearchProviders: [] };
|
||||
}
|
||||
const allow = Array.isArray(plugins?.allow) && plugins.allow.length > 0 ? plugins.allow : null;
|
||||
const entries = plugins?.entries ?? {};
|
||||
const webSearchProviders = BUNDLED_WEB_SEARCH_PROVIDERS.filter((provider) => {
|
||||
if (allow && !allow.includes(provider.pluginId)) {
|
||||
return false;
|
||||
}
|
||||
if (entries[provider.pluginId]?.enabled === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map((provider) => ({
|
||||
pluginId: provider.pluginId,
|
||||
pluginName: provider.pluginId,
|
||||
source: "test" as const,
|
||||
provider: {
|
||||
id: provider.id,
|
||||
label: provider.id,
|
||||
hint: `${provider.id} provider`,
|
||||
envVars: [`${provider.id.toUpperCase()}_API_KEY`],
|
||||
placeholder: `${provider.id}-...`,
|
||||
signupUrl: `https://example.com/${provider.id}`,
|
||||
autoDetectOrder: provider.order,
|
||||
credentialPath: `plugins.entries.${provider.pluginId}.config.webSearch.apiKey`,
|
||||
getCredentialValue: () => "configured",
|
||||
setCredentialValue: () => {},
|
||||
createTool: () => ({
|
||||
description: provider.id,
|
||||
parameters: {},
|
||||
execute: async () => ({}),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
return { webSearchProviders };
|
||||
}),
|
||||
}));
|
||||
let createEmptyPluginRegistry: RegistryModule["createEmptyPluginRegistry"];
|
||||
let setActivePluginRegistry: RuntimeModule["setActivePluginRegistry"];
|
||||
let resolvePluginWebSearchProviders: WebSearchProvidersRuntimeModule["resolvePluginWebSearchProviders"];
|
||||
let resolveRuntimeWebSearchProviders: WebSearchProvidersRuntimeModule["resolveRuntimeWebSearchProviders"];
|
||||
let loadOpenClawPluginsMock: ReturnType<typeof vi.fn>;
|
||||
|
||||
vi.mock("./loader.js", () => ({
|
||||
loadOpenClawPlugins: loadOpenClawPluginsMock,
|
||||
}));
|
||||
function buildMockedWebSearchProviders(params?: {
|
||||
config?: { plugins?: Record<string, unknown> };
|
||||
}) {
|
||||
const plugins = params?.config?.plugins as
|
||||
| {
|
||||
enabled?: boolean;
|
||||
allow?: string[];
|
||||
entries?: Record<string, { enabled?: boolean }>;
|
||||
}
|
||||
| undefined;
|
||||
if (plugins?.enabled === false) {
|
||||
return [];
|
||||
}
|
||||
const allow = Array.isArray(plugins?.allow) && plugins.allow.length > 0 ? plugins.allow : null;
|
||||
const entries = plugins?.entries ?? {};
|
||||
const webSearchProviders = BUNDLED_WEB_SEARCH_PROVIDERS.filter((provider) => {
|
||||
if (allow && !allow.includes(provider.pluginId)) {
|
||||
return false;
|
||||
}
|
||||
if (entries[provider.pluginId]?.enabled === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map((provider) => ({
|
||||
pluginId: provider.pluginId,
|
||||
pluginName: provider.pluginId,
|
||||
source: "test" as const,
|
||||
provider: {
|
||||
id: provider.id,
|
||||
label: provider.id,
|
||||
hint: `${provider.id} provider`,
|
||||
envVars: [`${provider.id.toUpperCase()}_API_KEY`],
|
||||
placeholder: `${provider.id}-...`,
|
||||
signupUrl: `https://example.com/${provider.id}`,
|
||||
autoDetectOrder: provider.order,
|
||||
credentialPath: `plugins.entries.${provider.pluginId}.config.webSearch.apiKey`,
|
||||
getCredentialValue: () => "configured",
|
||||
setCredentialValue: () => {},
|
||||
createTool: () => ({
|
||||
description: provider.id,
|
||||
parameters: {},
|
||||
execute: async () => ({}),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
return webSearchProviders;
|
||||
}
|
||||
|
||||
describe("resolvePluginWebSearchProviders", () => {
|
||||
beforeEach(() => {
|
||||
loadOpenClawPluginsMock.mockClear();
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
({ createEmptyPluginRegistry } = await import("./registry.js"));
|
||||
const loaderModule = await import("./loader.js");
|
||||
loadOpenClawPluginsMock = vi
|
||||
.spyOn(loaderModule, "loadOpenClawPlugins")
|
||||
.mockImplementation((params) => {
|
||||
const registry = createEmptyPluginRegistry();
|
||||
registry.webSearchProviders = buildMockedWebSearchProviders(params);
|
||||
return registry;
|
||||
});
|
||||
({ setActivePluginRegistry } = await import("./runtime.js"));
|
||||
({ resolvePluginWebSearchProviders, resolveRuntimeWebSearchProviders } =
|
||||
await import("./web-search-providers.runtime.js"));
|
||||
setActivePluginRegistry(createEmptyPluginRegistry());
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createEmptyPluginRegistry());
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("loads bundled providers through the plugin loader in auto-detect order", () => {
|
||||
@@ -92,6 +106,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
"firecrawl:firecrawl",
|
||||
"exa:exa",
|
||||
"tavily:tavily",
|
||||
"duckduckgo:duckduckgo",
|
||||
]);
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user