1+ import { describe , it , expect , vi , beforeEach , afterEach } from "vitest"
2+ import { getProxyForUrl } from "./proxy"
3+
4+ describe ( "proxy" , ( ) => {
5+ let originalEnv :NodeJS . ProcessEnv
6+
7+ beforeEach ( ( ) => {
8+ // Save original environment
9+ originalEnv = { ...process . env }
10+ // Clear relevant proxy environment variables
11+ delete process . env . http_proxy
12+ delete process . env . HTTP_PROXY
13+ delete process . env . https_proxy
14+ delete process . env . HTTPS_PROXY
15+ delete process . env . ftp_proxy
16+ delete process . env . FTP_PROXY
17+ delete process . env . all_proxy
18+ delete process . env . ALL_PROXY
19+ delete process . env . no_proxy
20+ delete process . env . NO_PROXY
21+ delete process . env . npm_config_proxy
22+ delete process . env . npm_config_http_proxy
23+ delete process . env . npm_config_https_proxy
24+ delete process . env . npm_config_no_proxy
25+ } )
26+
27+ afterEach ( ( ) => {
28+ // Restore original environment
29+ process . env = originalEnv
30+ } )
31+
32+ describe ( "getProxyForUrl" , ( ) => {
33+ describe ( "basic proxy resolution" , ( ) => {
34+ it ( "should return proxy when httpProxy parameter is provided" , ( ) => {
35+ const result = getProxyForUrl (
36+ "http://example.com" ,
37+ "http://proxy.example.com:8080" ,
38+ undefined
39+ )
40+ expect ( result ) . toBe ( "http://proxy.example.com:8080" )
41+ } )
42+
43+ it ( "should return empty string when no proxy is configured" , ( ) => {
44+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
45+ expect ( result ) . toBe ( "" )
46+ } )
47+
48+ it ( "should use environment variable when httpProxy parameter is not provided" , ( ) => {
49+ process . env . http_proxy = "http://env-proxy.example.com:8080"
50+
51+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
52+ expect ( result ) . toBe ( "http://env-proxy.example.com:8080" )
53+ } )
54+
55+ it ( "should prefer httpProxy parameter over environment variables" , ( ) => {
56+ process . env . http_proxy = "http://env-proxy.example.com:8080"
57+
58+ const result = getProxyForUrl (
59+ "http://example.com" ,
60+ "http://param-proxy.example.com:8080" ,
61+ undefined
62+ )
63+ expect ( result ) . toBe ( "http://param-proxy.example.com:8080" )
64+ } )
65+ } )
66+
67+ describe ( "protocol-specific proxy resolution" , ( ) => {
68+ it ( "should use http_proxy for HTTP URLs" , ( ) => {
69+ process . env . http_proxy = "http://http-proxy.example.com:8080"
70+ process . env . https_proxy = "http://https-proxy.example.com:8080"
71+
72+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
73+ expect ( result ) . toBe ( "http://http-proxy.example.com:8080" )
74+ } )
75+
76+ it ( "should use https_proxy for HTTPS URLs" , ( ) => {
77+ process . env . http_proxy = "http://http-proxy.example.com:8080"
78+ process . env . https_proxy = "http://https-proxy.example.com:8080"
79+
80+ const result = getProxyForUrl ( "https://example.com" , undefined , undefined )
81+ expect ( result ) . toBe ( "http://https-proxy.example.com:8080" )
82+ } )
83+
84+ it ( "should use ftp_proxy for FTP URLs" , ( ) => {
85+ process . env . ftp_proxy = "http://ftp-proxy.example.com:8080"
86+
87+ const result = getProxyForUrl ( "ftp://example.com" , undefined , undefined )
88+ expect ( result ) . toBe ( "http://ftp-proxy.example.com:8080" )
89+ } )
90+
91+ it ( "should fall back to all_proxy when protocol-specific proxy is not set" , ( ) => {
92+ process . env . all_proxy = "http://all-proxy.example.com:8080"
93+
94+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
95+ expect ( result ) . toBe ( "http://all-proxy.example.com:8080" )
96+ } )
97+ } )
98+
99+ describe ( "npm config proxy resolution" , ( ) => {
100+ it ( "should use npm_config_http_proxy" , ( ) => {
101+ process . env . npm_config_http_proxy = "http://npm-http-proxy.example.com:8080"
102+
103+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
104+ expect ( result ) . toBe ( "http://npm-http-proxy.example.com:8080" )
105+ } )
106+
107+ it ( "should use npm_config_proxy as fallback" , ( ) => {
108+ process . env . npm_config_proxy = "http://npm-proxy.example.com:8080"
109+
110+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
111+ expect ( result ) . toBe ( "http://npm-proxy.example.com:8080" )
112+ } )
113+
114+ it ( "should prefer protocol-specific over npm_config_proxy" , ( ) => {
115+ process . env . http_proxy = "http://http-proxy.example.com:8080"
116+ process . env . npm_config_proxy = "http://npm-proxy.example.com:8080"
117+
118+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
119+ expect ( result ) . toBe ( "http://http-proxy.example.com:8080" )
120+ } )
121+ } )
122+
123+ describe ( "proxy URL normalization" , ( ) => {
124+ it ( "should add protocol scheme when missing" , ( ) => {
125+ const result = getProxyForUrl (
126+ "http://example.com" ,
127+ "proxy.example.com:8080" ,
128+ undefined
129+ )
130+ expect ( result ) . toBe ( "http://proxy.example.com:8080" )
131+ } )
132+
133+ it ( "should not modify proxy URL when scheme is present" , ( ) => {
134+ const result = getProxyForUrl (
135+ "http://example.com" ,
136+ "https://proxy.example.com:8080" ,
137+ undefined
138+ )
139+ expect ( result ) . toBe ( "https://proxy.example.com:8080" )
140+ } )
141+
142+ it ( "should use target URL protocol for missing scheme" , ( ) => {
143+ const result = getProxyForUrl (
144+ "https://example.com" ,
145+ "proxy.example.com:8080" ,
146+ undefined
147+ )
148+ expect ( result ) . toBe ( "https://proxy.example.com:8080" )
149+ } )
150+ } )
151+
152+ describe ( "NO_PROXY handling" , ( ) => {
153+ it ( "should not proxy when host is in noProxy parameter" , ( ) => {
154+ const result = getProxyForUrl (
155+ "http://example.com" ,
156+ "http://proxy.example.com:8080" ,
157+ "example.com"
158+ )
159+ expect ( result ) . toBe ( "" )
160+ } )
161+
162+ it ( "should not proxy when host is in NO_PROXY environment variable" , ( ) => {
163+ process . env . NO_PROXY = "example.com"
164+
165+ const result = getProxyForUrl (
166+ "http://example.com" ,
167+ "http://proxy.example.com:8080" ,
168+ undefined
169+ )
170+ expect ( result ) . toBe ( "" )
171+ } )
172+
173+ it ( "should prefer noProxy parameter over NO_PROXY environment" , ( ) => {
174+ process . env . NO_PROXY = "other.com"
175+
176+ const result = getProxyForUrl (
177+ "http://example.com" ,
178+ "http://proxy.example.com:8080" ,
179+ "example.com"
180+ )
181+ expect ( result ) . toBe ( "" )
182+ } )
183+
184+ it ( "should handle wildcard NO_PROXY" , ( ) => {
185+ const result = getProxyForUrl (
186+ "http://example.com" ,
187+ "http://proxy.example.com:8080" ,
188+ "*"
189+ )
190+ expect ( result ) . toBe ( "" )
191+ } )
192+
193+ it ( "should handle comma-separated NO_PROXY list" , ( ) => {
194+ const result = getProxyForUrl (
195+ "http://example.com" ,
196+ "http://proxy.example.com:8080" ,
197+ "other.com,example.com,another.com"
198+ )
199+ expect ( result ) . toBe ( "" )
200+ } )
201+
202+ it ( "should handle space-separated NO_PROXY list" , ( ) => {
203+ const result = getProxyForUrl (
204+ "http://example.com" ,
205+ "http://proxy.example.com:8080" ,
206+ "other.com example.com another.com"
207+ )
208+ expect ( result ) . toBe ( "" )
209+ } )
210+
211+ it ( "should handle wildcard subdomain matching" , ( ) => {
212+ const result = getProxyForUrl (
213+ "http://sub.example.com" ,
214+ "http://proxy.example.com:8080" ,
215+ "*.example.com"
216+ )
217+ expect ( result ) . toBe ( "" )
218+ } )
219+
220+ it ( "should handle domain suffix matching" , ( ) => {
221+ const result = getProxyForUrl (
222+ "http://sub.example.com" ,
223+ "http://proxy.example.com:8080" ,
224+ ".example.com"
225+ )
226+ expect ( result ) . toBe ( "" )
227+ } )
228+
229+ it ( "should match port-specific NO_PROXY rules" , ( ) => {
230+ const result = getProxyForUrl (
231+ "http://example.com:8080" ,
232+ "http://proxy.example.com:8080" ,
233+ "example.com:8080"
234+ )
235+ expect ( result ) . toBe ( "" )
236+ } )
237+
238+ it ( "should not match when ports differ in NO_PROXY rule" , ( ) => {
239+ const result = getProxyForUrl (
240+ "http://example.com:8080" ,
241+ "http://proxy.example.com:8080" ,
242+ "example.com:9090"
243+ )
244+ expect ( result ) . toBe ( "http://proxy.example.com:8080" )
245+ } )
246+
247+ it ( "should handle case-insensitive NO_PROXY matching" , ( ) => {
248+ const result = getProxyForUrl (
249+ "http://EXAMPLE.COM" ,
250+ "http://proxy.example.com:8080" ,
251+ "example.com"
252+ )
253+ expect ( result ) . toBe ( "" )
254+ } )
255+ } )
256+
257+ describe ( "default ports" , ( ) => {
258+ it ( "should use default HTTP port 80" , ( ) => {
259+ const result = getProxyForUrl (
260+ "http://example.com" ,
261+ "http://proxy.example.com:8080" ,
262+ "example.com:80"
263+ )
264+ expect ( result ) . toBe ( "" )
265+ } )
266+
267+ it ( "should use default HTTPS port 443" , ( ) => {
268+ const result = getProxyForUrl (
269+ "https://example.com" ,
270+ "http://proxy.example.com:8080" ,
271+ "example.com:443"
272+ )
273+ expect ( result ) . toBe ( "" )
274+ } )
275+
276+ it ( "should use default FTP port 21" , ( ) => {
277+ const result = getProxyForUrl (
278+ "ftp://example.com" ,
279+ "http://proxy.example.com:8080" ,
280+ "example.com:21"
281+ )
282+ expect ( result ) . toBe ( "" )
283+ } )
284+
285+ it ( "should use default WebSocket port 80" , ( ) => {
286+ const result = getProxyForUrl (
287+ "ws://example.com" ,
288+ "http://proxy.example.com:8080" ,
289+ "example.com:80"
290+ )
291+ expect ( result ) . toBe ( "" )
292+ } )
293+
294+ it ( "should use default secure WebSocket port 443" , ( ) => {
295+ const result = getProxyForUrl (
296+ "wss://example.com" ,
297+ "http://proxy.example.com:8080" ,
298+ "example.com:443"
299+ )
300+ expect ( result ) . toBe ( "" )
301+ } )
302+ } )
303+
304+ describe ( "edge cases" , ( ) => {
305+ it ( "should return empty string for URLs without protocol" , ( ) => {
306+ const result = getProxyForUrl (
307+ "example.com" ,
308+ "http://proxy.example.com:8080" ,
309+ undefined
310+ )
311+ expect ( result ) . toBe ( "" )
312+ } )
313+
314+ it ( "should return empty string for URLs without hostname" , ( ) => {
315+ const result = getProxyForUrl (
316+ "http://" ,
317+ "http://proxy.example.com:8080" ,
318+ undefined
319+ )
320+ expect ( result ) . toBe ( "" )
321+ } )
322+
323+ it ( "should handle IPv6 addresses" , ( ) => {
324+ const result = getProxyForUrl (
325+ "http://[2001:db8::1]:8080" ,
326+ "http://proxy.example.com:8080" ,
327+ undefined
328+ )
329+ expect ( result ) . toBe ( "http://proxy.example.com:8080" )
330+ } )
331+
332+ it ( "should handle IPv6 addresses in NO_PROXY" , ( ) => {
333+ const result = getProxyForUrl (
334+ "http://[2001:db8::1]:8080" ,
335+ "http://proxy.example.com:8080" ,
336+ "[2001:db8::1]:8080"
337+ )
338+ expect ( result ) . toBe ( "" )
339+ } )
340+
341+ it ( "should handle empty NO_PROXY entries" , ( ) => {
342+ const result = getProxyForUrl (
343+ "http://example.com" ,
344+ "http://proxy.example.com:8080" ,
345+ ",, example.com ,,"
346+ )
347+ expect ( result ) . toBe ( "" )
348+ } )
349+
350+ it ( "should handle null proxy configuration" , ( ) => {
351+ const result = getProxyForUrl ( "http://example.com" , null , null )
352+ expect ( result ) . toBe ( "" )
353+ } )
354+
355+ it ( "should be case-insensitive for environment variable names" , ( ) => {
356+ process . env . HTTP_PROXY = "http://upper-proxy.example.com:8080"
357+ process . env . http_proxy = "http://lower-proxy.example.com:8080"
358+
359+ // Should prefer lowercase
360+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
361+ expect ( result ) . toBe ( "http://lower-proxy.example.com:8080" )
362+ } )
363+
364+ it ( "should fall back to uppercase environment variables" , ( ) => {
365+ process . env . HTTP_PROXY = "http://upper-proxy.example.com:8080"
366+ // Don't set lowercase version
367+
368+ const result = getProxyForUrl ( "http://example.com" , undefined , undefined )
369+ expect ( result ) . toBe ( "http://upper-proxy.example.com:8080" )
370+ } )
371+ } )
372+ } )
373+ } )