Kundenansicht neu P10: Nachprüf-Skript prüft die ausgelieferten Seiten gegen alt oder neu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
416ebee18a
commit
3413355b14
2 files changed
+275
No files matched your search
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Kundenansicht neu (P10 Umschalten): prueft per HTTP, ob die oeffentlichen Seiten, das Manifest und die Abo-Bedingungen
|
||||
* so ausgeliefert werden, wie der Schalter KUNDENANSICHT_NEU es verlangt: "alt" (off und pilot) oder "neu" (on).
|
||||
* Nur lesend: je Pruefung ein GET ohne Cookies und ohne Umleitungen, kein Appwrite, kein Schreiben, keine Namen
|
||||
* oder Kundendaten in der Ausgabe (nur Adressen der Seiten und Gruende).
|
||||
* Usage:
|
||||
* node scripts/umschalten-nachpruefung.mjs https://project.webklar.com --erwartet=alt
|
||||
* node scripts/umschalten-nachpruefung.mjs https://project.webklar.com --erwartet=neu
|
||||
* Exit 0 = alle Zeilen gruen, 1 = mindestens eine Zeile rot, 2 = falscher Aufruf.
|
||||
*/
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
const ZEITLIMIT_MS = 15000
|
||||
|
||||
const html = (pfad, enthaelt, enthaeltNicht = []) => Object.freeze({ pfad, typ: 'html', enthaelt, enthaeltNicht })
|
||||
// Manifest: "schluessel=wert" prueft ein Feld, "shortcuts.name=X" einen Shortcut mit diesem Namen
|
||||
const manifest = (enthaelt) => Object.freeze({ pfad: '/manifest.webmanifest', typ: 'json', enthaelt, enthaeltNicht: [] })
|
||||
const pdf = Object.freeze({ pfad: '/WEBklar-Abo-Vertragsunterlagen.pdf', typ: 'pdf', enthaelt: ['application/pdf', '%PDF-'], enthaeltNicht: [] })
|
||||
|
||||
const NEU_FORMULAR = ['/neu/oeffentlich/oeffentlich.css', '/legal-form.js']
|
||||
|
||||
/** Marker je Erwartung; jede Zeile verlangt Status 200. */
|
||||
export const PRUEFUNGEN = Object.freeze({
|
||||
neu: Object.freeze([
|
||||
html('/dashboard.html', ['<meta name="kn-seite" content="neu">'], ['kb-page']),
|
||||
html('/login.html', ['content="#ffffff"', '/neu/tokens.css'], ['#9d3dfe', '/login.css']),
|
||||
html('/kuendigen.html', NEU_FORMULAR, ['/legal-form.css']),
|
||||
html('/widerrufen.html', NEU_FORMULAR, ['/legal-form.css']),
|
||||
html('/offline.html', ['content="#ffffff"'], ['#9d3dfe', '<link']),
|
||||
manifest([
|
||||
'id=/dashboard.html',
|
||||
'start_url=/dashboard.html',
|
||||
'scope=/',
|
||||
'background_color=#ffffff',
|
||||
'theme_color=#ffffff',
|
||||
'shortcuts.name=Chat',
|
||||
'shortcuts.name=Rechnungen',
|
||||
'shortcuts.name=Verträge',
|
||||
]),
|
||||
html('/abo-bedingungen.html', ['Version 6'], ['Version 5 · Stand', 'Entwurf – Prüfung durch Kanzlei offen']),
|
||||
pdf,
|
||||
]),
|
||||
alt: Object.freeze([
|
||||
html('/dashboard.html', ['class="portal-page dashboard-page kb-page"']),
|
||||
html('/login.html', ['/login.css', 'content="#09090b"']),
|
||||
html('/kuendigen.html', ['/legal-form.css']),
|
||||
html('/widerrufen.html', ['/legal-form.css']),
|
||||
html('/offline.html', ['#9d3dfe']),
|
||||
manifest(['background_color=#000000']),
|
||||
html('/abo-bedingungen.html', ['Version 5 · Stand']),
|
||||
pdf,
|
||||
]),
|
||||
})
|
||||
|
||||
const AUFRUF = 'Aufruf: node scripts/umschalten-nachpruefung.mjs <http(s)://host[:port]> --erwartet=neu|alt'
|
||||
|
||||
/** [basis, '--erwartet=neu'|'--erwartet=alt'] -> { basis, erwartet }; sonst Error "Aufruf: ...". */
|
||||
export function parseArgs(argv) {
|
||||
const args = Array.isArray(argv) ? argv : []
|
||||
const optionen = args.filter((a) => typeof a === 'string' && a.startsWith('--'))
|
||||
const rest = args.filter((a) => !optionen.includes(a))
|
||||
const erwartet = optionen.length === 1 ? /^--erwartet=(neu|alt)$/.exec(optionen[0])?.[1] : undefined
|
||||
const basis = rest.length === 1 ? rest[0] : undefined
|
||||
// nur Schema, Host und optional Port: kein Pfad, keine Zugangsdaten, keine Parameter
|
||||
if (!erwartet || typeof basis !== 'string' || !/^https?:\/\/([A-Za-z0-9.-]+|\[[0-9A-Fa-f:]+\])(:\d{1,5})?$/.test(basis)) throw new Error(AUFRUF)
|
||||
return { basis, erwartet }
|
||||
}
|
||||
|
||||
/** Ein Feld des Manifests nach "schluessel=wert" / "shortcuts.name=X" pruefen. */
|
||||
function manifestHat(daten, marker) {
|
||||
const gleich = marker.indexOf('=')
|
||||
const schluessel = marker.slice(0, gleich)
|
||||
const wert = marker.slice(gleich + 1)
|
||||
if (schluessel === 'shortcuts.name') return Array.isArray(daten?.shortcuts) && daten.shortcuts.some((s) => s?.name === wert)
|
||||
return daten?.[schluessel] === wert
|
||||
}
|
||||
|
||||
/** Eine Pruefung auswerten -> { pfad, ok, grund }. */
|
||||
async function pruefe(basis, pruefung, fetchFn) {
|
||||
const zeile = (ok, grund = '') => ({ pfad: pruefung.pfad, ok, grund })
|
||||
let antwort
|
||||
let text
|
||||
try {
|
||||
antwort = await fetchFn(basis + pruefung.pfad, {
|
||||
redirect: 'manual',
|
||||
headers: { 'cache-control': 'no-cache' },
|
||||
signal: AbortSignal.timeout(ZEITLIMIT_MS),
|
||||
})
|
||||
text = await antwort.text()
|
||||
} catch {
|
||||
return zeile(false, 'Netzfehler')
|
||||
}
|
||||
if (antwort.status !== 200) return zeile(false, `Status ${antwort.status} statt 200`)
|
||||
|
||||
if (pruefung.typ === 'pdf') {
|
||||
const art = antwort.headers.get('content-type') || ''
|
||||
if (!art.includes('application/pdf')) return zeile(false, `Inhaltstyp ${art || 'fehlt'} statt application/pdf`)
|
||||
return text.startsWith('%PDF-') ? zeile(true) : zeile(false, 'Beginn %PDF- fehlt')
|
||||
}
|
||||
|
||||
if (pruefung.typ === 'json') {
|
||||
let daten
|
||||
try {
|
||||
daten = JSON.parse(text)
|
||||
} catch {
|
||||
return zeile(false, 'kein JSON')
|
||||
}
|
||||
const fehlt = pruefung.enthaelt.find((m) => !manifestHat(daten, m))
|
||||
return fehlt ? zeile(false, `fehlt: ${fehlt}`) : zeile(true)
|
||||
}
|
||||
|
||||
const fehlt = pruefung.enthaelt.find((m) => !text.includes(m))
|
||||
if (fehlt) return zeile(false, `fehlt: ${fehlt}`)
|
||||
const verboten = pruefung.enthaeltNicht.find((m) => text.includes(m))
|
||||
return verboten ? zeile(false, `verboten: ${verboten}`) : zeile(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle Pruefungen der Erwartung ("neu" | "alt") gegen basis ausfuehren; ohne: Adressen, die ausgelassen werden
|
||||
* (z. B. die Fassung-6-Zeilen, solange sie nicht ausgeliefert werden). -> { ok, zeilen: [{ pfad, ok, grund }] }.
|
||||
*/
|
||||
export async function nachpruefen(basis, erwartet, { fetchFn = fetch, ohne = [] } = {}) {
|
||||
const liste = PRUEFUNGEN[erwartet]
|
||||
if (!liste) throw new Error(AUFRUF)
|
||||
const zeilen = []
|
||||
for (const pruefung of liste) {
|
||||
if (ohne.includes(pruefung.pfad)) continue
|
||||
zeilen.push(await pruefe(basis, pruefung, fetchFn))
|
||||
}
|
||||
return { ok: zeilen.every((z) => z.ok), zeilen }
|
||||
}
|
||||
|
||||
const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href
|
||||
if (invokedDirectly) {
|
||||
let args
|
||||
try {
|
||||
args = parseArgs(process.argv.slice(2))
|
||||
} catch (err) {
|
||||
console.error(err.message)
|
||||
process.exit(2)
|
||||
}
|
||||
nachpruefen(args.basis, args.erwartet)
|
||||
.then(({ ok, zeilen }) => {
|
||||
for (const z of zeilen) console.log(z.ok ? `ok ${z.pfad}` : `FEHLER ${z.pfad} ${z.grund}`)
|
||||
process.exit(ok ? 0 : 1)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`FEHLER ${err.message}`)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user