mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-17 08:50:47 +00:00
Merged via squash.
Prepared head SHA: b7c20a7398
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
214 lines
6.7 KiB
TypeScript
214 lines
6.7 KiB
TypeScript
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { type ChannelId, type ChannelPlugin } from "../channels/plugins/types.js";
|
|
import {
|
|
createSubsystemLogger,
|
|
runtimeForLogger,
|
|
type SubsystemLogger,
|
|
} from "../logging/subsystem.js";
|
|
import { createEmptyPluginRegistry, type PluginRegistry } from "../plugins/registry.js";
|
|
import { getActivePluginRegistry, setActivePluginRegistry } from "../plugins/runtime.js";
|
|
import { createRuntimeChannel } from "../plugins/runtime/runtime-channel.js";
|
|
import type { PluginRuntime } from "../plugins/runtime/types.js";
|
|
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
|
|
const hoisted = vi.hoisted(() => ({
|
|
startChannelApprovalHandlerBootstrap: vi.fn(async () => async () => {}),
|
|
}));
|
|
|
|
vi.mock("../infra/approval-handler-bootstrap.js", () => ({
|
|
startChannelApprovalHandlerBootstrap: hoisted.startChannelApprovalHandlerBootstrap,
|
|
}));
|
|
|
|
function createDeferred() {
|
|
let resolvePromise = () => {};
|
|
const promise = new Promise<void>((resolve) => {
|
|
resolvePromise = resolve;
|
|
});
|
|
return { promise, resolve: resolvePromise };
|
|
}
|
|
|
|
function createTestPlugin(params: {
|
|
startAccount: NonNullable<NonNullable<ChannelPlugin["gateway"]>["startAccount"]>;
|
|
}): ChannelPlugin {
|
|
return {
|
|
id: "discord",
|
|
meta: {
|
|
id: "discord",
|
|
label: "Discord",
|
|
selectionLabel: "Discord",
|
|
docsPath: "/channels/discord",
|
|
blurb: "test stub",
|
|
},
|
|
capabilities: { chatTypes: ["direct"] },
|
|
config: {
|
|
listAccountIds: () => [DEFAULT_ACCOUNT_ID],
|
|
resolveAccount: () => ({ enabled: true, configured: true }),
|
|
isEnabled: () => true,
|
|
describeAccount: () => ({
|
|
accountId: DEFAULT_ACCOUNT_ID,
|
|
enabled: true,
|
|
configured: true,
|
|
}),
|
|
},
|
|
approvalCapability: {
|
|
nativeRuntime: {
|
|
availability: {
|
|
isConfigured: vi.fn().mockReturnValue(true),
|
|
shouldHandle: vi.fn().mockReturnValue(true),
|
|
},
|
|
presentation: {
|
|
buildPendingPayload: vi.fn(),
|
|
buildResolvedResult: vi.fn(),
|
|
buildExpiredResult: vi.fn(),
|
|
},
|
|
transport: {
|
|
prepareTarget: vi.fn(),
|
|
deliverPending: vi.fn(),
|
|
},
|
|
},
|
|
},
|
|
gateway: {
|
|
startAccount: params.startAccount,
|
|
},
|
|
};
|
|
}
|
|
|
|
function installTestRegistry(plugin: ChannelPlugin) {
|
|
const registry = createEmptyPluginRegistry();
|
|
registry.channels.push({
|
|
pluginId: plugin.id,
|
|
source: "test",
|
|
plugin,
|
|
});
|
|
setActivePluginRegistry(registry);
|
|
}
|
|
|
|
function createManager(
|
|
createChannelManager: typeof import("./server-channels.js").createChannelManager,
|
|
options?: {
|
|
channelRuntime?: PluginRuntime["channel"];
|
|
},
|
|
) {
|
|
const log = createSubsystemLogger("gateway/server-channels-approval-bootstrap-test");
|
|
const channelLogs = { discord: log } as Record<ChannelId, SubsystemLogger>;
|
|
const runtime = runtimeForLogger(log);
|
|
const channelRuntimeEnvs = { discord: runtime } as unknown as Record<ChannelId, RuntimeEnv>;
|
|
return createChannelManager({
|
|
loadConfig: () => ({}),
|
|
channelLogs,
|
|
channelRuntimeEnvs,
|
|
...(options?.channelRuntime ? { channelRuntime: options.channelRuntime } : {}),
|
|
});
|
|
}
|
|
|
|
describe("server-channels approval bootstrap", () => {
|
|
let previousRegistry: PluginRegistry | null = null;
|
|
let createChannelManager: typeof import("./server-channels.js").createChannelManager;
|
|
|
|
beforeAll(async () => {
|
|
({ createChannelManager } = await import("./server-channels.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
previousRegistry = getActivePluginRegistry();
|
|
hoisted.startChannelApprovalHandlerBootstrap.mockReset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
setActivePluginRegistry(previousRegistry ?? createEmptyPluginRegistry());
|
|
});
|
|
|
|
it("starts and stops the shared approval bootstrap with the channel lifecycle", async () => {
|
|
const channelRuntime = createRuntimeChannel();
|
|
const stopApprovalBootstrap = vi.fn(async () => {});
|
|
hoisted.startChannelApprovalHandlerBootstrap.mockResolvedValue(stopApprovalBootstrap);
|
|
|
|
const started = createDeferred();
|
|
const stopped = createDeferred();
|
|
const startAccount = vi.fn(
|
|
async ({
|
|
abortSignal,
|
|
channelRuntime,
|
|
}: Parameters<NonNullable<NonNullable<ChannelPlugin["gateway"]>["startAccount"]>>[0]) => {
|
|
channelRuntime?.runtimeContexts.register({
|
|
channelId: "discord",
|
|
accountId: DEFAULT_ACCOUNT_ID,
|
|
capability: "approval.native",
|
|
context: { token: "tracked" },
|
|
});
|
|
started.resolve();
|
|
await new Promise<void>((resolve) => {
|
|
abortSignal.addEventListener(
|
|
"abort",
|
|
() => {
|
|
stopped.resolve();
|
|
resolve();
|
|
},
|
|
{ once: true },
|
|
);
|
|
});
|
|
},
|
|
);
|
|
|
|
installTestRegistry(createTestPlugin({ startAccount }));
|
|
const manager = createManager(createChannelManager, { channelRuntime });
|
|
|
|
await manager.startChannels();
|
|
await started.promise;
|
|
|
|
expect(hoisted.startChannelApprovalHandlerBootstrap).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
plugin: expect.objectContaining({ id: "discord" }),
|
|
cfg: {},
|
|
accountId: DEFAULT_ACCOUNT_ID,
|
|
channelRuntime: expect.objectContaining({
|
|
runtimeContexts: expect.any(Object),
|
|
}),
|
|
}),
|
|
);
|
|
expect(
|
|
channelRuntime.runtimeContexts.get({
|
|
channelId: "discord",
|
|
accountId: DEFAULT_ACCOUNT_ID,
|
|
capability: "approval.native",
|
|
}),
|
|
).toEqual({ token: "tracked" });
|
|
|
|
await manager.stopChannel("discord", DEFAULT_ACCOUNT_ID);
|
|
await stopped.promise;
|
|
|
|
expect(stopApprovalBootstrap).toHaveBeenCalledTimes(1);
|
|
expect(
|
|
channelRuntime.runtimeContexts.get({
|
|
channelId: "discord",
|
|
accountId: DEFAULT_ACCOUNT_ID,
|
|
capability: "approval.native",
|
|
}),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it("keeps the account stopped when approval bootstrap startup fails", async () => {
|
|
const channelRuntime = createRuntimeChannel();
|
|
const startAccount = vi.fn(async () => {});
|
|
hoisted.startChannelApprovalHandlerBootstrap.mockRejectedValue(new Error("boom"));
|
|
|
|
installTestRegistry(createTestPlugin({ startAccount }));
|
|
const manager = createManager(createChannelManager, { channelRuntime });
|
|
|
|
await manager.startChannels();
|
|
|
|
expect(startAccount).not.toHaveBeenCalled();
|
|
const accountSnapshot =
|
|
manager.getRuntimeSnapshot().channelAccounts.discord?.[DEFAULT_ACCOUNT_ID];
|
|
expect(accountSnapshot).toEqual(
|
|
expect.objectContaining({
|
|
accountId: DEFAULT_ACCOUNT_ID,
|
|
running: false,
|
|
restartPending: false,
|
|
lastError: "boom",
|
|
}),
|
|
);
|
|
});
|
|
});
|