mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-13 11:00:50 +00:00
Fix type-test harness issues from session routing and mock typing
This commit is contained in:
@@ -44,12 +44,12 @@ function createMergeConfigProvider() {
|
||||
return {
|
||||
baseUrl: "https://config.example/v1",
|
||||
apiKey: "CONFIG_KEY",
|
||||
api: "openai-responses",
|
||||
api: "openai-responses" as const,
|
||||
models: [
|
||||
{
|
||||
id: "config-model",
|
||||
name: "Config model",
|
||||
input: ["text"],
|
||||
input: ["text"] as Array<"text" | "image">,
|
||||
reasoning: false,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 8192,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { MockInstance } from "vitest";
|
||||
import { createTypingCallbacks } from "./typing.js";
|
||||
|
||||
type TypingCallbackOverrides = Partial<Parameters<typeof createTypingCallbacks>[0]>;
|
||||
type TypingHarnessStart = ReturnType<typeof vi.fn<() => Promise<void>>>;
|
||||
type TypingHarnessError = ReturnType<typeof vi.fn<(err: unknown) => void>>;
|
||||
|
||||
const flushMicrotasks = async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
@@ -16,17 +19,25 @@ async function withFakeTimers(run: () => Promise<void>) {
|
||||
}
|
||||
}
|
||||
|
||||
function createTypingHarness(overrides: Partial<Parameters<typeof createTypingCallbacks>[0]> = {}) {
|
||||
const start = (overrides.start ?? vi.fn().mockResolvedValue(undefined)) as MockInstance<
|
||||
[],
|
||||
Promise<void>
|
||||
>;
|
||||
const stop = (overrides.stop ?? vi.fn().mockResolvedValue(undefined)) as MockInstance<
|
||||
[],
|
||||
Promise<void>
|
||||
>;
|
||||
const onStartError = (overrides.onStartError ?? vi.fn()) as MockInstance<[unknown], void>;
|
||||
const onStopError = (overrides.onStopError ?? vi.fn()) as MockInstance<[unknown], void>;
|
||||
function createTypingHarness(overrides: TypingCallbackOverrides = {}) {
|
||||
const start: TypingHarnessStart = vi.fn<() => Promise<void>>(async () => {});
|
||||
const stop: TypingHarnessStart = vi.fn<() => Promise<void>>(async () => {});
|
||||
const onStartError: TypingHarnessError = vi.fn<(err: unknown) => void>();
|
||||
const onStopError: TypingHarnessError = vi.fn<(err: unknown) => void>();
|
||||
|
||||
if (overrides.start) {
|
||||
start.mockImplementation(overrides.start);
|
||||
}
|
||||
if (overrides.stop) {
|
||||
stop.mockImplementation(overrides.stop);
|
||||
}
|
||||
if (overrides.onStartError) {
|
||||
onStartError.mockImplementation(overrides.onStartError);
|
||||
}
|
||||
if (overrides.onStopError) {
|
||||
onStopError.mockImplementation(overrides.onStopError);
|
||||
}
|
||||
|
||||
const callbacks = createTypingCallbacks({
|
||||
start,
|
||||
stop,
|
||||
|
||||
@@ -10,6 +10,20 @@ import type { ChannelChoice } from "./onboard-types.js";
|
||||
import { getChannelOnboardingAdapter } from "./onboarding/registry.js";
|
||||
import type { ChannelOnboardingAdapter } from "./onboarding/types.js";
|
||||
|
||||
type ChannelOnboardingAdapterPatch = Partial<
|
||||
Pick<
|
||||
ChannelOnboardingAdapter,
|
||||
"configure" | "configureInteractive" | "configureWhenConfigured" | "getStatus"
|
||||
>
|
||||
>;
|
||||
|
||||
type PatchedOnboardingAdapterFields = {
|
||||
configure?: ChannelOnboardingAdapter["configure"];
|
||||
configureInteractive?: ChannelOnboardingAdapter["configureInteractive"];
|
||||
configureWhenConfigured?: ChannelOnboardingAdapter["configureWhenConfigured"];
|
||||
getStatus?: ChannelOnboardingAdapter["getStatus"];
|
||||
};
|
||||
|
||||
export function setDefaultChannelPluginRegistryForTests(): void {
|
||||
const channels = [
|
||||
{ pluginId: "discord", plugin: discordPlugin, source: "test" },
|
||||
@@ -24,21 +38,44 @@ export function setDefaultChannelPluginRegistryForTests(): void {
|
||||
|
||||
export function patchChannelOnboardingAdapter(
|
||||
channel: ChannelChoice,
|
||||
patch: Partial<ChannelOnboardingAdapter>,
|
||||
patch: ChannelOnboardingAdapterPatch,
|
||||
): () => void {
|
||||
const adapter = getChannelOnboardingAdapter(channel);
|
||||
if (!adapter) {
|
||||
throw new Error(`missing onboarding adapter for ${channel}`);
|
||||
}
|
||||
const keys = Object.keys(patch) as Array<keyof ChannelOnboardingAdapter>;
|
||||
const previous: Partial<ChannelOnboardingAdapter> = {};
|
||||
for (const key of keys) {
|
||||
previous[key] = adapter[key];
|
||||
adapter[key] = patch[key] as ChannelOnboardingAdapter[typeof key];
|
||||
|
||||
const previous: PatchedOnboardingAdapterFields = {};
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "getStatus")) {
|
||||
previous.getStatus = adapter.getStatus;
|
||||
adapter.getStatus = patch.getStatus ?? adapter.getStatus;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "configure")) {
|
||||
previous.configure = adapter.configure;
|
||||
adapter.configure = patch.configure ?? adapter.configure;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "configureInteractive")) {
|
||||
previous.configureInteractive = adapter.configureInteractive;
|
||||
adapter.configureInteractive = patch.configureInteractive;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "configureWhenConfigured")) {
|
||||
previous.configureWhenConfigured = adapter.configureWhenConfigured;
|
||||
adapter.configureWhenConfigured = patch.configureWhenConfigured;
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const key of keys) {
|
||||
adapter[key] = previous[key] as ChannelOnboardingAdapter[typeof key];
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "getStatus")) {
|
||||
adapter.getStatus = previous.getStatus!;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "configure")) {
|
||||
adapter.configure = previous.configure!;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "configureInteractive")) {
|
||||
adapter.configureInteractive = previous.configureInteractive;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "configureWhenConfigured")) {
|
||||
adapter.configureWhenConfigured = previous.configureWhenConfigured;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { CONTEXT_WINDOW_HARD_MIN_TOKENS } from "../agents/context-window-guard.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import {
|
||||
applyCustomApiConfig,
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, type Mock, vi } from "vitest";
|
||||
import { saveExecApprovals } from "../infra/exec-approvals.js";
|
||||
import type { ExecHostResponse } from "../infra/exec-host.js";
|
||||
import { handleSystemRunInvoke, formatSystemRunAllowlistMissMessage } from "./invoke-system-run.js";
|
||||
import type { HandleSystemRunInvokeOptions } from "./invoke-system-run.js";
|
||||
|
||||
type MockedRunCommand = Mock<HandleSystemRunInvokeOptions["runCommand"]>;
|
||||
type MockedRunViaMacAppExecHost = Mock<HandleSystemRunInvokeOptions["runViaMacAppExecHost"]>;
|
||||
type MockedSendInvokeResult = Mock<HandleSystemRunInvokeOptions["sendInvokeResult"]>;
|
||||
type MockedSendExecFinishedEvent = Mock<HandleSystemRunInvokeOptions["sendExecFinishedEvent"]>;
|
||||
type MockedSendNodeEvent = Mock<HandleSystemRunInvokeOptions["sendNodeEvent"]>;
|
||||
|
||||
describe("formatSystemRunAllowlistMissMessage", () => {
|
||||
it("returns legacy allowlist miss message by default", () => {
|
||||
expect(formatSystemRunAllowlistMissMessage()).toBe("SYSTEM_RUN_DENIED: allowlist miss");
|
||||
@@ -35,7 +41,7 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
||||
}
|
||||
|
||||
function expectInvokeOk(
|
||||
sendInvokeResult: ReturnType<typeof vi.fn>,
|
||||
sendInvokeResult: MockedSendInvokeResult,
|
||||
params?: { payloadContains?: string },
|
||||
) {
|
||||
expect(sendInvokeResult).toHaveBeenCalledWith(
|
||||
@@ -49,7 +55,7 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
||||
}
|
||||
|
||||
function expectInvokeErrorMessage(
|
||||
sendInvokeResult: ReturnType<typeof vi.fn>,
|
||||
sendInvokeResult: MockedSendInvokeResult,
|
||||
params: { message: string; exact?: boolean },
|
||||
) {
|
||||
expect(sendInvokeResult).toHaveBeenCalledWith(
|
||||
@@ -63,8 +69,8 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
||||
}
|
||||
|
||||
function expectApprovalRequiredDenied(params: {
|
||||
sendNodeEvent: ReturnType<typeof vi.fn>;
|
||||
sendInvokeResult: ReturnType<typeof vi.fn>;
|
||||
sendNodeEvent: MockedSendNodeEvent;
|
||||
sendInvokeResult: MockedSendInvokeResult;
|
||||
}) {
|
||||
expect(params.sendNodeEvent).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
@@ -126,7 +132,7 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
||||
}
|
||||
|
||||
function expectCommandPinnedToCanonicalPath(params: {
|
||||
runCommand: ReturnType<typeof vi.fn>;
|
||||
runCommand: MockedRunCommand;
|
||||
expected: string;
|
||||
commandTail: string[];
|
||||
cwd?: string;
|
||||
@@ -153,24 +159,44 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
||||
sendExecFinishedEvent?: HandleSystemRunInvokeOptions["sendExecFinishedEvent"];
|
||||
sendNodeEvent?: HandleSystemRunInvokeOptions["sendNodeEvent"];
|
||||
skillBinsCurrent?: () => Promise<Array<{ name: string; resolvedPath: string }>>;
|
||||
}) {
|
||||
const runCommand =
|
||||
params.runCommand ??
|
||||
(vi.fn(async (_command: string[], _cwd?: string, _env?: Record<string, string>) =>
|
||||
createLocalRunResult(),
|
||||
) as HandleSystemRunInvokeOptions["runCommand"]);
|
||||
const runViaMacAppExecHost =
|
||||
params.runViaMacAppExecHost ??
|
||||
(vi.fn(async () => params.runViaResponse ?? null) as HandleSystemRunInvokeOptions["runViaMacAppExecHost"]);
|
||||
const sendInvokeResult =
|
||||
params.sendInvokeResult ??
|
||||
(vi.fn(async () => {}) as HandleSystemRunInvokeOptions["sendInvokeResult"]);
|
||||
const sendExecFinishedEvent =
|
||||
params.sendExecFinishedEvent ??
|
||||
(vi.fn(async () => {}) as HandleSystemRunInvokeOptions["sendExecFinishedEvent"]);
|
||||
const sendNodeEvent =
|
||||
params.sendNodeEvent ??
|
||||
(vi.fn(async () => {}) as HandleSystemRunInvokeOptions["sendNodeEvent"]);
|
||||
}): Promise<{
|
||||
runCommand: MockedRunCommand;
|
||||
runViaMacAppExecHost: MockedRunViaMacAppExecHost;
|
||||
sendInvokeResult: MockedSendInvokeResult;
|
||||
sendNodeEvent: MockedSendNodeEvent;
|
||||
sendExecFinishedEvent: MockedSendExecFinishedEvent;
|
||||
}> {
|
||||
const runCommand: MockedRunCommand = vi.fn<HandleSystemRunInvokeOptions["runCommand"]>(
|
||||
async () => createLocalRunResult(),
|
||||
);
|
||||
const runViaMacAppExecHost: MockedRunViaMacAppExecHost = vi.fn<
|
||||
HandleSystemRunInvokeOptions["runViaMacAppExecHost"]
|
||||
>(async () => params.runViaResponse ?? null);
|
||||
const sendInvokeResult: MockedSendInvokeResult = vi.fn<
|
||||
HandleSystemRunInvokeOptions["sendInvokeResult"]
|
||||
>(async () => {});
|
||||
const sendNodeEvent: MockedSendNodeEvent = vi.fn<HandleSystemRunInvokeOptions["sendNodeEvent"]>(
|
||||
async () => {},
|
||||
);
|
||||
const sendExecFinishedEvent: MockedSendExecFinishedEvent = vi.fn<
|
||||
HandleSystemRunInvokeOptions["sendExecFinishedEvent"]
|
||||
>(async () => {});
|
||||
|
||||
if (params.runCommand !== undefined) {
|
||||
runCommand.mockImplementation(params.runCommand);
|
||||
}
|
||||
if (params.runViaMacAppExecHost !== undefined) {
|
||||
runViaMacAppExecHost.mockImplementation(params.runViaMacAppExecHost);
|
||||
}
|
||||
if (params.sendInvokeResult !== undefined) {
|
||||
sendInvokeResult.mockImplementation(params.sendInvokeResult);
|
||||
}
|
||||
if (params.sendNodeEvent !== undefined) {
|
||||
sendNodeEvent.mockImplementation(params.sendNodeEvent);
|
||||
}
|
||||
if (params.sendExecFinishedEvent !== undefined) {
|
||||
sendExecFinishedEvent.mockImplementation(params.sendExecFinishedEvent);
|
||||
}
|
||||
|
||||
await handleSystemRunInvoke({
|
||||
client: {} as never,
|
||||
@@ -198,7 +224,13 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
||||
preferMacAppExecHost: params.preferMacAppExecHost,
|
||||
});
|
||||
|
||||
return { runCommand, runViaMacAppExecHost, sendInvokeResult, sendExecFinishedEvent };
|
||||
return {
|
||||
runCommand,
|
||||
runViaMacAppExecHost,
|
||||
sendInvokeResult,
|
||||
sendNodeEvent,
|
||||
sendExecFinishedEvent,
|
||||
};
|
||||
}
|
||||
|
||||
it("uses local execution by default when mac app exec host preference is disabled", async () => {
|
||||
|
||||
@@ -76,16 +76,30 @@ async function runPinCase(input: PinCase = {}): Promise<void> {
|
||||
|
||||
describe("registerSlackPinEvents", () => {
|
||||
const cases: Array<{ name: string; args: PinCase; expectedCalls: number }> = [
|
||||
{ name: "enqueues DM pin system events when dmPolicy is open", args: { overrides: { dmPolicy: "open" } }, expectedCalls: 1 },
|
||||
{ name: "blocks DM pin system events when dmPolicy is disabled", args: { overrides: { dmPolicy: "disabled" } }, expectedCalls: 0 },
|
||||
{
|
||||
name: "enqueues DM pin system events when dmPolicy is open",
|
||||
args: { overrides: { dmPolicy: "open" } },
|
||||
expectedCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "blocks DM pin system events when dmPolicy is disabled",
|
||||
args: { overrides: { dmPolicy: "disabled" } },
|
||||
expectedCalls: 0,
|
||||
},
|
||||
{
|
||||
name: "blocks DM pin system events for unauthorized senders in allowlist mode",
|
||||
args: { overrides: { dmPolicy: "allowlist", allowFrom: ["U2"] }, event: makePinEvent({ user: "U1" }) },
|
||||
args: {
|
||||
overrides: { dmPolicy: "allowlist", allowFrom: ["U2"] },
|
||||
event: makePinEvent({ user: "U1" }),
|
||||
},
|
||||
expectedCalls: 0,
|
||||
},
|
||||
{
|
||||
name: "allows DM pin system events for authorized senders in allowlist mode",
|
||||
args: { overrides: { dmPolicy: "allowlist", allowFrom: ["U1"] }, event: makePinEvent({ user: "U1" }) },
|
||||
args: {
|
||||
overrides: { dmPolicy: "allowlist", allowFrom: ["U1"] },
|
||||
event: makePinEvent({ user: "U1" }),
|
||||
},
|
||||
expectedCalls: 1,
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user