// test/auftraegeSchema.test.js import { test } from 'node:test' import assert from 'node:assert/strict' import { AUFTRAEGE_COLLECTIONS, AUFTRAEGE_DATETIMES, AUFTRAEGE_ZUSATZ_ATTRIBUTE } from '../server/services/auftraege/schema.js' import { schemaKeys } from './support/schema.js' // Spalten aus Übersicht V1 in Kurzschrift: sN = Text mit Größe N, i = ganze Zahl, b = Wahrheitswert, d = Datum und Zeit const SPALTEN = { auftraege: `nummer:s20 jahr:i laufnummer:i status:s20 kundenId:s64 kundeJson:s2000 projektId:s64 titel:s200 zeitquelleJson:s1000 art:s20 positionenJson:s20000 stundensatzCents:i schaetzungMinuten:i summeCents:i zahlungsplanJson:s1000 zahlungszielTage:i auftragVom:s10 auftragWeg:s20 startAm:s10 fertigBis:s10 texteFassung:s20 agbFassung:s20 pdfDatei:s300 pdfSha256:s64 gesendetAm:d gesendetAn:s255 mailStatus:s20 mailFehler:s500 tokenHash:s64 tokenGueltigBis:d bestaetigtAm:d bestaetigtUeber:s10 bestaetigtPdfSha256:s64 fruehbeginn:b fruehbeginnFassung:s20 ersetztId:s64 ersetztDurchId:s64 storniertAm:d widerrufenAm:d erstelltVon:s255 erstelltAm:d geaendertAm:d`, projektRechnungen: `invoiceId:s64 kundenId:s64 projektId:s64 auftragId:s64 art:s20 betragCents:i arbeitsblattIdsJson:s20000 rechnungsnummer:s64 aktiv:b erstelltVon:s255 erstelltAm:d`, } function attribut(kurz) { const [key, art] = kurz.split(':') if (art === 'i') return { key, type: 'integer', required: false } if (art === 'b') return { key, type: 'boolean', required: false } if (art === 'd') return { key, type: 'datetime', required: false } return { key, type: 'string', size: Number(art.slice(1)), required: false } } const erwartet = (name) => SPALTEN[name].trim().split(/\s+/).map(attribut) test('zwei Collections mit den Spalten aus Übersicht V1', () => { assert.deepEqual(Object.keys(AUFTRAEGE_COLLECTIONS), ['auftraege', 'projektRechnungen']) assert.deepEqual(AUFTRAEGE_COLLECTIONS.auftraege.attributes, erwartet('auftraege')) assert.deepEqual(AUFTRAEGE_COLLECTIONS.projektRechnungen.attributes, erwartet('projektRechnungen')) assert.equal(AUFTRAEGE_COLLECTIONS.auftraege.attributes.length, 42) assert.equal(AUFTRAEGE_COLLECTIONS.projektRechnungen.attributes.length, 11) }) test('Indizes und Zusatzfelder wie in Übersicht V1', () => { assert.deepEqual(AUFTRAEGE_COLLECTIONS.auftraege.indexes, [ { key: 'idx_kunde', type: 'key', attributes: ['kundenId'] }, { key: 'idx_projekt', type: 'key', attributes: ['projektId'] }, { key: 'idx_jahr', type: 'key', attributes: ['jahr'] }, { key: 'idx_token', type: 'key', attributes: ['tokenHash'] }, { key: 'idx_nummer', type: 'key', attributes: ['nummer'] }, ]) assert.deepEqual(AUFTRAEGE_COLLECTIONS.projektRechnungen.indexes, [ { key: 'idx_kunde', type: 'key', attributes: ['kundenId'] }, { key: 'idx_projekt', type: 'key', attributes: ['projektId'] }, { key: 'idx_auftrag', type: 'key', attributes: ['auftragId'] }, ]) assert.deepEqual(AUFTRAEGE_ZUSATZ_ATTRIBUTE, { customers: [{ key: 'kundenart', type: 'string', size: 20, required: false }], financeSettings: [{ key: 'steuernummer', type: 'string', size: 40, required: false }], }) }) test('AUFTRAEGE_DATETIMES nennt je Collection genau die Datumsfelder', () => { assert.deepEqual(AUFTRAEGE_DATETIMES, { auftraege: ['gesendetAm', 'tokenGueltigBis', 'bestaetigtAm', 'storniertAm', 'widerrufenAm', 'erstelltAm', 'geaendertAm'], projektRechnungen: ['erstelltAm'], }) }) test('schemaKeys kennt die neuen Collections und die Steuernummer; Bestehendes bleibt', () => { assert.deepEqual(schemaKeys('auftraege'), AUFTRAEGE_COLLECTIONS.auftraege.attributes.map((a) => a.key)) assert.deepEqual(schemaKeys('projektRechnungen'), AUFTRAEGE_COLLECTIONS.projektRechnungen.attributes.map((a) => a.key)) const einstellungen = schemaKeys('financeSettings') for (const key of ['steuernummer', 'bankDetailsText', 'taxReservePercent']) assert.ok(einstellungen.includes(key), key) assert.ok(!schemaKeys('financeFixedCosts').includes('steuernummer')) assert.ok(schemaKeys('portalFeatures').includes('unlockedBy') && schemaKeys('termineConfig').includes('einstellung')) assert.throws(() => schemaKeys('gibtEsNicht'), /fehlt im Setup-Skript/) })