// What the report tells of each device (reportDevices) and which cards it // lists (reportCards), on FILIUS' example Internet_Komplett — the whole of it // is compared with Java's report in packages/editor/test/report.test.ts — // and on changes a Java file can carry: a forwarding row whose destination // is no address, a router's DNS server, a computer's DHCP server among its // programs, a home router's port forwarding written with numbers, small // letters or a protocol Java does not know. import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; import * as Y from 'yjs'; import { openProjectArchive } from '@mdg-filius/fls'; import { reportCards, reportDevices, writeProject } from '../src/index.js'; const corpus = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus/java-examples'); const FILE = 'Internet_Komplett_mit_eMail_Webserver_Intranet_Portforwarding_Firewall_DHCP_DE.fls'; function docOf(file: string): Y.Doc { const doc = new Y.Doc(); let next = 0; writeProject( doc, openProjectArchive(new Uint8Array(readFileSync(join(corpus, file))), () => `id${next++}`), ); return doc; } const nodeNamed = (doc: Y.Doc, name: string) => [...doc.getMap>('nodes').entries()].find(([, node]) => node.get('name') === name)!; const settings = (node: Y.Map) => node.get('settings') as Y.Map; const row = (fields: Readonly>) => { const map = new Y.Map(); for (const [key, value] of Object.entries(fields)) map.set(key, value); return map; }; describe('the devices of the report', () => { it('tells of the computers, routers and home routers in their order, not of the switches', () => { const devices = reportDevices(docOf(FILE)); // 26 devices, four of them switches. expect(devices.length).toBe(22); expect(devices.map((device) => device.kind)).not.toContain('networkSwitch'); const server = devices.find((device) => device.label === 'DNS (8.8.8.8)')!; expect(server).toMatchObject({ kind: 'pc', gateway: '8.0.0.1', dnsServer: '', dhcpClient: false, programs: ['dnsServer', 'textEditor', 'commandPrompt'] }); expect(server.dnsRecords![1]).toEqual({ domainName: 'gmx.de.', type: 'MX', rdata: 'mail.gmx.de.' }); expect(devices.find((device) => device.label === 'Firewall')).toMatchObject({ kind: 'gateway', portForwarding: [['TCP', '80', '10.0.0.1', '80']] }); }); it("leaves out a forwarding row whose destination is no address, as Java's report does", () => { const doc = docOf(FILE); const [id, provider] = nodeNamed(doc, 'Provider 1'); doc.transact(() => { const routes = new Y.Array>(); routes.push([ row({ destinationNetwork: 'kein Netz', subnetMask: '255.0.0.0', gateway: '2.0.0.2', interfaceIPAddress: '2.0.0.1' }), row({ destinationNetwork: '9.0.0.0', subnetMask: '255.0.0.0', gateway: '2.0.0.2', interfaceIPAddress: '2.0.0.1' }), ]); settings(provider).set('routes', routes); }); const destinations = reportDevices(doc) .find((device) => device.id === id)! .routes.map((route) => route.destinationNetwork); expect(destinations).toContain('9.0.0.0'); expect(destinations).not.toContain('kein Netz'); }); it("writes a router's DNS server, as Java's does", () => { const doc = docOf(FILE); const [id, provider] = nodeNamed(doc, 'Provider 1'); doc.transact(() => (settings(provider).get('device') as Y.Map | undefined)?.set('dnsServer', '8.8.8.8') ?? settings(provider).set('device', row({ dnsServer: '8.8.8.8' }))); expect(reportDevices(doc).find((device) => device.id === id)!.dnsServer).toBe('8.8.8.8'); }); it('leaves the DHCP server out of the programs, as it has no window', () => { const doc = docOf(FILE); const [id, server] = nodeNamed(doc, 'DNS (8.8.8.8)'); doc.transact(() => (server.get('programs') as Y.Map).set('dhcpServer', new Y.Map())); expect(reportDevices(doc).find((device) => device.id === id)!.programs).toEqual(['dnsServer', 'textEditor', 'commandPrompt']); }); it('writes the port forwarding Java reports: TCP and UDP, also written 6, 17 or in small letters; no other protocol', () => { const doc = docOf(FILE); const [id, firewall] = nodeNamed(doc, 'Firewall'); doc.transact(() => { const rows = new Y.Array>(); for (const protocolValue of ['6', '17', 'udp', 'Tcp', 'ICMP']) rows.push([row({ protocolValue, publicPortValue: '8080', lanIPAddress: '10.0.0.1', lanPortValue: '80' })]); settings(firewall).set('portForwarding', rows); }); expect( reportDevices(doc) .find((device) => device.id === id)! .portForwarding!.map(([protocol]) => protocol), ).toEqual(['TCP', 'UDP', 'UDP', 'TCP']); }); }); describe('the cards of the report', () => { it("lists every card with an address, under its device's label, in the devices' order", () => { const cards = reportCards(docOf(FILE)); expect(cards[0]).toMatchObject({ label: '10.10.20.11', ipAddress: '10.10.20.11' }); expect(cards.filter((card) => card.label === 'Provider 1').map((card) => card.ipAddress)).toEqual(['42.0.0.1', '6.0.0.2', '2.0.0.1']); // No switch has one. expect(cards.some((card) => card.label.startsWith('Switch'))).toBe(false); expect(cards.length).toBe(reportDevices(docOf(FILE)).reduce((sum, device) => sum + device.cards.length, 0)); }); });