Der index-pending-Cron (PROJ-58) scannte bei jedem Lauf die komplette emails-Tabelle, weil indexed_at unindiziert war — skaliert linear mit Archivgröße. Partial Index auf (received_at DESC) WHERE indexed_at IS NULL verifiziert auf 192.168.1.132: Seq Scan (13.3ms, 52895 Zeilen) → Index Scan (0.086ms). Restlicher PROJ-75-Audit (pgxpool-Tuning, reconciliation N+1) bewusst zurückgestellt, siehe Feature-Spec. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
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) | In Review | 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.Newwird mit einemstringstattstorage.Configaufgerufen.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, vongo vetgemeldet.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 auftime.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)
archivmail-export:storage.New-Aufruf auf aktuellestorage.Config- Signatur anpassen (Config-Feld statt reinem String-Pfad).storage.go:508Self-Assignment beheben.- Testdateien (
storage_test.go,audit_test.go,api_test.go,userstore_test.go) auf aktuelle Signaturen (storage.New,os.Discard,UpsertLDAPUser) nachziehen. TestParseMissingDateklären: ist der Test oder das aktuelle Date-Fallback-Verhalten falsch? Danach eins von beiden anpassen.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 jetztSave(ctx, raw, _ time.Time, tenantID *int64)); Importcontextergänzt.internal/audit/audit_test.go:os.Discard→io.Discard(4x), Importioergänzt (osbleibt füros.Getenv/os.Opennötig).audit.New,Log,Querywaren unverändert.internal/api/api_test.go:os.Discard→io.Discard+ Importio;storage.New(filepath.Join(dir,"store"))→storage.New(storage.Config{Dir: ...}).api.New,auth.New,userstore.Creategegen den Produktionscode geprüft — unverändert.internal/userstore/userstore_test.go:UpsertLDAPUser(username, email, role)→UpsertLDAPUser(username, email, role, nil)(4. ParametertenantID *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_<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.gointernal/storage/storage.gointernal/storage/storage_test.gointernal/audit/audit_test.gointernal/api/api_test.gointernal/userstore/userstore_test.gointernal/auth/auth_test.gopkg/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 (vongo vetgemeldet).internal/api/api_test.goHelperlogin()las den JWT bisher aus dem JSON-Response-Body (resp["token"]), was zunil-Panic führte. Die echte API liefert den Token nur noch als httpOnly-Cookie (archivmail_session, sieheinternal/api/auth_handlers.go:81-99) — Auth-Contract-Drift, unabhängig von PROJ-73/74. Test liest den Token jetzt ausw.Result().Cookies().
Deployed auf 132 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).