mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 18:01:46 +00:00
fix(test): reduce channel suite startup hotspots
This commit is contained in:
@@ -12,7 +12,7 @@ import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { buildPluginBindingApprovalCustomId } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { peekSystemEvents, resetSystemEventsForTest } from "../../../../src/infra/system-events.js";
|
||||
import { peekSystemEvents, resetSystemEventsForTest } from "../../../../src/infra/system-events.ts";
|
||||
import {
|
||||
clearDiscordComponentEntries,
|
||||
registerDiscordComponentEntries,
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
|
||||
const readAllowFromStoreMock = vi.hoisted(() => vi.fn());
|
||||
const upsertPairingRequestMock = vi.hoisted(() => vi.fn());
|
||||
const enqueueSystemEventMock = vi.hoisted(() => vi.fn());
|
||||
const dispatchReplyMock = vi.hoisted(() => vi.fn());
|
||||
const recordInboundSessionMock = vi.hoisted(() => vi.fn());
|
||||
const readSessionUpdatedAtMock = vi.hoisted(() => vi.fn());
|
||||
@@ -87,6 +88,34 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
resolvePluginConversationBindingApprovalMock(...args),
|
||||
buildPluginBindingResolvedText: (...args: unknown[]) =>
|
||||
buildPluginBindingResolvedTextMock(...args),
|
||||
recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args),
|
||||
};
|
||||
});
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
|
||||
resolvePluginConversationBindingApproval: (...args: unknown[]) =>
|
||||
resolvePluginConversationBindingApprovalMock(...args),
|
||||
buildPluginBindingResolvedText: (...args: unknown[]) =>
|
||||
buildPluginBindingResolvedTextMock(...args),
|
||||
recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/infra-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
|
||||
};
|
||||
});
|
||||
vi.mock("openclaw/plugin-sdk/infra-runtime.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/infra-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -111,14 +140,6 @@ vi.mock("../../../../src/auto-reply/reply/provider-dispatcher.js", async (import
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
return {
|
||||
@@ -138,14 +159,15 @@ vi.mock("openclaw/plugin-sdk/plugin-runtime", async (importOriginal) => {
|
||||
});
|
||||
|
||||
describe("agent components", () => {
|
||||
const createCfg = (): OpenClawConfig => ({}) as OpenClawConfig;
|
||||
const dmSessionKey = buildAgentSessionKey({
|
||||
const defaultDmSessionKey = buildAgentSessionKey({
|
||||
agentId: "main",
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
peer: { kind: "direct", id: "123456789" },
|
||||
});
|
||||
|
||||
const createCfg = (): OpenClawConfig => ({}) as OpenClawConfig;
|
||||
|
||||
const createBaseDmInteraction = (overrides: Record<string, unknown> = {}) => {
|
||||
const reply = vi.fn().mockResolvedValue(undefined);
|
||||
const defer = vi.fn().mockResolvedValue(undefined);
|
||||
@@ -203,11 +225,9 @@ describe("agent components", () => {
|
||||
const pairingText = String(reply.mock.calls[0]?.[0]?.content ?? "");
|
||||
expect(pairingText).toContain("Pairing code:");
|
||||
const code = pairingText.match(/Pairing code:\s*([A-Z2-9]{8})/)?.[1];
|
||||
if (!code) {
|
||||
throw new Error(`pairing reply did not include an 8-character pairing code: ${pairingText}`);
|
||||
}
|
||||
expect(code).toBeDefined();
|
||||
expect(pairingText).toContain(`openclaw pairing approve discord ${code}`);
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
|
||||
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]);
|
||||
expect(readAllowFromStoreMock).toHaveBeenCalledWith("discord", "default");
|
||||
});
|
||||
|
||||
@@ -226,7 +246,7 @@ describe("agent components", () => {
|
||||
content: "You are not authorized to use this button.",
|
||||
ephemeral: true,
|
||||
});
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
|
||||
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -243,7 +263,7 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
|
||||
"[Discord component: hello clicked by Alice#1234 (123456789)]",
|
||||
]);
|
||||
expect(upsertPairingRequestMock).not.toHaveBeenCalled();
|
||||
@@ -263,7 +283,7 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
|
||||
"[Discord component: hello clicked by Alice#1234 (123456789)]",
|
||||
]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
@@ -285,7 +305,7 @@ describe("agent components", () => {
|
||||
content: "DM interactions are disabled.",
|
||||
ephemeral: true,
|
||||
});
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
|
||||
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -303,7 +323,7 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
|
||||
"[Discord select menu: hello interacted by Alice#1234 (123456789) (selected: alpha)]",
|
||||
]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
@@ -322,7 +342,7 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
|
||||
"[Discord component: hello_cid clicked by Alice#1234 (123456789)]",
|
||||
]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
@@ -341,7 +361,7 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
|
||||
"[Discord component: hello%2G clicked by Alice#1234 (123456789)]",
|
||||
]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
@@ -502,7 +522,7 @@ describe("discord component interactions", () => {
|
||||
lastDispatchCtx = undefined;
|
||||
readAllowFromStoreMock.mockClear().mockResolvedValue([]);
|
||||
upsertPairingRequestMock.mockClear().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
resetSystemEventsForTest();
|
||||
enqueueSystemEventMock.mockClear();
|
||||
dispatchReplyMock.mockClear().mockImplementation(async (params: DispatchParams) => {
|
||||
lastDispatchCtx = params.ctx;
|
||||
await params.dispatcherOptions.deliver({ text: "ok" });
|
||||
|
||||
209
extensions/signal/src/monitor.tool-result.autostart.test.ts
Normal file
209
extensions/signal/src/monitor.tool-result.autostart.test.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { SignalDaemonExitEvent } from "./daemon.js";
|
||||
import {
|
||||
createMockSignalDaemonHandle,
|
||||
config,
|
||||
getSignalToolResultTestMocks,
|
||||
installSignalToolResultTestHooks,
|
||||
setSignalToolResultTestConfig,
|
||||
} from "./monitor.tool-result.test-harness.js";
|
||||
|
||||
installSignalToolResultTestHooks();
|
||||
|
||||
vi.resetModules();
|
||||
const { monitorSignalProvider } = await import("./monitor.js");
|
||||
|
||||
const { waitForTransportReadyMock, spawnSignalDaemonMock, streamMock } =
|
||||
getSignalToolResultTestMocks();
|
||||
|
||||
const SIGNAL_BASE_URL = "http://127.0.0.1:8080";
|
||||
type MonitorSignalProviderOptions = Parameters<typeof monitorSignalProvider>[0];
|
||||
|
||||
function createMonitorRuntime() {
|
||||
return {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: ((code: number): never => {
|
||||
throw new Error(`exit ${code}`);
|
||||
}) as (code: number) => never,
|
||||
};
|
||||
}
|
||||
|
||||
function createSignalConfig(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
const base = config as OpenClawConfig;
|
||||
const channels = (base.channels ?? {}) as Record<string, unknown>;
|
||||
const signal = (channels.signal ?? {}) as Record<string, unknown>;
|
||||
return {
|
||||
...base,
|
||||
channels: {
|
||||
...channels,
|
||||
signal: {
|
||||
...signal,
|
||||
autoStart: true,
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
...overrides,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function setSignalAutoStartConfig(overrides: Record<string, unknown> = {}) {
|
||||
setSignalToolResultTestConfig(createSignalConfig(overrides));
|
||||
}
|
||||
|
||||
function createAutoAbortController() {
|
||||
const abortController = new AbortController();
|
||||
streamMock.mockImplementation(async () => {
|
||||
abortController.abort();
|
||||
return;
|
||||
});
|
||||
return abortController;
|
||||
}
|
||||
|
||||
async function runMonitorWithMocks(opts: MonitorSignalProviderOptions) {
|
||||
return monitorSignalProvider({
|
||||
config: config as OpenClawConfig,
|
||||
waitForTransportReady: waitForTransportReadyMock as any,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
function expectWaitForTransportReadyTimeout(timeoutMs: number) {
|
||||
expect(waitForTransportReadyMock).toHaveBeenCalledTimes(1);
|
||||
expect(waitForTransportReadyMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
timeoutMs,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("monitorSignalProvider autostart", () => {
|
||||
it("uses bounded readiness checks when auto-starting the daemon", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig();
|
||||
const abortController = createAutoAbortController();
|
||||
await runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
abortSignal: abortController.signal,
|
||||
runtime,
|
||||
});
|
||||
|
||||
expect(waitForTransportReadyMock).toHaveBeenCalledTimes(1);
|
||||
expect(waitForTransportReadyMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
label: "signal daemon",
|
||||
timeoutMs: 30_000,
|
||||
logAfterMs: 10_000,
|
||||
logIntervalMs: 10_000,
|
||||
pollIntervalMs: 150,
|
||||
runtime,
|
||||
abortSignal: expect.any(AbortSignal),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses startupTimeoutMs override when provided", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig({ startupTimeoutMs: 60_000 });
|
||||
const abortController = createAutoAbortController();
|
||||
|
||||
await runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
abortSignal: abortController.signal,
|
||||
runtime,
|
||||
startupTimeoutMs: 90_000,
|
||||
});
|
||||
|
||||
expectWaitForTransportReadyTimeout(90_000);
|
||||
});
|
||||
|
||||
it("caps startupTimeoutMs at 2 minutes", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig({ startupTimeoutMs: 180_000 });
|
||||
const abortController = createAutoAbortController();
|
||||
|
||||
await runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
abortSignal: abortController.signal,
|
||||
runtime,
|
||||
});
|
||||
|
||||
expectWaitForTransportReadyTimeout(120_000);
|
||||
});
|
||||
|
||||
it("fails fast when auto-started signal daemon exits during startup", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig();
|
||||
spawnSignalDaemonMock.mockReturnValueOnce(
|
||||
createMockSignalDaemonHandle({
|
||||
exited: Promise.resolve({ source: "process", code: 1, signal: null }),
|
||||
isExited: () => true,
|
||||
}),
|
||||
);
|
||||
waitForTransportReadyMock.mockImplementationOnce(
|
||||
async (params: { abortSignal?: AbortSignal | null }) => {
|
||||
await new Promise<void>((_resolve, reject) => {
|
||||
if (params.abortSignal?.aborted) {
|
||||
reject(params.abortSignal.reason);
|
||||
return;
|
||||
}
|
||||
params.abortSignal?.addEventListener(
|
||||
"abort",
|
||||
() => reject(params.abortSignal?.reason ?? new Error("aborted")),
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await expect(
|
||||
runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
runtime,
|
||||
}),
|
||||
).rejects.toThrow(/signal daemon exited/i);
|
||||
});
|
||||
|
||||
it("treats daemon exit after user abort as clean shutdown", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig();
|
||||
const abortController = new AbortController();
|
||||
let exited = false;
|
||||
let resolveExit!: (value: SignalDaemonExitEvent) => void;
|
||||
const exitedPromise = new Promise<SignalDaemonExitEvent>((resolve) => {
|
||||
resolveExit = resolve;
|
||||
});
|
||||
const stop = vi.fn(() => {
|
||||
if (exited) {
|
||||
return;
|
||||
}
|
||||
exited = true;
|
||||
resolveExit({ source: "process", code: null, signal: "SIGTERM" });
|
||||
});
|
||||
spawnSignalDaemonMock.mockReturnValueOnce(
|
||||
createMockSignalDaemonHandle({
|
||||
stop,
|
||||
exited: exitedPromise,
|
||||
isExited: () => exited,
|
||||
}),
|
||||
);
|
||||
streamMock.mockImplementationOnce(async () => {
|
||||
abortController.abort(new Error("stop"));
|
||||
});
|
||||
|
||||
await expect(
|
||||
runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
runtime,
|
||||
abortSignal: abortController.signal,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -2,9 +2,7 @@ import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { SignalDaemonExitEvent } from "./daemon.js";
|
||||
import {
|
||||
createMockSignalDaemonHandle,
|
||||
config,
|
||||
flush,
|
||||
getSignalToolResultTestMocks,
|
||||
@@ -26,26 +24,11 @@ const {
|
||||
enqueueSystemEventMock,
|
||||
upsertPairingRequestMock,
|
||||
waitForTransportReadyMock,
|
||||
spawnSignalDaemonMock,
|
||||
} = getSignalToolResultTestMocks();
|
||||
|
||||
const SIGNAL_BASE_URL = "http://127.0.0.1:8080";
|
||||
type MonitorSignalProviderOptions = Parameters<typeof monitorSignalProvider>[0];
|
||||
|
||||
function createMonitorRuntime() {
|
||||
return {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: ((code: number): never => {
|
||||
throw new Error(`exit ${code}`);
|
||||
}) as (code: number) => never,
|
||||
};
|
||||
}
|
||||
|
||||
function setSignalAutoStartConfig(overrides: Record<string, unknown> = {}) {
|
||||
setSignalToolResultTestConfig(createSignalConfig(overrides));
|
||||
}
|
||||
|
||||
function createSignalConfig(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
const base = config as OpenClawConfig;
|
||||
const channels = (base.channels ?? {}) as Record<string, unknown>;
|
||||
@@ -65,15 +48,6 @@ function createSignalConfig(overrides: Record<string, unknown> = {}): Record<str
|
||||
};
|
||||
}
|
||||
|
||||
function createAutoAbortController() {
|
||||
const abortController = new AbortController();
|
||||
streamMock.mockImplementation(async () => {
|
||||
abortController.abort();
|
||||
return;
|
||||
});
|
||||
return abortController;
|
||||
}
|
||||
|
||||
async function runMonitorWithMocks(opts: MonitorSignalProviderOptions) {
|
||||
return monitorSignalProvider({
|
||||
config: config as OpenClawConfig,
|
||||
@@ -163,143 +137,7 @@ function setReactionNotificationConfig(mode: "all" | "own", extra: Record<string
|
||||
);
|
||||
}
|
||||
|
||||
function expectWaitForTransportReadyTimeout(timeoutMs: number) {
|
||||
expect(waitForTransportReadyMock).toHaveBeenCalledTimes(1);
|
||||
expect(waitForTransportReadyMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
timeoutMs,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("monitorSignalProvider tool results", () => {
|
||||
it("uses bounded readiness checks when auto-starting the daemon", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig();
|
||||
const abortController = createAutoAbortController();
|
||||
await runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
abortSignal: abortController.signal,
|
||||
runtime,
|
||||
});
|
||||
|
||||
expect(waitForTransportReadyMock).toHaveBeenCalledTimes(1);
|
||||
expect(waitForTransportReadyMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
label: "signal daemon",
|
||||
timeoutMs: 30_000,
|
||||
logAfterMs: 10_000,
|
||||
logIntervalMs: 10_000,
|
||||
pollIntervalMs: 150,
|
||||
runtime,
|
||||
abortSignal: expect.any(AbortSignal),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses startupTimeoutMs override when provided", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig({ startupTimeoutMs: 60_000 });
|
||||
const abortController = createAutoAbortController();
|
||||
|
||||
await runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
abortSignal: abortController.signal,
|
||||
runtime,
|
||||
startupTimeoutMs: 90_000,
|
||||
});
|
||||
|
||||
expectWaitForTransportReadyTimeout(90_000);
|
||||
});
|
||||
|
||||
it("caps startupTimeoutMs at 2 minutes", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig({ startupTimeoutMs: 180_000 });
|
||||
const abortController = createAutoAbortController();
|
||||
|
||||
await runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
abortSignal: abortController.signal,
|
||||
runtime,
|
||||
});
|
||||
|
||||
expectWaitForTransportReadyTimeout(120_000);
|
||||
});
|
||||
|
||||
it("fails fast when auto-started signal daemon exits during startup", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig();
|
||||
spawnSignalDaemonMock.mockReturnValueOnce(
|
||||
createMockSignalDaemonHandle({
|
||||
exited: Promise.resolve({ source: "process", code: 1, signal: null }),
|
||||
isExited: () => true,
|
||||
}),
|
||||
);
|
||||
waitForTransportReadyMock.mockImplementationOnce(
|
||||
async (params: { abortSignal?: AbortSignal | null }) => {
|
||||
await new Promise<void>((_resolve, reject) => {
|
||||
if (params.abortSignal?.aborted) {
|
||||
reject(params.abortSignal.reason);
|
||||
return;
|
||||
}
|
||||
params.abortSignal?.addEventListener(
|
||||
"abort",
|
||||
() => reject(params.abortSignal?.reason ?? new Error("aborted")),
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await expect(
|
||||
runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
runtime,
|
||||
}),
|
||||
).rejects.toThrow(/signal daemon exited/i);
|
||||
});
|
||||
|
||||
it("treats daemon exit after user abort as clean shutdown", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig();
|
||||
const abortController = new AbortController();
|
||||
let exited = false;
|
||||
let resolveExit!: (value: SignalDaemonExitEvent) => void;
|
||||
const exitedPromise = new Promise<SignalDaemonExitEvent>((resolve) => {
|
||||
resolveExit = resolve;
|
||||
});
|
||||
const stop = vi.fn(() => {
|
||||
if (exited) {
|
||||
return;
|
||||
}
|
||||
exited = true;
|
||||
resolveExit({ source: "process", code: null, signal: "SIGTERM" });
|
||||
});
|
||||
spawnSignalDaemonMock.mockReturnValueOnce(
|
||||
createMockSignalDaemonHandle({
|
||||
stop,
|
||||
exited: exitedPromise,
|
||||
isExited: () => exited,
|
||||
}),
|
||||
);
|
||||
streamMock.mockImplementationOnce(async () => {
|
||||
abortController.abort(new Error("stop"));
|
||||
});
|
||||
|
||||
await expect(
|
||||
runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
runtime,
|
||||
abortSignal: abortController.signal,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips tool summaries with responsePrefix", async () => {
|
||||
replyMock.mockResolvedValue({ text: "final reply" });
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ const createTelegramBot = (opts: Parameters<typeof createTelegramBotBase>[0]) =>
|
||||
});
|
||||
|
||||
const loadConfig = getLoadConfigMock();
|
||||
const loadWebMedia = getLoadWebMediaMock();
|
||||
const readChannelAllowFromStore = getReadChannelAllowFromStoreMock();
|
||||
const upsertChannelPairingRequest = getUpsertChannelPairingRequestMock();
|
||||
|
||||
@@ -1259,7 +1260,6 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("sends GIF replies as animations", async () => {
|
||||
const loadWebMedia = getLoadWebMediaMock();
|
||||
replySpy.mockResolvedValueOnce({
|
||||
text: "caption",
|
||||
mediaUrl: "https://example.com/fun",
|
||||
@@ -1269,7 +1269,6 @@ describe("createTelegramBot", () => {
|
||||
contentType: "image/gif",
|
||||
fileName: "fun.gif",
|
||||
});
|
||||
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
|
||||
@@ -1292,6 +1291,8 @@ describe("createTelegramBot", () => {
|
||||
reply_to_message_id: undefined,
|
||||
});
|
||||
expect(sendPhotoSpy).not.toHaveBeenCalled();
|
||||
expect(loadWebMedia).toHaveBeenCalledTimes(1);
|
||||
expect(loadWebMedia.mock.calls[0]?.[0]).toBe("https://example.com/fun");
|
||||
});
|
||||
|
||||
function resetHarnessSpies() {
|
||||
|
||||
Reference in New Issue
Block a user