import { readFileSync } from 'node:fs' import { describe, expect, it } from 'vitest' import { googleSeiteLesen, googleZeitpunkt, zeigtAlsAusGoogle } from './google' import { AntwortUnbrauchbar } from './typen' function fixture(name: string): unknown { return JSON.parse( readFileSync(`test/fixtures/kalender/${name}.json`, 'utf8'), ) as unknown } describe('zeigtAlsAusGoogle', () => { it('kennt nur frei und gebucht', () => { expect(zeigtAlsAusGoogle('transparent')).toBe('FREI') expect(zeigtAlsAusGoogle('opaque')).toBe('GEBUCHT') }) it('nimmt ein fehlendes Feld als gebucht', () => { // Google lässt transparency weg, wenn der Termin die Zeit belegt. expect(zeigtAlsAusGoogle(undefined)).toBe('GEBUCHT') }) }) describe('googleZeitpunkt', () => { it('liest eine Zeit mit Versatz', () => { const ergebnis = googleZeitpunkt({ dateTime: '2026-03-17T14:00:00+01:00' }) expect(ergebnis.zeitpunkt.toISOString()).toBe('2026-03-17T13:00:00.000Z') expect(ergebnis.ganztaegig).toBe(false) }) it('erkennt ein reines Datum als ganztägig', () => { const ergebnis = googleZeitpunkt({ date: '2026-03-20' }) expect(ergebnis.zeitpunkt.toISOString()).toBe('2026-03-20T00:00:00.000Z') expect(ergebnis.ganztaegig).toBe(true) }) it('bricht ab, wenn beides fehlt', () => { expect(() => googleZeitpunkt({})).toThrow(AntwortUnbrauchbar) }) it('bricht bei unlesbaren Angaben ab', () => { expect(() => googleZeitpunkt({ dateTime: 'irgendwann' })).toThrow(AntwortUnbrauchbar) expect(() => googleZeitpunkt({ date: '2026-13-45' })).toThrow(AntwortUnbrauchbar) }) }) describe('googleSeiteLesen', () => { it('liest die erste Seite samt Seitenverweis', () => { const seite = googleSeiteLesen(fixture('google-events-seite1')) expect(seite.geaendert).toHaveLength(2) expect(seite.weiter).toBe('BEISPIEL-SEITE-2') expect(seite.deltaVerweis).toBeNull() }) it('rechnet den Versatz nach UTC um', () => { const [erster] = googleSeiteLesen(fixture('google-events-seite1')).geaendert expect(erster?.extId).toBe('beispiel-termin-a') expect(erster?.betreff).toBe('Zahnarzt') expect(erster?.beginn.toISOString()).toBe('2026-03-17T13:00:00.000Z') expect(erster?.ende.toISOString()).toBe('2026-03-17T14:00:00.000Z') expect(erster?.ganztaegig).toBe(false) }) it('erkennt ganztägige Termine am reinen Datum', () => { const ganztaegig = googleSeiteLesen(fixture('google-events-seite1')).geaendert[1] expect(ganztaegig?.ganztaegig).toBe(true) expect(ganztaegig?.betreff).toBe('Urlaubstag') }) it('liest die letzte Seite mit dem Sync-Verweis', () => { const seite = googleSeiteLesen(fixture('google-events-seite2')) expect(seite.weiter).toBeNull() expect(seite.deltaVerweis).toBe('BEISPIEL-SYNC-1') }) it('behandelt abgesagte Termine als entfernt', () => { const seite = googleSeiteLesen(fixture('google-events-seite2')) expect(seite.entfernt).toEqual(['beispiel-termin-d']) expect(seite.geaendert.map((t) => t.extId)).toEqual(['beispiel-termin-c']) }) it('erkennt einen durchlässigen Termin als frei', () => { const [frei] = googleSeiteLesen(fixture('google-events-seite2')).geaendert expect(frei?.zeigtAls).toBe('FREI') }) it('kommt mit einer leeren Antwort zurecht', () => { const seite = googleSeiteLesen(fixture('google-events-leer')) expect(seite.geaendert).toEqual([]) expect(seite.entfernt).toEqual([]) expect(seite.deltaVerweis).toBe('BEISPIEL-SYNC-2') }) it('weist eine Antwort mit falschem Aufbau ab', () => { expect(() => googleSeiteLesen({ items: 'keine Liste' })).toThrow(AntwortUnbrauchbar) }) it('übergeht Einträge ohne Zeitraum', () => { const seite = googleSeiteLesen({ items: [{ id: 'ohne-zeit', status: 'confirmed', summary: 'Kein Zeitraum' }], }) expect(seite.geaendert).toEqual([]) }) })