// // MessageCenterAPITests.swift // suixinkanTests // import XCTest @testable import suixinkan /// 消息中心 API 路径、参数与模型解码测试。 @MainActor final class MessageCenterAPITests: XCTestCase { func testListBuildsAndroidQueryAndDecodesMessagePayload() async throws { let payload = """ { "has_more": true, "last_id": 18, "is_refresh": false, "items": [{ "id": 21, "receiver_id": 9, "receiver_type": "staff", "type": 2, "type_name": "退款成功通知", "title": "unused", "content": "退款已完成", "push_at": "2026-07-09 10:00:00", "is_read": false, "read_at": "", "push_channel": 1, "push_channel_name": "站内信", "extra_data": {"order_no":"NO123","amount":12.5}, "created_at": "2026-07-09 10:00:00", "updated_at": "2026-07-09 10:01:00" }] } """ let session = MockURLSession(responses: [envelopeJSON(payload)]) let api = MessageCenterAPI(client: APIClient(environment: .testing, session: session)) let response = try await api.list(lastId: 7, limit: 20, unread: 0) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.httpMethod, "GET") XCTAssertEqual(request.url?.path, "/api/app/msg/list") let query = URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)?.queryItems XCTAssertEqual(query?.first { $0.name == "last_id" }?.value, "7") XCTAssertEqual(query?.first { $0.name == "limit" }?.value, "20") XCTAssertEqual(query?.first { $0.name == "unread" }?.value, "0") XCTAssertTrue(response.hasMore) XCTAssertEqual(response.lastId, 18) XCTAssertEqual(response.items.first?.id, 21) XCTAssertEqual(response.items.first?.isRead, false) XCTAssertEqual(response.items.first?.extraData?["order_no"], .string("NO123")) } func testUnreadCountReadAllReadAndDeleteUseExpectedContracts() async throws { let session = MockURLSession(responses: [ envelopeJSON(#"{"unread_count":"12"}"#), envelopeJSON(#"{"unread_count":11}"#), envelopeJSON(#"{"updated_count":11,"unread_count":0}"#), envelopeJSON("{}"), ]) let api = MessageCenterAPI(client: APIClient(environment: .testing, session: session)) let unread = try await api.unreadCount() let read = try await api.markAsRead(messageId: 21) let readAll = try await api.markAllAsRead() try await api.delete(messageId: 21) XCTAssertEqual(unread.unreadCount, 12) XCTAssertEqual(session.requests[0].httpMethod, "GET") XCTAssertEqual(session.requests[0].url?.path, "/api/app/msg/unread-count") XCTAssertEqual(read.unreadCount, 11) XCTAssertEqual(session.requests[1].httpMethod, "POST") XCTAssertEqual(session.requests[1].url?.path, "/api/app/msg/read") let readQuery = URLComponents(url: try XCTUnwrap(session.requests[1].url), resolvingAgainstBaseURL: false)?.queryItems XCTAssertEqual(readQuery?.first { $0.name == "id" }?.value, "21") XCTAssertEqual(readAll, MessageReadAllResponse(updatedCount: 11, unreadCount: 0)) XCTAssertEqual(session.requests[2].httpMethod, "POST") XCTAssertEqual(session.requests[2].url?.path, "/api/app/msg/read-all") XCTAssertEqual(session.requests[3].httpMethod, "POST") XCTAssertEqual(session.requests[3].url?.path, "/api/app/msg/delete") let deleteBody = try bodyJSON(session.requests[3]) XCTAssertEqual(deleteBody["id"] as? Int, 21) } private func envelopeJSON(_ dataJSON: String) -> Data { """ {"code":100000,"msg":"success","data":\(dataJSON)} """.data(using: .utf8)! } private func bodyJSON(_ request: URLRequest) throws -> [String: Any] { try XCTUnwrap(JSONSerialization.jsonObject(with: try XCTUnwrap(request.httpBody)) as? [String: Any]) } }