From b239db6aebf4e1bc3b66b0e488ed37ff9d890dae Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Sat, 29 Aug 2026 13:37:29 +0800 Subject: [PATCH] test(net): assert the proxy-name contract on a platform that folds case The Windows coverage lane failed on four cases that assume `http_proxy` and `HTTP_PROXY` are separate variables. They are one variable there: `process.env` is case-insensitive, and the launch snapshot folds names for the same reason. A scenario built on "the user set only the lowercase name" cannot exist. Two of them assert a contract rather than a spelling, so they now hold either way: what reaches a child for a scheme the user named is the user's own value and never the derived one, and a nested install carries the active policy's value rather than the outer install's published one. Both read over the pair of names instead of one. The other two are about the case distinction itself. Neither can hold on a folded environment, so each asserts what that platform does instead of skipping: the later entry wins where a preference cannot be expressed, and a diagnostic names the spelling resolution asked for. Both were verified against the folding arm rather than reasoned about. --- packages/net/http-proxy/tests/install.spec.ts | 22 ++++++++++++------- packages/net/http-proxy/tests/policy.spec.ts | 12 ++++++++-- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/net/http-proxy/tests/install.spec.ts b/packages/net/http-proxy/tests/install.spec.ts index 6b2f1b5e85..431cb3af79 100644 --- a/packages/net/http-proxy/tests/install.spec.ts +++ b/packages/net/http-proxy/tests/install.spec.ts @@ -237,9 +237,13 @@ describe('childProxyEnv', () => { const dispose = await installGlobalProxy(proxyAll('example.com')) try { const child = childProxyEnv() - // The published policy invented an HTTPS proxy for this process; the child must not see it. - expect(child.https_proxy).toBe('socks5://127.0.0.1:1080') - expect(child.HTTPS_PROXY).toBeUndefined() + // The published policy derived an HTTPS proxy for this process; the child must not see it. + // Asserted over both casings rather than one: Windows folds the pair into a single variable, + // so which spelling carries the value is the platform's to decide — that it is the user's + // value and never the derived one is not. + const https = [child.https_proxy, child.HTTPS_PROXY] + expect(https).toContain('socks5://127.0.0.1:1080') + expect(https).not.toContain(proxyUrl) expect(child.HTTP_PROXY).toBe(proxyUrl) // The bypass list is the resolved one even though the user set none: it only adds entries, // and without it the child sends its own loopback traffic to a proxy that cannot route it. @@ -312,17 +316,19 @@ describe('childProxyEnv', () => { const disposeInner = await installGlobalProxy(nested) try { const child = childProxyEnv() - // Recording the outer install's published environment as the user's would show the - // lowercase name it wrote and the outer proxy for a scheme the user never named. - expect(child.http_proxy).toBeUndefined() + // The user named no HTTPS proxy, so this scheme carries whichever policy is active. Reading + // the outer install's published environment as the user's would pin it to the outer proxy + // instead — the one discriminator that does not depend on how a platform cases names. expect(child.https_proxy).toBe(nestedUrl) + expect(child.HTTPS_PROXY).toBe(nestedUrl) } finally { await disposeInner() } // Unmounting the inner install must leave the outer one still able to describe that - // environment; clearing the record instead sends every later child the normalized values. + // environment; clearing the record instead makes this an empty object, so every later child + // inherits the normalized values from `process.env` untouched. expect(childProxyEnv().HTTP_PROXY).toBe(proxyUrl) - expect(childProxyEnv().http_proxy).toBeUndefined() + expect(childProxyEnv().https_proxy).toBe(proxyUrl) } finally { await disposeOuter() for (const [name, value] of Object.entries(saved)) { diff --git a/packages/net/http-proxy/tests/policy.spec.ts b/packages/net/http-proxy/tests/policy.spec.ts index b76da665fb..6db288e7a1 100644 --- a/packages/net/http-proxy/tests/policy.spec.ts +++ b/packages/net/http-proxy/tests/policy.spec.ts @@ -15,6 +15,9 @@ function env(values: Record): ReturnType { it('resolves nothing when the environment carries no proxy', () => { const { policy, diagnostics } = resolveProxyPolicy(env({})) @@ -32,7 +35,10 @@ describe('resolveProxyPolicy', () => { it('prefers the lowercase name, matching undici', () => { const { policy } = resolveProxyPolicy(env({ http_proxy: PROXY, HTTP_PROXY: OTHER })) - expect(policy.httpProxy).toBe(PROXY) + // Windows has no such preference to express: the launch snapshot folds names, so the two + // spellings are one variable there and the later entry is simply the value. Asserted rather + // than skipped, so a change to that folding fails here instead of passing unnoticed. + expect(policy.httpProxy).toBe(FOLDS_ENV_CASE ? OTHER : PROXY) }) it('treats a blank lowercase value as unset instead of letting it shadow the uppercase one', () => { @@ -95,7 +101,9 @@ describe('resolveProxyPolicy', () => { const { policy, diagnostics } = resolveProxyPolicy(env({ HTTP_PROXY: 'not a url' })) expect(policy).toEqual(DIRECT_POLICY) expect(diagnostics[0]?.kind).toBe('invalid') - expect(diagnostics[0]?.origin).toBe('HTTP_PROXY') + // The origin names the spelling resolution asked for, which on a folded environment is the + // lowercase one it tries first — the same variable the user set, reported in the other case. + expect(diagnostics[0]?.origin).toBe(FOLDS_ENV_CASE ? 'http_proxy' : 'HTTP_PROXY') }) it('reports a proxy URL whose scheme is neither http(s) nor SOCKS', () => {