Files

193 lines
5.9 KiB
TypeScript

import { describe, test, expect } from 'bun:test';
import { parseHostPort, validatePort, validateIp, expandPortBindings, formatHostPort } from '../src/lib/utils/port-parse';
describe('parseHostPort', () => {
test('empty string', () => {
expect(parseHostPort('')).toEqual({ hostIp: '', hostPort: '' });
expect(parseHostPort(' ')).toEqual({ hostIp: '', hostPort: '' });
});
test('simple port number', () => {
expect(parseHostPort('8080')).toEqual({ hostIp: '', hostPort: '8080' });
expect(parseHostPort('443')).toEqual({ hostIp: '', hostPort: '443' });
});
test('port range', () => {
expect(parseHostPort('8000-8005')).toEqual({ hostIp: '', hostPort: '8000-8005' });
});
test('IPv4:port', () => {
expect(parseHostPort('127.0.0.1:8080')).toEqual({ hostIp: '127.0.0.1', hostPort: '8080' });
expect(parseHostPort('0.0.0.0:3000')).toEqual({ hostIp: '0.0.0.0', hostPort: '3000' });
expect(parseHostPort('192.168.1.100:443')).toEqual({ hostIp: '192.168.1.100', hostPort: '443' });
});
test('IPv4 with port range', () => {
expect(parseHostPort('127.0.0.1:8000-8005')).toEqual({ hostIp: '127.0.0.1', hostPort: '8000-8005' });
});
test('bracketed IPv6:port', () => {
expect(parseHostPort('[::1]:8080')).toEqual({ hostIp: '::1', hostPort: '8080' });
expect(parseHostPort('[::]:3000')).toEqual({ hostIp: '::', hostPort: '3000' });
expect(parseHostPort('[fe80::1]:443')).toEqual({ hostIp: 'fe80::1', hostPort: '443' });
});
test('unbracketed IPv6 is treated as bare IP (use brackets for IPv6:port)', () => {
// Bare multi-colon strings are ambiguous — treated as IPv6 address, no port
expect(parseHostPort('::1:8080')).toEqual({ hostIp: '::1:8080', hostPort: '' });
expect(parseHostPort('::1')).toEqual({ hostIp: '::1', hostPort: '' });
});
test('whitespace trimming', () => {
expect(parseHostPort(' 8080 ')).toEqual({ hostIp: '', hostPort: '8080' });
expect(parseHostPort(' 127.0.0.1:8080 ')).toEqual({ hostIp: '127.0.0.1', hostPort: '8080' });
});
});
describe('validatePort', () => {
test('empty is valid (random allocation)', () => {
expect(validatePort('')).toBe('');
});
test('valid single ports', () => {
expect(validatePort('1')).toBe('');
expect(validatePort('80')).toBe('');
expect(validatePort('8080')).toBe('');
expect(validatePort('65535')).toBe('');
});
test('valid port ranges', () => {
expect(validatePort('8000-8005')).toBe('');
expect(validatePort('1-65535')).toBe('');
});
test('invalid port numbers', () => {
expect(validatePort('0')).not.toBe('');
expect(validatePort('65536')).not.toBe('');
expect(validatePort('99999')).not.toBe('');
expect(validatePort('abc')).not.toBe('');
expect(validatePort('-1')).not.toBe('');
});
test('invalid port ranges', () => {
expect(validatePort('8005-8000')).not.toBe(''); // start >= end
expect(validatePort('0-100')).not.toBe(''); // start out of range
expect(validatePort('100-70000')).not.toBe(''); // end out of range
});
});
describe('validateIp', () => {
test('empty is valid (all interfaces)', () => {
expect(validateIp('')).toBe('');
});
test('valid IPv4', () => {
expect(validateIp('127.0.0.1')).toBe('');
expect(validateIp('0.0.0.0')).toBe('');
expect(validateIp('192.168.1.100')).toBe('');
expect(validateIp('255.255.255.255')).toBe('');
});
test('valid IPv6', () => {
expect(validateIp('::1')).toBe('');
expect(validateIp('::')).toBe('');
expect(validateIp('fe80::1')).toBe('');
});
test('invalid IPs', () => {
expect(validateIp('256.0.0.1')).not.toBe('');
expect(validateIp('not-an-ip')).not.toBe('');
expect(validateIp('192.168.1')).not.toBe('');
});
});
describe('expandPortBindings', () => {
test('single port mapping', () => {
const result = expandPortBindings('8080', '80', 'tcp', '');
expect(result).toEqual({
'80/tcp': { HostPort: '8080' }
});
});
test('single port with IP', () => {
const result = expandPortBindings('8080', '80', 'tcp', '127.0.0.1');
expect(result).toEqual({
'80/tcp': { HostIp: '127.0.0.1', HostPort: '8080' }
});
});
test('empty host port (random allocation)', () => {
const result = expandPortBindings('', '80', 'tcp', '');
expect(result).toEqual({
'80/tcp': { HostPort: '0' }
});
});
test('UDP protocol', () => {
const result = expandPortBindings('5353', '53', 'udp', '');
expect(result).toEqual({
'53/udp': { HostPort: '5353' }
});
});
test('port range expansion', () => {
const result = expandPortBindings('8000-8002', '9000-9002', 'tcp', '');
expect(result).toEqual({
'9000/tcp': { HostPort: '8000' },
'9001/tcp': { HostPort: '8001' },
'9002/tcp': { HostPort: '8002' }
});
});
test('port range with IP', () => {
const result = expandPortBindings('8000-8001', '9000-9001', 'tcp', '192.168.1.1');
expect(result).toEqual({
'9000/tcp': { HostIp: '192.168.1.1', HostPort: '8000' },
'9001/tcp': { HostIp: '192.168.1.1', HostPort: '8001' }
});
});
test('container range with single host port (each gets same host port)', () => {
const result = expandPortBindings('0', '9000-9002', 'tcp', '');
expect(result).toEqual({
'9000/tcp': { HostPort: '0' },
'9001/tcp': { HostPort: '0' },
'9002/tcp': { HostPort: '0' }
});
});
});
describe('formatHostPort', () => {
test('no IP', () => {
expect(formatHostPort('', '8080')).toBe('8080');
});
test('IPv4', () => {
expect(formatHostPort('127.0.0.1', '8080')).toBe('127.0.0.1:8080');
});
test('IPv6 gets brackets', () => {
expect(formatHostPort('::1', '8080')).toBe('[::1]:8080');
expect(formatHostPort('fe80::1', '443')).toBe('[fe80::1]:443');
});
});
describe('round-trip: parse then format', () => {
const cases = [
'8080',
'127.0.0.1:8080',
'0.0.0.0:3000',
'[::1]:8080',
'8000-8005',
'127.0.0.1:8000-8005',
];
for (const input of cases) {
test(`round-trip: ${input}`, () => {
const parsed = parseHostPort(input);
const formatted = formatHostPort(parsed.hostIp, parsed.hostPort);
expect(formatted).toBe(input);
});
}
});