Analyse: K18 mit PDFs und Nachweis, K19 neu, Tickets für Einschalten, Ausschalten und Veröffentlichen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3bac4093af
commit
fbc40252a3
5 files changed
+306
-28
No files matched your search
+122
-5
@@ -46,6 +46,7 @@ const textform = { date: '2026-10-02', channel: 'email' }
|
||||
const NOW = new Date('2026-10-05T10:00:00.000Z')
|
||||
const consents = (p) => cl.listConsents(p)
|
||||
const analyseTickets = () => [...aw.docs('workorders').values()].filter((t) => String(t.topic).includes('Hinweis auf Analyse'))
|
||||
const topics = () => [...aw.docs('workorders').values()].map((t) => String(t.topic))
|
||||
const doc = (c, p) => sf.getFeatureDoc(c, p, 'analyse')
|
||||
|
||||
aw.seed('customers', { $id: 'c1', name: 'Kunde Eins', email: 'eins@example.invalid', location: 'Berlin' })
|
||||
@@ -111,11 +112,26 @@ test('Einschalten: Dokument, Sammeldienst, Neubau, Ticket, Mail K18', async () =
|
||||
assert.match(tickets[0].details, /Funktion: analyse/)
|
||||
assert.match(tickets[0].details, /Domain\(s\): baeckerei\.project\.webklar\.com, baeckerei\.de/)
|
||||
assert.match(tickets[0].details, /https:\/\/baeckerei\.de\/datenschutz-analyse\.html/)
|
||||
assert.match(tickets[0].details, /Satz \+ Link in die bestehende Datenschutzerklärung einfügen/)
|
||||
assert.doesNotMatch(tickets[0].details, /Satz \+ Link in die bestehende Datenschutzerklärung einfügen/)
|
||||
for (const part of ['Textbaustein', 'offensichtlicher Widerspruch', 'externer Generator', 'Stundenkontingent', 'Wir messen mit einer eigenen Reichweitenmessung ohne Cookies', 'Solange Sie im Banner nichts ausgewählt haben', 'Solange Du im Banner nichts ausgewählt hast']) {
|
||||
assert.ok(tickets[0].details.includes(part), part)
|
||||
}
|
||||
assert.ok(tickets[0].details.length < 8000, 'passt in workorders.details (8000)')
|
||||
assert.equal(topics().filter((t) => t.includes('veröffentlichen')).length, 0, 'Neubau lief: kein Veröffentlichen-Ticket')
|
||||
assert.equal((await doc('c1', 'p1')).privacyTicketId, tickets[0].$id)
|
||||
assert.equal(mails.sent.length, 1)
|
||||
assert.equal(mails.sent[0].subject, 'Deine Website-Analyse ist aktiv – Bäckerei')
|
||||
assert.deepEqual(mails.sent[0].to.map((t) => t.address), ['eins@example.invalid'])
|
||||
const k18 = mails.sent[0]
|
||||
assert.deepEqual(k18.attachments.map((a) => a.filename), ['AVV-Website-Analyse-2026-10-02.pdf', 'Leistungsbeschreibung-Website-Analyse-2026-10-02.pdf'])
|
||||
for (const a of k18.attachments) assert.equal(Buffer.from(a.content, 'base64').subarray(0, 4).toString(), '%PDF')
|
||||
assert.match(k18.html, /2026-10-02/)
|
||||
assert.ok(k18.html.includes(`${config.publicBaseUrl}/rechtliches/avv-website-analyse-2026-10-02.html`))
|
||||
assert.ok(k18.html.includes(`${config.publicBaseUrl}/rechtliches/leistungsbeschreibung-website-analyse-2026-10-02.html`))
|
||||
assert.match(k18.html, /durch Kunde Eins <eins@example\.invalid>/)
|
||||
assert.match(k18.html, /\d\d\.\d\d\.\d{4}, \d\d:\d\d Uhr/)
|
||||
assert.match(k18.text, /Das Einwilligungsbanner erscheint mit der nächsten Veröffentlichung/)
|
||||
assert.doesNotMatch(k18.html, /in Textform am/)
|
||||
})
|
||||
|
||||
test('Doppelt einschalten: kein zweites Ticket, keine zweite Mail, nur Farbe', async () => {
|
||||
@@ -177,6 +193,11 @@ test('Abo-Ende-Job: aktive Analyse ohne Wartungs-Abo wird beendet, K19', async (
|
||||
assert.equal(d.offSince, now.toISOString())
|
||||
}
|
||||
assert.deepEqual(mails.sent.map((m) => m.subject).sort(), ['Deine Website-Analyse wurde beendet – Hosting-Kunde', 'Deine Website-Analyse wurde beendet – Ohne Abo'])
|
||||
for (const m of mails.sent) {
|
||||
assert.match(m.text, /keine Daten mehr gespeichert/)
|
||||
assert.match(m.text, /46\. Tag/)
|
||||
assert.doesNotMatch(m.text, /Der Banner und die Messung sind von Deiner Website entfernt/)
|
||||
}
|
||||
mails.sent.length = 0
|
||||
assert.equal((await an.runAboCheck({ now })).deactivated.length, 0, 'zweiter Lauf: nichts mehr zu tun')
|
||||
assert.equal(mails.sent.length, 0)
|
||||
@@ -204,18 +225,17 @@ test('selbstheilend: Ticket scheitert einmal, Wiederholung holt Ticket, Neubau u
|
||||
const before = analyseTickets().length
|
||||
await assert.rejects(an.activateAnalyse({ customerId: 'c1', projectId: 'p6', ...cust() }), /Ticketsystem down/)
|
||||
assert.equal((await doc('c1', 'p6')).state, 'active')
|
||||
assert.equal(rebuilds.length, 0)
|
||||
assert.equal(mails.sent.length, 0)
|
||||
const r = await an.activateAnalyse({ customerId: 'c1', projectId: 'p6', ...cust() })
|
||||
assert.equal(r.doc.state, 'active')
|
||||
assert.ok(r.doc.privacyTicketId && r.doc.activatedMailAt)
|
||||
assert.equal(analyseTickets().length, before + 1)
|
||||
assert.deepEqual(rebuilds, ['p6'])
|
||||
assert.deepEqual(rebuilds, ['p6', 'p6'], 'Neubau kommt vor dem Ticket, die Wiederholung baut erneut')
|
||||
assert.equal(mails.sent.length, 1)
|
||||
// dritter Aufruf: nichts mehr offen
|
||||
await an.activateAnalyse({ customerId: 'c1', projectId: 'p6', ...cust() })
|
||||
assert.equal(analyseTickets().length, before + 1)
|
||||
assert.deepEqual(rebuilds, ['p6'])
|
||||
assert.deepEqual(rebuilds, ['p6', 'p6'])
|
||||
assert.equal(mails.sent.length, 1)
|
||||
an.analyseDeps.ticket = original
|
||||
})
|
||||
@@ -362,7 +382,7 @@ test('Historie: Kunde, Aus, An durch Mitarbeiter in zeitlicher Folge; Portal-Dok
|
||||
const { doc: d3 } = await an.activateAnalyse({ customerId: 'c1', projectId: 'p14', consent: consent(), by: 'Kenso', textform, audienceConfirmed: true, now: NOW })
|
||||
assert.equal(d3.privacyTicketId && d3.privacyTicketId !== d1.privacyTicketId, true, 'neues Datenschutz-Ticket')
|
||||
assert.equal(d3.removalTicketId, '')
|
||||
assert.equal(d3.publishTicketId, '')
|
||||
assert.ok(d3.publishTicketId && d3.publishTicketId !== 'u1', 'p14 ohne Repo: neues Veröffentlichen-Ticket statt altem Marker')
|
||||
h = await consents('p14')
|
||||
assert.deepEqual(h.map((x) => x.event), ['on', 'off', 'on'])
|
||||
assert.deepEqual(h.map((x) => x.via), ['portal', 'portal', 'woms'])
|
||||
@@ -407,3 +427,100 @@ test('Ausschalten schreibt off-Historie; Fehler dort verhindert das Ausschalten
|
||||
await an.deactivateAnalyse({ customerId: 'c1', projectId: 'p16', now: NOW, actor })
|
||||
assert.deepEqual((await consents('p16')).map((x) => x.event), ['on', 'on', 'off'])
|
||||
})
|
||||
|
||||
const staff = (extra = {}) => ({ consent: consent(), by: 'Kenso', textform: { date: '2026-10-03', channel: 'email' }, audienceConfirmed: true, now: NOW, ...extra })
|
||||
|
||||
test('K18 Mitarbeiter-Weg: in Textform am 03.10.2026 per E-Mail', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'p20', customerId: 'c1', projectName: 'Textform', subdomain: 'textform', isPublic: true, repoFullName: 'WEBklar/textform' })
|
||||
await an.activateAnalyse({ customerId: 'c1', projectId: 'p20', ...staff() })
|
||||
assert.equal(mails.sent.length, 1)
|
||||
assert.match(mails.sent[0].html, /in Textform am 03\.10\.2026 per E-Mail/)
|
||||
assert.doesNotMatch(mails.sent[0].html, /Uhr/)
|
||||
})
|
||||
|
||||
test('K18: PDFs nicht lesbar => keine Mail, Marker leer, Aktivierung gültig, spätere Wiederholung sendet', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'p21', customerId: 'c1', projectName: 'OhnePdf', subdomain: 'ohnepdf', isPublic: true, repoFullName: 'WEBklar/ohnepdf' })
|
||||
const { legalPdfAttachments } = await import('../server/services/analyseLegal.js')
|
||||
const ok = an.analyseDeps.legalPdfs
|
||||
an.analyseDeps.legalPdfs = () => legalPdfAttachments('1999-01-01')
|
||||
const r = await an.activateAnalyse({ customerId: 'c1', projectId: 'p21', ...cust() })
|
||||
assert.equal(r.doc.state, 'active')
|
||||
assert.equal(r.doc.activatedMailAt, '')
|
||||
assert.equal(mails.sent.length, 0)
|
||||
an.analyseDeps.legalPdfs = ok
|
||||
const again = await an.activateAnalyse({ customerId: 'c1', projectId: 'p21', ...cust() })
|
||||
assert.ok(again.doc.activatedMailAt)
|
||||
assert.equal(mails.sent.length, 1)
|
||||
})
|
||||
|
||||
test('Entfernen-Ticket: Kunde, Mitarbeiter, zweimal = ein Ticket, Aus/An/Aus = zwei', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'p22', customerId: 'c1', projectName: 'Bäckerei Aus', subdomain: 'baeckereiaus', isPublic: true, repoFullName: 'WEBklar/aus' })
|
||||
const removal = () => topics().filter((t) => t === 'Website-Analyse von Bäckerei Aus entfernen')
|
||||
await an.activateAnalyse({ customerId: 'c1', projectId: 'p22', ...cust() })
|
||||
assert.equal(removal().length, 0)
|
||||
const off1 = await an.deactivateAnalyse({ customerId: 'c1', projectId: 'p22', now: NOW, actor })
|
||||
assert.ok(off1.doc.removalTicketId)
|
||||
await an.deactivateAnalyse({ customerId: 'c1', projectId: 'p22', now: NOW, actor })
|
||||
assert.equal(removal().length, 1)
|
||||
const ticket = [...aw.docs('workorders').values()].find((t) => t.topic === 'Website-Analyse von Bäckerei Aus entfernen')
|
||||
assert.match(ticket.details, /fünf Werktagen/)
|
||||
assert.match(ticket.details, /Stundenkontingent/)
|
||||
await an.activateAnalyse({ customerId: 'c1', projectId: 'p22', ...staff() })
|
||||
const off2 = await an.deactivateAnalyse({ customerId: 'c1', projectId: 'p22', now: NOW, by: 'Kenso' })
|
||||
assert.equal(removal().length, 2)
|
||||
assert.notEqual(off2.doc.removalTicketId, off1.doc.removalTicketId)
|
||||
})
|
||||
|
||||
test('Entfernen-Ticket: Ticketfehler verhindert das Ausschalten nicht', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'p23', customerId: 'c1', projectName: 'TicketFehler', subdomain: 'ticketfehler', isPublic: true, repoFullName: 'WEBklar/tf' })
|
||||
await an.activateAnalyse({ customerId: 'c1', projectId: 'p23', ...cust() })
|
||||
const original = an.analyseDeps.ticket
|
||||
an.analyseDeps.ticket = async () => { throw new Error('Ticketsystem down') }
|
||||
const r = await an.deactivateAnalyse({ customerId: 'c1', projectId: 'p23', now: NOW, actor })
|
||||
an.analyseDeps.ticket = original
|
||||
assert.equal(r.doc.state, 'off')
|
||||
assert.equal(r.changed, true)
|
||||
})
|
||||
|
||||
test('Abo-Ende über runAboCheck: Entfernen-Ticket genau einmal', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'p24', customerId: 'c3', projectName: 'Abo Ende', subdomain: 'aboende', isPublic: true, repoFullName: 'WEBklar/aboende' })
|
||||
const d = await sf.ensureFeatureDoc('c3', 'p24', 'analyse')
|
||||
await updateDocument(config.collections.portalFeatures, d.$id, { ...sf.stateFields('active'), accentColor: '#111111' })
|
||||
await an.runAboCheck({ now: NOW })
|
||||
await an.runAboCheck({ now: NOW })
|
||||
assert.equal(topics().filter((t) => t === 'Website-Analyse von Abo Ende entfernen').length, 1)
|
||||
})
|
||||
|
||||
test('rebuildSafe-Ergebnis: kein_repo, preview_aus, fehler => Veröffentlichen-Ticket; Erfolg => keins', async () => {
|
||||
const cases = [
|
||||
['p30', 'KeinRepo', {}, 'kein_repo'],
|
||||
['p31', 'PreviewAus', { repoFullName: 'WEBklar/pa', previewMode: 'off' }, 'preview_aus'],
|
||||
['p32', 'BauFehler', { repoFullName: 'WEBklar/bf' }, 'fehler'],
|
||||
['p33', 'BauErgebnisFehler', { repoFullName: 'WEBklar/bef' }, 'fehler'],
|
||||
['p34', 'BauOk', { repoFullName: 'WEBklar/bo' }, null],
|
||||
]
|
||||
const original = an.analyseDeps.rebuild
|
||||
for (const [id, name, extra, reason] of cases) {
|
||||
aw.seed('websiteProjects', { $id: id, customerId: 'c1', projectName: name, subdomain: name.toLowerCase(), isPublic: true, ...extra })
|
||||
an.analyseDeps.rebuild = async (p) => {
|
||||
rebuilds.push(p.$id)
|
||||
if (id === 'p32') throw new Error('Runner weg')
|
||||
if (id === 'p33') return { error: 'Build kaputt' }
|
||||
return { ok: true }
|
||||
}
|
||||
rebuilds.length = 0
|
||||
const r = await an.activateAnalyse({ customerId: 'c1', projectId: id, ...cust() })
|
||||
const pub = [...aw.docs('workorders').values()].filter((t) => t.topic === `Website-Analyse von ${name} veröffentlichen`)
|
||||
assert.equal(pub.length, reason ? 1 : 0, name)
|
||||
if (reason) {
|
||||
assert.equal(r.doc.publishTicketId, pub[0].$id)
|
||||
assert.ok(pub[0].details.includes(reason === 'kein_repo' ? 'kein Repo' : reason === 'preview_aus' ? 'ausgeschaltet' : 'fehlgeschlagen'), name)
|
||||
const privacy = [...aw.docs('workorders').values()].find((t) => t.topic === `Datenschutzerklärung von ${name} um Hinweis auf Analyse ergänzen`)
|
||||
assert.match(privacy.details, /Veröffentlichung:/)
|
||||
}
|
||||
if (id === 'p30' || id === 'p31') assert.deepEqual(rebuilds, [], 'Rebuild-Stub nicht aufgerufen')
|
||||
await an.activateAnalyse({ customerId: 'c1', projectId: id, ...cust() })
|
||||
assert.equal([...aw.docs('workorders').values()].filter((t) => t.topic === `Website-Analyse von ${name} veröffentlichen`).length, reason ? 1 : 0, 'genau eines je Aktivierung')
|
||||
}
|
||||
an.analyseDeps.rebuild = original
|
||||
})
|
||||
Reference in new issue
Block a user