// // TaskAPITests.swift // suixinkanTests // // Created by Codex on 2026/6/23. // import XCTest @testable import suixinkan @MainActor /// 任务 API 测试,覆盖请求参数、请求体和模型解码。 final class TaskAPITests: XCTestCase { /// 测试任务列表接口使用正确 path 和 query。 func testTaskListUsesExpectedPathAndQuery() async throws { let session = TaskRecordingURLSession(data: try TestFixture.data(named: "task_list_success")) let api = TaskAPI(client: APIClient(session: session)) _ = try await api.taskList( scenicId: 88, page: 0, pageSize: 0, taskStatus: -2, taskName: " 精修 ", startTime: "2026-06-01", endTime: "2026-06-30" ) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/task/list") let query = taskQueryItems(from: request) XCTAssertEqual(query["scenic_id"], "88") XCTAssertEqual(query["page"], "1") XCTAssertEqual(query["page_size"], "1") XCTAssertEqual(query["task_status"], "0") XCTAssertEqual(query["task_name"], "精修") XCTAssertEqual(query["start_time"], "2026-06-01") XCTAssertEqual(query["end_time"], "2026-06-30") } /// 测试任务详情接口使用正确 path 和 id。 func testTaskDetailUsesExpectedPath() async throws { let session = TaskRecordingURLSession(data: try TestFixture.data(named: "task_detail_success")) let api = TaskAPI(client: APIClient(session: session)) let detail = try await api.taskDetail(id: 101) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/task/info") XCTAssertEqual(taskQueryItems(from: request)["id"], "101") XCTAssertEqual(detail.urgentHour, 24) XCTAssertEqual(detail.taskResult.first?.isVideo, true) } /// 测试可关联订单接口使用正确景区参数。 func testAvailableOrderUsesScenicId() async throws { let session = TaskRecordingURLSession(data: try TestFixture.data(named: "available_order_success")) let api = TaskAPI(client: APIClient(session: session)) let orders = try await api.availableOrderList(scenicId: 88) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/task/available-order") XCTAssertEqual(taskQueryItems(from: request)["scenic_id"], "88") XCTAssertEqual(orders.first?.orderNumber, "ORD-TASK-001") } /// 测试发布任务接口提交正确 body。 func testAddTaskUsesExpectedBody() async throws { let session = TaskRecordingURLSession(data: try TestFixture.data(named: "empty_success")) let api = TaskAPI(client: APIClient(session: session)) let request = AddTaskRequest( scenicId: "88", name: "任务", orderNumber: "ORD001", remark: "备注", urgentHour: 12, cloudFile: [CloudFileItem(fileId: 1, remark: "")], uploadFile: [UploadFileItem(fileUrl: "https://cdn.example.com/a.jpg", fileName: "a.jpg", remark: "")] ) try await api.addTask(request) let urlRequest = try XCTUnwrap(session.requests.first) XCTAssertEqual(urlRequest.url?.path, "/api/yf-handset-app/photog/task/create") let body = try XCTUnwrap(urlRequest.httpBody) let object = try XCTUnwrap(JSONSerialization.jsonObject(with: body) as? [String: Any]) XCTAssertEqual(object["scenic_id"] as? String, "88") XCTAssertEqual(object["name"] as? String, "任务") XCTAssertEqual(object["order_number"] as? String, "ORD001") XCTAssertEqual(object["photog_remark"] as? String, "备注") XCTAssertEqual(object["urgent_hour"] as? Int, 12) XCTAssertEqual((object["cloud_file"] as? [[String: Any]])?.first?["file_id"] as? Int, 1) XCTAssertEqual((object["upload_file"] as? [[String: Any]])?.first?["file_url"] as? String, "https://cdn.example.com/a.jpg") } /// 测试云盘文件列表接口使用正确 path 和 query。 func testCloudFileListUsesExpectedQuery() async throws { let session = TaskRecordingURLSession(data: try TestFixture.data(named: "cloud_file_list_success")) let api = TaskAPI(client: APIClient(session: session)) let payload = try await api.cloudFileList(parentFolderId: 501, name: "photo", type: 2, orderBy: 2, page: 0, pageSize: 0) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/cloud-driver/list") let query = taskQueryItems(from: request) XCTAssertEqual(query["parent_folder_id"], "501") XCTAssertEqual(query["name"], "photo") XCTAssertEqual(query["type"], "2") XCTAssertEqual(query["page"], "1") XCTAssertEqual(query["page_size"], "1") XCTAssertEqual(payload.list.first?.isFolder, true) } } /// 任务 API 测试用 URLSession,记录请求并返回固定数据。 private final class TaskRecordingURLSession: URLSessionProtocol { let data: Data private(set) var requests: [URLRequest] = [] /// 初始化测试 Session。 init(data: Data) { self.data = data } /// 记录请求并返回成功响应。 func data(for request: URLRequest) async throws -> (Data, URLResponse) { requests.append(request) let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! return (data, response) } } /// 从请求中提取 query 字典。 private func taskQueryItems(from request: URLRequest) -> [String: String] { guard let url = request.url, let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return [:] } return Dictionary(uniqueKeysWithValues: (components.queryItems ?? []).compactMap { item in item.value.map { (item.name, $0) } }) }