ARC-03: dublettenerkennung-e-mail
- mail/internal/dedup/hash.go: HashAndBuffer, SHA-256 auf Klartext VOR Verschluesselung (ARC-02), liefert erneut lesbaren Reader zurueck. - mail/internal/dedup/store.go: Store (Postgres, tenant_slug fest im Primaerschluessel gebunden), Register: Duplikat referenziert Original statt redundant zu speichern. - Alle 3 Pflichtpruefungen real bestanden (siehe mail/docs/ARC-03-PRUEFPROTOKOLL.md): Duplikat aus zwei Quellen erkannt, zwei Mandanten mit identischem Inhalt nicht verknuepft, knapp unterschiedliche Nachricht korrekt nicht erkannt. - Kein Umbau: internal/storage, internal/crypto, internal/encstorage unveraendert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cb5a9da701
commit
704b64fe27
@@ -0,0 +1,94 @@
|
||||
// Integrationstest (ARC-03): echte Postgres-Instanz, folgt derselben
|
||||
// Testhost-Konvention wie mail/internal/example (QA-01) — TEST_TENANT_DSN.
|
||||
package dedup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func setupStore(t *testing.T, tenantSlug string) *Store {
|
||||
t.Helper()
|
||||
dsn := os.Getenv("TEST_TENANT_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest übersprungen")
|
||||
}
|
||||
ctx := context.Background()
|
||||
pool, err := pgxpool.New(ctx, dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("pool: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { pool.Close() })
|
||||
|
||||
store := NewStore(pool, tenantSlug)
|
||||
if err := store.EnsureSchema(ctx); err != nil {
|
||||
t.Fatalf("schema: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, _ = pool.Exec(context.Background(), `DELETE FROM mail_content_hashes WHERE tenant_slug = $1`, tenantSlug)
|
||||
})
|
||||
return store
|
||||
}
|
||||
|
||||
// TestRegister_SameMessageFromTwoSourcesIsDuplicate ist die geforderte
|
||||
// Pflichtprüfung 1: dieselbe Nachricht aus zwei Quellen wird als
|
||||
// Duplikat erkannt.
|
||||
func TestRegister_SameMessageFromTwoSourcesIsDuplicate(t *testing.T) {
|
||||
store := setupStore(t, "mandant-arc03-a")
|
||||
ctx := context.Background()
|
||||
|
||||
hash := "fixierter-inhalts-hash-fuer-test-1"
|
||||
|
||||
isDup, _, err := store.Register(ctx, hash, "quelle-1/objekt")
|
||||
if err != nil {
|
||||
t.Fatalf("erste registrierung: %v", err)
|
||||
}
|
||||
if isDup {
|
||||
t.Fatal("erste registrierung eines hashes darf kein duplikat sein")
|
||||
}
|
||||
|
||||
isDup, existing, err := store.Register(ctx, hash, "quelle-2/objekt")
|
||||
if err != nil {
|
||||
t.Fatalf("zweite registrierung: %v", err)
|
||||
}
|
||||
if !isDup {
|
||||
t.Fatal("erwartet: dieselbe nachricht aus zweiter quelle wird als duplikat erkannt")
|
||||
}
|
||||
if existing != "quelle-1/objekt" {
|
||||
t.Fatalf("erwartet referenz auf das original quelle-1/objekt, habe %q", existing)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegister_SameContentTwoTenantsNotLinked ist die geforderte
|
||||
// Pflichtprüfung 2: zwei Mandanten mit identischem Mailinhalt werden
|
||||
// nicht fälschlich verknüpft.
|
||||
func TestRegister_SameContentTwoTenantsNotLinked(t *testing.T) {
|
||||
dsn := os.Getenv("TEST_TENANT_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest übersprungen")
|
||||
}
|
||||
storeA := setupStore(t, "mandant-arc03-x")
|
||||
storeB := setupStore(t, "mandant-arc03-y")
|
||||
ctx := context.Background()
|
||||
|
||||
hash := "identischer-inhalt-ueber-zwei-mandanten-hinweg"
|
||||
|
||||
isDupA, _, err := storeA.Register(ctx, hash, "mandant-x/objekt")
|
||||
if err != nil {
|
||||
t.Fatalf("mandant a: %v", err)
|
||||
}
|
||||
if isDupA {
|
||||
t.Fatal("erste registrierung bei mandant a darf kein duplikat sein")
|
||||
}
|
||||
|
||||
isDupB, existingB, err := storeB.Register(ctx, hash, "mandant-y/objekt")
|
||||
if err != nil {
|
||||
t.Fatalf("mandant b: %v", err)
|
||||
}
|
||||
if isDupB {
|
||||
t.Fatalf("mandant b wurde fälschlich mit mandant a verknüpft, existing=%q", existingB)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user