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:
130
iosApp/CaseraUITests/Framework/TestDataCleaner.swift
Normal file
130
iosApp/CaseraUITests/Framework/TestDataCleaner.swift
Normal file
@@ -0,0 +1,130 @@
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
/// Tracks and cleans up resources created during integration tests.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```
|
||||
/// let cleaner = TestDataCleaner(token: session.token)
|
||||
/// let residence = TestDataSeeder.createResidence(token: session.token)
|
||||
/// cleaner.trackResidence(residence.id)
|
||||
/// // ... test runs ...
|
||||
/// cleaner.cleanAll() // called in tearDown
|
||||
/// ```
|
||||
class TestDataCleaner {
|
||||
private let token: String
|
||||
private var residenceIds: [Int] = []
|
||||
private var taskIds: [Int] = []
|
||||
private var contractorIds: [Int] = []
|
||||
private var documentIds: [Int] = []
|
||||
|
||||
init(token: String) {
|
||||
self.token = token
|
||||
}
|
||||
|
||||
// MARK: - Track Resources
|
||||
|
||||
func trackResidence(_ id: Int) {
|
||||
residenceIds.append(id)
|
||||
}
|
||||
|
||||
func trackTask(_ id: Int) {
|
||||
taskIds.append(id)
|
||||
}
|
||||
|
||||
func trackContractor(_ id: Int) {
|
||||
contractorIds.append(id)
|
||||
}
|
||||
|
||||
func trackDocument(_ id: Int) {
|
||||
documentIds.append(id)
|
||||
}
|
||||
|
||||
// MARK: - Seed + Track (Convenience)
|
||||
|
||||
/// Create a residence and automatically track it for cleanup.
|
||||
@discardableResult
|
||||
func seedResidence(name: String? = nil) -> TestResidence {
|
||||
let residence = TestDataSeeder.createResidence(token: token, name: name)
|
||||
trackResidence(residence.id)
|
||||
return residence
|
||||
}
|
||||
|
||||
/// Create a task and automatically track it for cleanup.
|
||||
@discardableResult
|
||||
func seedTask(residenceId: Int, title: String? = nil, fields: [String: Any] = [:]) -> TestTask {
|
||||
let task = TestDataSeeder.createTask(token: token, residenceId: residenceId, title: title, fields: fields)
|
||||
trackTask(task.id)
|
||||
return task
|
||||
}
|
||||
|
||||
/// Create a contractor and automatically track it for cleanup.
|
||||
@discardableResult
|
||||
func seedContractor(name: String? = nil, fields: [String: Any] = [:]) -> TestContractor {
|
||||
let contractor = TestDataSeeder.createContractor(token: token, name: name, fields: fields)
|
||||
trackContractor(contractor.id)
|
||||
return contractor
|
||||
}
|
||||
|
||||
/// Create a document and automatically track it for cleanup.
|
||||
@discardableResult
|
||||
func seedDocument(residenceId: Int, title: String? = nil, documentType: String = "Other") -> TestDocument {
|
||||
let document = TestDataSeeder.createDocument(token: token, residenceId: residenceId, title: title, documentType: documentType)
|
||||
trackDocument(document.id)
|
||||
return document
|
||||
}
|
||||
|
||||
/// Create a residence with tasks, all tracked for cleanup.
|
||||
func seedResidenceWithTasks(residenceName: String? = nil, taskCount: Int = 3) -> (residence: TestResidence, tasks: [TestTask]) {
|
||||
let result = TestDataSeeder.createResidenceWithTasks(token: token, residenceName: residenceName, taskCount: taskCount)
|
||||
trackResidence(result.residence.id)
|
||||
result.tasks.forEach { trackTask($0.id) }
|
||||
return result
|
||||
}
|
||||
|
||||
/// Create a full residence with task, contractor, and document, all tracked.
|
||||
func seedFullResidence() -> (residence: TestResidence, task: TestTask, contractor: TestContractor, document: TestDocument) {
|
||||
let result = TestDataSeeder.createFullResidence(token: token)
|
||||
trackResidence(result.residence.id)
|
||||
trackTask(result.task.id)
|
||||
trackContractor(result.contractor.id)
|
||||
trackDocument(result.document.id)
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Cleanup
|
||||
|
||||
/// Delete all tracked resources in reverse dependency order.
|
||||
/// Documents and tasks first (they depend on residences), then contractors, then residences.
|
||||
/// Failures are logged but don't fail the test — cleanup is best-effort.
|
||||
func cleanAll() {
|
||||
// Delete documents first (depend on residences)
|
||||
for id in documentIds.reversed() {
|
||||
_ = TestAccountAPIClient.deleteDocument(token: token, id: id)
|
||||
}
|
||||
documentIds.removeAll()
|
||||
|
||||
// Delete tasks (depend on residences)
|
||||
for id in taskIds.reversed() {
|
||||
_ = TestAccountAPIClient.deleteTask(token: token, id: id)
|
||||
}
|
||||
taskIds.removeAll()
|
||||
|
||||
// Delete contractors (independent, but clean before residences)
|
||||
for id in contractorIds.reversed() {
|
||||
_ = TestAccountAPIClient.deleteContractor(token: token, id: id)
|
||||
}
|
||||
contractorIds.removeAll()
|
||||
|
||||
// Delete residences last
|
||||
for id in residenceIds.reversed() {
|
||||
_ = TestAccountAPIClient.deleteResidence(token: token, id: id)
|
||||
}
|
||||
residenceIds.removeAll()
|
||||
}
|
||||
|
||||
/// Number of tracked resources (for debugging).
|
||||
var trackedCount: Int {
|
||||
residenceIds.count + taskIds.count + contractorIds.count + documentIds.count
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user