import { describe, expect, test } from 'bun:test'; import { addCIDR, generateIPsInSubnet, isIPInRange, isInSubnet, parseSubnet, stripCIDR, } from './subnet-parser'; describe('Subnet Parser', () => { describe('parseSubnet', () => { test('should parse valid /24 subnet', () => { const result = parseSubnet('10.0.10.0/24'); expect(result.network).toBe('10.0.10.0'); expect(result.maskBits).toBe(24); expect(result.octets).toEqual([10, 0, 10, 0]); expect(result.firstUsableIp).toBe('10.0.10.10'); expect(result.lastUsableIp).toBe('10.0.10.254'); }); test('should reject invalid subnet format', () => { expect(() => parseSubnet('invalid')).toThrow('Invalid subnet format'); expect(() => parseSubnet('10.0.10.0')).toThrow('Invalid subnet format'); expect(() => parseSubnet('10.0.10.0/abc')).toThrow('Invalid subnet format'); }); test('should reject invalid IP address', () => { expect(() => parseSubnet('256.0.10.0/24')).toThrow('Invalid IP address'); expect(() => parseSubnet('10.0.10/24')).toThrow('Invalid IP address'); }); test('should reject subnets smaller than /24', () => { expect(() => parseSubnet('10.0.10.0/25')).toThrow('Subnet too small'); expect(() => parseSubnet('10.0.10.0/30')).toThrow('Subnet too small'); }); }); describe('stripCIDR', () => { test('should strip CIDR mask', () => { expect(stripCIDR('10.0.10.10/24')).toBe('10.0.10.10'); expect(stripCIDR('192.168.1.1/16')).toBe('192.168.1.1'); }); test('should return IP unchanged if no mask', () => { expect(stripCIDR('10.0.10.10')).toBe('10.0.10.10'); }); }); describe('addCIDR', () => { test('should add CIDR mask', () => { expect(addCIDR('10.0.10.10', 24)).toBe('10.0.10.10/24'); expect(addCIDR('192.168.1.1', 16)).toBe('192.168.1.1/16'); }); }); describe('isInSubnet', () => { test('should return true for IP in subnet', () => { expect(isInSubnet('10.0.10.50/24', '10.0.10.0/24')).toBe(true); expect(isInSubnet('10.0.10.10/24', '10.0.10.0/24')).toBe(true); expect(isInSubnet('10.0.10.254/24', '10.0.10.0/24')).toBe(true); }); test('should return false for IP outside subnet', () => { expect(isInSubnet('10.0.11.50/24', '10.0.10.0/24')).toBe(false); expect(isInSubnet('10.0.20.10/24', '10.0.10.0/24')).toBe(false); expect(isInSubnet('192.168.1.1/24', '10.0.10.0/24')).toBe(false); }); }); describe('generateIPsInSubnet', () => { test('should generate IPs from .10 to .254', () => { const ips = Array.from(generateIPsInSubnet('10.0.10.0/24')); expect(ips[0]).toBe('10.0.10.10/24'); // First usable (reserve .1-.9) expect(ips[ips.length - 1]).toBe('10.0.10.254/24'); // Last usable expect(ips.length).toBe(245); // 254 - 10 + 1 = 245 IPs }); test('should generate correct IPs for different subnet', () => { const ips = Array.from(generateIPsInSubnet('192.168.1.0/24')); expect(ips[0]).toBe('192.168.1.10/24'); expect(ips[ips.length - 1]).toBe('192.168.1.254/24'); }); }); describe('isIPInRange', () => { test('should match single IP reservation', () => { expect(isIPInRange('10.0.10.50', '10.0.10.50', null)).toBe(true); expect(isIPInRange('10.0.10.51', '10.0.10.50', null)).toBe(false); }); test('should match IP range reservation', () => { expect(isIPInRange('10.0.10.50', '10.0.10.50', '10.0.10.60')).toBe(true); expect(isIPInRange('10.0.10.55', '10.0.10.50', '10.0.10.60')).toBe(true); expect(isIPInRange('10.0.10.60', '10.0.10.50', '10.0.10.60')).toBe(true); expect(isIPInRange('10.0.10.49', '10.0.10.50', '10.0.10.60')).toBe(false); expect(isIPInRange('10.0.10.61', '10.0.10.50', '10.0.10.60')).toBe(false); }); test('should handle IPs with CIDR notation', () => { expect(isIPInRange('10.0.10.50/24', '10.0.10.50', '10.0.10.60')).toBe(true); }); }); });