// // ProfileEditViewModelTests.swift // suixinkanTests // import XCTest @testable import suixinkan @MainActor /// 资料编辑页 ViewModel 测试。 final class ProfileEditViewModelTests: XCTestCase { func testInitPrefillsNicknameWhenSet() { let viewModel = ProfileEditViewModel(nickname: "测试昵称", avatarURL: "https://example.com/a.jpg") XCTAssertEqual(viewModel.editingNickname, "测试昵称") } func testInitClearsPlaceholderNickname() { let viewModel = ProfileEditViewModel(nickname: "未设置昵称", avatarURL: "") XCTAssertEqual(viewModel.editingNickname, "") } func testSaveProfileRejectsEmptyNickname() async { let viewModel = ProfileEditViewModel(nickname: "未设置昵称", avatarURL: "") let session = MockURLSession(responses: []) let api = ProfileAPI(client: APIClient(environment: .testing, session: session)) do { try await viewModel.saveProfile( api: api, uploader: MockOSSUploadService(), scenicId: 1 ) XCTFail("空昵称应抛出校验错误") } catch { guard let validationError = error as? ProfileValidationError else { return XCTFail("期望 ProfileValidationError,实际为 \(error)") } XCTAssertEqual(validationError, .emptyNickname) } } func testSaveProfilePostsNotificationAndUpdatesAppStore() async throws { let viewModel = ProfileEditViewModel(nickname: "旧昵称", avatarURL: "") viewModel.updateEditingNickname("新昵称") let expectation = expectation(forNotification: NotificationName.userProfileDidUpdate, object: nil) let responseData = try TestJSON.envelope(data: EmptyPayload()) let session = MockURLSession(responses: [responseData]) let api = ProfileAPI(client: APIClient(environment: .testing, session: session)) try await viewModel.saveProfile( api: api, uploader: MockOSSUploadService(), scenicId: 1 ) await fulfillment(of: [expectation], timeout: 1) XCTAssertEqual(AppStore.shared.session.userName, "新昵称") } } /// 测试用 OSS 上传替身,直接返回固定 URL。 @MainActor private struct MockOSSUploadService: OSSUploadServing { func uploadUserAvatar( data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void ) async throws -> String { onProgress(100) return "https://cdn.example.com/avatar.jpg" } func uploadRealNameImage( data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void ) async throws -> String { onProgress(100) return "https://cdn.example.com/realname.jpg" } func uploadBankCardImage( data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void ) async throws -> String { onProgress(100) return "https://cdn.example.com/bank.jpg" } }