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:
@@ -12,8 +12,9 @@
|
||||
// Back the other way: what the simulation changes that Java keeps in the
|
||||
// project (its objects are the project's), so a project saved after it holds
|
||||
// it. That is a router's or home router's firewall, set on its page konfig,
|
||||
// and an e-mail server's mailboxes (Java writes them into
|
||||
// /mailserver/konten.txt at once).
|
||||
// an e-mail server's mailboxes (Java writes them into
|
||||
// /mailserver/konten.txt at once), and an e-mail program's inbox and sent
|
||||
// mails.
|
||||
|
||||
import { portSnapshot, topologySnapshot, type DnsServerConfiguration, type EchoServerSnapshot, type EmailServerSnapshot, type Id, type WebServerSnapshot, type TopologySnapshot } from '@mdg-filius/core';
|
||||
import type { ImportedProject } from './importProject.js';
|
||||
@@ -36,6 +37,7 @@ export function projectSnapshot(project: ImportedProject): TopologySnapshot {
|
||||
if (project.installedPrograms.get(nodeId)?.has('emailServer') !== true) continue;
|
||||
emailServers.set(nodeId, { domain: configuration.domain, accounts: configuration.accounts, isActive: project.activePrograms.get(nodeId)?.has('emailServer') === true });
|
||||
}
|
||||
const emailClients = new Map([...project.emailClientConfigurations].filter(([nodeId]) => project.installedPrograms.get(nodeId)?.has('emailClient') === true));
|
||||
const webBrowsers = new Set([...project.installedPrograms].filter(([, programs]) => programs.has('webBrowser')).map(([nodeId]) => nodeId));
|
||||
return topologySnapshot({
|
||||
nodes: project.graph.nodes.map((node) => ({
|
||||
@@ -60,13 +62,15 @@ export function projectSnapshot(project: ImportedProject): TopologySnapshot {
|
||||
webServersByNodeId: webServers,
|
||||
webBrowserNodeIds: webBrowsers,
|
||||
emailServersByNodeId: emailServers,
|
||||
emailClientsByNodeId: emailClients,
|
||||
fileSystemsByNodeId: project.virtualFileSystems,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The project with what the simulation changed that Java keeps, as the run's
|
||||
* snapshot holds it: the firewalls, and the e-mail servers' mailboxes.
|
||||
* snapshot holds it: the firewalls, the e-mail servers' mailboxes, and the
|
||||
* e-mail programs' inboxes and sent mails.
|
||||
*/
|
||||
export function projectWithSimulationChanges(project: ImportedProject, snapshot: TopologySnapshot): ImportedProject {
|
||||
const emailServers = new Map(project.emailServerConfigurations);
|
||||
@@ -76,5 +80,14 @@ export function projectWithSimulationChanges(project: ImportedProject, snapshot:
|
||||
const messages = server.accounts.reduce((count, account) => count + account.mailbox.length, 0);
|
||||
emailServers.set(nodeId, { ...configuration, accounts: server.accounts, nextMessageID: messages + 1 });
|
||||
}
|
||||
return { ...project, firewallConfigurationsByNodeId: new Map(snapshot.firewallConfigurationsByNodeId), emailServerConfigurations: emailServers };
|
||||
const emailClients = new Map(project.emailClientConfigurations);
|
||||
for (const [nodeId, client] of snapshot.emailClientsByNodeId) {
|
||||
if (emailClients.has(nodeId)) emailClients.set(nodeId, client);
|
||||
}
|
||||
return {
|
||||
...project,
|
||||
firewallConfigurationsByNodeId: new Map(snapshot.firewallConfigurationsByNodeId),
|
||||
emailServerConfigurations: emailServers,
|
||||
emailClientConfigurations: emailClients,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user