--- id: PROJ-74 title: Vorbestehende Test-/Vet-Signatur-Drift beheben (go vet/test wieder komplett grün) status: In Review created: 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:39` — `storage.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.Discard` → `io.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.Discard` → `io.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.go` — `TEST_DATABASE_URL` + pro Test isoliertes Schema (`authtest_`, `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. ## Acceptance Criteria - [x] `CGO_ENABLED=0 go build ./...` auf 132 fehlerfrei (alle Pakete inkl. `archivmail-export`). - [x] `go vet ./...` ohne Meldungen. - [x] `go test ./...` komplett grün (oder verbleibende Fails explizit als bekannt/erwartet dokumentiert, nicht stillschweigend).