import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import os from 'node:os'; import path from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; /* * Befund 67 zur Fassung 5.9.0. * * `scripts/pruefsummen.mjs` schrieb `new Date().toISOString().slice(0, 10)` als * "Erstellt am" in die Pruefsummendatei. `toISOString` liefert Weltzeit, und * `.slice(0, 10)` schneidet das 'Z' ab, das den Wert als Weltzeit auswiese. * Uebrig bleibt ein nackter Kalendertag unter einer deutschen Beschriftung - * nach ISO 8601 als Ortszeit zu lesen. Deutschland liegt oestlich von * Greenwich; zwischen 00:00 und 02:00 Ortszeit (Sommerzeit) beziehungsweise * 00:00 und 01:00 (Winterzeit) traegt die Datei damit den Vortag, waehrend die * Pakete daneben den laufenden Tag nennen. Die Datei ist ausdruecklich als Nachweis gedacht: * "Ein Pruefer muss sagen koennen, mit welchem Programmstand eine Unterlage * erstellt wurde." * * WARUM DER LAUF IN EINEM EIGENEN VERZEICHNIS STATTFINDET: Das Skript ist ein * ausfuehrbares Modul ohne Ausfuhren - es liest `package.json` und `release/` * ueber seinen eigenen Ort und schreibt die Datei dorthin. Geprueft wird * deshalb das Skript selbst, mit einer Nachbildung des Projektordners im * Kratzbereich; der Projektbaum wird nicht angefasst. * * WARUM ZWEI ZEITZONEN: Zu jedem Zeitpunkt weicht mindestens eine der beiden * vom Weltzeit-Tag ab - UTC+14 ab 10:00 UTC, UTC-12 bis 12:00 UTC. Damit faellt * der Fall gegen den Altstand unabhaengig davon, wann er laeuft und in welcher * Zeitzone der Rechner steht. */ const VERSION = '9.9.9'; const PAKETNAME = `LSA-Planer-Setup-${VERSION}.exe`; /** Zeitzonen, deren Kalendertag zu keiner Stunde beide mit dem der Weltzeit uebereinstimmt. */ const ZONEN = ['Etc/GMT-14', 'Etc/GMT+12'] as const; let wurzel = ''; /** Kalendertag, den eine Uhr in dieser Zeitzone gerade zeigt. */ function tagIn(zone: string): string { return new Intl.DateTimeFormat('en-CA', { timeZone: zone, year: 'numeric', month: '2-digit', day: '2-digit', }).format(new Date()); } beforeAll(() => { wurzel = mkdtempSync(path.join(os.tmpdir(), 'lsa-pruefsummen-')); mkdirSync(path.join(wurzel, 'scripts')); mkdirSync(path.join(wurzel, 'release')); writeFileSync(path.join(wurzel, 'package.json'), JSON.stringify({ version: VERSION }), 'utf8'); writeFileSync(path.join(wurzel, 'release', PAKETNAME), 'Platzhalter statt eines Pakets', 'utf8'); copyFileSync('scripts/pruefsummen.mjs', path.join(wurzel, 'scripts', 'pruefsummen.mjs')); }); afterAll(() => { if (wurzel !== '') rmSync(wurzel, { recursive: true, force: true }); }); /** Laesst das Skript in der angegebenen Zeitzone laufen und gibt die Datei zurueck. */ function laufIn(zone: string): string { const lauf = spawnSync(process.execPath, [path.join(wurzel, 'scripts', 'pruefsummen.mjs')], { env: { ...process.env, TZ: zone }, encoding: 'utf8', }); expect(lauf.status, `Lauf in ${zone}: ${lauf.stderr}`).toBe(0); return readFileSync(path.join(wurzel, 'release', `PRUEFSUMMEN-${VERSION}.txt`), 'utf8'); } describe('Pruefsummendatei - das Erstellungsdatum', () => { it.each(ZONEN)('nennt in %s den oertlichen Kalendertag, nicht den der Weltzeit', (zone) => { const vorher = tagIn(zone); const datei = laufIn(zone); const nachher = tagIn(zone); const treffer = /Erstellt am (\d{4}-\d{2}-\d{2})/.exec(datei); expect(treffer, 'Zeile "Erstellt am" nicht gefunden').not.toBeNull(); // Zwei Werte, falls der Lauf ueber Mitternacht dieser Zone hinweg faellt. expect([vorher, nachher], `Erstellungsdatum in ${zone}`).toContain(treffer![1]); }); it('schreibt die uebrigen Angaben der Nachweisdatei unveraendert', () => { // Gegenprobe: Die Aenderung betrifft nur das Datum. const datei = laufIn('Etc/GMT-14'); expect(datei, 'Kopfzeile').toContain(`LSA-Planer Professional ${VERSION} – Prüfsummen`); expect(datei, 'Name des Pakets').toContain(PAKETNAME); // Als Zeichennummer geschrieben, nicht als Zeichen: Die Bytefolgemarke // selbst im Quelltext stehen zu lassen, beanstandet der Linter zu Recht. expect(datei.charCodeAt(0), 'Bytefolgemarke').toBe(0xfeff); expect(datei, 'Hinweis zur Aussagekraft').toContain('Was eine Prüfsumme leistet'); }); });