waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
| 1 | /** |
| 2 | * Rauchtest gegen das **Store-Paket** (AppX/MSIX). |
| 3 | * |
| 4 | * ## Warum es diesen Test neben `gepackt.spec.ts` gibt |
| 5 | * |
| 6 | * `gepackt.spec.ts` misst den NSIS-Bau unter `release/win-unpacked/`. Das |
| 7 | * Store-Paket ist ein anderes Erzeugnis mit einer anderen Umgebung: eigene |
| 8 | * Paketidentität, eigener AppContainer, eigene Registrierungssicht. Ein Test, |
| 9 | * der den einen Bau misst, sagt über den anderen nichts. |
| 10 | * |
| 11 | * ## Was hier geprüft wird – und warum gerade das |
| 12 | * |
| 13 | * Der Punkt, an dem sich die beiden Wege am ehesten unterscheiden, ist der |
| 14 | * **Ablageort der Nutzerdaten**. Windows kann den Anwendungsdatenordner eines |
| 15 | * Paketes umlenken, üblicherweise nach `%LOCALAPPDATA%\Packages\…`. Genau |
| 16 | * davon ging dieses Projekt lange aus: `docs/datenschutz.md` 4.6 und 7.4 |
| 17 | * sagten es dem Leser zu, und `docs/stand.md` führte es als „am gepackten |
| 18 | * Store-Paket zu messen“. |
| 19 | * |
| 20 | * **Gemessen am 28.08.2026: Es wird nicht umgelenkt.** Das Paket lief unter |
| 21 | * seiner echten Store-Identität, und `lernstand.db-wal` und `-shm` – die |
| 22 | * Begleitdateien, die SQLite nur führt, solange die Datenbank offen ist – |
| 23 | * lagen unter `%APPDATA%\Waffensachkunde Lernsoftware\`. Im paketeigenen |
| 24 | * Bereich lag ausschließlich, was Windows selbst dort anlegt. |
| 25 | * |
| 26 | * Dieser Test hält dieses Ergebnis fest. Er behauptet nichts über Windows, |
| 27 | * sondern misst, was diese Anwendung in dieser Verpackung tut – und schlägt |
| 28 | * an, wenn eine künftige Windows- oder electron-builder-Fassung es ändert. |
| 29 | * Dann sind 4.6 und 7.4 der Datenschutzerklärung nachzuführen, nicht der |
| 30 | * Test. |
| 31 | * |
| 32 | * ## Eine Falle für den, der das nachprüfen will |
| 33 | * |
| 34 | * Eine Suche nach `lernstand.db` über das Benutzerprofil findet einen Treffer |
| 35 | * unter `%LOCALAPPDATA%\Packages\Claude_…\LocalCache\Roaming\` und legt den |
| 36 | * Schluss nahe, es werde eben doch umgelenkt. Wird es nicht: Am 28.08.2026 |
| 37 | * mit `fsutil file queryfileid` gegengeprüft – dieser Pfad und |
| 38 | * `%APPDATA%\Waffensachkunde Lernsoftware\` tragen **dieselbe NTFS-Datei-ID** |
| 39 | * und denselben SHA-256. Es ist eine Datei, gesehen durch die Umlenkung eines |
| 40 | * **fremden** Paketbehälters, nicht durch die dieser Anwendung. Wer das |
| 41 | * nachmisst, prüfe die Datei-ID, nicht den Pfad. |
| 42 | * |
| 43 | * ## Warum er sich überspringt statt fehlzuschlagen |
| 44 | * |
| 45 | * Er braucht ein registriertes Paket, und das entsteht nicht beim Bauen, |
| 46 | * sondern durch einen bewussten Schritt am Rechner (`npm run dist:store`, |
| 47 | * auspacken, `Add-AppxPackage -Register`). Ein harter Abbruch machte jeden |
| 48 | * gewöhnlichen Lauf rot, ohne dass etwas falsch wäre. Übersprungen **mit |
| 49 | * Grund** ist die ehrliche Form: Der Gate-Bericht sagt dann, was er nicht |
| 50 | * geprüft hat. |
| 51 | */ |
| 52 | import { execFileSync } from 'node:child_process'; |
| 53 | import { existsSync } from 'node:fs'; |
| 54 | import { join } from 'node:path'; |
| 55 | |
| 56 | import { expect, test } from './konsolenwache'; |
| 57 | |
| 58 | /** Die Paketfamilie aus dem Partner Center – Produktverwaltung, Product Identity. */ |
| 59 | const PAKETFAMILIE = 'O-W.Waffensachkunde-Lernsoftware-OW_c9rr4ryw66n3y'; |
| 60 | |
| 61 | /* Zugriff über den Index und nicht über die Punktschreibweise: `process.env` |
| 62 | ist eine Indexsignatur, und `noPropertyAccessFromIndexSignature` besteht |
| 63 | zu Recht darauf — bei `process.env.APPDATA` fiele ein Tippfehler erst zur |
| 64 | Laufzeit auf, und zwar als leerer Pfad statt als Fehler. */ |
| 65 | |
| 66 | /** Der Ordner, den die Anwendung ohne Umlenkung benutzt. */ |
| 67 | const KLASSISCH = join(process.env['APPDATA'] ?? '', 'Waffensachkunde Lernsoftware'); |
| 68 | |
| 69 | /** Der paketeigene Bereich, in den Windows umlenken **könnte**. */ |
| 70 | const PAKETABLAGE = join(process.env['LOCALAPPDATA'] ?? '', 'Packages', PAKETFAMILIE); |
| 71 | |
| 72 | /** Ist das Paket auf diesem Rechner registriert? `null`, wenn nicht messbar. */ |
| 73 | function paketRegistriert(): boolean | null { |
| 74 | if (process.platform !== 'win32') { |
| 75 | return null; |
| 76 | } |
| 77 | try { |
| 78 | const ausgabe = execFileSync( |
| 79 | 'powershell', |
| 80 | [ |
| 81 | '-NoProfile', |
| 82 | '-Command', |
| 83 | `(Get-AppxPackage -Name 'O-W.Waffensachkunde-Lernsoftware-OW').PackageFullName`, |
| 84 | ], |
| 85 | { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }, |
| 86 | ).trim(); |
| 87 | return ausgabe.length > 0; |
| 88 | } catch { |
| 89 | /* Kein PowerShell, keine Berechtigung, kein Windows – nicht messbar. */ |
| 90 | return null; |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | test.describe('Store-Paket – Ablageort der Nutzerdaten', () => { |
| 95 | test.beforeAll(() => { |
| 96 | const registriert = paketRegistriert(); |
| 97 | test.skip( |
| 98 | registriert !== true, |
| 99 | 'Kein registriertes Store-Paket auf diesem Rechner. Erzeugen mit ' + |
| 100 | '`npm run dist:store`, auspacken (die Namen im AppX sind ' + |
| 101 | 'prozentkodiert!) und `Add-AppxPackage -Register …\\AppxManifest.xml`.', |
| 102 | ); |
| 103 | }); |
| 104 | |
| 105 | test('legt den Lernstand am klassischen Ort ab, nicht in der Paketablage', () => { |
| 106 | /* |
| 107 | Die eine Aussage, auf die sich die Datenschutzerklärung stützt. Geprüft |
| 108 | wird die Datenbank selbst und nicht ein Ordner: Ein Ordner entsteht auch |
| 109 | leer, eine `lernstand.db` nur, wenn die Anwendung dort wirklich |
| 110 | geschrieben hat. |
| 111 | */ |
| 112 | expect( |
| 113 | existsSync(join(KLASSISCH, 'lernstand.db')), |
| 114 | `Unter ${KLASSISCH} liegt keine lernstand.db. Entweder hat die ` + |
| 115 | 'Anwendung dort noch nie geschrieben, oder der Ablageort hat sich ' + |
| 116 | 'geändert — dann sind docs/datenschutz.md 4.6 und 7.4 nachzuführen.', |
| 117 | ).toBe(true); |
| 118 | }); |
| 119 | |
| 120 | test('legt keine zweite Datenbank in der Paketablage an', () => { |
| 121 | /* |
| 122 | Die Gegenrichtung, und der eigentliche Wächter: Sobald Windows oder |
| 123 | electron-builder die Umlenkung doch einschalten, entsteht hier eine |
| 124 | zweite lernstand.db — und dann stimmt die Zusage in der |
| 125 | Datenschutzerklärung nicht mehr, dass beide Fassungen sich denselben |
| 126 | Lernstand teilen. |
| 127 | */ |
| 128 | const umgelenkt = [ |
| 129 | join(PAKETABLAGE, 'LocalCache', 'Roaming', 'Waffensachkunde Lernsoftware', 'lernstand.db'), |
| 130 | join(PAKETABLAGE, 'RoamingState', 'Waffensachkunde Lernsoftware', 'lernstand.db'), |
| 131 | join(PAKETABLAGE, 'LocalState', 'Waffensachkunde Lernsoftware', 'lernstand.db'), |
| 132 | ]; |
| 133 | const gefunden = umgelenkt.filter((pfad) => existsSync(pfad)); |
| 134 | expect( |
| 135 | gefunden, |
| 136 | 'In der Paketablage liegt ein Lernstand. Windows lenkt den ' + |
| 137 | 'Anwendungsdatenordner jetzt also doch um. Das ist kein Fehler dieser ' + |
| 138 | 'Anwendung, aber docs/datenschutz.md 4.6 und 7.4 behaupten dann das ' + |
| 139 | 'Gegenteil und sind vor der nächsten Veröffentlichung nachzuführen.', |
| 140 | ).toEqual([]); |
| 141 | }); |
| 142 | }); |