---
name: project_test_infra
description: Test-Coverage-Stand + wie man Test-Targets für bisher test-lose Projekte aufsetzt
metadata: 
  node_type: memory
  type: project
  originSessionId: c4e04d11-9b40-4c6a-9333-ce3bbf949ba2
---

**Stand 2026-06-03 (Branch development): 8 Test-Projekte, 232 Tests grün.**
`nx run-many -t test`: plato-customer-be (74), frontend-common (71), customer-fe (30),
plato-customer-fe (25), shared (13), command-be (8), customer-be (6), ui-common (5).

**Bug-Jagd (3 parallele Review-Agenten, von mir am Code gegengeprüft) → 7 echte Bugs gefixt:**
- `b8e3355` xlImport2Customer: `customer_number='undefined'` (`String(x)??''` greift nie).
- `f0c4e48` outbox.peek: Command-Verlust, wenn Crash zwischen Outbox-Remove und Inflight-Write
  (Reihenfolge gedreht: erst Inflight, recoverInflight dedupt per id).
- `7dd3dc0` dedupeByCustomerNumber: doppelte Importnummer lautlos verloren (jetzt erster gewinnt + warn).
- `fc9cc22` setSort (Vergleich nach Zuweisung), NumberInput (v-model.number), setEditing (Karten-Wechsel).
- `042b9b3` connectivity: Heartbeat re-armte trotz stop() (Race), Generation-Token.
- `012a243` reconcile gehärtet: Form per `Array.isArray` statt `.length` (leeres Array galt als
  Einzelobjekt; null/undefined Payload/forcedState crashte nicht mehr), delete.list rollt ganze
  Liste zurück, reconcile-Fehler wird über den Notifier gemeldet statt still geloggt.
Verworfen als kein-Live-Bug: QUEUED→badBox (by design), selectedId-Highlight (visuell).
**NICHT als Bug melden:** `CustomerCoreService.deleteAll` löscht bewusst die GLOBALE customer-Tabelle
ohne Länderfilter — reine Test-/Reset-Funktion, fliegt vor Prod raus (Marcus, 2026-06-03).
DEV-SAP-Mock (`.get()` auf Kreuzprodukt-Join wählt willkürlichen Kontakt/Adresse): **bewusst nicht
gefixt** — SAP-OData-Schema trägt eh nur EINEN Partner/Adresse, und eine Query-Änderung würde die
26 gepinnten E2E-Fixtures riskieren.

**Diese Session per TDD abgedeckt** (vorher Lücken): Sync-Kern (outbox, background-sync,
backoff, **reconcile**=Offline-first-Konvergenz inkl. Rollback, **connectivity**=Heartbeat),
customer-fe Store + FE-`sap-adapter` (live SAP-Parsing), und die SW-Routing-Policy.

**Muster: Test-Target für ein Projekt ohne Tests nachrüsten** (war bei customer-fe und der
App plato-customer-fe nötig — `nx run-many` ließ sie sonst aus):
- **Lib** (z. B. customer-fe): `test`-Block in die vorhandene `vite.config.ts`
  (`{ watch:false, globals:true, environment:'node', include:['src/**/*.{test,spec}.{ts,tsx}'], passWithNoTests:true }`)
  + `"test": { "executor": "@nx/vite:test" }` ins `project.json`.
- **App mit VitePWA/vue** (plato-customer-fe): NICHT die Haupt-`vite.config.ts` nehmen (PWA/vue-
  Plugins stören den Testlauf). Eigene `vitest.config.ts` ohne diese Plugins (nur `nxViteTsPaths()`),
  und im Test-Target `"options": { "configFile": "apps/plato-customer-fe/vitest.config.ts" }`.
- jsdom je Datei via `// @vitest-environment jsdom` (z. B. DOMParser im sap-adapter, window/fetch
  in connectivity). Cross-Lib-Wertimporte (`@plato/...`) im Vitest-Modus mocken, sonst lädt das Modul nicht.

**SW-Login-Loop festgenagelt:** Routing-Regeln aus `sw.ts` in pures `sw-routing.ts` ausgelagert
(`isNetworkOnly`/`isOidcCallback`/`NAV_DENYLIST`) und getestet. Siehe [[project_pwa_sw_auth_bypass]].

**Test-lose Projekte:** alle relevanten haben jetzt ein Test-Target. `libs/shared` per eigener
`vitest.config.ts` + `configFile`-Option (gleiches Muster wie die App). Keine bewusst offene
Coverage-Lücke mehr in den verzweigten/kritischen Pfaden.
