test(fls): e-mail account files against Swift's reader

The Swift oracle reads the 43 account files Java read (--email-files, with
TopologyRuntimeEmailStorage.decodeJavaServer and decodeJavaClient). The
accounts the model takes match Swift's except where Swift checks names,
hosts and addresses and fails the whole file on one it rejects, or reads
the format otherwise (the first client line instead of the last, exactly
nine fields, a required messages field). Every departure is named with its
reason; Java's reading is right in each, none is a Java error Swift fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 07:40:14 +00:00
co-authored by Claude Opus 5.5
parent 1efbb8a660
commit 956443726b
7 changed files with 910 additions and 2 deletions
+1
View File
@@ -48,6 +48,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
| `test/javaIp.test.ts`, `test/ip-check.*` | — | neu: `IP.ipCheck`-Fälle, Ergebnis aus Java | | `test/javaIp.test.ts`, `test/ip-check.*` | — | neu: `IP.ipCheck`-Fälle, Ergebnis aus Java |
| `src/javaEmail.ts` | Java `EmailServer` (Konten-Bean, `kontenLaden`/`stringZuListeBenutzerkonten`), `EmailAnwendung` (Ordner, Adressbuch, `laden`), `EmailKonto`, `Email`, `AddressEntry`, `EmailUtils` | neu nach Java: E-Mail-Beans über ihre Setter, `/mailserver/konten.txt` und `/konten.txt` im Format von Javas Lesern, ohne ihre Fehler (leere Felder zählen, eine kurze Zeile fällt allein weg, Nachrichten mit leerem Text oder Betreff bleiben, `;` im Betreff bleibt, CRLF, beim Client die letzte vollständige Zeile); Empfänger älterer Dateien als Zeichenketten; gegen Java auf dem Korpus und an 43 Fällen geprüft (13 benannte Java-Fehler) | | `src/javaEmail.ts` | Java `EmailServer` (Konten-Bean, `kontenLaden`/`stringZuListeBenutzerkonten`), `EmailAnwendung` (Ordner, Adressbuch, `laden`), `EmailKonto`, `Email`, `AddressEntry`, `EmailUtils` | neu nach Java: E-Mail-Beans über ihre Setter, `/mailserver/konten.txt` und `/konten.txt` im Format von Javas Lesern, ohne ihre Fehler (leere Felder zählen, eine kurze Zeile fällt allein weg, Nachrichten mit leerem Text oder Betreff bleiben, `;` im Betreff bleibt, CRLF, beim Client die letzte vollständige Zeile); Empfänger älterer Dateien als Zeichenketten; gegen Java auf dem Korpus und an 43 Fällen geprüft (13 benannte Java-Fehler) |
| `src/importEmail.ts` | `Persistence/TopologyProjectStore.swift` (`registerJavaEmailConfigurations`, `assignedJavaEmailMessages`) | übertragen, Eingabe ist Javas Sicht: Server-Konten aus `/mailserver/konten.txt`, ohne Datei aus der Bean-Liste (Java verlöre sie); Client-Konto aus `/konten.txt`, ohne Datei aus der älteren `kontoListe` (Filius 2.14 liest sie nicht mehr); Konten und Nachrichten bleiben, wo Swift verwirft, was seine Prüfung nicht besteht; Vor- und Nachname getrennt; Empfangszeit nur aus der Datei (Swift nummeriert); gegen Swifts Modell geprüft | | `src/importEmail.ts` | `Persistence/TopologyProjectStore.swift` (`registerJavaEmailConfigurations`, `assignedJavaEmailMessages`) | übertragen, Eingabe ist Javas Sicht: Server-Konten aus `/mailserver/konten.txt`, ohne Datei aus der Bean-Liste (Java verlöre sie); Client-Konto aus `/konten.txt`, ohne Datei aus der älteren `kontoListe` (Filius 2.14 liest sie nicht mehr); Konten und Nachrichten bleiben, wo Swift verwirft, was seine Prüfung nicht besteht; Vor- und Nachname getrennt; Empfangszeit nur aus der Datei (Swift nummeriert); gegen Swifts Modell geprüft |
| `test/importEmail.test.ts`, `test/email-*.swift.json` | `State/TopologyEmail.swift` (`decodeJavaServer`, `decodeJavaClient`) als Vergleich | neu: dieselben 43 Kontodateien mit Swifts Leser; wie Swift bis auf benannte Fälle, in denen Swift prüft und die ganze Datei verwirft, wo Java liest |
| `src/javaFileSystem.ts`, `src/javaString.ts` | Java `Dateisystem.holeDatei`, `String.trim`/`split`/`equalsIgnoreCase` | neu nach Java; die Groß-/Kleinschreibung gegenüber ASCII am JDK über die ganze BMP gemessen | | `src/javaFileSystem.ts`, `src/javaString.ts` | Java `Dateisystem.holeDatei`, `String.trim`/`split`/`equalsIgnoreCase` | neu nach Java; die Groß-/Kleinschreibung gegenüber ASCII am JDK über die ganze BMP gemessen |
| `test/javaEmail.test.ts`, `test/email-*.cases.txt`, `test/email-*.java.json`, `test/javaEmailNames.ts`, `test/javaFileSystem.test.ts` | — | neu: Kontodateien von Server und Client, Ergebnis aus Java; Dateifund | | `test/javaEmail.test.ts`, `test/email-*.cases.txt`, `test/email-*.java.json`, `test/javaEmailNames.ts`, `test/javaFileSystem.test.ts` | — | neu: Kontodateien von Server und Client, Ergebnis aus Java; Dateifund |
| `test/dnsHosts.test.ts`, `test/dns-hosts.*` | — | neu: `/dns/hosts`-Fälle, wie Java bis auf 7 benannte Java-Fehler, das Modell wie Swift bis auf benannte Fälle mit Grund | | `test/dnsHosts.test.ts`, `test/dns-hosts.*` | — | neu: `/dns/hosts`-Fälle, wie Java bis auf 7 benannte Java-Fehler, das Modell wie Swift bis auf benannte Fälle mit Grund |
+129
View File
@@ -0,0 +1,129 @@
[
[
"mail.gmx.de;mail.gmx.de;110;25;herbert;herbert1234;Schmidt;Herbert;herbert@gmx.de",
{
"email" : "herbert@gmx.de",
"name" : "Herbert Schmidt",
"password" : "herbert1234",
"pop3Host" : "mail.gmx.de",
"pop3Port" : 110,
"smtpHost" : "mail.gmx.de",
"smtpPort" : 25,
"username" : "herbert"
}
],
[
"mail.gmx.de;mail.gmx.de;110;25;herbert;herbert1234;Schmidt;Herbert;herbert@gmx.de\\u000A",
{
"email" : "herbert@gmx.de",
"name" : "Herbert Schmidt",
"password" : "herbert1234",
"pop3Host" : "mail.gmx.de",
"pop3Port" : 110,
"smtpHost" : "mail.gmx.de",
"smtpPort" : 25,
"username" : "herbert"
}
],
[
"a;a;110;25;first;p;L;F;first@x\\u000Ab;b;110;25;second;p;L;F;second@x\\u000A",
{
"email" : "first@x",
"name" : "F L",
"password" : "p",
"pop3Host" : "a",
"pop3Port" : 110,
"smtpHost" : "a",
"smtpPort" : 25,
"username" : "first"
}
],
[
"a;a;110;25;first;p;L;F;first@x\\u000Abroken\\u000A",
{
"email" : "first@x",
"name" : "F L",
"password" : "p",
"pop3Host" : "a",
"pop3Port" : 110,
"smtpHost" : "a",
"smtpPort" : 25,
"username" : "first"
}
],
[
"",
"!invalidAccount(\"missing client account\")"
],
[
"\\u000A",
"!invalidAccount(\"missing client account\")"
],
[
"\\u000A\\u000A\\u000A",
"!invalidAccount(\"missing client account\")"
],
[
"a;b;110;25;u;p;L;F;\\u000A",
"!invalidAddress(\"\")"
],
[
"a;b;110;25;u;p;L;F;u@x;extra\\u000A",
"!invalidAccount(\"malformed client account\")"
],
[
"a;b;110;25;u;p;L;F;u@x\\u000D\\u000A",
{
"email" : "u@x",
"name" : "F L",
"password" : "p",
"pop3Host" : "a",
"pop3Port" : 110,
"smtpHost" : "b",
"smtpPort" : 25,
"username" : "u"
}
],
[
"a;;110;25;u;p;;;u@x\\u000A",
"!invalidHost(\"\")"
],
[
"a;b;110;25;u;p;L;F;u@x\\u000A\\u000A\\u000A",
{
"email" : "u@x",
"name" : "F L",
"password" : "p",
"pop3Host" : "a",
"pop3Port" : 110,
"smtpHost" : "b",
"smtpPort" : 25,
"username" : "u"
}
],
[
"\\u000Aa;b;110;25;u;p;L;F;u@x",
{
"email" : "u@x",
"name" : "F L",
"password" : "p",
"pop3Host" : "a",
"pop3Port" : 110,
"smtpHost" : "b",
"smtpPort" : 25,
"username" : "u"
}
],
[
" a ; b ;x;y; u ; p ; L ; F ; u@x \\u000A",
"!invalidAccount(\"malformed client account\")"
],
[
";;;;;;;;",
"!invalidAccount(\"malformed client account\")"
],
[
";;;;;;;;x",
"!invalidAccount(\"malformed client account\")"
]
]
+584
View File
@@ -0,0 +1,584 @@
[
[
"bob;filius.de;bob;Nachname;Vorname;\\u000A",
[
{
"mailbox" : [
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;\\u000Aalice;filius.de;alice;A;B;\\u000A",
[
{
"mailbox" : [
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
},
{
"mailbox" : [
],
"name" : "B A",
"password" : "alice",
"username" : "alice"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname",
"!invalidAccount(\"malformed server account\")"
],
[
"bob;;bob;Nachname;Vorname;\\u000A",
"!invalidDomain(\"\")"
],
[
"bob;filius.de;bob;Nachname\\u000A",
"!invalidAccount(\"malformed server account\")"
],
[
"bob;filius.de;bob;Nachname;Vorname;\\u000Abroken\\u000A",
"!invalidAccount(\"malformed server account\")"
],
[
" bob ;filius.de; bob ;Nachname;Vorname;\\u000A",
"!invalidAccount(\" bob \")"
],
[
"bob;filius.de;bob;Nachname;Vorname\\u000D\\u000A",
"!invalidAccount(\"malformed server account\")"
],
[
"",
[
]
],
[
"\\u000A\\u000A",
[
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "Hi BoB!\n",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
"Alice",
"alice@senior.de"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi Bob!",
"to" : [
[
null,
"bob@filius.de"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "Hi BoB!\n",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
"Alice",
"alice@senior.de"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi Bob!",
"to" : [
[
null,
"bob@filius.de"
]
]
},
{
"bcc" : [
],
"body" : "Hi BoB!\n",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
"Alice",
"alice@senior.de"
],
"id" : 2,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 2,
"subject" : "Hi Bob!",
"to" : [
[
null,
"bob@filius.de"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;Alice <alice@senior.de>$bob@filius.de$$$$Hi$Text\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "Text",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
"Alice",
"alice@senior.de"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi",
"to" : [
[
null,
"bob@filius.de"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi$\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
"Alice",
"alice@senior.de"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi",
"to" : [
[
null,
"bob@filius.de"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$$\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
"Alice",
"alice@senior.de"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "",
"to" : [
[
null,
"bob@filius.de"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi $5$Text\\u000A",
"!malformedMessage"
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$c@z$d@w$1234$Hi$Text\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
[
null,
"d@w"
]
],
"body" : "Text",
"cc" : [
[
null,
"c@z"
]
],
"deliveryIdentity" : null,
"from" : [
null,
"a@x"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1234,
"subject" : "Hi",
"to" : [
[
null,
"b@y"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y, ,c@z,$$$$Hi$Text\\u000A",
"!malformedMessage"
],
[
"bob;filius.de;bob;Nachname;Vorname;#Name <a@x$b@y$$$$Hi$Text\\u000A",
"!malformedMessage"
],
[
"bob;filius.de;bob;Nachname;Vorname;#<a@x>$Bob <b@y> , <c@z>$$$$Hi$Text\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "Text",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
null,
"a@x"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi",
"to" : [
[
"Bob",
"b@y"
],
[
null,
"c@z"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Only Name$b@y$$$$Hi$Text\\u000A",
"!malformedMessage"
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$l1||l2&&00124&&x&&00036&&y&&00059&&z&&00035&&w\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "l1\n\nl2|x$y;z#w",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
null,
"a@x"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi",
"to" : [
[
null,
"b@y"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$&&00124&&|\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "|\n",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
null,
"a@x"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi",
"to" : [
[
null,
"b@y"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$Text;extra\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "Text;extra",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
null,
"a@x"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi",
"to" : [
[
null,
"b@y"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;##a@x$b@y$$$$Hi$Text##\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "Text",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
null,
"a@x"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi",
"to" : [
[
null,
"b@y"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$Text\\u000D\\u000A",
[
{
"mailbox" : [
{
"bcc" : [
],
"body" : "Text",
"cc" : [
],
"deliveryIdentity" : null,
"from" : [
null,
"a@x"
],
"id" : 1,
"isMarkedForDeletion" : false,
"isNew" : true,
"isSent" : false,
"receivedAtMilliseconds" : 1,
"subject" : "Hi",
"to" : [
[
null,
"b@y"
]
]
}
],
"name" : "Vorname Nachname",
"password" : "bob",
"username" : "bob"
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#\\u001Fa@x $b@y$$$$Hi$Text\\u000A",
"!malformedMessage"
]
]
+157
View File
@@ -0,0 +1,157 @@
// The e-mail account files as the model takes them, against Swift's reader
// (TopologyRuntimeEmailStorage.decodeJavaServer/decodeJavaClient,
// email-*.swift.json, tools/swift-oracle --email-files) on the cases Java
// read too (javaEmail.test.ts). Swift checks names, hosts and addresses and
// fails the whole file on one it rejects, where Java reads it; each such
// case is named with its reason and must really depart.
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import type { EmailAddress, EmailMessage } from '@mdg-filius/core';
import { emailClientConfiguration, emailServerConfiguration } from '../src/importEmail.js';
import { readEmailClientFile, readEmailServerFile } from '../src/javaEmail.js';
import type { FlsApplication } from '../src/index.js';
const here = dirname(fileURLToPath(import.meta.url));
/** The cases file's escapes: a backslash, `u` and four hex digits for one UTF-16 unit. */
function unescape(line: string): string {
return line.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)));
}
const fullName = (person: { firstName: string; lastName: string }) =>
[person.firstName, person.lastName].filter((part) => part !== '').join(' ');
function swiftMessage(message: EmailMessage) {
const address = (entry: EmailAddress) => [entry.name, entry.mailAddress];
return {
id: message.id,
from: address(message.from),
to: message.to.map(address),
cc: message.cc.map(address),
bcc: message.bcc.map(address),
subject: message.subject,
body: message.body,
deliveryIdentity: null,
receivedAtMilliseconds: message.receivedAtMilliseconds ?? null,
isNew: message.isNew,
isMarkedForDeletion: message.isMarkedForDeletion,
isSent: message.isSent,
};
}
function serverAccounts(content: string) {
const application = { fileAccounts: readEmailServerFile(content, () => {}), mailDomain: 'filius.de' } as FlsApplication;
return emailServerConfiguration(application, () => {}).accounts.map((account) => ({
username: account.username,
password: account.password,
name: fullName(account),
mailbox: account.mailbox.map(swiftMessage),
}));
}
function clientAccount(content: string) {
const application = { fileAccount: readEmailClientFile(content, () => {}) } as FlsApplication;
const account = emailClientConfiguration(application, () => {}).account;
if (account === null) return null;
const { firstName, lastName, ...fields } = account;
return { ...fields, name: fullName({ firstName, lastName }) };
}
type Departures = { reason: string; lines: string[] }[];
/**
* Swift's result where it means the same as ours: Swift numbers a mailbox's
* messages as their received time (a Swift error named in
* importProject.test.ts), and "no account" is an error to Swift.
*/
function comparable(expected: unknown): unknown {
if (expected === '!invalidAccount("missing client account")') return null;
if (!Array.isArray(expected)) return expected;
return expected.map((account: { mailbox: { id: number; receivedAtMilliseconds: number | null }[] }) => ({
...account,
mailbox: account.mailbox.map((message) =>
message.receivedAtMilliseconds === message.id ? { ...message, receivedAtMilliseconds: null } : message,
),
}));
}
function compareWithSwift(kind: 'server' | 'client', departures: Departures, read: (content: string) => unknown) {
const cases = JSON.parse(readFileSync(join(here, `email-${kind}.swift.json`), 'utf8')) as [string, unknown][];
const lines = readFileSync(join(here, `email-${kind}.cases.txt`), 'ascii').split('\n').slice(0, -1);
const byLine = new Map(departures.flatMap(({ reason, lines: named }) => named.map((line) => [line, reason] as const)));
it('covers every line of the cases file and names only its lines', () => {
expect(cases.map(([line]) => line)).toEqual(lines);
expect([...byLine.keys()].filter((line) => !lines.includes(line))).toEqual([]);
});
for (const [line, expected] of cases) {
const reason = byLine.get(line);
it(reason === undefined ? JSON.stringify(line) : `${JSON.stringify(line)} (departs: ${reason})`, () => {
if (reason === undefined) expect(read(unescape(line))).toEqual(comparable(expected));
else expect(read(unescape(line))).not.toEqual(comparable(expected));
});
}
}
const SERVER_DEPARTURES: Departures = [
{
reason: 'a line without the messages field is an account, as in Java; Swift wants the sixth field and fails the file',
lines: [String.raw`bob;filius.de;bob;Nachname;Vorname`, String.raw`bob;filius.de;bob;Nachname;Vorname\u000D\u000A`],
},
{
reason: "the file's domain column is not used (Java takes the server's); Swift fails the file on an empty one",
lines: [String.raw`bob;;bob;Nachname;Vorname;\u000A`],
},
{
reason: 'a short line alone is left out; Swift fails the whole file',
lines: [String.raw`bob;filius.de;bob;Nachname\u000A`, String.raw`bob;filius.de;bob;Nachname;Vorname;\u000Abroken\u000A`],
},
{
reason: 'a user name stays as Java reads it; Swift rejects one with spaces and fails the file',
lines: [String.raw` bob ;filius.de; bob ;Nachname;Vorname;\u000A`],
},
{
reason: 'a message without seven fields alone is left out; Swift fails the whole file',
lines: [String.raw`bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi $5$Text\u000A`],
},
{
reason: "addresses as Java reads them (blank entries skipped, a name alone, Java's trim); Swift fails the file",
lines: [
String.raw`bob;filius.de;bob;Nachname;Vorname;#a@x$b@y, ,c@z,$$$$Hi$Text\u000A`,
String.raw`bob;filius.de;bob;Nachname;Vorname;#Name <a@x$b@y$$$$Hi$Text\u000A`,
String.raw`bob;filius.de;bob;Nachname;Vorname;#Only Name$b@y$$$$Hi$Text\u000A`,
String.raw`bob;filius.de;bob;Nachname;Vorname;#\u001Fa@x $b@y$$$$Hi$Text\u000A`,
],
},
];
const CLIENT_DEPARTURES: Departures = [
{
reason: 'the last complete line is the account, as in Java; Swift takes the first',
lines: [String.raw`a;a;110;25;first;p;L;F;first@x\u000Ab;b;110;25;second;p;L;F;second@x\u000A`],
},
{
reason: 'further fields are ignored, as in Java; Swift wants exactly nine',
lines: [String.raw`a;b;110;25;u;p;L;F;u@x;extra\u000A`],
},
{
reason: 'an account stays as Java reads it (a port that is no number is the default); Swift checks hosts, ports and the address and has none',
lines: [
String.raw`a;b;110;25;u;p;L;F;\u000A`,
String.raw`a;;110;25;u;p;;;u@x\u000A`,
String.raw` a ; b ;x;y; u ; p ; L ; F ; u@x \u000A`,
String.raw`;;;;;;;;`,
String.raw`;;;;;;;;x`,
],
},
];
describe('the server account file against Swift', () => {
compareWithSwift('server', SERVER_DEPARTURES, serverAccounts);
});
describe('the client account file against Swift', () => {
compareWithSwift('client', CLIENT_DEPARTURES, clientAccount);
});
+4
View File
@@ -38,6 +38,10 @@ Sortierung, Base64 — und die ganzen Mengen `whitespacesAndNewlines` und
unter Linux; ihre Steuerzeichen-Tabelle hat in Ebene 14 einen Fehler, den unter Linux; ihre Steuerzeichen-Tabelle hat in Ebene 14 einen Fehler, den
der Port nicht übernimmt. der Port nicht übernimmt.
`--email-files server|client` liest die Kontodateien
(`packages/fls/test/email-*.cases.txt`) mit Swifts
`TopologyRuntimeEmailStorage.decodeJavaServer`/`decodeJavaClient`.
`--dns-hosts` liest dieselben `/dns/hosts`-Fälle wie das Java-Orakel `--dns-hosts` liest dieselben `/dns/hosts`-Fälle wie das Java-Orakel
(`packages/fls/test/dns-hosts.cases.txt`) mit Swifts (`packages/fls/test/dns-hosts.cases.txt`) mit Swifts
`TopologyDNSHostsFileCodec`; unter `state` stehen die DNS-Einträge jedes `TopologyDNSHostsFileCodec`; unter `state` stehen die DNS-Einträge jedes
+32 -1
View File
@@ -666,7 +666,7 @@ func dnsRecordJSON(_ record: TopologyDNSResourceRecord) -> [Any] {
[record.name.rawValue, record.type.rawValue, Int(record.ttlSeconds), record.target] [record.name.rawValue, record.type.rawValue, Int(record.ttlSeconds), record.target]
} }
/// One case per line: a whole hosts file, ASCII with Java-style unicode /// One case per line: a whole file (hosts, e-mail accounts), ASCII with Java-style unicode
/// escapes (backslash, u, four hex digits) — the file the Java oracle reads. /// escapes (backslash, u, four hex digits) — the file the Java oracle reads.
func readDNSHostsCases(_ url: URL) throws -> [(line: String, text: String)] { func readDNSHostsCases(_ url: URL) throws -> [(line: String, text: String)] {
let lines = try String(contentsOf: url, encoding: .ascii).components(separatedBy: "\n") let lines = try String(contentsOf: url, encoding: .ascii).components(separatedBy: "\n")
@@ -722,6 +722,37 @@ if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--dns-hosts" {
exit(0) exit(0)
} }
if CommandLine.arguments.count == 5, CommandLine.arguments[1] == "--email-files" {
let server = CommandLine.arguments[2] == "server"
let cases = try readDNSHostsCases(URL(fileURLWithPath: CommandLine.arguments[3]))
let output: [[Any]] = cases.map { item in
do {
if server {
let configuration = try TopologyRuntimeEmailStorage.decodeJavaServer(item.text)
return [item.line, configuration.accounts.map { account in
[
"username": account.username, "password": account.password, "name": account.name,
"mailbox": account.mailbox.map(emailMessageJSON),
] as JSON
}]
}
let client = try TopologyRuntimeEmailStorage.decodeJavaClient(item.text)
return [item.line, [
"pop3Host": client.pop3Host, "pop3Port": client.pop3Port,
"smtpHost": client.smtpHost, "smtpPort": client.smtpPort,
"username": client.username, "password": client.password,
"name": client.name, "email": client.email,
] as JSON]
} catch {
return [item.line, "!\(error)"]
}
}
let data = try JSONSerialization.data(withJSONObject: output, options: [.prettyPrinted, .sortedKeys])
try (data + Data("\n".utf8)).write(to: URL(fileURLWithPath: CommandLine.arguments[4]))
print("email \(CommandLine.arguments[2]) files: \(cases.count) cases")
exit(0)
}
if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--corpus" { if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--corpus" {
let summary = try readCorpus(root: URL(fileURLWithPath: CommandLine.arguments[2])) let summary = try readCorpus(root: URL(fileURLWithPath: CommandLine.arguments[2]))
let data = try JSONSerialization.data(withJSONObject: summary, options: [.prettyPrinted, .sortedKeys]) let data = try JSONSerialization.data(withJSONObject: summary, options: [.prettyPrinted, .sortedKeys])
+3 -1
View File
@@ -15,4 +15,6 @@ docker run --rm -v "$root:/work" -w /work/tools/swift-oracle swift:6.1-jammy sh
.build/debug/Oracle --vfs /work/packages/core/test/vfs.cases.json /work/packages/core/test/vfs.swift.json .build/debug/Oracle --vfs /work/packages/core/test/vfs.cases.json /work/packages/core/test/vfs.swift.json
.build/debug/Oracle --strings /work/packages/core/test/strings.cases.json /work/packages/core/test/strings.swift.json .build/debug/Oracle --strings /work/packages/core/test/strings.cases.json /work/packages/core/test/strings.swift.json
.build/debug/Oracle --path-folding /work/packages/fls/test/path-folding.cases.json /work/packages/fls/test/path-folding.swift.json .build/debug/Oracle --path-folding /work/packages/fls/test/path-folding.cases.json /work/packages/fls/test/path-folding.swift.json
.build/debug/Oracle --dns-hosts /work/packages/fls/test/dns-hosts.cases.txt /work/packages/fls/test/dns-hosts.swift.json' .build/debug/Oracle --dns-hosts /work/packages/fls/test/dns-hosts.cases.txt /work/packages/fls/test/dns-hosts.swift.json
.build/debug/Oracle --email-files server /work/packages/fls/test/email-server.cases.txt /work/packages/fls/test/email-server.swift.json
.build/debug/Oracle --email-files client /work/packages/fls/test/email-client.cases.txt /work/packages/fls/test/email-client.swift.json'