


















@@ -44,24 +44,40 @@ const { initSpy, runSpy, getRuntimeConfigMock } = vi.hoisted(() => ({
4444})),
4545}));
464647-const { registerUnhandledRejectionHandlerMock, emitUnhandledRejection, resetUnhandledRejection } =
48-vi.hoisted(() => {
49-let handler: ((reason: unknown) => boolean) | undefined;
50-return {
51-registerUnhandledRejectionHandlerMock: vi.fn((next: (reason: unknown) => boolean) => {
52-handler = next;
53-return () => {
54-if (handler === next) {
55-handler = undefined;
56-}
57-};
58-}),
59-emitUnhandledRejection: (reason: unknown) => handler?.(reason) ?? false,
60-resetUnhandledRejection: () => {
61-handler = undefined;
62-},
63-};
64-});
47+const {
48+ registerUnhandledRejectionHandlerMock,
49+ registerUncaughtExceptionHandlerMock,
50+ emitUnhandledRejection,
51+ emitUncaughtException,
52+ resetProcessErrorHandlers,
53+} = vi.hoisted(() => {
54+let unhandledRejectionHandler: ((reason: unknown) => boolean) | undefined;
55+let uncaughtExceptionHandler: ((error: unknown) => boolean) | undefined;
56+return {
57+registerUnhandledRejectionHandlerMock: vi.fn((next: (reason: unknown) => boolean) => {
58+unhandledRejectionHandler = next;
59+return () => {
60+if (unhandledRejectionHandler === next) {
61+unhandledRejectionHandler = undefined;
62+}
63+};
64+}),
65+registerUncaughtExceptionHandlerMock: vi.fn((next: (error: unknown) => boolean) => {
66+uncaughtExceptionHandler = next;
67+return () => {
68+if (uncaughtExceptionHandler === next) {
69+uncaughtExceptionHandler = undefined;
70+}
71+};
72+}),
73+emitUnhandledRejection: (reason: unknown) => unhandledRejectionHandler?.(reason) ?? false,
74+emitUncaughtException: (error: unknown) => uncaughtExceptionHandler?.(error) ?? false,
75+resetProcessErrorHandlers: () => {
76+unhandledRejectionHandler = undefined;
77+uncaughtExceptionHandler = undefined;
78+},
79+};
80+});
65816682const { createTelegramBotErrors } = vi.hoisted(() => ({
6783createTelegramBotErrors: [] as unknown[],
@@ -113,6 +129,16 @@ function makeRecoverableFetchError() {
113129});
114130}
115131132+class MockHttpError extends Error {
133+constructor(
134+message: string,
135+public readonly error: unknown,
136+) {
137+super(message);
138+this.name = "HttpError";
139+}
140+}
141+116142async function makeTaggedPollingFetchError() {
117143const { tagTelegramNetworkError } = await import("./network-errors.js");
118144const err = makeRecoverableFetchError();
@@ -123,6 +149,13 @@ async function makeTaggedPollingFetchError() {
123149return err;
124150}
125151152+async function makeTaggedPollingHttpError() {
153+return new MockHttpError(
154+"Network request for 'getUpdates' failed!",
155+await makeTaggedPollingFetchError(),
156+);
157+}
158+126159const createAbortTask = (
127160abort: AbortController,
128161beforeAbort?: () => void,
@@ -316,6 +349,7 @@ vi.mock("openclaw/plugin-sdk/runtime-env", async () => {
316349 computeBackoff,
317350 sleepWithAbort,
318351registerUnhandledRejectionHandler: registerUnhandledRejectionHandlerMock,
352+registerUncaughtExceptionHandler: registerUncaughtExceptionHandlerMock,
319353};
320354});
321355@@ -364,7 +398,8 @@ describe("monitorTelegramProvider (grammY)", () => {
364398close: vi.fn(async () => undefined),
365399}));
366400registerUnhandledRejectionHandlerMock.mockClear();
367-resetUnhandledRejection();
401+registerUncaughtExceptionHandlerMock.mockClear();
402+resetProcessErrorHandlers();
368403createTelegramBotErrors.length = 0;
369404createdBotStops.length = 0;
370405consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
@@ -659,6 +694,38 @@ describe("monitorTelegramProvider (grammY)", () => {
659694expectRecoverableRetryState(2);
660695});
661696697+it("force-restarts polling when uncaught network exception stalls runner", async () => {
698+const abort = new AbortController();
699+const firstCycle = mockRunOnceWithStalledPollingRunner();
700+const secondCycle = mockRunOnceWithStalledPollingRunner();
701+702+const monitor = monitorTelegramProvider({ token: "tok", abortSignal: abort.signal });
703+await firstCycle.waitForRunStart();
704+705+expect(emitUncaughtException(await makeTaggedPollingFetchError())).toBe(true);
706+expect(firstCycle.stop).toHaveBeenCalledTimes(1);
707+await secondCycle.waitForRunStart();
708+abort.abort();
709+await monitor;
710+expectRecoverableRetryState(2);
711+});
712+713+it("force-restarts polling when uncaught polling HttpError stalls runner", async () => {
714+const abort = new AbortController();
715+const firstCycle = mockRunOnceWithStalledPollingRunner();
716+const secondCycle = mockRunOnceWithStalledPollingRunner();
717+718+const monitor = monitorTelegramProvider({ token: "tok", abortSignal: abort.signal });
719+await firstCycle.waitForRunStart();
720+721+expect(emitUncaughtException(await makeTaggedPollingHttpError())).toBe(true);
722+expect(firstCycle.stop).toHaveBeenCalledTimes(1);
723+await secondCycle.waitForRunStart();
724+abort.abort();
725+await monitor;
726+expectRecoverableRetryState(2);
727+});
728+662729it("rebuilds the resolved transport after a stalled polling restart", async () => {
663730vi.useFakeTimers({ shouldAdvanceTime: true });
664731try {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。