waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
| 1 | /** |
| 2 | * Der Meldetext zu einer Frage. |
| 3 | * |
| 4 | * Zwei Dinge stehen hier auf dem Spiel: dass die Meldung **eindeutig** ist – |
| 5 | * die amtliche Nummer allein ist es nicht – und dass **nichts** über die |
| 6 | * meldende Person hineingerät. Der Text verlässt später das Gerät. |
| 7 | */ |
| 8 | |
| 9 | import { readFileSync } from 'node:fs'; |
| 10 | import { join } from 'node:path'; |
| 11 | |
| 12 | import { describe, expect, it } from 'vitest'; |
| 13 | |
| 14 | import { KONTAKT } from '../src/shared/kontakt'; |
| 15 | import type { Katalog } from '../src/shared/katalog'; |
| 16 | import { meldetextBauen } from '../src/shared/meldetext'; |
| 17 | |
| 18 | const FRAGE = { id: 'I.1-01', amtliche_nummer: '1.01', seite: 6 }; |
| 19 | const KATALOG = { |
| 20 | herausgeber: 'Bundesverwaltungsamt', |
| 21 | stand: '2024-12-16', |
| 22 | quelldatei_sha256: '7e5e41faee0a9198b6ba3e2fe3b46ef313448b06753516579596f98212b1d108', |
| 23 | }; |
| 24 | const PROGRAMM = { fassung: '0.12.0', baukennung: '0218791', baustand: '2026-08-22' }; |
| 25 | |
| 26 | /** Der echte Katalog – für die Aussagen, die nur an ihm zu prüfen sind. */ |
| 27 | function echterKatalog(): Katalog { |
| 28 | const pfad = join(__dirname, '..', '..', 'content', 'katalog', 'katalog.json'); |
| 29 | return JSON.parse(readFileSync(pfad, 'utf-8')) as Katalog; |
| 30 | } |
| 31 | |
| 32 | describe('Der Meldetext', () => { |
| 33 | it('nennt Frage, Katalogstand und Programmfassung', () => { |
| 34 | const text = meldetextBauen(FRAGE, KATALOG, PROGRAMM); |
| 35 | |
| 36 | expect(text).toContain('1.01'); |
| 37 | expect(text).toContain('I.1-01'); |
| 38 | expect(text).toContain('Seite 6'); |
| 39 | expect(text).toContain('Bundesverwaltungsamt'); |
| 40 | expect(text).toContain('16.12.2024'); |
| 41 | expect(text).toContain('0.12.0'); |
| 42 | expect(text).toContain('0218791'); |
| 43 | expect(text).toContain(KONTAKT.epost); |
| 44 | }); |
| 45 | |
| 46 | it('kürzt die Prüfsumme und sagt, dass sie gekürzt ist', () => { |
| 47 | /* Die volle Summe hat 64 Zeichen und spränge die Zeile. Gekürzt genügt |
| 48 | sie, um zwei Katalogfassungen auseinanderzuhalten – aber das muss |
| 49 | dastehen, sonst hält der Empfänger sie für die ganze. */ |
| 50 | const text = meldetextBauen(FRAGE, KATALOG, PROGRAMM); |
| 51 | |
| 52 | expect(text).toContain('7e5e41faee0a'); |
| 53 | expect(text).toContain('(gekürzt)'); |
| 54 | expect(text).not.toContain(KATALOG.quelldatei_sha256); |
| 55 | }); |
| 56 | |
| 57 | it('kommt ohne Baukennung aus', () => { |
| 58 | /* Ohne Git zur Bauzeit gibt es keine. Dann steht dort nichts statt eines |
| 59 | nichtssagenden „unbekannt" – dasselbe Verhalten wie im Systemzustand. */ |
| 60 | const text = meldetextBauen(FRAGE, KATALOG, { ...PROGRAMM, baukennung: '', baustand: '' }); |
| 61 | |
| 62 | expect(text).toContain('Fassung 0.12.0'); |
| 63 | expect(text).not.toContain('Bau '); |
| 64 | }); |
| 65 | |
| 66 | it('führt zu jeder der 575 Fragen ihre eindeutige Kennung', () => { |
| 67 | /* |
| 68 | Der eigentliche Prüfstein. Nachgezählt am echten Katalog teilen 227 der |
| 69 | 575 Fragen ihre amtliche Nummer mit mindestens einer anderen – „01" gibt |
| 70 | es als II-01, III-01 und IV-01. Wer nur die Nummer meldet, meldet |
| 71 | dreideutig. |
| 72 | */ |
| 73 | const katalog = echterKatalog(); |
| 74 | |
| 75 | const mehrdeutig = katalog.fragen.filter( |
| 76 | (frage) => |
| 77 | katalog.fragen.filter((andere) => andere.amtliche_nummer === frage.amtliche_nummer).length > |
| 78 | 1, |
| 79 | ); |
| 80 | expect(mehrdeutig.length).toBeGreaterThan(200); |
| 81 | |
| 82 | for (const frage of katalog.fragen) { |
| 83 | const text = meldetextBauen(frage, katalog.meta, PROGRAMM); |
| 84 | expect(text).toContain(frage.id); |
| 85 | } |
| 86 | }); |
| 87 | |
| 88 | it('steht Zeichen für Zeichen fest – nichts kann versehentlich mitreisen', () => { |
| 89 | /* |
| 90 | Der stärkste Wächter, den es hier geben kann, und die eigentliche Sperre |
| 91 | gegen den Fehler, der beim nächsten Umbau naheliegt: schnell noch den |
| 92 | Profilnamen oder die gegebene Antwort mit hineinnehmen, weil es bei der |
| 93 | Bearbeitung hülfe. |
| 94 | |
| 95 | Ein Wortfilter fängt das nicht – er kennt nur die Wörter, an die jemand |
| 96 | vorher gedacht hat. Der vollständige Vergleich fängt jede Ergänzung, |
| 97 | auch die, die niemand vorhergesehen hat. Ändert sich der Wortlaut |
| 98 | absichtlich, ist diese Zeile mitzuändern; das ist der Sinn. |
| 99 | */ |
| 100 | expect(meldetextBauen(FRAGE, KATALOG, PROGRAMM)).toBe( |
| 101 | [ |
| 102 | 'Fehlermeldung zur Waffensachkunde-Lernsoftware', |
| 103 | `Senden an: ${KONTAKT.epost}`, |
| 104 | '', |
| 105 | 'Frage: 1.01 (Kennung I.1-01), Seite 6 der Vorlage', |
| 106 | 'Fragenkatalog: Bundesverwaltungsamt, Stand 16.12.2024, Prüfsumme 7e5e41faee0a (gekürzt)', |
| 107 | 'Programm: Fassung 0.12.0, Bau 0218791 vom 22.08.2026', |
| 108 | '', |
| 109 | 'Diese Angaben hat die Lernsoftware zusammengestellt. Sie enthalten nichts über die', |
| 110 | 'meldende Person und nichts über ihren Lernstand.', |
| 111 | '', |
| 112 | 'Was nicht stimmt (bitte hier beschreiben):', |
| 113 | '', |
| 114 | ].join('\n'), |
| 115 | ); |
| 116 | }); |
| 117 | |
| 118 | it('nennt keinen Pfad und keinen Rechnernamen', () => { |
| 119 | /* Die Angaben kommen aus vier Skalaren, nicht aus `AnwendungsInfo` – dort |
| 120 | hängt `datenbank.meldung` mit dran, die im Fehlerfall einen Dateipfad |
| 121 | enthalten kann. Diese Prüfung hält fest, dass die Trennung wirkt. */ |
| 122 | const text = meldetextBauen(FRAGE, KATALOG, PROGRAMM).toLowerCase(); |
| 123 | |
| 124 | for (const verboten of ['appdata', 'users', '.db', 'sqlite']) { |
| 125 | expect(text).not.toContain(verboten); |
| 126 | } |
| 127 | }); |
| 128 | |
| 129 | it('trennt Zeilen einheitlich und richtet nichts mit Leerzeichen aus', () => { |
| 130 | /* Der Text soll in einer Nur-Text-Mail genauso aussehen wie in einem |
| 131 | Forum. Ausgerichtete Spalten halten nur in Festbreitenschrift. */ |
| 132 | const text = meldetextBauen(FRAGE, KATALOG, PROGRAMM); |
| 133 | |
| 134 | expect(text).not.toContain('\r'); |
| 135 | expect(text).not.toMatch(/\S {2,}\S/u); |
| 136 | }); |
| 137 | }); |