// // AuthModelsTests.swift // suixinkanTests // // Created by Codex on 2026/6/26. // import XCTest @testable import suixinkan /// Auth 模块模型测试,覆盖 v9 登录响应解码和账号转换规则。 final class AuthModelsTests: XCTestCase { /// 测试多账号登录 fixture 能正确解码并合并景区、门店账号。 func testV9AuthResponseDecodesMultiAccountFixture() throws { let response = try TestFixture.payload(V9AuthResponse.self, named: "v9_login_multi_success") XCTAssertEqual(response.token, "person-temp-token") XCTAssertEqual(response.scenicUsers.count, 1) XCTAssertEqual(response.storeUsers.count, 1) XCTAssertEqual(response.accounts.map(\.businessUserId), [101, 201]) } /// 测试景区账号会优先使用 ss_user_id 作为业务账号 ID。 func testV9ScenicUserBusinessUserIdPrefersSsUserId() throws { let json = """ { "account_type": "scenic_user", "id": 1, "user_id": 2, "scenic_user_id": 3, "ss_user_id": 101, "username": "scenic_admin", "real_name": "张三", "nickname": "张三", "phone": "13800138000", "scenic_id": 10, "scenic_name": "示例景区", "is_current": false } """.data(using: .utf8)! let user = try JSONDecoder().decode(V9ScenicUser.self, from: json) XCTAssertEqual(user.businessUserId, 101) XCTAssertEqual(user.toAccountSwitchAccount().accountType, V9ScenicUser.accountTypeValue) XCTAssertEqual(user.toAccountSwitchAccount().toSetUserRequest(), SetUserRequest(ssUserId: 101)) } /// 测试门店账号会优先使用 store_user_id 作为业务账号 ID。 func testV9StoreUserBusinessUserIdPrefersStoreUserId() throws { let json = """ { "account_type": "store_user", "id": 1, "user_id": 2, "store_user_id": 201, "username": "store_admin", "user_name": "store_admin", "real_name": "张三", "phone": "13800138000", "avatar": "", "scenic_id": 10, "scenic_name": "示例景区", "store_id": 20, "store_name": "示例门店", "is_current": false } """.data(using: .utf8)! let user = try JSONDecoder().decode(V9StoreUser.self, from: json) XCTAssertEqual(user.businessUserId, 201) XCTAssertEqual(user.toAccountSwitchAccount().accountType, V9StoreUser.accountTypeValue) XCTAssertEqual(user.toAccountSwitchAccount().toSetUserRequest(), SetUserRequest(storeUserId: 201)) } /// 测试登录响应会派生景区和门店作用域,并去重。 func testV9AuthResponseDerivesScopesFromAccounts() throws { let response = try TestFixture.payload(V9AuthResponse.self, named: "v9_login_multi_success") XCTAssertEqual(response.scenicScopes.map(\.id), [10]) XCTAssertEqual(response.storeScopes.map(\.id), [20]) XCTAssertEqual(response.primaryProfile?.displayName, "示例门店") } /// 测试切换门店账号时会优先使用 isCurrent 标记校准景区和门店作用域。 func testV9AuthResponsePreferredScopeSelectionUsesCurrentStoreAccount() throws { let scenicJSON = """ { "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": 1, "scenic_name": "旧景区", "is_current": false } """ let storeJSON = """ { "account_type": "store_user", "id": 201, "user_id": 201, "store_user_id": 201, "username": "store_admin", "user_name": "store_admin", "real_name": "李四", "phone": "13800138000", "avatar": "", "scenic_id": 2, "scenic_name": "新景区", "store_id": 20, "store_name": "新门店", "is_current": true } """ let scenicUser = try JSONDecoder().decode(V9ScenicUser.self, from: Data(scenicJSON.utf8)) let storeUser = try JSONDecoder().decode(V9StoreUser.self, from: Data(storeJSON.utf8)) let response = V9AuthResponse(token: "token", scenicUsers: [scenicUser], storeUsers: [storeUser]) XCTAssertEqual(response.currentAccountIdentity?.accountType, V9StoreUser.accountTypeValue) XCTAssertEqual(response.currentAccountIdentity?.businessUserId, 201) XCTAssertEqual(response.preferredScopeSelection.scenicId, 2) XCTAssertEqual(response.preferredScopeSelection.storeId, 20) let context = AccountContext() context.replaceScopes( scenic: [BusinessScope(id: 1, name: "旧景区", kind: .scenic)], stores: [], currentScenicId: 1, currentStoreId: nil ) context.replaceScopes( scenic: response.scenicScopes, stores: response.storeScopes, currentScenicId: response.preferredScopeSelection.scenicId, currentStoreId: response.preferredScopeSelection.storeId ) XCTAssertEqual(context.currentScenic?.id, 2) XCTAssertEqual(context.currentStore?.id, 20) } /// 测试 LoginRequest 会填充后端要求的默认字段。 func testLoginRequestEncodesExpectedDefaults() throws { let request = LoginRequest(username: "18651857230", password: "secret123") let json = try JSONSerialization.jsonObject(with: JSONEncoder().encode(request)) as? [String: Any] XCTAssertEqual(json?["username"] as? String, "18651857230") XCTAssertEqual(json?["password"] as? String, "secret123") XCTAssertEqual(json?["type"] as? Int, 1) XCTAssertEqual(json?["mobile"] as? String, "") XCTAssertEqual(json?["code"] as? String, "") } }