mirror of https://github.com/openclaw/openclaw.git
122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
import { EventEmitter } from "node:events";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
createAccountStatusSink,
|
|
keepHttpServerTaskAlive,
|
|
runPassiveAccountLifecycle,
|
|
waitUntilAbort,
|
|
} from "./channel-lifecycle.js";
|
|
|
|
type FakeServer = EventEmitter & {
|
|
close: (callback?: () => void) => void;
|
|
};
|
|
|
|
function createFakeServer(): FakeServer {
|
|
const server = new EventEmitter() as FakeServer;
|
|
server.close = (callback) => {
|
|
queueMicrotask(() => {
|
|
server.emit("close");
|
|
callback?.();
|
|
});
|
|
};
|
|
return server;
|
|
}
|
|
|
|
describe("plugin-sdk channel lifecycle helpers", () => {
|
|
it("binds account id onto status patches", () => {
|
|
const setStatus = vi.fn();
|
|
const statusSink = createAccountStatusSink({
|
|
accountId: "default",
|
|
setStatus,
|
|
});
|
|
|
|
statusSink({ running: true, lastStartAt: 123 });
|
|
|
|
expect(setStatus).toHaveBeenCalledWith({
|
|
accountId: "default",
|
|
running: true,
|
|
lastStartAt: 123,
|
|
});
|
|
});
|
|
|
|
it("resolves waitUntilAbort when signal aborts", async () => {
|
|
const abort = new AbortController();
|
|
const task = waitUntilAbort(abort.signal);
|
|
|
|
const early = await Promise.race([
|
|
task.then(() => "resolved"),
|
|
new Promise<"pending">((resolve) => setTimeout(() => resolve("pending"), 25)),
|
|
]);
|
|
expect(early).toBe("pending");
|
|
|
|
abort.abort();
|
|
await expect(task).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("runs abort cleanup before resolving", async () => {
|
|
const abort = new AbortController();
|
|
const onAbort = vi.fn(async () => undefined);
|
|
|
|
const task = waitUntilAbort(abort.signal, onAbort);
|
|
abort.abort();
|
|
|
|
await expect(task).resolves.toBeUndefined();
|
|
expect(onAbort).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("keeps passive account lifecycle pending until abort, then stops once", async () => {
|
|
const abort = new AbortController();
|
|
const stop = vi.fn();
|
|
const task = runPassiveAccountLifecycle({
|
|
abortSignal: abort.signal,
|
|
start: async () => ({ stop }),
|
|
stop: async (handle) => {
|
|
handle.stop();
|
|
},
|
|
});
|
|
|
|
const early = await Promise.race([
|
|
task.then(() => "resolved"),
|
|
new Promise<"pending">((resolve) => setTimeout(() => resolve("pending"), 25)),
|
|
]);
|
|
expect(early).toBe("pending");
|
|
expect(stop).not.toHaveBeenCalled();
|
|
|
|
abort.abort();
|
|
await expect(task).resolves.toBeUndefined();
|
|
expect(stop).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("keeps server task pending until close, then resolves", async () => {
|
|
const server = createFakeServer();
|
|
const task = keepHttpServerTaskAlive({ server });
|
|
|
|
const early = await Promise.race([
|
|
task.then(() => "resolved"),
|
|
new Promise<"pending">((resolve) => setTimeout(() => resolve("pending"), 25)),
|
|
]);
|
|
expect(early).toBe("pending");
|
|
|
|
server.close();
|
|
await expect(task).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("triggers abort hook once and resolves after close", async () => {
|
|
const server = createFakeServer();
|
|
const abort = new AbortController();
|
|
const onAbort = vi.fn(async () => {
|
|
server.close();
|
|
});
|
|
|
|
const task = keepHttpServerTaskAlive({
|
|
server,
|
|
abortSignal: abort.signal,
|
|
onAbort,
|
|
});
|
|
|
|
abort.abort();
|
|
await expect(task).resolves.toBeUndefined();
|
|
expect(onAbort).toHaveBeenCalledOnce();
|
|
});
|
|
});
|