375 lines
20 KiB
JavaScript
375 lines
20 KiB
JavaScript
import { test, after, beforeEach } 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'
|
||
|
||
// Buchungskalender Task 10: Aufbewahrung, Fristen, Loeschen (aufbewahrung.js, Tagesjob, Kundenloeschung). Die Uhr ist immer ein fester Zeitpunkt.
|
||
const NAMEN = ['ukUnterkuenfte', 'ukPreise', 'ukSperren', 'ukAnfragen', 'ukRechnungen', 'ukEinstellungen']
|
||
const aw = await startAppwriteMock({
|
||
schema: { ...Object.fromEntries(NAMEN.map((n) => [n, schemaKeys(n)])), portalFeatures: schemaKeys('portalFeatures'), termineOutbox: schemaKeys('termineOutbox') },
|
||
datetimes: { ukAnfragen: ['fristAt', 'freigegebenAt', 'gebuchtAt', 'abgelehntAt', 'abgelaufenAt', 'storniertAt', 'bezahltAt', 'anonymisiertAt'], termineOutbox: ['naechsterVersuch', 'versendetAt'] },
|
||
defaultLimit: 25,
|
||
})
|
||
after(() => aw.close())
|
||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1/v1' })
|
||
|
||
const { config } = await import('../server/config.js')
|
||
const { deleteDocument, updateDocument } = await import('../server/services/appwriteAdmin.js')
|
||
const { anonymisiere, loescheAbgeschaltete, loescheUkDaten, schliesseUkVonKunde } = await import('../server/services/unterkunft/aufbewahrung.js')
|
||
const jobs = await import('../server/services/unterkunft/jobs.js')
|
||
const mails = await import('../server/services/unterkunft/mails.js')
|
||
const { deleteCustomerWithPortalAccess } = await import('../server/services/customerAdmin.js')
|
||
const { withLocks } = await import('../server/services/keyedLock.js')
|
||
const { raeumeAuf } = await import('../server/services/termine/outbox.js')
|
||
const { sendeHinweiseVorLoeschung } = await import('../server/services/unterkunft/abschaltHinweis.js')
|
||
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
||
const { mailSink } = await import('./support/mailSink.js')
|
||
|
||
const TAG = 24 * 3600 * 1000
|
||
const JETZT = new Date('2026-10-07T10:00:00.000Z')
|
||
const vor = (tage, von = JETZT) => new Date(von.getTime() - tage * TAG).toISOString()
|
||
const GAST = { name: 'Eva Gast', firma: '', email: 'eva@example.com', telefon: '0123', nachricht: 'Hallo' }
|
||
const ADRESSE = { name: 'Eva Gast', strasse: 'Weg 1', plz: '12345', ort: 'Berlin', land: 'DE' }
|
||
let n = 0
|
||
const anfrage = (over = {}) => {
|
||
const $id = over.$id ?? `a${++n}`
|
||
aw.seed('ukAnfragen', { $id, featureId: 'f1', customerId: 'c1', projectId: 'p1', unterkunftId: 'u1', nummer: 'A-' + $id, von: '2026-09-01', bis: '2026-09-05', personen: 2, status: 'neu', gast: JSON.stringify(GAST), nachrichtVermieter: 'Willkommen', ipHash: 'abcd', erstattetCent: 0, ...over })
|
||
return $id
|
||
}
|
||
const holen = (id) => aw.docs('ukAnfragen').get(id)
|
||
const gast = (id) => JSON.parse(holen(id).gast)
|
||
|
||
beforeEach(async () => {
|
||
for (const c of [...NAMEN, 'termineOutbox', 'portalFeatures', 'customers', 'websiteProjects']) for (const id of aw.docs(c).keys()) await deleteDocument(c, id)
|
||
mails._aufrufe.length = 0
|
||
})
|
||
|
||
test('ipHash: nach 29 Tagen noch da, nach 31 Tagen leer; idempotent', async () => {
|
||
const jung = anfrage({ $createdAt: vor(29) })
|
||
const alt = anfrage({ $createdAt: vor(31) })
|
||
const r = await anonymisiere(JETZT)
|
||
assert.equal(r.ipHashes, 1)
|
||
assert.equal(holen(jung).ipHash, 'abcd')
|
||
assert.equal(holen(alt).ipHash, '')
|
||
assert.equal((await anonymisiere(JETZT)).ipHashes, 0)
|
||
})
|
||
|
||
test('neu: nach 59 Tagen bleibt neu, nach 61 Tagen abgelaufen ohne Mail', async () => {
|
||
const jung = anfrage({ $createdAt: vor(59) })
|
||
const alt = anfrage({ $createdAt: vor(61) })
|
||
const entschieden = anfrage({ $createdAt: vor(61), status: 'freigegeben' })
|
||
const r = await anonymisiere(JETZT)
|
||
assert.equal(r.abgelaufen, 1)
|
||
assert.equal(holen(jung).status, 'neu')
|
||
assert.equal(holen(alt).status, 'abgelaufen')
|
||
assert.ok(Math.abs(Date.parse(holen(alt).abgelaufenAt) - JETZT.getTime()) < 1000)
|
||
assert.equal(holen(entschieden).status, 'freigegeben')
|
||
assert.equal(mails._aufrufe.length, 0)
|
||
})
|
||
|
||
test('Unverbindliche Anfragen: nach 6 Monaten anonymisiert, knapp davor nicht; storniert ohne Rechnung auch, mit Rechnung nicht', async () => {
|
||
const sechs = new Date('2026-04-07T10:00:00.000Z')
|
||
const davor = anfrage({ status: 'abgelehnt', $createdAt: new Date(sechs.getTime() + 3600e3).toISOString(), rechnungsadresse: JSON.stringify(ADRESSE) })
|
||
const danach = anfrage({ status: 'abgelehnt', $createdAt: new Date(sechs.getTime() - 3600e3).toISOString(), rechnungsadresse: JSON.stringify(ADRESSE) })
|
||
const verfallen = anfrage({ status: 'abgelaufen', $createdAt: vor(200) })
|
||
const stornoOhne = anfrage({ status: 'storniert', $createdAt: vor(200) })
|
||
const stornoMit = anfrage({ status: 'storniert', $createdAt: vor(200), rechnungId: 'r1', rechnungsadresse: JSON.stringify(ADRESSE), bis: '2026-09-05' })
|
||
aw.seed('ukRechnungen', { $id: 'r1', featureId: 'f1', anfrageId: stornoMit, art: 'rechnung', nummer: 'R-1', datum: '2026-09-01', bruttoCent: 100, inhalt: '{"x":1}' })
|
||
const r = await anonymisiere(JETZT)
|
||
assert.equal(r.anfragen, 3)
|
||
assert.equal(r.gaeste, 0)
|
||
assert.deepEqual(gast(davor), GAST)
|
||
for (const id of [danach, verfallen, stornoOhne]) {
|
||
const d = holen(id)
|
||
assert.deepEqual(JSON.parse(d.gast), {})
|
||
assert.equal(d.nachrichtVermieter, '')
|
||
assert.equal(d.rechnungsadresse, null)
|
||
assert.equal(d.ipHash, '')
|
||
assert.ok(d.anonymisiertAt)
|
||
}
|
||
assert.deepEqual(gast(stornoMit), GAST)
|
||
assert.deepEqual(JSON.parse(holen(stornoMit).rechnungsadresse), ADRESSE)
|
||
assert.equal((await anonymisiere(JETZT)).anfragen, 0)
|
||
})
|
||
|
||
test('Gebuchte Aufenthalte: Gast weg 3 Jahre nach Jahresende der Abreise (Berlin); Rechnungsadresse und Rechnung bleiben unveraendert', async () => {
|
||
const inhalt = JSON.stringify({ gast: ADRESSE, summe: 12345 })
|
||
const a = anfrage({ status: 'bezahlt', bis: '2022-12-31', von: '2022-12-28', $createdAt: vor(1500), rechnungsadresse: JSON.stringify(ADRESSE), rechnungId: 'r1', tokenHash: 'abc123' })
|
||
const b = anfrage({ status: 'gebucht', bis: '2023-01-02', von: '2022-12-30', $createdAt: vor(1500), rechnungsadresse: JSON.stringify(ADRESSE) })
|
||
aw.seed('ukRechnungen', { $id: 'r1', featureId: 'f1', anfrageId: a, art: 'rechnung', nummer: 'R-1', datum: '2022-12-20', bruttoCent: 12345, inhalt })
|
||
// 31.12.2025 23:30 Berlin: noch Jahr 2025 -> nichts
|
||
assert.equal((await anonymisiere(new Date('2025-12-31T22:30:00Z'))).gaeste, 0)
|
||
assert.deepEqual(gast(a), GAST)
|
||
// 01.01.2026 00:30 Berlin -> a (Abreise 2022), b (Abreise 2023) noch nicht
|
||
const r = await anonymisiere(new Date('2025-12-31T23:30:00Z'))
|
||
assert.equal(r.gaeste, 1)
|
||
assert.deepEqual(gast(a), {})
|
||
assert.ok(holen(a).anonymisiertAt)
|
||
assert.equal(holen(a).tokenHash, '', 'Buchungslink tot')
|
||
assert.deepEqual(JSON.parse(holen(a).rechnungsadresse), ADRESSE)
|
||
assert.deepEqual(gast(b), GAST)
|
||
const re = aw.docs('ukRechnungen').get('r1')
|
||
assert.equal(re.inhalt, inhalt, 'PDF-Schnappschuss unveraendert')
|
||
assert.equal(re.nummer, 'R-1')
|
||
assert.equal((await anonymisiere(new Date('2025-12-31T23:30:00Z'))).gaeste, 0)
|
||
})
|
||
|
||
test('Rechnungen: 8 Jahre nach Ende des Rechnungsjahres geloescht, zugleich Rechnungsadresse der Anfrage weg', async () => {
|
||
const a = anfrage({ status: 'bezahlt', bis: '2018-05-05', anonymisiertAt: vor(1000), gast: '{}', rechnungsadresse: JSON.stringify(ADRESSE), rechnungId: 'r1' })
|
||
aw.seed('ukRechnungen', { $id: 'r1', featureId: 'f1', anfrageId: a, art: 'rechnung', nummer: 'R-1', datum: '2018-05-01', bruttoCent: 1, inhalt: '{}' })
|
||
aw.seed('ukRechnungen', { $id: 'r2', featureId: 'f1', anfrageId: a, art: 'storno', nummer: 'S-1', datum: '2019-01-10', bruttoCent: -1, inhalt: '{}' })
|
||
// Ende 2026 -> r1 faellig am 01.01.2027; r2 (2019) erst 01.01.2028
|
||
assert.equal((await anonymisiere(new Date('2026-12-31T22:30:00Z'))).rechnungen, 0)
|
||
const r = await anonymisiere(new Date('2026-12-31T23:30:00Z'))
|
||
assert.equal(r.rechnungen, 1)
|
||
assert.equal(aw.docs('ukRechnungen').has('r1'), false)
|
||
assert.equal(aw.docs('ukRechnungen').has('r2'), true)
|
||
assert.deepEqual(JSON.parse(holen(a).rechnungsadresse), ADRESSE, 'noch eine Rechnung der Anfrage uebrig')
|
||
const r2 = await anonymisiere(new Date('2027-12-31T23:30:00Z'))
|
||
assert.equal(r2.rechnungen, 1)
|
||
assert.equal(r2.adressen, 1)
|
||
assert.equal(aw.docs('ukRechnungen').size, 0)
|
||
assert.equal(holen(a).rechnungsadresse, null)
|
||
})
|
||
|
||
test('Deckel begrenzt die Zahl je Lauf, der Rest folgt am naechsten', async () => {
|
||
for (let i = 0; i < 5; i++) anfrage({ $createdAt: vor(40) })
|
||
const r1 = await anonymisiere(JETZT, { deckel: 2 })
|
||
assert.equal(r1.ipHashes, 2)
|
||
assert.equal(r1.gedeckelt, true)
|
||
assert.equal((await anonymisiere(JETZT, { deckel: 2 })).ipHashes, 2)
|
||
const r3 = await anonymisiere(JETZT, { deckel: 2 })
|
||
assert.equal(r3.ipHashes, 1)
|
||
assert.equal(r3.gedeckelt, false)
|
||
})
|
||
|
||
test('Logs ohne Personendaten: ein Fehler wird gezaehlt, der Lauf macht weiter', async (t) => {
|
||
const log = t.mock.method(console, 'warn', () => {})
|
||
const a = anfrage({ $createdAt: vor(40) })
|
||
const b = anfrage({ $createdAt: vor(40) })
|
||
aw.failNext({ collection: 'ukAnfragen', method: 'PATCH', status: 500, message: 'kaputt eva@example.com' })
|
||
const r = await anonymisiere(JETZT)
|
||
assert.equal(r.fehler, 1)
|
||
assert.equal(r.ipHashes, 1)
|
||
assert.deepEqual([holen(a).ipHash, holen(b).ipHash].sort(), ['', 'abcd'])
|
||
assert.ok(log.mock.calls.length >= 1)
|
||
assert.ok(!JSON.stringify(log.mock.calls.map((c) => c.arguments)).includes('eva@example.com'), 'keine Personendaten im Log')
|
||
})
|
||
|
||
const funktion = (over = {}) => aw.seed('portalFeatures', { $id: 'f1', customerId: 'c1', projectId: 'p1', featureKey: 'unterkunft', siteKey: 'abcdefghijklmnop', state: 'active', enabled: true, ...over })
|
||
const datenFuer = (f) => {
|
||
aw.seed('ukEinstellungen', { $id: f, featureId: f, firma: 'Haus' })
|
||
aw.seed('ukUnterkuenfte', { $id: 'u_' + f, featureId: f, customerId: 'c1', name: 'W', aktiv: true })
|
||
aw.seed('ukPreise', { $id: 'p_' + f, unterkunftId: 'u_' + f, featureId: f })
|
||
aw.seed('ukSperren', { $id: 's_' + f, unterkunftId: 'u_' + f, featureId: f, von: '2026-12-01', bis: '2026-12-02' })
|
||
anfrage({ $id: 'an_' + f, featureId: f })
|
||
aw.seed('ukRechnungen', { $id: 're_' + f, featureId: f, anfrageId: 'an_' + f, art: 'rechnung', nummer: 'R', datum: '2026-09-01', bruttoCent: 1, inhalt: '{}' })
|
||
}
|
||
const anzahl = (f) => NAMEN.reduce((s, c) => s + [...aw.docs(c).values()].filter((d) => d.featureId === f).length, 0)
|
||
|
||
test('Abgeschaltete Funktion: nach 89 Tagen alles da, nach 91 Tagen alle uk*-Daten weg, Marker gesetzt, Rest unberuehrt, idempotent', async () => {
|
||
funktion({ state: 'disabled', enabled: false, disabledAt: vor(89) })
|
||
funktion({ $id: 'f2', state: 'disabled', enabled: false, disabledAt: vor(100), siteKey: 'bcdefghijklmnopq' })
|
||
funktion({ $id: 'f3', state: 'active' })
|
||
for (const f of ['f1', 'f2', 'f3']) datenFuer(f)
|
||
let r = await loescheAbgeschaltete(JETZT)
|
||
assert.equal(r.funktionen, 1)
|
||
assert.equal(anzahl('f1'), 6)
|
||
assert.equal(anzahl('f2'), 0)
|
||
assert.equal(anzahl('f3'), 6)
|
||
assert.match(aw.docs('portalFeatures').get('f2').adminNote, /^Buchungsdaten gelöscht am 2026-10-07/)
|
||
r = await loescheAbgeschaltete(JETZT)
|
||
assert.equal(r.funktionen, 0)
|
||
r = await loescheAbgeschaltete(new Date(JETZT.getTime() + 3 * TAG))
|
||
assert.equal(r.funktionen, 1)
|
||
assert.equal(anzahl('f1'), 0)
|
||
assert.equal(anzahl('f3'), 6)
|
||
})
|
||
|
||
test('K35: einmal pro Abschaltung, mit Loeschdatum, nur mit Daten; Marker; ohne Adresse nur Marker; Fehler wird wiederholt; Tagesjob sendet vor der Loeschung', async () => {
|
||
const senke = mailSink()
|
||
setMailTransportForTests(senke)
|
||
try {
|
||
aw.seed('customers', { $id: 'c1', name: 'Mimi', email: 'mimi@example.com' })
|
||
aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Donau Home' })
|
||
funktion({ state: 'disabled', enabled: false, disabledAt: vor(1) })
|
||
funktion({ $id: 'f2', state: 'disabled', enabled: false, disabledAt: vor(1), customerId: 'c9' }) // Kunde fehlt
|
||
funktion({ $id: 'f3', state: 'disabled', enabled: false, disabledAt: vor(1) }) // ohne Daten
|
||
funktion({ $id: 'f4', state: 'active' })
|
||
for (const f of ['f1', 'f2', 'f4']) datenFuer(f)
|
||
const r = await sendeHinweiseVorLoeschung(JETZT)
|
||
assert.deepEqual([r.gesendet, r.ohneAdresse, r.fehler], [1, 1, 0])
|
||
assert.equal(senke.sent.length, 1)
|
||
const m = senke.sent[0]
|
||
assert.equal(m.to[0].address, 'mimi@example.com')
|
||
assert.match(m.subject, /Buchungskalender abgeschaltet/)
|
||
assert.match(m.text, /Donau Home/)
|
||
assert.match(m.text, /04.01.2027/, 'disabledAt + 90 Tage')
|
||
assert.match(aw.docs('portalFeatures').get('f1').adminNote, /K35 am 2026-10-07/)
|
||
assert.match(aw.docs('portalFeatures').get('f2').adminNote, /K35 am 2026-10-07/)
|
||
assert.ok(!aw.docs('portalFeatures').get('f3').adminNote)
|
||
assert.ok(!aw.docs('portalFeatures').get('f4').adminNote)
|
||
// zweiter Lauf: nichts mehr
|
||
assert.equal((await sendeHinweiseVorLoeschung(new Date(JETZT.getTime() + TAG))).gesendet, 0)
|
||
assert.equal(senke.sent.length, 1)
|
||
// erneut abgeschaltet (alter Marker liegt vor disabledAt): wieder ein Hinweis
|
||
await updateDocument(config.collections.portalFeatures, 'f1', { disabledAt: vor(-5) })
|
||
assert.equal((await sendeHinweiseVorLoeschung(new Date(JETZT.getTime() + 6 * TAG))).gesendet, 1)
|
||
// Versandfehler: kein Marker, naechster Lauf wiederholt
|
||
aw.seed('portalFeatures', { ...aw.docs('portalFeatures').get('f3'), adminNote: '' })
|
||
datenFuer('f3')
|
||
setMailTransportForTests({ async sendMail() { throw new Error('smtp down') } })
|
||
const log = console.warn
|
||
console.warn = () => {}
|
||
const f = await sendeHinweiseVorLoeschung(JETZT)
|
||
console.warn = log
|
||
assert.equal(f.fehler, 1)
|
||
assert.ok(!aw.docs('portalFeatures').get('f3').adminNote)
|
||
setMailTransportForTests(senke)
|
||
assert.equal((await sendeHinweiseVorLoeschung(JETZT)).gesendet, 1)
|
||
} finally {
|
||
setMailTransportForTests(null)
|
||
}
|
||
})
|
||
|
||
test('K35: Tagesjob schickt den Hinweis und loescht erst nach 90 Tagen', async () => {
|
||
const senke = mailSink()
|
||
setMailTransportForTests(senke)
|
||
try {
|
||
aw.seed('customers', { $id: 'c1', name: 'Mimi', email: 'mimi@example.com' })
|
||
funktion({ state: 'disabled', enabled: false, disabledAt: vor(3) })
|
||
datenFuer('f1')
|
||
await jobs.taeglicherJob(JETZT)
|
||
assert.equal(senke.sent.length, 1)
|
||
assert.equal(anzahl('f1'), 6)
|
||
await jobs.taeglicherJob(new Date(JETZT.getTime() + 88 * TAG))
|
||
assert.equal(senke.sent.length, 1)
|
||
assert.equal(anzahl('f1'), 0)
|
||
} finally {
|
||
setMailTransportForTests(null)
|
||
}
|
||
})
|
||
|
||
test('loescheUkDaten mit Deckel: unvollstaendig, Fortsetzung loescht den Rest', async () => {
|
||
datenFuer('f1')
|
||
const a = await loescheUkDaten({ featureId: 'f1', deckel: 2 })
|
||
assert.equal(a.vollstaendig, false)
|
||
assert.equal(a.geloescht, 2)
|
||
const b = await loescheUkDaten({ featureId: 'f1', deckel: 100 })
|
||
assert.equal(b.vollstaendig, true)
|
||
assert.equal(anzahl('f1'), 0)
|
||
})
|
||
|
||
test('Tagesjob: off ohne Daten = aus, mit Daten laeuft er; on fuehrt Aufbewahrung und Loeschung aus', async () => {
|
||
const modus = config.unterkunft.mode
|
||
try {
|
||
config.unterkunft.mode = 'off'
|
||
assert.deepEqual(await jobs.taeglicherJob(JETZT), { aus: true })
|
||
anfrage({ $createdAt: vor(40) })
|
||
const r = await jobs.taeglicherJob(JETZT)
|
||
assert.equal(r.aufbewahrung.ipHashes, 1, 'die Fristen gelten auch bei off')
|
||
config.unterkunft.mode = 'on'
|
||
funktion({ state: 'disabled', enabled: false, disabledAt: vor(120) })
|
||
datenFuer('f1')
|
||
const r2 = await jobs.taeglicherJob(JETZT)
|
||
assert.equal(r2.abgeschaltet.funktionen, 1)
|
||
assert.equal(anzahl('f1'), 0)
|
||
} finally {
|
||
config.unterkunft.mode = modus
|
||
}
|
||
})
|
||
|
||
test('Warteschlange: raeumeAuf loescht Eintraege jeder art (auch uk-*) 7 Tage nach Versand bzw. 48 Stunden nach Fehler', async () => {
|
||
const eintrag = (id, status, wann) => aw.seed('termineOutbox', { $id: id, featureId: 'f1', art: 'G16', bookingId: 'x', status, naechsterVersuch: wann })
|
||
eintrag('o1', 'gesendet', vor(8))
|
||
eintrag('o2', 'gesendet', vor(-6))
|
||
eintrag('o3', 'fehler', vor(3))
|
||
eintrag('o4', 'wartet', vor(10))
|
||
assert.equal(await raeumeAuf(JETZT), 2)
|
||
assert.deepEqual([...aw.docs('termineOutbox').keys()].sort(), ['o2', 'o4'])
|
||
})
|
||
|
||
test('Kundenloeschung schaltet die Funktion ab, schliesst die Unterkuenfte, Daten bleiben bis zur Frist', async () => {
|
||
aw.seed('customers', { $id: 'c1', name: 'Weg', email: 'weg@example.invalid' })
|
||
aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Haus' })
|
||
funktion()
|
||
datenFuer('f1')
|
||
aw.seed('ukUnterkuenfte', { $id: 'u_fremd', featureId: 'f9', customerId: 'c9', name: 'X', aktiv: true })
|
||
await deleteCustomerWithPortalAccess('c1')
|
||
const f = aw.docs('portalFeatures').get('f1')
|
||
assert.deepEqual([f.state, f.enabled, f.adminNote, f.disabledReason], ['disabled', false, 'Kunde gelöscht', 'kunde_geloescht'])
|
||
assert.equal(aw.docs('ukUnterkuenfte').get('u_f1').aktiv, false)
|
||
assert.equal(aw.docs('ukUnterkuenfte').get('u_fremd').aktiv, true)
|
||
assert.equal(anzahl('f1'), 6)
|
||
assert.equal((await loescheAbgeschaltete(new Date(Date.parse(f.disabledAt) + 91 * TAG))).funktionen, 1)
|
||
assert.equal(anzahl('f1'), 0)
|
||
assert.equal(await schliesseUkVonKunde('c1'), 0)
|
||
})
|
||
|
||
test('Sperre wie ablauf.js: ein Wechsel auf freigegeben unter der Unterkunfts-Sperre wird nicht ueberschrieben', async () => {
|
||
const id = anfrage({ $createdAt: vor(61), unterkunftId: 'u1' })
|
||
let lauf
|
||
await withLocks(['unterkunft:u1'], async () => {
|
||
// der Job laeuft an und wartet auf die Sperre; waehrenddessen gibt der Vermieter frei
|
||
lauf = anonymisiere(JETZT)
|
||
await new Promise((r) => setTimeout(r, 150))
|
||
await (await import('../server/services/appwriteAdmin.js')).updateDocument(config.collections.ukAnfragen, id, { status: 'freigegeben' })
|
||
})
|
||
const r = await lauf
|
||
assert.equal(r.abgelaufen, 0)
|
||
assert.equal(holen(id).status, 'freigegeben')
|
||
})
|
||
|
||
test('Gebucht ohne jede Rechnung: Rechnungsadresse geht mit den Gastdaten', async () => {
|
||
const a = anfrage({ status: 'gebucht', bis: '2022-06-01', rechnungsadresse: JSON.stringify(ADRESSE) })
|
||
const r = await anonymisiere(new Date('2026-01-02T10:00:00Z'))
|
||
assert.equal(r.gaeste, 1)
|
||
assert.deepEqual(gast(a), {})
|
||
assert.equal(holen(a).rechnungsadresse, null)
|
||
})
|
||
|
||
test('adminNote: bisheriger Text bleibt vor dem Marker; Wiedereinschalten bricht ab; erneutes Abschalten loescht nach neuen 90 Tagen; aktive Funktion mit altem Marker unberuehrt', async () => {
|
||
const sp = await import('../server/services/appwriteAdmin.js')
|
||
funktion({ state: 'disabled', enabled: false, disabledAt: vor(100), adminNote: 'Kunde gelöscht' })
|
||
datenFuer('f1')
|
||
assert.equal((await loescheAbgeschaltete(JETZT)).funktionen, 1)
|
||
assert.equal(aw.docs('portalFeatures').get('f1').adminNote, 'Kunde gelöscht – Buchungsdaten gelöscht am 2026-10-07')
|
||
// wieder eingeschaltet: nichts wird geloescht
|
||
funktion({ $id: 'f4', state: 'disabled', enabled: false, disabledAt: vor(91) })
|
||
datenFuer('f4')
|
||
await sp.updateDocument(config.collections.portalFeatures, 'f4', { state: 'active', enabled: true })
|
||
assert.equal((await loescheAbgeschaltete(JETZT)).funktionen, 0)
|
||
assert.equal(anzahl('f4'), 6)
|
||
// erneut abgeschaltet (alter Marker steht noch): erst nach neuen 90 Tagen
|
||
await sp.updateDocument(config.collections.portalFeatures, 'f4', { adminNote: 'Buchungsdaten gelöscht am 2026-01-01', state: 'disabled', enabled: false, disabledAt: vor(10) })
|
||
assert.equal((await loescheAbgeschaltete(JETZT)).funktionen, 0)
|
||
assert.equal(anzahl('f4'), 6)
|
||
assert.equal((await loescheAbgeschaltete(new Date(JETZT.getTime() + 81 * TAG))).funktionen, 1)
|
||
assert.equal(anzahl('f4'), 0)
|
||
// aktive Funktion mit altem Marker: unberuehrt
|
||
funktion({ $id: 'f5', state: 'active', adminNote: 'Buchungsdaten gelöscht am 2026-01-01' })
|
||
datenFuer('f5')
|
||
await loescheAbgeschaltete(new Date(JETZT.getTime() + 500 * TAG))
|
||
assert.equal(anzahl('f5'), 6)
|
||
})
|
||
|
||
test('Gemeinsamer Deckel: der Tagesjob gibt der Loeschung nur den Rest', async () => {
|
||
const modus = config.unterkunft.mode
|
||
const alt = config.unterkunft.aufbewahrungDeckel
|
||
try {
|
||
config.unterkunft.mode = 'on'
|
||
config.unterkunft.aufbewahrungDeckel = 3
|
||
for (let i = 0; i < 3; i++) anfrage({ $createdAt: vor(40) })
|
||
funktion({ state: 'disabled', enabled: false, disabledAt: vor(120) })
|
||
datenFuer('f1')
|
||
const r = await jobs.taeglicherJob(JETZT)
|
||
assert.equal(r.aufbewahrung.verbraucht, 3)
|
||
assert.equal(r.abgeschaltet.dokumente, 0)
|
||
assert.equal(r.abgeschaltet.gedeckelt, true)
|
||
} finally {
|
||
config.unterkunft.mode = modus
|
||
config.unterkunft.aufbewahrungDeckel = alt
|
||
}
|
||
})
|