Add comprehensive iOS unit and UI test suites for greenfield test plan
- Create unit tests: DataLayerTests (27 tests for DATA-001–007), DataManagerExtendedTests (20 tests for TASK-005, TASK-012, TCOMP-003, THEME-001, QA-002), plus ValidationHelpers, TaskMetrics, StringExtensions, DoubleExtensions, DateUtils, DocumentHelpers, ErrorMessageParser - Create UI tests: AuthenticationTests, PasswordResetTests, OnboardingTests, TaskIntegration, ContractorIntegration, ResidenceIntegration, DocumentIntegration, DataLayer, Stability - Add UI test framework: AuthenticatedTestCase, ScreenObjects, TestFlows, TestAccountManager, TestAccountAPIClient, TestDataCleaner, TestDataSeeder - Add accessibility identifiers to password reset views for UI test support - Add greenfield test plan CSVs and update automated column for 27 test IDs - All 297 unit tests pass across 60 suites Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
127
iosApp/CaseraUITests/Framework/TestAccountManager.swift
Normal file
127
iosApp/CaseraUITests/Framework/TestAccountManager.swift
Normal file
@@ -0,0 +1,127 @@
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
/// High-level account lifecycle management for UI tests.
|
||||
enum TestAccountManager {
|
||||
|
||||
// MARK: - Credential Generation
|
||||
|
||||
/// Generate unique credentials with a timestamp + random suffix to avoid collisions.
|
||||
static func uniqueCredentials(prefix: String = "uit") -> (username: String, email: String, password: String) {
|
||||
let stamp = Int(Date().timeIntervalSince1970)
|
||||
let random = Int.random(in: 1000...9999)
|
||||
let username = "\(prefix)_\(stamp)_\(random)"
|
||||
let email = "\(username)@test.example.com"
|
||||
let password = "Pass\(stamp)!"
|
||||
return (username, email, password)
|
||||
}
|
||||
|
||||
// MARK: - Account Creation
|
||||
|
||||
/// Create a verified account via the backend API. Returns a ready-to-use session.
|
||||
/// Calls `XCTFail` and returns nil if any step fails.
|
||||
static func createVerifiedAccount(
|
||||
file: StaticString = #filePath,
|
||||
line: UInt = #line
|
||||
) -> TestSession? {
|
||||
let creds = uniqueCredentials()
|
||||
|
||||
guard let session = TestAccountAPIClient.createVerifiedAccount(
|
||||
username: creds.username,
|
||||
email: creds.email,
|
||||
password: creds.password
|
||||
) else {
|
||||
XCTFail("Failed to create verified account for \(creds.username)", file: file, line: line)
|
||||
return nil
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
/// Create an unverified account (register only, no email verification).
|
||||
/// Useful for testing the verification gate.
|
||||
static func createUnverifiedAccount(
|
||||
file: StaticString = #filePath,
|
||||
line: UInt = #line
|
||||
) -> TestSession? {
|
||||
let creds = uniqueCredentials()
|
||||
|
||||
guard let response = TestAccountAPIClient.register(
|
||||
username: creds.username,
|
||||
email: creds.email,
|
||||
password: creds.password
|
||||
) else {
|
||||
XCTFail("Failed to register unverified account for \(creds.username)", file: file, line: line)
|
||||
return nil
|
||||
}
|
||||
|
||||
return TestSession(
|
||||
token: response.token,
|
||||
user: response.user,
|
||||
username: creds.username,
|
||||
password: creds.password
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Seeded Accounts
|
||||
|
||||
/// Login with a pre-seeded account that already exists in the database.
|
||||
static func loginSeededAccount(
|
||||
username: String = "admin",
|
||||
password: String = "test1234",
|
||||
file: StaticString = #filePath,
|
||||
line: UInt = #line
|
||||
) -> TestSession? {
|
||||
guard let response = TestAccountAPIClient.login(username: username, password: password) else {
|
||||
XCTFail("Failed to login seeded account '\(username)'", file: file, line: line)
|
||||
return nil
|
||||
}
|
||||
|
||||
return TestSession(
|
||||
token: response.token,
|
||||
user: response.user,
|
||||
username: username,
|
||||
password: password
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Password Reset
|
||||
|
||||
/// Execute the full forgot→verify→reset cycle via the backend API.
|
||||
static func resetPassword(
|
||||
email: String,
|
||||
newPassword: String,
|
||||
file: StaticString = #filePath,
|
||||
line: UInt = #line
|
||||
) -> Bool {
|
||||
guard TestAccountAPIClient.forgotPassword(email: email) != nil else {
|
||||
XCTFail("Forgot password request failed for \(email)", file: file, line: line)
|
||||
return false
|
||||
}
|
||||
|
||||
guard let verifyResponse = TestAccountAPIClient.verifyResetCode(email: email) else {
|
||||
XCTFail("Verify reset code failed for \(email)", file: file, line: line)
|
||||
return false
|
||||
}
|
||||
|
||||
guard TestAccountAPIClient.resetPassword(resetToken: verifyResponse.resetToken, newPassword: newPassword) != nil else {
|
||||
XCTFail("Reset password failed for \(email)", file: file, line: line)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - Token Management
|
||||
|
||||
/// Invalidate a session token via the logout API.
|
||||
static func invalidateToken(
|
||||
_ session: TestSession,
|
||||
file: StaticString = #filePath,
|
||||
line: UInt = #line
|
||||
) {
|
||||
if TestAccountAPIClient.logout(token: session.token) == nil {
|
||||
XCTFail("Failed to invalidate token for \(session.username)", file: file, line: line)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user