feat(core): the e-mail program after Java, measured against Java

The program sends to its account's SMTP server, one connection for each
recipient, and fetches its mails by POP3; the server now sends mails on
to other domains by MX and returns notices to their senders. The
program's folders are in the snapshot and go back into the project.
EmailClientRun plays six stories with two servers; the mail oracles run
in the language's locale, so Java's notice dates match the core's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 19:38:44 +00:00
co-authored by Claude Opus 5.5
parent 015635926f
commit 058675a148
30 changed files with 58319 additions and 1724 deletions
+21
View File
@@ -836,6 +836,27 @@ describe('importProject rules', () => {
expect(second!.mailbox).toMatchObject([{ from: { name: null, mailAddress: 'rechner1@filius.de' }, to: [{ name: null, mailAddress: 'rechner2@filius.de' }], subject: 'Hallo', body: 'Gespeichert?\n' }]);
});
it('keeps what the e-mail programs sent and fetched in the project saved after the run (projectWithSimulationChanges)', () => {
const project = openProjectArchive(new Uint8Array(readFileSync(join(corpusRoot, 'java-examples/email_komplett.fls'))), counter());
const node = (name: string) => project.graph.nodes.find((each) => each.displayName === name)!.id;
const [sender, receiver] = [node('Rechner 1'), node('Rechner 3')];
const withoutProgram = { ...project, installedPrograms: new Map([...project.installedPrograms].map(([id, programs]) => [id, id === sender ? new Set<never>() : programs])) };
expect(projectSnapshot(withoutProgram).emailClientsByNodeId.has(sender)).toBe(false);
const engine = new NetworkRuntimeEngine(14);
engine.handle({ type: 'start', snapshot: projectSnapshot(project), seed: 14, initialTimeMilliseconds: 0 });
expect(engine.emailClient(sender)!.send({ to: 'rechner3@senior.de', cc: '', bcc: '', subject: 'Gruß', text: 'Über den MX' })).toBeUndefined();
engine.handle({ type: 'advance', toMilliseconds: 20_000 });
expect(engine.emailClient(receiver)!.fetch()).toBe(true);
engine.handle({ type: 'advance', toMilliseconds: 30_000 });
const reopened = openProjectArchive(saveProjectArchive(projectWithSimulationChanges(project, engine.state.topologySnapshot)).archive, counter());
const client = (name: string) => reopened.emailClientConfigurations.get(reopened.graph.nodes.find((each) => each.displayName === name)!.id)!;
const mail = { from: { name: 'Rechner 1', mailAddress: 'rechner1@filius.de' }, to: [{ name: null, mailAddress: 'rechner3@senior.de' }], subject: 'Gruß' };
expect(client('Rechner 1').sent).toMatchObject([{ ...mail, body: 'Über den MX', isSent: true }]);
expect(client('Rechner 3').inbox).toMatchObject([{ ...mail, body: 'Über den MX\n', isSent: false }]);
expect(client('Rechner 3').account).toEqual(project.emailClientConfigurations.get(receiver)!.account);
});
it("keeps a node's IP forwarding where it differs from its kind's: a host that forwards, a router that does not", () => {
const system = (ipForwardingEnabled: boolean) => ({ manualRoutes: [], ipForwardingEnabled });
const project = importProject(