Files
openclaw/test/setup.ts

168 lines
5.2 KiB
TypeScript
Raw Normal View History

import { afterAll, afterEach, beforeEach, vi } from "vitest";
feat: Add Ollama provider with automatic model discovery (#1606) * feat: Add Ollama provider with automatic model discovery - Add Ollama provider builder with automatic model detection - Discover available models from local Ollama instance via /api/tags API - Make resolveImplicitProviders async to support dynamic model discovery - Add comprehensive Ollama documentation with setup and usage guide - Add tests for Ollama provider integration - Update provider index and model providers documentation Closes #1531 * fix: Correct Ollama provider type definitions and error handling - Fix input property type to match ModelDefinitionConfig - Import ModelDefinitionConfig type properly - Fix error template literal to use String() for type safety - Simplify return type signature of discoverOllamaModels * fix: Suppress unhandled promise warnings from ensureClawdbotModelsJson in tests - Cast unused promise returns to 'unknown' to suppress TypeScript warnings - Tests that don't await the promise are intentionally not awaiting it - This fixes the failing test suite caused by unawaited async calls * fix: Skip Ollama model discovery during tests - Check for VITEST or NODE_ENV=test before making HTTP requests - Prevents test timeouts and hangs from network calls - Ollama discovery will still work in production/normal usage * fix: Set VITEST environment variable in test setup - Ensures Ollama discovery is skipped in all test runs - Prevents network calls during tests that could cause timeouts * test: Temporarily skip Ollama provider tests to diagnose CI failures * fix: Make Ollama provider opt-in to avoid breaking existing tests **Root Cause:** The Ollama provider was being added to ALL configurations by default (with a fallback API key of 'ollama-local'), which broke tests that expected NO providers when no API keys were configured. **Solution:** - Removed the default fallback API key for Ollama - Ollama provider now requires explicit configuration via: - OLLAMA_API_KEY environment variable, OR - Ollama profile in auth store - Updated documentation to reflect the explicit configuration requirement - Added a test to verify Ollama is not added by default This fixes all 4 failing test suites: - checks (node, test, pnpm test) - checks (bun, test, bunx vitest run) - checks-windows (node, test, pnpm test) - checks-macos (test, pnpm test) Closes #1531
2026-01-24 22:38:52 +00:00
// Ensure Vitest environment is properly set
process.env.VITEST = "true";
2026-01-18 08:58:41 +00:00
import type {
ChannelId,
ChannelOutboundAdapter,
ChannelPlugin,
} from "../src/channels/plugins/types.js";
2026-01-30 03:15:10 +01:00
import type { OpenClawConfig } from "../src/config/config.js";
import type { OutboundSendDeps } from "../src/infra/outbound/deliver.js";
import { installProcessWarningFilter } from "../src/infra/warning-filter.js";
import { setActivePluginRegistry } from "../src/plugins/runtime.js";
import { createTestRegistry } from "../src/test-utils/channel-plugins.js";
import { withIsolatedTestHome } from "./test-env.js";
2026-01-24 10:48:33 +00:00
installProcessWarningFilter();
const testEnv = withIsolatedTestHome();
afterAll(() => testEnv.cleanup());
const pickSendFn = (id: ChannelId, deps?: OutboundSendDeps) => {
switch (id) {
case "discord":
return deps?.sendDiscord;
case "slack":
return deps?.sendSlack;
case "telegram":
return deps?.sendTelegram;
case "whatsapp":
return deps?.sendWhatsApp;
case "signal":
return deps?.sendSignal;
case "imessage":
return deps?.sendIMessage;
default:
return undefined;
}
};
const createStubOutbound = (
id: ChannelId,
deliveryMode: ChannelOutboundAdapter["deliveryMode"] = "direct",
): ChannelOutboundAdapter => ({
deliveryMode,
sendText: async ({ deps, to, text }) => {
const send = pickSendFn(id, deps);
if (send) {
// oxlint-disable-next-line typescript/no-explicit-any
const result = await send(to, text, { verbose: false } as any);
return { channel: id, ...result };
}
return { channel: id, messageId: "test" };
},
sendMedia: async ({ deps, to, text, mediaUrl }) => {
const send = pickSendFn(id, deps);
if (send) {
// oxlint-disable-next-line typescript/no-explicit-any
const result = await send(to, text, { verbose: false, mediaUrl } as any);
return { channel: id, ...result };
}
return { channel: id, messageId: "test" };
},
});
const createStubPlugin = (params: {
id: ChannelId;
label?: string;
aliases?: string[];
deliveryMode?: ChannelOutboundAdapter["deliveryMode"];
preferSessionLookupForAnnounceTarget?: boolean;
}): ChannelPlugin => ({
id: params.id,
meta: {
id: params.id,
label: params.label ?? String(params.id),
selectionLabel: params.label ?? String(params.id),
docsPath: `/channels/${params.id}`,
blurb: "test stub.",
aliases: params.aliases,
preferSessionLookupForAnnounceTarget: params.preferSessionLookupForAnnounceTarget,
},
capabilities: { chatTypes: ["direct", "group"] },
config: {
2026-01-30 03:15:10 +01:00
listAccountIds: (cfg: OpenClawConfig) => {
const channels = cfg.channels as Record<string, unknown> | undefined;
const entry = channels?.[params.id];
if (!entry || typeof entry !== "object") {
return [];
}
const accounts = (entry as { accounts?: Record<string, unknown> }).accounts;
const ids = accounts ? Object.keys(accounts).filter(Boolean) : [];
return ids.length > 0 ? ids : ["default"];
},
resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) => {
const channels = cfg.channels as Record<string, unknown> | undefined;
const entry = channels?.[params.id];
if (!entry || typeof entry !== "object") {
return {};
}
const accounts = (entry as { accounts?: Record<string, unknown> }).accounts;
const match = accountId ? accounts?.[accountId] : undefined;
return (match && typeof match === "object") || typeof match === "string" ? match : entry;
},
2026-01-30 03:15:10 +01:00
isConfigured: async (_account, cfg: OpenClawConfig) => {
const channels = cfg.channels as Record<string, unknown> | undefined;
return Boolean(channels?.[params.id]);
},
},
outbound: createStubOutbound(params.id, params.deliveryMode),
});
const createDefaultRegistry = () =>
createTestRegistry([
2026-01-18 08:58:41 +00:00
{
pluginId: "discord",
plugin: createStubPlugin({ id: "discord", label: "Discord" }),
source: "test",
},
{
pluginId: "slack",
plugin: createStubPlugin({ id: "slack", label: "Slack" }),
source: "test",
},
{
pluginId: "telegram",
plugin: {
...createStubPlugin({ id: "telegram", label: "Telegram" }),
status: {
buildChannelSummary: async () => ({
configured: false,
tokenSource: process.env.TELEGRAM_BOT_TOKEN ? "env" : "none",
}),
},
},
source: "test",
},
{
pluginId: "whatsapp",
plugin: createStubPlugin({
id: "whatsapp",
label: "WhatsApp",
deliveryMode: "gateway",
preferSessionLookupForAnnounceTarget: true,
}),
source: "test",
},
2026-01-18 08:58:41 +00:00
{
pluginId: "signal",
plugin: createStubPlugin({ id: "signal", label: "Signal" }),
source: "test",
},
{
pluginId: "imessage",
plugin: createStubPlugin({ id: "imessage", label: "iMessage", aliases: ["imsg"] }),
source: "test",
},
]);
beforeEach(() => {
setActivePluginRegistry(createDefaultRegistry());
});
afterEach(() => {
// Guard against leaked fake timers across test files/workers.
vi.useRealTimers();
});