mirror of https://github.com/openclaw/openclaw.git
test: share telegram sticky fetch helpers
This commit is contained in:
parent
2da384e110
commit
34a552383f
|
|
@ -106,6 +106,32 @@ async function runDefaultStickyIpv4FallbackProbe(code = "EHOSTUNREACH"): Promise
|
||||||
await resolved("https://api.telegram.org/botx/sendChatAction");
|
await resolved("https://api.telegram.org/botx/sendChatAction");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function primeStickyFallbackRetry(code = "EHOSTUNREACH", successCount = 2): void {
|
||||||
|
undiciFetch.mockRejectedValueOnce(buildFetchFallbackError(code));
|
||||||
|
for (let i = 0; i < successCount; i += 1) {
|
||||||
|
undiciFetch.mockResolvedValueOnce({ ok: true } as Response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectStickyAutoSelectDispatcher(
|
||||||
|
dispatcher:
|
||||||
|
| {
|
||||||
|
options?: {
|
||||||
|
connect?: Record<string, unknown>;
|
||||||
|
proxyTls?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| undefined,
|
||||||
|
field: "connect" | "proxyTls" = "connect",
|
||||||
|
): void {
|
||||||
|
expect(dispatcher?.options?.[field]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
autoSelectFamily: true,
|
||||||
|
autoSelectFamilyAttemptTimeout: 300,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function expectPinnedIpv4ConnectDispatcher(args: {
|
function expectPinnedIpv4ConnectDispatcher(args: {
|
||||||
pinnedCall: number;
|
pinnedCall: number;
|
||||||
firstCall?: number;
|
firstCall?: number;
|
||||||
|
|
@ -126,6 +152,36 @@ function expectPinnedIpv4ConnectDispatcher(args: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expectCallerDispatcherPreserved(callIndexes: number[], dispatcher: unknown) {
|
||||||
|
for (const callIndex of callIndexes) {
|
||||||
|
const callInit = undiciFetch.mock.calls[callIndex - 1]?.[1] as
|
||||||
|
| (RequestInit & { dispatcher?: unknown })
|
||||||
|
| undefined;
|
||||||
|
expect(callInit?.dispatcher).toBe(dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectNoStickyRetryWithSameDispatcher(params: {
|
||||||
|
resolved: ReturnType<typeof resolveTelegramFetchOrThrow>;
|
||||||
|
expectedAgentCtor: typeof ProxyAgentCtor | typeof EnvHttpProxyAgentCtor;
|
||||||
|
field: "connect" | "proxyTls";
|
||||||
|
}) {
|
||||||
|
await expect(params.resolved("https://api.telegram.org/botx/sendMessage")).rejects.toThrow(
|
||||||
|
"fetch failed",
|
||||||
|
);
|
||||||
|
await params.resolved("https://api.telegram.org/botx/sendChatAction");
|
||||||
|
|
||||||
|
expect(undiciFetch).toHaveBeenCalledTimes(2);
|
||||||
|
expect(params.expectedAgentCtor).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
const firstDispatcher = getDispatcherFromUndiciCall(1);
|
||||||
|
const secondDispatcher = getDispatcherFromUndiciCall(2);
|
||||||
|
|
||||||
|
expect(firstDispatcher).toBe(secondDispatcher);
|
||||||
|
expectStickyAutoSelectDispatcher(firstDispatcher, params.field);
|
||||||
|
expect(firstDispatcher?.options?.[params.field]?.family).not.toBe(4);
|
||||||
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
undiciFetch.mockReset();
|
undiciFetch.mockReset();
|
||||||
setGlobalDispatcher.mockReset();
|
setGlobalDispatcher.mockReset();
|
||||||
|
|
@ -279,8 +335,7 @@ describe("resolveTelegramFetch", () => {
|
||||||
const { makeProxyFetch } = await import("./proxy.js");
|
const { makeProxyFetch } = await import("./proxy.js");
|
||||||
const proxyFetch = makeProxyFetch("http://127.0.0.1:7890");
|
const proxyFetch = makeProxyFetch("http://127.0.0.1:7890");
|
||||||
ProxyAgentCtor.mockClear();
|
ProxyAgentCtor.mockClear();
|
||||||
const fetchError = buildFetchFallbackError("EHOSTUNREACH");
|
primeStickyFallbackRetry("EHOSTUNREACH", 1);
|
||||||
undiciFetch.mockRejectedValueOnce(fetchError).mockResolvedValueOnce({ ok: true } as Response);
|
|
||||||
|
|
||||||
const resolved = resolveTelegramFetchOrThrow(proxyFetch, {
|
const resolved = resolveTelegramFetchOrThrow(proxyFetch, {
|
||||||
network: {
|
network: {
|
||||||
|
|
@ -289,31 +344,16 @@ describe("resolveTelegramFetch", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(resolved("https://api.telegram.org/botx/sendMessage")).rejects.toThrow(
|
await expectNoStickyRetryWithSameDispatcher({
|
||||||
"fetch failed",
|
resolved,
|
||||||
);
|
expectedAgentCtor: ProxyAgentCtor,
|
||||||
await resolved("https://api.telegram.org/botx/sendChatAction");
|
field: "proxyTls",
|
||||||
|
});
|
||||||
expect(undiciFetch).toHaveBeenCalledTimes(2);
|
|
||||||
expect(ProxyAgentCtor).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
const firstDispatcher = getDispatcherFromUndiciCall(1);
|
|
||||||
const secondDispatcher = getDispatcherFromUndiciCall(2);
|
|
||||||
|
|
||||||
expect(firstDispatcher).toBe(secondDispatcher);
|
|
||||||
expect(firstDispatcher?.options?.proxyTls).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
autoSelectFamily: true,
|
|
||||||
autoSelectFamilyAttemptTimeout: 300,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(firstDispatcher?.options?.proxyTls?.family).not.toBe(4);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not blind-retry when sticky IPv4 fallback is disallowed for env proxy paths", async () => {
|
it("does not blind-retry when sticky IPv4 fallback is disallowed for env proxy paths", async () => {
|
||||||
vi.stubEnv("HTTPS_PROXY", "http://127.0.0.1:7890");
|
vi.stubEnv("HTTPS_PROXY", "http://127.0.0.1:7890");
|
||||||
const fetchError = buildFetchFallbackError("EHOSTUNREACH");
|
primeStickyFallbackRetry("EHOSTUNREACH", 1);
|
||||||
undiciFetch.mockRejectedValueOnce(fetchError).mockResolvedValueOnce({ ok: true } as Response);
|
|
||||||
|
|
||||||
const resolved = resolveTelegramFetchOrThrow(undefined, {
|
const resolved = resolveTelegramFetchOrThrow(undefined, {
|
||||||
network: {
|
network: {
|
||||||
|
|
@ -322,25 +362,11 @@ describe("resolveTelegramFetch", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(resolved("https://api.telegram.org/botx/sendMessage")).rejects.toThrow(
|
await expectNoStickyRetryWithSameDispatcher({
|
||||||
"fetch failed",
|
resolved,
|
||||||
);
|
expectedAgentCtor: EnvHttpProxyAgentCtor,
|
||||||
await resolved("https://api.telegram.org/botx/sendChatAction");
|
field: "connect",
|
||||||
|
});
|
||||||
expect(undiciFetch).toHaveBeenCalledTimes(2);
|
|
||||||
expect(EnvHttpProxyAgentCtor).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
const firstDispatcher = getDispatcherFromUndiciCall(1);
|
|
||||||
const secondDispatcher = getDispatcherFromUndiciCall(2);
|
|
||||||
|
|
||||||
expect(firstDispatcher).toBe(secondDispatcher);
|
|
||||||
expect(firstDispatcher?.options?.connect).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
autoSelectFamily: true,
|
|
||||||
autoSelectFamilyAttemptTimeout: 300,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(firstDispatcher?.options?.connect?.family).not.toBe(4);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("treats ALL_PROXY-only env as direct transport and arms sticky IPv4 fallback", async () => {
|
it("treats ALL_PROXY-only env as direct transport and arms sticky IPv4 fallback", async () => {
|
||||||
|
|
@ -474,11 +500,7 @@ describe("resolveTelegramFetch", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("retries once and then keeps sticky IPv4 dispatcher for subsequent requests", async () => {
|
it("retries once and then keeps sticky IPv4 dispatcher for subsequent requests", async () => {
|
||||||
const fetchError = buildFetchFallbackError("ETIMEDOUT");
|
primeStickyFallbackRetry("ETIMEDOUT");
|
||||||
undiciFetch
|
|
||||||
.mockRejectedValueOnce(fetchError)
|
|
||||||
.mockResolvedValueOnce({ ok: true } as Response)
|
|
||||||
.mockResolvedValueOnce({ ok: true } as Response);
|
|
||||||
|
|
||||||
const resolved = resolveTelegramFetchOrThrow(undefined, {
|
const resolved = resolveTelegramFetchOrThrow(undefined, {
|
||||||
network: {
|
network: {
|
||||||
|
|
@ -502,12 +524,7 @@ describe("resolveTelegramFetch", () => {
|
||||||
expect(firstDispatcher).not.toBe(secondDispatcher);
|
expect(firstDispatcher).not.toBe(secondDispatcher);
|
||||||
expect(secondDispatcher).toBe(thirdDispatcher);
|
expect(secondDispatcher).toBe(thirdDispatcher);
|
||||||
|
|
||||||
expect(firstDispatcher?.options?.connect).toEqual(
|
expectStickyAutoSelectDispatcher(firstDispatcher);
|
||||||
expect.objectContaining({
|
|
||||||
autoSelectFamily: true,
|
|
||||||
autoSelectFamilyAttemptTimeout: 300,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(secondDispatcher?.options?.connect).toEqual(
|
expect(secondDispatcher?.options?.connect).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
family: 4,
|
family: 4,
|
||||||
|
|
@ -533,24 +550,11 @@ describe("resolveTelegramFetch", () => {
|
||||||
} as RequestInit);
|
} as RequestInit);
|
||||||
|
|
||||||
expect(undiciFetch).toHaveBeenCalledTimes(2);
|
expect(undiciFetch).toHaveBeenCalledTimes(2);
|
||||||
|
expectCallerDispatcherPreserved([1, 2], callerDispatcher);
|
||||||
const firstCallInit = undiciFetch.mock.calls[0]?.[1] as
|
|
||||||
| (RequestInit & { dispatcher?: unknown })
|
|
||||||
| undefined;
|
|
||||||
const secondCallInit = undiciFetch.mock.calls[1]?.[1] as
|
|
||||||
| (RequestInit & { dispatcher?: unknown })
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
expect(firstCallInit?.dispatcher).toBe(callerDispatcher);
|
|
||||||
expect(secondCallInit?.dispatcher).toBe(callerDispatcher);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not arm sticky fallback from caller-provided dispatcher failures", async () => {
|
it("does not arm sticky fallback from caller-provided dispatcher failures", async () => {
|
||||||
const fetchError = buildFetchFallbackError("EHOSTUNREACH");
|
primeStickyFallbackRetry();
|
||||||
undiciFetch
|
|
||||||
.mockRejectedValueOnce(fetchError)
|
|
||||||
.mockResolvedValueOnce({ ok: true } as Response)
|
|
||||||
.mockResolvedValueOnce({ ok: true } as Response);
|
|
||||||
|
|
||||||
const resolved = resolveTelegramFetchOrThrow(undefined, {
|
const resolved = resolveTelegramFetchOrThrow(undefined, {
|
||||||
network: {
|
network: {
|
||||||
|
|
@ -566,23 +570,10 @@ describe("resolveTelegramFetch", () => {
|
||||||
await resolved("https://api.telegram.org/botx/sendChatAction");
|
await resolved("https://api.telegram.org/botx/sendChatAction");
|
||||||
|
|
||||||
expect(undiciFetch).toHaveBeenCalledTimes(3);
|
expect(undiciFetch).toHaveBeenCalledTimes(3);
|
||||||
|
expectCallerDispatcherPreserved([1, 2], callerDispatcher);
|
||||||
const firstCallInit = undiciFetch.mock.calls[0]?.[1] as
|
|
||||||
| (RequestInit & { dispatcher?: unknown })
|
|
||||||
| undefined;
|
|
||||||
const secondCallInit = undiciFetch.mock.calls[1]?.[1] as
|
|
||||||
| (RequestInit & { dispatcher?: unknown })
|
|
||||||
| undefined;
|
|
||||||
const thirdDispatcher = getDispatcherFromUndiciCall(3);
|
const thirdDispatcher = getDispatcherFromUndiciCall(3);
|
||||||
|
|
||||||
expect(firstCallInit?.dispatcher).toBe(callerDispatcher);
|
expectStickyAutoSelectDispatcher(thirdDispatcher);
|
||||||
expect(secondCallInit?.dispatcher).toBe(callerDispatcher);
|
|
||||||
expect(thirdDispatcher?.options?.connect).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
autoSelectFamily: true,
|
|
||||||
autoSelectFamilyAttemptTimeout: 300,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(thirdDispatcher?.options?.connect?.family).not.toBe(4);
|
expect(thirdDispatcher?.options?.connect?.family).not.toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue