mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 06:16:37 +00:00
Add 34 unit tests covering: - getProxyUrl: env var precedence (lowercase > uppercase > all_proxy) - shouldBypassProxy: wildcard, exact match, suffix patterns, case-insensitivity - getHostname: URL parsing with error handling - getProxyAgent: proxy creation, NO_PROXY bypass, error handling Export internal functions via __testExports for testability.
261 lines
8.8 KiB
TypeScript
261 lines
8.8 KiB
TypeScript
/**
|
|
* 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<string, string | undefined> = {};
|
|
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');
|
|
});
|
|
});
|
|
|
|
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 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 return false for invalid target URL', () => {
|
|
process.env.https_proxy = 'http://proxy:8080';
|
|
expect(getProxyAgent('not-a-url')).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
});
|