lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests tools verweiseNachDraussen.test.ts

6,4 KB Rohdatei
tests/tools/verweiseNachDraussen.test.ts — 149 Zeilen
1 import { readFileSync } from 'node:fs';
2 import { describe, expect, it } from 'vitest';
3 import { VERWEISZIELE, verweisadresse } from '../../electron/verweise';
4
5 /**
6 * Die Verweise, die aus dem Programm herausführen.
7 *
8 * DREI ZIELE, UND DER RENDERER KENNT KEINE ADRESSE. Er schickt einen Schlüssel
9 * über `IPC.oeffneVerweis`; die Adresse dahinter steht im Hauptprozess. Das ist
10 * der Kern: `shell.openExternal` gibt eine Zeichenkette an das Betriebssystem,
11 * und nähme der Kanal eine Adresse entgegen, bestimmte am Ende jeder, der etwas
12 * in die Oberfläche trägt, wohin sie zeigt. Über genau dieses Muster ließ sich
13 * im Altbestand mit einer präparierten Projektdatei beliebiger Code ausführen.
14 *
15 * WAS HIER GEPRÜFT WIRD:
16 * - Jeder Schlüssel führt auf eine Adresse, und zwar auf https ohne Anhängsel.
17 * - Was nicht in der Liste steht, öffnet nichts - auch nicht mit Tricks.
18 * - Die Adresse des Quellcodes ist dieselbe wie in Beilage und README.
19 * Mehrere Stellen für eine Adresse laufen auseinander, und eine falsche wäre
20 * schlimmer als keine (Artikel 3 der EUPL).
21 */
22
23 const UEBER = readFileSync('src/ui/ueber.ts', 'utf8');
24 const MENUE = readFileSync('electron/menu.ts', 'utf8');
25 const BEILAGE = readFileSync('QUELLCODE.txt', 'utf8');
26 const LIESMICH = readFileSync('README.md', 'utf8');
27
28 describe('Jeder Schluessel fuehrt auf genau eine saubere Adresse', () => {
29 it('gibt es ueberhaupt Ziele', () => {
30 // Ohne diesen Fall liefen die beiden darunter über eine leere Liste.
31 expect(VERWEISZIELE.length, 'Verweisziele').toBeGreaterThan(0);
32 });
33
34 it.each([...VERWEISZIELE])('fuehrt "%s" auf eine https-Adresse ohne Anhaengsel', (ziel) => {
35 const adresse = verweisadresse(ziel);
36 expect(adresse, `Adresse zu ${ziel}`).not.toBeNull();
37 const url = new URL(String(adresse));
38 expect(url.protocol, `Protokoll von ${ziel}`).toBe('https:');
39 /*
40 * KEINE VERFOLGUNGSPARAMETER. Wer einen dieser Verweise anklickt, soll dem
41 * Ziel nichts mitteilen, das er nicht ohnehin mitteilte, indem er die
42 * Adresse eintippt - kein `?ref=`, keine Kennung, kein Anker.
43 */
44 expect(url.search, `${ziel} traegt eine Abfragezeichenkette`).toBe('');
45 expect(url.hash, `${ziel} traegt einen Anker`).toBe('');
46 expect(url.username + url.password, `${ziel} traegt Anmeldedaten`).toBe('');
47 });
48 });
49
50 describe('Was nicht in der Liste steht, oeffnet nichts', () => {
51 it.each([
52 ['unbekannt', 'ein Wort, das es nicht gibt'],
53 ['', 'die leere Zeichenkette'],
54 ['https://boese.invalid/', 'eine Adresse statt eines Schluessels'],
55 ['__proto__', 'ein Eigenname des Prototyps'],
56 ['constructor', 'ein zweiter'],
57 ['toString', 'ein dritter'],
58 ])('weist %s ab (%s)', (ziel, _grund) => {
59 expect(verweisadresse(ziel)).toBeNull();
60 });
61
62 it.each([
63 [null, 'null'],
64 [undefined, 'undefined'],
65 [42, 'eine Zahl'],
66 [{}, 'ein Objekt'],
67 [['quellcode'], 'ein Feld mit gueltigem Inhalt'],
68 ])('weist %s ab (%s)', (ziel, _grund) => {
69 expect(verweisadresse(ziel)).toBeNull();
70 });
71
72 it('gibt kein Ergebnis zurueck, das nicht aus der Liste stammt', () => {
73 /*
74 * Die Gegenprobe zu allem darüber: Was auch immer hineingeht, es kommt
75 * entweder `null` heraus oder eine der hinterlegten Adressen. Ein
76 * Rückfall auf irgendetwas gäbe es nicht.
77 */
78 const erlaubt = new Set(VERWEISZIELE.map((z) => verweisadresse(z)));
79 for (const unsinn of ['unbekannt', '', 'https://boese.invalid/', '__proto__', 'valueOf']) {
80 const ergebnis = verweisadresse(unsinn);
81 expect(ergebnis === null || erlaubt.has(ergebnis), `Ergebnis zu ${unsinn}`).toBe(true);
82 }
83 });
84 });
85
86 describe('Die Adressen decken sich mit den Unterlagen', () => {
87 it('nennt jede Unterlage denselben Quellcode-Ort wie der Hauptprozess', () => {
88 /*
89 * Artikel 3 der EUPL: Der Vermerk verweist auf einen Speicherort, an dem
90 * der Quellcode unentgeltlich zu bekommen ist. Dieser Fall haelt die
91 * Beilage und die README gegen den Hauptprozess. Die Schaltflaeche im
92 * "Über"-Fenster fuehrt jetzt dorthin - eine abweichende Adresse waere
93 * schlimmer als keine Schaltflaeche.
94 */
95 const ort = verweisadresse('quellcode');
96 expect(ort, 'Adresse zum Quellcode').not.toBeNull();
97 for (const [name, text] of [
98 ['QUELLCODE.txt', BEILAGE],
99 ['README.md', LIESMICH],
100 ] as const) {
101 expect(text, `${name} nennt einen anderen Ort`).toContain(String(ort));
102 }
103 });
104
105 it('haelt das "Über"-Fenster keine eigene Fassung der Adresse', () => {
106 /*
107 * Die Kehrseite des Falles darueber, und der wichtigere: Das Fenster ZEIGT
108 * die Adresse an - es darf sie aber nicht ein zweites Mal fuehren. Bis zum
109 * Zusammenlegen stand sie dort als eigene Konstante und im Hauptprozess
110 * noch einmal; zwei Staende fuer eine Adresse, und Artikel 3 der EUPL
111 * haengt daran.
112 */
113 expect(UEBER, 'das Fenster fuehrt die Adresse selbst').not.toContain('olaf-willerding.de');
114 expect(UEBER, 'das Fenster holt die Adresse nicht aus der Tafel').toContain(
115 'VERWEISADRESSEN.quellcode',
116 );
117 });
118
119 it('oeffnet das Hilfemenue dieselbe Unterstuetzungsseite', () => {
120 // Zwei Wege zu einer Seite - der Menüeintrag und die Schaltfläche im
121 // "Über"-Fenster. Zwei Adressen wären eine zu viel.
122 const ort = verweisadresse('unterstuetzung');
123 expect(ort, 'Adresse zur Unterstuetzung').not.toBeNull();
124 expect(MENUE, 'das Hilfemenue nennt eine andere Adresse').toContain(String(ort));
125 });
126
127 it('sagt das "Über"-Fenster vorher, dass der Browser aufgeht', () => {
128 /*
129 * Eine Schaltfläche, die das Programm verlässt, kündigt das an - im Text
130 * und nicht in der Beschriftung. Das ist die Gepflogenheit dieses
131 * Programms; der Luftbild-Dialog nennt ebenso im Fließtext, was den
132 * Rechner verlässt.
133 */
134 const einzeilig = UEBER.replace(/\s+/g, ' ');
135 expect(einzeilig, 'das Fenster kuendigt den Browser nicht an').toContain('in Ihrem Browser');
136 expect(einzeilig, 'das Fenster verschweigt, dass es selbst nichts abruft').toContain(
137 'keine Verbindung auf',
138 );
139 });
140
141 it('nennt das "Über"-Fenster die Unterstuetzung als freiwillig', () => {
142 // Der Ton ist Teil der Sache: Das Programm ist frei und kostet nichts.
143 const einzeilig = UEBER.replace(/\s+/g, ' ');
144 expect(einzeilig, 'die Freiwilligkeit fehlt').toMatch(/freiwillig/);
145 expect(einzeilig, 'der Hinweis auf die Nutzung fehlt').toContain(
146 'auf die Nutzung hat das keinen Einfluss',
147 );
148 });
149 });