fix(fls): follow Java's formats, not Java's errors

Java stays the reference for the .fls format, but its errors are no longer
copied. The Java oracle still measures Java as it is; each deliberate
departure is named in the tests with its reason (JAVA_BUGS) and must really
depart. The corpus - what Java itself writes - still reads exactly as in
Java.

- /dns/hosts: a label may start with a digit, holds no backslash, and the
  last one is not all digits (RFC 1123, RFC 3696); A, MX and NS are read in
  any case (RFC 4343); a negative TTL is invalid, so 3600 (RFC 2181). Also
  for the e-mail server's domain.
- /mailserver/konten.txt: empty fields count (Java's StringTokenizer skips
  them and shifts the rest, so an empty password breaks the account); a
  short line alone is left out, with a warning (Java throws for the whole
  file); a message keeps an empty text or subject (Java's split loses it);
  a ";" in an unescaped subject stays in the message; CRLF lines.
- /konten.txt: the last complete line is the account, also with an empty
  address (Java takes the last line and loses the account); CRLF lines.
- Static DHCP assignments: a broken entry alone is left out (Java loses
  every entry after it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 07:20:52 +00:00
co-authored by Claude Opus 5.5
parent ec5523d7c7
commit 8f9930f1ff
10 changed files with 319 additions and 160 deletions
+9 -10
View File
@@ -19,7 +19,6 @@ import {
type FlsNodeClass,
type FlsTopology,
type JavaEmailAccount,
type JavaFailure,
} from '../src/index.js';
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
import { javaEmail, javaEmailAccount } from './javaEmailNames.js';
@@ -122,9 +121,8 @@ function javaApplications(applications: FlsApplication[]): unknown[] {
}));
}
function javaFileAccounts(accounts: JavaEmailAccount[] | JavaFailure | null): unknown {
if (accounts === null) return null;
return Array.isArray(accounts) ? accounts.map(javaEmailAccount) : `!${accounts.exception}`;
function javaFileAccounts(accounts: JavaEmailAccount[] | null): unknown {
return accounts === null ? null : accounts.map(javaEmailAccount);
}
/** Java's property names, as FlsOracle.java writes them. */
@@ -370,9 +368,9 @@ describe('readTopology rules', () => {
});
});
it('splits static DHCP assignments at Java whitespace and stops where Java throws', () => {
// U+3000 separates, U+00A0 does not; the third entry has no IP, so Java
// throws there and the fourth is never read.
it('splits static DHCP assignments at Java whitespace and leaves out only a broken one', () => {
// U+3000 separates, U+00A0 does not; the third entry has no IP. Java
// throws there and never reads the fourth — an error not copied.
const { nodes, warnings } = topologyOf(`<object class="java.util.LinkedList">${nodeItem(`<object class="filius.hardware.knoten.Rechner">
<void property="systemSoftware"><void property="DHCPServer">
<void property="staticAssignedAddresses"><object class="java.util.ArrayList">
@@ -393,10 +391,11 @@ describe('readTopology rules', () => {
staticAssignedAddresses: [
{ mac: '0A:00:00:00:00:01', ip: '10.0.0.1' },
{ mac: '0A:00:00:00:00:02', ip: '10.0.0.2\u00A0x' },
{ mac: '0A:00:00:00:00:04', ip: '10.0.0.4' },
],
});
expect(warnings).toEqual([
'node 1: DHCPServer: static assignment "0A:00:00:00:00:03" has no MAC and IP; Java stops reading here',
'node 1: DHCPServer: static assignment "0A:00:00:00:00:03" has no MAC and IP, left out',
]);
});
@@ -462,7 +461,7 @@ describe('readTopology rules', () => {
const { nodes } = topologyOf(`<object class="java.util.LinkedList">${nodeItem(`<object class="filius.hardware.knoten.Rechner">
<void property="systemSoftware"><void property="installierteAnwendungen">
${put('filius.software.email.EmailServer', `
<void property="mailDomain"><string>1und1.de</string></void>
<void property="mailDomain"><string>a_b.de</string></void>
<void property="listeBenutzerkonten"><void method="add"><object class="filius.software.email.EmailKonto">
<void property="benutzername"><string>bob</string></void>
<void property="nachname"><null/></void>
@@ -473,7 +472,7 @@ describe('readTopology rules', () => {
</void></void></object>`)}</object>`);
const server = nodes[0]!.system.applications![0]!;
// setMailDomain keeps "filius.de" for a domain Java's pattern rejects.
// setMailDomain keeps "filius.de" for a domain that is not a domain name.
expect(server).toMatchObject({ isActive: false, mailDomain: 'filius.de' });
expect(server.beanAccounts![0]).toMatchObject({ username: 'bob', lastName: '', firstName: '' });
expect(server.beanAccounts![0]!.messages[0]!.sender).toEqual({ name: 'Alice', mailAddress: 'alice@senior.de' });