fix(types): annotate portable exported helper types

This commit is contained in:
Vincent Koc
2026-04-04 02:44:18 +09:00
parent 4b71a94450
commit 88d3b73c6d
28 changed files with 840 additions and 284 deletions

View File

@@ -1,6 +1,6 @@
import { EventEmitter } from "node:events";
import type { IncomingMessage, ServerResponse } from "node:http";
import { expect, vi } from "vitest";
import { expect, vi, type Mock } from "vitest";
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
import { handleBlueBubblesWebhookRequest } from "./monitor.js";
import { registerBlueBubblesWebhookTarget } from "./monitor.js";
@@ -16,6 +16,11 @@ export type WebhookRequestParams = {
};
export const LOOPBACK_REMOTE_ADDRESSES_FOR_TEST = ["127.0.0.1", "::1", "::ffff:127.0.0.1"] as const;
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type HangingWebhookRequestForTest = {
req: IncomingMessage;
destroyMock: UnknownMock;
};
export function createMockAccount(
overrides: Partial<ResolvedBlueBubblesAccount["config"]> = {},
@@ -182,7 +187,7 @@ export function createLoopbackWebhookRequestParamsForTest(
export function createHangingWebhookRequestForTest(
url = "/bluebubbles-webhook?password=test-password",
remoteAddress = "127.0.0.1",
) {
): HangingWebhookRequestForTest {
const req = new EventEmitter() as IncomingMessage;
const destroyMock = vi.fn();
req.method = "POST";

View File

@@ -1,3 +1,4 @@
import type { Command } from "commander";
import { vi } from "vitest";
import * as parentCoreApiModule from "../core-api.js";
import * as browserCliSharedModule from "./browser-cli-shared.js";
@@ -56,7 +57,7 @@ vi.spyOn(cliCoreApiModule.defaultRuntime, "exit").mockImplementation(browserCliR
const { registerBrowserManageCommands } = await import("./browser-cli-manage.js");
export function createBrowserManageProgram(params?: { withParentTimeout?: boolean }) {
export function createBrowserManageProgram(params?: { withParentTimeout?: boolean }): Command {
const { program, browser, parentOpts } = createBrowserProgram();
if (params?.withParentTimeout) {
browser.option("--timeout <ms>", "Timeout in ms", "30000");

View File

@@ -1,6 +1,28 @@
import { expect, vi } from "vitest";
import { expect, vi, type Mock } from "vitest";
export function createDiscordOutboundHoisted() {
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
type DiscordOutboundHoisted = {
sendMessageDiscordMock: AsyncUnknownMock;
sendDiscordComponentMessageMock: AsyncUnknownMock;
sendPollDiscordMock: AsyncUnknownMock;
sendWebhookMessageDiscordMock: AsyncUnknownMock;
getThreadBindingManagerMock: UnknownMock;
};
type DiscordSendModule = typeof import("./send.js");
type DiscordSendComponentsModule = typeof import("./send.components.js");
type DiscordThreadBindingsModule = typeof import("./monitor/thread-bindings.js");
function invokeMock<TArgs extends unknown[], TResult>(
mock: (...args: unknown[]) => unknown,
...args: TArgs
): TResult {
return mock(...args) as TResult;
}
export function createDiscordOutboundHoisted(): DiscordOutboundHoisted {
const sendMessageDiscordMock = vi.fn();
const sendDiscordComponentMessageMock = vi.fn();
const sendPollDiscordMock = vi.fn();
@@ -21,28 +43,94 @@ export const DEFAULT_DISCORD_SEND_RESULT = {
channelId: "ch-1",
} as const;
type DiscordOutboundHoisted = ReturnType<typeof createDiscordOutboundHoisted>;
export async function createDiscordSendModuleMock(
hoisted: DiscordOutboundHoisted,
importOriginal: () => Promise<DiscordSendModule>,
): Promise<DiscordSendModule> {
const actual = await importOriginal();
return {
...actual,
sendMessageDiscord: (...args: Parameters<DiscordSendModule["sendMessageDiscord"]>) =>
invokeMock<
Parameters<DiscordSendModule["sendMessageDiscord"]>,
ReturnType<DiscordSendModule["sendMessageDiscord"]>
>(hoisted.sendMessageDiscordMock, ...args),
sendPollDiscord: (...args: Parameters<DiscordSendModule["sendPollDiscord"]>) =>
invokeMock<
Parameters<DiscordSendModule["sendPollDiscord"]>,
ReturnType<DiscordSendModule["sendPollDiscord"]>
>(hoisted.sendPollDiscordMock, ...args),
sendWebhookMessageDiscord: (
...args: Parameters<DiscordSendModule["sendWebhookMessageDiscord"]>
) =>
invokeMock<
Parameters<DiscordSendModule["sendWebhookMessageDiscord"]>,
ReturnType<DiscordSendModule["sendWebhookMessageDiscord"]>
>(hoisted.sendWebhookMessageDiscordMock, ...args),
};
}
export async function createDiscordSendComponentsModuleMock(
hoisted: DiscordOutboundHoisted,
importOriginal: () => Promise<DiscordSendComponentsModule>,
): Promise<DiscordSendComponentsModule> {
const actual = await importOriginal();
return {
...actual,
sendDiscordComponentMessage: (
...args: Parameters<DiscordSendComponentsModule["sendDiscordComponentMessage"]>
) =>
invokeMock<
Parameters<DiscordSendComponentsModule["sendDiscordComponentMessage"]>,
ReturnType<DiscordSendComponentsModule["sendDiscordComponentMessage"]>
>(hoisted.sendDiscordComponentMessageMock, ...args),
};
}
export async function createDiscordThreadBindingsModuleMock(
hoisted: DiscordOutboundHoisted,
importOriginal: () => Promise<DiscordThreadBindingsModule>,
): Promise<DiscordThreadBindingsModule> {
const actual = await importOriginal();
return {
...actual,
getThreadBindingManager: (
...args: Parameters<DiscordThreadBindingsModule["getThreadBindingManager"]>
) =>
invokeMock<
Parameters<DiscordThreadBindingsModule["getThreadBindingManager"]>,
ReturnType<DiscordThreadBindingsModule["getThreadBindingManager"]>
>(hoisted.getThreadBindingManagerMock, ...args),
};
}
export async function installDiscordOutboundModuleSpies(hoisted: DiscordOutboundHoisted) {
const sendModule = await import("./send.js");
vi.spyOn(sendModule, "sendMessageDiscord").mockImplementation((...args: unknown[]) =>
hoisted.sendMessageDiscordMock(...args),
const mockedSendModule = await createDiscordSendModuleMock(hoisted, async () => sendModule);
vi.spyOn(sendModule, "sendMessageDiscord").mockImplementation(
mockedSendModule.sendMessageDiscord,
);
vi.spyOn(sendModule, "sendPollDiscord").mockImplementation((...args: unknown[]) =>
hoisted.sendPollDiscordMock(...args),
);
vi.spyOn(sendModule, "sendWebhookMessageDiscord").mockImplementation((...args: unknown[]) =>
hoisted.sendWebhookMessageDiscordMock(...args),
vi.spyOn(sendModule, "sendPollDiscord").mockImplementation(mockedSendModule.sendPollDiscord);
vi.spyOn(sendModule, "sendWebhookMessageDiscord").mockImplementation(
mockedSendModule.sendWebhookMessageDiscord,
);
const sendComponentsModule = await import("./send.components.js");
const mockedSendComponentsModule = await createDiscordSendComponentsModuleMock(
hoisted,
async () => sendComponentsModule,
);
vi.spyOn(sendComponentsModule, "sendDiscordComponentMessage").mockImplementation(
(...args: unknown[]) => hoisted.sendDiscordComponentMessageMock(...args),
mockedSendComponentsModule.sendDiscordComponentMessage,
);
const threadBindingsModule = await import("./monitor/thread-bindings.js");
const mockedThreadBindingsModule = await createDiscordThreadBindingsModuleMock(
hoisted,
async () => threadBindingsModule,
);
vi.spyOn(threadBindingsModule, "getThreadBindingManager").mockImplementation(
(...args: unknown[]) => hoisted.getThreadBindingManagerMock(...args),
mockedThreadBindingsModule.getThreadBindingManager,
);
}

View File

@@ -1,32 +1,51 @@
import { vi } from "vitest";
import { vi, type Mock } from "vitest";
import { parsePluginBindingApprovalCustomId } from "../../../../src/plugins/conversation-binding.js";
import { resolvePinnedMainDmOwnerFromAllowlist } from "../../../../src/security/dm-policy-shared.js";
const runtimeMocks = vi.hoisted(() => ({
buildPluginBindingResolvedTextMock: vi.fn(),
dispatchPluginInteractiveHandlerMock: vi.fn(),
dispatchReplyMock: vi.fn(),
enqueueSystemEventMock: vi.fn(),
readAllowFromStoreMock: vi.fn(),
readSessionUpdatedAtMock: vi.fn(),
recordInboundSessionMock: vi.fn(),
resolveStorePathMock: vi.fn(),
resolvePluginConversationBindingApprovalMock: vi.fn(),
upsertPairingRequestMock: vi.fn(),
}));
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
export const readAllowFromStoreMock = runtimeMocks.readAllowFromStoreMock;
export const dispatchPluginInteractiveHandlerMock =
type DiscordComponentRuntimeMocks = {
buildPluginBindingResolvedTextMock: UnknownMock;
dispatchPluginInteractiveHandlerMock: AsyncUnknownMock;
dispatchReplyMock: UnknownMock;
enqueueSystemEventMock: UnknownMock;
readAllowFromStoreMock: AsyncUnknownMock;
readSessionUpdatedAtMock: UnknownMock;
recordInboundSessionMock: AsyncUnknownMock;
resolveStorePathMock: UnknownMock;
resolvePluginConversationBindingApprovalMock: AsyncUnknownMock;
upsertPairingRequestMock: AsyncUnknownMock;
};
const runtimeMocks = vi.hoisted(
(): DiscordComponentRuntimeMocks => ({
buildPluginBindingResolvedTextMock: vi.fn(),
dispatchPluginInteractiveHandlerMock: vi.fn(),
dispatchReplyMock: vi.fn(),
enqueueSystemEventMock: vi.fn(),
readAllowFromStoreMock: vi.fn(),
readSessionUpdatedAtMock: vi.fn(),
recordInboundSessionMock: vi.fn(),
resolveStorePathMock: vi.fn(),
resolvePluginConversationBindingApprovalMock: vi.fn(),
upsertPairingRequestMock: vi.fn(),
}),
);
export const readAllowFromStoreMock: AsyncUnknownMock = runtimeMocks.readAllowFromStoreMock;
export const dispatchPluginInteractiveHandlerMock: AsyncUnknownMock =
runtimeMocks.dispatchPluginInteractiveHandlerMock;
export const dispatchReplyMock = runtimeMocks.dispatchReplyMock;
export const enqueueSystemEventMock = runtimeMocks.enqueueSystemEventMock;
export const upsertPairingRequestMock = runtimeMocks.upsertPairingRequestMock;
export const recordInboundSessionMock = runtimeMocks.recordInboundSessionMock;
export const readSessionUpdatedAtMock = runtimeMocks.readSessionUpdatedAtMock;
export const resolveStorePathMock = runtimeMocks.resolveStorePathMock;
export const resolvePluginConversationBindingApprovalMock =
export const dispatchReplyMock: UnknownMock = runtimeMocks.dispatchReplyMock;
export const enqueueSystemEventMock: UnknownMock = runtimeMocks.enqueueSystemEventMock;
export const upsertPairingRequestMock: AsyncUnknownMock = runtimeMocks.upsertPairingRequestMock;
export const recordInboundSessionMock: AsyncUnknownMock = runtimeMocks.recordInboundSessionMock;
export const readSessionUpdatedAtMock: UnknownMock = runtimeMocks.readSessionUpdatedAtMock;
export const resolveStorePathMock: UnknownMock = runtimeMocks.resolveStorePathMock;
export const resolvePluginConversationBindingApprovalMock: AsyncUnknownMock =
runtimeMocks.resolvePluginConversationBindingApprovalMock;
export const buildPluginBindingResolvedTextMock = runtimeMocks.buildPluginBindingResolvedTextMock;
export const buildPluginBindingResolvedTextMock: UnknownMock =
runtimeMocks.buildPluginBindingResolvedTextMock;
async function readStoreAllowFromForDmPolicy(params: {
provider: string;

View File

@@ -1,31 +1,77 @@
import { vi } from "vitest";
import { vi, type Mock } from "vitest";
type BoundConversation = {
bindingId: string;
targetSessionKey: string;
};
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
type FinalizeInboundContextMock = Mock<
(ctx: Record<string, unknown>, opts?: unknown) => Record<string, unknown>
>;
type DispatchReplyCounts = {
final: number;
block?: number;
tool?: number;
};
type DispatchReplyContext = Record<string, unknown> & {
SessionKey?: string;
};
type DispatchReplyDispatcher = {
sendFinalReply: (payload: { text: string }) => unknown | Promise<unknown>;
};
type DispatchReplyFromConfigMock = Mock<
(params: {
ctx: DispatchReplyContext;
dispatcher: DispatchReplyDispatcher;
}) => Promise<{ queuedFinal: boolean; counts: DispatchReplyCounts }>
>;
type WithReplyDispatcherMock = Mock<
(params: { run: () => unknown | Promise<unknown> }) => Promise<unknown>
>;
type FeishuLifecycleTestMocks = {
createEventDispatcherMock: UnknownMock;
monitorWebSocketMock: AsyncUnknownMock;
monitorWebhookMock: AsyncUnknownMock;
createFeishuThreadBindingManagerMock: UnknownMock;
createFeishuReplyDispatcherMock: UnknownMock;
resolveBoundConversationMock: Mock<() => BoundConversation | null>;
touchBindingMock: UnknownMock;
resolveAgentRouteMock: UnknownMock;
resolveConfiguredBindingRouteMock: UnknownMock;
ensureConfiguredBindingRouteReadyMock: UnknownMock;
dispatchReplyFromConfigMock: DispatchReplyFromConfigMock;
withReplyDispatcherMock: WithReplyDispatcherMock;
finalizeInboundContextMock: FinalizeInboundContextMock;
getMessageFeishuMock: AsyncUnknownMock;
listFeishuThreadMessagesMock: AsyncUnknownMock;
sendMessageFeishuMock: AsyncUnknownMock;
sendCardFeishuMock: AsyncUnknownMock;
};
const feishuLifecycleTestMocks = vi.hoisted(() => ({
createEventDispatcherMock: vi.fn(),
monitorWebSocketMock: vi.fn(async () => {}),
monitorWebhookMock: vi.fn(async () => {}),
createFeishuThreadBindingManagerMock: vi.fn(() => ({ stop: vi.fn() })),
createFeishuReplyDispatcherMock: vi.fn(),
resolveBoundConversationMock: vi.fn<() => BoundConversation | null>(() => null),
touchBindingMock: vi.fn(),
resolveAgentRouteMock: vi.fn(),
resolveConfiguredBindingRouteMock: vi.fn(),
ensureConfiguredBindingRouteReadyMock: vi.fn(),
dispatchReplyFromConfigMock: vi.fn(),
withReplyDispatcherMock: vi.fn(),
finalizeInboundContextMock: vi.fn((ctx) => ctx),
getMessageFeishuMock: vi.fn(async () => null),
listFeishuThreadMessagesMock: vi.fn(async () => []),
sendMessageFeishuMock: vi.fn(async () => ({ messageId: "om_sent", chatId: "chat_default" })),
sendCardFeishuMock: vi.fn(async () => ({ messageId: "om_card", chatId: "chat_default" })),
}));
const feishuLifecycleTestMocks = vi.hoisted(
(): FeishuLifecycleTestMocks => ({
createEventDispatcherMock: vi.fn(),
monitorWebSocketMock: vi.fn(async () => {}),
monitorWebhookMock: vi.fn(async () => {}),
createFeishuThreadBindingManagerMock: vi.fn(() => ({ stop: vi.fn() })),
createFeishuReplyDispatcherMock: vi.fn(),
resolveBoundConversationMock: vi.fn<() => BoundConversation | null>(() => null),
touchBindingMock: vi.fn(),
resolveAgentRouteMock: vi.fn(),
resolveConfiguredBindingRouteMock: vi.fn(),
ensureConfiguredBindingRouteReadyMock: vi.fn(),
dispatchReplyFromConfigMock: vi.fn(),
withReplyDispatcherMock: vi.fn(),
finalizeInboundContextMock: vi.fn((ctx) => ctx),
getMessageFeishuMock: vi.fn(async () => null),
listFeishuThreadMessagesMock: vi.fn(async () => []),
sendMessageFeishuMock: vi.fn(async () => ({ messageId: "om_sent", chatId: "chat_default" })),
sendCardFeishuMock: vi.fn(async () => ({ messageId: "om_card", chatId: "chat_default" })),
}),
);
export function getFeishuLifecycleTestMocks() {
export function getFeishuLifecycleTestMocks(): FeishuLifecycleTestMocks {
return feishuLifecycleTestMocks;
}

View File

@@ -1,5 +1,5 @@
import { randomUUID } from "node:crypto";
import { expect, vi } from "vitest";
import { expect, vi, type Mock } from "vitest";
import { createPluginRuntimeMock } from "../../../../test/helpers/plugins/plugin-runtime-mock.js";
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../../runtime-api.js";
import { setFeishuRuntime } from "../runtime.js";
@@ -9,6 +9,37 @@ type InboundDebouncerParams<T> = {
onFlush?: (items: T[]) => Promise<void>;
onError?: (err: unknown, items: T[]) => void;
};
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
type FeishuDispatchReplyCounts = {
final: number;
block?: number;
tool?: number;
};
type FeishuDispatchReplyContext = Record<string, unknown> & {
SessionKey?: string;
};
type FeishuDispatchReplyDispatcher = {
sendFinalReply: (payload: { text: string }) => unknown | Promise<unknown>;
};
type FeishuDispatchReplyMock = Mock<
(args: {
ctx: FeishuDispatchReplyContext;
dispatcher: FeishuDispatchReplyDispatcher;
}) => Promise<{ queuedFinal: boolean; counts: FeishuDispatchReplyCounts }>
>;
type FeishuLifecycleReplyDispatcher = {
dispatcher: {
sendToolResult: UnknownMock;
sendBlockReply: UnknownMock;
sendFinalReply: AsyncUnknownMock;
waitForIdle: AsyncUnknownMock;
getQueuedCounts: UnknownMock;
markComplete: UnknownMock;
};
replyOptions: Record<string, never>;
markDispatchIdle: UnknownMock;
};
export function setFeishuLifecycleStateDir(prefix: string) {
process.env.OPENCLAW_STATE_DIR = `/tmp/${prefix}-${randomUUID()}`;
@@ -28,7 +59,7 @@ export const FEISHU_PREFETCHED_BOT_OPEN_ID_SOURCE = {
botName: "Bot",
} as const;
export function createFeishuLifecycleReplyDispatcher() {
export function createFeishuLifecycleReplyDispatcher(): FeishuLifecycleReplyDispatcher {
return {
dispatcher: {
sendToolResult: vi.fn(() => false),
@@ -134,16 +165,7 @@ export function installFeishuLifecycleReplyRuntime(params: {
}
export function mockFeishuReplyOnceDispatch(params: {
dispatchReplyFromConfigMock: {
mockImplementation: (
fn: (args: {
ctx?: unknown;
dispatcher?: {
sendFinalReply?: (payload: { text: string }) => Promise<unknown>;
};
}) => Promise<unknown>,
) => void;
};
dispatchReplyFromConfigMock: FeishuDispatchReplyMock;
replyText: string;
shouldSendFinalReply?: (ctx: unknown) => boolean;
}) {

View File

@@ -1,10 +1,12 @@
import { vi } from "vitest";
import { vi, type Mock } from "vitest";
const hoisted = vi.hoisted(() => ({
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
const hoisted = vi.hoisted((): { recordInboundSessionMock: AsyncUnknownMock } => ({
recordInboundSessionMock: vi.fn().mockResolvedValue(undefined),
}));
export const recordInboundSessionMock = hoisted.recordInboundSessionMock;
export const recordInboundSessionMock: AsyncUnknownMock = hoisted.recordInboundSessionMock;
vi.mock("./bot-message-context.session.runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./bot-message-context.session.runtime.js")>();

View File

@@ -1,5 +1,5 @@
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { expect, vi } from "vitest";
import { expect, vi, type Mock } from "vitest";
import type { OpenClawConfig } from "../runtime-api.js";
import type { TelegramNativeCommandDeps } from "./bot-native-command-deps.runtime.js";
import {
@@ -12,6 +12,7 @@ type RegisteredCommand = {
command: string;
description: string;
};
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type CreateCommandBotResult = {
bot: RegisterTelegramNativeCommandsParams["bot"];
@@ -36,7 +37,7 @@ const deliveryMocks = vi.hoisted(() => ({
export const listSkillCommandsForAgents = skillCommandMocks.listSkillCommandsForAgents;
export const deliverReplies = deliveryMocks.deliverReplies;
export const editMessageTelegram = deliveryMocks.editMessageTelegram;
export const emitTelegramMessageSentHooks = deliveryMocks.emitTelegramMessageSentHooks;
export const emitTelegramMessageSentHooks: UnknownMock = deliveryMocks.emitTelegramMessageSentHooks;
vi.mock("./bot/delivery.js", () => ({
deliverReplies,

View File

@@ -73,6 +73,7 @@ type TelegramBotRuntime = {
sequentialize: typeof sequentialize;
apiThrottler: typeof apiThrottler;
};
type TelegramBotInstance = InstanceType<TelegramBotRuntime["Bot"]>;
const DEFAULT_TELEGRAM_BOT_RUNTIME: TelegramBotRuntime = {
Bot,
@@ -134,7 +135,7 @@ function extractTelegramApiMethod(input: TelegramFetchInput): string | null {
}
}
export function createTelegramBot(opts: TelegramBotOptions) {
export function createTelegramBot(opts: TelegramBotOptions): TelegramBotInstance {
const botRuntime = telegramBotRuntimeForTest ?? DEFAULT_TELEGRAM_BOT_RUNTIME;
const runtime: RuntimeEnv = opts.runtime ?? createNonExitingRuntime();
const telegramDeps = opts.telegramDeps ?? defaultTelegramBotDeps;

View File

@@ -1,11 +1,14 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/testing";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi, type Mock } from "vitest";
export const readConfigFileSnapshotForWrite = vi.fn();
export const writeConfigFile = vi.fn();
export const loadCronStore = vi.fn();
export const resolveCronStorePath = vi.fn();
export const saveCronStore = vi.fn();
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
export const readConfigFileSnapshotForWrite: AsyncUnknownMock = vi.fn();
export const writeConfigFile: AsyncUnknownMock = vi.fn();
export const loadCronStore: AsyncUnknownMock = vi.fn();
export const resolveCronStorePath: UnknownMock = vi.fn();
export const saveCronStore: AsyncUnknownMock = vi.fn();
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();

View File

@@ -4,7 +4,7 @@ import os from "node:os";
import path from "node:path";
import { resetInboundDedupe } from "openclaw/plugin-sdk/reply-runtime";
import { resetLogger, setLoggerOverride } from "openclaw/plugin-sdk/runtime-env";
import { afterAll, afterEach, beforeAll, beforeEach, vi } from "vitest";
import { afterAll, afterEach, beforeAll, beforeEach, vi, type Mock } from "vitest";
import type { WebInboundMessage, WebListenerCloseReason } from "./inbound.js";
import {
resetBaileysMocks as _resetBaileysMocks,
@@ -25,6 +25,18 @@ type MockWebListener = {
sendReaction: () => Promise<void>;
sendComposingTo: () => Promise<void>;
};
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
type WebAutoReplyRuntime = {
log: UnknownMock;
error: UnknownMock;
exit: UnknownMock;
};
type WebAutoReplyMonitorHarness = {
runtime: WebAutoReplyRuntime;
controller: AbortController;
run: Promise<unknown>;
};
export const TEST_NET_IP = "203.0.113.10";
@@ -228,7 +240,7 @@ export function createWebInboundDeliverySpies(): AnyExport {
};
}
export function createWebAutoReplyRuntime() {
export function createWebAutoReplyRuntime(): WebAutoReplyRuntime {
return {
log: vi.fn(),
error: vi.fn(),
@@ -239,13 +251,13 @@ export function createWebAutoReplyRuntime() {
export function startWebAutoReplyMonitor(params: {
monitorWebChannelFn: (...args: unknown[]) => Promise<unknown>;
listenerFactory: unknown;
sleep: ReturnType<typeof vi.fn>;
sleep: UnknownMock | AsyncUnknownMock;
signal?: AbortSignal;
heartbeatSeconds?: number;
messageTimeoutMs?: number;
watchdogCheckMs?: number;
reconnect?: { initialMs: number; maxMs: number; maxAttempts: number; factor: number };
}) {
}): WebAutoReplyMonitorHarness {
const runtime = createWebAutoReplyRuntime();
const controller = new AbortController();
const run = params.monitorWebChannelFn(

View File

@@ -29,7 +29,7 @@ export const DEFAULT_WEB_INBOX_CONFIG = {
responsePrefix: undefined,
},
} as const;
export const mockLoadConfig = loadConfigMock;
export const mockLoadConfig: typeof loadConfigMock = loadConfigMock;
export const readAllowFromStoreMock = pairingReadAllowFromStoreMock;
export const upsertPairingRequestMock = pairingUpsertPairingRequestMock;

View File

@@ -3,7 +3,7 @@ import {
resolveOpenProviderRuntimeGroupPolicy,
warnMissingProviderGroupPolicyFallbackOnce,
} from "openclaw/plugin-sdk/runtime-group-policy";
import { vi } from "vitest";
import { vi, type Mock } from "vitest";
export type AsyncMock<TArgs extends unknown[] = unknown[], TResult = unknown> = {
(...args: TArgs): Promise<TResult>;
@@ -11,8 +11,9 @@ export type AsyncMock<TArgs extends unknown[] = unknown[], TResult = unknown> =
mockResolvedValue: (value: TResult) => AsyncMock<TArgs, TResult>;
mockResolvedValueOnce: (value: TResult) => AsyncMock<TArgs, TResult>;
};
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
export const loadConfigMock = vi.fn();
export const loadConfigMock: UnknownMock = vi.fn();
export const readAllowFromStoreMock = vi.fn() as AsyncMock;
export const upsertPairingRequestMock = vi.fn() as AsyncMock;

View File

@@ -1,5 +1,5 @@
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/zalo";
import { vi } from "vitest";
import { vi, type Mock } from "vitest";
import {
createEmptyPluginRegistry,
setActivePluginRegistry,
@@ -16,19 +16,34 @@ const secretInputModuleUrl = new URL("../src/secret-input.ts", import.meta.url).
const apiModuleId = new URL("../src/api.js", import.meta.url).pathname;
const runtimeModuleId = new URL("../src/runtime.js", import.meta.url).pathname;
const lifecycleMocks = vi.hoisted(() => ({
setWebhookMock: vi.fn(async () => ({ ok: true, result: { url: "" } })),
deleteWebhookMock: vi.fn(async () => ({ ok: true, result: { url: "" } })),
getWebhookInfoMock: vi.fn(async () => ({ ok: true, result: { url: "" } })),
getUpdatesMock: vi.fn(() => new Promise(() => {})),
sendChatActionMock: vi.fn(async () => ({ ok: true })),
sendMessageMock: vi.fn(async () => ({
ok: true,
result: { message_id: "zalo-test-reply-1" },
})),
sendPhotoMock: vi.fn(async () => ({ ok: true })),
getZaloRuntimeMock: vi.fn(),
}));
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
type ZaloLifecycleMocks = {
setWebhookMock: AsyncUnknownMock;
deleteWebhookMock: AsyncUnknownMock;
getWebhookInfoMock: AsyncUnknownMock;
getUpdatesMock: UnknownMock;
sendChatActionMock: AsyncUnknownMock;
sendMessageMock: AsyncUnknownMock;
sendPhotoMock: AsyncUnknownMock;
getZaloRuntimeMock: UnknownMock;
};
const lifecycleMocks = vi.hoisted(
(): ZaloLifecycleMocks => ({
setWebhookMock: vi.fn(async () => ({ ok: true, result: { url: "" } })),
deleteWebhookMock: vi.fn(async () => ({ ok: true, result: { url: "" } })),
getWebhookInfoMock: vi.fn(async () => ({ ok: true, result: { url: "" } })),
getUpdatesMock: vi.fn(() => new Promise(() => {})),
sendChatActionMock: vi.fn(async () => ({ ok: true })),
sendMessageMock: vi.fn(async () => ({
ok: true,
result: { message_id: "zalo-test-reply-1" },
})),
sendPhotoMock: vi.fn(async () => ({ ok: true })),
getZaloRuntimeMock: vi.fn(),
}),
);
export const setWebhookMock = lifecycleMocks.setWebhookMock;
export const deleteWebhookMock = lifecycleMocks.deleteWebhookMock;
@@ -37,7 +52,7 @@ export const getUpdatesMock = lifecycleMocks.getUpdatesMock;
export const sendChatActionMock = lifecycleMocks.sendChatActionMock;
export const sendMessageMock = lifecycleMocks.sendMessageMock;
export const sendPhotoMock = lifecycleMocks.sendPhotoMock;
export const getZaloRuntimeMock = lifecycleMocks.getZaloRuntimeMock;
export const getZaloRuntimeMock: UnknownMock = lifecycleMocks.getZaloRuntimeMock;
function installLifecycleModuleMocks() {
vi.doMock(apiModuleId, async (importOriginal) => {

View File

@@ -1,31 +1,59 @@
import { vi } from "vitest";
import { vi, type Mock } from "vitest";
const zaloJsMocks = vi.hoisted(() => ({
checkZaloAuthenticatedMock: vi.fn(async () => false),
getZaloUserInfoMock: vi.fn(async () => null),
listZaloFriendsMock: vi.fn(async () => []),
listZaloFriendsMatchingMock: vi.fn(async () => []),
listZaloGroupMembersMock: vi.fn(async () => []),
listZaloGroupsMock: vi.fn(async () => []),
listZaloGroupsMatchingMock: vi.fn(async () => []),
logoutZaloProfileMock: vi.fn(async () => {}),
resolveZaloAllowFromEntriesMock: vi.fn(async ({ entries }: { entries: string[] }) =>
entries.map((entry) => ({ input: entry, resolved: true, id: entry, note: undefined })),
),
resolveZaloGroupContextMock: vi.fn(async () => null),
resolveZaloGroupsByEntriesMock: vi.fn(async ({ entries }: { entries: string[] }) =>
entries.map((entry) => ({ input: entry, resolved: true, id: entry, note: undefined })),
),
startZaloListenerMock: vi.fn(async () => ({ stop: vi.fn() })),
startZaloQrLoginMock: vi.fn(async () => ({
message: "qr pending",
qrDataUrl: undefined,
})),
waitForZaloQrLoginMock: vi.fn(async () => ({
connected: false,
message: "login pending",
})),
}));
type ZaloJsModule = typeof import("./zalo-js.js");
type ZaloJsMocks = {
checkZaloAuthenticatedMock: Mock<ZaloJsModule["checkZaloAuthenticated"]>;
getZaloUserInfoMock: Mock<ZaloJsModule["getZaloUserInfo"]>;
listZaloFriendsMock: Mock<ZaloJsModule["listZaloFriends"]>;
listZaloFriendsMatchingMock: Mock<ZaloJsModule["listZaloFriendsMatching"]>;
listZaloGroupMembersMock: Mock<ZaloJsModule["listZaloGroupMembers"]>;
listZaloGroupsMock: Mock<ZaloJsModule["listZaloGroups"]>;
listZaloGroupsMatchingMock: Mock<ZaloJsModule["listZaloGroupsMatching"]>;
logoutZaloProfileMock: Mock<ZaloJsModule["logoutZaloProfile"]>;
resolveZaloAllowFromEntriesMock: Mock<ZaloJsModule["resolveZaloAllowFromEntries"]>;
resolveZaloGroupContextMock: Mock<ZaloJsModule["resolveZaloGroupContext"]>;
resolveZaloGroupsByEntriesMock: Mock<ZaloJsModule["resolveZaloGroupsByEntries"]>;
startZaloListenerMock: Mock<ZaloJsModule["startZaloListener"]>;
startZaloQrLoginMock: Mock<ZaloJsModule["startZaloQrLogin"]>;
waitForZaloQrLoginMock: Mock<ZaloJsModule["waitForZaloQrLogin"]>;
};
const zaloJsMocks = vi.hoisted(
(): ZaloJsMocks => ({
checkZaloAuthenticatedMock: vi.fn(async () => false),
getZaloUserInfoMock: vi.fn(async () => null),
listZaloFriendsMock: vi.fn(async () => []),
listZaloFriendsMatchingMock: vi.fn(async () => []),
listZaloGroupMembersMock: vi.fn(async () => []),
listZaloGroupsMock: vi.fn(async () => []),
listZaloGroupsMatchingMock: vi.fn(async () => []),
logoutZaloProfileMock: vi.fn(async () => ({
cleared: true,
loggedOut: true,
message: "Logged out and cleared local session.",
})),
resolveZaloAllowFromEntriesMock: vi.fn(async ({ entries }: { entries: string[] }) =>
entries.map((entry) => ({ input: entry, resolved: true, id: entry, note: undefined })),
),
resolveZaloGroupContextMock: vi.fn(async (_profile, groupId) => ({
groupId,
name: undefined,
members: [],
})),
resolveZaloGroupsByEntriesMock: vi.fn(async ({ entries }: { entries: string[] }) =>
entries.map((entry) => ({ input: entry, resolved: true, id: entry, note: undefined })),
),
startZaloListenerMock: vi.fn(async () => ({ stop: vi.fn() })),
startZaloQrLoginMock: vi.fn(async () => ({
message: "qr pending",
qrDataUrl: undefined,
})),
waitForZaloQrLoginMock: vi.fn(async () => ({
connected: false,
message: "login pending",
})),
}),
);
export const checkZaloAuthenticatedMock = zaloJsMocks.checkZaloAuthenticatedMock;
export const getZaloUserInfoMock = zaloJsMocks.getZaloUserInfoMock;
@@ -38,7 +66,8 @@ export const logoutZaloProfileMock = zaloJsMocks.logoutZaloProfileMock;
export const resolveZaloAllowFromEntriesMock = zaloJsMocks.resolveZaloAllowFromEntriesMock;
export const resolveZaloGroupContextMock = zaloJsMocks.resolveZaloGroupContextMock;
export const resolveZaloGroupsByEntriesMock = zaloJsMocks.resolveZaloGroupsByEntriesMock;
export const startZaloListenerMock = zaloJsMocks.startZaloListenerMock;
export const startZaloListenerMock: Mock<ZaloJsModule["startZaloListener"]> =
zaloJsMocks.startZaloListenerMock;
export const startZaloQrLoginMock = zaloJsMocks.startZaloQrLoginMock;
export const waitForZaloQrLoginMock = zaloJsMocks.waitForZaloQrLoginMock;

View File

@@ -1,46 +1,65 @@
import fs from "node:fs/promises";
import type { Mock } from "vitest";
import { beforeEach, vi } from "vitest";
import { buildAnthropicCliBackend } from "../../extensions/anthropic/test-api.js";
import { buildGoogleGeminiCliBackend } from "../../extensions/google/test-api.js";
import { buildOpenAICodexCliBackend } from "../../extensions/openai/test-api.js";
import type { OpenClawConfig } from "../config/config.js";
import type { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
import type { enqueueSystemEvent } from "../infra/system-events.js";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import type { getProcessSupervisor } from "../process/supervisor/index.js";
import { setCliRunnerExecuteTestDeps } from "./cli-runner/execute.js";
import { setCliRunnerPrepareTestDeps } from "./cli-runner/prepare.js";
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
import type { WorkspaceBootstrapFile } from "./workspace.js";
export const supervisorSpawnMock = vi.fn();
export const enqueueSystemEventMock = vi.fn();
export const requestHeartbeatNowMock = vi.fn();
type ProcessSupervisor = ReturnType<typeof getProcessSupervisor>;
type SupervisorSpawnFn = ProcessSupervisor["spawn"];
type EnqueueSystemEventFn = typeof enqueueSystemEvent;
type RequestHeartbeatNowFn = typeof requestHeartbeatNow;
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type BootstrapContext = {
bootstrapFiles: WorkspaceBootstrapFile[];
contextFiles: EmbeddedContextFile[];
};
type ResolveBootstrapContextForRunMock = Mock<() => Promise<BootstrapContext>>;
export const supervisorSpawnMock: UnknownMock = vi.fn();
export const enqueueSystemEventMock: UnknownMock = vi.fn();
export const requestHeartbeatNowMock: UnknownMock = vi.fn();
export const SMALL_PNG_BASE64 =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/woAAn8B9FD5fHAAAAAASUVORK5CYII=";
const hoisted = vi.hoisted(() => {
type BootstrapContext = {
bootstrapFiles: WorkspaceBootstrapFile[];
contextFiles: EmbeddedContextFile[];
};
return {
resolveBootstrapContextForRunMock: vi.fn<() => Promise<BootstrapContext>>(async () => ({
bootstrapFiles: [],
contextFiles: [],
})),
};
});
const hoisted = vi.hoisted(
(): {
resolveBootstrapContextForRunMock: ResolveBootstrapContextForRunMock;
} => {
return {
resolveBootstrapContextForRunMock: vi.fn<() => Promise<BootstrapContext>>(async () => ({
bootstrapFiles: [],
contextFiles: [],
})),
};
},
);
setCliRunnerExecuteTestDeps({
getProcessSupervisor: () => ({
spawn: (...args: unknown[]) => supervisorSpawnMock(...args),
spawn: (params: Parameters<SupervisorSpawnFn>[0]) =>
supervisorSpawnMock(params) as ReturnType<SupervisorSpawnFn>,
cancel: vi.fn(),
cancelScope: vi.fn(),
reconcileOrphans: vi.fn(),
getRecord: vi.fn(),
}),
enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
requestHeartbeatNow: (...args: unknown[]) => requestHeartbeatNowMock(...args),
enqueueSystemEvent: (
text: Parameters<EnqueueSystemEventFn>[0],
options: Parameters<EnqueueSystemEventFn>[1],
) => enqueueSystemEventMock(text, options) as ReturnType<EnqueueSystemEventFn>,
requestHeartbeatNow: (options?: Parameters<RequestHeartbeatNowFn>[0]) =>
requestHeartbeatNowMock(options) as ReturnType<RequestHeartbeatNowFn>,
});
setCliRunnerPrepareTestDeps({
@@ -71,7 +90,19 @@ type TestCliBackendConfig = {
clearEnv?: string[];
};
export function createManagedRun(exit: MockRunExit, pid = 1234) {
type ManagedRunMock = {
runId: string;
pid: number;
startedAtMs: number;
stdin: undefined;
wait: Mock<() => Promise<MockRunExit>>;
cancel: Mock<() => void>;
};
export function createManagedRun(
exit: MockRunExit,
pid = 1234,
): ManagedRunMock & Awaited<ReturnType<SupervisorSpawnFn>> {
return {
runId: "run-supervisor",
pid,

View File

@@ -3,7 +3,7 @@ import os from "node:os";
import path from "node:path";
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { Api, Model } from "@mariozechner/pi-ai";
import { expect, vi } from "vitest";
import { expect, vi, type Mock } from "vitest";
import type {
AssembleResult,
BootstrapResult,
@@ -22,12 +22,37 @@ type AcquireSessionWriteLockFn =
typeof import("../../session-write-lock.js").acquireSessionWriteLock;
type SubscriptionMock = ReturnType<SubscribeEmbeddedPiSessionFn>;
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
type BootstrapContext = {
bootstrapFiles: WorkspaceBootstrapFile[];
contextFiles: EmbeddedContextFile[];
};
type SessionManagerMocks = {
getLeafEntry: UnknownMock;
branch: UnknownMock;
resetLeaf: UnknownMock;
buildSessionContext: Mock<() => { messages: AgentMessage[] }>;
appendCustomEntry: UnknownMock;
};
type AttemptSpawnWorkspaceHoisted = {
spawnSubagentDirectMock: UnknownMock;
createAgentSessionMock: UnknownMock;
sessionManagerOpenMock: UnknownMock;
resolveSandboxContextMock: UnknownMock;
subscribeEmbeddedPiSessionMock: Mock<SubscribeEmbeddedPiSessionFn>;
acquireSessionWriteLockMock: Mock<AcquireSessionWriteLockFn>;
installToolResultContextGuardMock: UnknownMock;
flushPendingToolResultsAfterIdleMock: AsyncUnknownMock;
releaseWsSessionMock: UnknownMock;
resolveBootstrapContextForRunMock: Mock<() => Promise<BootstrapContext>>;
getGlobalHookRunnerMock: Mock<() => unknown>;
initializeGlobalHookRunnerMock: UnknownMock;
runContextEngineMaintenanceMock: AsyncUnknownMock;
sessionManager: SessionManagerMocks;
};
const hoisted = vi.hoisted(() => {
type BootstrapContext = {
bootstrapFiles: WorkspaceBootstrapFile[];
contextFiles: EmbeddedContextFile[];
};
const hoisted = vi.hoisted((): AttemptSpawnWorkspaceHoisted => {
const spawnSubagentDirectMock = vi.fn();
const createAgentSessionMock = vi.fn();
const sessionManagerOpenMock = vi.fn();
@@ -90,7 +115,7 @@ const hoisted = vi.hoisted(() => {
};
});
export function getHoisted() {
export function getHoisted(): AttemptSpawnWorkspaceHoisted {
return hoisted;
}

View File

@@ -6,7 +6,7 @@ export function streamWithPayloadPatch(
context: Parameters<StreamFn>[1],
options: Parameters<StreamFn>[2],
patchPayload: (payload: Record<string, unknown>) => void,
) {
): ReturnType<StreamFn> {
const originalOnPayload = options?.onPayload;
return underlying(model, context, {
...options,

View File

@@ -1,21 +1,30 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { beforeEach, expect, vi } from "vitest";
import { beforeEach, expect, vi, type Mock } from "vitest";
let actualOpenBoundaryFile:
| ((
...args: Parameters<typeof import("../../infra/boundary-file-read.js").openBoundaryFile>
) => ReturnType<typeof import("../../infra/boundary-file-read.js").openBoundaryFile>)
| undefined;
type ExecDockerRawFn = typeof import("./docker.js").execDockerRaw;
type OpenBoundaryFileFn = typeof import("../../infra/boundary-file-read.js").openBoundaryFile;
type ExecDockerArgs = Parameters<ExecDockerRawFn>[0];
type ExecDockerRawMock = Mock<ExecDockerRawFn>;
type OpenBoundaryFileMock = Mock<OpenBoundaryFileFn>;
type FsBridgeHoisted = {
execDockerRaw: ExecDockerRawMock;
openBoundaryFile: OpenBoundaryFileMock;
};
const hoisted = vi.hoisted(() => ({
execDockerRaw: vi.fn(),
openBoundaryFile: vi.fn(),
}));
let actualOpenBoundaryFile: OpenBoundaryFileFn | undefined;
const hoisted = vi.hoisted(
(): FsBridgeHoisted => ({
execDockerRaw: vi.fn(),
openBoundaryFile: vi.fn(),
}),
);
vi.mock("./docker.js", () => ({
execDockerRaw: (...args: unknown[]) => hoisted.execDockerRaw(...args),
execDockerRaw: (args: ExecDockerArgs, opts?: Parameters<ExecDockerRawFn>[1]) =>
hoisted.execDockerRaw(args, opts),
}));
vi.mock("../../infra/boundary-file-read.js", async (importOriginal) => {
@@ -23,7 +32,8 @@ vi.mock("../../infra/boundary-file-read.js", async (importOriginal) => {
actualOpenBoundaryFile = actual.openBoundaryFile;
return {
...actual,
openBoundaryFile: (...args: unknown[]) => hoisted.openBoundaryFile(...args),
openBoundaryFile: (params: Parameters<OpenBoundaryFileFn>[0]) =>
hoisted.openBoundaryFile(params),
};
});
@@ -35,14 +45,16 @@ let createSandboxFsBridgeImpl: typeof import("./fs-bridge.js").createSandboxFsBr
async function loadFreshFsBridgeModuleForTest() {
vi.resetModules();
vi.doMock("./docker.js", () => ({
execDockerRaw: (...args: unknown[]) => hoisted.execDockerRaw(...args),
execDockerRaw: (args: ExecDockerArgs, opts?: Parameters<ExecDockerRawFn>[1]) =>
hoisted.execDockerRaw(args, opts),
}));
vi.doMock("../../infra/boundary-file-read.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../../infra/boundary-file-read.js")>();
actualOpenBoundaryFile = actual.openBoundaryFile;
return {
...actual,
openBoundaryFile: (...args: unknown[]) => hoisted.openBoundaryFile(...args),
openBoundaryFile: (params: Parameters<OpenBoundaryFileFn>[0]) =>
hoisted.openBoundaryFile(params),
};
});
({ createSandboxFsBridge: createSandboxFsBridgeImpl } = await import("./fs-bridge.js"));
@@ -57,8 +69,8 @@ export function createSandboxFsBridge(
return createSandboxFsBridgeImpl(...args);
}
export const mockedExecDockerRaw = hoisted.execDockerRaw;
export const mockedOpenBoundaryFile = hoisted.openBoundaryFile;
export const mockedExecDockerRaw: ExecDockerRawMock = hoisted.execDockerRaw;
export const mockedOpenBoundaryFile: OpenBoundaryFileMock = hoisted.openBoundaryFile;
const DOCKER_SCRIPT_INDEX = 5;
const DOCKER_FIRST_SCRIPT_ARG_INDEX = 7;

View File

@@ -1,38 +1,52 @@
import { Command } from "commander";
import type { Mock } from "vitest";
import { vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
export const loadConfig = vi.fn<() => OpenClawConfig>(() => ({}) as OpenClawConfig);
export const readConfigFileSnapshot = vi.fn();
export const writeConfigFile = vi.fn<(config: OpenClawConfig) => Promise<void>>(
async () => undefined,
);
export const replaceConfigFile = vi.fn(
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
type LoadConfigFn = (typeof import("../config/config.js"))["loadConfig"];
type ParseClawHubPluginSpecFn = (typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"];
type InstallPluginFromMarketplaceFn =
(typeof import("../plugins/marketplace.js"))["installPluginFromMarketplace"];
type ListMarketplacePluginsFn =
(typeof import("../plugins/marketplace.js"))["listMarketplacePlugins"];
type ResolveMarketplaceInstallShortcutFn =
(typeof import("../plugins/marketplace.js"))["resolveMarketplaceInstallShortcut"];
function invokeMock<TArgs extends unknown[], TResult>(mock: unknown, ...args: TArgs): TResult {
return (mock as (...args: TArgs) => TResult)(...args);
}
export const loadConfig: Mock<LoadConfigFn> = vi.fn<LoadConfigFn>(() => ({}) as OpenClawConfig);
export const readConfigFileSnapshot: AsyncUnknownMock = vi.fn();
export const writeConfigFile: AsyncUnknownMock = vi.fn(async () => undefined);
export const replaceConfigFile: AsyncUnknownMock = vi.fn(
async (params: { nextConfig: OpenClawConfig }) => await writeConfigFile(params.nextConfig),
);
export const resolveStateDir = vi.fn(() => "/tmp/openclaw-state");
export const installPluginFromMarketplace = vi.fn();
export const listMarketplacePlugins = vi.fn();
export const resolveMarketplaceInstallShortcut = vi.fn();
export const enablePluginInConfig = vi.fn();
export const recordPluginInstall = vi.fn();
export const clearPluginManifestRegistryCache = vi.fn();
export const buildPluginSnapshotReport = vi.fn();
export const buildPluginDiagnosticsReport = vi.fn();
export const buildPluginCompatibilityNotices = vi.fn();
export const applyExclusiveSlotSelection = vi.fn();
export const uninstallPlugin = vi.fn();
export const updateNpmInstalledPlugins = vi.fn();
export const updateNpmInstalledHookPacks = vi.fn();
export const promptYesNo = vi.fn();
export const installPluginFromNpmSpec = vi.fn();
export const installPluginFromPath = vi.fn();
export const installPluginFromClawHub = vi.fn();
export const parseClawHubPluginSpec = vi.fn();
export const installHooksFromNpmSpec = vi.fn();
export const installHooksFromPath = vi.fn();
export const recordHookInstall = vi.fn();
) as AsyncUnknownMock;
export const resolveStateDir: Mock<() => string> = vi.fn(() => "/tmp/openclaw-state");
export const installPluginFromMarketplace: Mock<InstallPluginFromMarketplaceFn> = vi.fn();
export const listMarketplacePlugins: Mock<ListMarketplacePluginsFn> = vi.fn();
export const resolveMarketplaceInstallShortcut: Mock<ResolveMarketplaceInstallShortcutFn> = vi.fn();
export const enablePluginInConfig: UnknownMock = vi.fn();
export const recordPluginInstall: UnknownMock = vi.fn();
export const clearPluginManifestRegistryCache: UnknownMock = vi.fn();
export const buildPluginSnapshotReport: UnknownMock = vi.fn();
export const buildPluginDiagnosticsReport: UnknownMock = vi.fn();
export const buildPluginCompatibilityNotices: UnknownMock = vi.fn();
export const applyExclusiveSlotSelection: UnknownMock = vi.fn();
export const uninstallPlugin: AsyncUnknownMock = vi.fn();
export const updateNpmInstalledPlugins: AsyncUnknownMock = vi.fn();
export const updateNpmInstalledHookPacks: AsyncUnknownMock = vi.fn();
export const promptYesNo: AsyncUnknownMock = vi.fn();
export const installPluginFromNpmSpec: AsyncUnknownMock = vi.fn();
export const installPluginFromPath: AsyncUnknownMock = vi.fn();
export const installPluginFromClawHub: AsyncUnknownMock = vi.fn();
export const parseClawHubPluginSpec: Mock<ParseClawHubPluginSpecFn> = vi.fn();
export const installHooksFromNpmSpec: AsyncUnknownMock = vi.fn();
export const installHooksFromPath: AsyncUnknownMock = vi.fn();
export const recordHookInstall: UnknownMock = vi.fn();
const { defaultRuntime, runtimeLogs, runtimeErrors, resetRuntimeCapture } =
createCliRuntimeCapture();
@@ -45,9 +59,28 @@ vi.mock("../runtime.js", () => ({
vi.mock("../config/config.js", () => ({
loadConfig: () => loadConfig(),
readConfigFileSnapshot: (...args: unknown[]) => readConfigFileSnapshot(...args),
writeConfigFile: (config: OpenClawConfig) => writeConfigFile(config),
replaceConfigFile: (params: { nextConfig: OpenClawConfig }) => replaceConfigFile(params),
readConfigFileSnapshot: ((
...args: Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshot"]>
) =>
invokeMock<
Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshot"]>,
ReturnType<(typeof import("../config/config.js"))["readConfigFileSnapshot"]>
>(
readConfigFileSnapshot,
...args,
)) as (typeof import("../config/config.js"))["readConfigFileSnapshot"],
writeConfigFile: ((config: OpenClawConfig) =>
invokeMock<
[OpenClawConfig],
ReturnType<(typeof import("../config/config.js"))["writeConfigFile"]>
>(writeConfigFile, config)) as (typeof import("../config/config.js"))["writeConfigFile"],
replaceConfigFile: ((
params: Parameters<(typeof import("../config/config.js"))["replaceConfigFile"]>[0],
) =>
invokeMock<
[Parameters<(typeof import("../config/config.js"))["replaceConfigFile"]>[0]],
ReturnType<(typeof import("../config/config.js"))["replaceConfigFile"]>
>(replaceConfigFile, params)) as (typeof import("../config/config.js"))["replaceConfigFile"],
}));
vi.mock("../config/paths.js", () => ({
@@ -55,18 +88,34 @@ vi.mock("../config/paths.js", () => ({
}));
vi.mock("../plugins/marketplace.js", () => ({
installPluginFromMarketplace: (...args: unknown[]) => installPluginFromMarketplace(...args),
listMarketplacePlugins: (...args: unknown[]) => listMarketplacePlugins(...args),
resolveMarketplaceInstallShortcut: (...args: unknown[]) =>
resolveMarketplaceInstallShortcut(...args),
installPluginFromMarketplace: ((...args: Parameters<InstallPluginFromMarketplaceFn>) =>
installPluginFromMarketplace(...args)) as InstallPluginFromMarketplaceFn,
listMarketplacePlugins: ((...args: Parameters<ListMarketplacePluginsFn>) =>
listMarketplacePlugins(...args)) as ListMarketplacePluginsFn,
resolveMarketplaceInstallShortcut: ((...args: Parameters<ResolveMarketplaceInstallShortcutFn>) =>
resolveMarketplaceInstallShortcut(...args)) as ResolveMarketplaceInstallShortcutFn,
}));
vi.mock("../plugins/enable.js", () => ({
enablePluginInConfig: (...args: unknown[]) => enablePluginInConfig(...args),
enablePluginInConfig: ((cfg: OpenClawConfig, pluginId: string) =>
invokeMock<[OpenClawConfig, string], unknown>(
enablePluginInConfig,
cfg,
pluginId,
)) as (typeof import("../plugins/enable.js"))["enablePluginInConfig"],
}));
vi.mock("../plugins/installs.js", () => ({
recordPluginInstall: (...args: unknown[]) => recordPluginInstall(...args),
recordPluginInstall: ((
...args: Parameters<(typeof import("../plugins/installs.js"))["recordPluginInstall"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/installs.js"))["recordPluginInstall"]>,
ReturnType<(typeof import("../plugins/installs.js"))["recordPluginInstall"]>
>(
recordPluginInstall,
...args,
)) as (typeof import("../plugins/installs.js"))["recordPluginInstall"],
}));
vi.mock("../plugins/manifest-registry.js", () => ({
@@ -74,17 +123,59 @@ vi.mock("../plugins/manifest-registry.js", () => ({
}));
vi.mock("../plugins/status.js", () => ({
buildPluginSnapshotReport: (...args: unknown[]) => buildPluginSnapshotReport(...args),
buildPluginDiagnosticsReport: (...args: unknown[]) => buildPluginDiagnosticsReport(...args),
buildPluginCompatibilityNotices: (...args: unknown[]) => buildPluginCompatibilityNotices(...args),
buildPluginSnapshotReport: ((
...args: Parameters<(typeof import("../plugins/status.js"))["buildPluginSnapshotReport"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/status.js"))["buildPluginSnapshotReport"]>,
ReturnType<(typeof import("../plugins/status.js"))["buildPluginSnapshotReport"]>
>(
buildPluginSnapshotReport,
...args,
)) as (typeof import("../plugins/status.js"))["buildPluginSnapshotReport"],
buildPluginDiagnosticsReport: ((
...args: Parameters<(typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"]>,
ReturnType<(typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"]>
>(
buildPluginDiagnosticsReport,
...args,
)) as (typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"],
buildPluginCompatibilityNotices: ((
...args: Parameters<(typeof import("../plugins/status.js"))["buildPluginCompatibilityNotices"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/status.js"))["buildPluginCompatibilityNotices"]>,
ReturnType<(typeof import("../plugins/status.js"))["buildPluginCompatibilityNotices"]>
>(
buildPluginCompatibilityNotices,
...args,
)) as (typeof import("../plugins/status.js"))["buildPluginCompatibilityNotices"],
}));
vi.mock("../plugins/slots.js", () => ({
applyExclusiveSlotSelection: (...args: unknown[]) => applyExclusiveSlotSelection(...args),
applyExclusiveSlotSelection: ((
params: Parameters<(typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"]>[0],
) =>
invokeMock<
[Parameters<(typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"]>[0]],
ReturnType<(typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"]>
>(
applyExclusiveSlotSelection,
params,
)) as (typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"],
}));
vi.mock("../plugins/uninstall.js", () => ({
uninstallPlugin: (...args: unknown[]) => uninstallPlugin(...args),
uninstallPlugin: ((
...args: Parameters<(typeof import("../plugins/uninstall.js"))["uninstallPlugin"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/uninstall.js"))["uninstallPlugin"]>,
ReturnType<(typeof import("../plugins/uninstall.js"))["uninstallPlugin"]>
>(uninstallPlugin, ...args)) as (typeof import("../plugins/uninstall.js"))["uninstallPlugin"],
resolveUninstallDirectoryTarget: ({
installRecord,
}: {
@@ -93,33 +184,97 @@ vi.mock("../plugins/uninstall.js", () => ({
}));
vi.mock("../plugins/update.js", () => ({
updateNpmInstalledPlugins: (...args: unknown[]) => updateNpmInstalledPlugins(...args),
updateNpmInstalledPlugins: ((
...args: Parameters<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>,
ReturnType<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>
>(
updateNpmInstalledPlugins,
...args,
)) as (typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"],
}));
vi.mock("../hooks/update.js", () => ({
updateNpmInstalledHookPacks: (...args: unknown[]) => updateNpmInstalledHookPacks(...args),
updateNpmInstalledHookPacks: ((
...args: Parameters<(typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"]>
) =>
invokeMock<
Parameters<(typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"]>,
ReturnType<(typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"]>
>(
updateNpmInstalledHookPacks,
...args,
)) as (typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"],
}));
vi.mock("./prompt.js", () => ({
promptYesNo: (...args: unknown[]) => promptYesNo(...args),
promptYesNo: ((...args: Parameters<(typeof import("./prompt.js"))["promptYesNo"]>) =>
invokeMock<
Parameters<(typeof import("./prompt.js"))["promptYesNo"]>,
ReturnType<(typeof import("./prompt.js"))["promptYesNo"]>
>(promptYesNo, ...args)) as (typeof import("./prompt.js"))["promptYesNo"],
}));
vi.mock("../plugins/install.js", () => ({
PLUGIN_INSTALL_ERROR_CODE: {
NPM_PACKAGE_NOT_FOUND: "npm_package_not_found",
},
installPluginFromNpmSpec: (...args: unknown[]) => installPluginFromNpmSpec(...args),
installPluginFromPath: (...args: unknown[]) => installPluginFromPath(...args),
installPluginFromNpmSpec: ((
...args: Parameters<(typeof import("../plugins/install.js"))["installPluginFromNpmSpec"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/install.js"))["installPluginFromNpmSpec"]>,
ReturnType<(typeof import("../plugins/install.js"))["installPluginFromNpmSpec"]>
>(
installPluginFromNpmSpec,
...args,
)) as (typeof import("../plugins/install.js"))["installPluginFromNpmSpec"],
installPluginFromPath: ((
...args: Parameters<(typeof import("../plugins/install.js"))["installPluginFromPath"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/install.js"))["installPluginFromPath"]>,
ReturnType<(typeof import("../plugins/install.js"))["installPluginFromPath"]>
>(
installPluginFromPath,
...args,
)) as (typeof import("../plugins/install.js"))["installPluginFromPath"],
}));
vi.mock("../hooks/install.js", () => ({
installHooksFromNpmSpec: (...args: unknown[]) => installHooksFromNpmSpec(...args),
installHooksFromPath: (...args: unknown[]) => installHooksFromPath(...args),
installHooksFromNpmSpec: ((
...args: Parameters<(typeof import("../hooks/install.js"))["installHooksFromNpmSpec"]>
) =>
invokeMock<
Parameters<(typeof import("../hooks/install.js"))["installHooksFromNpmSpec"]>,
ReturnType<(typeof import("../hooks/install.js"))["installHooksFromNpmSpec"]>
>(
installHooksFromNpmSpec,
...args,
)) as (typeof import("../hooks/install.js"))["installHooksFromNpmSpec"],
installHooksFromPath: ((
...args: Parameters<(typeof import("../hooks/install.js"))["installHooksFromPath"]>
) =>
invokeMock<
Parameters<(typeof import("../hooks/install.js"))["installHooksFromPath"]>,
ReturnType<(typeof import("../hooks/install.js"))["installHooksFromPath"]>
>(
installHooksFromPath,
...args,
)) as (typeof import("../hooks/install.js"))["installHooksFromPath"],
resolveHookInstallDir: (hookId: string) => `/tmp/hooks/${hookId}`,
}));
vi.mock("../hooks/installs.js", () => ({
recordHookInstall: (...args: unknown[]) => recordHookInstall(...args),
recordHookInstall: ((
...args: Parameters<(typeof import("../hooks/installs.js"))["recordHookInstall"]>
) =>
invokeMock<
Parameters<(typeof import("../hooks/installs.js"))["recordHookInstall"]>,
ReturnType<(typeof import("../hooks/installs.js"))["recordHookInstall"]>
>(recordHookInstall, ...args)) as (typeof import("../hooks/installs.js"))["recordHookInstall"],
}));
vi.mock("../plugins/clawhub.js", () => ({
@@ -127,13 +282,31 @@ vi.mock("../plugins/clawhub.js", () => ({
PACKAGE_NOT_FOUND: "package_not_found",
VERSION_NOT_FOUND: "version_not_found",
},
installPluginFromClawHub: (...args: unknown[]) => installPluginFromClawHub(...args),
installPluginFromClawHub: ((
...args: Parameters<(typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"]>
) =>
invokeMock<
Parameters<(typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"]>,
ReturnType<(typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"]>
>(
installPluginFromClawHub,
...args,
)) as (typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"],
formatClawHubSpecifier: ({ name, version }: { name: string; version?: string }) =>
`clawhub:${name}${version ? `@${version}` : ""}`,
}));
vi.mock("../infra/clawhub.js", () => ({
parseClawHubPluginSpec: (...args: unknown[]) => parseClawHubPluginSpec(...args),
parseClawHubPluginSpec: ((
...args: Parameters<(typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"]>
) =>
invokeMock<
Parameters<(typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"]>,
ReturnType<(typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"]>
>(
parseClawHubPluginSpec,
...args,
)) as (typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"],
}));
const { registerPluginsCli } = await import("./plugins-cli.js");
@@ -197,7 +370,8 @@ export function resetPluginsCliTestState() {
});
writeConfigFile.mockResolvedValue(undefined);
replaceConfigFile.mockImplementation(
async (params: { nextConfig: OpenClawConfig }) => await writeConfigFile(params.nextConfig),
(async (params: { nextConfig: OpenClawConfig }) =>
await writeConfigFile(params.nextConfig)) as (...args: unknown[]) => Promise<unknown>,
);
resolveStateDir.mockReturnValue("/tmp/openclaw-state");
resolveMarketplaceInstallShortcut.mockResolvedValue(null);
@@ -205,8 +379,12 @@ export function resetPluginsCliTestState() {
ok: false,
error: "marketplace install failed",
});
enablePluginInConfig.mockImplementation((cfg: OpenClawConfig) => ({ config: cfg }));
recordPluginInstall.mockImplementation((cfg: OpenClawConfig) => cfg);
enablePluginInConfig.mockImplementation(((cfg: OpenClawConfig) => ({ config: cfg })) as (
...args: unknown[]
) => unknown);
recordPluginInstall.mockImplementation(
((cfg: OpenClawConfig) => cfg) as (...args: unknown[]) => unknown,
);
const defaultPluginReport = {
plugins: [],
diagnostics: [],
@@ -214,10 +392,10 @@ export function resetPluginsCliTestState() {
buildPluginSnapshotReport.mockReturnValue(defaultPluginReport);
buildPluginDiagnosticsReport.mockReturnValue(defaultPluginReport);
buildPluginCompatibilityNotices.mockReturnValue([]);
applyExclusiveSlotSelection.mockImplementation(({ config }: { config: OpenClawConfig }) => ({
applyExclusiveSlotSelection.mockImplementation((({ config }: { config: OpenClawConfig }) => ({
config,
warnings: [],
}));
})) as (...args: unknown[]) => unknown);
uninstallPlugin.mockResolvedValue({
ok: true,
config: {} as OpenClawConfig,
@@ -260,5 +438,7 @@ export function resetPluginsCliTestState() {
ok: false,
error: "hook npm install disabled in test",
});
recordHookInstall.mockImplementation((cfg: OpenClawConfig) => cfg);
recordHookInstall.mockImplementation(
((cfg: OpenClawConfig) => cfg) as (...args: unknown[]) => unknown,
);
}

View File

@@ -172,6 +172,7 @@ describe("plugins cli install", () => {
ok: true,
pluginId: "alpha",
targetDir: cliInstallPath("alpha"),
extensions: ["index.js"],
version: "1.2.3",
marketplaceName: "Claude",
marketplaceSource: "local/repo",

View File

@@ -1,3 +1,4 @@
import type { Mock } from "vitest";
import { vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { mergeMockedModule } from "../test-utils/vitest-module-mocks.js";
@@ -7,9 +8,11 @@ type ReplaceConfigFileResult = Awaited<
ReturnType<(typeof import("../config/config.js"))["replaceConfigFile"]>
>;
export const readConfigFileSnapshotMock = vi.fn();
export const writeConfigFileMock = vi.fn().mockResolvedValue(undefined);
export const replaceConfigFileMock = vi.fn(
export const readConfigFileSnapshotMock: Mock<(...args: unknown[]) => Promise<unknown>> = vi.fn();
export const writeConfigFileMock: Mock<(...args: unknown[]) => Promise<unknown>> = vi
.fn()
.mockResolvedValue(undefined);
export const replaceConfigFileMock: Mock<(...args: unknown[]) => Promise<unknown>> = vi.fn(
async (params: { nextConfig: OpenClawConfig }): Promise<ReplaceConfigFileResult> => {
await writeConfigFileMock(params.nextConfig);
return {
@@ -19,17 +22,18 @@ export const replaceConfigFileMock = vi.fn(
nextConfig: params.nextConfig,
};
},
);
) as Mock<(...args: unknown[]) => Promise<unknown>>;
vi.mock("../config/config.js", async (importOriginal) => {
return await mergeMockedModule(
await importOriginal<typeof import("../config/config.js")>(),
() => ({
readConfigFileSnapshot: readConfigFileSnapshotMock,
writeConfigFile: writeConfigFileMock,
replaceConfigFile: replaceConfigFileMock,
}),
);
const actual = await importOriginal<typeof import("../config/config.js")>();
return await mergeMockedModule(actual, () => ({
readConfigFileSnapshot: (...args: Parameters<typeof actual.readConfigFileSnapshot>) =>
readConfigFileSnapshotMock(...args) as ReturnType<typeof actual.readConfigFileSnapshot>,
writeConfigFile: (...args: Parameters<typeof actual.writeConfigFile>) =>
writeConfigFileMock(...args) as ReturnType<typeof actual.writeConfigFile>,
replaceConfigFile: (...args: Parameters<typeof actual.replaceConfigFile>) =>
replaceConfigFileMock(...args) as ReturnType<typeof actual.replaceConfigFile>,
}));
});
export const runtime = createTestRuntime();

View File

@@ -1,6 +1,7 @@
import type { Mock } from "vitest";
import { vi } from "vitest";
export const terminalNoteMock = vi.fn();
export const terminalNoteMock: Mock<(...args: unknown[]) => unknown> = vi.fn();
vi.mock("../terminal/note.js", () => ({
note: (...args: unknown[]) => terminalNoteMock(...args),

View File

@@ -1,7 +1,27 @@
import type { Mock } from "vitest";
import { vi } from "vitest";
import type { OpenClawConfig } from "../config/types.js";
export function createStatusScanSharedMocks(configPathLabel: string) {
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type ResolveConfigPathMock = Mock<() => string>;
export type StatusScanSharedMocks = {
resolveConfigPath: ResolveConfigPathMock;
hasPotentialConfiguredChannels: UnknownMock;
readBestEffortConfig: UnknownMock;
resolveCommandSecretRefsViaGateway: UnknownMock;
getUpdateCheckResult: UnknownMock;
getAgentLocalStatuses: UnknownMock;
getStatusSummary: UnknownMock;
getMemorySearchManager: UnknownMock;
buildGatewayConnectionDetails: UnknownMock;
probeGateway: UnknownMock;
resolveGatewayProbeAuthResolution: UnknownMock;
ensurePluginRegistryLoaded: UnknownMock;
buildPluginCompatibilityNotices: Mock<() => unknown[]>;
};
export function createStatusScanSharedMocks(configPathLabel: string): StatusScanSharedMocks {
return {
resolveConfigPath: vi.fn(() => `/tmp/openclaw-${configPathLabel}-missing-${process.pid}.json`),
hasPotentialConfiguredChannels: vi.fn(),
@@ -19,37 +39,54 @@ export function createStatusScanSharedMocks(configPathLabel: string) {
};
}
export type StatusScanSharedMocks = ReturnType<typeof createStatusScanSharedMocks>;
type StatusOsSummaryModuleMock = {
resolveOsSummary: Mock<() => { label: string }>;
};
export function createStatusOsSummaryModuleMock() {
export function createStatusOsSummaryModuleMock(): StatusOsSummaryModuleMock {
return {
resolveOsSummary: vi.fn(() => ({ label: "test-os" })),
};
}
type StatusScanDepsRuntimeModuleMock = {
getTailnetHostname: UnknownMock;
getMemorySearchManager: StatusScanSharedMocks["getMemorySearchManager"];
};
export function createStatusScanDepsRuntimeModuleMock(
mocks: Pick<StatusScanSharedMocks, "getMemorySearchManager">,
) {
): StatusScanDepsRuntimeModuleMock {
return {
getTailnetHostname: vi.fn(),
getMemorySearchManager: mocks.getMemorySearchManager,
};
}
type StatusGatewayProbeModuleMock = {
pickGatewaySelfPresence: Mock<() => null>;
resolveGatewayProbeAuthResolution: StatusScanSharedMocks["resolveGatewayProbeAuthResolution"];
};
export function createStatusGatewayProbeModuleMock(
mocks: Pick<StatusScanSharedMocks, "resolveGatewayProbeAuthResolution">,
) {
): StatusGatewayProbeModuleMock {
return {
pickGatewaySelfPresence: vi.fn(() => null),
resolveGatewayProbeAuthResolution: mocks.resolveGatewayProbeAuthResolution,
};
}
type StatusGatewayCallModuleMock = {
buildGatewayConnectionDetails: StatusScanSharedMocks["buildGatewayConnectionDetails"];
callGateway?: unknown;
};
export function createStatusGatewayCallModuleMock(
mocks: Pick<StatusScanSharedMocks, "buildGatewayConnectionDetails"> & {
callGateway?: unknown;
},
) {
): StatusGatewayCallModuleMock {
return {
buildGatewayConnectionDetails: mocks.buildGatewayConnectionDetails,
...(mocks.callGateway ? { callGateway: mocks.callGateway } : {}),
@@ -58,7 +95,7 @@ export function createStatusGatewayCallModuleMock(
export function createStatusPluginRegistryModuleMock(
mocks: Pick<StatusScanSharedMocks, "ensurePluginRegistryLoaded">,
) {
): { ensurePluginRegistryLoaded: StatusScanSharedMocks["ensurePluginRegistryLoaded"] } {
return {
ensurePluginRegistryLoaded: mocks.ensurePluginRegistryLoaded,
};
@@ -66,7 +103,7 @@ export function createStatusPluginRegistryModuleMock(
export function createStatusPluginStatusModuleMock(
mocks: Pick<StatusScanSharedMocks, "buildPluginCompatibilityNotices">,
) {
): { buildPluginCompatibilityNotices: StatusScanSharedMocks["buildPluginCompatibilityNotices"] } {
return {
buildPluginCompatibilityNotices: mocks.buildPluginCompatibilityNotices,
};
@@ -74,7 +111,7 @@ export function createStatusPluginStatusModuleMock(
export function createStatusUpdateModuleMock(
mocks: Pick<StatusScanSharedMocks, "getUpdateCheckResult">,
) {
): { getUpdateCheckResult: StatusScanSharedMocks["getUpdateCheckResult"] } {
return {
getUpdateCheckResult: mocks.getUpdateCheckResult,
};
@@ -82,7 +119,7 @@ export function createStatusUpdateModuleMock(
export function createStatusAgentLocalModuleMock(
mocks: Pick<StatusScanSharedMocks, "getAgentLocalStatuses">,
) {
): { getAgentLocalStatuses: StatusScanSharedMocks["getAgentLocalStatuses"] } {
return {
getAgentLocalStatuses: mocks.getAgentLocalStatuses,
};
@@ -90,23 +127,23 @@ export function createStatusAgentLocalModuleMock(
export function createStatusSummaryModuleMock(
mocks: Pick<StatusScanSharedMocks, "getStatusSummary">,
) {
): { getStatusSummary: StatusScanSharedMocks["getStatusSummary"] } {
return {
getStatusSummary: mocks.getStatusSummary,
};
}
export function createStatusExecModuleMock() {
export function createStatusExecModuleMock(): { runExec: UnknownMock } {
return {
runExec: vi.fn(),
};
}
type StatusScanModuleTestMocks = StatusScanSharedMocks & {
buildChannelsTable?: ReturnType<typeof vi.fn>;
callGateway?: ReturnType<typeof vi.fn>;
getStatusCommandSecretTargetIds?: ReturnType<typeof vi.fn>;
resolveMemorySearchConfig?: ReturnType<typeof vi.fn>;
buildChannelsTable?: UnknownMock;
callGateway?: UnknownMock;
getStatusCommandSecretTargetIds?: UnknownMock;
resolveMemorySearchConfig?: UnknownMock;
};
export async function loadStatusScanModuleForTest(

View File

@@ -33,6 +33,9 @@ type GetReplyFromConfigFn = (
opts?: GetReplyOptions,
configOverride?: OpenClawConfig,
) => Promise<ReplyPayload | ReplyPayload[] | undefined>;
type CronIsolatedRunFn = (...args: unknown[]) => Promise<{ status: string; summary: string }>;
type AgentCommandFn = (...args: unknown[]) => Promise<void>;
type SendWhatsAppFn = (...args: unknown[]) => Promise<{ messageId: string; toJid: string }>;
const createStubOutboundAdapter = (channelId: ChannelPlugin["id"]): ChannelOutboundAdapter => ({
deliveryMode: "direct",
@@ -233,8 +236,8 @@ const hoisted = vi.hoisted(() => {
reasoning?: boolean;
}>;
};
cronIsolatedRun: ReturnType<typeof vi.fn>;
agentCommand: ReturnType<typeof vi.fn>;
cronIsolatedRun: Mock<CronIsolatedRunFn>;
agentCommand: Mock<AgentCommandFn>;
testIsNixMode: { value: boolean };
sessionStoreSaveDelayMs: { value: number };
embeddedRunMock: {
@@ -244,8 +247,8 @@ const hoisted = vi.hoisted(() => {
waitResults: Map<string, boolean>;
};
testTailscaleWhois: { value: TailscaleWhoisIdentity | null };
getReplyFromConfig: ReturnType<typeof vi.fn<GetReplyFromConfigFn>>;
sendWhatsAppMock: ReturnType<typeof vi.fn>;
getReplyFromConfig: Mock<GetReplyFromConfigFn>;
sendWhatsAppMock: Mock<SendWhatsAppFn>;
testState: {
agentConfig: Record<string, unknown> | undefined;
agentsConfig: Record<string, unknown> | undefined;
@@ -346,13 +349,13 @@ export const setTestConfigRoot = (root: string) => {
export const testTailnetIPv4 = hoisted.testTailnetIPv4;
export const testTailscaleWhois = hoisted.testTailscaleWhois;
export const piSdkMock = hoisted.piSdkMock;
export const cronIsolatedRun = hoisted.cronIsolatedRun;
export const agentCommand = hoisted.agentCommand;
export const cronIsolatedRun: Mock<CronIsolatedRunFn> = hoisted.cronIsolatedRun;
export const agentCommand: Mock<AgentCommandFn> = hoisted.agentCommand;
export const getReplyFromConfig: Mock<GetReplyFromConfigFn> = hoisted.getReplyFromConfig;
export const mockGetReplyFromConfigOnce = (impl: GetReplyFromConfigFn) => {
getReplyFromConfig.mockImplementationOnce(impl);
};
export const sendWhatsAppMock = hoisted.sendWhatsAppMock;
export const sendWhatsAppMock: Mock<SendWhatsAppFn> = hoisted.sendWhatsAppMock;
export const testState = hoisted.testState;

View File

@@ -29,7 +29,9 @@ export function logVerboseConsole(message: string) {
console.log(theme.muted(message));
}
export const success = theme.success;
export const warn = theme.warn;
export const info = theme.info;
export const danger = theme.error;
type ThemeFormatter = (value: string) => string;
export const success: ThemeFormatter = theme.success;
export const warn: ThemeFormatter = theme.warn;
export const info: ThemeFormatter = theme.info;
export const danger: ThemeFormatter = theme.error;

View File

@@ -2,15 +2,17 @@
import { createLazyRuntimeMethodBinder, createLazyRuntimeModule } from "../shared/lazy-runtime.js";
type ProviderAuthLoginRuntime = typeof import("./provider-auth-login.runtime.js");
const loadProviderAuthLoginRuntime = createLazyRuntimeModule(
() => import("./provider-auth-login.runtime.js"),
);
const bindProviderAuthLoginRuntime = createLazyRuntimeMethodBinder(loadProviderAuthLoginRuntime);
export const githubCopilotLoginCommand = bindProviderAuthLoginRuntime(
(runtime) => runtime.githubCopilotLoginCommand,
);
export const loginChutes = bindProviderAuthLoginRuntime((runtime) => runtime.loginChutes);
export const loginOpenAICodexOAuth = bindProviderAuthLoginRuntime(
(runtime) => runtime.loginOpenAICodexOAuth,
export const githubCopilotLoginCommand: ProviderAuthLoginRuntime["githubCopilotLoginCommand"] =
bindProviderAuthLoginRuntime((runtime) => runtime.githubCopilotLoginCommand);
export const loginChutes: ProviderAuthLoginRuntime["loginChutes"] = bindProviderAuthLoginRuntime(
(runtime) => runtime.loginChutes,
);
export const loginOpenAICodexOAuth: ProviderAuthLoginRuntime["loginOpenAICodexOAuth"] =
bindProviderAuthLoginRuntime((runtime) => runtime.loginOpenAICodexOAuth);

View File

@@ -1,10 +1,23 @@
import { vi } from "vitest";
import { vi, type Mock } from "vitest";
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
import type { WizardPrompter } from "../../../src/wizard/prompts.js";
import { createRuntimeEnv } from "./runtime-env.js";
export type { WizardPrompter } from "../../../src/wizard/prompts.js";
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
type QueuedWizardPrompter = {
intro: AsyncUnknownMock;
outro: AsyncUnknownMock;
note: AsyncUnknownMock;
select: AsyncUnknownMock;
multiselect: AsyncUnknownMock;
text: AsyncUnknownMock;
confirm: AsyncUnknownMock;
progress: Mock<() => { update: UnknownMock; stop: UnknownMock }>;
prompter: WizardPrompter;
};
export async function selectFirstWizardOption<T>(params: {
options: Array<{ value: T }>;
@@ -34,7 +47,7 @@ export function createQueuedWizardPrompter(params?: {
selectValues?: string[];
textValues?: string[];
confirmValues?: boolean[];
}) {
}): QueuedWizardPrompter {
const selectValues = [...(params?.selectValues ?? [])];
const textValues = [...(params?.textValues ?? [])];
const confirmValues = [...(params?.confirmValues ?? [])];