// Integrationstests (INT-07): echte Postgres-, MinIO- und // Manticore-Instanzen, gleiche Umgebungsvariablen-Konvention wie // mail/internal/storage (TEST_S3_...) und mail/internal/folderstate // (TEST_TENANT_DSN). package healthcheck import ( "context" "os" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/jackc/pgx/v5/pgxpool" "gitea.perlbach24.de/scripte/nexarch/mail/internal/search" "gitea.perlbach24.de/scripte/nexarch/mail/internal/storage" ) // TestDatabaseCheck_RealPostgres ist Teil der geforderten // Pflichtprüfung "je Komponente getrennt" (Akzeptanzkriterium 1) — // gegen eine echte, laufende Postgres-Instanz. func TestDatabaseCheck_RealPostgres(t *testing.T) { dsn := os.Getenv("TEST_TENANT_DSN") if dsn == "" { t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest übersprungen") } pool, err := pgxpool.New(context.Background(), dsn) if err != nil { t.Fatalf("pool: %v", err) } defer pool.Close() check := DatabaseCheck(pool) if err := check(context.Background()); err != nil { t.Fatalf("DatabaseCheck gegen echte instanz fehlgeschlagen: %v", err) } } // TestJobQueueCheck_RealPostgres prüft die Jobqueue-Erreichbarkeit // gegen eine echte Instanz — inklusive Schema-Anlage, damit der Test // unabhängig davon läuft, ob indexworker bereits initialisiert wurde. func TestJobQueueCheck_RealPostgres(t *testing.T) { dsn := os.Getenv("TEST_TENANT_DSN") if dsn == "" { t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest übersprungen") } pool, err := pgxpool.New(context.Background(), dsn) if err != nil { t.Fatalf("pool: %v", err) } defer pool.Close() if _, err := pool.Exec(context.Background(), ` CREATE TABLE IF NOT EXISTS mail_index_jobs ( id SERIAL PRIMARY KEY, job_type TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending' ) `); err != nil { t.Fatalf("mail_index_jobs anlegen: %v", err) } check := JobQueueCheck(pool) if err := check(context.Background()); err != nil { t.Fatalf("JobQueueCheck gegen echte instanz fehlgeschlagen: %v", err) } } // TestObjectStorageCheck_RealMinIO prüft die Objektspeicher- // Erreichbarkeit gegen eine echte MinIO-Instanz (echtes, per ARC-06 // provisioniertes Bucket). func TestObjectStorageCheck_RealMinIO(t *testing.T) { endpoint := os.Getenv("TEST_S3_ENDPOINT") if endpoint == "" { t.Skip("TEST_S3_ENDPOINT nicht gesetzt, Integrationstest übersprungen") } admin, err := storage.NewS3AdminClient(context.Background(), "us-east-1", endpoint, os.Getenv("TEST_S3_ACCESS_KEY"), os.Getenv("TEST_S3_SECRET_KEY"), true) if err != nil { t.Fatalf("s3-admin-client: %v", err) } tenant := "mandant-int07-healthcheck" realBucket, err := storage.ProvisionTenant(context.Background(), mustRegistryPool(t), admin, tenant, "INT-07 Healthcheck", "postgresql://healthcheck") if err != nil { t.Fatalf("ProvisionTenant: %v", err) } t.Cleanup(func() { ctx := context.Background() out, err := admin.ListObjectsV2(ctx, &s3.ListObjectsV2Input{Bucket: aws.String(realBucket)}) if err == nil { for _, obj := range out.Contents { _, _ = admin.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: aws.String(realBucket), Key: obj.Key}) } } _, _ = admin.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: aws.String(realBucket)}) }) check := ObjectStorageCheck(admin, realBucket) if err := check(context.Background()); err != nil { t.Fatalf("ObjectStorageCheck gegen echtes bucket fehlgeschlagen: %v", err) } } func mustRegistryPool(t *testing.T) *pgxpool.Pool { t.Helper() dsn := os.Getenv("TEST_TENANT_DSN") if dsn == "" { t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest übersprungen") } pool, err := pgxpool.New(context.Background(), dsn) if err != nil { t.Fatalf("pool: %v", err) } t.Cleanup(pool.Close) if _, err := pool.Exec(context.Background(), ` CREATE TABLE IF NOT EXISTS tenants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL, db_dsn TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ) `); err != nil { t.Fatalf("tenants-tabelle anlegen: %v", err) } t.Cleanup(func() { _, _ = pool.Exec(context.Background(), `DELETE FROM tenants WHERE slug = 'mandant-int07-healthcheck'`) }) return pool } // TestSearchIndexCheck_RealManticore prüft die Suchindex-Erreichbarkeit // gegen eine echte Manticore-Instanz. func TestSearchIndexCheck_RealManticore(t *testing.T) { baseURL := os.Getenv("TEST_MANTICORE_URL") if baseURL == "" { t.Skip("TEST_MANTICORE_URL nicht gesetzt, Integrationstest übersprungen") } client := search.NewClient(baseURL) if err := client.EnsureSchema(context.Background()); err != nil { t.Fatalf("schema sicherstellen: %v", err) } check := SearchIndexCheck(client) if err := check(context.Background()); err != nil { t.Fatalf("SearchIndexCheck gegen echte instanz fehlgeschlagen: %v", err) } }