feat(fls): installed applications as Java loads them
readSystemSoftware lists a PC's or notebook's installed applications with the settings the model needs: server applications switched on and their port, the generic client's target, DNS recursion, web server virtual hosts, Gnutella's peer limit and the personal firewall (on by default, unlike a router's). Defaults the file leaves out are measured in Java. All 23 corpus files match Java. Two examples install an application of their own (Accumulator, Broadcast), compiled into projekt/anwendungen; it is left out with a warning, as this reader cannot run Java classes. It must survive a save. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
// Warnings the corpus is expected to produce, shared by the corpus tests.
|
||||
|
||||
/**
|
||||
* Two examples install an application of their own, compiled into the
|
||||
* archive (projekt/anwendungen). Java loads it from there; this reader
|
||||
* cannot run it — and the Java oracle, reading only konfiguration.xml, does
|
||||
* not see it either.
|
||||
*/
|
||||
export const EXPECTED_WARNINGS: Record<string, string[]> = {
|
||||
'java-examples/anwendung_komplett.fls': [
|
||||
"node 1: application filius.software.clientserver.Accumulator is not part of FILIUS (a project's own application), left out",
|
||||
],
|
||||
'java-examples/multicast_server.fls': [
|
||||
"node 1: application filius.software.clientserver.Broadcast is not part of FILIUS (a project's own application), left out",
|
||||
],
|
||||
};
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
type FlsTopology,
|
||||
type ImportedProject,
|
||||
} from '../src/index.js';
|
||||
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
|
||||
|
||||
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||
interface SwiftSummary {
|
||||
@@ -289,7 +290,7 @@ describe('corpus: the imported model as Swift builds it', () => {
|
||||
expect(withoutApplicationDirectories(asSwiftState(project), swiftWebRoots(expected))).toEqual(
|
||||
withoutApplicationDirectories(expected),
|
||||
);
|
||||
expect(project.warnings).toEqual([]);
|
||||
expect(project.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -309,7 +310,7 @@ describe('corpus: every file imports completely', () => {
|
||||
|
||||
expect(project.graph.nodes).toHaveLength(javaView[name]!.nodes.length);
|
||||
expect(project.graph.links).toHaveLength(javaView[name]!.cables.length);
|
||||
expect(project.warnings).toEqual([]);
|
||||
expect(project.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,12 +12,14 @@ import {
|
||||
decodeJavaBeans,
|
||||
readFlsArchive,
|
||||
readTopology,
|
||||
type FlsApplication,
|
||||
type FlsDhcpServer,
|
||||
type FlsFileTree,
|
||||
type FlsModem,
|
||||
type FlsNodeClass,
|
||||
type FlsTopology,
|
||||
} from '../src/index.js';
|
||||
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
|
||||
|
||||
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||
const javaView = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, unknown>;
|
||||
@@ -63,6 +65,35 @@ function javaFileTree(tree: FlsFileTree): unknown {
|
||||
}
|
||||
}
|
||||
|
||||
/** FlsOracle.applications' shape: Java's property names, sorted by key. */
|
||||
function javaApplications(applications: FlsApplication[]): unknown[] {
|
||||
return [...applications]
|
||||
.sort((lhs, rhs) => ((lhs.key ?? '') < (rhs.key ?? '') ? -1 : 1))
|
||||
.map((application) => ({
|
||||
key: application.key,
|
||||
class: application.javaClass,
|
||||
...(application.isActive === undefined ? {} : { aktiv: application.isActive, port: application.port }),
|
||||
...(application.targetPort === undefined
|
||||
? {}
|
||||
: { zielIPAdresse: application.targetAddress, zielPort: application.targetPort }),
|
||||
...(application.recursiveResolutionEnabled === undefined
|
||||
? {}
|
||||
: { recursiveResolutionEnabled: application.recursiveResolutionEnabled }),
|
||||
...(application.useVirtualHosts === undefined ? {} : { useVHost: application.useVirtualHosts }),
|
||||
...(application.maxPeers === undefined ? {} : { maxTeilnehmerZahl: application.maxPeers }),
|
||||
...(application.firewall === undefined
|
||||
? {}
|
||||
: {
|
||||
activated: application.firewall.activated,
|
||||
defaultPolicy: application.firewall.defaultPolicy,
|
||||
dropICMP: application.firewall.dropICMP,
|
||||
filterSYNSegmentsOnly: application.firewall.filterSYNSegmentsOnly,
|
||||
filterUdp: application.firewall.filterUdp,
|
||||
rules: application.firewall.rules.length,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
/** Java's property names, as FlsOracle.java writes them. */
|
||||
function javaDhcpServer(server: FlsDhcpServer) {
|
||||
return {
|
||||
@@ -106,6 +137,7 @@ function asJavaView(topology: FlsTopology, javaBounds: (number[] | null)[]) {
|
||||
}),
|
||||
...(node.system.modem === undefined ? {} : { modem: javaModem(node.system.modem) }),
|
||||
...(node.system.fileSystem === undefined ? {} : { fileSystem: javaFileTree(node.system.fileSystem) }),
|
||||
...(node.system.applications === undefined ? {} : { applications: javaApplications(node.system.applications) }),
|
||||
...(node.system.firewall === undefined
|
||||
? {}
|
||||
: {
|
||||
@@ -164,7 +196,7 @@ describe('corpus: the topology as Java loads it', () => {
|
||||
const expected = javaView[name] as { nodes: { bounds: number[] | null }[] };
|
||||
|
||||
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(withoutTemplateFiles(expected));
|
||||
expect(topology.warnings).toEqual([]);
|
||||
expect(topology.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -368,6 +400,28 @@ describe('readTopology rules', () => {
|
||||
expect(warnings).toEqual(['node 2: file system: insert on a node is not read']);
|
||||
});
|
||||
|
||||
it("gives installed applications Java's measured defaults for what the file leaves out", () => {
|
||||
const put = (className: string, body = '') =>
|
||||
`<void method="put"><string>${className}</string><object class="${className}">${body}</object></void>`;
|
||||
const { nodes } = topologyOf(`<object class="java.util.LinkedList">${nodeItem(`<object class="filius.hardware.knoten.Rechner">
|
||||
<void property="systemSoftware"><void property="installierteAnwendungen">
|
||||
${put('filius.software.www.WebServer')}
|
||||
${put('filius.software.clientserver.ClientBaustein')}
|
||||
${put('filius.software.dateiaustausch.PeerToPeerAnwendung')}
|
||||
${put('filius.software.firewall.Firewall')}
|
||||
${put('filius.software.dns.DNSServer', '<void property="aktiv"><boolean>true</boolean></void><void property="port"><int>5353</int></void>')}
|
||||
${put('filius.software.lokal.Terminal')}
|
||||
</void></void></object>`)}</object>`);
|
||||
const byClass = new Map(nodes[0]!.system.applications!.map((application) => [application.javaClass, application]));
|
||||
|
||||
expect(byClass.get('filius.software.www.WebServer')).toMatchObject({ isActive: false, port: 80, useVirtualHosts: false });
|
||||
expect(byClass.get('filius.software.clientserver.ClientBaustein')).toMatchObject({ targetAddress: null, targetPort: 55_555 });
|
||||
expect(byClass.get('filius.software.dateiaustausch.PeerToPeerAnwendung')).toMatchObject({ maxPeers: 4 });
|
||||
expect(byClass.get('filius.software.firewall.Firewall')?.firewall).toMatchObject({ activated: true, defaultPolicy: 0 });
|
||||
expect(byClass.get('filius.software.dns.DNSServer')).toMatchObject({ isActive: true, port: 5353, recursiveResolutionEnabled: false });
|
||||
expect(Object.keys(byClass.get('filius.software.lokal.Terminal')!).sort()).toEqual(['javaClass', 'key', 'source']);
|
||||
});
|
||||
|
||||
it("gives a modem Java's defaults and keeps an address the file sets to null", () => {
|
||||
const { nodes } = topologyOf(`<object class="java.util.LinkedList">
|
||||
${nodeItem('<object class="filius.hardware.knoten.Modem"/>')}
|
||||
|
||||
Reference in New Issue
Block a user