Bisher sah man nirgends, ob die Angebots-Mail rausging, Fehler landeten nicht im Log, und senden ließ sie sich nur beim Anlegen. - Angebotsliste: Kunden-Adresse unter dem Namen, im Status "E-Mail gesendet <Zeitpunkt>" bzw. "E-Mail nicht verschickt" / "Keine E-Mail-Adresse hinterlegt" - Knopf "E-Mail senden" / "Erneut senden" mit Rückfrage (Adresse, letzter Versand), solange das Angebot offen und gültig ist; bei laufendem Abo, solange die erste rabattierte Abbuchung noch bevorsteht (die Mail nennt sie). POST /api/portal-admin/offers/:id/notify setzt notifiedAt. - Server-Log: "[offers] Angebots-Mail <Ref> vom Mailserver angenommen: <Message-ID> 250 … queued as …" bzw. "… fehlgeschlagen: <Grund>" - Lehnt der Mailserver nur den Kunden ab (die BCC-Kopie ans Team geht trotzdem raus), gilt die Mail als nicht verschickt - sendCustomerMail gibt die nodemailer-Info zurück Tests: npm test (40), Server-Durchlauf gegen Appwrite-Mock, SMTP-Sink und Stripe-Double (28 Prüfungen), Oberfläche (18 Prüfungen). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
2.3 KiB
JavaScript
51 lines
2.3 KiB
JavaScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { adminOfferView } from '../server/services/offers.js'
|
|
|
|
const DAY = 86400000
|
|
const iso = (ms) => new Date(ms).toISOString()
|
|
const ymd = (ms) => iso(ms).slice(0, 10)
|
|
const now = Date.now()
|
|
|
|
const offer = (fields = {}) => ({
|
|
$id: 'offer-1', $createdAt: iso(now - DAY), status: 'offen', scope: 'neu', months: 2,
|
|
monthlyPriceCents: 2500, regularPriceCents: 5000, customerId: 'c1',
|
|
validUntil: `${ymd(now + 10 * DAY)}T00:00:00.000Z`, notifiedAt: null, ...fields,
|
|
})
|
|
const customer = { name: 'Martin', email: 'martin@example.invalid' }
|
|
|
|
test('Offenes, gültiges Angebot: Mail kann (erneut) raus, Adresse steht in der Admin-Ansicht', () => {
|
|
const v = adminOfferView(offer(), customer)
|
|
assert.equal(v.mailSendable, true)
|
|
assert.equal(v.customerEmail, 'martin@example.invalid')
|
|
assert.equal(v.notifiedAt, null)
|
|
assert.equal(adminOfferView(offer({ notifiedAt: iso(now) }), customer).mailSendable, true)
|
|
})
|
|
|
|
test('Keine Mail bei abgelaufenem, zurückgezogenem oder eingelöstem Angebot und ohne Adresse', () => {
|
|
assert.equal(adminOfferView(offer({ validUntil: `${ymd(now - 2 * DAY)}T00:00:00.000Z` }), customer).mailSendable, false)
|
|
assert.equal(adminOfferView(offer({ status: 'zurueckgezogen' }), customer).mailSendable, false)
|
|
const sub = { startedAt: iso(now - DAY), trialEnd: null }
|
|
assert.equal(adminOfferView(offer({ status: 'aktiv', appliedAt: iso(now - DAY + 5000) }), customer, sub).mailSendable, false)
|
|
const v = adminOfferView(offer(), { name: 'Ohne Mail', email: '' })
|
|
assert.equal(v.mailSendable, false)
|
|
assert.equal(v.customerEmail, '')
|
|
})
|
|
|
|
test('Laufendes Abo: Mail nur, solange die erste rabattierte Abbuchung bevorsteht', () => {
|
|
const future = adminOfferView(
|
|
offer({ status: 'aktiv', scope: 'laufend', appliedAt: iso(now - 3600000) }),
|
|
customer,
|
|
{ startedAt: iso(now - 10 * DAY), trialEnd: null }
|
|
)
|
|
assert.equal(future.mailSendable, true)
|
|
const past = adminOfferView(
|
|
offer({ status: 'aktiv', scope: 'laufend', appliedAt: iso(now - 40 * DAY) }),
|
|
customer,
|
|
{ startedAt: iso(now - 50 * DAY), trialEnd: null }
|
|
)
|
|
assert.equal(past.mailSendable, false)
|
|
// Abo-Dokument fehlt: Termin unbekannt, also keine Mail
|
|
assert.equal(adminOfferView(offer({ status: 'aktiv', scope: 'laufend', appliedAt: iso(now) }), customer, null).mailSendable, false)
|
|
})
|