test(fls): the Java oracle opens files as FILIUS does (M8)

FlsOracle now loads through SzenarioVerwaltung.laden, FILIUS's full
loading path: the archive unpacked, the project's own applications copied
where FILIUS's class loader finds them, then the configuration.
--network keeps the configuration-only load (netzwerkLaden).

On the corpus this changes two files: anwendung_komplett.fls and
multicast_server.fls then have their own applications (Accumulator,
Broadcast) installed. Java's views of the written and the exported files
are byte-equal to before: Java-FILIUS 2.14 opens our exports, with the
classes they carry, as it opens a file of its own, and sees what it sees
in the original. Until own applications on hosts are written (B2), the
reader, writer and export tests leave them out (test/ownApplications.ts);
a test checks that only those two files are touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 09:32:24 +00:00
co-authored by Claude Opus 5.5
parent 75c3da2bd1
commit 95c5b7d97b
8 changed files with 99 additions and 9 deletions
+3 -1
View File
@@ -39,6 +39,7 @@ import {
type ProjectToExport,
} from '../src/index.js';
import { writeTopologyXml } from '../src/topologyWriter.js';
import { withoutOwnApplications } from './ownApplications.js';
const here = dirname(fileURLToPath(import.meta.url));
const corpusRoot = join(here, '../../../corpus');
@@ -349,7 +350,8 @@ describe('the editor model exported, as Java loads it', () => {
return nic.mac === derived ? derived : undefined;
}),
);
const source = original[name]!;
// The model does not install own applications yet (B2).
const source = withoutOwnApplications(original[name]!);
const nodeIds = project.graph.nodes.map((node) => node.id);
const pairs = nodeIds.map((id) => project.remoteLinkConfigurations.get(id)?.pairIdentifier);
// The account files of each host the export writes anew, by path, as the file has them.
+38
View File
@@ -0,0 +1,38 @@
// A project's own applications (classes under projekt/anwendungen): Java
// installs them when it loads the whole archive (tools/java-oracle, laden);
// this package reads, writes and exports them in a later step (B2). Until
// then the tests compare Java's views without them.
import { FILIUS_APPLICATION_CLASSES } from '../src/index.js';
/** The corpus files with an own application installed on a host. */
export const OWN_APPLICATION_FILES = ['java-examples/anwendung_komplett.fls', 'java-examples/multicast_server.fls'];
type View = { nodes?: { system: Record<string, unknown> }[] };
/** Java's view without the applications FILIUS does not ship. */
export function withoutOwnApplications<T>(view: T): T {
const { nodes } = view as View;
if (nodes === undefined) return view;
return {
...view,
nodes: nodes.map((node) =>
node.system.applications === undefined
? node
: {
...node,
system: {
...node.system,
applications: (node.system.applications as { class: string }[]).filter((application) =>
(FILIUS_APPLICATION_CLASSES as readonly string[]).includes(application.class),
),
},
},
),
};
}
/** Whether Java's view has an own application installed. */
export function hasOwnApplications(view: unknown): boolean {
return JSON.stringify(withoutOwnApplications(view)) !== JSON.stringify(view);
}
+9 -1
View File
@@ -21,6 +21,7 @@ import {
type JavaEmailAccount,
} from '../src/index.js';
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
import { hasOwnApplications, OWN_APPLICATION_FILES, withoutOwnApplications } from './ownApplications.js';
import { javaEmail, javaEmailAccount } from './javaEmailNames.js';
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
@@ -250,11 +251,18 @@ describe('corpus: the topology as Java loads it', () => {
const topology = readTopology(values);
const expected = javaView[name] as { nodes: { bounds: number[] | null }[] };
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(withoutTemplateFiles(expected, topology));
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(
withoutTemplateFiles(withoutOwnApplications(expected), topology),
);
expect(topology.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
});
}
it('leaves out own applications only where Java installs some', () => {
const files = flsFiles(corpusRoot).map((file) => relative(corpusRoot, file));
expect(files.filter((name) => hasOwnApplications(javaView[name]))).toEqual(OWN_APPLICATION_FILES);
});
it("a router's constructor makes the same files in every corpus file", () => {
for (const javaClass of ['Vermittlungsrechner', 'Gateway']) {
const templates = [...(templateChildren.get(javaClass) ?? [])].map((text) => JSON.parse(text) as { directory?: string }[]);
+3 -1
View File
@@ -18,6 +18,7 @@ import { describe, expect, it } from 'vitest';
import { decodeJavaBeans, readFlsArchive, readTopology, writeFlsArchive } from '../src/index.js';
import type { JavaEmail } from '../src/javaEmail.js';
import { writeTopologyXml, type TopologyToWrite } from '../src/topologyWriter.js';
import { withoutOwnApplications } from './ownApplications.js';
const here = dirname(fileURLToPath(import.meta.url));
const corpusRoot = join(here, '../../../corpus');
@@ -103,7 +104,8 @@ describe('the topology written again, as Java loads it', () => {
expect(java?.sha256, 'written.java.json is not about this output: UPDATE_WRITTEN=1, then npm run oracle:java').toBe(
createHash('sha256').update(archive).digest('hex'),
);
expect(topologyOf(java!)).toEqual(topologyOf(withRecipientsRead(original[name]!) as JavaFile));
// The written file has no archive entries: Java installs no own application (B2).
expect(topologyOf(java!)).toEqual(topologyOf(withRecipientsRead(withoutOwnApplications(original[name]!)) as JavaFile));
});
}