Analyse: Einwilligungs-Historie, Nachweis beim Einschalten, Sperre ohne Verantwortlichen-Angaben, Textform für Mitarbeiter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c6824f83a6
commit
3d05472b25
14 files changed
+585
-115
No files matched your search
+60
-14
@@ -6,14 +6,15 @@ import { useTestEnv } from './support/testEnv.js'
|
||||
import { schemaKeys } from './support/schema.js'
|
||||
import { mailSink } from './support/mailSink.js'
|
||||
|
||||
const ANALYSE_KEYS = ['accentColor', 'avvVersion', 'avvAcceptedAt', 'avvAcceptedBy', 'activatedAt', 'offSince', 'privacyTicketId', 'activatedMailAt']
|
||||
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 aw = await startAppwriteMock({
|
||||
schema: { portalFeatures: [...schemaKeys('portalFeatures'), ...ANALYSE_KEYS], subscriptions: schemaKeys('subscriptions') },
|
||||
schema: { portalFeatures: [...schemaKeys('portalFeatures'), ...ANALYSE_KEYS], subscriptions: schemaKeys('subscriptions'), analyseConsents: CONSENT_KEYS },
|
||||
})
|
||||
const st = await startFakeStripe()
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||
const { config } = await import('../server/config.js')
|
||||
const { startTestApp, get, rawGet, post } = await import('./support/testApp.js')
|
||||
const { startTestApp, get, rawGet, post, sessionCookie } = await import('./support/testApp.js')
|
||||
const { customerCookie, wartungDoc, seedFixtures } = await import('./support/fixtures.js')
|
||||
const { default: funktionenRoutes } = await import('../server/routes/funktionen.js')
|
||||
const { default: adminAnalyseRoutes } = await import('../server/routes/admin/analyse.js')
|
||||
@@ -46,6 +47,8 @@ an.analyseDeps.client = {
|
||||
an.analyseDeps.rebuild = async () => {}
|
||||
config.analyse.enabled = true
|
||||
config.analyse.heatSecret = 'h'.repeat(32)
|
||||
// Die Fassung 2026-10-02 liegt (je nach Uhr) noch in der Zukunft: Textform-Mindestdatum fuer die Tests auf frueh setzen
|
||||
config.analyse.avvVersion = '2000-01-01'
|
||||
|
||||
seedFixtures(aw)
|
||||
aw.seed('subscriptions', wartungDoc)
|
||||
@@ -60,6 +63,9 @@ const cookie = customerCookie('c1')
|
||||
const A = '/api/funktionen/analyse'
|
||||
const consent = () => ({ accepted: true, avvVersion: config.analyse.avvVersion })
|
||||
const adminPost = (id, body, jwt = 'jwt-admin') => post(`/api/admin/analyse/${id}`, body, { headers: { Authorization: `Bearer ${jwt}` } })
|
||||
const todayBerlin = () => new Intl.DateTimeFormat('sv-SE', { timeZone: 'Europe/Berlin' }).format(new Date())
|
||||
const tf = (extra = {}) => ({ textformDate: todayBerlin(), textformChannel: 'chat', audienceConfirmed: true, ...extra })
|
||||
const consentsOf = (p) => [...aw.docs('analyseConsents').values()].filter((d) => d.projectId === p).sort((a, b) => a.$seq - b.$seq)
|
||||
const featureDoc = (c, p) => sf.getFeatureDoc(c, p, 'analyse')
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -89,7 +95,7 @@ test('Schalter aus: alle Analyse-Routen 409', async () => {
|
||||
post(`${A}/deactivate`, { projectId: 'p1' }, { cookie }),
|
||||
rawGet(`${A}/stats?projectId=p1`, { cookie }),
|
||||
post(`${A}/heatmap-link`, { projectId: 'p1', path: '/', vp: 'd' }, { cookie }),
|
||||
adminPost('p1', { on: true, avvConfirmedVerbally: true }),
|
||||
adminPost('p1', { on: true, ...tf() }),
|
||||
]) {
|
||||
const res = await r
|
||||
assert.equal(res.status, 409)
|
||||
@@ -102,7 +108,7 @@ test('GET /api/funktionen: analyse-Zustand je Projekt, mit und ohne Schalter', a
|
||||
const body = await get('/api/funktionen', { cookie })
|
||||
const p = (id) => body.projects.find((x) => x.projectId === id)
|
||||
assert.equal(p('p1').features.news.state, 'off')
|
||||
assert.deepEqual(p('p1').analyse, { state: 'off', devMode: false, accentColor: '#111111', eligible: true, reason: null, reasonText: null, avvVersion: config.analyse.avvVersion })
|
||||
assert.deepEqual(p('p1').analyse, { state: 'off', devMode: false, accentColor: '#111111', eligible: true, reason: null, reasonText: null, avvVersion: config.analyse.avvVersion, controller: { company: 'Kunde Eins', address: '', email: 'k@example.invalid', ok: true, missing: [] } })
|
||||
assert.equal(p('p2').analyse.eligible, false)
|
||||
assert.equal(p('p2').analyse.reason, 'nicht_oeffentlich')
|
||||
assert.equal(p('p2').analyse.reasonText, an.ELIGIBILITY_MESSAGES.nicht_oeffentlich)
|
||||
@@ -189,18 +195,34 @@ test('Heatmap-Link: Prüfung, URL, Token', async () => {
|
||||
|
||||
test('Admin: 401, 400, 200', async () => {
|
||||
assert.equal((await post('/api/admin/analyse/p6', { on: true })).status, 401)
|
||||
assert.equal((await adminPost('p6', { on: true, avvConfirmedVerbally: true }, 'jwt-kunde')).status, 403)
|
||||
const noAvv = await adminPost('p6', { on: true })
|
||||
assert.equal(noAvv.status, 400)
|
||||
assert.equal(noAvv.body.error, 'Bitte bestätigen, dass der Kunde den AVV bestätigt hat')
|
||||
assert.equal((await adminPost('p6', { on: true, ...tf() }, 'jwt-kunde')).status, 403)
|
||||
const noTf = await adminPost('p6', { on: true, audienceConfirmed: true, textformChannel: 'chat' })
|
||||
assert.equal(noTf.status, 400)
|
||||
assert.equal((await adminPost('p6', { on: true, ...tf({ textformDate: undefined }) })).status, 400)
|
||||
assert.equal((await adminPost('p6', { on: true, ...tf({ textformChannel: undefined }) })).status, 400)
|
||||
assert.equal((await adminPost('p6', { on: true, ...tf({ textformChannel: 'telefon' }) })).status, 400)
|
||||
assert.equal((await adminPost('p6', { on: true, ...tf({ textformDate: '1999-01-01' }) })).status, 400, 'vor der Fassung')
|
||||
assert.equal((await adminPost('p6', { on: true, ...tf({ audienceConfirmed: undefined }) })).status, 400)
|
||||
const old = await adminPost('p6', { on: true, avvConfirmedVerbally: true, ...tf() })
|
||||
assert.equal(old.status, 400)
|
||||
assert.equal(old.body.error, 'Zustimmung in Textform nötig')
|
||||
assert.equal(await featureDoc('c1', 'p6'), null, 'nichts angelegt')
|
||||
assert.equal((await adminPost('p6', { on: 'ja' })).status, 400)
|
||||
assert.equal((await adminPost('nix', { on: true, avvConfirmedVerbally: true })).status, 404)
|
||||
const on = await adminPost('p6', { on: true, avvConfirmedVerbally: true })
|
||||
assert.equal((await adminPost('nix', { on: true, ...tf() })).status, 404)
|
||||
const on = await adminPost('p6', { on: true, ...tf() })
|
||||
assert.equal(on.status, 200)
|
||||
assert.deepEqual(on.body, { ok: true, state: 'active' })
|
||||
const d = await featureDoc('c1', 'p6')
|
||||
assert.equal(d.avvAcceptedBy, 'kenso@webklar.com')
|
||||
assert.equal(d.avvVersion, config.analyse.avvVersion)
|
||||
assert.equal(d.avvConsentDate, todayBerlin())
|
||||
assert.equal(d.avvConsentChannel, 'chat')
|
||||
const h = consentsOf('p6')
|
||||
assert.equal(h.length, 1)
|
||||
assert.equal(h[0].via, 'woms')
|
||||
assert.equal(h[0].acceptedBy, 'kenso@webklar.com')
|
||||
assert.equal(h[0].textformDate, todayBerlin())
|
||||
assert.equal(h[0].textformChannel, 'chat')
|
||||
const off = await adminPost('p6', { on: false })
|
||||
assert.deepEqual(off.body, { ok: true, state: 'off' })
|
||||
})
|
||||
@@ -217,9 +239,12 @@ test('Admin: Lesezugriff GET', async () => {
|
||||
assert.equal(off.body.eligible, true)
|
||||
assert.equal(off.body.reasonText, null)
|
||||
assert.equal(off.body.avvVersion, config.analyse.avvVersion)
|
||||
await adminPost('p1', { on: true, avvConfirmedVerbally: true })
|
||||
await adminPost('p1', { on: true, ...tf() })
|
||||
const on = await adminGet('p1')
|
||||
assert.equal(on.body.state, 'active')
|
||||
assert.equal(on.body.avvConsentDate, todayBerlin())
|
||||
assert.equal(on.body.avvConsentChannel, 'chat')
|
||||
assert.deepEqual(on.body.privacy, { ok: true, missing: [] })
|
||||
assert.equal(on.body.avvAcceptedBy, 'kenso@webklar.com')
|
||||
assert.ok(on.body.activatedAt)
|
||||
assert.ok(!('siteKey' in on.body) && !JSON.stringify(on.body).includes((await featureDoc('c1', 'p1')).siteKey))
|
||||
@@ -230,7 +255,7 @@ test('Admin: Lesezugriff GET', async () => {
|
||||
})
|
||||
|
||||
test('Fix a: Kunde ändert gesperrtes Dokument nicht, Mitarbeiter schon', async () => {
|
||||
await adminPost('p6', { on: true, avvConfirmedVerbally: true })
|
||||
await adminPost('p6', { on: true, ...tf() })
|
||||
const cur = await featureDoc('c1', 'p6')
|
||||
await sf.disable(cur.$id, { by: 'Test' })
|
||||
puts.length = 0
|
||||
@@ -242,7 +267,7 @@ test('Fix a: Kunde ändert gesperrtes Dokument nicht, Mitarbeiter schon', async
|
||||
})
|
||||
|
||||
test('Fix b: Abschalten durch Mitarbeiter meldet Sammeldienst active:false', async () => {
|
||||
await adminPost('p6', { on: true, avvConfirmedVerbally: true })
|
||||
await adminPost('p6', { on: true, ...tf() })
|
||||
const cur = await featureDoc('c1', 'p6')
|
||||
puts.length = 0
|
||||
await sf.disable(cur.$id, { by: 'Test' })
|
||||
@@ -275,3 +300,24 @@ test('Härtung: ungültige Hosts, Pfadzeichen, Fehler bei einem Projekt', async
|
||||
assert.equal(bad.body.error, 'Keine gültige Domain für die Analyse')
|
||||
assert.equal(puts.length, before)
|
||||
})
|
||||
|
||||
test('Kunden-Aktivierung schreibt Historie mit Sitzungsnamen; fehlende Angaben: controller + 409', async () => {
|
||||
aw.seed('customers', { $id: 'c9', appwriteUserId: 'u9', email: 'neun', name: '', status: 'active', portalAccessEnabled: true })
|
||||
aw.seed('websiteProjects', { $id: 'p9', customerId: 'c9', projectName: 'Leer', subdomain: 'leer', isPublic: true })
|
||||
aw.seed('subscriptions', { ...wartungDoc, $id: 'sub_w_c9', stripeSubscriptionId: 'sub_w_c9', customerId: 'c9' })
|
||||
const c9 = sessionCookie({ customerId: 'c9', appwriteUserId: 'u9', name: 'Neun', email: 'neun' })
|
||||
const view = (await get('/api/funktionen', { cookie: c9 })).projects.find((x) => x.projectId === 'p9').analyse
|
||||
assert.deepEqual(view.controller, { company: '', address: '', email: 'neun', ok: false, missing: ['firma', 'email'] })
|
||||
const r = await post(`${A}/activate`, { projectId: 'p9', consent: consent() }, { cookie: c9 })
|
||||
assert.equal(r.status, 409)
|
||||
assert.match(r.body.error, /fehlen in Deinen Kundendaten Firma oder Name/)
|
||||
assert.equal(consentsOf('p9').length, 0)
|
||||
const before = consentsOf('p3').length
|
||||
await post(`${A}/activate`, { projectId: 'p3', consent: consent() }, { cookie })
|
||||
const h = consentsOf('p3')
|
||||
assert.equal(h.length, before + 1)
|
||||
assert.equal(h.at(-1).acceptedBy, 'Kunde Eins')
|
||||
assert.equal(h.at(-1).via, 'portal')
|
||||
const admin = (await rawGet('/api/admin/analyse/p9', { headers: { Authorization: 'Bearer jwt-admin' } })).body
|
||||
assert.deepEqual(admin.privacy, { ok: false, missing: ['firma', 'email'] })
|
||||
})
|
||||
Reference in new issue
Block a user