feat(core): the firewall settings page of routers after Java, measured against Java

The page konfig changes the firewall at run time (into the snapshot, back
into the project through projectWithSimulationChanges). KonfigCases feeds
Java's plug-in directly (25 cases, de and en); RouterWebRun plays two
stories over the network. Ten Java bugs named, not adopted. The web server
answers a method not written GET or POST with 501, as Java does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 18:13:31 +00:00
co-authored by Claude Opus 5.5
parent 7dac57cbbe
commit 8438b42018
23 changed files with 13229 additions and 24 deletions
+37
View File
@@ -9,6 +9,7 @@ import {
firewallLogPage,
firewallRule,
firewallRuleText,
firewallSettingsPage,
forwardingTablePage,
interfaceKey,
javaTextLength,
@@ -140,6 +141,42 @@ describe("a router's firewall log", () => {
});
});
describe("a router's settings page (konfig)", () => {
const pageOf = (engine: NetworkRuntimeEngine) => {
const pages = engine.webBrowser(LEFT)!.log.all.flatMap((line) => (line.kind === 'page' ? [line] : []));
return pages[pages.length - 1];
};
const ask = (engine: NetworkRuntimeEngine, data?: string) => {
engine.webBrowser(LEFT)!.open('10.0.0.1/konfig', data);
engine.handle({ type: 'advance', toMilliseconds: engine.state.currentTimeMilliseconds + 5_000 });
return pageOf(engine);
};
it('changes the firewall at run time, into the snapshot; a page without data changes nothing', () => {
const engine = startedEngine({ ...network(), webBrowserNodeIds: new Set([LEFT]) });
const before = engine.state.topologySnapshot;
expect(ask(engine)).toMatchObject({ statusCode: 200, contentType: 'text/html' });
expect(engine.state.topologySnapshot).toBe(before);
const page = ask(engine, 'firewallActivate=1&btnSave=speichern&defaultPolicy=accept&srcIP=&srcMask=&destIP=&destMask=&protocol=*&port=&action=drop');
expect(engine.state.topologySnapshot.firewallConfigurationsByNodeId.get(ROUTER)).toEqual(
firewallConfiguration({ isActive: true, defaultPolicy: 'accept', filterSynSegmentsOnly: false, rules: [firewallRule({ protocol: 'udp', port: 7, action: 'drop' })] }),
);
// The browser reads the answer as HTTPNachricht does: without its last empty lines.
expect(page?.body).toBe(firewallSettingsPage(engine.firewallConfiguration(ROUTER), 'de').replace(/\n+$/, ''));
});
it("answers in the project's language, and on a home router as on a router", () => {
const gateway = network({ language: 'en', webBrowserNodeIds: new Set([LEFT]) });
const engine = startedEngine({ ...gateway, nodes: gateway.nodes.map((node) => (node.id === ROUTER ? { ...node, kind: 'gateway' as const } : node)) });
const page = ask(engine, 'srcIP=&srcMask=&destIP=&destMask=&protocol=TCP&port=22&action=accept&addrule=add');
expect(page?.body).toContain('<title>Router Firewall Configuration</title>');
expect(engine.firewallConfiguration(ROUTER).rules).toEqual([firewallRule({ protocol: 'udp', port: 7, action: 'drop' }), firewallRule({ protocol: 'tcp', port: 22, action: 'accept' })]);
const rejected = ask(engine, 'srcIP=&srcMask=&destIP=&destMask=&protocol=TCP&port=0&action=accept&addrule=add');
expect(rejected?.body).toContain('<font color="red">Erroneous input(s):<ul><li>Port invalid</li></ul></font><br>');
expect(engine.firewallConfiguration(ROUTER).rules).toHaveLength(2);
});
});
describe("a RIP route's public address (hopPublicIp)", () => {
it('is the telling router\'s, also when a shorter way replaces the route', () => {
const engine = startedEngine(network({ ripEnabledByNodeId: new Map([[ROUTER, true]]), firewallConfigurationsByNodeId: new Map() }));