mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 03:42:21 +03:00
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host. Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean. Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
139 lines
4.9 KiB
TypeScript
139 lines
4.9 KiB
TypeScript
/**
|
|
* #9694 — proxy "Test connection" false-negative on IPv4-only SOCKS5/SSH proxies.
|
|
*
|
|
* #1255 moved every egress probe to `api64.ipify.org`, which is IPv6-first, so a
|
|
* proxy with no IPv6 route has nothing to connect to and the probe hangs until the
|
|
* caller's deadline — a healthy proxy reported dead. Swapping the target to
|
|
* `api4.ipify.org` fixes that case and breaks #1255's.
|
|
*
|
|
* The probe now tries the targets in order inside the budget the caller already
|
|
* enforced. `api64` stays FIRST so a proxy with working IPv6 behaves exactly as it
|
|
* did after #1255 — including which of its addresses is reported, which matters
|
|
* because the egress IP is used as an identity to detect accounts sharing an address.
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const {
|
|
probeEchoTargets,
|
|
resolveEgressEchoUrls,
|
|
splitEchoAttemptBudget,
|
|
EGRESS_ECHO_URL_DUAL,
|
|
EGRESS_ECHO_URL_V4,
|
|
EGRESS_ECHO_URL_ENV,
|
|
MIN_ECHO_ATTEMPT_MS,
|
|
} = await import("../../src/lib/proxyEchoTarget.ts");
|
|
|
|
test("#9694: the IPv6-first target is still tried first", () => {
|
|
assert.deepEqual(resolveEgressEchoUrls({}), [EGRESS_ECHO_URL_DUAL, EGRESS_ECHO_URL_V4]);
|
|
assert.equal(EGRESS_ECHO_URL_DUAL, "https://api64.ipify.org?format=json");
|
|
assert.equal(EGRESS_ECHO_URL_V4, "https://api4.ipify.org?format=json");
|
|
});
|
|
|
|
test("#9694: an operator override pins exactly one target", () => {
|
|
const env = { [EGRESS_ECHO_URL_ENV]: " https://echo.internal/ip " };
|
|
assert.deepEqual(resolveEgressEchoUrls(env), ["https://echo.internal/ip"]);
|
|
for (const blank of ["", " "]) {
|
|
assert.deepEqual(resolveEgressEchoUrls({ [EGRESS_ECHO_URL_ENV]: blank }), [
|
|
EGRESS_ECHO_URL_DUAL,
|
|
EGRESS_ECHO_URL_V4,
|
|
]);
|
|
}
|
|
});
|
|
|
|
test("#9694: attempts share the caller's budget instead of extending it", () => {
|
|
// The real call sites use 5s, 6s and 10s.
|
|
assert.deepEqual(splitEchoAttemptBudget(10000, 2), [5000, 5000]);
|
|
assert.deepEqual(splitEchoAttemptBudget(6000, 2), [3000, 3000]);
|
|
assert.deepEqual(splitEchoAttemptBudget(5000, 2), [2500, 2500]);
|
|
for (const total of [10000, 6000, 5000]) {
|
|
const budgets = splitEchoAttemptBudget(total, 2);
|
|
assert.ok(
|
|
budgets.reduce((a, b) => a + b, 0) <= total,
|
|
"the sum must never exceed the deadline the caller already enforced"
|
|
);
|
|
}
|
|
});
|
|
|
|
test("#9694: a budget too small to split is spent on one attempt, not two useless ones", () => {
|
|
assert.deepEqual(splitEchoAttemptBudget(MIN_ECHO_ATTEMPT_MS * 2 - 2, 2), [
|
|
MIN_ECHO_ATTEMPT_MS * 2 - 2,
|
|
]);
|
|
assert.deepEqual(splitEchoAttemptBudget(0, 2), []);
|
|
assert.deepEqual(splitEchoAttemptBudget(-1, 2), []);
|
|
assert.deepEqual(splitEchoAttemptBudget(10000, 0), []);
|
|
assert.deepEqual(splitEchoAttemptBudget(10000, 1), [10000]);
|
|
});
|
|
|
|
test("#9694: a reachable IPv6-first target is used and the IPv4 target is never touched", async () => {
|
|
const tried: string[] = [];
|
|
const outcome = await probeEchoTargets(
|
|
async (url) => {
|
|
tried.push(url);
|
|
return '{"ip":"2001:db8::1"}';
|
|
},
|
|
10000,
|
|
{}
|
|
);
|
|
assert.deepEqual(tried, [EGRESS_ECHO_URL_DUAL], "no extra request for a healthy IPv6 proxy");
|
|
assert.equal(outcome.url, EGRESS_ECHO_URL_DUAL);
|
|
assert.equal(outcome.result, '{"ip":"2001:db8::1"}');
|
|
});
|
|
|
|
test("#9694: an IPv4-only proxy reaches the IPv4 target and succeeds", async () => {
|
|
const tried: Array<{ url: string; timeoutMs: number }> = [];
|
|
const outcome = await probeEchoTargets(
|
|
async (url, timeoutMs) => {
|
|
tried.push({ url, timeoutMs });
|
|
// What an IPv4-only SOCKS5 tunnel does with an IPv6-first host: nothing,
|
|
// until the attempt budget aborts it.
|
|
if (url === EGRESS_ECHO_URL_DUAL) throw new Error("This operation was aborted");
|
|
return '{"ip":"203.0.113.7"}';
|
|
},
|
|
10000,
|
|
{}
|
|
);
|
|
assert.deepEqual(
|
|
tried.map((t) => t.url),
|
|
[EGRESS_ECHO_URL_DUAL, EGRESS_ECHO_URL_V4],
|
|
"the IPv6 attempt must not end the probe"
|
|
);
|
|
assert.deepEqual(
|
|
tried.map((t) => t.timeoutMs),
|
|
[5000, 5000],
|
|
"each attempt gets half of the caller's 10s budget"
|
|
);
|
|
assert.equal(outcome.url, EGRESS_ECHO_URL_V4);
|
|
assert.equal(outcome.result, '{"ip":"203.0.113.7"}');
|
|
});
|
|
|
|
test("#9694: a genuinely dead proxy still fails, with the last real error", async () => {
|
|
await assert.rejects(
|
|
() =>
|
|
probeEchoTargets(
|
|
async (url) => {
|
|
throw new Error(`ECONNREFUSED ${url}`);
|
|
},
|
|
10000,
|
|
{}
|
|
),
|
|
/ECONNREFUSED .*api4\.ipify\.org/,
|
|
"the surfaced error must describe a network failure, not internal bookkeeping"
|
|
);
|
|
});
|
|
|
|
test("#9694: an override that fails is not silently retried against ipify", async () => {
|
|
const tried: string[] = [];
|
|
await assert.rejects(() =>
|
|
probeEchoTargets(
|
|
async (url) => {
|
|
tried.push(url);
|
|
throw new Error("nope");
|
|
},
|
|
10000,
|
|
{ [EGRESS_ECHO_URL_ENV]: "https://echo.internal/ip" }
|
|
)
|
|
);
|
|
assert.deepEqual(tried, ["https://echo.internal/ip"]);
|
|
});
|