Files
archivmail/features/PROJ-74-test-suite-signatur-drift.md
T
sysopsandClaude Sonnet 5 d1b4497893 fix(PROJ-79): next lint kaputt seit Next-16-Upgrade repariert + 30 Findings gefixt
npm run lint rief next lint auf, das es in Next.js 16 nicht mehr gibt —
seit dem Next-16-Upgrade lief effektiv gar kein Lint mehr. Umgestellt auf
eslint . mit Flat-Config (eslint.config.mjs statt .eslintrc.json). Der
dadurch wieder sichtbare Lint-Lauf zeigte 30 Findings (25 Fehler,
5 Warnungen), alle gefixt:

- 19x react-hooks/set-state-in-effect: Loading-States wo möglich als
  echte Ableitung statt eigenem Effect-State (use-mobile.tsx komplett auf
  useSyncExternalStore umgebaut), sonst async-Wrapper mit Cancel-Guard um
  bestehende Loader — Timing/Ladeanzeige unverändert.
- react-hooks/refs (useSearch.ts): Ref-Schreibzugriff aus dem Render in
  einen Effect verschoben.
- 4x no-html-link-for-pages: <a href> durch next/link ersetzt in
  admin/login, forgot-password, signup.
- Rest (exhaustive-deps, no-img-element, unused disable) einzeln gefixt.
- 4 bewusst belassene disable-Kommentare mit Begründung (shadcn/ui-Datei,
  QR-Code-data-URL, Full-Reload nach Auth laut Projektregel).

eslint-Major-Upgrade auf 10 selbst bleibt blockiert: eslint-plugin-react/
jsx-a11y/import unterstützen ESLint 10 in ihrer aktuellen Latest-Version
noch nicht (Crash beim Laden), siehe Feature-Spec PROJ-79.

Verifiziert auf 132 (Build-Sandbox, kein Live-Deploy): npm ci/tsc/lint/
build grün, 8 Kern-Routen per Standalone-Server auf HTTP 200 geprüft.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
2026-08-05 21:14:17 +02:00

7.5 KiB

id, title, status, created
id title status created
PROJ-74 Vorbestehende Test-/Vet-Signatur-Drift beheben (go vet/test wieder komplett grün) Deployed 2026-08-05

Problem

Bei der Verifikation von PROJ-73-Vorarbeiten auf 192.168.1.132 (go build ./... && go vet ./... && go test ./...) traten mehrere vorbestehende, vom aktuellen Crash-Härtung-Fix unabhängige Fehler auf:

  • cmd/archivmail-export/main.go:39storage.New wird mit einem string statt storage.Config aufgerufen. storage.New-Signatur wurde in einem früheren Commit geändert, dieses CLI-Tool nicht nachgezogen. Build schlägt komplett fehl.
  • internal/storage/storage.go:508 — Self-Assignment, von go vet gemeldet.
  • internal/storage/storage_test.go, internal/audit/audit_test.go, internal/api/api_test.go, internal/userstore/userstore_test.go — Testdateien passen nicht mehr zu aktuellen Signaturen (storage.New, os.Discard, UpsertLDAPUser). Build der Testpakete schlägt fehl.
  • pkg/mailparser/parser_test.go:98 (TestParseMissingDate) — Testfall erwartet Fallback auf time.Now() bei fehlendem Date-Header, aktuelles Verhalten weicht ab. Nicht Teil der Multipart-Depth/strings.Builder- Änderungen aus PROJ-73-Vorarbeiten.
  • internal/auth-Tests — userstore.New: cannot parse .../users.db, vermutlich Umgebungs-/Test-DSN-Problem statt Code-Bug.

Solange diese Drift besteht, ist go vet ./... / go test ./... insgesamt rot und liefert kein verlässliches Gesamtbild mehr — jede künftige Verifikation muss den Diff manuell gegen die bekannten Altlasten abgleichen.

Lösung (Vorschlag)

  1. archivmail-export: storage.New-Aufruf auf aktuelle storage.Config- Signatur anpassen (Config-Feld statt reinem String-Pfad).
  2. storage.go:508 Self-Assignment beheben.
  3. Testdateien (storage_test.go, audit_test.go, api_test.go, userstore_test.go) auf aktuelle Signaturen (storage.New, os.Discard, UpsertLDAPUser) nachziehen.
  4. TestParseMissingDate klären: ist der Test oder das aktuelle Date-Fallback-Verhalten falsch? Danach eins von beiden anpassen.
  5. internal/auth-Test-DSN-Problem auf 132 diagnostizieren (evtl. reines Testumgebungs-Setup-Problem, kein Code-Bug).

Implementation Notes

Umgesetzt (lokal, ohne Build/Test — kein Go-Toolchain auf der Workstation; Verifikation erfolgt separat auf 192.168.1.132).

1. cmd/archivmail-export/main.go:39

storage.New(cfg.Storage.StorePath)storage.New(storage.Config{Dir, Keyfile, DSN, CompressEnabled}), analog zu cmd/archivmail/cmd_export.go:88-93. Zusätzlich defer mailStore.Close() ergänzt (der Store hält jetzt einen pgxpool, der bisherige Aufruf kannte noch keine DB-Verbindung). Ohne Keyfile/Compress hätte das Tool verschlüsselte bzw. gzip-komprimierte Mails als Rohbytes exportiert — der Fix stellt das alte Verhalten ("exportiere Klartext-EML") wieder her.

2. internal/storage/storage.go:~506 Self-Assignment

pm.MessageID = pm.MessageID war echter toter Code (Kommentar im Original: "no-op; thread resolved inside insertMeta"). Der komplette if pm.InReplyTo != "" || len(pm.References) > 0-Block hatte keinerlei Effekt und wurde entfernt; der erklärende Kommentar bleibt als Doku über resolveThreadID stehen. Kein Verhaltensunterschied.

3. Testdateien an aktuelle Signaturen angeglichen

  • internal/storage/storage_test.go: storage.New(dir)storage.New(storage.Config{Dir: dir}) (5x); store.Save(raw, time.Now())store.Save(context.Background(), raw, time.Now(), nil) (Signatur ist jetzt Save(ctx, raw, _ time.Time, tenantID *int64)); Import context ergänzt.
  • internal/audit/audit_test.go: os.Discardio.Discard (4x), Import io ergänzt (os bleibt für os.Getenv/os.Open nötig). audit.New, Log, Query waren unverändert.
  • internal/api/api_test.go: os.Discardio.Discard + Import io; storage.New(filepath.Join(dir,"store"))storage.New(storage.Config{Dir: ...}). api.New, auth.New, userstore.Create gegen den Produktionscode geprüft — unverändert.
  • internal/userstore/userstore_test.go: UpsertLDAPUser(username, email, role)UpsertLDAPUser(username, email, role, nil) (4. Parameter tenantID *int64, nil = tenant-loser LDAP-User, entspricht dem alten Verhalten).

4. TestParseMissingDate — Entscheidung: Test angepasst, Code bleibt

Aktuelles Verhalten in pkg/mailparser/parser.go:188-191: Wenn der Date-Header fehlt oder durch keinen der Fallback-Layouts parsbar ist, bleibt pm.Date der Zero-Value (expliziter Kommentar: "Leave pm.Date as zero — storage will use DB DEFAULT NOW()"). Der time.Now()-Fallback ist bewusst in die Storage-Schicht gewandert: internal/storage/storage.go:998-1000 (insertMeta) und :1034-1036 (SaveMeta) setzen received_at = time.Now(), wenn pm.Date zero ist.

Das ist die sinnvollere Aufteilung und wird deshalb nicht zurückgedreht: Der Parser bildet die Mail ab, wie sie ist, und erfindet kein Datum — dadurch bleibt für jeden Konsumenten unterscheidbar, ob ein Datum aus der Mail stammt oder vom Archiv gesetzt wurde (GoBD-Nachvollziehbarkeit). Ein time.Now() direkt im Parser würde diese Information unwiederbringlich verschlucken. Der Test prüft nun p.Date.IsZero() == true und dokumentiert die Aufteilung inkl. Verweis auf die Storage-Fallbacks.

5. internal/auth-Tests — Ursache: veralteter SQLite-DSN

Kein Produktionscode-Bug. internal/auth/auth_test.go:13 rief userstore.New(filepath.Join(t.TempDir(), "users.db")) auf — ein Relikt aus der SQLite-Zeit. userstore.New erwartet seit der PostgreSQL-Migration einen pgx-DSN (userstore.go:71, pgxpool.New), weshalb pgx den Dateipfad mit "cannot parse .../users.db" ablehnte.

Fix: newTestAuth nutzt jetzt dasselbe Muster wie userstore_test.go / api_test.goTEST_DATABASE_URL + pro Test isoliertes Schema (authtest_<testname>, search_path=-Suffix am DSN), t.Skip wenn TEST_DATABASE_URL nicht gesetzt ist, Cleanup per DROP SCHEMA ... CASCADE. TestHasRole ist reine Logik und läuft weiterhin ohne PostgreSQL.

Geänderte Dateien

  • cmd/archivmail-export/main.go
  • internal/storage/storage.go
  • internal/storage/storage_test.go
  • internal/audit/audit_test.go
  • internal/api/api_test.go
  • internal/userstore/userstore_test.go
  • internal/auth/auth_test.go
  • pkg/mailparser/parser_test.go

Verifikation auf 132 (2026-08-05)

Mit TEST_DATABASE_URL gegen die lokale Postgres-Instanz auf 132 (eigenes Schema pro Testlauf, per DROP SCHEMA CASCADE aufgeräumt) und erreichbarem Manticore: go build ./..., go vet ./... und go test ./... liefen komplett grün, alle Pakete PASS, keine Skips.

Zwei zusätzliche Fixes waren nötig, über die ursprüngliche Analyse hinaus:

  • internal/api/api_test.go — ungenutzter Import "net/http" entfernt (von go vet gemeldet).
  • internal/api/api_test.go Helper login() las den JWT bisher aus dem JSON-Response-Body (resp["token"]), was zu nil-Panic führte. Die echte API liefert den Token nur noch als httpOnly-Cookie (archivmail_session, siehe internal/api/auth_handlers.go:81-99) — Auth-Contract-Drift, unabhängig von PROJ-73/74. Test liest den Token jetzt aus w.Result().Cookies().

Deployed auf 132 am 2026-08-05.

Deployed auf 131 (Produktiv) am 2026-08-05.

Acceptance Criteria

  • CGO_ENABLED=0 go build ./... auf 132 fehlerfrei (alle Pakete inkl. archivmail-export).
  • go vet ./... ohne Meldungen.
  • go test ./... komplett grün (oder verbleibende Fails explizit als bekannt/erwartet dokumentiert, nicht stillschweigend).