Buchungskalender: Abgleich – iCal-Export (Escape, aktive Unterkünfte, ab gestern, Rate-Limit)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
95cbd473d0
commit
fbd07b3d8c
3 files changed
+40
-3
No files matched your search
@@ -24,6 +24,7 @@ const mails = await import('../server/services/unterkunft/mails.js')
|
||||
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
||||
const { warteAufVersand } = await import('../server/services/termine/outbox.js')
|
||||
const { parseIcs, baueIcs } = await import('../server/services/unterkunft/ical.js')
|
||||
const { resetTermineLimitsForTests } = await import('../server/services/termine/limits.js')
|
||||
const { holeIcs, gleicheAb, gleicheAlleAb, istPrivateAdresse } = await import('../server/services/unterkunft/icalAbgleich.js')
|
||||
const { viertelstundenJob } = await import('../server/services/unterkunft/jobs.js')
|
||||
const { unterkunftSeiteRouter } = await import('../server/routes/unterkunftSeite.js')
|
||||
@@ -85,6 +86,11 @@ test('parseIcs: HTML oder Unsinn => kein_ics', () => {
|
||||
assert.deepEqual(parseIcs('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nEND:VCALENDAR\r\n', OPT).ereignisse, [])
|
||||
})
|
||||
|
||||
test('baueIcs: Semikolon, Komma und Backslash im Namen werden maskiert', () => {
|
||||
const s = baueIcs({ name: 'A;B,C\\D', ereignisse: [], jetzt: new Date('2026-11-07T10:00:00Z') })
|
||||
assert.match(s, /X-WR-CALNAME:A\\;B\\,C\\\\D\r\n/)
|
||||
})
|
||||
|
||||
test('baueIcs: CRLF, gefaltete Zeilen <= 75 Oktette, Pflichtfelder', () => {
|
||||
const s = baueIcs({ name: 'Wohnung mit sehr langem Namen äöü '.repeat(4), ereignisse: [{ uid: 'a1@project.webklar.com', von: '2026-11-10', bis: '2026-11-12' }], jetzt: new Date('2026-11-07T10:00:00Z') })
|
||||
assert.ok(s.startsWith('BEGIN:VCALENDAR\r\n') && s.endsWith('END:VCALENDAR\r\n'))
|
||||
@@ -334,3 +340,27 @@ test('Export: unbekanntes Token, falsches Format, Funktion aus/inaktiv => 404',
|
||||
await updateDocument(config.collections.portalFeatures, FEATURE, { state: 'off' })
|
||||
assert.equal((await rawGet(`/unterkunft/ical/${TOKEN}.ics`)).status, 404)
|
||||
})
|
||||
|
||||
test('Export: nur aktive Unterkuenfte; nur Ereignisse ab gestern', async () => {
|
||||
const basis = { featureId: FEATURE, customerId: 'c1', projectId: 'p1', unterkunftId: 'u1', gast: JSON.stringify({ name: 'Eva' }), tokenHash: 'h', ipHash: 'i', personen: 2, status: 'gebucht' }
|
||||
const tag = (n) => new Date(Date.now() + n * 86400e3).toISOString().slice(0, 10)
|
||||
seedU([], { icalExportToken: TOKEN })
|
||||
aw.seed('ukAnfragen', { $id: 'a_lang_vorbei', ...basis, von: tag(-30), bis: tag(-10), nummer: 'A-1' })
|
||||
aw.seed('ukAnfragen', { $id: 'a_laeuft', ...basis, von: tag(-3), bis: tag(+3), nummer: 'A-2' })
|
||||
aw.seed('ukSperren', { $id: 's_vorbei', unterkunftId: 'u1', featureId: FEATURE, von: tag(-20), bis: tag(-15), quelle: 'manuell' })
|
||||
const r = await rawGet(`/unterkunft/ical/${TOKEN}.ics`)
|
||||
assert.equal(r.status, 200)
|
||||
assert.ok(r.body.includes('UID:a_laeuft@') && !r.body.includes('a_lang_vorbei') && !r.body.includes('s_vorbei'))
|
||||
await updateDocument(config.collections.ukUnterkuenfte, 'u1', { aktiv: false })
|
||||
assert.equal((await rawGet(`/unterkunft/ical/${TOKEN}.ics`)).status, 404, 'inaktive Unterkunft')
|
||||
})
|
||||
|
||||
test('Export: hoechstens 60 Abrufe je Minute und IP, danach 429', async () => {
|
||||
resetTermineLimitsForTests()
|
||||
seedU([], { icalExportToken: TOKEN })
|
||||
for (let i = 0; i < 60; i++) assert.equal((await rawGet(`/unterkunft/ical/${TOKEN}.ics`)).status, 200)
|
||||
const r = await rawGet(`/unterkunft/ical/${TOKEN}.ics`)
|
||||
assert.equal(r.status, 429)
|
||||
assert.equal(r.headers.get('retry-after'), '60')
|
||||
resetTermineLimitsForTests()
|
||||
})
|
||||
Reference in new issue
Block a user