Re-architect iOS XCUITest suite: per-test isolation + domain organization
Migrate the XCUITest suite off the legacy shared-account model (and the prior Django-style auth assumptions) to a parallel-safe, domain-organized architecture, validated end-to-end against the live Kratos stack. Isolation (parallel-safe by construction): - Core/Fixtures/TestAccount.swift: each test mints its own pre-verified Kratos identity (uit_<domain>_<uuid>@test.honeydue.local), logs in, seeds under its own token, and deletes the identity in teardown (cascading all data + clearing Kratos). No shared testuser; parallel workers no longer race. - AuthenticatedUITestCase rewritten to that model (member surface preserved); adds requiresResidence / seedAccountPreconditions to seed UI-gated data BEFORE login (a fresh account is empty at login). Organization (255 tests preserved, none dropped): - 21 domain suites under Auth/ Onboarding/ Residence/ Task/ Contractor/ Document/ Sharing/ Navigation/ Smoke/ CrossCutting/ E2E/, consistent <Domain>UITests naming. Removes the Suite1..11 / AAA_ / ZZ_ / Tests/Rebuild naming chaos and the overlapping task/residence/auth suites. Runner + test plans: - run_ui_tests.sh: Smoke gate -> Seed -> Parallel(8 workers) -> Sweep. The parallel phase runs the whole target minus phase-managed suites via -skip-testing, so new suites auto-include (no hand-maintained list to drift). Drops the 2-worker cap and Suite6 isolation (isolation made them moot). - HoneyDueUITests.xctestplan skips the 4 phase-managed suites; adds Smoke.xctestplan. Kratos auth fixes folded in (login/verify/reset endpoints removed under Kratos): real Mailpit verification codes replace the obsolete fixed "123456"; teardown deletes Kratos identities; admin-panel login uses the correct seeded password. Build green; isolation, parallelism, and the precondition/sharing migrations validated against the live stack (0 leaked accounts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,24 @@ import XCTest
|
||||
|
||||
/// Phase 3 — Cleanup tests run sequentially after all parallel suites.
|
||||
/// Clears test data via the admin API, then re-seeds the required accounts.
|
||||
///
|
||||
/// CLEANUP ORDER (XCTest runs methods alphabetically):
|
||||
/// testCleanup01_clearAllTestData → admin-panel login + POST /admin/settings/clear-all-data
|
||||
/// testCleanup01b_deleteKratosIdentities → delete seeded Kratos identities (clean slate)
|
||||
/// testCleanup02_reSeedTestUser → re-create testuser via Kratos
|
||||
/// testCleanup03_reSeedAdmin → re-create admin via Kratos
|
||||
///
|
||||
/// WHY WE DELETE KRATOS IDENTITIES:
|
||||
/// `clear-all-data` is LOCAL-ONLY — it wipes residences/tasks and non-superuser
|
||||
/// `auth_user` rows in Postgres, but it does NOT touch Kratos. The Kratos
|
||||
/// identities (testuser@honeydue.com, admin@honeydue.com) survive the wipe, and
|
||||
/// the backend also caches validated Kratos sessions in Redis (kratos_sess:<hash>,
|
||||
/// 24h TTL). Left alone, that leaves orphaned/stale auth state across runs:
|
||||
/// - Re-seeding via createVerifiedAccount would hit a Kratos 409 (identity exists).
|
||||
/// - Tokens minted before the wipe map to now-deleted local user rows → stale-session
|
||||
/// errors until the next GET /auth/me/ lazily re-provisions the local user.
|
||||
/// Deleting the Kratos identities after the local wipe makes re-seed a TRUE reset:
|
||||
/// fresh identities, no 409, no orphaned sessions.
|
||||
final class SuiteZZ_CleanupTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
@@ -14,20 +32,37 @@ final class SuiteZZ_CleanupTests: XCTestCase {
|
||||
func testCleanup01_clearAllTestData() {
|
||||
let baseURL = TestAccountAPIClient.baseURL
|
||||
|
||||
// 1. Login to admin panel (admin API uses Bearer token)
|
||||
// Try re-seeded password first, then fallback to default
|
||||
var adminToken = adminLogin(baseURL: baseURL, password: "test1234")
|
||||
if adminToken == nil {
|
||||
adminToken = adminLogin(baseURL: baseURL, password: "password123")
|
||||
}
|
||||
// 1. Login to the admin PANEL (SQL super-admin: admin@honeydue.com / password123).
|
||||
// This is a different system from the Kratos APP identity that happens to
|
||||
// share the admin@honeydue.com email — see AuthenticatedUITestCase for the
|
||||
// full distinction. Admin API uses a Bearer token.
|
||||
let adminToken = adminLogin(baseURL: baseURL, password: "password123")
|
||||
XCTAssertNotNil(adminToken, "Admin login failed — cannot clear test data")
|
||||
guard let token = adminToken else { return }
|
||||
|
||||
// 2. Call clear-all-data
|
||||
// 2. Call clear-all-data (LOCAL-ONLY wipe — see class header).
|
||||
let clearResult = adminClearAllData(baseURL: baseURL, token: token)
|
||||
XCTAssertTrue(clearResult, "Failed to clear all test data via admin API")
|
||||
}
|
||||
|
||||
// MARK: - Delete Kratos Identities
|
||||
|
||||
/// Runs between the local wipe (01) and re-seed (02). `clear-all-data` is
|
||||
/// local-only, so the seeded Kratos identities survive it. Delete them here so
|
||||
/// re-seeding creates fresh identities with no Kratos 409 and no orphaned/stale
|
||||
/// auth state (see class header). Best-effort: deleteKratosIdentity is idempotent
|
||||
/// (true if deleted or already absent); we log but do not hard-fail on false.
|
||||
func testCleanup01b_deleteKratosIdentities() {
|
||||
let deletedTestUser = TestAccountAPIClient.deleteKratosIdentity(email: "testuser@honeydue.com")
|
||||
if !deletedTestUser {
|
||||
NSLog("[Cleanup] deleteKratosIdentity(testuser@honeydue.com) returned false — continuing (best-effort)")
|
||||
}
|
||||
let deletedAdmin = TestAccountAPIClient.deleteKratosIdentity(email: "admin@honeydue.com")
|
||||
if !deletedAdmin {
|
||||
NSLog("[Cleanup] deleteKratosIdentity(admin@honeydue.com) returned false — continuing (best-effort)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Re-Seed Accounts
|
||||
|
||||
func testCleanup02_reSeedTestUser() {
|
||||
@@ -51,7 +86,8 @@ final class SuiteZZ_CleanupTests: XCTestCase {
|
||||
// MARK: - Private Helpers
|
||||
|
||||
/// Admin API uses `Bearer` token (not `Token` prefix), so we use inline URLRequest.
|
||||
private func adminLogin(baseURL: String, password: String = "test1234") -> String? {
|
||||
/// The admin-panel super-admin is admin@honeydue.com / password123.
|
||||
private func adminLogin(baseURL: String, password: String = "password123") -> String? {
|
||||
guard let url = URL(string: "\(baseURL)/admin/auth/login") else { return nil }
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
|
||||
Reference in New Issue
Block a user