// The software installation against Java's (tools/java-oracle InstallRun, // fixtures/installed-programs.fls): each program installed on a new computer, // all on one, each installed and removed again, a DNS server and an e-mail // server installed again where their removed ones left a record and an // account — the same programs, settings and files as Java saved, read with // the same reader. Named: Java's Gnutella rolls its number of peers. 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 type { Id, InstallableProgram, VirtualFileSystem } from '@mdg-filius/core'; import { openProjectArchive, PROGRAM_OF_CLASS, saveProjectArchive, type ImportedProject } from '@mdg-filius/fls'; import { addDevice, createUndoManager, emptyProject, installedSoftware, NEW_GNUTELLA_PEERS, readProject, ROOTS, setSoftware, writeProject, type Software } from '../src/index.js'; const fixture = join(dirname(fileURLToPath(import.meta.url)), 'fixtures/installed-programs.fls'); let counter = 0; const java = openProjectArchive(new Uint8Array(readFileSync(fixture)), () => `90000000-0000-0000-0000-${(++counter).toString(16).padStart(12, '0')}`); const PROGRAMS = Object.entries(PROGRAM_OF_CLASS).map(([javaClass, program]) => [javaClass.slice(javaClass.lastIndexOf('.') + 1), program as Software] as const); /** A new PC in a new document. */ function computer() { const doc = new Y.Doc(); writeProject(doc, emptyProject()); let ports = 0; addDevice(doc, { kind: 'pc', id: 'pc', name: 'PC', x: 0, y: 0, newId: () => `port-${++ports}` }, 'me'); return doc; } /** Folders and files, each with its content: text as it is, pictures by kind and size. */ function filesOf(files: VirtualFileSystem | undefined): string[] { const listed: string[] = []; const walk = (folder: string) => { for (const entry of files?.entriesInCreationOrder(folder) ?? []) { const content = entry.content; listed.push(`${entry.path} ${content.kind === 'text' ? JSON.stringify(content.text) : content.kind === 'directory' ? '/' : `${content.kind} ${content.data.length}`}`); if (content.kind === 'directory') walk(entry.path); } }; walk('/'); return listed; } /** What a computer holds of its programs, as the model has it. */ function slice(project: ImportedProject, id: Id) { const gnutella = project.gnutellaConfigurations.get(id); return { programs: [...(project.installedPrograms.get(id) ?? [])].sort(), active: [...(project.activePrograms.get(id) ?? [])], webServer: project.webServerConfigurations.get(id), echoServer: project.echoServerConfigurations.get(id), simpleClient: project.simpleClientConfigurations.get(id), dnsServer: project.dnsServerConfigurations.get(id), emailServer: project.emailServerConfigurations.get(id), emailClient: project.emailClientConfigurations.get(id), // Java rolls it (3 to 5), and its file may name it by any number that came before (an idref). gnutella: gnutella === undefined ? undefined : { maximumKnownPeers: NEW_GNUTELLA_PEERS }, firewall: project.firewallConfigurationsByNodeId.get(id), files: filesOf(project.virtualFileSystems.get(id)), }; } const javaComputer = (name: string) => java.graph.nodes.find((node) => node.displayName === name)!.id; describe("the software installation, against Java's", () => { it('installs each program with its settings and files', () => { for (const [name, program] of PROGRAMS) { const doc = computer(); setSoftware(doc, 'pc', { install: [program], remove: [] }, 'de', 'me'); expect(slice(readProject(doc), 'pc'), name).toEqual(slice(java, javaComputer(name))); } }); it('installs all programs on one computer, in the order of Java’s list', () => { const doc = computer(); // Taken on in any order: the dialog installs them in the order of its list. setSoftware(doc, 'pc', { install: PROGRAMS.map(([, program]) => program).reverse(), remove: [] }, 'de', 'me'); expect(slice(readProject(doc), 'pc')).toEqual(slice(java, javaComputer('Alle'))); }); it('removes a program with its settings, its files staying', () => { for (const [name, program] of PROGRAMS) { const doc = computer(); setSoftware(doc, 'pc', { install: [program], remove: [] }, 'de', 'me'); setSoftware(doc, 'pc', { install: [], remove: [program] }, 'de', 'me'); expect(slice(readProject(doc), 'pc'), name).toEqual(slice(java, javaComputer(`Entfernt-${name}`))); } }); it('reads the records and accounts a removed server left, when it is installed again', () => { for (const [name, program, path, text] of [ ['DNSServer', 'dnsServer', '/dns/hosts', 'www.filius.de. A 3600 1.2.3.4\n'], ['EmailServer', 'emailServer', '/mailserver/konten.txt', 'anna;filius.de;geheim;Muster;Anna;\n'], ] as const) { const doc = computer(); setSoftware(doc, 'pc', { install: [program], remove: [] }, 'de', 'me'); // What Java's server wrote when it got the record or the account (its windows follow in F4.6g). const files = doc.getMap>(ROOTS.nodes).get('pc')!.get('files') as Y.Map>; (files.get(path)!.get('text') as Y.Text).insert(0, text); setSoftware(doc, 'pc', { install: [], remove: [program] }, 'de', 'me'); setSoftware(doc, 'pc', { install: [program], remove: [] }, 'de', 'me'); expect(slice(readProject(doc), 'pc'), name).toEqual(slice(java, javaComputer(`Wieder-${name}`))); } }); }); describe('the software installation', () => { it("writes the web server's start page in the language FILIUS runs in, and keeps a page that is there", () => { const doc = computer(); setSoftware(doc, 'pc', { install: ['webServer'], remove: [] }, 'en', 'me'); const files = () => readProject(doc).virtualFileSystems.get('pc')!; const page = files().entriesInCreationOrder('/webserver')[0]!; expect(page.content.kind === 'text' && page.content.text).toContain('

Welcome at FILIUS!

'); // A page of its own: installed again, the server keeps it and brings no picture. const entries = doc.getMap>(ROOTS.nodes).get('pc')!.get('files') as Y.Map>; setSoftware(doc, 'pc', { install: [], remove: ['webServer'] }, 'en', 'me'); entries.delete('/webserver/splashscreen-mini.png'); (entries.get('/webserver/index.html')!.get('text') as Y.Text).insert(0, 'Meine Seite '); setSoftware(doc, 'pc', { install: ['webServer'], remove: [] }, 'de', 'me'); expect(filesOf(files()).filter((each) => each.startsWith('/webserver'))).toEqual(['/webserver /', expect.stringMatching(/^\/webserver\/index\.html "Meine Seite /)]); }); it('installs nothing twice, removes nothing not there, takes one step of the undo, and installs only on computers', () => { const doc = computer(); const undo = createUndoManager(doc, 'me'); setSoftware(doc, 'pc', { install: ['commandPrompt', 'webServer'], remove: ['textEditor'] }, 'de', 'me'); expect([...installedSoftware(doc, 'pc')!].sort()).toEqual(['commandPrompt', 'webServer']); const before = readProject(doc); setSoftware(doc, 'pc', { install: ['webServer'], remove: [] }, 'de', 'me'); expect(slice(readProject(doc), 'pc')).toEqual(slice(before, 'pc')); undo.undo(); expect(installedSoftware(doc, 'pc')).toEqual([]); expect(filesOf(readProject(doc).virtualFileSystems.get('pc'))).toEqual([]); let ports = 0; addDevice(doc, { kind: 'router', id: 'router', name: 'R', x: 0, y: 0, newId: () => `r-${++ports}` }, 'me'); setSoftware(doc, 'router', { install: ['commandPrompt'], remove: [] }, 'de', 'me'); expect(installedSoftware(doc, 'router')).toBeUndefined(); expect(readProject(doc).installedPrograms.has('router')).toBe(false); setSoftware(doc, 'nobody', { install: ['commandPrompt'], remove: [] }, 'de', 'me'); }); it("gives a new Gnutella 3 peers, as Java's file without the number reads (Java rolls 3 to 5)", () => { const doc = computer(); setSoftware(doc, 'pc', { install: ['gnutella'], remove: [] }, 'de', 'me'); expect(readProject(doc).gnutellaConfigurations.get('pc')).toEqual({ maximumKnownPeers: 3 }); let ids = 0; const reopened = openProjectArchive(saveProjectArchive(readProject(doc)).archive, () => `70000000-0000-0000-0000-${(++ids).toString(16).padStart(12, '0')}`); expect(reopened.gnutellaConfigurations.get(reopened.graph.nodes[0]!.id)).toEqual({ maximumKnownPeers: 3 }); }); it('goes into the file as Java would read it', () => { const doc = computer(); setSoftware(doc, 'pc', { install: ['webServer', 'emailServer', 'commandPrompt'], remove: [] }, 'de', 'me'); const project = readProject(doc); expect(project.installedPrograms.get('pc')).toEqual(new Set(['webServer', 'emailServer', 'commandPrompt'])); let ids = 0; const reopened = openProjectArchive(saveProjectArchive(project).archive, () => `80000000-0000-0000-0000-${(++ids).toString(16).padStart(12, '0')}`); const again = reopened.graph.nodes[0]!.id; expect(slice(reopened, again)).toEqual(slice(project, 'pc')); }); });