Add Tasks module with home routing for task management flows.
Implement task list, detail, and create pages with API integration, cloud/local attachment support, and unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
145
suixinkanTests/TaskAPITests.swift
Normal file
145
suixinkanTests/TaskAPITests.swift
Normal file
@ -0,0 +1,145 @@
|
||||
//
|
||||
// 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) }
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user