Push-Zeitmail: Vorlage T15, Zuordnung Pusher → Mitarbeiter, Versand
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec3f22d98e
commit
1a1005d099
5 files changed
+306
-3
No files matched your search
@@ -6,7 +6,7 @@ import { contractAttachmentFor, termsDocFor } from '../server/services/products.
|
||||
|
||||
const DIR = new URL('../server/mail-templates/', import.meta.url)
|
||||
// K12, K14 und T12 (Wechsel ins Wartungs-Abo) aus Task 9, K13 und T13 (Zusatzspeicher) aus Task 8 (Fix 1);
|
||||
// Endfassung der Texte in Task 12; T14 „Löschfristen“ aus Task 21 (F39)
|
||||
// Endfassung der Texte in Task 12; T14 „Löschfristen“ aus Task 21 (F39); T15 Push-Zeitmail
|
||||
const EXPECTED = [
|
||||
...Array.from({ length: 11 }, (_, i) => `K${String(i + 1).padStart(2, '0')}`),
|
||||
'K12',
|
||||
@@ -16,6 +16,7 @@ const EXPECTED = [
|
||||
'T12',
|
||||
'T13',
|
||||
'T14',
|
||||
'T15',
|
||||
]
|
||||
|
||||
const sampleValues = (tpl) => Object.fromEntries(tpl.placeholders.map((p) => [p, `Wert-${p}`]))
|
||||
@@ -31,8 +32,8 @@ for (const id of EXPECTED) {
|
||||
const source = readFileSync(new URL(tpl.file, DIR), 'utf8')
|
||||
const title = source.match(/<title>([\s\S]*?)<\/title>/)[1]
|
||||
assert.equal(title, tpl.subject, 'Betreff im Kopf und <title> sind gleich')
|
||||
// F34: Team-Hinweise tragen [Abo], [Speicher] (Zusatzspeicher-Aufgaben) oder [Datenschutz] (Loeschfristen)
|
||||
assert.equal(id.startsWith('T'), /^\[(Abo|Speicher|Datenschutz)\]/.test(tpl.subject) || id === 'T01', 'Team-Hinweise ab T02 mit [Abo], [Speicher] oder [Datenschutz]')
|
||||
// F34: Team-Hinweise tragen [Abo], [Speicher] (Zusatzspeicher-Aufgaben), [Datenschutz] (Loeschfristen) oder [Zeit] (Push-Zeitmail)
|
||||
assert.equal(id.startsWith('T'), /^\[(Abo|Speicher|Datenschutz|Zeit)\]/.test(tpl.subject) || id === 'T01', 'Team-Hinweise ab T02 mit [Abo], [Speicher], [Datenschutz] oder [Zeit]')
|
||||
|
||||
const used = new Set([...tpl.body.matchAll(/\{\{(\w+)\}\}/g), ...tpl.subject.matchAll(/\{\{(\w+)\}\}/g)].map((m) => m[1]))
|
||||
assert.deepEqual([...used].sort(), [...tpl.placeholders].sort(), 'deklarierte = verwendete Platzhalter')
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { test, before, after } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { startFakeAppwrite } from './helpers/fakeAppwrite.js'
|
||||
import { mailSink } from './support/mailSink.js'
|
||||
|
||||
let fake
|
||||
let findEmployeeForPusher, buildPushTimeMail, sendPushTimeMail
|
||||
let renderMail, verifyPushTimeToken, setMailTransportForTests
|
||||
|
||||
const KENSO = { $id: 'k', displayName: 'Kenso Grimm', shortcode: 'KNSO', email: 'kenso@webklar.com', $createdAt: '2026-01-01T00:00:00.000Z' }
|
||||
const ANDREJ = { $id: 'a', displayName: 'Andrej Stevanovski', shortcode: 'ANDJ', email: '', $createdAt: '2026-01-02T00:00:00.000Z' }
|
||||
|
||||
const ticket = { $id: 't1', woid: '100118', customerName: 'Amin Friseur', topic: 'Website Amin Friseur', details: 'Neue Seite', status: 'Occupied' }
|
||||
const worksheet = { $id: 'ws9', woid: '100118', serviceType: 'GIT' }
|
||||
const commits = [
|
||||
{ id: 'abc1234def', message: 'Startseite: Hero neu\n\nlanger Text' },
|
||||
{ id: 'def5678abc', message: '<script>x</script> & Co' },
|
||||
]
|
||||
const input = (extra = {}) => ({
|
||||
ticket, worksheet, repoFullName: 'WEBklar/amin-friseur', branch: 'main', commits,
|
||||
pushedAt: new Date('2026-09-30T14:46:00Z'), ...extra,
|
||||
})
|
||||
|
||||
before(async () => {
|
||||
fake = await startFakeAppwrite({ seed: { employees: [ANDREJ, KENSO] } })
|
||||
Object.assign(process.env, {
|
||||
APPWRITE_ENDPOINT: fake.endpoint,
|
||||
APPWRITE_API_KEY: 'test-key',
|
||||
PUSH_TIME_SECRET: 'z'.repeat(40),
|
||||
})
|
||||
;({ findEmployeeForPusher, buildPushTimeMail, sendPushTimeMail } = await import('../server/services/pushTimeMail.js'))
|
||||
;({ renderMail } = await import('../server/services/mailTemplates.js'))
|
||||
;({ verifyPushTimeToken } = await import('../server/services/pushTimeToken.js'))
|
||||
;({ setMailTransportForTests } = await import('../server/services/mailer.js'))
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
setMailTransportForTests?.(null)
|
||||
await fake.close()
|
||||
})
|
||||
|
||||
test('findEmployeeForPusher: Kürzel, voller Name, ohne E-Mail, unbekannt', () => {
|
||||
const employees = [ANDREJ, KENSO]
|
||||
assert.equal(findEmployeeForPusher({ username: 'knso' }, employees)?.$id, 'k')
|
||||
assert.equal(findEmployeeForPusher({ full_name: ' kenso grimm ' }, employees)?.$id, 'k')
|
||||
assert.equal(findEmployeeForPusher({ login: 'ANDJ' }, employees), null)
|
||||
assert.equal(findEmployeeForPusher({ username: 'fremd' }, employees), null)
|
||||
assert.equal(findEmployeeForPusher({}, employees), null)
|
||||
})
|
||||
|
||||
test('buildPushTimeMail: Betreff, Links, Escaping, gültiger Token', () => {
|
||||
const mail = buildPushTimeMail({ employee: KENSO, ...input() })
|
||||
assert.equal(mail.template, 'T15')
|
||||
const out = renderMail('T15', mail.values)
|
||||
assert.deepEqual(out.problems, [])
|
||||
assert.equal(out.subject, '[Zeit] #100118 Amin Friseur – 2 Commit(s)')
|
||||
assert.match(out.html, /Hallo Kenso,/)
|
||||
assert.match(out.html, /In Arbeit/)
|
||||
assert.match(out.html, /30\.09\.2026 16:46/)
|
||||
for (const m of [15, 30, 45, 60, 90, 120, 180]) {
|
||||
assert.match(out.html, new RegExp(`/zeit/[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\?min=${m}"`))
|
||||
}
|
||||
assert.match(out.html, /\/zeit\/[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"/, 'Andere Zeit ohne min')
|
||||
assert.match(out.html, /https:\/\/ticket\.webklar\.com\/tickets\?woid=100118/)
|
||||
assert.match(out.html, /abc1234 Startseite: Hero neu/)
|
||||
assert.doesNotMatch(out.html, /langer Text/)
|
||||
assert.match(out.html, /<script>x<\/script> & Co/)
|
||||
assert.doesNotMatch(out.html, /<script>/)
|
||||
|
||||
const token = mail.values.link_andere.split('/zeit/')[1]
|
||||
assert.deepEqual(verifyPushTimeToken(token), { worksheetId: 'ws9', employeeId: 'k' })
|
||||
})
|
||||
|
||||
test('buildPushTimeMail: 12 Commits → 10 Zeilen + Rest', () => {
|
||||
const many = Array.from({ length: 12 }, (_, i) => ({ id: `${String(i).padStart(7, '0')}ffff`, message: `Commit ${i}` }))
|
||||
const { values } = buildPushTimeMail({ employee: KENSO, ...input({ commits: many }) })
|
||||
const lines = values.commits.split('\n')
|
||||
assert.equal(lines.length, 11)
|
||||
assert.equal(lines.at(-1), '… und 2 weitere')
|
||||
assert.equal(values.anzahl_commits, 12)
|
||||
})
|
||||
|
||||
test('buildPushTimeMail: lange Beschreibung gekürzt, ohne Kunde', () => {
|
||||
const { values } = buildPushTimeMail({
|
||||
employee: KENSO,
|
||||
...input({ ticket: { ...ticket, customerName: '', details: 'x'.repeat(400), status: 'Weird' } }),
|
||||
})
|
||||
assert.equal(values.ticket_beschreibung.length, 301)
|
||||
assert.ok(values.ticket_beschreibung.endsWith('…'))
|
||||
assert.equal(values.kunde, 'ohne Kunde')
|
||||
assert.equal(values.ticket_status, 'Weird')
|
||||
})
|
||||
|
||||
test('sendPushTimeMail: an den Pusher, ohne BCC', async () => {
|
||||
const sink = mailSink()
|
||||
setMailTransportForTests(sink)
|
||||
const r = await sendPushTimeMail({ ...input(), pusher: { username: 'knso' } })
|
||||
assert.deepEqual(r, { sent: true })
|
||||
assert.equal(sink.sent.length, 1)
|
||||
assert.deepEqual(sink.sent[0].to.map((t) => t.address), ['kenso@webklar.com'])
|
||||
assert.equal(sink.sent[0].from.name, 'WEBklar Kundenportal')
|
||||
assert.equal(sink.sent[0].bcc, undefined)
|
||||
})
|
||||
|
||||
test('sendPushTimeMail: unbekannter Pusher → keine Mail', async () => {
|
||||
const sink = mailSink()
|
||||
setMailTransportForTests(sink)
|
||||
const r = await sendPushTimeMail({ ...input(), pusher: { username: 'fremd' } })
|
||||
assert.equal(r.sent, false)
|
||||
assert.equal(sink.sent.length, 0)
|
||||
})
|
||||
|
||||
test('sendPushTimeMail: Versandfehler wird geschluckt', async () => {
|
||||
setMailTransportForTests({ sendMail: async () => { throw new Error('SMTP kaputt') } })
|
||||
const r = await sendPushTimeMail({ ...input(), pusher: { username: 'knso' } })
|
||||
assert.equal(r.sent, false)
|
||||
})
|
||||
Reference in new issue
Block a user