Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
Voller Merge der 15 verbleibenden Vorbedingungen (QA-07/QA-08/TEN-08/AUD-02/ API-04/OPS-03/API-06/AUD-05/API-07/LIC-05/OPS-04/OPS-05/OPS-06/IAM-15 plus QA-03) auf den bereits gemergten Staenden von QA-02/QA-04/QA-09. Konsolidiert alle sechs vorgelagerten Pruefgates (QA-02/03/04/07/08/09) - widerspruchsfrei, Akzeptanzkriterium 1 erfuellt. Echter, substanzieller Befund beim Audit-Log-Stichprobenabgleich (Pruefung 1): internal/policy.Store.Grant/Revoke, Tenant-Lifecycle-Uebergaenge, Lockout und KEK-Rotation rufen internal/audit.Log.Record nirgends auf - der zentrale, unveraenderliche Audit-Log (AUD-01/02) existiert und ist getestet, wird aber von keinem Produktions-Handler tatsaechlich befuellt. Bewusst NICHT in dieser Kachel behoben (waere Umbau vieler bestehender Pakete, kein punktueller Fix) - dokumentiert mit Begruendung und Auflage vor QA-06. Pruefung 2 (Vier-Augen-Gegenlesen) mangels zweiter Person nicht durchgefuehrt, ebenfalls als Auflage vermerkt. Siehe docs/QA-05-ABNAHME-COMPLIANCE-PRUEFUNG.md. Zwei reale Testinfrastruktur-Fehler gefunden und behoben (kein Produktions- code): fehlender PG-Fehlercode 42723 (duplicate_function, AUD-02s CREATE FUNCTION bei zweiter Migrationsanwendung) in der Toleranzliste der E2E-/Pentest-Testhelfer; internal/loadtest wiederholte die aus QA-04 bekannte defer-vor-t.Cleanup-Reihenfolge-Fehlerklasse (200 liegen gebliebene synthetische Tenant-Zeilen verfaelschten internal/migrate). 51/51 Pakete gruen auf 192.168.1.131. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
108 lines
3.7 KiB
Go
108 lines
3.7 KiB
Go
// Package e2e enthaelt QA-02 (Pruefgate Identitaet & Mandanten): echte
|
|
// End-to-End-Tests gegen eine laufende Postgres-Instanz, die den vollen Weg
|
|
// "Mandant anlegen -> Benutzer anlegen -> anmelden" ueber die tatsaechlichen
|
|
// Produktionspakete (internal/tenant, internal/user, internal/auth) treiben,
|
|
// statt nur die Einzelpakete isoliert zu testen.
|
|
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// requireAdminDSN liefert TEST_ADMIN_DSN oder ueberspringt den Test - wie in
|
|
// allen bestehenden Integrationstests dieses Repos (siehe
|
|
// internal/tenant/provisioner_test.go).
|
|
func requireAdminDSN(t *testing.T) string {
|
|
t.Helper()
|
|
dsn := os.Getenv("TEST_ADMIN_DSN")
|
|
if dsn == "" {
|
|
t.Skip("TEST_ADMIN_DSN nicht gesetzt, E2E-Test uebersprungen")
|
|
}
|
|
return dsn
|
|
}
|
|
|
|
// repoRoot findet den Modul-Root ausgehend vom Testverzeichnis, damit die
|
|
// SQL-Migrationsverzeichnisse unabhaengig vom Arbeitsverzeichnis (go test
|
|
// wechselt ins Paketverzeichnis) gefunden werden.
|
|
func repoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("getwd: %v", err)
|
|
}
|
|
// internal/e2e -> zwei Ebenen hoch zum Modul-Root.
|
|
return filepath.Join(wd, "..", "..")
|
|
}
|
|
|
|
// applyRegistrySchema legt die Control-Plane-Tabellen (tenants, superadmins,
|
|
// lifecycle-Spalten) frisch an, indem alle migrations/*.up.sql in
|
|
// Dateinamen-Reihenfolge angewendet werden - identisch zur echten
|
|
// Rollout-Reihenfolge, nicht eine verkuerzte Testkopie des Schemas.
|
|
func applyRegistrySchema(t *testing.T, ctx context.Context, pool *pgxpool.Pool, root string) {
|
|
t.Helper()
|
|
applyAllUpSQL(t, ctx, pool, filepath.Join(root, "migrations"))
|
|
}
|
|
|
|
// applyTenantSchema initialisiert eine frisch provisionierte Tenant-Datenbank
|
|
// mit allen migrations/tenant/*.up.sql (nicht nur 0001_users, damit
|
|
// password_hash, role_assignments etc. wie in Produktion vorhanden sind).
|
|
func applyTenantSchema(t *testing.T, ctx context.Context, pool *pgxpool.Pool, root string) {
|
|
t.Helper()
|
|
applyAllUpSQL(t, ctx, pool, filepath.Join(root, "migrations", "tenant"))
|
|
}
|
|
|
|
func applyAllUpSQL(t *testing.T, ctx context.Context, pool *pgxpool.Pool, dir string) {
|
|
t.Helper()
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
t.Fatalf("migrationsverzeichnis %q lesen: %v", dir, err)
|
|
}
|
|
var names []string
|
|
for _, e := range entries {
|
|
if !e.IsDir() && strings.HasSuffix(e.Name(), ".up.sql") {
|
|
names = append(names, e.Name())
|
|
}
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
sqlBytes, err := os.ReadFile(filepath.Join(dir, name))
|
|
if err != nil {
|
|
t.Fatalf("migration %q lesen: %v", name, err)
|
|
}
|
|
if _, err := pool.Exec(ctx, string(sqlBytes)); err != nil {
|
|
// Migrationen sind Produktionsartefakte ohne eingebaute
|
|
// Idempotenz (in Produktion durch eine Migrations-Tracking-
|
|
// Tabelle abgesichert, die dieser Test bewusst nicht nachbaut).
|
|
// Zwei E2E-Tests im selben Prozess teilen sich dieselbe
|
|
// Registry-Datenbank, daher ist "schon vorhanden" hier kein
|
|
// Fehler, sondern der Normalfall beim zweiten Testlauf.
|
|
var pgErr *pgconn.PgError
|
|
if errors.As(err, &pgErr) && (pgErr.Code == "42P07" || pgErr.Code == "42701" || pgErr.Code == "42710" || pgErr.Code == "42723") {
|
|
continue
|
|
}
|
|
t.Fatalf("migration %q anwenden: %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// dropTenantDBs entfernt Test-Datenbanken bedingungslos, fuer Cleanup nach
|
|
// dem Test unabhaengig vom Testausgang.
|
|
func dropTenantDBs(ctx context.Context, adminPool *pgxpool.Pool, names ...string) {
|
|
for _, n := range names {
|
|
_, _ = adminPool.Exec(ctx, `DROP DATABASE IF EXISTS `+quoteIdent(n))
|
|
}
|
|
}
|
|
|
|
func quoteIdent(name string) string {
|
|
return `"` + strings.ReplaceAll(name, `"`, `""`) + `"`
|
|
}
|