Files
suixinkan_uikit/suixinkanTests/LoginViewModelTests.swift

203 lines
6.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// LoginViewModelTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
@MainActor
/// ViewModel
final class LoginViewModelTests: XCTestCase {
func testNormalizeUsernameCountryCodeRemovesChinaPrefix() {
let viewModel = LoginViewModel()
viewModel.updateAccount("+86 186 5185 7230")
viewModel.normalizeUsernameCountryCodeIfNeeded()
XCTAssertEqual(viewModel.normalizedUsername, "18651857230")
XCTAssertTrue(viewModel.isValidPhone)
}
func testIsLoginEnabledRequiresValidPhoneAndNonEmptyPassword() {
let viewModel = LoginViewModel()
viewModel.updateAccount("18651857230")
viewModel.updatePassword(" secret123 ")
XCTAssertTrue(viewModel.isLoginEnabled)
XCTAssertEqual(viewModel.trimmedPassword, "secret123")
}
func testValidateForLoginReturnsExpectedErrorsInOrder() {
let viewModel = LoginViewModel()
viewModel.updateAccount("12345")
viewModel.updatePassword("secret123")
viewModel.togglePrivacyChecked()
XCTAssertEqual(viewModel.validateForLogin(), .invalidPhone)
viewModel.updateAccount("18651857230")
viewModel.updatePassword(" ")
XCTAssertEqual(viewModel.validateForLogin(), .emptyPassword)
viewModel.updatePassword("secret123")
viewModel.togglePrivacyChecked()
XCTAssertEqual(viewModel.validateForLogin(), .privacyUnchecked)
}
func testLoginWithSingleAccountAutoCompletesSetUser() async throws {
let loginJSON = """
{
"code": 100000,
"msg": "success",
"data": {
"token": "temp-token",
"scenic_users": [
{
"account_type": "scenic_user",
"id": 101,
"user_id": 101,
"scenic_user_id": 101,
"ss_user_id": 101,
"username": "scenic_admin",
"real_name": "",
"nickname": "",
"phone": "13800138000",
"scenic_id": 10,
"scenic_name": "",
"is_current": false
}
],
"store_users": []
}
}
""".data(using: .utf8)!
let setUserJSON = """
{
"code": 100000,
"msg": "success",
"data": {
"token": "business-token-scenic",
"scenic_users": [],
"store_users": []
}
}
""".data(using: .utf8)!
let session = MockURLSession(responses: [loginJSON, setUserJSON])
let api = AuthAPI(client: APIClient(environment: .testing, session: session))
let viewModel = LoginViewModel()
viewModel.updateAccount("18651857230")
viewModel.updatePassword("secret123")
viewModel.togglePrivacyChecked()
let resolution = try await viewModel.login(authAPI: api)
guard case .completed(let response) = resolution else {
return XCTFail("单账号登录应直接完成")
}
XCTAssertEqual(response.token, "business-token-scenic")
XCTAssertFalse(viewModel.isLoading)
XCTAssertNil(viewModel.pendingAccountSelection)
XCTAssertEqual(session.requests.map { $0.url?.path }, [
"/api/app/v9/login",
"/api/app/v9/set-user",
])
}
func testLoginWithMultipleAccountsNeedsSelection() async throws {
let loginJSON = """
{
"code": 100000,
"msg": "success",
"data": {
"token": "person-temp-token",
"scenic_users": [
{
"account_type": "scenic_user",
"id": 101,
"user_id": 101,
"scenic_user_id": 101,
"ss_user_id": 101,
"username": "scenic_admin",
"real_name": "",
"nickname": "",
"phone": "13800138000",
"scenic_id": 10,
"scenic_name": "",
"is_current": false
}
],
"store_users": [
{
"account_type": "store_user",
"id": 201,
"user_id": 201,
"store_user_id": 201,
"username": "store_admin",
"user_name": "store_admin",
"real_name": "",
"phone": "13900139000",
"avatar": "",
"scenic_id": 10,
"scenic_name": "",
"store_id": 20,
"store_name": "",
"is_current": false
}
]
}
}
""".data(using: .utf8)!
let session = MockURLSession(responses: [loginJSON])
let api = AuthAPI(client: APIClient(environment: .testing, session: session))
let viewModel = LoginViewModel()
viewModel.updateAccount("18651857230")
viewModel.updatePassword("secret123")
viewModel.togglePrivacyChecked()
let resolution = try await viewModel.login(authAPI: api)
guard case .needsAccountSelection(let payload) = resolution else {
return XCTFail("多账号登录应进入账号选择")
}
XCTAssertEqual(payload.tempToken, "person-temp-token")
XCTAssertEqual(payload.accounts.map(\.businessUserId), [101, 201])
XCTAssertNotNil(viewModel.pendingAccountSelection)
XCTAssertEqual(session.requests.count, 1)
}
func testLoginThrowsWhenNoAvailableAccounts() async {
let loginJSON = """
{
"code": 100000,
"msg": "success",
"data": {
"token": "person-temp-token",
"scenic_users": [],
"store_users": []
}
}
""".data(using: .utf8)!
let session = MockURLSession(responses: [loginJSON])
let api = AuthAPI(client: APIClient(environment: .testing, session: session))
let viewModel = LoginViewModel()
viewModel.updateAccount("18651857230")
viewModel.updatePassword("secret123")
viewModel.togglePrivacyChecked()
do {
_ = try await viewModel.login(authAPI: api)
XCTFail("没有可用账号时应抛出错误")
} catch let error as LoginFlowError {
guard case .noAvailableAccount = error else {
return XCTFail("期望 noAvailableAccount实际为 \(error)")
}
} catch {
XCTFail("意外错误: \(error)")
}
}
}