Neue Datei storage/archivekey.go: ArchiveKey(mailbox, sentAt, messageID, partIndex) liefert den Objektschlüssel nach dem Schema Postfach/Jahr innerhalb des bereits mandantenspezifischen Buckets (ARC-06) — additiv neben dem bestehenden, flachen ObjectKey (ARC-01), das für bestehende Aufrufer (mailapi/INT-01) unverändert bleibt. ArchiveYearPrefix(mailbox, year) ist der eigenständig berechenbare Präfix eines Postfach-Jahrs — Grundlage für spätere Retention-Regeln ohne Migration. Fehlendes Postfach bzw. Null-Sendedatum führen zu dokumentierten Fallback-Segmenten statt einem Ablagefehler; ArchiveKey liefert bewusst keinen error, da es strukturell keinen Fehlerfall gibt. Alle drei Pflichtprüfungen: mehrjähriger Import erzeugt nachweislich getrennte Jahresordner, fehlendes Postfach/Sendedatum nutzt den dokumentierten Fallback (inkl. Test gegen mehrere ungewöhnliche Eingaben), sowie ergänzend ein Nachweis für Akzeptanzkriterium 2 (Retention-Präfix trifft exakt die zuvor abgelegten Schlüssel desselben Postfach-Jahrs). Pflichtprüfung 3 (Stichprobenreview durch zweite Person) bleibt strukturell offen, im Prüfprotokoll dokumentiert (analog zu ING-10/QA-04/QA-02). go build/go vet/golangci-lint clean, gesamtes Mail-Modul regressionsfrei getestet.
94 lines
3.6 KiB
Go
94 lines
3.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestArchiveKey_MultipleYearsProduceSeparateYearFolders ist die
|
|
// geforderte Pflichtprüfung 1 (ARC-04): Import über mehrere Jahre
|
|
// erzeugt korrekt getrennte Jahresordner.
|
|
func TestArchiveKey_MultipleYearsProduceSeparateYearFolders(t *testing.T) {
|
|
sent2019 := time.Date(2019, 3, 1, 0, 0, 0, 0, time.UTC)
|
|
sent2024 := time.Date(2024, 11, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
key2019 := ArchiveKey("INBOX", sent2019, "msg-a", 0)
|
|
key2024 := ArchiveKey("INBOX", sent2024, "msg-b", 0)
|
|
|
|
if !strings.HasPrefix(key2019, "postfach/INBOX/2019/") {
|
|
t.Fatalf("erwartete jahresordner 2019, habe: %q", key2019)
|
|
}
|
|
if !strings.HasPrefix(key2024, "postfach/INBOX/2024/") {
|
|
t.Fatalf("erwartete jahresordner 2024, habe: %q", key2024)
|
|
}
|
|
if key2019 == key2024 {
|
|
t.Fatalf("erwartete unterschiedliche schlüssel für unterschiedliche jahre")
|
|
}
|
|
}
|
|
|
|
// TestArchiveKey_MissingMailboxUsesDocumentedFallback ist die
|
|
// geforderte Pflichtprüfung 2 (ARC-04): fehlendes Postfach-Attribut
|
|
// nutzt den dokumentierten Fallback statt eines Ablagefehlers.
|
|
func TestArchiveKey_MissingMailboxUsesDocumentedFallback(t *testing.T) {
|
|
key := ArchiveKey("", time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), "msg-a", 0)
|
|
if !strings.Contains(key, "/"+FallbackMailboxSegment+"/") {
|
|
t.Fatalf("erwartete fallback-postfach-segment %q, habe: %q", FallbackMailboxSegment, key)
|
|
}
|
|
}
|
|
|
|
// TestArchiveKey_MissingSentAtUsesDocumentedFallback belegt denselben
|
|
// Fallback-Grundsatz für ein fehlendes (Null-)Sendedatum.
|
|
func TestArchiveKey_MissingSentAtUsesDocumentedFallback(t *testing.T) {
|
|
key := ArchiveKey("INBOX", time.Time{}, "msg-a", 0)
|
|
if !strings.Contains(key, "/"+FallbackYearSegment+"/") {
|
|
t.Fatalf("erwartete fallback-jahr-segment %q, habe: %q", FallbackYearSegment, key)
|
|
}
|
|
}
|
|
|
|
// TestArchiveKey_NeverErrorsOnAnyInput bestätigt, dass ArchiveKey für
|
|
// KEINE Eingabekombination fehlschlägt (Akzeptanzkriterium 3:
|
|
// "definierter Fallback-Pfad, nicht Ablagefehler" — ArchiveKey liefert
|
|
// bewusst keinen error-Rückgabewert, weil es strukturell keinen
|
|
// Fehlerfall gibt).
|
|
func TestArchiveKey_NeverErrorsOnAnyInput(t *testing.T) {
|
|
inputs := []struct {
|
|
mailbox string
|
|
sentAt time.Time
|
|
}{
|
|
{"", time.Time{}},
|
|
{"Postfach/Mit/Slashes", time.Time{}},
|
|
{" ", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)},
|
|
}
|
|
for _, in := range inputs {
|
|
key := ArchiveKey(in.mailbox, in.sentAt, "msg", 0)
|
|
if key == "" {
|
|
t.Fatalf("erwartete nicht-leeren schlüssel für eingabe %+v", in)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestArchiveYearPrefix_FoundationForRetentionWithoutMigration ist die
|
|
// geforderte Pflichtprüfung für Akzeptanzkriterium 2: die Struktur ist
|
|
// Grundlage für spätere Retention-Regeln ohne Migration — ein
|
|
// Retention-Job kann den Präfix EINES Postfach-Jahrs berechnen und
|
|
// findet darunter GENAU die zuvor mit ArchiveKey abgelegten Schlüssel
|
|
// desselben Postfachs/Jahrs, ohne dass an den bereits abgelegten
|
|
// Schlüsseln irgendetwas umbenannt werden müsste.
|
|
func TestArchiveYearPrefix_FoundationForRetentionWithoutMigration(t *testing.T) {
|
|
sent2022 := time.Date(2022, 6, 15, 0, 0, 0, 0, time.UTC)
|
|
key := ArchiveKey("Rechnungen", sent2022, "msg-x", 3)
|
|
|
|
prefix := ArchiveYearPrefix("Rechnungen", 2022)
|
|
if !strings.HasPrefix(key, prefix) {
|
|
t.Fatalf("ArchiveKey %q liegt nicht unter dem für retention berechenbaren präfix %q", key, prefix)
|
|
}
|
|
|
|
// Ein anderes Jahr desselben Postfachs liegt NICHT unter demselben
|
|
// Präfix — Retention kann Jahre gezielt einzeln adressieren.
|
|
otherYearPrefix := ArchiveYearPrefix("Rechnungen", 2023)
|
|
if strings.HasPrefix(key, otherYearPrefix) {
|
|
t.Fatalf("ArchiveKey %q hätte NICHT unter dem 2023-präfix liegen dürfen", key)
|
|
}
|
|
}
|