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 3: Speicher (services/unterkunft/speicher.js) und Belegung (belegung.js) const NAMEN = ['ukUnterkuenfte', 'ukPreise', 'ukSperren', 'ukAnfragen', 'ukEinstellungen'] const aw = await startAppwriteMock({ schema: Object.fromEntries(NAMEN.map((n) => [n, schemaKeys(n)])), datetimes: { ukAnfragen: ['fristAt', 'freigegebenAt', 'gebuchtAt'] }, }) useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1/v1' }) const { config } = await import('../server/config.js') const { listDocuments, deleteDocument } = await import('../server/services/appwriteAdmin.js') const sp = await import('../server/services/unterkunft/speicher.js') const bel = await import('../server/services/unterkunft/belegung.js') after(() => aw.close()) const NOW = new Date('2026-11-01T12:00:00Z') const ZUKUNFT = '2026-11-02T12:00:00.000+00:00' const VERGANGENHEIT = '2026-10-31T12:00:00.000+00:00' let n = 0 const anfrage = (felder) => { const doc = { $id: `a${++n}`, featureId: 'f1', unterkunftId: 'u1', von: '2026-11-10', bis: '2026-11-12', status: 'neu', ...felder } aw.seed('ukAnfragen', doc) return doc } beforeEach(async () => { for (const c of NAMEN) for (const id of aw.docs(c).keys()) await deleteDocument(c, id) }) test('ueberschneidet: Halboffen, Abreise gleich Anreise ist frei', () => { assert.equal(bel.ueberschneidet({ von: '2026-11-01', bis: '2026-11-05' }, { von: '2026-11-05', bis: '2026-11-07' }), false) assert.equal(bel.ueberschneidet({ von: '2026-11-01', bis: '2026-11-05' }, { von: '2026-11-04', bis: '2026-11-07' }), true) assert.equal(bel.ueberschneidet({ von: '2026-11-04', bis: '2026-11-07' }, { von: '2026-11-01', bis: '2026-11-05' }), true) assert.equal(bel.ueberschneidet({ von: '2026-11-01', bis: '2026-11-10' }, { von: '2026-11-03', bis: '2026-11-04' }), true) }) test('Belegung laedt nur Sperren und Anfragen, die nach dem Fensteranfang enden (bis > von)', async () => { await sp.legeSperreAn({ unterkunftId: 'u1', featureId: 'f1', von: '2026-09-01', bis: '2026-09-10', quelle: 'manuell' }) await sp.legeSperreAn({ unterkunftId: 'u1', featureId: 'f1', von: '2026-11-10', bis: '2026-11-12', quelle: 'manuell' }) anfrage({ status: 'gebucht', von: '2026-09-01', bis: '2026-09-05' }) anfrage({ status: 'gebucht', von: '2026-11-20', bis: '2026-11-22' }) assert.deepEqual((await sp.listeSperren('u1', { bisNach: '2026-11-01' })).map((s) => s.bis), ['2026-11-12']) assert.equal((await sp.listeSperren('u1')).length, 2) assert.deepEqual((await sp.listeAnfragenNachStatus('u1', ['gebucht'], { bisNach: '2026-11-01' })).map((a) => a.bis), ['2026-11-22']) // bis == von: beruehrt das Fenster nicht assert.equal((await sp.listeSperren('u1', { bisNach: '2026-11-12' })).length, 0) assert.deepEqual(await bel.belegteBereiche('u1', '2026-11-01', '2026-11-30', { now: NOW }), [ { von: '2026-11-10', bis: '2026-11-12', quelle: 'manuell' }, { von: '2026-11-20', bis: '2026-11-22', quelle: 'gebucht' }, ]) }) test('Sperre manuell blockiert, Abreisetag nicht', async () => { await sp.legeSperreAn({ unterkunftId: 'u1', featureId: 'f1', von: '2026-11-10', bis: '2026-11-12', quelle: 'manuell' }) assert.equal(await bel.istFrei('u1', '2026-11-11', '2026-11-13', { now: NOW }), false) assert.equal(await bel.istFrei('u1', '2026-11-12', '2026-11-14', { now: NOW }), true) assert.equal(await bel.istFrei('u1', '2026-11-08', '2026-11-10', { now: NOW }), true) assert.equal(await bel.istFrei('u2', '2026-11-11', '2026-11-13', { now: NOW }), true) }) test('Anfragen: freigegeben mit Frist, gebucht, bezahlt blockieren; Rest nicht', async () => { anfrage({ status: 'freigegeben', fristAt: ZUKUNFT, von: '2026-12-01', bis: '2026-12-03' }) anfrage({ status: 'freigegeben', fristAt: VERGANGENHEIT, von: '2026-12-05', bis: '2026-12-07' }) anfrage({ status: 'gebucht', von: '2026-12-10', bis: '2026-12-12' }) anfrage({ status: 'bezahlt', von: '2026-12-15', bis: '2026-12-17' }) for (const [i, s] of ['neu', 'abgelehnt', 'abgelaufen', 'storniert'].entries()) { anfrage({ status: s, fristAt: ZUKUNFT, von: `2026-12-${20 + i}`, bis: `2026-12-${21 + i}` }) } const frei = (v, b) => bel.istFrei('u1', v, b, { now: NOW }) assert.equal(await frei('2026-12-01', '2026-12-02'), false) assert.equal(await frei('2026-12-05', '2026-12-06'), true) assert.equal(await frei('2026-12-10', '2026-12-11'), false) assert.equal(await frei('2026-12-15', '2026-12-16'), false) assert.equal(await frei('2026-12-20', '2026-12-25'), true) const b = await bel.belegteBereiche('u1', '2026-12-01', '2026-12-31', { now: NOW }) assert.deepEqual(b.map((x) => [x.von, x.bis, x.quelle]), [ ['2026-12-01', '2026-12-03', 'reserviert'], ['2026-12-10', '2026-12-12', 'gebucht'], ['2026-12-15', '2026-12-17', 'gebucht'], ]) }) test('now als Zahl (ms) und Standard Date.now()', async () => { anfrage({ status: 'freigegeben', fristAt: ZUKUNFT }) assert.equal(await bel.istFrei('u1', '2026-11-10', '2026-11-12', { now: NOW.getTime() }), false) assert.equal(await bel.istFrei('u1', '2026-11-10', '2026-11-12', { now: Date.parse('2026-11-03T00:00:00Z') }), true) anfrage({ status: 'gebucht', von: '2027-01-01', bis: '2027-01-03' }) assert.equal(await bel.istFrei('u1', '2027-01-01', '2027-01-02'), false) }) test('ohneAnfrage ignoriert genau diese Anfrage', async () => { const a = anfrage({ status: 'gebucht' }) anfrage({ status: 'gebucht', von: '2026-11-20', bis: '2026-11-22' }) assert.equal(await bel.istFrei('u1', '2026-11-10', '2026-11-12', { now: NOW }), false) assert.equal(await bel.istFrei('u1', '2026-11-10', '2026-11-12', { now: NOW, ohneAnfrage: a.$id }), true) assert.equal(await bel.istFrei('u1', '2026-11-20', '2026-11-22', { now: NOW, ohneAnfrage: a.$id }), false) }) test('belegteBereiche: Quelle ical, Ausschnitt auf [von,bis), sortiert', async () => { await sp.legeSperreAn({ unterkunftId: 'u1', featureId: 'f1', von: '2026-11-08', bis: '2026-11-14', quelle: 'ical:abc' }) await sp.legeSperreAn({ unterkunftId: 'u1', featureId: 'f1', von: '2026-11-20', bis: '2026-11-22', quelle: 'manuell' }) const b = await bel.belegteBereiche('u1', '2026-11-10', '2026-11-21', { now: NOW }) assert.deepEqual(b, [ { von: '2026-11-10', bis: '2026-11-14', quelle: 'ical' }, { von: '2026-11-20', bis: '2026-11-21', quelle: 'manuell' }, ]) assert.deepEqual(bel.AKTIVE_STATUS, ['gebucht', 'bezahlt']) }) test('Einstellungen: anlegen mit featureId als ID, JSON geparst, aendern', async () => { assert.equal(await sp.ladeEinstellungen('f1'), null) const e = await sp.speichereEinstellungen('f1', { firma: 'Haus am See', rechnungZaehler: { RE: 3 }, anfrage: { a: 1 } }) assert.equal(e.$id, 'f1') assert.equal(e.featureId, 'f1') assert.deepEqual(e.rechnungZaehler, { RE: 3 }) const roh = (await listDocuments(config.collections.ukEinstellungen))[0] assert.equal(roh.rechnungZaehler, '{"RE":3}') const e2 = await sp.speichereEinstellungen('f1', { ort: 'Stetten', stripeStatus: { ok: true } }) assert.equal(e2.firma, 'Haus am See') assert.equal(e2.ort, 'Stetten') assert.deepEqual((await sp.ladeEinstellungen('f1')).stripeStatus, { ok: true }) assert.equal(aw.docs('ukEinstellungen').size, 1) }) test('Unterkuenfte: sortiert, nurAktive, icalImporte JSON, aendern per ID', async () => { const b = await sp.speichereUnterkunft('f1', { name: 'B', sort: 2, aktiv: true }) const a = await sp.speichereUnterkunft('f1', { name: 'A', sort: 1, aktiv: false, icalImporte: [{ id: 'abc', url: 'https://x' }] }) await sp.speichereUnterkunft('f2', { name: 'Fremd', sort: 0, aktiv: true }) assert.deepEqual((await sp.listeUnterkuenfte('f1')).map((u) => u.name), ['A', 'B']) assert.deepEqual((await sp.listeUnterkuenfte('f1', { nurAktive: true })).map((u) => u.name), ['B']) assert.deepEqual((await sp.ladeUnterkunft(a.$id)).icalImporte, [{ id: 'abc', url: 'https://x' }]) const b2 = await sp.speichereUnterkunft('f1', { name: 'B2' }, b.$id) assert.equal(b2.$id, b.$id) assert.equal(b2.sort, 2) assert.equal((await sp.ladeUnterkunft(b.$id)).name, 'B2') assert.equal(await sp.ladeUnterkunft('gibtsnicht'), null) }) test('Preise: 1:1 je Unterkunft, staffeln JSON, zweites Speichern aendert', async () => { assert.equal(await sp.ladePreise('u1'), null) const p = await sp.speicherePreise('u1', 'f1', { staffeln: [{ abNaechte: 1, nachtCent: 9000 }], reinigungCent: 5000 }) assert.deepEqual(p.staffeln, [{ abNaechte: 1, nachtCent: 9000 }]) const p2 = await sp.speicherePreise('u1', 'f1', { reinigungCent: 6000 }) assert.equal(p2.$id, p.$id) assert.equal(p2.reinigungCent, 6000) assert.deepEqual(p2.staffeln, [{ abNaechte: 1, nachtCent: 9000 }]) assert.equal(aw.docs('ukPreise').size, 1) }) test('Sperren: liste, anlegen, loeschen', async () => { const s = await sp.legeSperreAn({ unterkunftId: 'u1', featureId: 'f1', von: '2026-11-10', bis: '2026-11-12', quelle: 'manuell', notiz: 'Familie' }) await sp.legeSperreAn({ unterkunftId: 'u2', featureId: 'f1', von: '2026-11-10', bis: '2026-11-12', quelle: 'manuell' }) assert.equal((await sp.listeSperren('u1')).length, 1) await sp.loescheSperre(s.$id) assert.equal((await sp.listeSperren('u1')).length, 0) }) test('Anfragen: anlegen, lesen, aendern, filtern, JSON geparst', async () => { const a = await sp.legeAnfrageDokAn({ featureId: 'f1', unterkunftId: 'u1', nummer: 'A-1', von: '2026-11-10', bis: '2026-11-12', status: 'neu', gast: { name: 'Anna' }, posten: [{ t: 'x' }], summen: { brutto: 100 } }) assert.deepEqual(a.gast, { name: 'Anna' }) const roh = [...aw.docs('ukAnfragen').values()][0] assert.equal(roh.gast, '{"name":"Anna"}') const a2 = await sp.aktualisiereAnfrage(a.$id, { status: 'freigegeben', fristAt: ZUKUNFT, rechnungsadresse: { ort: 'X' } }) assert.equal(a2.status, 'freigegeben') assert.deepEqual(a2.gast, { name: 'Anna' }) assert.deepEqual((await sp.ladeAnfrage(a.$id)).rechnungsadresse, { ort: 'X' }) assert.equal(await sp.ladeAnfrage('nix'), null) anfrage({ status: 'gebucht', unterkunftId: 'u2' }) anfrage({ status: 'gebucht', featureId: 'f2' }) assert.equal((await sp.listeAnfragen('f1')).length, 2) assert.equal((await sp.listeAnfragen('f1', { status: ['gebucht'] })).length, 1) assert.equal((await sp.listeAnfragen('f1', { unterkunftId: 'u1' })).length, 1) })