trihub-ernaehrung-backseatDevs/src/tests/contact.test.js

225 lines
7.7 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createApp } from "../server/index.js";
import { createContactFormStorage } from "../server/services/contact-form-storage.js";
import { createCsvStorage } from "../server/services/csv-storage.js";
import { createContactEmailService } from "../server/services/contact-email.js";
import { contactBookingUrl } from "../shared/contact.js";
async function fixture(t, options = {}) {
const dir = await mkdtemp(join(tmpdir(), "trihub-contact-"));
const storage = createCsvStorage(join(dir, "bookings.csv"));
await storage.writeAll([
{
bookingId: "TH-000001",
email: "kunde@example.test",
name: "Testkunde",
},
]);
const requests = createContactFormStorage(
join(dir, "advisor-contacts.csv"),
[
"createdAt",
"email",
"advisorId",
"reason",
"simulationAccepted",
"consent",
],
);
const sent = [];
const app = createApp({
storage,
advisorContactStorage: requests,
sendConfirmation: async () => {},
sendContact: async (data) => sent.push(data),
...options,
});
await new Promise((resolve) => app.listen(0, "127.0.0.1", resolve));
t.after(async () => {
app.closeAllConnections();
await new Promise((resolve) => app.close(resolve));
await rm(dir, { recursive: true, force: true });
});
const base = `http://127.0.0.1:${app.address().port}`;
const post = async (path, input) => {
const response = await fetch(base + path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
});
return { status: response.status, body: await response.json() };
};
return { post, sent, storage, requests, base };
}
const input = {
consent: true,
email: "kunde@example.test",
advisorId: "relindis-agethen",
reason: "coaching",
simulationAccepted: true,
};
test("CSV-Abgleich normalisiert E-Mail; Versand erhält Berater und festen Redirect", async (t) => {
const f = await fixture(t);
const before = await f.storage.readAll();
const validation = await f.post("/api/contact/validate", {
email: " KUNDE@example.test ",
});
assert.equal(validation.status, 200);
assert.equal(validation.body.email, input.email);
const result = await f.post("/api/contact", {
...input,
advisor: "Manipuliert",
advisorEmail: "falsch@example.test",
});
assert.equal(result.status, 201);
assert.equal(result.body.redirectUrl, contactBookingUrl);
assert.equal(result.body.simulated, true);
assert.equal(f.sent[0].advisor.email, "relindis.agethen@tri-hub.de");
assert.equal(f.sent[0].advisor.vorname, "Relindis");
const second = await f.post("/api/contact", {
...input,
advisorId: "maren-hoffmann",
});
assert.equal(second.status, 201);
assert.equal(f.sent[1].advisor.email, "maren.hoffmann@tri-hub.de");
assert.deepEqual(await f.storage.readAll(), before);
const records = await f.requests.readAll();
assert.equal(records.length, 2);
assert.equal(records[0].consent, true);
assert.equal(records[0].advisorId, input.advisorId);
});
test("Ungültige und unbekannte Kunden, manipulierte Berater, Gründe und fehlende Zustimmung senden nichts", async (t) => {
const f = await fixture(t);
for (const [data, status] of [
...[false, "true", undefined].map((consent) => [
{ ...input, consent },
400,
]),
[null, 400],
[{ ...input, email: "bad" }, 400],
[{ ...input, email: "unknown@example.test" }, 403],
[{ ...input, advisorId: "unbekannt" }, 400],
[{ ...input, advisorId: null }, 400],
[{ ...input, reason: "__proto__" }, 400],
[{ ...input, simulationAccepted: false }, 400],
])
assert.equal((await f.post("/api/contact", data)).status, status);
assert.equal(f.sent.length, 0);
assert.deepEqual(await f.requests.readAll(), []);
});
test("Absenden prüft CSV erneut; Speicherfehler bleiben geschlossen", async (t) => {
const f = await fixture(t);
assert.equal((await f.post("/api/contact/validate", input)).status, 200);
await f.storage.writeAll([]);
assert.equal((await f.post("/api/contact", input)).status, 403);
const broken = await fixture(t, {
storage: {
readAll() {
throw new Error("unavailable");
},
},
});
assert.equal((await broken.post("/api/contact", input)).status, 503);
assert.equal(broken.sent.length, 0);
});
test("HTTP-Vertrag weist falsche Methode, ungültiges JSON und große Requests zurück", async (t) => {
const f = await fixture(t);
assert.equal((await fetch(f.base + "/api/contact")).status, 405);
assert.equal(
(await fetch(f.base + "/api/contact", { method: "POST", body: "x" }))
.status,
415,
);
assert.equal(
(
await fetch(f.base + "/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: "{",
})
).status,
400,
);
assert.equal(
(await f.post("/api/contact", { email: "x".repeat(9000) })).status,
413,
);
});
test("Zwei getrennte simulierte Bestätigungen; Teilfehler verhindern Erfolg", async (t) => {
const messages = [];
const sender = createContactEmailService((config) => {
assert.equal(config.host, "127.0.0.1");
assert.equal(config.port, 1025);
return {
async sendMail(message) {
messages.push(message);
return { accepted: [message.to] };
},
};
});
const f = await fixture(t, { sendContact: sender });
assert.equal((await f.post("/api/contact", input)).status, 201);
assert.deepEqual(
messages.map((m) => m.to),
[input.email, "relindis.agethen@tri-hub.de"],
);
for (const message of messages) {
assert.match(message.text, /Simulation/);
assert.match(message.text, /Persönliches Coaching/);
}
const failing = createContactEmailService(() => ({
async sendMail(message) {
return { accepted: message.to === input.email ? [message.to] : [] };
},
}));
const failure = await fixture(t, { sendContact: failing });
const result = await failure.post("/api/contact", input);
assert.equal(result.status, 502);
assert.equal(result.body.redirectUrl, undefined);
});
test("Anzeigename bevorzugt Spitzname, sonst Vorname, ohne Nachnamen", async () => {
const { advisorDisplayName } = await import("../shared/berater.js");
assert.equal(
advisorDisplayName({
spitzname: " Lilli ",
vorname: "Relindis",
nachname: "Agethen",
}),
"Lilli",
);
assert.equal(
advisorDisplayName({
spitzname: " ",
vorname: "Maren",
nachname: "Hoffmann",
}),
"Maren",
);
assert.equal(
advisorDisplayName({ vorname: "Maren", nachname: "Hoffmann" }),
"Maren",
);
});
test("Berateranfrage wird bei CSV-Schreibfehler nicht versendet", async (t) => {
const f = await fixture(t, {
advisorContactStorage: {
append: async () => {
throw new Error("Disk full");
},
},
});
assert.equal((await f.post("/api/contact", input)).status, 503);
assert.equal(f.sent.length, 0);
});