import Foundation import XCTest /// Seeds backend data for integration tests via API calls. /// /// All methods require a valid auth token from a `TestSession`. /// Created resources are tracked so `TestDataCleaner` can remove them in teardown. enum TestDataSeeder { // MARK: - Residence Seeding /// Create a residence with just a name. Returns the residence or fails the test. @discardableResult static func createResidence( token: String, name: String? = nil, file: StaticString = #filePath, line: UInt = #line ) -> TestResidence { let residenceName = name ?? "Test Residence \(uniqueSuffix())" guard let residence = TestAccountAPIClient.createResidence(token: token, name: residenceName) else { XCTFail("Failed to seed residence '\(residenceName)'", file: file, line: line) preconditionFailure("seeding failed") } return residence } /// Create a residence with address fields populated. @discardableResult static func createResidenceWithAddress( token: String, name: String? = nil, street: String = "123 Test St", city: String = "Testville", state: String = "TX", postalCode: String = "78701", file: StaticString = #filePath, line: UInt = #line ) -> TestResidence { let residenceName = name ?? "Addressed Residence \(uniqueSuffix())" guard let residence = TestAccountAPIClient.createResidence( token: token, name: residenceName, fields: [ "street_address": street, "city": city, "state_province": state, "postal_code": postalCode ] ) else { XCTFail("Failed to seed residence with address '\(residenceName)'", file: file, line: line) preconditionFailure("seeding failed") } return residence } // MARK: - Task Seeding /// Create a task in a residence. Returns the task or fails the test. @discardableResult static func createTask( token: String, residenceId: Int, title: String? = nil, fields: [String: Any] = [:], file: StaticString = #filePath, line: UInt = #line ) -> TestTask { let taskTitle = title ?? "Test Task \(uniqueSuffix())" guard let task = TestAccountAPIClient.createTask( token: token, residenceId: residenceId, title: taskTitle, fields: fields ) else { XCTFail("Failed to seed task '\(taskTitle)'", file: file, line: line) preconditionFailure("seeding failed") } return task } /// Create a task with a due date. @discardableResult static func createTaskWithDueDate( token: String, residenceId: Int, title: String? = nil, daysFromNow: Int = 7, file: StaticString = #filePath, line: UInt = #line ) -> TestTask { let dueDate = Calendar.current.date(byAdding: .day, value: daysFromNow, to: Date())! let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withFullDate] let dueDateStr = formatter.string(from: dueDate) return createTask( token: token, residenceId: residenceId, title: title ?? "Due Task \(uniqueSuffix())", fields: ["due_date": dueDateStr], file: file, line: line ) } /// Create a cancelled task (create then cancel via API). @discardableResult static func createCancelledTask( token: String, residenceId: Int, title: String? = nil, file: StaticString = #filePath, line: UInt = #line ) -> TestTask { let task = createTask(token: token, residenceId: residenceId, title: title ?? "Cancelled Task \(uniqueSuffix())", file: file, line: line) guard let cancelled = TestAccountAPIClient.cancelTask(token: token, id: task.id) else { XCTFail("Failed to cancel seeded task \(task.id)", file: file, line: line) preconditionFailure("seeding failed") } return cancelled } // MARK: - Contractor Seeding /// Create a contractor. Returns the contractor or fails the test. @discardableResult static func createContractor( token: String, name: String? = nil, fields: [String: Any] = [:], file: StaticString = #filePath, line: UInt = #line ) -> TestContractor { let contractorName = name ?? "Test Contractor \(uniqueSuffix())" guard let contractor = TestAccountAPIClient.createContractor( token: token, name: contractorName, fields: fields ) else { XCTFail("Failed to seed contractor '\(contractorName)'", file: file, line: line) preconditionFailure("seeding failed") } return contractor } /// Create a contractor with contact info. @discardableResult static func createContractorWithContact( token: String, name: String? = nil, company: String = "Test Co", phone: String = "555-0100", email: String? = nil, file: StaticString = #filePath, line: UInt = #line ) -> TestContractor { let contractorName = name ?? "Contact Contractor \(uniqueSuffix())" let contactEmail = email ?? "\(uniqueSuffix())@contractor.test" return createContractor( token: token, name: contractorName, fields: ["company": company, "phone": phone, "email": contactEmail], file: file, line: line ) } // MARK: - Document Seeding /// Create a document in a residence. Returns the document or fails the test. @discardableResult static func createDocument( token: String, residenceId: Int, title: String? = nil, documentType: String = "general", fields: [String: Any] = [:], file: StaticString = #filePath, line: UInt = #line ) -> TestDocument { let docTitle = title ?? "Test Doc \(uniqueSuffix())" guard let document = TestAccountAPIClient.createDocument( token: token, residenceId: residenceId, title: docTitle, documentType: documentType, fields: fields ) else { XCTFail("Failed to seed document '\(docTitle)'", file: file, line: line) preconditionFailure("seeding failed") } return document } // MARK: - Composite Scenarios /// Create a residence with N tasks already in it. Returns (residence, [tasks]). static func createResidenceWithTasks( token: String, residenceName: String? = nil, taskCount: Int = 3, file: StaticString = #filePath, line: UInt = #line ) -> (residence: TestResidence, tasks: [TestTask]) { let residence = createResidence(token: token, name: residenceName, file: file, line: line) var tasks: [TestTask] = [] for i in 1...taskCount { let task = createTask(token: token, residenceId: residence.id, title: "Task \(i) \(uniqueSuffix())", file: file, line: line) tasks.append(task) } return (residence, tasks) } /// Create a residence with a contractor and a document. Returns all three. static func createFullResidence( token: String, file: StaticString = #filePath, line: UInt = #line ) -> (residence: TestResidence, task: TestTask, contractor: TestContractor, document: TestDocument) { let residence = createResidence(token: token, file: file, line: line) let task = createTask(token: token, residenceId: residence.id, file: file, line: line) let contractor = createContractor(token: token, file: file, line: line) let document = createDocument(token: token, residenceId: residence.id, file: file, line: line) return (residence, task, contractor, document) } // MARK: - Private private static func uniqueSuffix() -> String { let stamp = Int(Date().timeIntervalSince1970) % 100000 let random = Int.random(in: 100...999) return "\(stamp)_\(random)" } }