Volle Merge-Verifikation aller 21 Vorbedingungs-Tickets: neue internal/e2e-Tests (Tenant-Onboarding-Login-Flow, Cross-Tenant-Isolation) gegen echte Postgres-Instanz, docs/QA-02-PRUEFPROTOKOLL.md mit Ticket-zu-Test-Zuordnung. Dabei echten Merge-Konflikt zwischen IAM-12 und IAM-13 gefunden und behoben (jwk/jwkSet-Typkollision in internal/oidc). 124/124 Tests gruen auf 131.
113 lines
3.8 KiB
Go
113 lines
3.8 KiB
Go
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/auth"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/tenant"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/user"
|
|
)
|
|
|
|
// TestE2E_TenantOnboardingLoginFlow ist der zentrale QA-02-Nachweis: der
|
|
// vollstaendige, echte Weg ueber TEN-01..07/IAM-01..14 hinweg -
|
|
//
|
|
// Mandant provisionieren (internal/tenant.Provisioner, physisch isolierte DB)
|
|
// -> Administrator anlegen (internal/user.TenantUserStore.Create +
|
|
// SetPasswordHash, wie es internal/tenant.OnboardingService fuer TEN-02
|
|
// tut, hier explizit nachgebaut um zusaetzlich ein Passwort zu setzen)
|
|
// -> Anmelden (internal/auth.LoginService.Login)
|
|
// -> Assert: gueltiges Token, richtiger Tenant.
|
|
func TestE2E_TenantOnboardingLoginFlow(t *testing.T) {
|
|
adminDSN := requireAdminDSN(t)
|
|
ctx := context.Background()
|
|
root := repoRoot(t)
|
|
|
|
adminPool, err := pgxpool.New(ctx, adminDSN)
|
|
if err != nil {
|
|
t.Fatalf("admin pool: %v", err)
|
|
}
|
|
defer adminPool.Close()
|
|
|
|
applyRegistrySchema(t, ctx, adminPool, root)
|
|
|
|
registry := tenant.NewRegistry(adminPool)
|
|
dsnTemplate := strings.Replace(adminDSN, "/postgres?", "/%s?", 1)
|
|
provisioner := tenant.NewProvisioner(adminPool, registry, dsnTemplate)
|
|
|
|
t.Cleanup(func() {
|
|
_, _ = adminPool.Exec(ctx, `DELETE FROM tenants WHERE slug = 'e2e_onboard'`)
|
|
dropTenantDBs(ctx, adminPool, "tenant_e2e_onboard")
|
|
})
|
|
|
|
// Schritt 1: Mandant provisionieren (TEN-01/TEN-02) - eigene, physisch
|
|
// getrennte Datenbank statt gemeinsamer tenant_id-Spalte.
|
|
tn, err := provisioner.Provision(ctx, "e2e_onboard", "E2E Onboarding GmbH")
|
|
if err != nil {
|
|
t.Fatalf("provision: %v", err)
|
|
}
|
|
|
|
tenantPool, err := pgxpool.New(ctx, tn.DBDSN)
|
|
if err != nil {
|
|
t.Fatalf("connect tenant db: %v", err)
|
|
}
|
|
defer tenantPool.Close()
|
|
|
|
// Schema in der frisch angelegten Tenant-Datenbank ausrollen (TEN-07-
|
|
// Migrationslogik, hier direkt statt ueber den Orchestrator, um im Test
|
|
// nicht von der Registry-Iteration abhaengig zu sein).
|
|
applyTenantSchema(t, ctx, tenantPool, root)
|
|
|
|
// Schritt 2: Benutzer anlegen (IAM-01/IAM-02) und Passwort setzen
|
|
// (IAM-03, bcrypt ueber internal/auth.HashPassword).
|
|
userStore := user.NewTenantUserStore(tenantPool)
|
|
created, err := userStore.Create(ctx, "admin@e2e_onboard.example", "E2E Admin")
|
|
if err != nil {
|
|
t.Fatalf("benutzer anlegen: %v", err)
|
|
}
|
|
|
|
hash, err := auth.HashPassword("korrekt-pferd-batterie-heftklammer")
|
|
if err != nil {
|
|
t.Fatalf("passwort hashen: %v", err)
|
|
}
|
|
if err := userStore.SetPasswordHash(ctx, created.ID, hash); err != nil {
|
|
t.Fatalf("passwort-hash setzen: %v", err)
|
|
}
|
|
|
|
// Schritt 3: Anmelden (IAM-04/IAM-05 - LoginService gegen genau diese
|
|
// Tenant-DB, kein tenant-uebergreifendes Login moeglich).
|
|
issuer := auth.NewTokenIssuer("test-signing-secret-fuer-qa-02-e2e")
|
|
login := auth.NewLoginService(userStore, issuer, tn.Slug)
|
|
|
|
token, err := login.Login(ctx, "admin@e2e_onboard.example", "korrekt-pferd-batterie-heftklammer")
|
|
if err != nil {
|
|
t.Fatalf("login: %v", err)
|
|
}
|
|
if token == "" {
|
|
t.Fatal("login lieferte leeres token")
|
|
}
|
|
|
|
// Assert: Token ist gueltig UND traegt den richtigen Tenant (Verify statt
|
|
// blindem String-Nichtleer-Check - sonst waere ein kaputtes Token auch
|
|
// "gruen").
|
|
claims, err := issuer.Verify(token)
|
|
if err != nil {
|
|
t.Fatalf("token verifizieren: %v", err)
|
|
}
|
|
if claims.TenantSlug != "e2e_onboard" {
|
|
t.Fatalf("token tenant = %q, want %q", claims.TenantSlug, "e2e_onboard")
|
|
}
|
|
if claims.UserID != created.ID {
|
|
t.Fatalf("token user id = %q, want %q", claims.UserID, created.ID)
|
|
}
|
|
|
|
// Gegenprobe: falsches Passwort scheitert weiterhin (kein False-Positive
|
|
// durch einen zu freizuegigen Testaufbau).
|
|
if _, err := login.Login(ctx, "admin@e2e_onboard.example", "falsches-passwort"); err == nil {
|
|
t.Fatal("login mit falschem passwort haette scheitern muessen")
|
|
}
|
|
}
|