feat(fls): read and write a project's own applications as their beans
A project's own applications (classes under projekt/anwendungen, installed on a host) are no longer left out. The decoder now keeps the Java type of every primitive — at setters, constructor and call arguments, also through shared ids like Integer0 — and which properties an object's own getter made. beanCapture.ts turns a decoded bean back into what the writer writes, with the node's system software as a mark the writer replaces by the system software it writes; what the format cannot say is refused, not guessed (an unresolved value, a Field, a factory-made object, an object a getter reaches elsewhere in the file). The reader keeps own applications as captured beans; the corpus no longer warns. The capture first took a setter with another object's getter result for an own getter; the round-trip test found it. The Java oracle did not show whether an application runs on its node's system software, so a mutation writing the mark instead survived; it now reports onItsSystem (true for all 192 applications of the corpus). Checked by Java: from the files written again, FILIUS loads Accumulator and Broadcast installed, switched on and on their node's system software, as in the original. The round-trip test covers every primitive, null, a class, an array, shared objects, getter chains, constructed elements, map reads and static constants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
// A decoded bean written again (beanCapture.ts): decoded, captured, written
|
||||
// and decoded once more, it holds the same values with the same Java types —
|
||||
// so XMLDecoder calls the same setters with the same arguments. Java's own
|
||||
// check is in topologyWriter.test.ts and exportProject.test.ts: the corpus's
|
||||
// own applications, loaded by FILIUS from the files written again.
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { captureBean, SYSTEM_SOFTWARE_CLASS, withSystemSoftware } from '../src/beanCapture.js';
|
||||
import { decodeJavaBeans, isJavaObject, type JavaObject, type JavaValue } from '../src/javabeans.js';
|
||||
import { encodeJavaBeans, object, reference, type BeanObject } from '../src/javaBeansWriter.js';
|
||||
|
||||
function doc(body: string): string {
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n<java version="17" class="java.beans.XMLDecoder">\n${body}\n</java>\n`;
|
||||
}
|
||||
|
||||
function bean(value: JavaValue | undefined): JavaObject {
|
||||
if (!isJavaObject(value)) throw new Error(`not an object: ${JSON.stringify(value)}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
/** What the decoder keeps of an object, by value: class, arguments, properties and calls with their types. */
|
||||
function shape(value: JavaValue, seen = new Set<JavaObject>()): unknown {
|
||||
if (!isJavaObject(value)) return typeof value === 'bigint' ? `${value}n` : value;
|
||||
if (seen.has(value)) return { shared: value.className };
|
||||
seen.add(value);
|
||||
return {
|
||||
className: value.className,
|
||||
staticField: value.staticField,
|
||||
fromGetter: value.fromGetter,
|
||||
args: value.args.map((arg) => shape(arg, seen)),
|
||||
argKinds: value.argKinds,
|
||||
properties: [...value.properties].map(([name, each]) => [name, shape(each, seen), value.propertyKinds.get(name)]),
|
||||
presetElements: [...value.presetElements].map(([index, each]) => [index, shape(each, seen)]),
|
||||
presetEntries: [...value.presetEntries].map(([key, each]) => [key, shape(each, seen)]),
|
||||
calls: value.calls.filter((call) => call.method !== 'get').map((call) => [call.method, call.args.map((arg) => shape(arg, seen)), call.kinds]),
|
||||
};
|
||||
}
|
||||
|
||||
/** The application of a file: the second value, the first being the system software it refers to. */
|
||||
const APPLICATION = doc(`<object class="filius.software.system.Betriebssystem" id="Betriebssystem0"/>
|
||||
<object class="example.OwnApplication">
|
||||
<short>3</short><char>x</char>
|
||||
<void property="systemSoftware"><object idref="Betriebssystem0"/></void>
|
||||
<void property="aktiv"><boolean>true</boolean></void>
|
||||
<void property="count"><long>9007199254740993</long></void>
|
||||
<void property="small"><byte>-2</byte></void>
|
||||
<void property="ratio"><float>0.5</float></void>
|
||||
<void property="exact"><double>0.1</double></void>
|
||||
<void property="letter"><char>y</char></void>
|
||||
<void property="port"><int>55555</int></void>
|
||||
<void property="name"><string>Thread-7</string></void>
|
||||
<void property="nothing"><null/></void>
|
||||
<void property="type"><class>java.lang.String</class></void>
|
||||
<void property="names"><array class="java.lang.String" length="2"><void index="0"><string>a</string></void><void index="1"><string>b</string></void></array></void>
|
||||
<void property="first"><object class="java.util.ArrayList" id="ArrayList0"><void method="add"><int>1</int></void></object></void>
|
||||
<void property="second"><object idref="ArrayList0"/></void>
|
||||
<void property="alignment"><object class="javax.swing.SwingConstants" field="CENTER"/></void>
|
||||
<void property="settings"><void property="depth"><int>4</int></void></void>
|
||||
<void property="list"><void index="0"><void property="label"><string>constructed</string></void></void></void>
|
||||
<void property="table"><void method="put"><string>k</string><long>5</long></void></void>
|
||||
</object>`);
|
||||
|
||||
describe('captureBean', () => {
|
||||
it('writes a bean again with the same values and Java types', () => {
|
||||
const [system, application] = decodeJavaBeans(APPLICATION);
|
||||
const captured = captureBean(bean(application), system);
|
||||
if (!('bean' in captured)) throw new Error(captured.failure);
|
||||
const written = withSystemSoftware(captured.bean, object('filius.software.system.Betriebssystem', [], []));
|
||||
const [again] = decodeJavaBeans(encodeJavaBeans([written]));
|
||||
|
||||
const { properties: _original, ...rest } = shape(application!) as { properties: [string, unknown, unknown][] };
|
||||
const { properties: _again, ...restAgain } = shape(again!) as { properties: [string, unknown, unknown][] };
|
||||
expect(restAgain).toEqual(rest);
|
||||
// The system software is another object now, the one it was given. A
|
||||
// static constant the decoder answers with its measured value comes back
|
||||
// as that value, SwingConstants.CENTER as the int it is.
|
||||
const withoutSystem = (properties: [string, unknown, unknown][]) => properties.filter(([name]) => name !== 'systemSoftware');
|
||||
const expected = withoutSystem((shape(application!) as { properties: [string, unknown, unknown][] }).properties).map(
|
||||
([name, value, kind]) => (name === 'alignment' ? [name, value, 'int'] : [name, value, kind]),
|
||||
);
|
||||
expect(withoutSystem((shape(again!) as { properties: [string, unknown, unknown][] }).properties)).toEqual(expected);
|
||||
expect(bean(bean(again).properties.get('systemSoftware')).className).toBe('filius.software.system.Betriebssystem');
|
||||
// Shared within the bean stays shared.
|
||||
expect(bean(again).properties.get('first')).toBe(bean(again).properties.get('second'));
|
||||
});
|
||||
|
||||
it("marks the node's system software and puts the given one in its place", () => {
|
||||
const [system, application] = decodeJavaBeans(APPLICATION);
|
||||
const captured = captureBean(bean(application), system) as { bean: BeanObject };
|
||||
const mark = captured.bean.statements!.find((statement) => 'set' in statement && statement.set === 'systemSoftware');
|
||||
expect(mark).toEqual({ set: 'systemSoftware', value: { kind: 'object', className: SYSTEM_SOFTWARE_CLASS } });
|
||||
|
||||
const self = reference('SystemSoftware');
|
||||
const replaced = withSystemSoftware(captured.bean, self) as BeanObject;
|
||||
expect(replaced.statements!.find((statement) => 'set' in statement && statement.set === 'systemSoftware')).toEqual({ set: 'systemSoftware', value: self });
|
||||
// The captured bean itself is left as it was.
|
||||
expect(captured.bean.statements!).toContainEqual(mark);
|
||||
});
|
||||
|
||||
it('refuses what it cannot write again, and says why', () => {
|
||||
/** The last value of the file captured, the first being the system software. */
|
||||
const failure = (body: string) => {
|
||||
const values = decodeJavaBeans(doc(`<object class="filius.software.system.Betriebssystem" id="Betriebssystem0"/>${body}`));
|
||||
return captureBean(bean(values.at(-1)), values[0]);
|
||||
};
|
||||
expect(failure(`<object class="example.A"><void property="x"><object idref="Missing0"/></void></object>`)).toEqual({
|
||||
failure: 'a value is unresolved (idref Missing0 is not defined)',
|
||||
});
|
||||
expect(
|
||||
failure(`<object class="example.A"><void property="x"><object class="example.Factory" method="create"/></void></object>`),
|
||||
).toEqual({ failure: 'example.Factory.create makes an object this writer cannot' });
|
||||
// A folder of the file system, reached elsewhere by a getter: a copy would not be that folder.
|
||||
expect(
|
||||
failure(`<object class="example.B"><void id="DefaultMutableTreeNode0" property="root"><void property="x"><int>1</int></void></void></object>
|
||||
<object class="example.A"><void property="folder"><object idref="DefaultMutableTreeNode0"/></void></object>`),
|
||||
).toEqual({ failure: 'it refers to DefaultMutableTreeNode, which a getter reaches elsewhere in the file' });
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,8 @@
|
||||
// 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.
|
||||
* None since the reader keeps a project's own applications as their beans
|
||||
* (beanCapture.ts); until then the two examples that install one warned
|
||||
* that it was left out.
|
||||
*/
|
||||
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",
|
||||
],
|
||||
};
|
||||
export const EXPECTED_WARNINGS: Record<string, string[]> = {};
|
||||
|
||||
@@ -765,13 +765,13 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
||||
}),
|
||||
// In the order of Java's key.
|
||||
applications: [
|
||||
{ key: 'filius.software.clientserver.ClientBaustein', class: 'filius.software.clientserver.ClientBaustein', zielIPAdresse: '10.0.0.9', zielPort: 7777 },
|
||||
{ key: 'filius.software.clientserver.ServerBaustein', class: 'filius.software.clientserver.ServerBaustein', aktiv: true, port: 7777 },
|
||||
{ key: 'filius.software.dateiaustausch.PeerToPeerAnwendung', class: 'filius.software.dateiaustausch.PeerToPeerAnwendung', maxTeilnehmerZahl: 7 },
|
||||
{ key: 'filius.software.dns.DNSServer', class: 'filius.software.dns.DNSServer', aktiv: true, port: 53, recursiveResolutionEnabled: true },
|
||||
{ key: 'filius.software.clientserver.ClientBaustein', class: 'filius.software.clientserver.ClientBaustein', onItsSystem: true, zielIPAdresse: '10.0.0.9', zielPort: 7777 },
|
||||
{ key: 'filius.software.clientserver.ServerBaustein', class: 'filius.software.clientserver.ServerBaustein', onItsSystem: true, aktiv: true, port: 7777 },
|
||||
{ key: 'filius.software.dateiaustausch.PeerToPeerAnwendung', class: 'filius.software.dateiaustausch.PeerToPeerAnwendung', onItsSystem: true, maxTeilnehmerZahl: 7 },
|
||||
{ key: 'filius.software.dns.DNSServer', class: 'filius.software.dns.DNSServer', onItsSystem: true, aktiv: true, port: 53, recursiveResolutionEnabled: true },
|
||||
{
|
||||
key: 'filius.software.email.EmailAnwendung',
|
||||
class: 'filius.software.email.EmailAnwendung',
|
||||
class: 'filius.software.email.EmailAnwendung', onItsSystem: true,
|
||||
received: [javaMail()],
|
||||
sent: [javaMail({ versendet: true })],
|
||||
drafts: [javaMail({ betreff: '', text: '', empfaenger: [] })],
|
||||
@@ -792,7 +792,7 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
||||
},
|
||||
{
|
||||
key: 'filius.software.email.EmailServer',
|
||||
class: 'filius.software.email.EmailServer',
|
||||
class: 'filius.software.email.EmailServer', onItsSystem: true,
|
||||
aktiv: true,
|
||||
mailDomain: 'schule.de',
|
||||
beanAccounts: [serverAccount([javaMail()])],
|
||||
@@ -801,7 +801,7 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
||||
},
|
||||
{
|
||||
key: 'filius.software.firewall.Firewall',
|
||||
class: 'filius.software.firewall.Firewall',
|
||||
class: 'filius.software.firewall.Firewall', onItsSystem: true,
|
||||
activated: false,
|
||||
defaultPolicy: 1,
|
||||
dropICMP: false,
|
||||
@@ -809,12 +809,12 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
||||
filterUdp: true,
|
||||
rules: [['10.0.0.0', '255.0.0.0', '', '', 22, 6, 1]],
|
||||
},
|
||||
{ key: 'filius.software.lokal.FileExplorer', class: 'filius.software.lokal.FileExplorer' },
|
||||
{ key: 'filius.software.lokal.ImageViewer', class: 'filius.software.lokal.ImageViewer' },
|
||||
{ key: 'filius.software.lokal.Terminal', class: 'filius.software.lokal.Terminal' },
|
||||
{ key: 'filius.software.lokal.TextEditor', class: 'filius.software.lokal.TextEditor' },
|
||||
{ key: 'filius.software.www.WebBrowser', class: 'filius.software.www.WebBrowser' },
|
||||
{ key: 'filius.software.www.WebServer', class: 'filius.software.www.WebServer', aktiv: true, port: 8080, useVHost: true },
|
||||
{ key: 'filius.software.lokal.FileExplorer', class: 'filius.software.lokal.FileExplorer', onItsSystem: true },
|
||||
{ key: 'filius.software.lokal.ImageViewer', class: 'filius.software.lokal.ImageViewer', onItsSystem: true },
|
||||
{ key: 'filius.software.lokal.Terminal', class: 'filius.software.lokal.Terminal', onItsSystem: true },
|
||||
{ key: 'filius.software.lokal.TextEditor', class: 'filius.software.lokal.TextEditor', onItsSystem: true },
|
||||
{ key: 'filius.software.www.WebBrowser', class: 'filius.software.www.WebBrowser', onItsSystem: true },
|
||||
{ key: 'filius.software.www.WebServer', class: 'filius.software.www.WebServer', onItsSystem: true, aktiv: true, port: 8080, useVHost: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -843,11 +843,11 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
||||
fileSystem: { directory: 'root', children: [{ directory: 'peer2peer', children: [] }] },
|
||||
applications: [
|
||||
// Without a setting: no target (null), the model's 3 peers, Java's personal firewall.
|
||||
{ key: 'filius.software.clientserver.ClientBaustein', class: 'filius.software.clientserver.ClientBaustein', zielIPAdresse: null, zielPort: 55_555 },
|
||||
{ key: 'filius.software.dateiaustausch.PeerToPeerAnwendung', class: 'filius.software.dateiaustausch.PeerToPeerAnwendung', maxTeilnehmerZahl: 3 },
|
||||
{ key: 'filius.software.clientserver.ClientBaustein', class: 'filius.software.clientserver.ClientBaustein', onItsSystem: true, zielIPAdresse: null, zielPort: 55_555 },
|
||||
{ key: 'filius.software.dateiaustausch.PeerToPeerAnwendung', class: 'filius.software.dateiaustausch.PeerToPeerAnwendung', onItsSystem: true, maxTeilnehmerZahl: 3 },
|
||||
{
|
||||
key: 'filius.software.firewall.Firewall',
|
||||
class: 'filius.software.firewall.Firewall',
|
||||
class: 'filius.software.firewall.Firewall', onItsSystem: true,
|
||||
activated: true,
|
||||
defaultPolicy: 0,
|
||||
dropICMP: false,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -525,7 +525,7 @@ function flsNode(javaClass: FlsNode['javaClass'], fields: Partial<FlsNode> = {})
|
||||
portCount: 0,
|
||||
system: {},
|
||||
source: { kind: 'object', className: javaClass, id: undefined, args: [], staticField: undefined,
|
||||
factoryMethod: undefined, properties: new Map(), calls: [], presetElements: new Map(), presetEntries: new Map(), fromGetter: false },
|
||||
factoryMethod: undefined, properties: new Map(), propertyKinds: new Map(), propertiesFromGetters: new Set(), argKinds: [], calls: [], presetElements: new Map(), presetEntries: new Map(), fromGetter: false },
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,6 +92,28 @@ describe('XMLDecoder rules', () => {
|
||||
expect(object(rectangle).properties.get('y')).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps the Java type of each primitive a setter, a constructor or a call gets', () => {
|
||||
// A shared getter result, as in the corpus: a button's measured icon text gap.
|
||||
const [, bean] = decodeJavaBeans(
|
||||
doc(`<object class="filius.gui.netzwerksicht.JSidebarButton"><void id="Integer0" property="iconTextGap"/></object>
|
||||
<object class="example.Bean"><short>3</short><char>x</char>
|
||||
<void property="count"><long>7</long></void>
|
||||
<void property="ratio"><float>0.5</float></void>
|
||||
<void property="gap"><object idref="Integer0"/></void>
|
||||
<void property="shared"><byte id="Byte0">2</byte></void>
|
||||
<void property="again"><object idref="Byte0"/></void>
|
||||
<void property="text"><string>7</string></void>
|
||||
<void property="count2"><int>1</int></void>
|
||||
<void property="count2"><string>no longer a number</string></void>
|
||||
<void method="put"><string>key</string><double>1.0</double></void>
|
||||
</object>`),
|
||||
);
|
||||
|
||||
expect(object(bean).argKinds).toEqual(['short', 'char']);
|
||||
expect(Object.fromEntries(object(bean).propertyKinds)).toEqual({ count: 'long', ratio: 'float', gap: 'int', shared: 'byte', again: 'byte' });
|
||||
expect(object(bean).calls).toEqual([{ method: 'put', args: ['key', 1], kinds: [undefined, 'double'] }]);
|
||||
});
|
||||
|
||||
it('says "unresolved" for a shared getter value it has no measurement for', () => {
|
||||
const [, holder] = decodeJavaBeans(
|
||||
doc(`<object class="com.example.Unknown"><void id="Integer0" property="size"/></object>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
// installs them when it loads the whole archive (tools/java-oracle, laden).
|
||||
// The reader keeps them as beans, without knowing what Java makes of them;
|
||||
// its test compares them by key and class.
|
||||
|
||||
import { FILIUS_APPLICATION_CLASSES } from '../src/index.js';
|
||||
|
||||
@@ -32,6 +32,30 @@ export function withoutOwnApplications<T>(view: T): T {
|
||||
};
|
||||
}
|
||||
|
||||
/** Java's view with each own application as its key, class and whether it runs on its node's system software. */
|
||||
export function withOwnApplicationsByClass<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 { key: string; class: string }[]).map((application) =>
|
||||
(FILIUS_APPLICATION_CLASSES as readonly string[]).includes(application.class)
|
||||
? application
|
||||
: { key: application.key, class: application.class, onItsSystem: (application as { onItsSystem?: boolean }).onItsSystem },
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/** Whether Java's view has an own application installed. */
|
||||
export function hasOwnApplications(view: unknown): boolean {
|
||||
return JSON.stringify(withoutOwnApplications(view)) !== JSON.stringify(view);
|
||||
|
||||
@@ -21,7 +21,9 @@ import {
|
||||
type JavaEmailAccount,
|
||||
} from '../src/index.js';
|
||||
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
|
||||
import { hasOwnApplications, OWN_APPLICATION_FILES, withoutOwnApplications } from './ownApplications.js';
|
||||
import { SYSTEM_SOFTWARE_CLASS } from '../src/beanCapture.js';
|
||||
import type { BeanValue } from '../src/javaBeansWriter.js';
|
||||
import { hasOwnApplications, OWN_APPLICATION_FILES, withOwnApplicationsByClass } from './ownApplications.js';
|
||||
import { javaEmail, javaEmailAccount } from './javaEmailNames.js';
|
||||
|
||||
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||
@@ -69,12 +71,18 @@ function javaFileTree(tree: FlsFileTree): unknown {
|
||||
}
|
||||
|
||||
/** FlsOracle.applications' shape: Java's property names, sorted by key. */
|
||||
function isSystemSoftwareMark(value: BeanValue): boolean {
|
||||
return value !== null && typeof value === 'object' && value.kind === 'object' && value.className === SYSTEM_SOFTWARE_CLASS;
|
||||
}
|
||||
|
||||
function javaApplications(applications: FlsApplication[]): unknown[] {
|
||||
return [...applications]
|
||||
.sort((lhs, rhs) => ((lhs.key ?? '') < (rhs.key ?? '') ? -1 : 1))
|
||||
.map((application) => ({
|
||||
key: application.key,
|
||||
class: application.javaClass,
|
||||
// The reader's applications are its node's: each file gives them its system software.
|
||||
onItsSystem: true,
|
||||
...(application.isActive === undefined ? {} : { aktiv: application.isActive, port: application.port }),
|
||||
...(application.targetPort === undefined
|
||||
? {}
|
||||
@@ -178,7 +186,21 @@ 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) }),
|
||||
...('templateFiles' in node.system ? { templateFiles: undefined } : {}),
|
||||
...(node.system.applications === undefined ? {} : { applications: javaApplications(node.system.applications) }),
|
||||
...(node.system.applications === undefined
|
||||
? {}
|
||||
: {
|
||||
applications: [
|
||||
...(javaApplications(node.system.applications) as { key: string | null }[]),
|
||||
...(node.system.ownApplications ?? []).map((application) => ({
|
||||
key: application.key,
|
||||
class: application.javaClass,
|
||||
onItsSystem: application.bean.statements?.some(
|
||||
(statement) => 'set' in statement && statement.set === 'systemSoftware' && isSystemSoftwareMark(statement.value),
|
||||
),
|
||||
})),
|
||||
].sort((lhs, rhs) => ((lhs.key ?? '') < (rhs.key ?? '') ? -1 : 1)),
|
||||
}),
|
||||
...('ownApplications' in node.system ? { ownApplications: undefined } : {}),
|
||||
...(node.system.firewall === undefined
|
||||
? {}
|
||||
: {
|
||||
@@ -252,7 +274,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(withoutOwnApplications(expected), topology),
|
||||
withoutTemplateFiles(withOwnApplicationsByClass(expected), topology),
|
||||
);
|
||||
expect(topology.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@ 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');
|
||||
@@ -94,8 +93,10 @@ describe('the topology written again, as Java loads it', () => {
|
||||
for (const file of flsFiles(corpusRoot)) {
|
||||
const name = relative(corpusRoot, file);
|
||||
it(name, () => {
|
||||
const topology = readTopology(decodeJavaBeans(readFlsArchive(new Uint8Array(readFileSync(file))).configurationXml));
|
||||
const archive = writeFlsArchive(new TextEncoder().encode(writeTopologyXml(topology)));
|
||||
const contents = readFlsArchive(new Uint8Array(readFileSync(file)));
|
||||
const topology = readTopology(decodeJavaBeans(contents.configurationXml));
|
||||
// With the archive's other entries: the classes of the project's own applications.
|
||||
const archive = writeFlsArchive(new TextEncoder().encode(writeTopologyXml(topology)), contents.supplementalEntries);
|
||||
if (process.env.UPDATE_WRITTEN === '1') {
|
||||
mkdirSync(dirname(join(writtenRoot, name)), { recursive: true });
|
||||
writeFileSync(join(writtenRoot, name), archive);
|
||||
@@ -104,8 +105,7 @@ 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'),
|
||||
);
|
||||
// The written file has no archive entries: Java installs no own application (B2).
|
||||
expect(topologyOf(java!)).toEqual(topologyOf(withRecipientsRead(withoutOwnApplications(original[name]!)) as JavaFile));
|
||||
expect(topologyOf(java!)).toEqual(topologyOf(withRecipientsRead(original[name]!) as JavaFile));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -448,13 +448,13 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
||||
},
|
||||
// In the order of Java's key.
|
||||
applications: [
|
||||
{ key: 'filius.software.clientserver.ClientBaustein', class: 'filius.software.clientserver.ClientBaustein', zielIPAdresse: '10.0.0.9', zielPort: 7777 },
|
||||
{ key: 'filius.software.clientserver.ServerBaustein', class: 'filius.software.clientserver.ServerBaustein', aktiv: true, port: 7777 },
|
||||
{ key: 'filius.software.dateiaustausch.PeerToPeerAnwendung', class: 'filius.software.dateiaustausch.PeerToPeerAnwendung', maxTeilnehmerZahl: 7 },
|
||||
{ key: 'filius.software.dns.DNSServer', class: 'filius.software.dns.DNSServer', aktiv: true, port: 5353, recursiveResolutionEnabled: true },
|
||||
{ key: 'filius.software.clientserver.ClientBaustein', class: 'filius.software.clientserver.ClientBaustein', onItsSystem: true, zielIPAdresse: '10.0.0.9', zielPort: 7777 },
|
||||
{ key: 'filius.software.clientserver.ServerBaustein', class: 'filius.software.clientserver.ServerBaustein', onItsSystem: true, aktiv: true, port: 7777 },
|
||||
{ key: 'filius.software.dateiaustausch.PeerToPeerAnwendung', class: 'filius.software.dateiaustausch.PeerToPeerAnwendung', onItsSystem: true, maxTeilnehmerZahl: 7 },
|
||||
{ key: 'filius.software.dns.DNSServer', class: 'filius.software.dns.DNSServer', onItsSystem: true, aktiv: true, port: 5353, recursiveResolutionEnabled: true },
|
||||
{
|
||||
key: 'filius.software.email.EmailAnwendung',
|
||||
class: 'filius.software.email.EmailAnwendung',
|
||||
class: 'filius.software.email.EmailAnwendung', onItsSystem: true,
|
||||
received: [javaMail()],
|
||||
sent: [javaMail({ versendet: true, neu: false })],
|
||||
drafts: [javaMail({ betreff: null, text: null, dateReceived: null, empfaenger: [] })],
|
||||
@@ -464,7 +464,7 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
||||
},
|
||||
{
|
||||
key: 'filius.software.email.EmailServer',
|
||||
class: 'filius.software.email.EmailServer',
|
||||
class: 'filius.software.email.EmailServer', onItsSystem: true,
|
||||
aktiv: true,
|
||||
mailDomain: 'schule.de',
|
||||
beanAccounts: [
|
||||
@@ -486,7 +486,7 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
||||
},
|
||||
{
|
||||
key: 'filius.software.firewall.Firewall',
|
||||
class: 'filius.software.firewall.Firewall',
|
||||
class: 'filius.software.firewall.Firewall', onItsSystem: true,
|
||||
activated: false,
|
||||
defaultPolicy: 1,
|
||||
dropICMP: true,
|
||||
@@ -494,12 +494,12 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
||||
filterUdp: false,
|
||||
rules: [['10.0.0.0', '255.0.0.0', '', '', 22, 6, 1]],
|
||||
},
|
||||
{ key: 'filius.software.lokal.FileExplorer', class: 'filius.software.lokal.FileExplorer' },
|
||||
{ key: 'filius.software.lokal.ImageViewer', class: 'filius.software.lokal.ImageViewer' },
|
||||
{ key: 'filius.software.lokal.Terminal', class: 'filius.software.lokal.Terminal' },
|
||||
{ key: 'filius.software.lokal.TextEditor', class: 'filius.software.lokal.TextEditor' },
|
||||
{ key: 'filius.software.www.WebBrowser', class: 'filius.software.www.WebBrowser' },
|
||||
{ key: 'filius.software.www.WebServer', class: 'filius.software.www.WebServer', aktiv: true, port: 8080, useVHost: true },
|
||||
{ key: 'filius.software.lokal.FileExplorer', class: 'filius.software.lokal.FileExplorer', onItsSystem: true },
|
||||
{ key: 'filius.software.lokal.ImageViewer', class: 'filius.software.lokal.ImageViewer', onItsSystem: true },
|
||||
{ key: 'filius.software.lokal.Terminal', class: 'filius.software.lokal.Terminal', onItsSystem: true },
|
||||
{ key: 'filius.software.lokal.TextEditor', class: 'filius.software.lokal.TextEditor', onItsSystem: true },
|
||||
{ key: 'filius.software.www.WebBrowser', class: 'filius.software.www.WebBrowser', onItsSystem: true },
|
||||
{ key: 'filius.software.www.WebServer', class: 'filius.software.www.WebServer', onItsSystem: true, aktiv: true, port: 8080, useVHost: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
+333
-115
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user