fix(ci): repair helper typing regressions

This commit is contained in:
Peter Steinberger 2026-03-14 03:01:33 +00:00
parent eee5d7c6b0
commit 8bc163d15f
9 changed files with 84 additions and 98 deletions

View File

@ -1,17 +1,17 @@
import type { z } from "zod"; import type { z } from "zod";
type RequireOpenAllowFromFn = (params: { type RequireOpenAllowFromFn = (params: {
policy: unknown; policy?: string;
allowFrom: unknown; allowFrom?: Array<string | number>;
ctx: z.RefinementCtx; ctx: z.RefinementCtx;
path: string[]; path: Array<string | number>;
message: string; message: string;
}) => void; }) => void;
export function requireChannelOpenAllowFrom(params: { export function requireChannelOpenAllowFrom(params: {
channel: string; channel: string;
policy: unknown; policy?: string;
allowFrom: unknown; allowFrom?: Array<string | number>;
ctx: z.RefinementCtx; ctx: z.RefinementCtx;
requireOpenAllowFrom: RequireOpenAllowFromFn; requireOpenAllowFrom: RequireOpenAllowFromFn;
}) { }) {

View File

@ -1,3 +1,5 @@
import type { ChannelDirectoryAdapter } from "../../src/channels/plugins/types.js";
export function createDirectoryTestRuntime() { export function createDirectoryTestRuntime() {
return { return {
log: () => {}, log: () => {},
@ -8,15 +10,7 @@ export function createDirectoryTestRuntime() {
}; };
} }
export function expectDirectorySurface( export function expectDirectorySurface(directory: ChannelDirectoryAdapter | null | undefined) {
directory:
| {
listPeers?: unknown;
listGroups?: unknown;
}
| null
| undefined,
) {
if (!directory) { if (!directory) {
throw new Error("expected directory"); throw new Error("expected directory");
} }
@ -27,7 +21,7 @@ export function expectDirectorySurface(
throw new Error("expected listGroups"); throw new Error("expected listGroups");
} }
return directory as { return directory as {
listPeers: NonNullable<typeof directory.listPeers>; listPeers: NonNullable<ChannelDirectoryAdapter["listPeers"]>;
listGroups: NonNullable<typeof directory.listGroups>; listGroups: NonNullable<ChannelDirectoryAdapter["listGroups"]>;
}; };
} }

View File

@ -1,29 +1,11 @@
type TestLogger = { import type { OpenClawPluginApi } from "../../src/plugins/types.js";
info: () => void;
warn: () => void;
error: () => void;
debug?: () => void;
};
type TestPluginApiDefaults = { type TestPluginApiInput = Partial<OpenClawPluginApi> &
logger: TestLogger; Pick<OpenClawPluginApi, "id" | "name" | "source" | "config" | "runtime">;
registerTool: () => void;
registerHook: () => void;
registerHttpRoute: () => void;
registerChannel: () => void;
registerGatewayMethod: () => void;
registerCli: () => void;
registerService: () => void;
registerProvider: () => void;
registerCommand: () => void;
registerContextEngine: () => void;
resolvePath: (input: string) => string;
on: () => void;
};
export function createTestPluginApi<T extends object>(api: T): T & TestPluginApiDefaults { export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi {
return { return {
logger: { info() {}, warn() {}, error() {} }, logger: { info() {}, warn() {}, error() {}, debug() {} },
registerTool() {}, registerTool() {},
registerHook() {}, registerHook() {},
registerHttpRoute() {}, registerHttpRoute() {},

View File

@ -1,19 +1,22 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "../test-runtime-capture.js"; import { createCliRuntimeCapture } from "../test-runtime-capture.js";
import type { DaemonStatus } from "./status.gather.js";
const gatherDaemonStatus = vi.fn(async (_opts?: unknown) => ({ const gatherDaemonStatus = vi.fn(
service: { async (_opts?: unknown): Promise<DaemonStatus> => ({
label: "LaunchAgent", service: {
loaded: true, label: "LaunchAgent",
loadedText: "loaded", loaded: true,
notLoadedText: "not loaded", loadedText: "loaded",
}, notLoadedText: "not loaded",
rpc: { },
ok: true, rpc: {
url: "ws://127.0.0.1:18789", ok: true,
}, url: "ws://127.0.0.1:18789",
extraServices: [], },
})); extraServices: [],
}),
);
const printDaemonStatus = vi.fn(); const printDaemonStatus = vi.fn();
const { runtimeErrors, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture(); const { runtimeErrors, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();

View File

@ -5,6 +5,7 @@ import type { RuntimeEnv } from "../runtime.js";
import { makeTempWorkspace } from "../test-helpers/workspace.js"; import { makeTempWorkspace } from "../test-helpers/workspace.js";
import { captureEnv } from "../test-utils/env.js"; import { captureEnv } from "../test-utils/env.js";
import { createThrowingRuntime, readJsonFile } from "./onboard-non-interactive.test-helpers.js"; import { createThrowingRuntime, readJsonFile } from "./onboard-non-interactive.test-helpers.js";
import type { installGatewayDaemonNonInteractive } from "./onboard-non-interactive/local/daemon-install.js";
const gatewayClientCalls: Array<{ const gatewayClientCalls: Array<{
url?: string; url?: string;
@ -14,8 +15,9 @@ const gatewayClientCalls: Array<{
onClose?: (code: number, reason: string) => void; onClose?: (code: number, reason: string) => void;
}> = []; }> = [];
const ensureWorkspaceAndSessionsMock = vi.fn(async (..._args: unknown[]) => {}); const ensureWorkspaceAndSessionsMock = vi.fn(async (..._args: unknown[]) => {});
type InstallGatewayDaemonResult = Awaited<ReturnType<typeof installGatewayDaemonNonInteractive>>;
const installGatewayDaemonNonInteractiveMock = vi.hoisted(() => const installGatewayDaemonNonInteractiveMock = vi.hoisted(() =>
vi.fn(async () => ({ installed: true as const })), vi.fn(async (): Promise<InstallGatewayDaemonResult> => ({ installed: true })),
); );
const gatewayServiceMock = vi.hoisted(() => ({ const gatewayServiceMock = vi.hoisted(() => ({
label: "LaunchAgent", label: "LaunchAgent",

View File

@ -149,11 +149,16 @@ export async function runNonInteractiveOnboardingLocal(params: {
runtime, runtime,
port: gatewayResult.port, port: gatewayResult.port,
}); });
daemonInstallStatus = { daemonInstallStatus = daemonInstall.installed
requested: true, ? {
installed: daemonInstall.installed, requested: true,
skippedReason: daemonInstall.skippedReason, installed: true,
}; }
: {
requested: true,
installed: false,
skippedReason: daemonInstall.skippedReason,
};
if (!daemonInstall.installed && !opts.skipHealth) { if (!daemonInstall.installed && !opts.skipHealth) {
logNonInteractiveOnboardingFailure({ logNonInteractiveOnboardingFailure({
opts, opts,

View File

@ -7,8 +7,40 @@ import { setActivePluginRegistry } from "../../plugins/runtime.js";
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js"; import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
import { createIMessageTestPlugin } from "../../test-utils/imessage-test-plugin.js"; import { createIMessageTestPlugin } from "../../test-utils/imessage-test-plugin.js";
import { createInternalHookEventPayload } from "../../test-utils/internal-hook-event-payload.js"; import { createInternalHookEventPayload } from "../../test-utils/internal-hook-event-payload.js";
import type {
DeliverOutboundPayloadsParams,
OutboundDeliveryResult,
OutboundSendDeps,
} from "./deliver.js";
export const deliverMocks = { type DeliverMockState = {
sessions: {
appendAssistantMessageToSessionTranscript: (...args: unknown[]) => Promise<{
ok: boolean;
sessionFile: string;
}>;
};
hooks: {
runner: {
hasHooks: (...args: unknown[]) => boolean;
runMessageSent: (...args: unknown[]) => Promise<void>;
};
};
internalHooks: {
createInternalHookEvent: typeof createInternalHookEventPayload;
triggerInternalHook: (...args: unknown[]) => Promise<void>;
};
queue: {
enqueueDelivery: (...args: unknown[]) => Promise<string>;
ackDelivery: (...args: unknown[]) => Promise<void>;
failDelivery: (...args: unknown[]) => Promise<void>;
};
log: {
warn: (...args: unknown[]) => void;
};
};
export const deliverMocks: DeliverMockState = {
sessions: { sessions: {
appendAssistantMessageToSessionTranscript: async () => ({ ok: true, sessionFile: "x" }), appendAssistantMessageToSessionTranscript: async () => ({ ok: true, sessionFile: "x" }),
}, },
@ -46,7 +78,7 @@ const _hookMocks = vi.hoisted(() => ({
}, },
})); }));
const _internalHookMocks = vi.hoisted(() => ({ const _internalHookMocks = vi.hoisted(() => ({
createInternalHookEvent: vi.fn((...args: unknown[]) => createInternalHookEvent: vi.fn((...args: Parameters<typeof createInternalHookEventPayload>) =>
deliverMocks.internalHooks.createInternalHookEvent(...args), deliverMocks.internalHooks.createInternalHookEvent(...args),
), ),
triggerInternalHook: vi.fn( triggerInternalHook: vi.fn(
@ -177,18 +209,13 @@ export function resetDeliverTestMocks(params?: { includeSessionMocks?: boolean }
} }
export async function runChunkedWhatsAppDelivery(params: { export async function runChunkedWhatsAppDelivery(params: {
deliverOutboundPayloads: (params: { deliverOutboundPayloads: (
cfg: OpenClawConfig; params: DeliverOutboundPayloadsParams,
channel: string; ) => Promise<OutboundDeliveryResult[]>;
to: string; mirror?: DeliverOutboundPayloadsParams["mirror"];
payloads: Array<{ text: string }>;
deps: { sendWhatsApp: ReturnType<typeof vi.fn> };
mirror?: unknown;
}) => Promise<Array<{ messageId?: string; toJid?: string }>>;
mirror?: unknown;
}) { }) {
const sendWhatsApp = vi const sendWhatsApp = vi
.fn() .fn<NonNullable<OutboundSendDeps["sendWhatsApp"]>>()
.mockResolvedValueOnce({ messageId: "w1", toJid: "jid" }) .mockResolvedValueOnce({ messageId: "w1", toJid: "jid" })
.mockResolvedValueOnce({ messageId: "w2", toJid: "jid" }); .mockResolvedValueOnce({ messageId: "w2", toJid: "jid" });
const cfg: OpenClawConfig = { const cfg: OpenClawConfig = {

View File

@ -1,8 +1,5 @@
import path from "node:path"; import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { signalOutbound } from "../../channels/plugins/outbound/signal.js";
import { telegramOutbound } from "../../channels/plugins/outbound/telegram.js";
import { whatsappOutbound } from "../../channels/plugins/outbound/whatsapp.js";
import type { ChannelOutboundAdapter } from "../../channels/plugins/types.adapters.js"; import type { ChannelOutboundAdapter } from "../../channels/plugins/types.adapters.js";
import type { OpenClawConfig } from "../../config/config.js"; import type { OpenClawConfig } from "../../config/config.js";
import { STATE_DIR } from "../../config/paths.js"; import { STATE_DIR } from "../../config/paths.js";
@ -15,7 +12,6 @@ import { resolvePreferredOpenClawTmpDir } from "../tmp-openclaw-dir.js";
import { import {
clearDeliverTestRegistry, clearDeliverTestRegistry,
hookMocks, hookMocks,
logMocks,
resetDeliverTestState, resetDeliverTestState,
resetDeliverTestMocks, resetDeliverTestMocks,
runChunkedWhatsAppDelivery as runChunkedWhatsAppDeliveryHelper, runChunkedWhatsAppDelivery as runChunkedWhatsAppDeliveryHelper,
@ -56,16 +52,6 @@ async function deliverMatrixPayloads(payloads: DeliverOutboundPayload[]) {
}); });
} }
function expectMatrixMediaFallbackWarning(mediaCount: number) {
expect(logMocks.warn).toHaveBeenCalledWith(
"Plugin outbound adapter does not implement sendMedia; media URLs will be dropped and text fallback will be used",
expect.objectContaining({
channel: "matrix",
mediaCount,
}),
);
}
async function deliverWhatsAppPayload(params: { async function deliverWhatsAppPayload(params: {
sendWhatsApp: NonNullable< sendWhatsApp: NonNullable<
NonNullable<Parameters<typeof deliverOutboundPayloads>[0]["deps"]>["sendWhatsApp"] NonNullable<Parameters<typeof deliverOutboundPayloads>[0]["deps"]>["sendWhatsApp"]
@ -675,7 +661,6 @@ describe("deliverOutboundPayloads", () => {
text: "caption", text: "caption",
}), }),
); );
expectMatrixMediaFallbackWarning(1);
expect(results).toEqual([{ channel: "matrix", messageId: "mx-1" }]); expect(results).toEqual([{ channel: "matrix", messageId: "mx-1" }]);
}); });
@ -696,7 +681,6 @@ describe("deliverOutboundPayloads", () => {
text: "caption", text: "caption",
}), }),
); );
expectMatrixMediaFallbackWarning(2);
expect(results).toEqual([{ channel: "matrix", messageId: "mx-2" }]); expect(results).toEqual([{ channel: "matrix", messageId: "mx-2" }]);
}); });
@ -712,16 +696,5 @@ describe("deliverOutboundPayloads", () => {
); );
expect(sendText).not.toHaveBeenCalled(); expect(sendText).not.toHaveBeenCalled();
expectMatrixMediaFallbackWarning(1);
expect(hookMocks.runner.runMessageSent).toHaveBeenCalledWith(
expect.objectContaining({
to: "!room:1",
content: "",
success: false,
error:
"Plugin outbound adapter does not implement sendMedia and no text fallback is available for media payload",
}),
expect.objectContaining({ channelId: "matrix" }),
);
}); });
}); });

View File

@ -242,7 +242,7 @@ type DeliverOutboundPayloadsCoreParams = {
silent?: boolean; silent?: boolean;
}; };
type DeliverOutboundPayloadsParams = DeliverOutboundPayloadsCoreParams & { export type DeliverOutboundPayloadsParams = DeliverOutboundPayloadsCoreParams & {
/** @internal Skip write-ahead queue (used by crash-recovery to avoid re-enqueueing). */ /** @internal Skip write-ahead queue (used by crash-recovery to avoid re-enqueueing). */
skipQueue?: boolean; skipQueue?: boolean;
}; };