diff --git a/src/docker-manager-utils.test.ts b/src/docker-manager-utils.test.ts index d0c926c5a..a1bb41789 100644 --- a/src/docker-manager-utils.test.ts +++ b/src/docker-manager-utils.test.ts @@ -1,4 +1,4 @@ -import { subnetsOverlap, validateIdNotInSystemRange, getSafeHostUid, getSafeHostGid, getRealUserHome, extractGhHostFromServerUrl, readGitHubPathEntries, mergeGitHubPathEntries, readGitHubEnvEntries, parseGitHubEnvFile, readEnvFile, MIN_REGULAR_UID, ACT_PRESET_BASE_IMAGE, stripScheme } from './host-env'; +import { testHelpers, validateIdNotInSystemRange, getSafeHostUid, getSafeHostGid, getRealUserHome, extractGhHostFromServerUrl, readGitHubPathEntries, mergeGitHubPathEntries, readGitHubEnvEntries, parseGitHubEnvFile, readEnvFile, MIN_REGULAR_UID, ACT_PRESET_BASE_IMAGE, stripScheme } from './host-env'; import { parseDifcProxyHost } from './docker-manager'; import * as fs from 'fs'; import * as path from 'path'; @@ -14,31 +14,31 @@ describe('docker-manager utilities', () => { describe('subnetsOverlap', () => { it('should detect overlapping subnets with same CIDR', () => { - expect(subnetsOverlap('172.30.0.0/24', '172.30.0.0/24')).toBe(true); + expect(testHelpers.subnetsOverlap('172.30.0.0/24', '172.30.0.0/24')).toBe(true); }); it('should detect non-overlapping subnets', () => { - expect(subnetsOverlap('172.30.0.0/24', '172.31.0.0/24')).toBe(false); - expect(subnetsOverlap('192.168.1.0/24', '192.168.2.0/24')).toBe(false); + expect(testHelpers.subnetsOverlap('172.30.0.0/24', '172.31.0.0/24')).toBe(false); + expect(testHelpers.subnetsOverlap('192.168.1.0/24', '192.168.2.0/24')).toBe(false); }); it('should detect when smaller subnet is inside larger subnet', () => { - expect(subnetsOverlap('172.16.0.0/16', '172.16.5.0/24')).toBe(true); - expect(subnetsOverlap('172.16.5.0/24', '172.16.0.0/16')).toBe(true); + expect(testHelpers.subnetsOverlap('172.16.0.0/16', '172.16.5.0/24')).toBe(true); + expect(testHelpers.subnetsOverlap('172.16.5.0/24', '172.16.0.0/16')).toBe(true); }); it('should detect partial overlap', () => { - expect(subnetsOverlap('172.30.0.0/23', '172.30.1.0/24')).toBe(true); + expect(testHelpers.subnetsOverlap('172.30.0.0/23', '172.30.1.0/24')).toBe(true); }); it('should handle Docker default bridge network', () => { - expect(subnetsOverlap('172.17.0.0/16', '172.17.5.0/24')).toBe(true); - expect(subnetsOverlap('172.17.0.0/16', '172.18.0.0/16')).toBe(false); + expect(testHelpers.subnetsOverlap('172.17.0.0/16', '172.17.5.0/24')).toBe(true); + expect(testHelpers.subnetsOverlap('172.17.0.0/16', '172.18.0.0/16')).toBe(false); }); it('should handle /32 (single host) networks', () => { - expect(subnetsOverlap('192.168.1.1/32', '192.168.1.1/32')).toBe(true); - expect(subnetsOverlap('192.168.1.1/32', '192.168.1.2/32')).toBe(false); + expect(testHelpers.subnetsOverlap('192.168.1.1/32', '192.168.1.1/32')).toBe(true); + expect(testHelpers.subnetsOverlap('192.168.1.1/32', '192.168.1.2/32')).toBe(false); }); }); diff --git a/src/domain-patterns.ts b/src/domain-patterns.ts index f60e12253..06148bc6b 100644 --- a/src/domain-patterns.ts +++ b/src/domain-patterns.ts @@ -88,6 +88,8 @@ export function isWildcardPattern(domain: string): boolean { * Uses character class instead of .* to prevent catastrophic backtracking (ReDoS). * Per RFC 1035, valid domain characters are: letters, digits, hyphens, and dots. */ +/** @internal Exported only for test assertions — not part of the public API. */ +// ts-prune-ignore-next export const DOMAIN_CHAR_PATTERN = '[a-zA-Z0-9.-]*'; /** diff --git a/src/host-env.ts b/src/host-env.ts index 0c903ff0f..e7ce6b252 100644 --- a/src/host-env.ts +++ b/src/host-env.ts @@ -415,7 +415,7 @@ export function readEnvFile(filePath: string): Record { * Checks if two subnets overlap * Returns true if the new subnet conflicts with an existing subnet */ -export function subnetsOverlap(subnet1: string, subnet2: string): boolean { +function subnetsOverlap(subnet1: string, subnet2: string): boolean { // Parse CIDR notation: "172.17.0.0/16" -> ["172.17.0.0", "16"] const [ip1, cidr1] = subnet1.split('/'); const [ip2, cidr2] = subnet2.split('/'); @@ -442,6 +442,10 @@ export function subnetsOverlap(subnet1: string, subnet2: string): boolean { return (start1 <= end2 && end1 >= start2); } +/** @internal Exposed only for unit tests — not part of the public API. */ +// ts-prune-ignore-next +export const testHelpers = { subnetsOverlap }; + /** * SSL configuration for Docker Compose (when SSL Bump is enabled) */ diff --git a/src/host-iptables-cleanup.test.ts b/src/host-iptables-cleanup.test.ts index 1c83f5389..80409c34f 100644 --- a/src/host-iptables-cleanup.test.ts +++ b/src/host-iptables-cleanup.test.ts @@ -1,9 +1,9 @@ import { execaResult, mockedExeca, setupHostIptablesTestSuite } from './test-helpers/host-iptables-test-setup'; import { cleanupHostIptables, setupHostIptables } from './host-iptables'; -import { _testing } from './host-iptables-shared'; +import { testHelpers } from './host-iptables-shared'; describe('host-iptables (cleanup)', () => { - setupHostIptablesTestSuite(_testing.resetIpv6State); + setupHostIptablesTestSuite(testHelpers.resetIpv6State); describe('cleanupHostIptables', () => { it('should flush and delete both FW_WRAPPER and FW_WRAPPER_V6 chains', async () => { diff --git a/src/host-iptables-doh.test.ts b/src/host-iptables-doh.test.ts index b0f54a90f..21f370a1d 100644 --- a/src/host-iptables-doh.test.ts +++ b/src/host-iptables-doh.test.ts @@ -1,9 +1,9 @@ import { execaResult, mockedExeca, setupHostIptablesTestSuite } from './test-helpers/host-iptables-test-setup'; import { setupHostIptables } from './host-iptables'; -import { _testing } from './host-iptables-shared'; +import { testHelpers } from './host-iptables-shared'; describe('host-iptables (doh)', () => { - setupHostIptablesTestSuite(_testing.resetIpv6State); + setupHostIptablesTestSuite(testHelpers.resetIpv6State); describe('setupHostIptables with DoH proxy', () => { it('should add HTTPS ACCEPT rule for DoH proxy when dohProxyIp is provided', async () => { diff --git a/src/host-iptables-host-access.test.ts b/src/host-iptables-host-access.test.ts index 574596f4a..9bbd5c07b 100644 --- a/src/host-iptables-host-access.test.ts +++ b/src/host-iptables-host-access.test.ts @@ -1,9 +1,9 @@ import { mockedExeca, setupDefaultIptablesMocks, setupHostIptablesTestSuite } from './test-helpers/host-iptables-test-setup'; import { HostAccessConfig, setupHostIptables } from './host-iptables'; -import { _testing } from './host-iptables-shared'; +import { testHelpers } from './host-iptables-shared'; describe('host-iptables (host access)', () => { - setupHostIptablesTestSuite(_testing.resetIpv6State); + setupHostIptablesTestSuite(testHelpers.resetIpv6State); describe('setupHostIptables with host access', () => { it('should add gateway ACCEPT rules when hostAccess is enabled', async () => { diff --git a/src/host-iptables-network.test.ts b/src/host-iptables-network.test.ts index f67867127..b06d4f33b 100644 --- a/src/host-iptables-network.test.ts +++ b/src/host-iptables-network.test.ts @@ -1,10 +1,10 @@ import { execaResult, mockedExeca, setupHostIptablesTestSuite } from './test-helpers/host-iptables-test-setup'; import { cleanupFirewallNetwork } from './host-iptables-network'; import { ensureFirewallNetwork } from './host-iptables'; -import { _testing } from './host-iptables-shared'; +import { testHelpers } from './host-iptables-shared'; describe('host-iptables (network)', () => { - setupHostIptablesTestSuite(_testing.resetIpv6State); + setupHostIptablesTestSuite(testHelpers.resetIpv6State); describe('ensureFirewallNetwork', () => { it('should return network config when network already exists', async () => { diff --git a/src/host-iptables-setup.test.ts b/src/host-iptables-setup.test.ts index b423aaedb..4b68f9fc9 100644 --- a/src/host-iptables-setup.test.ts +++ b/src/host-iptables-setup.test.ts @@ -2,13 +2,13 @@ import { API_PROXY_PORTS } from './types'; import { execaError, execaResult, mockedExeca, setupDefaultIptablesMocks, setupHostIptablesTestSuite } from './test-helpers/host-iptables-test-setup'; import { isValidPortSpec } from './host-iptables-rules'; import { setupHostIptables } from './host-iptables'; -import { _testing } from './host-iptables-shared'; +import { testHelpers } from './host-iptables-shared'; // setupHostIptables intentionally allows the inclusive min:max API proxy port window. const apiProxyPortRange = `${Math.min(...Object.values(API_PROXY_PORTS))}:${Math.max(...Object.values(API_PROXY_PORTS))}`; describe('host-iptables (setup)', () => { - setupHostIptablesTestSuite(_testing.resetIpv6State); + setupHostIptablesTestSuite(testHelpers.resetIpv6State); describe('setupHostIptables', () => { it('should throw error if iptables permission denied', async () => { diff --git a/src/host-iptables-shared.ts b/src/host-iptables-shared.ts index e6d3c841c..0807137ce 100644 --- a/src/host-iptables-shared.ts +++ b/src/host-iptables-shared.ts @@ -22,9 +22,9 @@ function resetIpv6State(): void { ipv6DisabledViaSysctl = false; } -/** @internal Test-only helpers */ +/** @internal Exposed only for unit tests — not part of the public API. */ // ts-prune-ignore-next -export const _testing = { resetIpv6State }; +export const testHelpers = { resetIpv6State }; /** * Gets the bridge interface name for the firewall network