Analyse: Zielgruppen-Bestätigung in Historie, Rücknahme bei Fehler nach Dokument-Update, handelnde Person mit Name und E-Mail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-01 18:50:36 +00:00
1 parent 3d05472b25
commit f401dc14d7
7 files changed
+108 -56

No files matched your search

+19 -11
View File
@@ -7,8 +7,8 @@ import { schemaKeys } from './support/schema.js'
import { mailSink } from './support/mailSink.js'
// Das Setup-Skript laedt config.js: erst nach useTestEnv() importieren, daher die Attributnamen hier fest
const ANALYSE_KEYS = ['accentColor', 'avvVersion', 'avvAcceptedAt', 'avvAcceptedBy', 'activatedAt', 'offSince', 'privacyTicketId', 'activatedMailAt', 'avvConsentDate', 'avvConsentChannel', 'removalTicketId', 'publishTicketId']
const CONSENT_KEYS = ['customerId', 'projectId', 'siteKey', 'event', 'avvVersion', 'via', 'acceptedAt', 'acceptedBy', 'textformDate', 'textformChannel']
const ANALYSE_KEYS = ['accentColor', 'avvVersion', 'avvAcceptedAt', 'avvAcceptedBy', 'activatedAt', 'offSince', 'privacyTicketId', 'activatedMailAt', 'avvConsentDate', 'avvConsentChannel', 'removalTicketId', 'publishTicketId', 'audienceConfirmed', 'audienceTextVersion']
const CONSENT_KEYS = ['customerId', 'projectId', 'siteKey', 'event', 'avvVersion', 'via', 'acceptedAt', 'acceptedBy', 'textformDate', 'textformChannel', 'audienceConfirmed', 'audienceTextVersion']
const aw = await startAppwriteMock({
schema: {
portalFeatures: [...schemaKeys('portalFeatures'), ...ANALYSE_KEYS],
@@ -101,7 +101,7 @@ test('Einschalten: Dokument, Sammeldienst, Neubau, Ticket, Mail K18', async () =
assert.match(d.siteKey, /^[a-z0-9]{16,40}$/)
assert.equal(d.accentColor, '#1a73e8')
assert.equal(d.avvVersion, config.analyse.avvVersion)
assert.equal(d.avvAcceptedBy, 'Kunde Eins')
assert.equal(d.avvAcceptedBy, 'Kunde Eins <eins@example.invalid>')
assert.ok(d.avvAcceptedAt && d.activatedAt)
assert.deepEqual(puts, [{ siteKey: d.siteKey, projectId: 'p1', domains: ['baeckerei.project.webklar.com', 'baeckerei.de', 'www.baeckerei.de'], active: true }])
assert.deepEqual(rebuilds, ['p1'])
@@ -149,7 +149,7 @@ test('Ausschalten: off, offSince, Sammeldienst inaktiv, Neubau; erneut = nichts'
test('Wieder einschalten: gleicher siteKey, offSince leer, neues Ticket und Historie', async () => {
const before = await doc('c1', 'p1')
const { doc: d } = await an.activateAnalyse({ customerId: 'c1', projectId: 'p1', consent: consent(), by: 'Kenso', textform, now: NOW })
const { doc: d } = await an.activateAnalyse({ customerId: 'c1', projectId: 'p1', consent: consent(), by: 'Kenso', textform, audienceConfirmed: true, now: NOW })
assert.equal(d.siteKey, before.siteKey)
assert.equal(d.state, 'active')
assert.equal(d.offSince, '')
@@ -257,7 +257,7 @@ test('gesperrte Analyse: Kunde 403, Mitarbeiter darf', async () => {
const g = await sf.ensureFeatureDoc('c1', 'p10', 'analyse')
await sf.disable(g.$id, {})
await assert.rejects(an.activateAnalyse({ customerId: 'c1', projectId: 'p10', ...cust() }), { status: 403, message: 'Analyse wurde gesperrt – bitte melde dich bei uns' })
const r = await an.activateAnalyse({ customerId: 'c1', projectId: 'p10', consent: consent(), by: 'Kenso', textform, now: NOW })
const r = await an.activateAnalyse({ customerId: 'c1', projectId: 'p10', consent: consent(), by: 'Kenso', textform, audienceConfirmed: true, now: NOW })
assert.equal(r.doc.state, 'active')
})
@@ -326,7 +326,8 @@ test('Kunde ohne actor-Namen oder Mitarbeiter ohne Textform: 400', async () => {
await assert.rejects(an.activateAnalyse({ customerId: 'c1', projectId: 'p13', consent: consent() }), { status: 400 })
await assert.rejects(an.activateAnalyse({ customerId: 'c1', projectId: 'p13', consent: consent(), actor: {} }), { status: 400 })
await assert.rejects(an.activateAnalyse({ customerId: 'c1', projectId: 'p13', consent: consent(), by: 'Kenso', now: NOW }), { status: 400 })
await assert.rejects(an.activateAnalyse({ customerId: 'c1', projectId: 'p13', by: 'Kenso', textform: { date: '2026-10-01', channel: 'chat' }, now: NOW }), { status: 400 })
await assert.rejects(an.activateAnalyse({ customerId: 'c1', projectId: 'p13', by: 'Kenso', textform, now: NOW }), { status: 400 }) // ohne Zielgruppen-Bestätigung
await assert.rejects(an.activateAnalyse({ customerId: 'c1', projectId: 'p13', by: 'Kenso', audienceConfirmed: true, textform: { date: '2026-10-01', channel: 'chat' }, now: NOW }), { status: 400 })
assert.equal(puts.length, 0)
assert.equal((await consents('p13')).length, 0)
})
@@ -339,12 +340,16 @@ test('Historie: Kunde, Aus, An durch Mitarbeiter in zeitlicher Folge; Portal-Dok
assert.equal(h[0].event, 'on')
assert.equal(h[0].via, 'portal')
assert.equal(h[0].avvVersion, '2026-10-02')
assert.equal(h[0].acceptedBy, 'Kunde Eins')
assert.equal(h[0].acceptedBy, 'Kunde Eins <eins@example.invalid>')
assert.equal(h[0].customerId, 'c1')
assert.equal(h[0].siteKey, d1.siteKey)
assert.match(h[0].acceptedAt, /^\d{4}-\d\d-\d\dT[\d:.]+Z$/)
assert.equal(h[0].acceptedAt, d1.avvAcceptedAt)
assert.equal(d1.avvAcceptedBy, 'Kunde Eins')
assert.equal(d1.avvAcceptedBy, 'Kunde Eins <eins@example.invalid>')
assert.equal(d1.audienceConfirmed, true)
assert.equal(d1.audienceTextVersion, '2026-10-02')
assert.equal(h[0].audienceConfirmed, true)
assert.equal(h[0].audienceTextVersion, '2026-10-02')
assert.equal(d1.avvConsentDate, '')
assert.equal(d1.avvConsentChannel, '')
// Farbwechsel und erneutes Aktivieren: keine neue Zeile
@@ -354,17 +359,19 @@ test('Historie: Kunde, Aus, An durch Mitarbeiter in zeitlicher Folge; Portal-Dok
// Marker zurücksetzen beim Wiedereinschalten
await updateDocument(config.collections.portalFeatures, d1.$id, { removalTicketId: 'r1', publishTicketId: 'u1' })
await an.deactivateAnalyse({ customerId: 'c1', projectId: 'p14', now: NOW, actor })
const { doc: d3 } = await an.activateAnalyse({ customerId: 'c1', projectId: 'p14', consent: consent(), by: 'Kenso', textform, now: NOW })
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, '')
h = await consents('p14')
assert.deepEqual(h.map((x) => x.event), ['on', 'off', 'on'])
assert.deepEqual(h.map((x) => x.via), ['portal', 'portal', 'woms'])
assert.equal(h[1].acceptedBy, 'Kunde Eins')
assert.equal(h[1].acceptedBy, 'Kunde Eins <eins@example.invalid>')
assert.equal(h[2].acceptedBy, 'Kenso')
assert.equal(h[2].textformDate, '2026-10-02')
assert.equal(h[2].textformChannel, 'email')
assert.equal(h[2].audienceConfirmed, true)
assert.equal(d3.audienceConfirmed, true)
assert.equal(d3.avvConsentDate, '2026-10-02')
assert.equal(d3.avvAcceptedBy, 'Kenso')
})
@@ -382,7 +389,8 @@ test('recordConsent scheitert: putSite wird zurückgenommen, Dokument bleibt aus
assert.equal(puts.length, 2)
assert.equal(puts[0].active, true)
assert.equal(puts[1].active, false)
assert.equal((await doc('c1', 'p15')).state, 'off')
assert.equal((await doc('c1', 'p15')).state, 'off', 'Dokument zurückgesetzt')
assert.equal((await doc('c1', 'p15')).offSince, '')
assert.equal(analyseTickets().filter((t) => t.details.includes('Rollback')).length, 0)
})