模块化 AppStore 并完善素材管理与个人空间设置。

将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 11:01:08 +08:00
parent ceca780ab3
commit 005349f8e6
128 changed files with 2953 additions and 1123 deletions

View File

@ -96,4 +96,59 @@ final class APIClientTests: XCTestCase {
await fulfillment(of: [expectation], timeout: 1)
}
func testMultipartUploadBuildsAndroidCompatibleRequest() async throws {
let session = MockURLSession(responses: [try TestJSON.envelope(data: EmptyPayload())])
let client = APIClient(
environment: .testing,
session: session,
appVersion: "9.0.0",
osType: "iOS"
)
client.bindAuthTokenProvider { "avatar-token" }
let bytes = Data([0x01, 0x02, 0xFE, 0xFF])
let _: EmptyPayload = try await client.sendMultipart(
path: "/api/yf-handset-app/photog/userinfo-update-avatar",
fileName: "avatar.jpg",
mimeType: "image/jpeg",
data: bytes
)
let request = try XCTUnwrap(session.requests.first)
XCTAssertEqual(request.httpMethod, "POST")
XCTAssertEqual(request.timeoutInterval, 60)
XCTAssertEqual(request.value(forHTTPHeaderField: "token"), "avatar-token")
XCTAssertEqual(request.value(forHTTPHeaderField: "X-APP-VERSION"), "9.0.0")
XCTAssertEqual(request.value(forHTTPHeaderField: "X-OS-TYPE"), "iOS")
XCTAssertTrue(request.value(forHTTPHeaderField: "Content-Type")?.hasPrefix("multipart/form-data; boundary=") == true)
let body = try XCTUnwrap(request.httpBody)
let bodyText = String(decoding: body, as: UTF8.self)
XCTAssertTrue(bodyText.contains("name=\"file\"; filename=\"avatar.jpg\""))
XCTAssertTrue(bodyText.contains("Content-Type: image/jpeg"))
XCTAssertNotNil(body.range(of: bytes))
}
func testMultipartUploadMapsBusinessFailure() async {
let session = MockURLSession(responses: [try! TestJSON.errorEnvelope(code: 100001, msg: "头像上传失败")])
let client = APIClient(environment: .testing, session: session)
do {
let _: EmptyPayload = try await client.sendMultipart(
path: "/api/yf-handset-app/photog/userinfo-update-avatar",
fileName: "avatar.jpg",
mimeType: "image/jpeg",
data: Data([1])
)
XCTFail("业务失败时应抛出 serverCode")
} catch let error as APIError {
guard case let .serverCode(code, message) = error else {
return XCTFail("期望 serverCode实际为 \(error)")
}
XCTAssertEqual(code, 100001)
XCTAssertEqual(message, "头像上传失败")
} catch {
XCTFail("期望 APIError实际为 \(error)")
}
}
}