372 lines
20 KiB
JavaScript
372 lines
20 KiB
JavaScript
import { test, after, beforeEach, afterEach, mock } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||
import { mailSink } from './support/mailSink.js'
|
||
import { schemaKeys } from './support/schema.js'
|
||
import { useTestEnv } from './support/testEnv.js'
|
||
import { TERMINE_DATETIMES } from '../server/services/termine/schema.js'
|
||
|
||
// Webshop E4: Mails des Shops (services/shop/mails.js). Käufermails laufen durch die Warteschlange des Terminplaners
|
||
// (termineOutbox), Händlermails gehen direkt an das Kundenkonto. Mails landen in der Senke, nichts verlässt den Prozess.
|
||
const aw = await startAppwriteMock({
|
||
schema: { termineOutbox: schemaKeys('termineOutbox') },
|
||
datetimes: TERMINE_DATETIMES,
|
||
defaultLimit: 25,
|
||
})
|
||
after(() => aw.close())
|
||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
|
||
|
||
const { config } = await import('../server/config.js')
|
||
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
|
||
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
||
const { resetOutboxForTests, warteAufVersand } = await import('../server/services/termine/outbox.js')
|
||
const { STANDARD_EINSTELLUNGEN } = await import('../server/services/shop/einstellungen.js')
|
||
const { gewaehrleistungsHinweis } = await import('../server/services/shop/rechtstexte.js')
|
||
const M = await import('../server/services/shop/mails.js')
|
||
const { anKaeufer, anHaendler, kaeuferWerte, haendlerK25, haendlerK26 } = M
|
||
after(() => setMailTransportForTests(null))
|
||
|
||
const JETZT = new Date('2026-10-05T12:03:00.000Z') // 14:03 Berlin
|
||
const SITE_KEY = 'abcdefghijklmnop'
|
||
const ABSENDER_VORHER = config.smtp.from
|
||
const BASIS_VORHER = config.publicBaseUrl
|
||
|
||
const einstellungen = (over = {}) => ({
|
||
...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)),
|
||
shopname: 'Töpferei Muster',
|
||
anbieter: { name: 'Erika Muster', strasse: 'Hauptstr. 1', plz: '67722', ort: 'Winnweiler', land: 'DE', email: 'info@toepferei.example', telefon: '', ustId: '' },
|
||
lieferzeit: '2–4 Werktage',
|
||
akzent: '#aa3300',
|
||
abholung: { aktiv: true, adresse: 'Werkstatt, Hauptstr. 1, 67722 Winnweiler', hinweis: 'Di–Fr 10–16 Uhr' },
|
||
...over,
|
||
})
|
||
const texte = (over = {}) => ({
|
||
agb: 'AGB Zeile 1\nAGB Zeile 2',
|
||
widerrufsbelehrung: 'Belehrung Zeile 1\nZeile 2',
|
||
widerrufsformular: 'An Töpferei Muster\nHiermit widerrufe ich',
|
||
impressumUrl: 'https://toepferei.example/impressum',
|
||
datenschutzUrl: 'https://toepferei.example/datenschutz',
|
||
...over,
|
||
})
|
||
const ctxMit = (e = einstellungen(), t = texte()) => ({
|
||
doc: { $id: 'f_shop01', customerId: 'c1', projectId: 'p1', featureKey: 'shop', siteKey: SITE_KEY },
|
||
shop: {
|
||
id: 'f_shop01',
|
||
customerId: 'c1',
|
||
projectId: 'p1',
|
||
siteKey: SITE_KEY,
|
||
einstellungen: e,
|
||
rechtstexte: { entwurf: texte(), bestaetigt: { texte: t, hash: 'h1', at: JETZT.toISOString(), von: 'c1' } },
|
||
},
|
||
})
|
||
|
||
const bestellung = (over = {}, snap = {}) => ({
|
||
$id: 'o_bestellung01',
|
||
bestellnummer: '26-K7QM2X',
|
||
kaeuferEmail: 'max@example.com',
|
||
bezahltAt: JETZT.toISOString(),
|
||
erstelltAt: new Date(JETZT.getTime() - 600000).toISOString(),
|
||
summeCent: 4140,
|
||
bestellung: JSON.stringify({
|
||
positionen: [
|
||
{ name: 'Tasse', varianteText: 'blau', menge: 2, summeCent: 2400, widerruf: 'gesetzlich' },
|
||
{ name: 'Teller', varianteText: '', menge: 1, summeCent: 1250, summeText: '12,50 €', widerruf: 'individuell' },
|
||
],
|
||
summen: { zwischensummeCent: 3650, versandCent: 490, gesamtCent: 4140, texte: { zwischensumme: '36,50 €', versand: '4,90 €', gesamt: '41,40 €', steuerHinweis: 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.' } },
|
||
lieferart: 'versand',
|
||
land: 'DE',
|
||
kaeufer: { name: 'Max Mustermann', email: 'max@example.com' },
|
||
lieferadresse: { name: 'Max Mustermann', line1: 'Musterweg 3', postal_code: '12345', city: 'Musterstadt', country: 'DE' },
|
||
zahlungsart: 'card',
|
||
...snap,
|
||
}),
|
||
...over,
|
||
})
|
||
|
||
let sink
|
||
let log
|
||
beforeEach(async () => {
|
||
for (const id of aw.docs('termineOutbox').keys()) await deleteDocument(config.collections.termineOutbox, id)
|
||
for (const id of aw.docs('customers').keys()) await deleteDocument(config.collections.customers, id)
|
||
resetOutboxForTests()
|
||
config.smtp.from = 'noreply@webklar.com'
|
||
config.publicBaseUrl = 'https://project.webklar.com'
|
||
sink = mailSink()
|
||
setMailTransportForTests(sink)
|
||
log = { error: mock.method(console, 'error', () => {}), warn: mock.method(console, 'warn', () => {}) }
|
||
aw.seed('customers', { $id: 'c1', name: 'Erika Muster', email: 'erika@toepferei.example' })
|
||
})
|
||
afterEach(async () => {
|
||
await warteAufVersand()
|
||
mock.restoreAll()
|
||
config.smtp.from = ABSENDER_VORHER
|
||
config.publicBaseUrl = BASIS_VORHER
|
||
})
|
||
|
||
const eintraege = () => [...aw.docs('termineOutbox').values()]
|
||
const warnLog = () => log.warn.mock.calls.map((aufruf) => aufruf.arguments.map(String).join(' '))
|
||
|
||
// ---------------------------------------------------------------- Werte
|
||
|
||
test('kaeuferWerte: Kopf, Fuß, Positionen, Summen, Lieferadresse und Rechtstexte aus Schnappschuss und Shop', () => {
|
||
const w = kaeuferWerte(ctxMit(), bestellung(), JETZT)
|
||
// euro() setzt ein geschütztes Leerzeichen vor das €-Zeichen
|
||
for (const [schluessel, wert] of Object.entries(w)) w[schluessel] = wert.replaceAll('\u00a0', ' ')
|
||
assert.equal(w.farbe, '#aa3300')
|
||
assert.equal(w.betrieb_name, 'Töpferei Muster')
|
||
assert.equal(w.anbieter_zeile, 'Erika Muster, Hauptstr. 1, 67722 Winnweiler, info@toepferei.example')
|
||
assert.equal(w.widerruf_url, `https://project.webklar.com/shop/${SITE_KEY}/widerruf`)
|
||
assert.equal(w.impressum_url, 'https://toepferei.example/impressum')
|
||
assert.equal(w.datenschutz_url, 'https://toepferei.example/datenschutz')
|
||
assert.equal(w.bestellnummer, '26-K7QM2X')
|
||
assert.equal(w.datum, '05.10.2026')
|
||
assert.equal(w.positionen, '2 × Tasse (blau) – 24,00 €\n1 × Teller – 12,50 €')
|
||
assert.deepEqual([w.zwischensumme, w.versand, w.gesamt], ['36,50 €', '4,90 €', '41,40 €'])
|
||
assert.equal(w.steuer_hinweis, 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.')
|
||
assert.equal(w.lieferart_text, 'Versand nach Deutschland')
|
||
assert.equal(w.lieferadresse, 'Max Mustermann\nMusterweg 3\n12345 Musterstadt')
|
||
assert.equal(w.abholort, 'Werkstatt, Hauptstr. 1, 67722 Winnweiler')
|
||
assert.equal(w.lieferzeit, '2–4 Werktage')
|
||
assert.equal(w.zahlungsart, 'Kreditkarte, Apple Pay, Google Pay')
|
||
assert.equal(w.ausschluss_text, 'Teller: Vom Widerruf ausgeschlossen: nach Kundenwunsch angefertigt')
|
||
assert.equal(w.agb, 'AGB Zeile 1\nAGB Zeile 2')
|
||
assert.equal(w.widerrufsbelehrung, 'Belehrung Zeile 1\nZeile 2')
|
||
assert.equal(w.widerrufsformular, 'An Töpferei Muster\nHiermit widerrufe ich')
|
||
assert.equal(w.gewaehrleistung, gewaehrleistungsHinweis())
|
||
})
|
||
|
||
test('kaeuferWerte: Links fallen ohne Impressum-/Datenschutz-Adresse auf den Shop zurück, ungültige Farbe auf Schwarz, ohne Bestellung "–"', () => {
|
||
const w = kaeuferWerte(ctxMit(einstellungen({ akzent: 'red;x' }), texte({ impressumUrl: '', datenschutzUrl: '' })), null, JETZT)
|
||
assert.equal(w.farbe, '#111111')
|
||
assert.equal(w.impressum_url, `https://project.webklar.com/shop/${SITE_KEY}`)
|
||
assert.equal(w.datenschutz_url, `https://project.webklar.com/shop/${SITE_KEY}`)
|
||
assert.equal(w.bestellnummer, '–')
|
||
assert.equal(w.positionen, '–')
|
||
assert.equal(w.lieferadresse, '–')
|
||
assert.equal(w.datum, '05.10.2026', 'ohne Bestellung gilt das übergebene Datum')
|
||
})
|
||
|
||
// ---------------------------------------------------------------- Käufermails
|
||
|
||
test('G09: wird mit Shopname als Absender und Antwort an den Händler eingereiht, kein BCC, Rechtstexte stehen in der Mail', async () => {
|
||
const erg = await anKaeufer('G09', ctxMit(), bestellung(), {}, JETZT)
|
||
assert.equal(erg.ok, true)
|
||
assert.equal(erg.gekuerzt, false)
|
||
await warteAufVersand()
|
||
|
||
const [doc] = eintraege()
|
||
assert.equal(doc.art, 'G09')
|
||
assert.equal(doc.featureId, 'f_shop01')
|
||
assert.equal(doc.bookingId, 'o_bestellung01')
|
||
assert.equal(doc.fromName, 'Töpferei Muster')
|
||
assert.equal(doc.replyTo, 'info@toepferei.example')
|
||
assert.equal(doc.status, 'gesendet')
|
||
assert.equal(doc.an, '', 'nach dem Versand ist die Adresse gelöscht')
|
||
|
||
assert.equal(sink.sent.length, 1)
|
||
const mail = sink.sent[0]
|
||
assert.deepEqual(mail.to.map((t) => t.address), ['max@example.com'])
|
||
assert.ok(!mail.bcc, 'kein BCC')
|
||
assert.equal(mail.from.name, 'Töpferei Muster')
|
||
assert.equal(mail.from.address, 'noreply@webklar.com')
|
||
assert.equal(mail.replyTo[0].address, 'info@toepferei.example')
|
||
assert.equal(mail.subject, 'Ihre Bestellung 26-K7QM2X bei Töpferei Muster')
|
||
for (const teil of ['2 × Tasse (blau) – 24,00 €', '41,40 €', 'Max Mustermann', 'Musterweg 3', 'Vom Widerruf ausgeschlossen', 'AGB Zeile 2', 'Belehrung Zeile 1', 'Hiermit widerrufe ich', 'GESETZLICHE GEWÄHRLEISTUNG', 'Vertrag widerrufen']) {
|
||
assert.ok(mail.text.includes(teil), teil)
|
||
}
|
||
assert.match(mail.html, new RegExp(`href="https://project\\.webklar\\.com/shop/${SITE_KEY}/widerruf"`))
|
||
assert.doesNotMatch(mail.text, /Abholort/)
|
||
assert.deepEqual(warnLog(), [], 'keine Platzhalter-Warnung der Vorlage')
|
||
})
|
||
|
||
test('G09 Abholung: Abholort statt Lieferadresse', async () => {
|
||
const erg = await anKaeufer('G09', ctxMit(), bestellung({}, { lieferart: 'abholung', lieferadresse: null, land: null, positionen: [{ name: 'Tasse', menge: 1, summeCent: 1200, widerruf: 'gesetzlich' }] }), {}, JETZT)
|
||
assert.equal(erg.ok, true)
|
||
await warteAufVersand()
|
||
const text = sink.sent[0].text
|
||
assert.match(text, /Lieferart: Abholung/)
|
||
assert.match(text, /Abholort: Werkstatt, Hauptstr\. 1, 67722 Winnweiler/)
|
||
assert.doesNotMatch(text, /Lieferadresse|Hinweis zum Widerrufsrecht/)
|
||
assert.deepEqual(warnLog(), [])
|
||
})
|
||
|
||
test('G09: zu lang für die Warteschlange ⇒ der AGB-Text wird durch einen Link auf die AGB ersetzt, Belehrung und Formular bleiben', async () => {
|
||
const lang = 'Dies ist ein langer Satz der Geschäftsbedingungen.\n'.repeat(560) // etwa 28 000 Zeichen
|
||
const t = texte({ agb: lang, widerrufsbelehrung: 'Belehrung Satz.\n'.repeat(1000), widerrufsformular: 'Formular Zeile\n'.repeat(300) })
|
||
const erg = await anKaeufer('G09', ctxMit(einstellungen(), t), bestellung(), {}, JETZT)
|
||
assert.equal(erg.ok, true)
|
||
assert.equal(erg.gekuerzt, true)
|
||
const doc = eintraege()[0]
|
||
assert.ok(doc.html.length <= 100000 && doc.text.length <= 50000, `${doc.html.length}/${doc.text.length}`)
|
||
assert.match(doc.text, new RegExp(`Die vollständigen AGB finden Sie unter https://project\\.webklar\\.com/shop/${SITE_KEY}\\?ansicht=agb`))
|
||
assert.doesNotMatch(doc.text, /langer Satz der Geschäftsbedingungen/)
|
||
assert.match(doc.text, /Belehrung Satz\./)
|
||
assert.match(doc.text, /Formular Zeile/)
|
||
assert.match(doc.text, /GESETZLICHE GEWÄHRLEISTUNG/)
|
||
})
|
||
|
||
test('G09: auch gekürzt zu lang ⇒ { ok: false } ohne Wurf und ohne Eintrag', async () => {
|
||
const t = texte({ widerrufsbelehrung: 'x'.repeat(60000) })
|
||
const erg = await anKaeufer('G09', ctxMit(einstellungen(), t), bestellung(), {}, JETZT)
|
||
assert.deepEqual(erg, { ok: false, fehler: 'Die E-Mail an den Käufer konnte nicht eingereiht werden.' })
|
||
assert.equal(eintraege().length, 0)
|
||
assert.equal(sink.sent.length, 0)
|
||
assert.doesNotMatch(warnLog().join('\n'), /max@example\.com/)
|
||
})
|
||
|
||
test('G10: Versanddienst und Sendungsnummer (Block sendung); ohne beide kein Block', async () => {
|
||
assert.equal((await anKaeufer('G10', ctxMit(), bestellung(), { versanddienst: 'DHL', sendungsnummer: '00340434' }, JETZT)).ok, true)
|
||
assert.equal((await anKaeufer('G10', ctxMit(), bestellung({ $id: 'o_zwei' }), {}, JETZT)).ok, true)
|
||
await warteAufVersand()
|
||
const [mit, ohne] = sink.sent
|
||
assert.equal(mit.subject, 'Ihre Bestellung 26-K7QM2X wurde versendet')
|
||
assert.match(mit.text, /Versanddienst: DHL/)
|
||
assert.match(mit.text, /Sendungsnummer: 00340434/)
|
||
assert.doesNotMatch(ohne.text, /Sendungsnummer/)
|
||
})
|
||
|
||
test('G11: Abholort und Hinweis des Händlers, ersatzweise ein Standardhinweis', async () => {
|
||
await anKaeufer('G11', ctxMit(), bestellung(), {}, JETZT)
|
||
const ohneHinweis = einstellungen({ abholung: { aktiv: true, adresse: 'Werkstatt', hinweis: '' } })
|
||
await anKaeufer('G11', ctxMit(ohneHinweis), bestellung({ $id: 'o_zwei' }), {}, JETZT)
|
||
await warteAufVersand()
|
||
const texte11 = sink.sent.map((m) => m.text)
|
||
assert.match(texte11[0], /Abholort: Werkstatt, Hauptstr\. 1/)
|
||
assert.match(texte11[0], /Hinweis: Di–Fr 10–16 Uhr/)
|
||
assert.match(texte11[1], /Hinweis: Bitte bringen Sie Ihre Bestellnummer mit\./)
|
||
assert.equal(sink.sent[0].subject, 'Ihre Bestellung 26-K7QM2X ist abholbereit')
|
||
})
|
||
|
||
test('G12: ohne passende Bestellung, an die Adresse der Erklärung, mit Datum und Uhrzeit; Vorrang-Klasse', async () => {
|
||
const erg = await anKaeufer('G12', ctxMit(), null, { an: 'kaeufer@example.com', bookingId: 'w_widerruf01', name: 'Kai Käufer', bestellnummer: '26-ZZZZZZ', eingang: JETZT, nachricht: 'Ich widerrufe.' }, JETZT)
|
||
assert.equal(erg.ok, true)
|
||
assert.equal(erg.eintrag.bookingId, 'w_widerruf01')
|
||
await warteAufVersand()
|
||
const mail = sink.sent[0]
|
||
assert.deepEqual(mail.to.map((t) => t.address), ['kaeufer@example.com'])
|
||
assert.equal(mail.subject, 'Eingangsbestätigung Ihres Widerrufs bei Töpferei Muster')
|
||
assert.match(mail.text, /Guten Tag Kai Käufer,/)
|
||
assert.match(mail.text, /Eingegangen am: 05\.10\.2026, 14:03 Uhr/)
|
||
assert.match(mail.text, /Bestellnummer: 26-ZZZZZZ/)
|
||
assert.match(mail.text, /Ihre Erklärung: Ich widerrufe\./)
|
||
assert.deepEqual(warnLog(), [])
|
||
})
|
||
|
||
test('G12 und K26 ohne Bestellnummer: „–“ statt leerer Stelle', async () => {
|
||
const erg = await anKaeufer('G12', ctxMit(), null, { an: 'kaeufer@example.com', bookingId: 'w_widerruf02', name: 'Kai Käufer', bestellnummer: '', eingang: JETZT, nachricht: 'Bestellt am 01.10., Tasse blau' }, JETZT)
|
||
assert.equal(erg.ok, true)
|
||
const k = haendlerK26(ctxMit(), { name: 'Kai', email: 'kai@example.com', bestellnummer: '', nachricht: 'Bestellt am 01.10., Tasse blau', eingang: JETZT })
|
||
assert.equal(k.values.bestellnummer, '–')
|
||
assert.equal((await anHaendler('K26', ctxMit(), k.values, k.blocks)).ok, true)
|
||
await warteAufVersand()
|
||
assert.match(sink.sent[0].text, /Bestellnummer: –/)
|
||
assert.match(sink.sent[1].text, /Bestellnummer: –/)
|
||
})
|
||
|
||
test('G13: erstatteter Betrag in Euro, Grund nur wenn angegeben; ohne Betrag { ok: false }', async () => {
|
||
assert.equal((await anKaeufer('G13', ctxMit(), bestellung(), { betragCent: 1250, grund: 'Artikel beschädigt' }, JETZT)).ok, true)
|
||
assert.equal((await anKaeufer('G13', ctxMit(), bestellung({ $id: 'o_zwei' }), { betragCent: 4140 }, JETZT)).ok, true)
|
||
assert.equal((await anKaeufer('G13', ctxMit(), bestellung({ $id: 'o_drei' }), {}, JETZT)).ok, false)
|
||
assert.equal((await anKaeufer('G13', ctxMit(), bestellung({ $id: 'o_vier' }), { betragCent: 12.5 }, JETZT)).ok, false)
|
||
await warteAufVersand()
|
||
assert.equal(sink.sent.length, 2)
|
||
assert.match(sink.sent[0].text, /Erstatteter Betrag: 12,50 €/)
|
||
assert.match(sink.sent[0].text, /Grund: Artikel beschädigt/)
|
||
assert.match(sink.sent[1].text, /Erstatteter Betrag: 41,40 €/)
|
||
assert.doesNotMatch(sink.sent[1].text, /Grund:/)
|
||
assert.match(sink.sent[1].text, /Die Gutschrift erfolgt über die ursprüngliche Zahlungsart\./)
|
||
})
|
||
|
||
test('G14: Zahlung fehlgeschlagen, nichts berechnet', async () => {
|
||
assert.equal((await anKaeufer('G14', ctxMit(), bestellung(), {}, JETZT)).ok, true)
|
||
await warteAufVersand()
|
||
assert.equal(sink.sent[0].subject, 'Ihre Bestellung 26-K7QM2X wurde nicht abgeschlossen')
|
||
assert.match(sink.sent[0].text, /es wurde nichts berechnet/)
|
||
})
|
||
|
||
test('anKaeufer wirft nie: unbekannte Art, fehlende Adresse und Speicherfehler ergeben { ok: false }', async () => {
|
||
assert.equal((await anKaeufer('G01', ctxMit(), bestellung(), {}, JETZT)).ok, false)
|
||
assert.equal((await anKaeufer('G09', ctxMit(), bestellung({ kaeuferEmail: '' }), {}, JETZT)).ok, false)
|
||
assert.equal((await anKaeufer('G09', ctxMit(), bestellung(), {}, new Date('kaputt'))).ok, false)
|
||
aw.failNext({ collection: 'termineOutbox', method: 'POST', status: 500, message: 'Nachbau kaputt' })
|
||
const erg = await anKaeufer('G09', ctxMit(), bestellung(), {}, JETZT)
|
||
assert.equal(erg.ok, false)
|
||
assert.equal(eintraege().length, 0)
|
||
assert.equal(sink.sent.length, 0)
|
||
})
|
||
|
||
test('Käufermails zählen als Gästemails: G09 und G12 mit Vorrang, die übrigen normal', async () => {
|
||
const { mailKlasse } = await import('../server/services/termine/outbox.js')
|
||
assert.deepEqual(['G09', 'G10', 'G11', 'G12', 'G13', 'G14'].map(mailKlasse), ['vorrang', 'normal', 'normal', 'vorrang', 'normal', 'normal'])
|
||
})
|
||
|
||
// ---------------------------------------------------------------- Händlermails
|
||
|
||
test('K25: geht an das Kundenkonto, ohne BCC ans Team, mit Käufer, Positionen und Portal-Link', async () => {
|
||
const { values, blocks } = haendlerK25(ctxMit(), bestellung())
|
||
assert.deepEqual(blocks, { ueberverkauft: false, sepa: false })
|
||
const erg = await anHaendler('K25', ctxMit(), values, blocks)
|
||
assert.deepEqual(erg, { ok: true })
|
||
assert.equal(sink.sent.length, 1)
|
||
const mail = sink.sent[0]
|
||
assert.deepEqual(mail.to.map((t) => t.address), ['erika@toepferei.example'])
|
||
assert.ok(!mail.bcc, 'kein BCC ans Team')
|
||
assert.equal(mail.subject, 'Neue Bestellung 26-K7QM2X in Deinem Webshop')
|
||
assert.match(mail.text, /Hallo Erika Muster/)
|
||
assert.match(mail.text, /Töpferei Muster/)
|
||
assert.match(mail.text, /Käufer: Max Mustermann/)
|
||
assert.match(mail.text, /Gesamt: 41,40 €/)
|
||
assert.match(mail.html.replaceAll('\u00a0', ' '), /2 × Tasse \(blau\) – 24,00 €<br>1 × Teller – 12,50 €/)
|
||
assert.match(mail.html, /href="https:\/\/project\.webklar\.com\/dashboard\.html#webshop\?projekt=p1"/)
|
||
assert.doesNotMatch(mail.text, /Achtung|Lastschrift/)
|
||
assert.deepEqual(warnLog(), [])
|
||
})
|
||
|
||
test('K25: Blöcke ueberverkauft und sepa', async () => {
|
||
const { values, blocks } = haendlerK25(ctxMit(), bestellung({}, { zahlungsart: 'sepa_debit' }), { ueberverkauft: true })
|
||
assert.deepEqual(blocks, { ueberverkauft: true, sepa: true })
|
||
assert.equal((await anHaendler('K25', ctxMit(), values, blocks)).ok, true)
|
||
assert.match(sink.sent[0].text, /Achtung: Bestand reichte nicht/)
|
||
assert.match(sink.sent[0].text, /Zahlung per Lastschrift/)
|
||
})
|
||
|
||
test('K26: Widerruf mit Eingang in Berliner Zeit und Erklärung, ohne BCC; ohne Erklärung kein Block', async () => {
|
||
const widerruf = { name: 'Kai <K>', email: 'kai@example.com', bestellnummer: '26-ZZZZZZ', nachricht: 'Ich widerrufe.', eingangAt: JETZT.toISOString() }
|
||
const { values, blocks } = haendlerK26(ctxMit(), widerruf)
|
||
assert.equal(values.eingang, '05.10.2026, 14:03 Uhr')
|
||
assert.deepEqual(blocks, { nachricht: true })
|
||
assert.equal((await anHaendler('K26', ctxMit(), values, blocks)).ok, true)
|
||
const ohne = haendlerK26(ctxMit(), { ...widerruf, nachricht: ' ' })
|
||
assert.deepEqual(ohne.blocks, { nachricht: false })
|
||
assert.equal((await anHaendler('K26', ctxMit(), ohne.values, ohne.blocks)).ok, true)
|
||
const [mit, kurz] = sink.sent
|
||
assert.ok(!mit.bcc)
|
||
assert.equal(mit.subject, 'Widerruf in Deinem Webshop eingegangen')
|
||
assert.match(mit.text, /Eingegangen am: 05\.10\.2026, 14:03 Uhr/)
|
||
assert.match(mit.text, /Käufer: Kai <K>/)
|
||
assert.match(mit.text, /E-Mail: kai@example\.com/)
|
||
assert.match(mit.text, /Erklärung: Ich widerrufe\./)
|
||
assert.match(mit.html, /Kai <K>/)
|
||
assert.doesNotMatch(kurz.text, /Erklärung:/)
|
||
})
|
||
|
||
test('anHaendler wirft nie: Kunde fehlt oder ohne E-Mail, Versandfehler ⇒ { ok: false } und Warnung ohne Adresse', async () => {
|
||
const { values, blocks } = haendlerK25(ctxMit(), bestellung())
|
||
const fremd = ctxMit()
|
||
fremd.shop.customerId = 'c_gibtsnicht'
|
||
assert.equal((await anHaendler('K25', fremd, values, blocks)).ok, false)
|
||
aw.seed('customers', { $id: 'c_ohne', name: 'Ohne', email: '' })
|
||
const ohneMail = ctxMit()
|
||
ohneMail.shop.customerId = 'c_ohne'
|
||
assert.equal((await anHaendler('K25', ohneMail, values, blocks)).ok, false)
|
||
setMailTransportForTests({
|
||
async sendMail() {
|
||
throw new Error('SMTP erika@toepferei.example kaputt')
|
||
},
|
||
})
|
||
assert.deepEqual(await anHaendler('K25', ctxMit(), values, blocks), { ok: false, fehler: 'Die E-Mail an den Händler konnte nicht gesendet werden.' })
|
||
assert.equal(sink.sent.length, 0)
|
||
assert.doesNotMatch(warnLog().join('\n'), /erika@toepferei\.example/, 'keine Adresse im Log')
|
||
assert.ok(warnLog().some((zeile) => /K25 nicht gesendet/.test(zeile)))
|
||
})
|