






















@@ -787,8 +787,11 @@ describe("resolveTelegramFetch", () => {
787787);
788788});
789789790-it("retries once and then keeps sticky IPv4 dispatcher for subsequent requests", async () => {
791-primeStickyFallbackRetry("ETIMEDOUT");
790+it("retries once, keeps sticky IPv4, then recovers to primary dispatcher", async () => {
791+undiciFetch.mockRejectedValueOnce(buildFetchFallbackError("ETIMEDOUT"));
792+for (let i = 0; i < 7; i += 1) {
793+undiciFetch.mockResolvedValueOnce({ ok: true } as Response);
794+}
792795793796const resolved = resolveTelegramFetchOrThrow(undefined, {
794797network: {
@@ -797,20 +800,30 @@ describe("resolveTelegramFetch", () => {
797800});
798801799802await resolved("https://api.telegram.org/botx/sendMessage");
800-await resolved("https://api.telegram.org/botx/sendChatAction");
803+for (let i = 0; i < 4; i += 1) {
804+await resolved(`https://api.telegram.org/botx/sendChatAction?sticky=${i}`);
805+}
806+await resolved("https://api.telegram.org/botx/getMe");
807+await resolved("https://api.telegram.org/botx/deleteWebhook");
801808802-expect(undiciFetch).toHaveBeenCalledTimes(3);
809+expect(undiciFetch).toHaveBeenCalledTimes(8);
803810804811const firstDispatcher = getDispatcherFromUndiciCall(1);
805812const secondDispatcher = getDispatcherFromUndiciCall(2);
806-const thirdDispatcher = getDispatcherFromUndiciCall(3);
813+const sixthDispatcher = getDispatcherFromUndiciCall(6);
814+const seventhDispatcher = getDispatcherFromUndiciCall(7);
815+const eighthDispatcher = getDispatcherFromUndiciCall(8);
807816808817expect(firstDispatcher).toBeDefined();
809818expect(secondDispatcher).toBeDefined();
810-expect(thirdDispatcher).toBeDefined();
819+expect(sixthDispatcher).toBeDefined();
820+expect(seventhDispatcher).toBeDefined();
821+expect(eighthDispatcher).toBeDefined();
811822812823expect(firstDispatcher).not.toBe(secondDispatcher);
813-expect(secondDispatcher).toBe(thirdDispatcher);
824+expect(secondDispatcher).toBe(sixthDispatcher);
825+expect(seventhDispatcher).toBe(firstDispatcher);
826+expect(eighthDispatcher).toBe(firstDispatcher);
814827815828expectStickyAutoSelectDispatcher(firstDispatcher);
816829expect(secondDispatcher?.options?.connect).toEqual(
@@ -822,17 +835,21 @@ describe("resolveTelegramFetch", () => {
822835expect(loggerDebug).toHaveBeenCalledWith(
823836expect.stringContaining("fetch fallback: enabling sticky IPv4-only dispatcher"),
824837);
838+expect(loggerDebug).toHaveBeenCalledWith(
839+expect.stringContaining("fetch fallback: recovered from attempt 1 to attempt 0"),
840+);
825841expect(loggerWarn).not.toHaveBeenCalledWith(
826842expect.stringContaining("fetch fallback: enabling sticky IPv4-only dispatcher"),
827843);
828844});
829845830-it("escalates from IPv4 fallback to pinned Telegram IP and keeps it sticky", async () => {
846+it("escalates from IPv4 fallback to pinned Telegram IP and recovers to primary", async () => {
831847undiciFetch
832848.mockRejectedValueOnce(buildFetchFallbackError("ETIMEDOUT"))
833-.mockRejectedValueOnce(buildFetchFallbackError("EHOSTUNREACH"))
834-.mockResolvedValueOnce({ ok: true } as Response)
835-.mockResolvedValueOnce({ ok: true } as Response);
849+.mockRejectedValueOnce(buildFetchFallbackError("EHOSTUNREACH"));
850+for (let i = 0; i < 7; i += 1) {
851+undiciFetch.mockResolvedValueOnce({ ok: true } as Response);
852+}
836853837854const resolved = resolveTelegramFetchOrThrow(undefined, {
838855network: {
@@ -842,20 +859,72 @@ describe("resolveTelegramFetch", () => {
842859});
843860844861await resolved("https://api.telegram.org/botx/sendMessage");
845-await resolved("https://api.telegram.org/botx/sendChatAction");
862+for (let i = 0; i < 4; i += 1) {
863+await resolved(`https://api.telegram.org/botx/sendChatAction?sticky=${i}`);
864+}
865+await resolved("https://api.telegram.org/botx/getMe");
866+await resolved("https://api.telegram.org/botx/deleteWebhook");
846867847-expect(undiciFetch).toHaveBeenCalledTimes(4);
868+expect(undiciFetch).toHaveBeenCalledTimes(9);
848869870+const firstDispatcher = getDispatcherFromUndiciCall(1);
849871const secondDispatcher = getDispatcherFromUndiciCall(2);
850872const thirdDispatcher = getDispatcherFromUndiciCall(3);
851-const fourthDispatcher = getDispatcherFromUndiciCall(4);
873+const seventhDispatcher = getDispatcherFromUndiciCall(7);
874+const eighthDispatcher = getDispatcherFromUndiciCall(8);
875+const ninthDispatcher = getDispatcherFromUndiciCall(9);
852876853877expect(secondDispatcher).not.toBe(thirdDispatcher);
854-expect(thirdDispatcher).toBe(fourthDispatcher);
878+expect(thirdDispatcher).toBe(seventhDispatcher);
879+expect(eighthDispatcher).toBe(firstDispatcher);
880+expect(ninthDispatcher).toBe(firstDispatcher);
855881expectPinnedFallbackIpDispatcher(3);
856882expect(loggerWarn).toHaveBeenCalledWith(
857883expect.stringContaining("fetch fallback: DNS-resolved IP unreachable"),
858884);
885+expect(loggerDebug).toHaveBeenCalledWith(
886+expect.stringContaining("fetch fallback: recovered from attempt 2 to attempt 0"),
887+);
888+});
889+890+it("keeps sticky fallback after a failed primary recovery probe", async () => {
891+undiciFetch
892+.mockRejectedValueOnce(buildFetchFallbackError("ETIMEDOUT"))
893+.mockResolvedValueOnce({ ok: true } as Response)
894+.mockResolvedValueOnce({ ok: true } as Response)
895+.mockResolvedValueOnce({ ok: true } as Response)
896+.mockResolvedValueOnce({ ok: true } as Response)
897+.mockResolvedValueOnce({ ok: true } as Response)
898+.mockRejectedValueOnce(buildFetchFallbackError("ETIMEDOUT"))
899+.mockResolvedValueOnce({ ok: true } as Response)
900+.mockResolvedValueOnce({ ok: true } as Response);
901+902+const resolved = resolveTelegramFetchOrThrow(undefined, {
903+network: {
904+autoSelectFamily: true,
905+},
906+});
907+908+await resolved("https://api.telegram.org/botx/sendMessage");
909+for (let i = 0; i < 4; i += 1) {
910+await resolved(`https://api.telegram.org/botx/sendChatAction?sticky=${i}`);
911+}
912+await resolved("https://api.telegram.org/botx/getMe");
913+await resolved("https://api.telegram.org/botx/deleteWebhook");
914+915+expect(undiciFetch).toHaveBeenCalledTimes(9);
916+917+const firstDispatcher = getDispatcherFromUndiciCall(1);
918+const secondDispatcher = getDispatcherFromUndiciCall(2);
919+920+expect(firstDispatcher).not.toBe(secondDispatcher);
921+expect(getDispatcherFromUndiciCall(6)).toBe(secondDispatcher);
922+expect(getDispatcherFromUndiciCall(7)).toBe(firstDispatcher);
923+expect(getDispatcherFromUndiciCall(8)).toBe(secondDispatcher);
924+expect(getDispatcherFromUndiciCall(9)).toBe(secondDispatcher);
925+expect(loggerDebug).toHaveBeenCalledWith(
926+expect.stringContaining("fetch fallback: re-probing primary dispatcher"),
927+);
859928});
860929861930it("keeps the armed fallback sticky when all attempts fail", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。