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