mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-15 07:36:10 +00:00
168 lines
5.1 KiB
TypeScript
168 lines
5.1 KiB
TypeScript
/**
|
|
* Tests delivery queue runtime ordering and retry behavior.
|
|
*/
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
getActiveGatewayRootWorkCount,
|
|
resetGatewayWorkAdmission,
|
|
runWithGatewayRootWorkAdmission,
|
|
tryBeginGatewaySuspendAdmission,
|
|
} from "../process/gateway-work-admission.js";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
coreDrainPendingDeliveries: vi.fn(async () => {}),
|
|
deliverOutboundPayloads: vi.fn(async () => []),
|
|
deliverRuntimeModuleLoads: 0,
|
|
}));
|
|
|
|
vi.mock("../infra/outbound/delivery-queue.js", () => ({
|
|
drainPendingDeliveries: mocks.coreDrainPendingDeliveries,
|
|
}));
|
|
|
|
vi.mock("../infra/outbound/deliver-runtime.js", () => {
|
|
mocks.deliverRuntimeModuleLoads += 1;
|
|
return {
|
|
deliverOutboundPayloads: mocks.deliverOutboundPayloads,
|
|
deliverOutboundPayloadsInternal: mocks.deliverOutboundPayloads,
|
|
};
|
|
});
|
|
|
|
type DeliveryQueueRuntimeModule = typeof import("./delivery-queue-runtime.js");
|
|
|
|
let drainPendingDeliveries: DeliveryQueueRuntimeModule["drainPendingDeliveries"];
|
|
|
|
const log = {
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
({ drainPendingDeliveries } = await import("./delivery-queue-runtime.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
resetGatewayWorkAdmission();
|
|
mocks.coreDrainPendingDeliveries.mockReset().mockResolvedValue(undefined);
|
|
mocks.deliverOutboundPayloads.mockReset().mockResolvedValue([]);
|
|
log.info.mockClear();
|
|
log.warn.mockClear();
|
|
log.error.mockClear();
|
|
});
|
|
|
|
afterEach(resetGatewayWorkAdmission);
|
|
|
|
describe("plugin-sdk delivery queue drainPendingDeliveries", () => {
|
|
it("defers lazy runtime resolution and core draining while suspension is prepared", async () => {
|
|
const suspension = tryBeginGatewaySuspendAdmission(() => {});
|
|
expect(suspension?.commit()).toBe(true);
|
|
|
|
const pending = drainPendingDeliveries({
|
|
drainKey: "demo:test",
|
|
logLabel: "Demo reconnect drain",
|
|
cfg: {},
|
|
log,
|
|
selectEntry: () => ({ match: false }),
|
|
});
|
|
await Promise.resolve();
|
|
|
|
expect(mocks.deliverRuntimeModuleLoads).toBe(0);
|
|
expect(mocks.coreDrainPendingDeliveries).not.toHaveBeenCalled();
|
|
|
|
expect(suspension?.release()).toBe(true);
|
|
await pending;
|
|
|
|
expect(mocks.deliverRuntimeModuleLoads).toBe(1);
|
|
expect(mocks.coreDrainPendingDeliveries).toHaveBeenCalledOnce();
|
|
expect(getActiveGatewayRootWorkCount()).toBe(0);
|
|
});
|
|
|
|
it("counts a reconnect drain independently from its admitted parent", async () => {
|
|
let finishDrain = () => {};
|
|
const drainStarted = new Promise<void>((resolveStarted) => {
|
|
mocks.coreDrainPendingDeliveries.mockImplementationOnce(
|
|
() =>
|
|
new Promise<void>((resolveDrain) => {
|
|
finishDrain = resolveDrain;
|
|
resolveStarted();
|
|
}),
|
|
);
|
|
});
|
|
const deliver = vi.fn(async () => []);
|
|
|
|
await runWithGatewayRootWorkAdmission(async () => {
|
|
expect(getActiveGatewayRootWorkCount()).toBe(1);
|
|
const pending = drainPendingDeliveries({
|
|
drainKey: "demo:test",
|
|
logLabel: "Demo reconnect drain",
|
|
cfg: {},
|
|
log,
|
|
deliver,
|
|
selectEntry: () => ({ match: false }),
|
|
});
|
|
await drainStarted;
|
|
|
|
expect(getActiveGatewayRootWorkCount()).toBe(2);
|
|
finishDrain();
|
|
await pending;
|
|
expect(getActiveGatewayRootWorkCount()).toBe(1);
|
|
});
|
|
|
|
expect(getActiveGatewayRootWorkCount()).toBe(0);
|
|
});
|
|
|
|
it("releases its independent root when the core drain rejects", async () => {
|
|
mocks.coreDrainPendingDeliveries.mockImplementationOnce(async () => {
|
|
expect(getActiveGatewayRootWorkCount()).toBe(1);
|
|
throw new Error("drain failed");
|
|
});
|
|
|
|
await expect(
|
|
drainPendingDeliveries({
|
|
drainKey: "demo:test",
|
|
logLabel: "Demo reconnect drain",
|
|
cfg: {},
|
|
log,
|
|
deliver: vi.fn(async () => []),
|
|
selectEntry: () => ({ match: false }),
|
|
}),
|
|
).rejects.toThrow("drain failed");
|
|
|
|
expect(getActiveGatewayRootWorkCount()).toBe(0);
|
|
});
|
|
|
|
it("injects the lazy outbound deliver runtime when no deliver fn is provided", async () => {
|
|
await drainPendingDeliveries({
|
|
drainKey: "demo:test",
|
|
logLabel: "Demo reconnect drain",
|
|
cfg: {},
|
|
log,
|
|
selectEntry: () => ({ match: false }),
|
|
});
|
|
|
|
expect(mocks.coreDrainPendingDeliveries).toHaveBeenCalledTimes(1);
|
|
const [[{ deliver: lazyDeliver }]] = mocks.coreDrainPendingDeliveries.mock
|
|
.calls as unknown as Array<[{ deliver?: unknown }]>;
|
|
expect(lazyDeliver).toBe(mocks.deliverOutboundPayloads);
|
|
});
|
|
|
|
it("preserves an explicit deliver fn without loading the lazy runtime", async () => {
|
|
const deliver = vi.fn(async () => []);
|
|
|
|
await drainPendingDeliveries({
|
|
drainKey: "demo:test",
|
|
logLabel: "Demo reconnect drain",
|
|
cfg: {},
|
|
log,
|
|
deliver,
|
|
selectEntry: () => ({ match: false }),
|
|
});
|
|
|
|
expect(mocks.coreDrainPendingDeliveries).toHaveBeenCalledTimes(1);
|
|
const [[{ deliver: explicitDeliver }]] = mocks.coreDrainPendingDeliveries.mock
|
|
.calls as unknown as Array<[{ deliver?: unknown }]>;
|
|
expect(explicitDeliver).toBe(deliver);
|
|
expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled();
|
|
});
|
|
});
|