/** * Binary Downloader Proxy Support Tests * * Tests for proxy environment variable detection and NO_PROXY bypass logic. */ import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; import { __testExports } from '../../../src/cliproxy/binary/downloader'; const { getProxyUrl, shouldBypassProxy, getHostname, getProxyAgent } = __testExports; describe('Binary Downloader Proxy Support', () => { // Store original env vars to restore after tests const originalEnv: Record = {}; const proxyEnvVars = [ 'http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY', 'all_proxy', 'ALL_PROXY', 'no_proxy', 'NO_PROXY', ]; beforeEach(() => { // Save original values proxyEnvVars.forEach((key) => { originalEnv[key] = process.env[key]; delete process.env[key]; }); }); afterEach(() => { // Restore original values proxyEnvVars.forEach((key) => { if (originalEnv[key] !== undefined) { process.env[key] = originalEnv[key]; } else { delete process.env[key]; } }); }); describe('getProxyUrl', () => { describe('HTTPS requests', () => { it('should return undefined when no proxy env vars are set', () => { expect(getProxyUrl(true)).toBeUndefined(); }); it('should prefer https_proxy (lowercase)', () => { process.env.https_proxy = 'http://proxy1:8080'; process.env.HTTPS_PROXY = 'http://proxy2:8080'; expect(getProxyUrl(true)).toBe('http://proxy1:8080'); }); it('should fall back to HTTPS_PROXY (uppercase)', () => { process.env.HTTPS_PROXY = 'http://proxy:8080'; expect(getProxyUrl(true)).toBe('http://proxy:8080'); }); it('should fall back to all_proxy', () => { process.env.all_proxy = 'http://allproxy:8080'; expect(getProxyUrl(true)).toBe('http://allproxy:8080'); }); it('should fall back to ALL_PROXY', () => { process.env.ALL_PROXY = 'http://allproxy:8080'; expect(getProxyUrl(true)).toBe('http://allproxy:8080'); }); it('should skip invalid HTTP_PROXY and fall back to ALL_PROXY', () => { process.env.HTTP_PROXY = 'not-a-valid-url'; process.env.ALL_PROXY = 'http://allproxy:8080'; expect(getProxyUrl(true)).toBe('http://allproxy:8080'); }); }); describe('HTTP requests', () => { it('should return undefined when no proxy env vars are set', () => { expect(getProxyUrl(false)).toBeUndefined(); }); it('should prefer http_proxy (lowercase)', () => { process.env.http_proxy = 'http://proxy1:8080'; process.env.HTTP_PROXY = 'http://proxy2:8080'; expect(getProxyUrl(false)).toBe('http://proxy1:8080'); }); it('should fall back to HTTP_PROXY (uppercase)', () => { process.env.HTTP_PROXY = 'http://proxy:8080'; expect(getProxyUrl(false)).toBe('http://proxy:8080'); }); it('should fall back to all_proxy for HTTP', () => { process.env.all_proxy = 'http://allproxy:8080'; expect(getProxyUrl(false)).toBe('http://allproxy:8080'); }); it('should skip invalid HTTP_PROXY and use ALL_PROXY for HTTP', () => { process.env.HTTP_PROXY = 'not-a-valid-url'; process.env.ALL_PROXY = 'http://allproxy:8080'; expect(getProxyUrl(false)).toBe('http://allproxy:8080'); }); it('should not use https_proxy for HTTP requests', () => { process.env.https_proxy = 'http://httpsproxy:8080'; expect(getProxyUrl(false)).toBeUndefined(); }); }); }); describe('shouldBypassProxy', () => { describe('when NO_PROXY is not set', () => { it('should return false', () => { expect(shouldBypassProxy('example.com')).toBe(false); }); }); describe('wildcard pattern', () => { it('should bypass all hosts with *', () => { process.env.NO_PROXY = '*'; expect(shouldBypassProxy('example.com')).toBe(true); expect(shouldBypassProxy('any.host.here')).toBe(true); }); }); describe('exact match', () => { it('should match exact hostname', () => { process.env.NO_PROXY = 'example.com'; expect(shouldBypassProxy('example.com')).toBe(true); expect(shouldBypassProxy('other.com')).toBe(false); }); it('should be case-insensitive', () => { process.env.NO_PROXY = 'Example.COM'; expect(shouldBypassProxy('example.com')).toBe(true); expect(shouldBypassProxy('EXAMPLE.COM')).toBe(true); }); it('should not match subdomains for exact pattern', () => { process.env.NO_PROXY = 'example.com'; expect(shouldBypassProxy('sub.example.com')).toBe(true); // matches as suffix expect(shouldBypassProxy('notexample.com')).toBe(false); }); }); describe('domain suffix pattern', () => { it('should match subdomains with leading dot', () => { process.env.NO_PROXY = '.example.com'; expect(shouldBypassProxy('sub.example.com')).toBe(true); expect(shouldBypassProxy('deep.sub.example.com')).toBe(true); expect(shouldBypassProxy('example.com')).toBe(true); // exact match without dot }); it('should not match different domains', () => { process.env.NO_PROXY = '.example.com'; expect(shouldBypassProxy('notexample.com')).toBe(false); expect(shouldBypassProxy('example.org')).toBe(false); }); }); describe('multiple patterns', () => { it('should check comma-separated list', () => { process.env.NO_PROXY = 'localhost,127.0.0.1,.internal.corp'; expect(shouldBypassProxy('localhost')).toBe(true); expect(shouldBypassProxy('127.0.0.1')).toBe(true); expect(shouldBypassProxy('api.internal.corp')).toBe(true); expect(shouldBypassProxy('external.com')).toBe(false); }); it('should handle whitespace in list', () => { process.env.NO_PROXY = ' localhost , example.com , .corp '; expect(shouldBypassProxy('localhost')).toBe(true); expect(shouldBypassProxy('example.com')).toBe(true); expect(shouldBypassProxy('host.corp')).toBe(true); }); }); describe('lowercase no_proxy', () => { it('should respect no_proxy (lowercase)', () => { process.env.no_proxy = 'example.com'; expect(shouldBypassProxy('example.com')).toBe(true); }); it('should prefer no_proxy over NO_PROXY', () => { process.env.no_proxy = 'lower.com'; process.env.NO_PROXY = 'upper.com'; expect(shouldBypassProxy('lower.com')).toBe(true); expect(shouldBypassProxy('upper.com')).toBe(false); }); }); }); describe('getHostname', () => { it('should extract hostname from HTTPS URL', () => { expect(getHostname('https://example.com/path')).toBe('example.com'); }); it('should extract hostname from HTTP URL', () => { expect(getHostname('http://api.github.com:443/repos')).toBe('api.github.com'); }); it('should return empty string for invalid URL', () => { expect(getHostname('not-a-url')).toBe(''); expect(getHostname('')).toBe(''); }); it('should handle localhost', () => { expect(getHostname('http://localhost:8080')).toBe('localhost'); }); it('should handle IP addresses', () => { expect(getHostname('http://192.168.1.1:3000')).toBe('192.168.1.1'); }); }); describe('getProxyAgent', () => { describe('when no proxy is configured', () => { it('should return false for HTTPS URLs', () => { expect(getProxyAgent('https://example.com')).toBe(false); }); it('should return false for HTTP URLs', () => { expect(getProxyAgent('http://example.com')).toBe(false); }); }); describe('when proxy is configured', () => { it('should return HttpsProxyAgent for HTTPS URLs', () => { process.env.https_proxy = 'http://proxy:8080'; const agent = getProxyAgent('https://github.com/releases'); expect(agent).not.toBe(false); expect(agent).toBeDefined(); }); it('should return HttpProxyAgent for HTTP URLs', () => { process.env.http_proxy = 'http://proxy:8080'; const agent = getProxyAgent('http://example.com'); expect(agent).not.toBe(false); expect(agent).toBeDefined(); }); }); describe('NO_PROXY bypass', () => { it('should bypass proxy for NO_PROXY hosts', () => { process.env.https_proxy = 'http://proxy:8080'; process.env.NO_PROXY = 'github.com'; expect(getProxyAgent('https://github.com/releases')).toBe(false); }); it('should use proxy for non-bypassed hosts', () => { process.env.https_proxy = 'http://proxy:8080'; process.env.NO_PROXY = 'internal.corp'; const agent = getProxyAgent('https://github.com/releases'); expect(agent).not.toBe(false); }); }); describe('error handling', () => { it('should return false for invalid proxy URL', () => { process.env.https_proxy = 'not-a-valid-url'; expect(getProxyAgent('https://example.com')).toBe(false); }); it('should keep supporting socks proxy URLs for downloader traffic', () => { process.env.https_proxy = 'socks5://proxy:1080'; expect(getProxyAgent('https://example.com')).not.toBe(false); }); it('should return false for invalid target URL', () => { process.env.https_proxy = 'http://proxy:8080'; expect(getProxyAgent('not-a-url')).toBe(false); }); }); }); });