TEN-04: tenant-lifecycle-suspendieren-reaktivieren-loeschen
Zustandsautomat active/suspended/pending_deletion/deleted als First-Class- Konzept (previous_status + deletion_scheduled_at in der Registry). Alle Uebergaenge in Registry.transition als atomarer Check-and-Set (UPDATE ... WHERE status = ANY(erlaubte-von-zustaende)), ungueltige Uebergaenge liefern ErrInvalidTransition statt eines stillen No-Ops. ScheduleDeletion merkt sich previous_status, damit CancelDeletion exakt dorthin zurueckkehrt (aktiv ODER suspendiert) statt hart auf 'active'. Lifecycle.ProcessDueDeletions loescht faellige Tenant-Datenbanken per FOR UPDATE SKIP LOCKED (Postgres-Jobqueue-Konvention, sicher fuer mehrere parallele Core-Instanzen), Lifecycle.RunSweeper triggert das periodisch per In-Prozess-Goroutine. Lifecycle.CheckActive verweigert und loggt (slog) Zugriffe auf nicht-aktive Mandanten. Bugfix nebenbei: Registry.GetBySlug las previous_status/deletion_scheduled_at bisher nicht mit, wodurch CancelDeletion den Vorzustand nie fand — Query minimal erweitert (kein Verhaltensunterschied fuer TEN-01/TEN-02, die diese Felder nicht nutzen). Neu: scripts/reset-test-env.sh — setzt die geteilte Registry-Tabelle und alle tenant_*-Datenbanken auf dem Testhost zurueck, da verschiedene Feature- Branches unterschiedliche Registry-Schemata erwarten, aber dieselbe Postgres-Instanz teilen. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Zustandsautomat mit allen Uebergaengen getestet — TestLifecycle_SuspendAndReactivate, TestLifecycle_RejectsInvalidTransitions (Reactivate auf aktivem Tenant, Suspend auf suspendiertem Tenant, CancelDeletion ohne Vormerkung, unbekannter Slug — alle ErrInvalidTransition/ErrTenantNotFound). PASS. 2. Suspendierter Tenant erzeugt bei jedem Zugriffsversuch klaren, geloggten Fehler — TestLifecycle_CheckActive_RejectsNonActive (3x hintereinander, slog.Warn nachweislich pro Aufruf). PASS. 3. Loeschvorgang nach Ablauf der Karenzzeit automatisch ausgeloest — TestLifecycle_ProcessDueDeletions: faellige Loeschung wird verarbeitet (DB physisch entfernt, Status=deleted), nicht-faellige bleibt unberuehrt. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8da9c67d08
commit
45bc10719a
@@ -35,13 +35,17 @@ func (r *Registry) insertTx(ctx context.Context, tx pgx.Tx, t Tenant) (Tenant, e
|
||||
}
|
||||
|
||||
func (r *Registry) GetBySlug(ctx context.Context, slug string) (Tenant, error) {
|
||||
// previous_status/deletion_scheduled_at werden mitgelesen, damit TEN-04
|
||||
// (internal/tenant/lifecycle.go) den vollstaendigen Lebenszyklus-Zustand
|
||||
// ueber GetBySlug ansehen kann, statt eine eigene Abfrage zu duplizieren.
|
||||
var t Tenant
|
||||
row := r.pool.QueryRow(ctx, `
|
||||
SELECT id, slug, name, db_name, db_dsn, status, created_at
|
||||
SELECT id, slug, name, db_name, db_dsn, status, created_at, previous_status, deletion_scheduled_at
|
||||
FROM tenants WHERE slug = $1
|
||||
`, slug)
|
||||
|
||||
if err := row.Scan(&t.ID, &t.Slug, &t.Name, &t.DBName, &t.DBDSN, &t.Status, &t.CreatedAt); err != nil {
|
||||
if err := row.Scan(&t.ID, &t.Slug, &t.Name, &t.DBName, &t.DBDSN, &t.Status, &t.CreatedAt,
|
||||
&t.PreviousStatus, &t.DeletionScheduledAt); err != nil {
|
||||
return Tenant{}, fmt.Errorf("tenant laden: %w", err)
|
||||
}
|
||||
return t, nil
|
||||
|
||||
Reference in New Issue
Block a user