// // LiveAPITests.swift // suixinkanTests // // Created by Codex on 2026/6/25. // import XCTest @testable import suixinkan @MainActor /// 直播 API 测试,覆盖直播管理和直播相册接口编码。 final class LiveAPITests: XCTestCase { func testLiveListAndDetailDecodeFixturesAndUseExpectedQueries() async throws { let listSession = LiveRecordingURLSession(data: try TestFixture.data(named: "alive_list_success")) let listAPI = LiveAPI(client: APIClient(session: listSession)) let list = try await listAPI.liveList(scenicId: 88, page: 0, pageSize: 0) XCTAssertEqual(list.total, 2) XCTAssertEqual(list.page, 1) XCTAssertEqual(list.pageSize, 1) XCTAssertEqual(list.items.first?.id, 8101) XCTAssertEqual(list.items.first?.manualPushMode, 2) let listRequest = try XCTUnwrap(listSession.requests.first) XCTAssertEqual(listRequest.httpMethod, "GET") XCTAssertEqual(listRequest.url?.path, "/api/app/manual-live/list") XCTAssertEqual(liveQueryItems(from: listRequest)["scenic_id"], "88") XCTAssertEqual(liveQueryItems(from: listRequest)["page"], "1") XCTAssertEqual(liveQueryItems(from: listRequest)["page_size"], "1") let detailSession = LiveRecordingURLSession(data: try TestFixture.data(named: "alive_detail_success")) let detailAPI = LiveAPI(client: APIClient(session: detailSession)) let detail = try await detailAPI.liveDetail(liveId: 8101) XCTAssertEqual(detail.id, 8101) XCTAssertEqual(detail.pushUrl, "rtmp://push.example.com/live/8101") XCTAssertEqual(detail.status, 2) XCTAssertEqual(detail.duration, 3600) let detailRequest = try XCTUnwrap(detailSession.requests.first) XCTAssertEqual(detailRequest.url?.path, "/api/app/manual-live/detail") XCTAssertEqual(liveQueryItems(from: detailRequest)["live_id"], "8101") } func testLiveCreateAndControlsUseExpectedBodies() async throws { let createSession = LiveRecordingURLSession(responses: Array(repeating: try TestFixture.data(named: "empty_success"), count: 5)) let api = LiveAPI(client: APIClient(session: createSession)) try await api.liveCreate(LiveCreateRequest(scenicId: "88", title: "景区晨间直播", coverImg: "https://cdn.example.com/cover.jpg")) var request = try XCTUnwrap(createSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/manual-live/create") var json = try liveJSONBody(from: request) XCTAssertEqual(json["scenic_id"] as? String, "88") XCTAssertEqual(json["title"] as? String, "景区晨间直播") XCTAssertEqual(json["cover_img"] as? String, "https://cdn.example.com/cover.jpg") try await api.liveStart(liveId: 8101) request = try XCTUnwrap(createSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/manual-live/start") XCTAssertEqual(try liveJSONBody(from: request)["live_id"] as? Int, 8101) try await api.liveStop(liveId: 8101) request = try XCTUnwrap(createSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/manual-live/stop") XCTAssertEqual(try liveJSONBody(from: request)["live_id"] as? Int, 8101) try await api.liveFinish(liveId: 8101) request = try XCTUnwrap(createSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/manual-live/finish") XCTAssertEqual(try liveJSONBody(from: request)["live_id"] as? Int, 8101) try await api.liveSetPushMode(liveId: 8101, mode: 2) request = try XCTUnwrap(createSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/manual-live/set-push-mode") json = try liveJSONBody(from: request) XCTAssertEqual(json["live_id"] as? Int, 8101) XCTAssertEqual(json["manual_push_mode"] as? Int, 2) } func testLiveAlbumAPIsUseExpectedRequests() async throws { let listSession = LiveRecordingURLSession(data: try TestFixture.data(named: "alive_album_list_success")) let api = LiveAPI(client: APIClient(session: listSession)) let response = try await api.liveAlbumList(scenicId: 88, startTime: "2026-05-01", endTime: "2026-05-23", page: 0, pageSize: 0) XCTAssertEqual(response.total, 2) XCTAssertEqual(response.items.first?.id, 6101) XCTAssertEqual(response.items.first?.items.last?.previewURL, "https://cdn.example.com/alive/video-cover.jpg") var request = try XCTUnwrap(listSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/view-album/folders") var query = liveQueryItems(from: request) XCTAssertEqual(query["scenic_id"], "88") XCTAssertEqual(query["start_time"], "2026-05-01") XCTAssertEqual(query["end_time"], "2026-05-23") XCTAssertEqual(query["page"], "1") XCTAssertEqual(query["page_size"], "1") let mutationSession = LiveRecordingURLSession(data: try TestFixture.data(named: "empty_success")) let mutationAPI = LiveAPI(client: APIClient(session: mutationSession)) try await mutationAPI.liveAlbumCreateFolder( LiveAlbumCreateFolderRequest( scenicId: "88", name: "五一直播相册", items: [LiveAlbumCreateFileItem(url: "https://cdn.example.com/a.jpg", type: 1, size: 12, coverImg: nil)] ) ) request = try XCTUnwrap(mutationSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/view-album/create-folder") var json = try liveJSONBody(from: request) XCTAssertEqual(json["scenic_id"] as? String, "88") XCTAssertEqual((json["items"] as? [[String: Any]])?.first?["url"] as? String, "https://cdn.example.com/a.jpg") try await mutationAPI.liveAlbumDeleteFolder(folderId: 6101) request = try XCTUnwrap(mutationSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/view-album/delete-folder") XCTAssertEqual(try liveJSONBody(from: request)["folder_id"] as? Int, 6101) let detailSession = LiveRecordingURLSession(data: try TestFixture.data(named: "alive_album_folder_detail_success")) let detailAPI = LiveAPI(client: APIClient(session: detailSession)) let folder = try await detailAPI.liveAlbumFolderDetail(folderId: 6101) XCTAssertEqual(folder.items.count, 2) request = try XCTUnwrap(detailSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/view-album/folder-detail") XCTAssertEqual(liveQueryItems(from: request)["folder_id"], "6101") try await mutationAPI.liveAlbumDeleteFiles(folderId: 6101, fileIds: [7101, 7102]) request = try XCTUnwrap(mutationSession.requests.last) XCTAssertEqual(request.url?.path, "/api/app/view-album/delete-files") json = try liveJSONBody(from: request) XCTAssertEqual(json["folder_id"] as? Int, 6101) XCTAssertEqual(json["file_ids"] as? [Int], [7101, 7102]) } } private final class LiveRecordingURLSession: URLSessionProtocol { private var responses: [Data] private(set) var requests: [URLRequest] = [] init(data: Data) { responses = [data] } init(responses: [Data]) { self.responses = responses } func data(for request: URLRequest) async throws -> (Data, URLResponse) { requests.append(request) let data = responses.isEmpty ? try TestFixture.data(named: "empty_success") : responses.removeFirst() return (data, HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!) } } private func liveQueryItems(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) } }) } private func liveJSONBody(from request: URLRequest) throws -> [String: Any] { let body = try XCTUnwrap(request.httpBody) return try XCTUnwrap(JSONSerialization.jsonObject(with: body) as? [String: Any]) }