mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-15 04:18:19 +00:00
fix(cliproxy): add comprehensive port validation across proxy system
- Export validatePort() from config-generator with full edge case handling - Add YAML port validation in proxy-config-resolver before use - Add final port validation in cliproxy-executor after config merge - Add default port parameter and validation to proxy-detector - Add 38 new tests covering all port validation edge cases Fixes undefined port bug when upgrading from 7.11.1 to 7.12.0
This commit is contained in:
1 parent
4c35e8a39e
commit
e0a1f8f312
6 files changed
+327
-14
No files matched your search
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Port Validation Tests
|
||||
*
|
||||
* Comprehensive test suite for port validation across the CLIProxy system.
|
||||
* Tests the fix for undefined port bug (7.11.1 → 7.12.0 regression).
|
||||
*
|
||||
* Covers:
|
||||
* - validatePort() function edge cases
|
||||
* - Object spread undefined filtering in executor
|
||||
* - YAML port validation in resolver
|
||||
* - detectRunningProxy with invalid ports
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import {
|
||||
validatePort,
|
||||
CLIPROXY_DEFAULT_PORT,
|
||||
} from '../../../dist/cliproxy/config-generator';
|
||||
import { resolveProxyConfig } from '../../../dist/cliproxy/proxy-config-resolver';
|
||||
|
||||
describe('Port Validation', () => {
|
||||
describe('validatePort()', () => {
|
||||
describe('returns default port for invalid inputs', () => {
|
||||
it('handles undefined', () => {
|
||||
expect(validatePort(undefined)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles null', () => {
|
||||
expect(validatePort(null as unknown as number)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles NaN', () => {
|
||||
expect(validatePort(NaN)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles Infinity', () => {
|
||||
expect(validatePort(Infinity)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles negative Infinity', () => {
|
||||
expect(validatePort(-Infinity)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles zero', () => {
|
||||
expect(validatePort(0)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles negative numbers', () => {
|
||||
expect(validatePort(-1)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
expect(validatePort(-8317)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles ports > 65535', () => {
|
||||
expect(validatePort(65536)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
expect(validatePort(100000)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles floating point numbers', () => {
|
||||
expect(validatePort(8317.5)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
expect(validatePort(8317.999)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('returns valid port for valid inputs', () => {
|
||||
it('handles port 1 (minimum valid)', () => {
|
||||
expect(validatePort(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('handles port 65535 (maximum valid)', () => {
|
||||
expect(validatePort(65535)).toBe(65535);
|
||||
});
|
||||
|
||||
it('handles default port 8317', () => {
|
||||
expect(validatePort(8317)).toBe(8317);
|
||||
});
|
||||
|
||||
it('handles common ports', () => {
|
||||
expect(validatePort(80)).toBe(80);
|
||||
expect(validatePort(443)).toBe(443);
|
||||
expect(validatePort(3000)).toBe(3000);
|
||||
expect(validatePort(8080)).toBe(8080);
|
||||
});
|
||||
|
||||
it('handles variant ports (8318-8417)', () => {
|
||||
expect(validatePort(8318)).toBe(8318);
|
||||
expect(validatePort(8350)).toBe(8350);
|
||||
expect(validatePort(8417)).toBe(8417);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Object Spread Undefined Filtering', () => {
|
||||
/**
|
||||
* Bug: `{ ...DEFAULT_CONFIG, ...{ port: undefined } }` overwrites port with undefined
|
||||
* Fix: Filter undefined values before spreading
|
||||
*/
|
||||
it('demonstrates the original bug pattern', () => {
|
||||
const DEFAULT_CONFIG = { port: 8317, timeout: 2000 };
|
||||
const config = { port: undefined, timeout: undefined };
|
||||
|
||||
// BUG: Direct spread overwrites with undefined
|
||||
const bugResult = { ...DEFAULT_CONFIG, ...config };
|
||||
expect(bugResult.port).toBe(undefined); // This was the bug!
|
||||
|
||||
// FIX: Filter undefined values before spreading
|
||||
const filteredConfig = Object.fromEntries(
|
||||
Object.entries(config).filter(([, v]) => v !== undefined)
|
||||
);
|
||||
const fixResult = { ...DEFAULT_CONFIG, ...filteredConfig };
|
||||
expect(fixResult.port).toBe(8317); // Fixed!
|
||||
});
|
||||
|
||||
it('preserves explicit values while filtering undefined', () => {
|
||||
const DEFAULT_CONFIG = { port: 8317, timeout: 2000, protocol: 'http' };
|
||||
const config = { port: 9000, timeout: undefined, protocol: undefined };
|
||||
|
||||
const filteredConfig = Object.fromEntries(
|
||||
Object.entries(config).filter(([, v]) => v !== undefined)
|
||||
);
|
||||
const result = { ...DEFAULT_CONFIG, ...filteredConfig };
|
||||
|
||||
expect(result.port).toBe(9000); // Explicit value preserved
|
||||
expect(result.timeout).toBe(2000); // Default preserved (undefined filtered)
|
||||
expect(result.protocol).toBe('http'); // Default preserved (undefined filtered)
|
||||
});
|
||||
});
|
||||
|
||||
describe('YAML Port Validation in resolveProxyConfig', () => {
|
||||
it('validates YAML remote port', () => {
|
||||
const { config } = resolveProxyConfig([], {
|
||||
remote: { host: 'example.com', port: 9000 },
|
||||
});
|
||||
expect(config.port).toBe(9000);
|
||||
});
|
||||
|
||||
it('falls back to default for invalid YAML port (negative)', () => {
|
||||
const { config } = resolveProxyConfig([], {
|
||||
remote: { host: 'example.com', port: -1 },
|
||||
});
|
||||
expect(config.port).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('falls back to default for invalid YAML port (> 65535)', () => {
|
||||
const { config } = resolveProxyConfig([], {
|
||||
remote: { host: 'example.com', port: 70000 },
|
||||
});
|
||||
expect(config.port).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('falls back to default for invalid YAML port (zero)', () => {
|
||||
const { config } = resolveProxyConfig([], {
|
||||
remote: { host: 'example.com', port: 0 },
|
||||
});
|
||||
expect(config.port).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('validates YAML local port', () => {
|
||||
const { config } = resolveProxyConfig([], {
|
||||
local: { port: 9000 },
|
||||
});
|
||||
expect(config.port).toBe(9000);
|
||||
});
|
||||
|
||||
it('falls back to default for invalid YAML local port', () => {
|
||||
const { config } = resolveProxyConfig([], {
|
||||
local: { port: -100 },
|
||||
});
|
||||
expect(config.port).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('CLI port overrides invalid YAML port', () => {
|
||||
const { config } = resolveProxyConfig(['--proxy-port', '8080'], {
|
||||
remote: { host: 'example.com', port: -1 },
|
||||
});
|
||||
expect(config.port).toBe(8080);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Priority Chain with Invalid Values', () => {
|
||||
it('CLI valid > ENV valid > YAML invalid', () => {
|
||||
process.env.CCS_PROXY_PORT = '9000';
|
||||
const { config } = resolveProxyConfig(['--proxy-port', '8080'], {
|
||||
remote: { host: 'example.com', port: -1 },
|
||||
});
|
||||
expect(config.port).toBe(8080); // CLI wins
|
||||
delete process.env.CCS_PROXY_PORT;
|
||||
});
|
||||
|
||||
it('ENV valid > YAML invalid > default', () => {
|
||||
process.env.CCS_PROXY_PORT = '9000';
|
||||
const { config } = resolveProxyConfig([], {
|
||||
remote: { host: 'example.com', port: -1 },
|
||||
});
|
||||
expect(config.port).toBe(9000); // ENV wins
|
||||
delete process.env.CCS_PROXY_PORT;
|
||||
});
|
||||
|
||||
it('YAML valid when CLI and ENV not set', () => {
|
||||
const { config } = resolveProxyConfig([], {
|
||||
remote: { host: 'example.com', port: 9000 },
|
||||
});
|
||||
expect(config.port).toBe(9000);
|
||||
});
|
||||
|
||||
it('default when all sources invalid/missing', () => {
|
||||
const { config } = resolveProxyConfig([], {});
|
||||
expect(config.port).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Boundary Conditions', () => {
|
||||
it('handles port exactly at minimum boundary (1)', () => {
|
||||
expect(validatePort(1)).toBe(1);
|
||||
expect(validatePort(0)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles port exactly at maximum boundary (65535)', () => {
|
||||
expect(validatePort(65535)).toBe(65535);
|
||||
expect(validatePort(65536)).toBe(CLIPROXY_DEFAULT_PORT);
|
||||
});
|
||||
|
||||
it('handles reserved ports (< 1024)', () => {
|
||||
// Reserved ports are still valid from validation perspective
|
||||
expect(validatePort(22)).toBe(22);
|
||||
expect(validatePort(80)).toBe(80);
|
||||
expect(validatePort(443)).toBe(443);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Proxy Detector Port Validation Tests
|
||||
*
|
||||
* Tests for detectRunningProxy with invalid port inputs.
|
||||
* Verifies the fix handles undefined, null, and invalid ports gracefully.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { detectRunningProxy } from '../../../dist/cliproxy/proxy-detector';
|
||||
import { CLIPROXY_DEFAULT_PORT } from '../../../dist/cliproxy/config-generator';
|
||||
|
||||
describe('Proxy Detector Port Validation', () => {
|
||||
describe('detectRunningProxy with invalid ports', () => {
|
||||
it('handles undefined port (uses default)', async () => {
|
||||
// TypeScript allows this at runtime via any/unknown
|
||||
const result = await detectRunningProxy(undefined as unknown as number);
|
||||
// Should not throw, should use default port
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.running).toBe('boolean');
|
||||
});
|
||||
|
||||
it('handles NaN port (uses default)', async () => {
|
||||
const result = await detectRunningProxy(NaN);
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.running).toBe('boolean');
|
||||
});
|
||||
|
||||
it('handles negative port (uses default)', async () => {
|
||||
const result = await detectRunningProxy(-1);
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.running).toBe('boolean');
|
||||
});
|
||||
|
||||
it('handles zero port (uses default)', async () => {
|
||||
const result = await detectRunningProxy(0);
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.running).toBe('boolean');
|
||||
});
|
||||
|
||||
it('handles port > 65535 (uses default)', async () => {
|
||||
const result = await detectRunningProxy(70000);
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.running).toBe('boolean');
|
||||
});
|
||||
|
||||
it('handles valid port correctly', async () => {
|
||||
const result = await detectRunningProxy(CLIPROXY_DEFAULT_PORT);
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.running).toBe('boolean');
|
||||
expect(typeof result.verified).toBe('boolean');
|
||||
});
|
||||
|
||||
it('handles variant port correctly', async () => {
|
||||
const result = await detectRunningProxy(8318);
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.running).toBe('boolean');
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectRunningProxy default parameter', () => {
|
||||
it('works without port argument (uses default)', async () => {
|
||||
const result = await detectRunningProxy();
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.running).toBe('boolean');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user