Files
honeyDueKMP/iosApp/CaseraUITests/Framework/TestDataCleaner.swift
treyt fc0e0688eb 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>
2026-02-24 15:37:56 -06:00

131 lines
4.6 KiB
Swift

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
}
}