// Integrationstest (ARC-08): echte Postgres-Instanz, folgt derselben // Testhost-Konvention wie mail/internal/dedup/indexworker — TEST_TENANT_DSN. package crypto import ( "bytes" "context" "os" "testing" "github.com/jackc/pgx/v5/pgxpool" ) var testLocalWrapKey = bytes.Repeat([]byte{0x7a}, KEKSize) func setupKEKVersionStore(t *testing.T, tenantSlug string) *KEKVersionStore { 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 := NewKEKVersionStore(pool, testLocalWrapKey) if err := store.EnsureSchema(ctx); err != nil { t.Fatalf("schema: %v", err) } t.Cleanup(func() { _, _ = pool.Exec(context.Background(), `DELETE FROM mail_kek_versions WHERE tenant_slug = $1`, tenantSlug) }) return store } func TestKEKVersionStore_RecordIfNewDetectsRotationOnly(t *testing.T) { tenant := "mandant-arc08-recordifnew" store := setupKEKVersionStore(t, tenant) ctx := context.Background() kekV1 := bytes.Repeat([]byte{0x01}, KEKSize) v1, err := store.RecordIfNew(ctx, tenant, kekV1) if err != nil { t.Fatalf("erste erfassung: %v", err) } if v1 != 1 { t.Fatalf("erwartete version 1, habe %d", v1) } // Erneuter Aufruf mit UNVERÄNDERTEM KEK darf keine neue Version anlegen. vAgain, err := store.RecordIfNew(ctx, tenant, kekV1) if err != nil { t.Fatalf("zweite erfassung (unverändert): %v", err) } if vAgain != 1 { t.Fatalf("erwartete weiterhin version 1 bei unverändertem kek, habe %d", vAgain) } kekV2 := bytes.Repeat([]byte{0x02}, KEKSize) v2, err := store.RecordIfNew(ctx, tenant, kekV2) if err != nil { t.Fatalf("dritte erfassung (rotiert): %v", err) } if v2 != 2 { t.Fatalf("erwartete version 2 nach rotation, habe %d", v2) } gotV1, err := store.Get(ctx, tenant, 1) if err != nil { t.Fatalf("get v1: %v", err) } if !bytes.Equal(gotV1, kekV1) { t.Fatal("v1 liefert nicht den ursprünglichen kek zurück") } gotV2, err := store.Get(ctx, tenant, 2) if err != nil { t.Fatalf("get v2: %v", err) } if !bytes.Equal(gotV2, kekV2) { t.Fatal("v2 liefert nicht den rotierten kek zurück") } } func TestKEKVersionStore_RevokeBlocksOnlyThatVersion(t *testing.T) { tenant := "mandant-arc08-revoke" store := setupKEKVersionStore(t, tenant) ctx := context.Background() kekV1 := bytes.Repeat([]byte{0x11}, KEKSize) kekV2 := bytes.Repeat([]byte{0x22}, KEKSize) if _, err := store.RecordIfNew(ctx, tenant, kekV1); err != nil { t.Fatalf("v1 erfassen: %v", err) } if _, err := store.RecordIfNew(ctx, tenant, kekV2); err != nil { t.Fatalf("v2 erfassen: %v", err) } if err := store.Revoke(ctx, tenant, 1); err != nil { t.Fatalf("v1 sperren: %v", err) } if _, err := store.Get(ctx, tenant, 1); err != ErrKEKVersionRevoked { t.Fatalf("erwartete ErrKEKVersionRevoked für gesperrte version 1, habe: %v", err) } if _, err := store.Get(ctx, tenant, 2); err != nil { t.Fatalf("version 2 sollte unberührt bleiben: %v", err) } }