- 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>
95 lines
2.7 KiB
Go
95 lines
2.7 KiB
Go
// 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)
|
|
}
|
|
}
|