Files
tickte-system/tests/setup-legal.test.mjs
T
ClaudeandClaude Opus 5.5 8319084930 Merge remote-tracking branch 'origin/test' into feature/oeffnungszeiten
Zusammenführung von TP2 und Webshop im Ticketsystem (origin/test 00fa229) mit Öffnungszeiten live. Die Funktionen
stehen in der Reihenfolge news, termine, shop, zeiten.

Konfliktdateien (6, erwartet war nur CustomerFunktionen.jsx; origin/test enthält seit der Vorbereitung auch die
beiden Webshop-Commits 0a2eab8 und 00fa229): scripts/setup-legal.mjs, src/components/customer/CustomerFunktionen.jsx,
src/lib/funktionenView.js, src/lib/teamApi.js, tests/setup-legal.test.mjs, tests/unit/funktionenView.test.mjs.

Entscheidungen: ersatzTicket-Behandlung der Zeiten und speicherStatus/K16_WARNUNG von TP2 bleiben beide in save()
(Abschalten der Zeiten zeigt abschaltMeldung, jede andere Aktion speicherStatus); Rechtliches-Karten Webshop (sort 10, 11)
vor der Karte der Öffnungszeiten (sort 12, DOCUMENTS hat 12 Einträge); siteKeyOf kennt termine, shop und zeiten; aktionen
behandelt shop und zeiten wie den Terminplaner (ticketbasiert, Abnahme der Zeiten mit hindernis); teamApi trägt Webshop-
und Zeiten-Aufrufe nebeneinander.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 06:19:42 +00:00

116 lines
6.6 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { COLLECTIONS, DOCUMENTS, waitForAttributes } from '../scripts/setup-legal.mjs'
const SCRIPT = fileURLToPath(new URL('../scripts/setup-legal.mjs', import.meta.url))
const indexOn = (id, attr) =>
COLLECTIONS.find((c) => c.id === id).indexes?.some((i) => i.attributes.includes(attr))
test('Indizes fuer alle Abfragen (equal/orderAsc)', () => {
assert.ok(indexOn('legalVersions', 'documentKey'))
assert.ok(indexOn('legalVersions', 'source'))
assert.ok(indexOn('legalDocuments', 'sort'))
assert.ok(indexOn('providerContracts', 'provider'))
})
test('waitForAttributes wartet, bis alle Attribute verfuegbar sind', async () => {
let calls = 0
const db = {
listAttributes: async () => {
calls++
return { attributes: [{ key: 'a', status: 'available' }, { key: 'b', status: calls < 3 ? 'processing' : 'available' }] }
},
}
await waitForAttributes(db, 'x', { intervalMs: 1, timeoutMs: 1000 })
assert.equal(calls, 3)
})
test('waitForAttributes bricht bei fehlgeschlagenem Attribut ab', async () => {
const db = { listAttributes: async () => ({ attributes: [{ key: 'b', status: 'failed', error: 'kaputt' }] }) }
await assert.rejects(waitForAttributes(db, 'x', { intervalMs: 1, timeoutMs: 1000 }), /b/)
})
test('documents_keys_eindeutig_und_sort_lueckenlos', () => {
const keys = DOCUMENTS.map((d) => d.key)
// Appwrite erlaubt als eigene Dokument-ID höchstens 36 Zeichen (a-z, 0-9, Bindestrich)
for (const k of keys) assert.match(k, /^[a-z0-9-]{1,36}$/, k)
assert.equal(new Set(keys).size, keys.length, 'Schluessel eindeutig')
assert.deepEqual(DOCUMENTS.map((d) => d.sort), DOCUMENTS.map((_, i) => i + 1), 'sort 1..n in Reihenfolge, ohne Luecke')
for (const d of DOCUMENTS) {
assert.ok(d.title.length > 0 && d.title.length <= 200, `${d.key}: Titel (Spalte title: 200 Zeichen)`)
assert.ok(d.liveUrl.length <= 500, `${d.key}: liveUrl (Spalte liveUrl: 500 Zeichen)`)
assert.match(d.liveUrl, /^https:\/\//, `${d.key}: liveUrl https`)
}
})
test('terminplaner_karten_vorhanden', () => {
const base = 'https://project.webklar.com/rechtliches/'
const erwartet = [
{ key: 'leistungsbeschreibung-terminplaner', title: 'Leistungsbeschreibung Terminplaner', sort: 7 },
{ key: 'avv-terminplaner', title: 'AVV Terminplaner (Auftragsverarbeitung)', sort: 8 },
{ key: 'widerrufsbelehrung-funktionen', title: 'Widerrufsbelehrung und Muster-Widerrufsformular (Funktionen)', sort: 9 },
{ key: 'leistungsbeschreibung-webshop', title: 'Leistungsbeschreibung Webshop', sort: 10 },
{ key: 'avv-webshop', title: 'AVV Webshop', sort: 11 },
]
for (const e of erwartet) {
const d = DOCUMENTS.find((x) => x.key === e.key)
assert.ok(d, e.key)
assert.equal(d.title, e.title)
assert.equal(d.sort, e.sort)
assert.equal(d.liveUrl, `${base}${e.key}.html`)
assert.ok(d.liveUrl.startsWith(base))
assert.ok(!/\d{4}-\d{2}-\d{2}/.test(d.liveUrl), 'Alias-Seite ohne Fassungsdatum')
}
// Oeffnungszeiten live: Karte 12, hinter den Terminplaner- und Webshop-Karten; der Schluessel hat 28 Zeichen (WOMS-Grenze 36, Ruling R35)
assert.equal(DOCUMENTS.length, 12, 'keine Analyse-Karten und keine weiteren')
})
test('zeiten_karte_vorhanden_am_ende', () => {
const d = DOCUMENTS.find((x) => x.key === 'leistungsbeschreibung-zeiten')
assert.ok(d, 'Karte Leistungsbeschreibung Oeffnungszeiten live')
assert.deepEqual(d, {
key: 'leistungsbeschreibung-zeiten',
title: 'Leistungsbeschreibung Öffnungszeiten live',
liveUrl: 'https://project.webklar.com/vertragsunterlagen/zeiten-leistungsbeschreibung.html',
sort: 12,
})
assert.ok(d.key.length <= 36, 'Dokument-ID hoechstens 36 Zeichen')
assert.equal(DOCUMENTS.at(-1), d, 'am Ende von DOCUMENTS, nach den Terminplaner- und Webshop-Karten')
assert.equal(DOCUMENTS.findIndex((x) => x.key === 'widerrufsbelehrung-funktionen'), 8, 'Terminplaner-Karten unveraendert davor')
})
test('bestehende_karten_unveraendert', () => {
assert.deepEqual(DOCUMENTS.slice(0, 9), [
{ key: 'agb', title: 'AGB', liveUrl: 'https://webklar.com/agb', sort: 1 },
{ key: 'abo-bedingungen', title: 'Abo-Bedingungen', liveUrl: 'https://project.webklar.com/abo-bedingungen.html', sort: 2 },
{ key: 'avv', title: 'AVV (Auftragsverarbeitung)', liveUrl: 'https://project.webklar.com/abo-bedingungen.html', sort: 3 },
{ key: 'datenschutz', title: 'Datenschutzerklärung (Website und Portal)', liveUrl: 'https://project.webklar.com/datenschutz.html', sort: 4 },
{ key: 'impressum', title: 'Impressum', liveUrl: 'https://webklar.com/impressum', sort: 5 },
{ key: 'widerruf', title: 'Widerrufsbelehrung und -formular', liveUrl: 'https://project.webklar.com/widerrufen.html', sort: 6 },
{ key: 'leistungsbeschreibung-terminplaner', title: 'Leistungsbeschreibung Terminplaner', liveUrl: 'https://project.webklar.com/rechtliches/leistungsbeschreibung-terminplaner.html', sort: 7 },
{ key: 'avv-terminplaner', title: 'AVV Terminplaner (Auftragsverarbeitung)', liveUrl: 'https://project.webklar.com/rechtliches/avv-terminplaner.html', sort: 8 },
{ key: 'widerrufsbelehrung-funktionen', title: 'Widerrufsbelehrung und Muster-Widerrufsformular (Funktionen)', liveUrl: 'https://project.webklar.com/rechtliches/widerrufsbelehrung-funktionen.html', sort: 9 },
])
})
test('dry_run_listet_karten', () => {
// Kein API-Key, Endpunkt auf einen geschlossenen Port: ein Netzaufruf wuerde scheitern, das Skript muss trotzdem sauber enden
const env = { ...process.env, APPWRITE_ENDPOINT: 'http://127.0.0.1:9/v1' }
delete env.APPWRITE_API_KEY
const r = spawnSync(process.execPath, ['--max-old-space-size=256', SCRIPT, '--dry-run'], { env, encoding: 'utf8', timeout: 30000 })
assert.equal(r.status, 0, r.stderr)
assert.ok(!/ECONNREFUSED|fetch failed|APPWRITE_API_KEY required/.test(r.stderr), r.stderr)
const zeilen = r.stdout.split('\n')
for (const d of DOCUMENTS) assert.ok(zeilen.includes(`Rechtstext ${d.key}: ${d.title}`), d.key)
assert.ok(zeilen.some((z) => z.startsWith('Rechtstext leistungsbeschreibung-terminplaner:')))
assert.ok(zeilen.some((z) => z.startsWith('Rechtstext avv-terminplaner:')))
assert.ok(zeilen.some((z) => z.startsWith('Rechtstext widerrufsbelehrung-funktionen:')))
assert.ok(zeilen.some((z) => z.startsWith('Rechtstext leistungsbeschreibung-webshop:')))
assert.ok(zeilen.some((z) => z.startsWith('Rechtstext avv-webshop:')))
assert.ok(zeilen.includes('Rechtstext leistungsbeschreibung-zeiten: Leistungsbeschreibung Öffnungszeiten live'))
assert.ok(!/angelegt|vorhanden|Fertig/.test(r.stdout), 'Trockenlauf legt nichts an')
})