// Integrationstest (IMP-07): echte Postgres-Instanz, folgt derselben // Testhost-Konvention wie mail/internal/dedup/folderstate — TEST_TENANT_DSN. package mailboxconfig import ( "bytes" "context" "os" "testing" "github.com/jackc/pgx/v5/pgxpool" "gitea.perlbach24.de/scripte/nexarch/mail/internal/crypto" ) // fakeKEKProvider liefert einen festen, mandantenspezifischen KEK — // gleiche Testkonvention wie encstorage_test.go (ARC-02). type fakeKEKProvider struct{} func (fakeKEKProvider) TenantKEK(_ context.Context, _ string) ([]byte, error) { return bytes.Repeat([]byte{0x42}, crypto.KEKSize), nil } func setupStore(t *testing.T) *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, crypto.NewService(fakeKEKProvider{})) if err := store.EnsureSchema(ctx); err != nil { t.Fatalf("schema: %v", err) } t.Cleanup(func() { _, _ = pool.Exec(context.Background(), `DELETE FROM mail_mailboxes WHERE tenant_slug LIKE 'mandant-%'`) }) return store } func createTestMailbox(t *testing.T, store *Store, tenant, name string) int64 { t.Helper() id, err := store.Create(context.Background(), tenant, CreateInput{ Name: name, IMAPHost: "imap." + name + ".example", IMAPPort: 993, IMAPUsername: "user@" + name + ".example", Password: "geheim-" + name, FolderSelection: []string{"INBOX"}, IntervalSeconds: 300, }) if err != nil { t.Fatalf("postfach %s anlegen: %v", name, err) } return id } // TestList_TwoTenantsWithMultipleMailboxesSeeOnlyOwn ist die geforderte // Pflichtprüfung 1: zwei Mandanten mit je mehreren Postfächern sehen // ausschließlich eigene Postfächer. func TestList_TwoTenantsWithMultipleMailboxesSeeOnlyOwn(t *testing.T) { store := setupStore(t) ctx := context.Background() tenantA := "mandant-imp07-a" tenantB := "mandant-imp07-b" createTestMailbox(t, store, tenantA, "vertrieb") createTestMailbox(t, store, tenantA, "support") createTestMailbox(t, store, tenantB, "buchhaltung") listA, err := store.List(ctx, tenantA) if err != nil { t.Fatalf("list mandant a: %v", err) } if len(listA) != 2 { t.Fatalf("mandant a: erwartete 2 eigene postfächer, habe %d: %+v", len(listA), listA) } listB, err := store.List(ctx, tenantB) if err != nil { t.Fatalf("list mandant b: %v", err) } if len(listB) != 1 || listB[0].Name != "buchhaltung" { t.Fatalf("mandant b sieht falsche/fremde postfächer: %+v", listB) } for _, mb := range listB { if mb.Name == "vertrieb" || mb.Name == "support" { t.Fatalf("mandant b sieht postfach von mandant a: %+v", mb) } } } // TestDelete_DoesNotAffectSiblingMailboxes ist die geforderte // Pflichtprüfung 2: Löschen eines Postfachs beeinträchtigt andere // Postfächer desselben Mandanten nicht. func TestDelete_DoesNotAffectSiblingMailboxes(t *testing.T) { store := setupStore(t) ctx := context.Background() tenant := "mandant-imp07-loeschen" idA := createTestMailbox(t, store, tenant, "eins") idB := createTestMailbox(t, store, tenant, "zwei") if err := store.Delete(ctx, tenant, idA); err != nil { t.Fatalf("löschen: %v", err) } list, err := store.List(ctx, tenant) if err != nil { t.Fatalf("list: %v", err) } if len(list) != 1 || list[0].ID != idB { t.Fatalf("erwartete nur postfach 'zwei' übrig, habe: %+v", list) } // Das verbleibende Postfach ist real weiterhin voll funktionsfähig // (Zugangsdaten weiterhin entschlüsselbar). pw, err := store.GetDecryptedPassword(ctx, tenant, idB) if err != nil { t.Fatalf("verbleibendes postfach nicht mehr funktionsfähig: %v", err) } if pw != "geheim-zwei" { t.Fatalf("erwartetes passwort für verbleibendes postfach, habe %q", pw) } } // TestUpdate_ConfigChangeDoesNotAffectOtherMailboxes ist die geforderte // Pflichtprüfung 3: Konfigurationsänderung an einem Postfach wirkt nicht // auf andere. func TestUpdate_ConfigChangeDoesNotAffectOtherMailboxes(t *testing.T) { store := setupStore(t) ctx := context.Background() tenant := "mandant-imp07-update" idA := createTestMailbox(t, store, tenant, "eins") idB := createTestMailbox(t, store, tenant, "zwei") if err := store.Update(ctx, tenant, idA, UpdateInput{ IMAPHost: "neuer-host.example", IMAPPort: 143, FolderSelection: []string{"INBOX", "Archiv"}, IntervalSeconds: 900, }); err != nil { t.Fatalf("update: %v", err) } list, err := store.List(ctx, tenant) if err != nil { t.Fatalf("list: %v", err) } var mbA, mbB MailboxConfig for _, mb := range list { switch mb.ID { case idA: mbA = mb case idB: mbB = mb } } if mbA.IMAPHost != "neuer-host.example" || mbA.IntervalSeconds != 900 { t.Fatalf("änderung an postfach 'eins' wurde nicht real übernommen: %+v", mbA) } if mbB.IMAPHost != "imap.zwei.example" || mbB.IntervalSeconds != 300 { t.Fatalf("postfach 'zwei' wurde fälschlich mitverändert: %+v", mbB) } }