Öffnungszeiten: Mails K24–K27 heißen jetzt K27–K30, Feld k24At heißt einschaltMailAt

K24–K26 sind seit dem Webshop-Livegang (main) vergeben; der Mail-Lader sucht die Vorlage über den
Dateianfang, doppelte Nummern hätten die falsche Mail geschickt. Das Feld ist noch nicht in der
Produktion angelegt (zeiten-setup.mjs lief noch nicht), daher jetzt ein sprechender Name.
Spec und Plan behalten die alten Nummern (Hinweis oben im Dokument).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-06 06:00:17 +00:00
1 parent ea4457e6a4
commit c7cd436ec2
34 files changed
+359 -354

No files matched your search

+73 -73
View File
@@ -6,7 +6,7 @@ import { startFakeStripe } from './support/fakeStripe.js'
import { mailSink } from './support/mailSink.js'
import { useTestEnv } from './support/testEnv.js'
// Mails K24–K27 (Öffnungszeiten live: eingeschaltet, Feiertage prüfen, Abo beendet, abgeschaltet), ihre Builder und
// Mails K27–K30 (Öffnungszeiten live: eingeschaltet, Feiertage prüfen, Abo beendet, abgeschaltet), ihre Builder und
// sendeZeitenMail (server/services/zeiten/mails.js, Task 13) sowie die neuen Blöcke behalten/neu in K15 und abo/team in T16.
// Die allgemeinen Vorlagenregeln (Kopf, Platzhalter, Blöcke, Logo, Text ohne Entities, Du-Form) prüft test/mailTemplates.test.js.
const aw = await startAppwriteMock()
@@ -18,7 +18,7 @@ const { loadTemplate, renderMail } = await import('../server/services/mailTempla
const { setMailTransportForTests } = await import('../server/services/mailer.js')
const reg = await import('../server/services/featureRegistry.js')
const sf = await import('../server/services/siteFeatures.js')
const { k24Mail, k25Mail, k26Mail, k27Mail, sendeZeitenMail } = await import('../server/services/zeiten/mails.js')
const { k27Mail, k28Mail, k29Mail, k30Mail, sendeZeitenMail } = await import('../server/services/zeiten/mails.js')
after(() => setMailTransportForTests(null))
const KUNDE = { $id: 'c1', name: 'Silke Müller', email: 'silke@example.com' }
@@ -40,13 +40,13 @@ const an = (nachricht) => Object.entries(nachricht.blocks || {}).filter(([, on])
// Ein Beispiel je Variante: genau diese Nachrichten gehen durch die Prüfung "Werte decken die Platzhalter ab"
const FRIST = new Date('2026-11-04T09:00:00.000Z')
const BEISPIELE = {
'K24 angestossen': () => k24Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', einbau: 'angestossen', wieder: false, ticketNr: 100123 }),
'K24 manuell mit Ticket': () => k24Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', einbau: 'manuell', wieder: false, ticketNr: 100123 }),
'K24 wieder ohne Ticket': () => k24Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', einbau: 'angestossen', wieder: true }),
'K24 wieder ohne Neubau': () => k24Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', einbau: null, wieder: true }),
'K25 ein Tag': () => k25Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', tage: [{ tag: '2026-10-03', name: 'Tag der Deutschen Einheit', art: 'feiertag' }] }),
'K25 mehrere mit prüfen': () =>
k25Mail({
'K27 angestossen': () => k27Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', einbau: 'angestossen', wieder: false, ticketNr: 100123 }),
'K27 manuell mit Ticket': () => k27Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', einbau: 'manuell', wieder: false, ticketNr: 100123 }),
'K27 wieder ohne Ticket': () => k27Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', einbau: 'angestossen', wieder: true }),
'K27 wieder ohne Neubau': () => k27Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', einbau: null, wieder: true }),
'K28 ein Tag': () => k28Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', tage: [{ tag: '2026-10-03', name: 'Tag der Deutschen Einheit', art: 'feiertag' }] }),
'K28 mehrere mit prüfen': () =>
k28Mail({
kunde: KUNDE.name,
projekt: 'Praxis Muster',
tage: [
@@ -54,18 +54,18 @@ const BEISPIELE = {
{ tag: '2026-12-25', name: '1. Weihnachtstag', art: 'feiertag' },
],
}),
'K26 beendet': () => k26Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', art: 'beendet', frist: FRIST, preis: '30 €' }),
'K26 hosting': () => k26Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', art: 'hosting', frist: FRIST, preis: '30 €' }),
'K26 Wechsel widerrufen': () => k26Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', art: 'wechsel_widerrufen', frist: FRIST, preis: '30 €' }),
K27: () => k27Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', loeschdatum: FRIST, preis: '30 €' }),
'K29 beendet': () => k29Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', art: 'beendet', frist: FRIST, preis: '30 €' }),
'K29 hosting': () => k29Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', art: 'hosting', frist: FRIST, preis: '30 €' }),
'K29 Wechsel widerrufen': () => k29Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', art: 'wechsel_widerrufen', frist: FRIST, preis: '30 €' }),
K30: () => k30Mail({ kunde: KUNDE.name, projekt: 'Praxis Muster', loeschdatum: FRIST, preis: '30 €' }),
}
test('Vorlagen K24–K27: Datei, Betreff, Platzhalter und Blöcke stehen fest', () => {
test('Vorlagen K27–K30: Datei, Betreff, Platzhalter und Blöcke stehen fest', () => {
const erwartet = {
K24: { file: 'K24 Zeiten eingeschaltet.html', subject: 'Öffnungszeiten live ist eingeschaltet', placeholders: ['kunde_name', 'projekt', 'ticket', 'portal_url'], blocks: ['angestossen', 'manuell', 'wieder', 'weiter'] },
K25: { file: 'K25 Zeiten Feiertage pruefen.html', subject: 'Feiertage stehen an – stimmen Deine Zeiten?', placeholders: ['kunde_name', 'projekt', 'tage', 'portal_url'], blocks: ['pruefen', 'mehrere'] },
K26: { file: 'K26 Zeiten Abo beendet.html', subject: 'Dein Wartungs-Abo ist beendet – Öffnungszeiten live behalten?', placeholders: ['kunde_name', 'projekt', 'frist', 'preis', 'portal_url'], blocks: ['abo_ende', 'hosting', 'wechsel_widerrufen'] },
K27: { file: 'K27 Zeiten abgeschaltet.html', subject: 'Öffnungszeiten live ist abgeschaltet', placeholders: ['kunde_name', 'projekt', 'loeschdatum', 'preis', 'portal_url'], blocks: [] },
K27: { file: 'K27 Zeiten eingeschaltet.html', subject: 'Öffnungszeiten live ist eingeschaltet', placeholders: ['kunde_name', 'projekt', 'ticket', 'portal_url'], blocks: ['angestossen', 'manuell', 'wieder', 'weiter'] },
K28: { file: 'K28 Zeiten Feiertage pruefen.html', subject: 'Feiertage stehen an – stimmen Deine Zeiten?', placeholders: ['kunde_name', 'projekt', 'tage', 'portal_url'], blocks: ['pruefen', 'mehrere'] },
K29: { file: 'K29 Zeiten Abo beendet.html', subject: 'Dein Wartungs-Abo ist beendet – Öffnungszeiten live behalten?', placeholders: ['kunde_name', 'projekt', 'frist', 'preis', 'portal_url'], blocks: ['abo_ende', 'hosting', 'wechsel_widerrufen'] },
K30: { file: 'K30 Zeiten abgeschaltet.html', subject: 'Öffnungszeiten live ist abgeschaltet', placeholders: ['kunde_name', 'projekt', 'loeschdatum', 'preis', 'portal_url'], blocks: [] },
}
for (const [id, soll] of Object.entries(erwartet)) {
const tpl = loadTemplate(id)
@@ -92,22 +92,22 @@ test('Builder decken genau die Platzhalter und Blöcke der Vorlage ab', () => {
})
test('Anrede: Kunde als Name oder als Kundendokument, ohne Namen bleibt der Wert leer (wie K22/K23)', () => {
assert.equal(k27Mail({ kunde: KUNDE, projekt: 'P', loeschdatum: FRIST, preis: '30 €' }).values.kunde_name, 'Silke Müller')
assert.equal(k30Mail({ kunde: KUNDE, projekt: 'P', loeschdatum: FRIST, preis: '30 €' }).values.kunde_name, 'Silke Müller')
for (const kunde of [undefined, null, '', ' ', {}, { name: null }]) {
assert.equal(k27Mail({ kunde, projekt: 'P', loeschdatum: FRIST, preis: '30 €' }).values.kunde_name, '', JSON.stringify(kunde))
assert.equal(k30Mail({ kunde, projekt: 'P', loeschdatum: FRIST, preis: '30 €' }).values.kunde_name, '', JSON.stringify(kunde))
}
})
test('K24: Text je nach einbau (angestossen: nach dem nächsten Veröffentlichen, manuell: das Team baut ein)', () => {
const auto = BEISPIELE['K24 angestossen']()
assert.equal(auto.template, 'K24')
test('K27: Text je nach einbau (angestossen: nach dem nächsten Veröffentlichen, manuell: das Team baut ein)', () => {
const auto = BEISPIELE['K27 angestossen']()
assert.equal(auto.template, 'K27')
assert.deepEqual(auto.blocks, { angestossen: true, manuell: false, wieder: false, weiter: true })
const textAuto = render(auto).text
assert.match(textAuto, /nach dem nächsten Veröffentlichen/)
assert.doesNotMatch(textAuto, /baut unser Team/)
assert.match(textAuto, /Ticket #100123/, 'Ticketnummer steht in beiden Fällen in der Mail')
const hand = BEISPIELE['K24 manuell mit Ticket']()
const hand = BEISPIELE['K27 manuell mit Ticket']()
assert.deepEqual(hand.blocks, { angestossen: false, manuell: true, wieder: false, weiter: true })
const textHand = render(hand).text
assert.match(textHand, /baut unser Team/)
@@ -116,13 +116,13 @@ test('K24: Text je nach einbau (angestossen: nach dem nächsten Veröffentlichen
// ohne bekannten Einbau sagt die Mail das Vorsichtige: das Team baut ein (null gilt nur mit wieder als "kein Einbau nötig", siehe unten)
for (const einbau of [undefined, null, '', 'unbekannt']) {
const unklar = k24Mail({ kunde: 'K', projekt: 'P', einbau })
const unklar = k27Mail({ kunde: 'K', projekt: 'P', einbau })
assert.deepEqual(unklar.blocks, { angestossen: false, manuell: true, wieder: false, weiter: true }, String(einbau))
}
})
test('K24 wieder ohne Neubau (einbau null, Abo kehrt vor dem Abschalten zurück): beide Einbau-Blöcke und der Kasten "Wie geht es weiter" entfallen', () => {
const ohne = BEISPIELE['K24 wieder ohne Neubau']()
test('K27 wieder ohne Neubau (einbau null, Abo kehrt vor dem Abschalten zurück): beide Einbau-Blöcke und der Kasten "Wie geht es weiter" entfallen', () => {
const ohne = BEISPIELE['K27 wieder ohne Neubau']()
assert.deepEqual(ohne.blocks, { angestossen: false, manuell: false, wieder: true, weiter: false })
const mail = render(ohne)
assert.deepEqual(mail.problems, [])
@@ -136,40 +136,40 @@ test('K24 wieder ohne Neubau (einbau null, Abo kehrt vor dem Abschalten zurück)
// mit Neubau (angestossen oder manuell) bleibt der Kasten, auch beim Wiedereinschalten
for (const einbau of ['angestossen', 'manuell']) {
const text = render(k24Mail({ kunde: 'K', projekt: 'P', einbau, wieder: true })).text
const text = render(k27Mail({ kunde: 'K', projekt: 'P', einbau, wieder: true })).text
assert.match(text, /Wie geht es weiter/, einbau)
}
// einbau null ohne wieder ist kein Fall der Mail: es bleibt beim Vorsichtigen
assert.deepEqual(k24Mail({ kunde: 'K', projekt: 'P', einbau: null }).blocks, { angestossen: false, manuell: true, wieder: false, weiter: true })
assert.deepEqual(k27Mail({ kunde: 'K', projekt: 'P', einbau: null }).blocks, { angestossen: false, manuell: true, wieder: false, weiter: true })
})
test('K24: ohne Ticketnummer steht kein leeres Ticket in der Mail; wieder nennt das Abo', () => {
const ohne = k24Mail({ kunde: 'K', projekt: 'P', einbau: 'manuell' })
test('K27: ohne Ticketnummer steht kein leeres Ticket in der Mail; wieder nennt das Abo', () => {
const ohne = k27Mail({ kunde: 'K', projekt: 'P', einbau: 'manuell' })
assert.equal(ohne.values.ticket, '')
assert.doesNotMatch(render(ohne).text, /Ticket|\(\)|\s\./)
const leer = k24Mail({ kunde: 'K', projekt: 'P', einbau: 'manuell', ticketNr: ' ' })
const leer = k27Mail({ kunde: 'K', projekt: 'P', einbau: 'manuell', ticketNr: ' ' })
assert.equal(leer.values.ticket, '')
assert.equal(k24Mail({ kunde: 'K', projekt: 'P', einbau: 'manuell', ticketNr: '#100124' }).values.ticket, ' (Ticket #100124)')
assert.equal(k27Mail({ kunde: 'K', projekt: 'P', einbau: 'manuell', ticketNr: '#100124' }).values.ticket, ' (Ticket #100124)')
const wieder = BEISPIELE['K24 wieder ohne Ticket']()
const wieder = BEISPIELE['K27 wieder ohne Ticket']()
assert.deepEqual(wieder.blocks, { angestossen: true, manuell: false, wieder: true, weiter: true })
const mitWieder = render(wieder).text
assert.match(mitWieder, /Dein Wartungs-Abo läuft wieder/)
assert.match(mitWieder, /wieder eingeschaltet/)
assert.match(mitWieder, /Zeiten und Einstellungen sind unverändert/)
assert.doesNotMatch(render(BEISPIELE['K24 angestossen']()).text, /Wartungs-Abo läuft wieder/)
assert.doesNotMatch(render(BEISPIELE['K27 angestossen']()).text, /Wartungs-Abo läuft wieder/)
})
test('K24: Betreff, Projekt und Knopf', () => {
const mail = render(BEISPIELE['K24 angestossen']())
test('K27: Betreff, Projekt und Knopf', () => {
const mail = render(BEISPIELE['K27 angestossen']())
assert.equal(mail.subject, 'Öffnungszeiten live ist eingeschaltet')
assert.match(mail.text, /Projekt: Praxis Muster/)
assert.match(mail.html, new RegExp(`<a href="${PORTAL.replaceAll('.', '\\.')}"`))
})
test('K25: eine Zeile je Tag mit Wochentag, Datum und Namen; „bitte prüfen“ und Mehrzahl nur bei Bedarf', () => {
const ein = BEISPIELE['K25 ein Tag']()
assert.equal(ein.template, 'K25')
test('K28: eine Zeile je Tag mit Wochentag, Datum und Namen; „bitte prüfen“ und Mehrzahl nur bei Bedarf', () => {
const ein = BEISPIELE['K28 ein Tag']()
assert.equal(ein.template, 'K28')
assert.equal(ein.values.tage, 'Sa 03.10. – Tag der Deutschen Einheit')
assert.deepEqual(an(ein), [])
assert.deepEqual(ein.blocks, { pruefen: false, mehrere: false })
@@ -179,7 +179,7 @@ test('K25: eine Zeile je Tag mit Wochentag, Datum und Namen; „bitte prüfen“
assert.match(textEin, /Stimmen Deine Zeiten/)
assert.match(textEin, /Google/, 'Hinweis: bei Google auch eintragen')
const viele = BEISPIELE['K25 mehrere mit prüfen']()
const viele = BEISPIELE['K28 mehrere mit prüfen']()
assert.equal(viele.values.tage, 'Do 24.12. – Heiligabend (bitte prüfen)\nFr 25.12. – 1. Weihnachtstag')
assert.deepEqual(viele.blocks, { pruefen: true, mehrere: true })
const mail = render(viele)
@@ -189,8 +189,8 @@ test('K25: eine Zeile je Tag mit Wochentag, Datum und Namen; „bitte prüfen“
assert.match(mail.html, /Heiligabend \(bitte prüfen\)<br>Fr 25\.12\./, 'Zeilenumbrüche werden zu <br>')
})
test('K25: Namen werden HTML-sicher eingesetzt, Wochentage stimmen über die Woche', () => {
const nachricht = k25Mail({
test('K28: Namen werden HTML-sicher eingesetzt, Wochentage stimmen über die Woche', () => {
const nachricht = k28Mail({
kunde: 'K',
projekt: 'P',
tage: [
@@ -209,15 +209,15 @@ test('K25: Namen werden HTML-sicher eingesetzt, Wochentage stimmen über die Woc
assert.doesNotMatch(mail.html, /<b>fett/)
})
test('K25 geht ohne BCC ans Team, K24 mit', async () => {
const k25 = await sendeZeitenMail(KUNDE, BEISPIELE['K25 ein Tag'](), { bccTeam: false })
assert.deepEqual(k25, { sent: true })
test('K28 geht ohne BCC ans Team, K27 mit', async () => {
const k28 = await sendeZeitenMail(KUNDE, BEISPIELE['K28 ein Tag'](), { bccTeam: false })
assert.deepEqual(k28, { sent: true })
assert.equal(sink.sent.length, 1)
assert.deepEqual(sink.sent[0].to.map((t) => t.address), ['silke@example.com'])
assert.ok(!sink.sent[0].bcc, 'keine Kopie ans Team')
assert.equal(sink.sent[0].subject, 'Feiertage stehen an – stimmen Deine Zeiten?')
assert.deepEqual(await sendeZeitenMail(KUNDE, BEISPIELE['K24 angestossen']()), { sent: true })
assert.deepEqual(await sendeZeitenMail(KUNDE, BEISPIELE['K27 angestossen']()), { sent: true })
assert.equal(sink.sent.length, 2)
assert.deepEqual(sink.sent[1].bcc.map((t) => t.address), config.chatNotify.emails, 'Standard: Kopie ans Team')
assert.ok(sink.sent[1].bcc.length > 0)
@@ -225,15 +225,15 @@ test('K25 geht ohne BCC ans Team, K24 mit', async () => {
assert.match(sink.sent[1].html, /Hallo Silke Müller,/)
})
test('K26: je Variante nur der passende Grund, Frist, Preis und Behalten-Knopf', () => {
test('K29: je Variante nur der passende Grund, Frist, Preis und Behalten-Knopf', () => {
const faelle = [
['K26 beendet', { abo_ende: true, hosting: false, wechsel_widerrufen: false }, /Dein Wartungs-Abo ist beendet/, /Hosting-Abo|Wechsel/],
['K26 hosting', { abo_ende: false, hosting: true, wechsel_widerrufen: false }, /Hosting-Abo/, /Dein Wartungs-Abo ist beendet|widerrufen/],
['K26 Wechsel widerrufen', { abo_ende: false, hosting: false, wechsel_widerrufen: true }, /Wechsel.*widerrufen/, /Dein Wartungs-Abo ist beendet|Dein Abo ist ein Hosting-Abo/],
['K29 beendet', { abo_ende: true, hosting: false, wechsel_widerrufen: false }, /Dein Wartungs-Abo ist beendet/, /Hosting-Abo|Wechsel/],
['K29 hosting', { abo_ende: false, hosting: true, wechsel_widerrufen: false }, /Hosting-Abo/, /Dein Wartungs-Abo ist beendet|widerrufen/],
['K29 Wechsel widerrufen', { abo_ende: false, hosting: false, wechsel_widerrufen: true }, /Wechsel.*widerrufen/, /Dein Wartungs-Abo ist beendet|Dein Abo ist ein Hosting-Abo/],
]
for (const [name, blocks, passt, fremd] of faelle) {
const nachricht = BEISPIELE[name]()
assert.equal(nachricht.template, 'K26')
assert.equal(nachricht.template, 'K29')
assert.deepEqual(nachricht.blocks, blocks, name)
assert.equal(nachricht.values.frist, '04.11.2026', `${name}: Datum TT.MM.JJJJ`)
assert.equal(nachricht.values.preis, '30 €')
@@ -245,22 +245,22 @@ test('K26: je Variante nur der passende Grund, Frist, Preis und Behalten-Knopf',
assert.match(text, /Für 30 € behalten: .*#zeiten/, `${name}: Knopf zum Behalten`)
}
// unbekannte Lage: lieber die Standard-Fassung (Abo beendet) als gar kein Grund
assert.deepEqual(k26Mail({ kunde: 'K', projekt: 'P', art: 'was-neues', frist: FRIST, preis: '30 €' }).blocks, { abo_ende: true, hosting: false, wechsel_widerrufen: false })
assert.equal(render(BEISPIELE['K26 beendet']()).subject, 'Dein Wartungs-Abo ist beendet – Öffnungszeiten live behalten?')
assert.deepEqual(k29Mail({ kunde: 'K', projekt: 'P', art: 'was-neues', frist: FRIST, preis: '30 €' }).blocks, { abo_ende: true, hosting: false, wechsel_widerrufen: false })
assert.equal(render(BEISPIELE['K29 beendet']()).subject, 'Dein Wartungs-Abo ist beendet – Öffnungszeiten live behalten?')
})
test('K26/K27: Datum aus Date (Berliner Tag), ungültiges Datum wirft früh', () => {
test('K29/K30: Datum aus Date (Berliner Tag), ungültiges Datum wirft früh', () => {
// Berliner Tag, nicht UTC: 22:30 UTC am 30.10. ist 23:30 MEZ (noch der 30.), 23:30 UTC am 31.10. ist 00:30 MEZ (schon der 1.11.)
assert.equal(k26Mail({ kunde: 'K', projekt: 'P', art: 'beendet', frist: new Date('2026-10-30T22:30:00.000Z'), preis: '30 €' }).values.frist, '30.10.2026')
assert.equal(k26Mail({ kunde: 'K', projekt: 'P', art: 'beendet', frist: new Date('2026-10-31T23:30:00.000Z'), preis: '30 €' }).values.frist, '01.11.2026')
assert.throws(() => k26Mail({ kunde: 'K', projekt: 'P', art: 'beendet', frist: new Date('kaputt'), preis: '30 €' }), TypeError)
assert.throws(() => k26Mail({ kunde: 'K', projekt: 'P', art: 'beendet', preis: '30 €' }), TypeError)
assert.throws(() => k27Mail({ kunde: 'K', projekt: 'P', loeschdatum: 'morgen', preis: '30 €' }), TypeError)
assert.equal(k29Mail({ kunde: 'K', projekt: 'P', art: 'beendet', frist: new Date('2026-10-30T22:30:00.000Z'), preis: '30 €' }).values.frist, '30.10.2026')
assert.equal(k29Mail({ kunde: 'K', projekt: 'P', art: 'beendet', frist: new Date('2026-10-31T23:30:00.000Z'), preis: '30 €' }).values.frist, '01.11.2026')
assert.throws(() => k29Mail({ kunde: 'K', projekt: 'P', art: 'beendet', frist: new Date('kaputt'), preis: '30 €' }), TypeError)
assert.throws(() => k29Mail({ kunde: 'K', projekt: 'P', art: 'beendet', preis: '30 €' }), TypeError)
assert.throws(() => k30Mail({ kunde: 'K', projekt: 'P', loeschdatum: 'morgen', preis: '30 €' }), TypeError)
})
test('K27: Leisten weg, feste Zeiten, Daten bis zum Löschdatum, Wiedereinschalten für den Preis', () => {
const nachricht = BEISPIELE.K27()
assert.equal(nachricht.template, 'K27')
test('K30: Leisten weg, feste Zeiten, Daten bis zum Löschdatum, Wiedereinschalten für den Preis', () => {
const nachricht = BEISPIELE.K30()
assert.equal(nachricht.template, 'K30')
assert.equal(nachricht.values.loeschdatum, '04.11.2026')
assert.equal(nachricht.values.preis, '30 €')
const mail = render(nachricht)
@@ -271,15 +271,15 @@ test('K27: Leisten weg, feste Zeiten, Daten bis zum Löschdatum, Wiedereinschalt
assert.match(mail.text, /Projekt: Praxis Muster/)
})
test('K26 und K27 gehen mit Kopie ans Team', async () => {
assert.equal((await sendeZeitenMail(KUNDE, BEISPIELE['K26 beendet']())).sent, true)
assert.equal((await sendeZeitenMail(KUNDE, BEISPIELE.K27())).sent, true)
test('K29 und K30 gehen mit Kopie ans Team', async () => {
assert.equal((await sendeZeitenMail(KUNDE, BEISPIELE['K29 beendet']())).sent, true)
assert.equal((await sendeZeitenMail(KUNDE, BEISPIELE.K30())).sent, true)
for (const mail of sink.sent) assert.ok(mail.bcc?.length > 0)
})
test('ohne E-Mail-Adresse: nichts senden, nichts werfen', async () => {
for (const kunde of [{ name: 'Silke' }, { name: 'Silke', email: '' }, { email: ' ' }, { email: null }, null, undefined]) {
assert.deepEqual(await sendeZeitenMail(kunde, BEISPIELE.K27()), { sent: false, reason: 'keine_adresse' }, JSON.stringify(kunde))
assert.deepEqual(await sendeZeitenMail(kunde, BEISPIELE.K30()), { sent: false, reason: 'keine_adresse' }, JSON.stringify(kunde))
}
assert.equal(sink.sent.length, 0)
})
@@ -290,13 +290,13 @@ test('Versandfehler werden gemeldet, nie geworfen; der Grund nennt keine Adresse
throw new Error('550 Recipient silke@example.com rejected for Silke Müller')
},
})
const ergebnis = await sendeZeitenMail(KUNDE, BEISPIELE.K27())
const ergebnis = await sendeZeitenMail(KUNDE, BEISPIELE.K30())
assert.equal(ergebnis.sent, false)
assert.equal(ergebnis.reason, 'fehler')
assert.doesNotMatch(JSON.stringify(ergebnis), /silke|Müller/i)
// auch ohne SMTP-Konfiguration (sendCustomerMail wirft) und mit unbrauchbarer Nachricht
setMailTransportForTests(null)
assert.equal((await sendeZeitenMail(KUNDE, BEISPIELE.K27())).sent, false)
assert.equal((await sendeZeitenMail(KUNDE, BEISPIELE.K30())).sent, false)
setMailTransportForTests(sink)
assert.equal((await sendeZeitenMail(KUNDE, undefined)).sent, false)
assert.equal((await sendeZeitenMail(KUNDE, { template: 'K99' })).sent, false)
@@ -309,19 +309,19 @@ test('Log enthält weder Adresse noch Namen', async () => {
throw new Error('550 Recipient silke@example.com rejected for Silke Müller (Praxis Muster)')
},
})
const ergebnis = await sendeZeitenMail(KUNDE, BEISPIELE['K26 beendet']())
const ergebnis = await sendeZeitenMail(KUNDE, BEISPIELE['K29 beendet']())
assert.equal(ergebnis.sent, false)
const text = logText()
assert.match(text, /K26/, 'das Log nennt die Vorlage')
assert.match(text, /K29/, 'das Log nennt die Vorlage')
assert.ok(text.length > 0)
assert.doesNotMatch(text, /silke|example\.com|Müller/i, text)
// auch der Fall ohne Adresse loggt höchstens ohne Namen
await sendeZeitenMail({ name: 'Silke Müller' }, BEISPIELE.K27())
await sendeZeitenMail({ name: 'Silke Müller' }, BEISPIELE.K30())
assert.doesNotMatch(logText(), /silke|Müller/i)
})
test('Erfolg schreibt nichts ins Log', async () => {
await sendeZeitenMail(KUNDE, BEISPIELE['K24 angestossen']())
await sendeZeitenMail(KUNDE, BEISPIELE['K27 angestossen']())
assert.equal(logText(), '')
})