TP2 Abschlussprüfung: Löschung (IP/Browser auf portalFeatures, Erstattungsspur, K22-Datum)
- markOrdered speichert IP und Browser der Bestellung nicht mehr am Funktionsdokument (nur im Nachweis featureConsents, dort mit Löschfrist); Altwerte werden bei einer neuen Bestellung geleert - Aufbewahrungslauf (retention): consentIp, consentUserAgent, consentText, consentAt werden an allen Funktionsdokumenten eines fälligen Kunden geleert, auch an abgeschalteten; Fehlschlag hält den Kunden wie die übrigen abhängigen Daten - Einmal-Skript scripts/feature-consent-felder-leeren.mjs (Standard Probelauf, --apply leert nur, wo featureConsents die Angabe trägt) - 30-Tage-Reset (jobs.js): auch eine bezahlte Einrichtung mit Grund abo_ende_vor_einrichtung und noch nicht gemeldetem T19 wird nicht zurückgesetzt, solange zahlungMeldung nicht gemeldet ist (die Spur der Erstattung bleibt) - K22: enddatum ist das Abo-Ende (lage.endedAt), sonst der Tag des Abschaltens; loeschdatum bleibt 30 Tage nach dem Abschalten Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b974b3a3ec
commit
2fc48df915
13 files changed
+425
-22
No files matched your search
@@ -0,0 +1,100 @@
|
||||
import { test, after } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||||
import { useTestEnv } from './support/testEnv.js'
|
||||
import { schemaKeys } from './support/schema.js'
|
||||
|
||||
// Einmal-Skript scripts/feature-consent-felder-leeren.mjs (C1): Probelauf als Standard, --apply leert IP und Browser nur dort, wo
|
||||
// featureConsents sie traegt. Nur Nachbauten, nie Produktion.
|
||||
const aw = await startAppwriteMock({ schema: { portalFeatures: schemaKeys('portalFeatures'), featureConsents: schemaKeys('featureConsents') } })
|
||||
after(() => aw.close())
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
|
||||
const { run, parseArgs } = await import('../scripts/feature-consent-felder-leeren.mjs')
|
||||
|
||||
const feature = (id, over = {}) =>
|
||||
aw.seed('portalFeatures', {
|
||||
$id: id, customerId: 'c1', projectId: `p_${id}`, featureKey: 'termine', state: 'active', enabled: true, siteKey: `k_${id}`,
|
||||
stripeSessionId: `cs_${id}`, consentText: 'Text', consentAt: '2026-10-01T10:00:00.000Z', consentIp: '203.0.113.7', consentUserAgent: 'UA', ...over,
|
||||
})
|
||||
const nachweis = (id, over = {}) =>
|
||||
aw.seed('featureConsents', {
|
||||
$id: `n_${id}`, customerId: 'c1', projectId: `p_${id}`, featureKey: 'termine', stripeSessionId: `cs_${id}`, acceptedAt: '2026-10-01T10:00:00.000Z',
|
||||
fassungen: '{}', texte: '[]', textHash: 'x', ip: '203.0.113.7', userAgent: 'UA', konto: 'k@example.invalid', ...over,
|
||||
})
|
||||
const angaben = (id) => {
|
||||
const d = aw.docs('portalFeatures').get(id)
|
||||
return [d.consentIp, d.consentUserAgent, d.consentText, d.consentAt, d.siteKey]
|
||||
}
|
||||
const stumm = () => {
|
||||
const zeilen = []
|
||||
return { log: (z) => zeilen.push(z), zeilen }
|
||||
}
|
||||
|
||||
feature('f_ok1'); nachweis('f_ok1') // IP und Browser im Nachweis
|
||||
feature('f_ok2', { consentUserAgent: '' }); nachweis('f_ok2') // nur IP am Dokument, im Nachweis vorhanden
|
||||
feature('f_alt'); // Altbestand: kein Nachweis
|
||||
feature('f_ohne_ip_im_nachweis'); nachweis('f_ohne_ip_im_nachweis', { ip: '' }) // Nachweis ohne IP: traegt sie nicht
|
||||
feature('f_keine_sitzung', { stripeSessionId: '' }) // ohne Sitzung kein Nachweis
|
||||
feature('f_leer', { consentIp: '', consentUserAgent: '' }); nachweis('f_leer') // nichts zu leeren
|
||||
aw.seed('portalFeatures', { $id: 'f_n', customerId: 'c2', projectId: 'p_n', featureKey: 'news', state: 'off', enabled: false }) // Feld nie gesetzt
|
||||
|
||||
test('parseArgs: ohne Argument und mit --dry-run ist es der Probelauf, nur --apply schreibt, Unbekanntes und beides zusammen ist ein Fehler', () => {
|
||||
assert.deepEqual(parseArgs([]), { apply: false })
|
||||
assert.deepEqual(parseArgs(['--dry-run']), { apply: false })
|
||||
assert.deepEqual(parseArgs(['--apply']), { apply: true })
|
||||
assert.throws(() => parseArgs(['--apply', '--dry-run']), /schliessen sich aus/)
|
||||
assert.throws(() => parseArgs(['--force']), /Unbekanntes Argument: --force/)
|
||||
})
|
||||
|
||||
test('Probelauf (Standard) zaehlt und listet, schreibt aber nichts', async () => {
|
||||
const vorher = JSON.stringify([...aw.docs('portalFeatures')])
|
||||
const out = stumm()
|
||||
const erg = await run({ log: out.log })
|
||||
assert.equal(erg.applied, false)
|
||||
assert.equal(erg.total, 7)
|
||||
assert.equal(erg.withData, 5, 'f_ok1, f_ok2, f_alt, f_ohne_ip_im_nachweis, f_keine_sitzung')
|
||||
assert.deepEqual(erg.clearable.sort(), ['f_ok1', 'f_ok2'])
|
||||
assert.deepEqual(erg.kept.sort(), ['f_alt', 'f_keine_sitzung', 'f_ohne_ip_im_nachweis'])
|
||||
assert.equal(erg.cleared, 0)
|
||||
assert.equal(JSON.stringify([...aw.docs('portalFeatures')]), vorher, 'nichts geschrieben')
|
||||
assert.match(out.zeilen.join('\n'), /Probelauf \(--dry-run\): es wird nichts geschrieben/)
|
||||
assert.match(out.zeilen.join('\n'), /zu leeren .*: 2/)
|
||||
})
|
||||
|
||||
test('--apply leert consentIp und consentUserAgent nur dort, wo der Nachweis sie traegt; Text, Zeit und alles andere bleiben', async () => {
|
||||
const out = stumm()
|
||||
const erg = await run({ apply: true, log: out.log })
|
||||
assert.equal(erg.applied, true)
|
||||
assert.equal(erg.cleared, 2)
|
||||
assert.deepEqual(erg.failed, [])
|
||||
assert.deepEqual(angaben('f_ok1').filter((_, i) => i !== 3), ['', '', 'Text', 'k_f_ok1'])
|
||||
assert.ok(aw.docs('portalFeatures').get('f_ok1').consentAt, 'consentAt bleibt')
|
||||
assert.deepEqual(angaben('f_ok2').slice(0, 2), ['', ''])
|
||||
for (const id of ['f_alt', 'f_ohne_ip_im_nachweis', 'f_keine_sitzung']) assert.equal(aw.docs('portalFeatures').get(id).consentIp, '203.0.113.7', `${id} bleibt`)
|
||||
// ein zweiter Lauf findet nichts mehr zu leeren
|
||||
const nochmal = await run({ apply: true, log: () => {} })
|
||||
assert.deepEqual(nochmal.clearable, [])
|
||||
assert.equal(nochmal.cleared, 0)
|
||||
})
|
||||
|
||||
test('--apply: ein Schreibfehler steht im Ergebnis, die uebrigen Dokumente werden trotzdem geleert; ein nicht lesbarer Nachweis bleibt unberuehrt', async () => {
|
||||
feature('f_x1'); nachweis('f_x1')
|
||||
feature('f_x2'); nachweis('f_x2')
|
||||
aw.failNext({ collection: 'portalFeatures', method: 'PATCH', status: 500, message: 'kaputt' })
|
||||
const erg = await run({ apply: true, log: () => {} })
|
||||
assert.equal(erg.failed.length, 1)
|
||||
assert.match(erg.failed[0].reason, /kaputt/)
|
||||
assert.equal(erg.cleared, 1)
|
||||
const offen = ['f_x1', 'f_x2'].filter((id) => aw.docs('portalFeatures').get(id).consentIp)
|
||||
assert.deepEqual(offen, [erg.failed[0].id])
|
||||
|
||||
assert.equal((await run({ apply: true, log: () => {} })).cleared, 1, 'der naechste Lauf holt das fehlgeschlagene Dokument nach')
|
||||
|
||||
feature('f_a_y'); nachweis('f_a_y')
|
||||
aw.failNext({ collection: 'featureConsents', method: 'GET', status: 500, message: 'Nachweis kaputt' })
|
||||
const out = stumm()
|
||||
const lesefehler = await run({ apply: true, log: out.log })
|
||||
assert.deepEqual(lesefehler.unreadable, ['f_a_y'])
|
||||
assert.equal(lesefehler.cleared, 0, 'nicht lesbar heisst nicht leeren')
|
||||
assert.equal(aw.docs('portalFeatures').get('f_a_y').consentIp, '203.0.113.7')
|
||||
})
|
||||
Reference in new issue
Block a user