feat: add AI retouch task center

This commit is contained in:
2026-08-14 15:06:57 +08:00
parent 0f3b26991e
commit 30bfe0313f
26 changed files with 3679 additions and 50 deletions
+52 -5
View File
@@ -183,7 +183,7 @@ final class TravelAlbumAPITests: XCTestCase {
}
func testSubmitAIRetouchEncodesRequiredAndSelectedOptionalTemplates() async throws {
let session = MockURLSession(responses: [envelopeJSON(#"{"task_id":9}"#)])
let session = MockURLSession(responses: [jobSubmissionJSON(batchId: 9, albumId: 6)])
let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session))
try await api.submitAIRetouch(
@@ -215,7 +215,7 @@ final class TravelAlbumAPITests: XCTestCase {
}
func testSubmitAIRetouchOmitsAllOptionalTemplatesWhenAbsent() async throws {
let session = MockURLSession(responses: [envelopeJSON(#"{"accepted":true}"#)])
let session = MockURLSession(responses: [jobSubmissionJSON(batchId: 10, albumId: 6)])
let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session))
try await api.submitAIRetouch(
@@ -240,9 +240,9 @@ final class TravelAlbumAPITests: XCTestCase {
func testSubmitAIReretouchEncodesOnlyFieldsRequiredByEachType() async throws {
let session = MockURLSession(responses: [
envelopeJSON(#"{"accepted":true}"#),
envelopeJSON(#"{"accepted":true}"#),
envelopeJSON(#"{"accepted":true}"#),
jobSubmissionJSON(batchId: 51, albumId: 6),
jobSubmissionJSON(batchId: 52, albumId: 6),
jobSubmissionJSON(batchId: 53, albumId: 6),
])
let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session))
@@ -295,9 +295,56 @@ final class TravelAlbumAPITests: XCTestCase {
XCTAssertEqual(bodies[2]?["type"] as? Int, 3)
}
func testAIJobListBuildsCursorQueryAndDecodesUnknownStatusSafely() async throws {
let data = envelopeJSON(
#"{"items":[{"ai_retouch_batch_id":91,"user_equity_travel_id":6,"scope":"album","status":"future_status","album":{"id":6,"name":"九寨沟旅拍","user_phone":"138****0000","cover_url":""},"source_count":1,"outputs":[{"type":"refined","count":1}],"preview_images":[],"progress":{"total":1,"queued":1,"processing":0,"succeeded":0,"failed":0,"canceled":0},"estimated_finish_at":null,"failure_summary":null,"created_at":"2026-08-14T06:00:00Z","started_at":null,"finished_at":null}],"next_cursor":"cursor-2","has_more":true}"#
)
let session = MockURLSession(responses: [data])
let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session))
let response = try await api.aiRetouchJobList(
statusGroup: .inProgress,
limit: 20,
cursor: "cursor-1"
)
XCTAssertEqual(response.items.first?.status, .unknown("future_status"))
XCTAssertEqual(response.nextCursor, "cursor-2")
let request = try XCTUnwrap(session.requests.first)
XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/travel-album/ai-retouch-job-list")
let query = URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)?.queryItems
XCTAssertEqual(query?.first { $0.name == "status_group" }?.value, "in_progress")
XCTAssertEqual(query?.first { $0.name == "limit" }?.value, "20")
XCTAssertEqual(query?.first { $0.name == "cursor" }?.value, "cursor-1")
}
func testAIJobInfoDecodesPerOutputFailureReason() async throws {
let data = envelopeJSON(
#"{"ai_retouch_batch_id":91,"user_equity_travel_id":6,"scope":"album","status":"partially_succeeded","album":{"id":6,"name":"九寨沟旅拍","user_phone":"138****0000","cover_url":""},"source_count":1,"outputs":[{"type":"refined","count":1}],"progress":{"total":1,"queued":0,"processing":0,"succeeded":0,"failed":1,"canceled":0},"quota_settlement":{"status":"settled","reserved_units":1,"consumed_units":0,"released_units":1,"cover_units":0},"targets":[{"target_id":1,"source_material":{"id":11,"file_name":"A.JPG","thumbnail_url":""},"input_material_ids":[11],"output_type":"refined","template":{"id":21,"name":"清透"},"status":"failed","result_asset":null,"error":{"code":"FACE_NOT_FOUND","message":"未识别到清晰人脸,请更换照片","retryable":true},"created_at":"2026-08-14T06:00:00Z","started_at":null,"finished_at":"2026-08-14T06:01:00Z"}],"estimated_finish_at":null,"created_at":"2026-08-14T06:00:00Z","started_at":null,"finished_at":"2026-08-14T06:01:00Z","duration_seconds":60}"#
)
let session = MockURLSession(responses: [data])
let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session))
let detail = try await api.aiRetouchJobInfo(batchId: 91)
XCTAssertEqual(detail.status, .partiallySucceeded)
XCTAssertEqual(detail.targets.first?.displayFailureMessage, "未识别到清晰人脸,请更换照片")
XCTAssertEqual(detail.targets.first?.error?.retryable, true)
let request = try XCTUnwrap(session.requests.first)
XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/travel-album/ai-retouch-job-info")
let query = URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)?.queryItems
XCTAssertEqual(query?.first { $0.name == "ai_retouch_batch_id" }?.value, "91")
}
private func envelopeJSON(_ dataJSON: String) -> Data {
"""
{"code":100000,"msg":"success","data":\(dataJSON)}
""".data(using: .utf8)!
}
private func jobSubmissionJSON(batchId: Int, albumId: Int) -> Data {
envelopeJSON(
#"{"ai_retouch_batch_id":\#(batchId),"user_equity_travel_id":\#(albumId),"status":"queued","progress":{"total":3,"queued":3,"processing":0,"succeeded":0,"failed":0,"canceled":0},"created_at":"2026-08-14T06:00:00Z"}"#
)
}
}