// // TravelAlbumAPITests.swift // suixinkanTests // import XCTest @testable import suixinkan @MainActor /// 旅拍相册 API 测试。 final class TravelAlbumAPITests: XCTestCase { func testListBuildsPathAndQuery() async throws { let data = envelopeJSON(#"{"total":1,"list":[]}"#) let session = MockURLSession(responses: [data]) let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session)) _ = try await api.list(page: 2, pageSize: 30) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/travel-album/list") let query = try XCTUnwrap(URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)?.queryItems) XCTAssertEqual(query.first { $0.name == "page" }?.value, "2") XCTAssertEqual(query.first { $0.name == "page_size" }?.value, "30") } func testCreateEncodesBody() async throws { let data = envelopeJSON(#"{"id":8}"#) let session = MockURLSession(responses: [data]) let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session)) _ = try await api.create( TravelAlbumCreateRequest( name: "2026-07-07-001", type: 1, orderNumber: nil, materialNum: 2, materialPrice: 10.5, materialPackagePrice: 88, photoPrice: 0 ) ) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.httpMethod, "POST") XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/travel-album/create") let body = try JSONSerialization.jsonObject(with: try XCTUnwrap(request.httpBody)) as? [String: Any] XCTAssertEqual(body?["name"] as? String, "2026-07-07-001") XCTAssertEqual(body?["type"] as? Int, 1) XCTAssertEqual(body?["material_num"] as? Int, 2) XCTAssertEqual(body?["material_price"] as? Double, 10.5) } func testMaterialListAndDeleteAndMpCode() async throws { let materialList = envelopeJSON( #"{"total":1,"list":[{"id":6,"user_equity_travel_id":3,"status":1,"order_number":"","user_id":9,"file_name":"A.JPG","file_type":2,"file_url":"https://cdn/a.jpg","file_size":1024,"cover_url":"","is_purchased":false,"ai_retouch_status":3,"ai_retouch_status_name":"AI已修","ai_refined_url":"https://cdn/refined.jpg","ai_atmosphere_url":"https://cdn/atmosphere.jpg","created_at":"","updated_at":""}]}"# ) let empty = envelopeJSON("{}") let code = envelopeJSON(#"{"mp_code_oss_url":"https://cdn/qr.png"}"#) let session = MockURLSession(responses: [materialList, empty, code]) let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session)) let materials = try await api.materialList( userEquityTravelId: 3, page: 1, pageSize: 30, orderBy: 4, isPurchased: 1 ) try await api.deleteAlbum(id: 3) let response = try await api.mpCode(id: 3) XCTAssertEqual(materials.list.first?.aiRetouchStatus, 3) XCTAssertEqual(materials.list.first?.aiRefinedURL, "https://cdn/refined.jpg") XCTAssertEqual(materials.list.first?.aiAtmosphereURL, "https://cdn/atmosphere.jpg") XCTAssertEqual(response.mpCodeOssUrl, "https://cdn/qr.png") XCTAssertEqual(session.requests[0].url?.path, "/api/yf-handset-app/photog/travel-album/material-list") let query = URLComponents(url: session.requests[0].url!, resolvingAgainstBaseURL: false)?.queryItems XCTAssertEqual(query?.first { $0.name == "user_equity_travel_id" }?.value, "3") XCTAssertEqual(query?.first { $0.name == "order_by" }?.value, "4") XCTAssertEqual(query?.first { $0.name == "is_purchased" }?.value, "1") XCTAssertEqual(session.requests[1].url?.path, "/api/yf-handset-app/photog/travel-album/delete") let body = try JSONSerialization.jsonObject(with: session.requests[1].httpBody!) as? [String: Any] XCTAssertEqual(body?["id"] as? Int, 3) XCTAssertEqual(session.requests[2].url?.path, "/api/yf-handset-app/photog/travel-album/mp-code") } func testUploadMaterialBuildsPathAndBody() async throws { let material = envelopeJSON(#"{"id":6,"user_equity_travel_id":3,"status":1,"order_number":"","user_id":9,"file_name":"A.JPG","file_type":1,"file_url":"https://cdn/a.jpg","file_size":1024,"cover_url":"","is_purchased":false,"created_at":"2026-07-08 10:00:00","updated_at":"2026-07-08 10:00:00"}"#) let session = MockURLSession(responses: [material]) let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session)) let response = try await api.uploadMaterial( TravelAlbumUploadMaterialRequest( userEquityTravelId: 3, fileName: "A.JPG", fileUrl: "https://cdn/a.jpg", clientPhotoId: "2a1d96b1-c0cc-489f-9f42-419ff1439a62" ) ) XCTAssertEqual(response.id, 6) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.httpMethod, "POST") XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/travel-album/upload-material") let body = try JSONSerialization.jsonObject(with: try XCTUnwrap(request.httpBody)) as? [String: Any] XCTAssertEqual(body?["user_equity_travel_id"] as? Int, 3) XCTAssertEqual(body?["file_name"] as? String, "A.JPG") XCTAssertEqual(body?["file_url"] as? String, "https://cdn/a.jpg") XCTAssertEqual(body?["client_photo_id"] as? String, "2a1d96b1-c0cc-489f-9f42-419ff1439a62") } func testMaterialClientPhotoIdsBuildsPathQueryAndDecodesResponse() async throws { let data = envelopeJSON(#"{"client_photo_ids":["one","two"]}"#) let session = MockURLSession(responses: [data]) let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session)) let response = try await api.materialClientPhotoIds(userEquityTravelId: 3) XCTAssertEqual(response.clientPhotoIds, ["one", "two"]) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.httpMethod, "GET") XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/travel-album/material-client-photo-ids") let query = URLComponents(url: request.url!, resolvingAgainstBaseURL: false)?.queryItems XCTAssertEqual(query?.first { $0.name == "user_equity_travel_id" }?.value, "3") } func testAIRetouchTemplatesBuildsQueryAndDecodesGroups() async throws { let data = envelopeJSON( #"{"refined_templates":[{"id":1,"name":"清透","preview_url":"https://cdn/refined.jpg"}],"atmosphere_templates":[{"id":2,"name":"暖阳","preview_url":"https://cdn/atmosphere.jpg"}],"cover_templates":[{"id":3,"name":"杂志","preview_url":"https://cdn/cover.jpg"}]}"# ) let session = MockURLSession(responses: [data]) let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session)) let response = try await api.aiRetouchTemplates(scenicId: 18) XCTAssertEqual(response.refinedTemplates.first?.name, "清透") XCTAssertEqual(response.atmosphereTemplates.first?.id, 2) XCTAssertEqual(response.coverTemplates.first?.previewURL, "https://cdn/cover.jpg") let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.httpMethod, "GET") XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/travel-album/ai-retouch-templates") let query = URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)?.queryItems XCTAssertEqual(query?.first { $0.name == "scenic_id" }?.value, "18") } func testSubmitAIRetouchEncodesRequiredAndSelectedOptionalTemplates() async throws { let session = MockURLSession(responses: [envelopeJSON(#"{"task_id":9}"#)]) let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session)) try await api.submitAIRetouch( TravelAlbumAIRetouchRequest( userEquityTravelId: 6, materialIds: [11, 12, 13, 14], refinedTemplateId: 21, atmosphereTemplateId: 22, aiPreviewTabs: 31 ) ) let request = try XCTUnwrap(session.requests.first) XCTAssertEqual(request.httpMethod, "POST") XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/travel-album/ai-retouch") let body = try JSONSerialization.jsonObject(with: try XCTUnwrap(request.httpBody)) as? [String: Any] XCTAssertEqual(body?["user_equity_travel_id"] as? Int, 6) XCTAssertEqual(body?["material_ids"] as? [Int], [11, 12, 13, 14]) XCTAssertEqual(body?["refined_template_id"] as? Int, 21) XCTAssertEqual(body?["atmosphere_template_id"] as? Int, 22) XCTAssertEqual(body?["ai_preview_tabs"] as? Int, 31) } func testSubmitAIRetouchOmitsAllOptionalTemplatesWhenAbsent() async throws { let session = MockURLSession(responses: [envelopeJSON(#"{"accepted":true}"#)]) let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session)) try await api.submitAIRetouch( TravelAlbumAIRetouchRequest( userEquityTravelId: 6, materialIds: [11], refinedTemplateId: 21, atmosphereTemplateId: nil, aiPreviewTabs: nil ) ) let body = try JSONSerialization.jsonObject(with: try XCTUnwrap(session.requests.first?.httpBody)) as? [String: Any] XCTAssertNil(body?["atmosphere_template_id"]) XCTAssertNil(body?["ai_preview_tabs"]) } private func envelopeJSON(_ dataJSON: String) -> Data { """ {"code":100000,"msg":"success","data":\(dataJSON)} """.data(using: .utf8)! } }