import { tap, expect } from '@git.zone/tstest/tapbundle'; import { IpMatcher } from '../../../ts/core/routing/matchers/ip.js'; tap.test('IpMatcher - exact match', async () => { expect(IpMatcher.match('192.168.1.1', '192.168.1.1')).toEqual(true); expect(IpMatcher.match('192.168.1.1', '192.168.1.2')).toEqual(false); expect(IpMatcher.match('10.0.0.1', '10.0.0.1')).toEqual(true); }); tap.test('IpMatcher - CIDR notation', async () => { // /24 subnet expect(IpMatcher.match('192.168.1.0/24', '192.168.1.1')).toEqual(true); expect(IpMatcher.match('192.168.1.0/24', '192.168.1.255')).toEqual(true); expect(IpMatcher.match('192.168.1.0/24', '192.168.2.1')).toEqual(false); // /16 subnet expect(IpMatcher.match('10.0.0.0/16', '10.0.1.1')).toEqual(true); expect(IpMatcher.match('10.0.0.0/16', '10.0.255.255')).toEqual(true); expect(IpMatcher.match('10.0.0.0/16', '10.1.0.1')).toEqual(false); // /32 (single host) expect(IpMatcher.match('192.168.1.1/32', '192.168.1.1')).toEqual(true); expect(IpMatcher.match('192.168.1.1/32', '192.168.1.2')).toEqual(false); }); tap.test('IpMatcher - wildcard matching', async () => { expect(IpMatcher.match('192.168.1.*', '192.168.1.1')).toEqual(true); expect(IpMatcher.match('192.168.1.*', '192.168.1.255')).toEqual(true); expect(IpMatcher.match('192.168.1.*', '192.168.2.1')).toEqual(false); expect(IpMatcher.match('192.168.*.*', '192.168.0.1')).toEqual(true); expect(IpMatcher.match('192.168.*.*', '192.168.255.255')).toEqual(true); expect(IpMatcher.match('192.168.*.*', '192.169.0.1')).toEqual(false); expect(IpMatcher.match('*.*.*.*', '1.2.3.4')).toEqual(true); expect(IpMatcher.match('*.*.*.*', '255.255.255.255')).toEqual(true); }); tap.test('IpMatcher - range matching', async () => { expect(IpMatcher.match('192.168.1.1-192.168.1.10', '192.168.1.1')).toEqual(true); expect(IpMatcher.match('192.168.1.1-192.168.1.10', '192.168.1.5')).toEqual(true); expect(IpMatcher.match('192.168.1.1-192.168.1.10', '192.168.1.10')).toEqual(true); expect(IpMatcher.match('192.168.1.1-192.168.1.10', '192.168.1.11')).toEqual(false); expect(IpMatcher.match('192.168.1.1-192.168.1.10', '192.168.1.0')).toEqual(false); }); tap.test('IpMatcher - IPv6-mapped IPv4', async () => { expect(IpMatcher.match('192.168.1.1', '::ffff:192.168.1.1')).toEqual(true); expect(IpMatcher.match('192.168.1.0/24', '::ffff:192.168.1.100')).toEqual(true); expect(IpMatcher.match('192.168.1.*', '::FFFF:192.168.1.50')).toEqual(true); }); tap.test('IpMatcher - IP validation', async () => { expect(IpMatcher.isValidIpv4('192.168.1.1')).toEqual(true); expect(IpMatcher.isValidIpv4('255.255.255.255')).toEqual(true); expect(IpMatcher.isValidIpv4('0.0.0.0')).toEqual(true); expect(IpMatcher.isValidIpv4('256.1.1.1')).toEqual(false); expect(IpMatcher.isValidIpv4('1.1.1')).toEqual(false); expect(IpMatcher.isValidIpv4('1.1.1.1.1')).toEqual(false); expect(IpMatcher.isValidIpv4('1.1.1.a')).toEqual(false); expect(IpMatcher.isValidIpv4('01.1.1.1')).toEqual(false); // No leading zeros }); tap.test('IpMatcher - isAuthorized', async () => { // Empty lists - allow all expect(IpMatcher.isAuthorized('192.168.1.1')).toEqual(true); // Allow list only const allowList = ['192.168.1.0/24', '10.0.0.0/16']; expect(IpMatcher.isAuthorized('192.168.1.100', allowList)).toEqual(true); expect(IpMatcher.isAuthorized('10.0.50.1', allowList)).toEqual(true); expect(IpMatcher.isAuthorized('172.16.0.1', allowList)).toEqual(false); // Block list only const blockList = ['192.168.1.100', '10.0.0.0/24']; expect(IpMatcher.isAuthorized('192.168.1.100', [], blockList)).toEqual(false); expect(IpMatcher.isAuthorized('10.0.0.50', [], blockList)).toEqual(false); expect(IpMatcher.isAuthorized('192.168.1.101', [], blockList)).toEqual(true); // Both lists - block takes precedence expect(IpMatcher.isAuthorized('192.168.1.100', allowList, ['192.168.1.100'])).toEqual(false); }); tap.test('IpMatcher - specificity calculation', async () => { // Exact IPs are most specific const exactScore = IpMatcher.calculateSpecificity('192.168.1.1'); const cidr32Score = IpMatcher.calculateSpecificity('192.168.1.1/32'); const cidr24Score = IpMatcher.calculateSpecificity('192.168.1.0/24'); const cidr16Score = IpMatcher.calculateSpecificity('192.168.0.0/16'); const wildcardScore = IpMatcher.calculateSpecificity('192.168.1.*'); const rangeScore = IpMatcher.calculateSpecificity('192.168.1.1-192.168.1.10'); expect(exactScore).toBeGreaterThan(cidr24Score); expect(cidr32Score).toBeGreaterThan(cidr24Score); expect(cidr24Score).toBeGreaterThan(cidr16Score); expect(rangeScore).toBeGreaterThan(wildcardScore); }); tap.test('IpMatcher - edge cases', async () => { // Empty/null inputs expect(IpMatcher.match('', '192.168.1.1')).toEqual(false); expect(IpMatcher.match('192.168.1.1', '')).toEqual(false); expect(IpMatcher.match(null as any, '192.168.1.1')).toEqual(false); expect(IpMatcher.match('192.168.1.1', null as any)).toEqual(false); // Invalid CIDR expect(IpMatcher.match('192.168.1.0/33', '192.168.1.1')).toEqual(false); expect(IpMatcher.match('192.168.1.0/-1', '192.168.1.1')).toEqual(false); expect(IpMatcher.match('192.168.1.0/', '192.168.1.1')).toEqual(false); // Invalid ranges expect(IpMatcher.match('192.168.1.10-192.168.1.1', '192.168.1.5')).toEqual(false); // Start > end expect(IpMatcher.match('192.168.1.1-', '192.168.1.5')).toEqual(false); expect(IpMatcher.match('-192.168.1.10', '192.168.1.5')).toEqual(false); }); tap.start();