Files
mdg-filius/packages/core/test/routeTable.test.ts
T
julian.kiedaischandClaude Opus 5.5 696334aa5a feat(core): event queue, Ethernet, switch, ARP, IPv4, ICMP, routing and firewall
First slice of the TypeScript port of the filius-ipad runtime engine,
with 29 tests ported from the Swift suite. Parts not ported yet throw
NotPortedError instead of guessing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:22:57 +00:00

122 lines
4.8 KiB
TypeScript

// Ported from filius-ipad ios/FiliusPadTests/TopologyJavaRouteTableTests.swift.
// Left out: column titles and geometry (editor), and the reducer tests
// (they move with the document layer, packages/doc).
import { describe, expect, it } from 'vitest';
import {
appendingBlankDraftIfNeeded,
bestMatchingManualRoute,
blankRouteDraft,
isValidJavaIPAddress,
isValidJavaSubnetMask,
manualRouteFromDraft,
routeTableRows,
visibleRouteRows,
type ManualRoute,
} from '../src/index.js';
function route(destinationNetwork: string, subnetMask: string, gateway: string, interfaceIPAddress: string): ManualRoute {
return { destinationNetwork, subnetMask, gateway, interfaceIPAddress };
}
describe('Java route table', () => {
it('projects multiple interfaces in Java order with Java values', () => {
const rows = routeTableRows(
[
{ ipAddress: '10.0.0.1', subnetMask: '255.255.255.0' },
{ ipAddress: '192.168.0.1', subnetMask: '255.255.255.0' },
],
[
route('172.16.0.0', '255.255.0.0', '10.0.0.2', '10.0.0.1'),
route('203.0.113.0', '255.255.255.0', '192.168.0.2', '192.168.0.1'),
],
'10.0.0.254',
);
expect(rows.map((row) => row.destinationNetwork)).toEqual([
'192.168.0.1', '10.0.0.1',
'192.168.0.0', '10.0.0.0',
'127.0.0.0',
'172.16.0.0', '203.0.113.0',
'0.0.0.0',
]);
expect(rows.map((row) => row.origin)).toEqual([
'localHost', 'localHost', 'connected', 'connected', 'localhost', 'manual', 'manual', 'defaultGateway',
]);
expect(rows.map((row) => row.isEditable)).toEqual([false, false, false, false, false, true, true, false]);
expect(rows[0]).toMatchObject({ subnetMask: '255.255.255.255', nextHop: '127.0.0.1', interfaceIPAddress: '127.0.0.1' });
expect(rows[2]?.nextHop).toBe('192.168.0.1');
expect(rows[4]?.nextHop).toBe('127.0.0.1');
expect(rows.at(-1)).toMatchObject({ nextHop: '10.0.0.254', interfaceIPAddress: '10.0.0.1' });
});
it('puts the default route on the last matching interface and falls back to the primary one', () => {
const interfaces = [
{ ipAddress: '10.0.0.1', subnetMask: '255.0.0.0' },
{ ipAddress: '10.1.0.1', subnetMask: '255.0.0.0' },
];
const matching = routeTableRows(interfaces, [], '10.2.0.1');
expect(matching.at(-1)).toMatchObject({ origin: 'defaultGateway', interfaceIPAddress: '10.1.0.1' });
const fallback = routeTableRows(interfaces, [], '192.168.0.1');
expect(fallback.at(-1)?.interfaceIPAddress).toBe('10.0.0.1');
});
it('shows only manual rows when generated rows are hidden', () => {
const rows = routeTableRows(
[{ ipAddress: '10.0.0.1', subnetMask: '255.255.255.0' }],
[route('172.16.0.0', '255.255.0.0', '10.0.0.2', '10.0.0.1')],
'10.0.0.254',
);
expect(visibleRouteRows(rows, false).map((row) => row.origin)).toEqual(['manual']);
expect(visibleRouteRows(rows, true)).toEqual(rows);
});
it('accepts leading zeros and non-contiguous masks like Java', () => {
expect(isValidJavaIPAddress('001.002.003.004')).toBe(true);
expect(isValidJavaSubnetMask('255.0.255.0')).toBe(true);
expect(isValidJavaIPAddress('256.0.0.1')).toBe(false);
expect(isValidJavaSubnetMask('255.255.255')).toBe(false);
expect(isValidJavaIPAddress(' 10.0.0.1')).toBe(false);
});
it('commits a draft only when all four cells are valid', () => {
const draft = blankRouteDraft('00000000-0000-0000-0000-000000000013');
draft.destinationNetwork = '10.0.0.0';
draft.subnetMask = '255.0.255.0';
draft.nextGateway = '10.0.0.2';
expect(manualRouteFromDraft(draft)).toBeUndefined();
draft.interfaceIPAddress = '10.0.0.1';
expect(manualRouteFromDraft(draft)).toEqual(route('10.0.0.0', '255.0.255.0', '10.0.0.2', '10.0.0.1'));
});
it('keeps at most one pending blank draft', () => {
let counter = 0;
const newId = () => `draft-${(counter += 1)}`;
const first = appendingBlankDraftIfNeeded([], newId);
const second = appendingBlankDraftIfNeeded(first, newId);
expect(first).toHaveLength(1);
expect(second).toEqual(first);
expect(second[0]?.isPendingInsertion).toBe(true);
});
it('picks the greatest numeric mask and keeps the first row on a tie', () => {
const broad = route('10.0.0.0', '255.0.0.0', '1.1.1.1', '10.0.0.1');
const firstSpecific = route('10.1.0.0', '255.255.0.0', '2.2.2.2', '10.0.0.1');
const tiedSpecific = route('10.1.0.0', '255.255.0.0', '3.3.3.3', '10.0.0.1');
expect(bestMatchingManualRoute('10.1.2.3', [broad, firstSpecific, tiedSpecific])).toBe(firstSpecific);
});
it('never includes learned RIP rows in the static projection', () => {
const rows = routeTableRows([{ ipAddress: '10.0.0.1', subnetMask: '255.255.255.0' }], [], undefined);
expect(rows.some((row) => row.origin === 'rip')).toBe(false);
});
});