mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 23:18:28 +00:00
refactor: unify monitor abort lifecycle handling
This commit is contained in:
92
src/line/monitor.lifecycle.test.ts
Normal file
92
src/line/monitor.lifecycle.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
||||
const { createLineBotMock, registerPluginHttpRouteMock, unregisterHttpMock } = vi.hoisted(() => ({
|
||||
createLineBotMock: vi.fn(() => ({
|
||||
account: { accountId: "default" },
|
||||
handleWebhook: vi.fn(),
|
||||
})),
|
||||
registerPluginHttpRouteMock: vi.fn(),
|
||||
unregisterHttpMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./bot.js", () => ({
|
||||
createLineBot: createLineBotMock,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/http-path.js", () => ({
|
||||
normalizePluginHttpPath: (_path: string | undefined, fallback: string) => fallback,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/http-registry.js", () => ({
|
||||
registerPluginHttpRoute: registerPluginHttpRouteMock,
|
||||
}));
|
||||
|
||||
vi.mock("./webhook-node.js", () => ({
|
||||
createLineNodeWebhookHandler: vi.fn(() => vi.fn()),
|
||||
}));
|
||||
|
||||
describe("monitorLineProvider lifecycle", () => {
|
||||
beforeEach(() => {
|
||||
createLineBotMock.mockClear();
|
||||
unregisterHttpMock.mockClear();
|
||||
registerPluginHttpRouteMock.mockClear().mockReturnValue(unregisterHttpMock);
|
||||
});
|
||||
|
||||
it("waits for abort before resolving", async () => {
|
||||
const { monitorLineProvider } = await import("./monitor.js");
|
||||
const abort = new AbortController();
|
||||
let resolved = false;
|
||||
|
||||
const task = monitorLineProvider({
|
||||
channelAccessToken: "token",
|
||||
channelSecret: "secret",
|
||||
config: {} as OpenClawConfig,
|
||||
runtime: {} as RuntimeEnv,
|
||||
abortSignal: abort.signal,
|
||||
}).then((monitor) => {
|
||||
resolved = true;
|
||||
return monitor;
|
||||
});
|
||||
|
||||
await vi.waitFor(() => expect(registerPluginHttpRouteMock).toHaveBeenCalledTimes(1));
|
||||
expect(resolved).toBe(false);
|
||||
|
||||
abort.abort();
|
||||
await task;
|
||||
expect(unregisterHttpMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("stops immediately when signal is already aborted", async () => {
|
||||
const { monitorLineProvider } = await import("./monitor.js");
|
||||
const abort = new AbortController();
|
||||
abort.abort();
|
||||
|
||||
await monitorLineProvider({
|
||||
channelAccessToken: "token",
|
||||
channelSecret: "secret",
|
||||
config: {} as OpenClawConfig,
|
||||
runtime: {} as RuntimeEnv,
|
||||
abortSignal: abort.signal,
|
||||
});
|
||||
|
||||
expect(unregisterHttpMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("returns immediately without abort signal and stop is idempotent", async () => {
|
||||
const { monitorLineProvider } = await import("./monitor.js");
|
||||
|
||||
const monitor = await monitorLineProvider({
|
||||
channelAccessToken: "token",
|
||||
channelSecret: "secret",
|
||||
config: {} as OpenClawConfig,
|
||||
runtime: {} as RuntimeEnv,
|
||||
});
|
||||
|
||||
expect(unregisterHttpMock).not.toHaveBeenCalled();
|
||||
monitor.stop();
|
||||
monitor.stop();
|
||||
expect(unregisterHttpMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/pr
|
||||
import { createReplyPrefixOptions } from "../channels/reply-prefix.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { danger, logVerbose } from "../globals.js";
|
||||
import { waitForAbortSignal } from "../infra/abort-signal.js";
|
||||
import { normalizePluginHttpPath } from "../plugins/http-path.js";
|
||||
import { registerPluginHttpRoute } from "../plugins/http-registry.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
@@ -296,7 +297,12 @@ export async function monitorLineProvider(
|
||||
logVerbose(`line: registered webhook handler at ${normalizedPath}`);
|
||||
|
||||
// Handle abort signal
|
||||
let stopped = false;
|
||||
const stopHandler = () => {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
stopped = true;
|
||||
logVerbose(`line: stopping provider for account ${resolvedAccountId}`);
|
||||
unregisterHttp();
|
||||
recordChannelRuntimeState({
|
||||
@@ -309,7 +315,12 @@ export async function monitorLineProvider(
|
||||
});
|
||||
};
|
||||
|
||||
abortSignal?.addEventListener("abort", stopHandler);
|
||||
if (abortSignal?.aborted) {
|
||||
stopHandler();
|
||||
} else if (abortSignal) {
|
||||
abortSignal.addEventListener("abort", stopHandler, { once: true });
|
||||
await waitForAbortSignal(abortSignal);
|
||||
}
|
||||
|
||||
return {
|
||||
account: bot.account,
|
||||
|
||||
Reference in New Issue
Block a user