Team-Mails: Links ins Ticketsystem statt Admin-Bereich

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-02 03:52:54 +00:00
1 parent 7c00633db0
commit 95e2807dc7
20 files changed
+132 -28

No files matched your search

-3
View File
@@ -51,11 +51,8 @@ test('Admin-Panel und alte Admin-Schnittstellen sind weg', () => {
})
test('keine Verweise mehr auf admin.html, kalkulation.html oder /api/portal-admin in server/ und public/', () => {
// Mail-Vorlagen und ihre Links stellt Task 3 um (eigener Test in mailTemplates.test.js)
const skip = [path.join(root, 'server/mail-templates/'), path.join(root, 'server/services/takeoverCredits.js')]
const hits = []
for (const file of [...filesUnder(path.join(root, 'server')), ...filesUnder(path.join(root, 'public'))]) {
if (skip.some((s) => file.startsWith(s))) continue
readFileSync(file, 'utf8').split('\n').forEach((line, i) => {
if (/admin\.html|kalkulation\.html|\/api\/portal-admin/.test(line)) hits.push(`${path.relative(root, file)}:${i + 1}`)
})
+25
View File
@@ -498,3 +498,28 @@ test('T07: Hinweis auf nicht genutzte Arbeitszeit nur im Block wartung', () => {
assert.match(h.text, /Gebuchter Zusatzspeicher endet mit dem Abo\./)
assert.doesNotMatch(h.text, /Arbeitszeit|Stundenkontingent|Restguthaben/)
})
// Etappe 4: das Admin-Panel ist abgeschaltet, Team-Mails fuehren ins Ticketsystem
const TEAM_FOOTER = ['T01', 'T02', 'T03', 'T04', 'T05', 'T06', 'T07', 'T08', 'T09', 'T10', 'T11', 'T12', 'T13', 'T14', 'T16']
test('keine Vorlage verlinkt admin.html oder nennt den Admin-Bereich', () => {
for (const id of EXPECTED) {
const source = readFileSync(new URL(loadTemplate(id).file, DIR), 'utf8')
assert.ok(!source.includes('admin.html'), `${id}: admin.html`)
assert.ok(!source.includes('Admin-Bereich'), `${id}: Admin-Bereich`)
}
})
test('Fusszeile der Team-Mails: Ticketsystem', () => {
for (const id of TEAM_FOOTER) {
const tpl = loadTemplate(id)
const mail = renderMail(id, sampleValues(tpl), allBlocks(tpl, true))
assert.match(mail.html, /Automatische Benachrichtigung aus dem WEBklar Kundenportal · <a class="mut" href="https:\/\/ticket\.webklar\.com\/customers"[^>]*>Ticketsystem<\/a>/, id)
}
})
test('T01: Knopf antwortet im Ticketsystem beim Kunden (team_link)', () => {
const mail = renderMail('T01', { kunde_name: 'Berta', nachricht: 'Hallo', team_link: 'https://ticket.webklar.com/customers/chat?kunde=c1' })
assert.deepEqual(mail.problems, [])
assert.match(mail.html, /<a href="https:\/\/ticket\.webklar\.com\/customers\/chat\?kunde=c1"[^>]*>Im Ticketsystem antworten &rarr;<\/a>/)
})
+73
View File
@@ -0,0 +1,73 @@
import { test, after, beforeEach } from 'node:test'
import assert from 'node:assert/strict'
import os from 'node:os'
import path from 'node:path'
import { mkdtempSync } from 'node:fs'
import { startAppwriteMock } from './support/appwriteMock.js'
import { startFakeStripe } from './support/fakeStripe.js'
import { useTestEnv } from './support/testEnv.js'
import { schemaKeys } from './support/schema.js'
import { mailSink } from './support/mailSink.js'
// Etappe 4: Team-Mails oeffnen das Ticketsystem (Chat beim Kunden, Antrag im Kunden-Reiter Gutschriften)
process.env.TAKEOVER_UPLOAD_DIR = mkdtempSync(path.join(os.tmpdir(), 'team-mail-links-'))
process.env.CHAT_NOTIFY_EMAILS = 'team@webklar.invalid'
const aw = await startAppwriteMock({
schema: { subscriptions: schemaKeys('subscriptions'), takeoverCredits: schemaKeys('takeoverCredits') },
})
const st = await startFakeStripe()
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
const { seedFixtures, hostingDoc, customers } = await import('./support/fixtures.js')
const { setMailTransportForTests, sendChatNotification } = await import('../server/services/mailer.js')
const { createTakeoverRequest, answerQuestion } = await import('../server/services/takeoverCredits.js')
after(() => {
setMailTransportForTests(null)
return Promise.all([aw.close(), st.close()])
})
seedFixtures(aw)
aw.seed('subscriptions', hostingDoc)
let sink
beforeEach(() => {
sink = mailSink()
setMailTransportForTests(sink)
})
const teamMail = (subject) => sink.sent.find((m) => m.to.some((t) => t.address === 'team@webklar.invalid') && subject.test(m.subject))
const input = {
email: 'k@example.invalid',
services: ['hosting'],
provider: 'Altanbieter',
domain: '',
invoiceAmountCents: 12000,
billingMonths: 12,
paidUntil: '2027-01-31',
message: '',
}
test('T01 antwortet im Ticketsystem beim Kunden', async () => {
const res = await sendChatNotification({ $id: 'c1', name: 'Berta' }, 'Hallo')
assert.deepEqual(res, { sent: true })
const mail = teamMail(/Neue Portalnachricht von Berta/)
assert.ok(mail)
assert.match(mail.html, /href="https:\/\/ticket\.webklar\.com\/customers\/chat\?kunde=c1"[^>]*>Im Ticketsystem antworten &rarr;<\/a>/)
assert.match(mail.html, /href="https:\/\/ticket\.webklar\.com\/customers"[^>]*>Ticketsystem<\/a>/)
assert.doesNotMatch(mail.html, /admin\.html|Admin-Bereich/)
})
test('T10/T11 oeffnen den Antrag im Kunden-Reiter Gutschriften', async () => {
const doc = await createTakeoverRequest({ customer: customers.c1, input, uploads: [], consentIp: '127.0.0.1' })
const link = `https://ticket.webklar.com/customers/c1?tab=gutschriften&amp;antrag=${doc.$id}`
const t10 = teamMail(/Übernahmegutschrift/)
assert.ok(t10, sink.sent.map((m) => m.subject).join(' | '))
assert.ok(t10.html.includes(`href="${link}"`), 'T10-Link')
assert.doesNotMatch(t10.html, /admin\.html|Admin-Bereich/)
aw.seed('takeoverCredits', { ...aw.docs('takeoverCredits').get(doc.$id), status: 'rueckfrage' })
sink.sent.length = 0
await answerQuestion({ customer: customers.c1, doc: aw.docs('takeoverCredits').get(doc.$id), message: 'Hier die Antwort', uploads: [] })
const t11 = sink.sent.find((m) => m.to.some((t) => t.address === 'team@webklar.invalid'))
assert.ok(t11)
assert.ok(t11.html.includes(`href="${link}"`), 'T11-Link')
assert.doesNotMatch(t11.html, /admin\.html|Admin-Bereich/)
})