import test from 'node:test' import assert from 'node:assert/strict' import { loadModule } from './load.mjs' const { KONTEN_FEHLER, AUFTRAEGE_FEHLER, ladeAuftraegeAnsicht, ansichtsZustand, kundeMitKundenart, erstelltMeldung, auftragMeldung, } = await loadModule('src/lib/auftraegeAnsicht.js') // Antwort von getProjektkonten (V5) mit den Feldern, die die Ansicht braucht const konten = (extra = {}) => ({ auftraegeAktiv: false, kunde: { id: 'k1', name: 'Anelia Wolf', kundenart: 'privat' }, konten: [{ projektId: 'p1', name: 'Website' }], ohneProjekt: { berechnetCents: 5000, bezahltCents: 0, anzahl: 1 }, stripeOk: true, ...extra, }) const OHNE_PROJEKT_LEER = { berechnetCents: 0, bezahltCents: 0, anzahl: 0 } // Attrappe für getProjektkonten und listAuftraege: gibt Werte zurück, wirft Fehler und merkt sich die Aufrufe function fakeApi({ projektkonten, auftraege } = {}) { const calls = [] const antwort = (wert) => { if (wert instanceof Error) throw wert return wert } return { calls, getProjektkonten: async (id) => { calls.push(['konten', id]); return antwort(projektkonten) }, listAuftraege: async (id) => { calls.push(['auftraege', id]); return antwort(auftraege) }, } } test('Schalter aus: die Aufträge werden gar nicht erst abgerufen', async () => { const api = fakeApi({ projektkonten: konten({ auftraegeAktiv: false }), auftraege: new Error('404 darf nie ankommen') }) const result = await ladeAuftraegeAnsicht('k1', api) assert.deepEqual(api.calls, [['konten', 'k1']]) assert.equal(result.konten.auftraegeAktiv, false) assert.equal(result.auftraege, null) assert.equal(result.auftraegeFehler, '') // ein fehlendes Feld gilt wie „aus“, nur true schaltet an; eine Antwort ohne Objekt wird zu {} const fehlt = fakeApi({ projektkonten: { konten: [] } }) assert.equal((await ladeAuftraegeAnsicht('k1', fehlt)).auftraege, null) assert.deepEqual(fehlt.calls, [['konten', 'k1']]) const nichts = await ladeAuftraegeAnsicht('k1', fakeApi({ projektkonten: null })) assert.deepEqual(nichts, { konten: {}, auftraege: null, auftraegeFehler: '' }) }) test('Schalter an: erst die Projektkonten, dann die Aufträge des Kunden', async () => { const liste = [{ id: 'a1', nummer: 'AB-2026-0001', status: 'versendet' }] const api = fakeApi({ projektkonten: konten({ auftraegeAktiv: true }), auftraege: { auftraege: liste } }) const result = await ladeAuftraegeAnsicht('k1', api) assert.deepEqual(api.calls, [['konten', 'k1'], ['auftraege', 'k1']]) assert.deepEqual(result.auftraege, liste) assert.equal(result.auftraegeFehler, '') // eine Antwort ohne Liste gibt eine leere Liste statt eines Absturzes const leer = await ladeAuftraegeAnsicht('k1', fakeApi({ projektkonten: konten({ auftraegeAktiv: true }), auftraege: {} })) assert.deepEqual(leer.auftraege, []) }) test('Aufträge scheitern allein: die Projektkonten bleiben, der Fehler steht eigens da', async () => { const api = fakeApi({ projektkonten: konten({ auftraegeAktiv: true }), auftraege: Object.assign(new Error('Interner Fehler.'), { status: 500 }), }) const result = await ladeAuftraegeAnsicht('k1', api) assert.equal(result.konten.konten.length, 1) assert.equal(result.auftraege, null) assert.equal(result.auftraegeFehler, 'Interner Fehler.') const ohneText = await ladeAuftraegeAnsicht('k1', fakeApi({ projektkonten: konten({ auftraegeAktiv: true }), auftraege: new Error('') })) assert.equal(ohneText.auftraegeFehler, AUFTRAEGE_FEHLER) assert.equal(AUFTRAEGE_FEHLER, 'Die Aufträge konnten nicht geladen werden.') }) test('Projektkonten scheitern, Aufträge laden: der Abschnitt Aufträge bleibt bedienbar, der Konten-Fehler steht eigens da', async () => { const fehler = Object.assign(new Error('Interner Fehler.'), { status: 500 }) const liste = [{ id: 'a1', nummer: 'AB-2026-0001', status: 'versendet' }] const api = fakeApi({ projektkonten: fehler, auftraege: { auftraege: liste } }) const result = await ladeAuftraegeAnsicht('k1', api) assert.deepEqual(api.calls, [['konten', 'k1'], ['auftraege', 'k1']]) assert.deepEqual(result, { konten: {}, kontenFehler: 'Interner Fehler.', auftraege: liste, auftraegeFehler: '' }) const ohneText = await ladeAuftraegeAnsicht('k1', fakeApi({ projektkonten: new Error(''), auftraege: { auftraege: liste } })) assert.equal(ohneText.kontenFehler, KONTEN_FEHLER) }) test('Projektkonten und Aufträge scheitern: der Fehler der Projektkonten geht weiter (Schalter aus gibt 404)', async () => { const fehler = Object.assign(new Error('Interner Fehler.'), { status: 500 }) const api = fakeApi({ projektkonten: fehler, auftraege: Object.assign(new Error('Nicht gefunden.'), { status: 404 }) }) await assert.rejects(ladeAuftraegeAnsicht('k1', api), (e) => e === fehler) assert.deepEqual(api.calls, [['konten', 'k1'], ['auftraege', 'k1']]) }) test('ansichtsZustand: Projekte-Fehler mit geladenen Aufträgen – Projekte „fehler“, Aufträge „bereit“', () => { const liste = [{ id: 'a1' }] const z = ansichtsZustand({ data: { konten: {}, kontenFehler: 'Interner Fehler.', auftraege: liste, auftraegeFehler: '' }, error: '' }) assert.equal(z.projekteStatus, 'fehler') assert.equal(z.fehler, 'Interner Fehler.') assert.equal(z.auftraegeAktiv, true) // der Server hat die Liste geliefert, der Schalter ist also an assert.equal(z.auftraegeStatus, 'bereit') assert.deepEqual(z.auftraege, liste) assert.deepEqual(z.konten, []) assert.equal(z.nachladeFehler, '') // scheitert auch ein späteres Neuladen, steht dessen Meldung im Fehlerkasten; die Aufträge bleiben stehen const spaeter = ansichtsZustand({ data: { konten: {}, kontenFehler: 'Interner Fehler.', auftraege: liste, auftraegeFehler: '' }, error: 'Netz weg' }) assert.equal(spaeter.projekteStatus, 'fehler') assert.equal(spaeter.fehler, 'Netz weg') assert.equal(spaeter.auftraegeStatus, 'bereit') }) test('ansichtsZustand: vor der ersten Antwort „laden“, nach einem Fehler „fehler“ mit der Meldung', () => { const laden = ansichtsZustand({ data: null, error: '' }) assert.equal(laden.projekteStatus, 'laden') assert.equal(laden.auftraegeStatus, 'aus') // der Schalter ist noch unbekannt: kein Abschnitt, kein Flackern assert.deepEqual(laden.konten, []) assert.equal(ansichtsZustand().projekteStatus, 'laden') const fehler = ansichtsZustand({ data: null, error: KONTEN_FEHLER }) assert.equal(fehler.projekteStatus, 'fehler') assert.equal(fehler.fehler, KONTEN_FEHLER) assert.equal(fehler.auftraegeStatus, 'aus') // ohne Projektkonten kein Schalter, also auch keine Aufträge assert.equal(KONTEN_FEHLER, 'Die Projektübersicht konnte nicht geladen werden.') }) test('ansichtsZustand: Schalter aus – Projekte bereit, kein Aufträge-Abschnitt', () => { const z = ansichtsZustand({ data: { konten: konten({ auftraegeAktiv: false }), auftraege: null, auftraegeFehler: '' }, error: '' }) assert.equal(z.projekteStatus, 'bereit') assert.equal(z.auftraegeAktiv, false) assert.equal(z.auftraegeStatus, 'aus') assert.equal(z.konten.length, 1) assert.deepEqual(z.ohneProjekt, { berechnetCents: 5000, bezahltCents: 0, anzahl: 1 }) assert.equal(z.stripeOk, true) assert.deepEqual(z.kunde, { id: 'k1', name: 'Anelia Wolf', kundenart: 'privat' }) assert.equal(z.nachladeFehler, '') }) test('ansichtsZustand: Schalter an – Aufträge bereit oder mit eigenem Fehler, die Projekte bleiben', () => { const liste = [{ id: 'a1' }] const bereit = ansichtsZustand({ data: { konten: konten({ auftraegeAktiv: true }), auftraege: liste, auftraegeFehler: '' }, error: '' }) assert.equal(bereit.auftraegeAktiv, true) assert.equal(bereit.auftraegeStatus, 'bereit') assert.deepEqual(bereit.auftraege, liste) const kaputt = ansichtsZustand({ data: { konten: konten({ auftraegeAktiv: true }), auftraege: null, auftraegeFehler: 'Interner Fehler.' }, error: '', }) assert.equal(kaputt.projekteStatus, 'bereit') assert.equal(kaputt.auftraegeStatus, 'fehler') assert.equal(kaputt.auftraegeFehler, 'Interner Fehler.') assert.deepEqual(kaputt.auftraege, []) }) test('ansichtsZustand: Neuladen scheitert – die alten Daten bleiben, die Meldung steht daneben', () => { const data = { konten: konten({ auftraegeAktiv: true }), auftraege: [{ id: 'a1' }], auftraegeFehler: '' } const z = ansichtsZustand({ data, error: 'Das Kundenportal ist gerade nicht erreichbar.' }) assert.equal(z.projekteStatus, 'bereit') assert.equal(z.fehler, '') assert.equal(z.nachladeFehler, 'Das Kundenportal ist gerade nicht erreichbar.') assert.equal(z.konten.length, 1) assert.equal(z.auftraegeStatus, 'bereit') assert.equal(z.auftraege.length, 1) }) test('ansichtsZustand: unvollständige Antworten brechen die Ansicht nicht', () => { for (const data of [{}, { konten: null }, { konten: {} }, { konten: { konten: 'x', ohneProjekt: 5, kunde: 'k' } }]) { const z = ansichtsZustand({ data, error: '' }) assert.equal(z.projekteStatus, 'bereit') assert.deepEqual(z.konten, []) assert.deepEqual(z.ohneProjekt, OHNE_PROJEKT_LEER) assert.equal(z.stripeOk, true) assert.equal(z.kunde, null) assert.equal(z.auftraegeAktiv, false) assert.equal(z.auftraegeStatus, 'aus') } assert.equal(ansichtsZustand({ data: { konten: konten({ stripeOk: false }) }, error: '' }).stripeOk, false) }) test('kundeMitKundenart: ein gültiger Wert des Portals gewinnt gegen das veraltete Kundendokument', () => { const doc = { $id: 'k1', name: 'Anelia Wolf', kundenart: '' } assert.deepEqual(kundeMitKundenart(doc, { id: 'k1', kundenart: 'privat' }), { ...doc, kundenart: 'privat' }) assert.equal(doc.kundenart, '') // das Dokument der Seite wird nicht verändert assert.equal(kundeMitKundenart({ ...doc, kundenart: 'unternehmen' }, { kundenart: 'privat' }).kundenart, 'privat') }) test('kundeMitKundenart: ohne gültigen Wert vom Portal bleibt das Dokument (dieselbe Referenz)', () => { const doc = { $id: 'k1', kundenart: 'unternehmen' } for (const info of [null, undefined, {}, { kundenart: '' }, { kundenart: 'firma' }, { kundenart: 5 }, 'privat']) { assert.equal(kundeMitKundenart(doc, info), doc) } assert.equal(kundeMitKundenart(null, { kundenart: 'privat' }), null) }) test('erstelltMeldung: gesendet, Entwurf und die Meldung nach festgeschriebener, nicht gesendeter Rechnung', () => { assert.deepEqual( erstelltMeldung({ number: 'WEBKLAR-0009', customerEmail: 'a@b.de' }, { senden: true }), { tone: 'ok', text: 'Rechnung WEBKLAR-0009 wurde an a@b.de gesendet.' }, ) assert.deepEqual(erstelltMeldung({ id: 'in_1' }, { senden: false }), { tone: 'ok', text: 'Der Entwurf wurde gespeichert.' }) assert.deepEqual(erstelltMeldung(null, undefined), { tone: 'ok', text: 'Der Entwurf wurde gespeichert.' }) assert.deepEqual(erstelltMeldung(null, null), { tone: 'ok', text: 'Der Entwurf wurde gespeichert.' }) const text = 'Rechnung WEBKLAR-0010 wurde festgeschrieben, aber nicht versendet. Bitte in der Liste erneut senden.' assert.deepEqual(erstelltMeldung(null, { senden: true, notice: text, tone: 'warn', finalizedFilter: 'offen' }), { tone: 'warn', text }) assert.deepEqual(erstelltMeldung(null, { senden: true, notice: text }), { tone: 'ok', text }) }) test('auftragMeldung: Entwurf, gesendet und gesendet ohne zugestellte Mail', () => { assert.deepEqual(auftragMeldung({ status: 'entwurf', nummer: '' }), { tone: 'ok', text: 'Der Entwurf wurde gespeichert.' }) assert.deepEqual( auftragMeldung({ status: 'versendet', nummer: 'AB-2026-0001', gesendetAn: 'k@x.de', mailStatus: 'gesendet' }), { tone: 'ok', text: 'Auftragsbestätigung AB-2026-0001 wurde an k@x.de gesendet.' }, ) assert.deepEqual( auftragMeldung({ status: 'versendet', nummer: 'AB-2026-0001', gesendetAn: 'k@x.de', mailStatus: 'fehler' }), { tone: 'warn', text: 'Auftragsbestätigung AB-2026-0001 ist versendet, die Mail an den Kunden wurde aber nicht zugestellt. Bitte in der Liste erneut senden.' }, ) assert.deepEqual(auftragMeldung(null), { tone: 'ok', text: 'Die Auftragsbestätigung wurde gespeichert.' }) })