100 lines
2.8 KiB
Go
100 lines
2.8 KiB
Go
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
|
|
}
|