Files
mdg-filius/packages/core/test/installation.test.ts
T
julian.kiedaischandClaude Opus 5.5 f0e7c73ec7 feat(editor): F4.6c the software installation on a computer's desktop
A new oracle, InstallRun, installs each of Java's programs on new
computers, all of them on one, each installed and removed again, and a
DNS and an e-mail server installed again after their removal; Java saves
the result as a project. A new program is switched off (a computer's
firewall is on), makes the folders and files it lacks (the web server's
start page in FILIUS' language and its picture, /peer2peer,
/mailserver/konten.txt, /dns/hosts), and removing it takes its settings
and leaves its files; a server installed again reads its records or
accounts from them. Java installs in the order of its list and starts
the program at once.

The document's setSoftware does the same, checked against Java's saved
installation. The core knows what a program makes in the files and
installs or removes a program while the simulation runs; the room
watches the document's programs and hands changes to the engine, so a
new command line works at once. The desktop offers Java's dialog with
two lists, arrows, double click and apply changes.

Also: Java's file may name a number it rolled by the result of any
getter (an idref, here a thread's priority); GetterDefaults now
measures the priority of the saved thread classes, which the import
read as 0 before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:03:35 +00:00

108 lines
6.1 KiB
TypeScript

// A program installed or removed while the simulation runs (Java's software
// installation): its settings and files from the document's snapshot, the
// program started at once; removed, a server is switched off and the program
// goes, its files staying.
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { installationFiles, topologySnapshot, VirtualFileSystem, type TopologySnapshot } from '../src/index.js';
import { device, pc, startedEngine, uuid } from './fixtures.js';
const PC = uuid(1);
const PORT = uuid(2);
/** The document's snapshot after a web server and a command line were installed. */
function source(files: VirtualFileSystem): TopologySnapshot {
for (const file of installationFiles('webServer', 'de')) {
if (file.content.kind === 'directory') files.createDirectory(file.path);
else if (file.content.kind === 'text') files.writeTextFile(file.path, file.content.text);
else if (file.content.kind === 'image') files.writeImageFile(file.path, file.content.data, file.content.mediaType);
}
return topologySnapshot({
nodes: [pc(PC, PORT)],
links: [],
deviceConfigurations: new Map([[PC, device('10.0.0.1')]]),
webServersByNodeId: new Map([[PC, { port: 80, documentRoot: '/webserver', virtualHosts: false, isActive: false }]]),
terminalNodeIds: new Set([PC]),
fileSystemsByNodeId: new Map([[PC, files]]),
});
}
describe('a program installed while the simulation runs', () => {
it("takes its settings and the files it makes from the document's snapshot, and starts", () => {
const own = new VirtualFileSystem();
own.createDirectory('/webserver');
own.writeTextFile('/webserver/index.html', 'Meine Seite');
const engine = startedEngine({ nodes: [pc(PC, PORT)], links: [], deviceConfigurations: new Map([[PC, device('10.0.0.1')]]), fileSystemsByNodeId: new Map([[PC, own]]) });
expect([engine.webServer(PC), engine.terminal(PC)]).toEqual([undefined, undefined]);
const from = source(new VirtualFileSystem());
engine.installProgram(PC, 'webServer', from);
engine.installProgram(PC, 'commandPrompt', from);
expect(engine.webServer(PC)).toBeDefined();
expect(engine.terminal(PC)).toBeDefined();
expect(engine.state.topologySnapshot.webServersByNodeId.get(PC)).toEqual({ port: 80, documentRoot: '/webserver', virtualHosts: false, isActive: false });
// The running files keep what is there; what is missing comes from the document.
const files = engine.state.topologySnapshot.fileSystemsByNodeId.get(PC)!;
expect(files.textFile('/webserver/index.html')).toBe('Meine Seite');
expect(files.imageFile('/webserver/splashscreen-mini.png').mediaType).toBe('image/png');
// Installed again: nothing changes, the same program runs on.
const terminal = engine.terminal(PC);
engine.installProgram(PC, 'commandPrompt', from);
expect(engine.terminal(PC)).toBe(terminal);
});
it('goes when removed, its files staying, and nothing happens outside a run or on a device the run has not', () => {
const engine = startedEngine({ nodes: [pc(PC, PORT)], links: [], deviceConfigurations: new Map([[PC, device('10.0.0.1')]]) });
const from = source(new VirtualFileSystem());
engine.installProgram(uuid(9), 'webServer', from);
expect(engine.webServer(uuid(9))).toBeUndefined();
engine.installProgram(PC, 'webServer', from);
engine.installProgram(PC, 'commandPrompt', from);
engine.removeProgram(PC, 'webServer');
engine.removeProgram(PC, 'commandPrompt');
expect([engine.webServer(PC), engine.terminal(PC)]).toEqual([undefined, undefined]);
expect(engine.state.topologySnapshot.webServersByNodeId.has(PC)).toBe(false);
expect(engine.state.topologySnapshot.terminalNodeIds.has(PC)).toBe(false);
expect(engine.state.topologySnapshot.fileSystemsByNodeId.get(PC)!.contains('/webserver/index.html')).toBe(true);
engine.handle({ type: 'stop' });
engine.installProgram(PC, 'commandPrompt', from);
expect(engine.terminal(PC)).toBeUndefined();
});
});
const templates = join(dirname(fileURLToPath(import.meta.url)), '../../../tools/java-oracle/.upstream/src/main/resources/tmpl');
describe("the files a program makes", () => {
it.skipIf(!existsSync(templates))("give the web server Java's start page in the language FILIUS runs in, as Java reads it (each line ended by a line break)", () => {
for (const [language, file] of [['de', 'webserver_index_de_DE.txt'], ['en', 'webserver_index_en_GB.txt']] as const) {
const lines = readFileSync(join(templates, file), 'utf8').split(/\r?\n/);
if (lines.at(-1) === '') lines.pop();
const page = installationFiles('webServer', language).find((each) => each.path === '/webserver/index.html')!;
expect(page.content, language).toEqual({ kind: 'text', text: lines.map((line) => `${line}\n`).join('') });
}
});
it('are none for a program that makes none', () => {
for (const program of ['commandPrompt', 'webBrowser', 'textEditor', 'fileExplorer', 'imageViewer', 'echoServer', 'simpleClient', 'emailClient', 'firewall'] as const) {
expect(installationFiles(program, 'de'), program).toEqual([]);
}
});
});
describe('a server removed while the simulation runs', () => {
it('is switched off: its port listens no more', () => {
const engine = startedEngine({ nodes: [pc(PC, PORT)], links: [], deviceConfigurations: new Map([[PC, device('10.0.0.1')]]) });
const from = topologySnapshot({ ...source(new VirtualFileSystem()), webServersByNodeId: new Map([[PC, { port: 80, documentRoot: '/webserver', virtualHosts: false, isActive: true }]]) });
engine.installProgram(PC, 'webServer', from);
const listening = () => [...engine.state.socketsById.values()].filter((socket) => socket.nodeId === PC && socket.localPort === 80 && socket.tcpState === 'LISTEN');
expect(listening()).toHaveLength(1);
engine.removeProgram(PC, 'webServer');
// The server's thread closes the socket, as Java's does, a moment later.
engine.handle({ type: 'advance', toMilliseconds: engine.state.currentTimeMilliseconds + 100 });
expect(listening()).toEqual([]);
});
});