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.
13 KiB
QA-02 – Prüfprotokoll: Prüfgate Identität & Mandanten
Stand: 2026-08-29. Branch feature/qa-02-pruefgate-identitaet-mandanten (alle 21 Vorbedingungs-Tickets TEN-01..07 und IAM-01..14 gemergt, per git merge-base gegen jeden Feature-Branch verifiziert).
1. Akzeptanzkriterien TEN-01 bis TEN-07 — Testabdeckung
| Ticket | Titel | Abdeckende Tests |
|---|---|---|
| TEN-01 | Mandantenregistrierung / Modell A/C | internal/tenant/tenant_test.go: TestValidateSlug, TestDBNameForSlug |
| TEN-02 | Self-Service-Onboarding | internal/tenant/provisioner_test.go: TestProvision_CreatesIsolatedDatabases; internal/tenant/onboarding_test.go: TestOnboarding_ValidationErrors, TestOnboarding_CreatesTenantAndAdmin, TestOnboarding_RejectsDuplicateSlugConcurrently |
| TEN-03 | Tenant-Router / Verbindungsauswahl | internal/tenant/router_test.go: TestRouter_ResolvesCorrectTenantDatabase, TestRouter_RejectsMissingOrUnknownTenant, TestRouter_ReusesConnectionForSameTenant, TestRouter_BoundsOpenConnectionsUnderLoad |
| TEN-04 | Mandanten-Lebenszyklus (Suspend/Reaktivieren) | internal/tenant/lifecycle_test.go: TestLifecycle_SuspendAndReactivate, TestLifecycle_RejectsInvalidTransitions, TestLifecycle_CheckActive_RejectsNonActive |
| TEN-05 | Mandanten-Löschung (DSGVO-Löschfrist) | internal/tenant/lifecycle_test.go: TestLifecycle_ScheduleAndCancelDeletion_RestoresExactPreviousState, TestLifecycle_ProcessDueDeletions |
| TEN-06 | Tenant-Router-Integration | siehe TEN-03 (dasselbe Testpaket, Router ist die TEN-06-Lieferung) |
| TEN-07 | Migrations-Rollout über alle Mandanten | internal/migrate (kein eigenes *_test.go im Merge-Ergebnis gefunden — siehe Abweichung 4.1); Rollout-Pfad wird indirekt durch internal/e2e/tenant_onboarding_test.go und cross_tenant_isolation_test.go genutzt (applyTenantSchema wendet dieselben migrations/tenant/*.up.sql in derselben Reihenfolge an) |
2. Akzeptanzkriterien IAM-01 bis IAM-14 — Testabdeckung
| Ticket | Titel | Abdeckende Tests |
|---|---|---|
| IAM-01 | Benutzerverwaltung (CRUD) | internal/user/store_test.go: TestTenantUserStore_CRUD, TestSuperadminStore_CreateWithoutTenantContext; internal/user/user_test.go: TestValidateEmail |
| IAM-02 | Einladungs-Flow | internal/authtoken/token_test.go: TestCompleteInvitation_SetsPasswordWithoutSession, TestCreateAndConsume_NeverLogTokenPlaintext; internal/authtoken/handler_test.go |
| IAM-03 | Passwort-Hashing (bcrypt) | internal/auth/password_test.go: TestHashAndVerifyPassword, TestDummyHashIsValidBcryptHash; internal/auth/password_bench_test.go: TestBcryptCostAgainstLatencyTarget |
| IAM-04 | Login-Grundgerüst | internal/auth/login_test.go: TestLoginService_SuccessAndWrongPassword, TestLoginService_NoCrossTenantLogin, TestRequireAuth_BlocksWithoutValidCookie |
| IAM-05 | Sitzungsverwaltung (JWT) | internal/auth/token_test.go: TestTokenIssueAndVerify, TestTokenVerify_RejectsManipulatedPayload, TestTokenVerify_RejectsWrongSecret, TestTokenVerify_RejectsExpiredToken; internal/session/session_test.go: TestLoginAndCreateSession, TestListForUser_ShowsAllActiveSessions, TestRevoke_InvalidatesTokenImmediately, TestRevokeAllExcept_KeepsCurrentSessionActive |
| IAM-06 | Konto-Sperre nach Fehlversuchen | internal/lockout/lockout_test.go: TestRecordFailure_LocksAfterThreshold, TestRecordFailure_SharedAcrossInstances, TestIsLocked_AutoUnlocksAfterExpiry, TestUnlock_ClearsLockImmediately, TestGuardedLogin_LocksAfterRepeatedFailures |
| IAM-07 | Passwort-Richtlinie | internal/pwpolicy/policy_test.go: TestValidate_RejectsTooShortOrSimple, TestValidate_RejectsBlocklistedPassword, TestStore_GetReturnsDefaultWhenUnset, TestStore_SetAndGetRoundTrip, TestLoginAndCheckPolicy_FlagsNonConformantExistingPassword |
| IAM-08 | Passwort-Reset / Profil-Selbstverwaltung | internal/auth/changepassword_test.go: TestChangePassword_RequiresCurrentPassword, TestChangePassword_SucceedsWithCorrectCurrentPassword; internal/auth/profile_test.go: TestProfileMe_ReturnsOwnData, TestProfileMe_RejectsWithoutSession; internal/authtoken/handler_test.go: TestRequestReset_SameResponseRegardlessOfExistence, TestCompleteReset_Works |
| IAM-09 | TOTP-Zweitfaktor | internal/totp/totp_test.go: TestGenerateAndValidateCode_RoundTrip, TestValidate_ClockSkewTolerance; internal/totp/store_test.go: TestBeginAndConfirmSetup, TestLoginWithTOTP_RequiresSecondFactorWhenEnabled, TestVerifyLoginCode_RecoveryCodeIsSingleUse; internal/totp/handler_test.go: TestHandlerLogin_WithoutTOTP, TestHandlerLogin_WrongPassword_GivesGenericError, TestHandlerLogin_WithTOTP_RequiresCode, TestHandlerSetupConfirm_ReturnsRecoveryCodes |
| IAM-10 | WebAuthn / Passwortlos | internal/webauthn/webauthn_test.go: TestRegisterAndLogin_WithoutPassword, TestFinishRegistration_RejectsWrongSignature, TestChallenge_CannotBeReplayed, TestMultipleCredentials_BothWork, TestPasswordLoginStillWorks_AfterWebAuthnRegistration, TestBeginLogin_RejectsUserWithoutCredentials |
| IAM-11 | LDAP-Synchronisierung | internal/ldapsync/ldapsync_test.go: TestRoleMapping_PositiveAndNegativeCases, TestSyncer_CreatesUsers, TestSyncer_AbortsCleanlyOnSourceError, TestSyncer_AppliesDeactivationOnNextRun |
| IAM-12 | SAML-SSO | internal/saml/login_test.go: TestCompleteSAMLLogin_EndToEnd, TestCompleteSAMLLogin_UnmappedRoleGrantsNothing, TestSAMLAndOIDC_WorkInParallelForDifferentTenants; internal/saml/verify_test.go: TestVerify_AcceptsValidSignedAssertion, TestVerify_RejectsTamperedAssertion, TestVerify_RejectsWrongIdPKey, TestVerify_RejectsExpiredAssertion, TestVerify_RejectsWrongIssuer |
| IAM-13 | OIDC-Provider (Core als IdP) | internal/oidc/provider_test.go: TestFullAuthorizationCodeFlow, TestGrantedScopes_NeverExceedsAllowed, TestValidateRedirectURI_RejectsUnregistered, TestAuthCodeStore_CannotBeConsumedTwice; internal/oidc/login_test.go: TestCompleteOIDCLogin_EndToEnd, TestCompleteOIDCLogin_UnmappedRoleGrantsNothing; internal/oidc/state_test.go, internal/oidc/verify_test.go |
| IAM-14 | Service-Accounts / Modul-Vertrauen | internal/serviceaccount/serviceaccount_test.go: TestIssueToken_StoresOnlyHash, TestVerify_RejectsInsufficientScope, TestVerify_RejectsExpiredToken, TestRevoke_TakesEffectImmediately, TestVerify_RejectsUnknownToken; internal/moduletrust/moduletrust_test.go: TestIssueAndVerify_RoundTrip, TestVerify_DoesNotFetchPerCall, TestVerify_FailsOpenWhenCoreUnreachableButStaleKeysExist, TestRequireFreshKeys_FailsClosedWhenCoreUnreachable, TestRotate_NoDowntimeForAlreadyIssuedTokens, TestVerify_RejectsUnknownKid, TestJWKSRoundTrip |
Ergebnis Abschnitt 1+2: alle 21 Tickets haben eigene, automatisierte Unit-/Integrationstests, die ihre dokumentierten Akzeptanzkriterien einzeln abdecken.
3. Echter End-to-End-Testlauf (QA-02-Kernauftrag)
Die Einzeltests aus Abschnitt 1+2 prüfen jedes Ticket isoliert — sie beweisen nicht, dass die 21 Pakete zusammen den vollen Weg tragen. Dafür neu geschrieben: internal/e2e/.
3.1 TestE2E_TenantOnboardingLoginFlow
Treibt den vollständigen Weg über die echten Produktionspakete, nicht über Mocks:
internal/tenant.Provisioner.Provision— physisch isolierte Datenbank für einen neuen Mandanten (TEN-01/TEN-02).- Schema-Rollout über alle
migrations/tenant/*.up.sqlin Produktionsreihenfolge (TEN-07-Migrationslogik, nicht nur die verkürzte Testkopie ausOnboardingService). internal/user.TenantUserStore.Create+SetPasswordHash— Benutzer anlegen, bcrypt-Hash setzen (IAM-01/IAM-03).internal/auth.LoginService.Login— Anmeldung (IAM-04/IAM-05).- Assert: Token ist mit
TokenIssuer.Verifygültig,Claims.TenantSlugundClaims.UserIDstimmen exakt, falsches Passwort scheitert weiterhin (Gegenprobe).
Ergebnis: bestanden (siehe Abschnitt 5, Build/Test-Ergebnis auf 131).
3.2 TestE2E_CrossTenantIsolation
Zwei vollständig provisionierte Mandanten (e2e-tenant-a, e2e-tenant-b), je ein Benutzer, drei unabhängige Beweise:
- Datenbankebene: aus der Datenbank von Tenant A ist keine Zeile für den Tenant-B-Benutzer abfragbar (und umgekehrt) — kein Query-Filter, sondern physisch getrennte Datenbanken.
- Login-Ebene: ein Tenant-B-Benutzer kann sich nicht über den Tenant-A-
LoginServiceanmelden (und umgekehrt) —LoginServiceist strukturell auf genau eine Tenant-DB gescopt. - Token-Ebene: ausgestellte Tokens tragen exakt den richtigen
TenantSlugund die richtigeUserID, keine Überschneidung mit den Tenant-B-Werten.
Ergebnis: bestanden (siehe Abschnitt 5).
4. Abweichungen
4.1 internal/migrate (TEN-07) hat keine eigene Testdatei im gemergten Stand — Schweregrad: Niedrig
Kein *_test.go für internal/migrate/orchestrator.go/migrations.go im Merge-Ergebnis dieses Branches gefunden. Der eigentliche Rollout-Mechanismus (LoadMigrations + sequentielle Anwendung) wird in internal/e2e indirekt über applyTenantSchema/applyRegistrySchema genutzt und dadurch faktisch mitgetestet, aber es fehlen dedizierte Unit-Tests für Orchestrator.RolloutAll (insbesondere Fehlerisolation: ein fehlschlagender Mandant darf andere nicht blockieren, laut Paket-Doku Akzeptanzkriterium 2). Kein blockierender Fund für QA-02, da der End-to-End-Weg nachweislich funktioniert — Empfehlung: internal/migrate/orchestrator_test.go bei Gelegenheit nachziehen.
Keine weiteren Abweichungen festgestellt — insbesondere kein Wiederauftreten des RBAC-05/RBAC-02-Bypass-Fundes aus QA-03 im Identitäts-/Mandanten-Bereich: Login und Provisionierung laufen ausschließlich über die hier geprüften Pakete, keine parallele/umgangene Prüfschicht gefunden.
4.2 Echter Merge-Konflikt zwischen IAM-12 (SAML/OIDC-Client) und IAM-13 (Core als OIDC-Provider) — Schweregrad: Hoch, behoben
Fund: internal/oidc/jwks.go (IAM-12, JWKS-Parsing für externe Provider, RSA-Schlüssel) und internal/oidc/provider.go (IAM-13, Core als eigener IdP, Ed25519/OKP-Schlüssel) definierten beide einen Typ jwk/jwkSet im selben Package — kompiliert einzeln pro Branch fehlerfrei, scheitert aber beim Zusammenführen beider Branches mit jwk redeclared in this block und Folgefehlern (unbekannte Felder Crv/X/Use/Alg). Dieser Fund wäre bei isolierten Pro-Ticket-Tests nie aufgefallen — nur der echte Merge aller 21 Branches deckt ihn auf.
Behoben: IAM-13s Typen in internal/oidc/provider.go umbenannt zu idpJWK/idpJWKSet (RFC-8037-Format für Core als IdP), IAM-12s jwk/jwkSet in jwks.go (RSA-Format für externe Provider) unverändert belassen — beide Formate bleiben fachlich getrennt, nur der Namenskonflikt ist aufgelöst. internal/oidc/provider_test.go entsprechend angepasst.
Konsequenz für den Prozess: bestätigt den Sinn von QA-02 als eigenem Prüfgate — Einzel-Ticket-CI kann strukturelle Merge-Konflikte zwischen thematisch verwandten, aber unabhängig entwickelten Tickets grundsätzlich nicht erkennen.
5. Build/Test-Ergebnis auf 131
Durchgeführt 2026-08-29 auf root@192.168.1.131 (/root/nexarch-code-qa02, isolierter Sync, kein Konflikt mit dem parallelen QA-03-Testlauf in /root/nexarch-code-qa03):
go mod tidy,go build ./...,go vet ./...— nach Behebung des Merge-Konflikts aus 4.2 alle sauber, keine Fehler.go test ./... -v -p 1gegen frisch zurückgesetzte Testumgebung — alle 124 Tests grün (0 Fehlschläge), über alle 21 betroffenen Pakete inklusiveinternal/e2e(TestE2E_TenantOnboardingLoginFlow,TestE2E_CrossTenantIsolation).- Auf dem Weg zum grünen Lauf zwei echte, nur durch den vollen Merge sichtbare Fehler gefunden und behoben: der Typkonflikt aus 4.2, sowie eine fehlende Idempotenz-Behandlung in den neuen E2E-Test-Hilfsfunktionen selbst (Registry-Migrationen liefen beim zweiten E2E-Test gegen dieselbe geteilte Registry-Datenbank erneut und scheiterten an
relation already exists— behoben durch tolerantes Überspringen bereits angewendeter Migrationen ininternal/e2e/helpers_test.go, kein Produktionscode betroffen). - Keine Regressionen in TEN-01..07 oder IAM-01..14 durch den 21-Branch-Merge.
QA-02 Gesamtergebnis: bestanden. Ein behobener Hoch-Schweregrad-Merge-Konflikt (4.2, echter struktureller Fund dieses Prüfgates), keine offenen Blocker.
6. Gesamtergebnis
Bestanden. Der volle End-to-End-Weg über alle 21 Vorbedingungs-Tickets (TEN-01..07, IAM-01..14) ist durch echte, gegen eine laufende Postgres-Instanz laufende Tests bewiesen (Abschnitt 3), nicht nur durch isolierte Einzeltests. Ein struktureller Merge-Konflikt wurde aufgedeckt und behoben (4.2) — genau die Art Fund, für die dieses Prüfgate existiert. Ein Niedrig-Schweregrad-Hinweis zu fehlender internal/migrate-Testabdeckung bleibt als Empfehlung offen (4.1).