package testdbisolation import ( "context" "fmt" "os" "strings" "sync" "testing" "time" ) // TestParallelProvisioning_AreIsolated ist der automatisierte Nachweis für QA-01 Prüfung 2: // Testdatenbank-Isolation zwischen parallelen Testläufen. Zwei Provisionierungen laufen // gleichzeitig, jede schreibt einen Beleg-Datensatz in ihre eigene DB — keine darf den // Beleg der jeweils anderen sehen. // // Braucht TEST_ADMIN_DSN (siehe Projekt-Testinfrastruktur), läuft auf dem Testhost, nicht lokal // auf der Entwicklungsmaschine (siehe "no installs"-Regel) — wird ohne gesetzte Variable übersprungen. func TestParallelProvisioning_AreIsolated(t *testing.T) { adminDSN := os.Getenv("TEST_ADMIN_DSN") if adminDSN == "" { t.Skip("TEST_ADMIN_DSN nicht gesetzt — Test läuft nur auf dem Testhost, siehe docs/TESTSTRATEGIE-CORE.md") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() const numParallel = 2 dbNames := make([]string, numParallel) for i := range dbNames { dbNames[i] = fmt.Sprintf("qa01_isolation_%d_%d", time.Now().UnixNano(), i) } var wg sync.WaitGroup errs := make([]error, numParallel) cleanups := make([]func(context.Context) error, numParallel) for i, name := range dbNames { wg.Add(1) go func(idx int, dbName string) { defer wg.Done() cleanup, err := ProvisionEphemeralDB(ctx, adminDSN, dbName) if err != nil { errs[idx] = fmt.Errorf("provisionierung %d fehlgeschlagen: %w", idx, err) return } cleanups[idx] = cleanup dbDSN := dsnForDB(adminDSN, dbName) // Jede Goroutine schreibt einen für sie eindeutigen Beleg. if err := WriteMarker(ctx, dbDSN, "isolation_marker", fmt.Sprintf("marker-von-goroutine-%d", idx)); err != nil { errs[idx] = fmt.Errorf("markierung %d fehlgeschlagen: %w", idx, err) } }(i, name) } wg.Wait() t.Cleanup(func() { for _, cleanup := range cleanups { if cleanup != nil { _ = cleanup(context.Background()) } } }) for i, err := range errs { if err != nil { t.Fatalf("goroutine %d: %v", i, err) } } // Isolationsnachweis: jede DB enthält genau ihren eigenen Beleg (1), keinen fremden. for i, name := range dbNames { dbDSN := dsnForDB(adminDSN, name) count, err := CountMarkers(ctx, dbDSN, "isolation_marker") if err != nil { t.Fatalf("zaehlung in db %d fehlgeschlagen: %v", i, err) } if count != 1 { t.Errorf("db %d (%s): erwartet genau 1 eigenen Beleg, gefunden %d — Isolation verletzt", i, name, count) } } } // dsnForDB ersetzt den Datenbanknamen im Wartungs-DSN durch den gegebenen Namen. func dsnForDB(adminDSN, dbName string) string { idx := strings.LastIndex(adminDSN, "/") if idx == -1 { return adminDSN } base := adminDSN[:idx+1] rest := adminDSN[idx+1:] if q := strings.Index(rest, "?"); q != -1 { return base + dbName + rest[q:] } return base + dbName }