// // AccountContextLoaderTests.swift // suixinkanTests // // Created by Codex on 2026/6/22. // import XCTest @testable import suixinkan @MainActor /// 账号上下文加载器测试,覆盖权限、景区兜底和门店容错。 final class AccountContextLoaderTests: XCTestCase { /// 测试权限、用户资料、景区和门店成功时会写入上下文。 func testRefreshLoadsRolePermissionScenicAndStoreContext() async throws { let accountContext = AccountContext() let permissionContext = PermissionContext() let api = MockAccountContextAPI() api.roles = [ RolePermissionResponse( role: RoleInfo(id: 7, name: "运营", permission: [PermissionItem(id: 1, name: "首页", uri: "/home")]), scenic: [ScenicInfo(id: 88, name: "东湖景区")] ) ] api.scenicResponse = ScenicListAllResponse(total: 1, list: [ScenicListItem(id: 88, name: "东湖景区")]) api.storeResponse = ListPayload(total: 1, list: [StoreItem(id: 66, scenicId: 88, name: "东湖门店")]) try await AccountContextLoader().refresh( accountContext: accountContext, permissionContext: permissionContext, profileAPI: MockUserProfileAPI(), accountContextAPI: api ) XCTAssertEqual(accountContext.profile?.displayName, "测试用户") XCTAssertEqual(permissionContext.currentRole?.id, 7) XCTAssertTrue(permissionContext.canAccess("/home")) XCTAssertEqual(accountContext.currentScenic?.id, 88) XCTAssertEqual(accountContext.currentStore?.id, 66) } /// 测试景区接口失败时会从角色权限景区兜底。 func testRefreshFallsBackToRoleScenicsWhenScenicAPIThrows() async throws { let accountContext = AccountContext() let permissionContext = PermissionContext() let api = MockAccountContextAPI() api.roles = [ RolePermissionResponse( role: RoleInfo(id: 7, name: "运营"), scenic: [ ScenicInfo(id: 88, name: "东湖景区"), ScenicInfo(id: 88, name: "东湖景区") ] ) ] api.scenicError = APIError.networkFailed("景区接口失败") try await AccountContextLoader().refresh( accountContext: accountContext, permissionContext: permissionContext, profileAPI: MockUserProfileAPI(), accountContextAPI: api ) XCTAssertEqual(accountContext.scenicScopes.map(\.id), [88]) } /// 测试仅恢复角色权限时会写入 PermissionContext,并在无快照景区时补齐作用域。 func testRestorePermissionsLoadsRolePermissionsOnly() async throws { let accountContext = AccountContext() let permissionContext = PermissionContext() let api = MockAccountContextAPI() api.roles = [ RolePermissionResponse( role: RoleInfo(id: 7, name: "运营", permission: [PermissionItem(id: 1, name: "首页", uri: "/home")]), scenic: [ScenicInfo(id: 88, name: "东湖景区")] ) ] try await AccountContextLoader().restorePermissions( permissionContext: permissionContext, accountContext: accountContext, accountContextAPI: api ) XCTAssertEqual(permissionContext.currentRole?.id, 7) XCTAssertTrue(permissionContext.canAccess("/home")) XCTAssertEqual(accountContext.scenicScopes.map(\.id), [88]) XCTAssertNil(accountContext.profile) } /// 测试补充刷新会更新用户资料、景区和门店。 func testRefreshSupplementalContextUpdatesProfileAndScopes() async throws { let accountContext = AccountContext() let permissionContext = PermissionContext() let api = MockAccountContextAPI() api.roles = [ RolePermissionResponse( role: RoleInfo(id: 7, name: "运营"), scenic: [ScenicInfo(id: 88, name: "东湖景区")] ) ] api.scenicResponse = ScenicListAllResponse(total: 1, list: [ScenicListItem(id: 88, name: "东湖景区")]) api.storeResponse = ListPayload(total: 1, list: [StoreItem(id: 66, scenicId: 88, name: "东湖门店")]) try await AccountContextLoader().restorePermissions( permissionContext: permissionContext, accountContext: accountContext, accountContextAPI: api ) try await AccountContextLoader().refreshSupplementalContext( accountContext: accountContext, permissionContext: permissionContext, profileAPI: MockUserProfileAPI(), accountContextAPI: api, cachedCurrentScenicId: 88, cachedCurrentStoreId: 66 ) XCTAssertEqual(accountContext.profile?.displayName, "测试用户") XCTAssertEqual(accountContext.currentScenic?.id, 88) XCTAssertEqual(accountContext.currentStore?.id, 66) } /// 测试门店接口失败时不阻断上下文加载。 func testRefreshAllowsStoreFailure() async throws { let accountContext = AccountContext() let permissionContext = PermissionContext() let api = MockAccountContextAPI() api.roles = [ RolePermissionResponse( role: RoleInfo(id: 7, name: "运营"), scenic: [ScenicInfo(id: 88, name: "东湖景区")] ) ] api.storeError = APIError.networkFailed("门店接口失败") try await AccountContextLoader().refresh( accountContext: accountContext, permissionContext: permissionContext, profileAPI: MockUserProfileAPI(), accountContextAPI: api ) XCTAssertEqual(accountContext.currentScenic?.id, 88) XCTAssertTrue(accountContext.storeScopes.isEmpty) XCTAssertNil(accountContext.currentStore) } /// 测试门店接口失败时会保留登录/切换时已写入的门店作用域。 func testRefreshSupplementalContextPreservesExistingStoreScopesWhenStoreAPIFails() async throws { let accountContext = AccountContext() accountContext.applyLogin(accountType: V9StoreUser.accountTypeValue) accountContext.replaceScopes( scenic: [BusinessScope(id: 88, name: "东湖景区", kind: .scenic)], stores: [BusinessScope(id: 66, name: "东湖门店", kind: .store, parentScenicId: 88)], currentScenicId: 88, currentStoreId: 66 ) let permissionContext = PermissionContext() let api = MockAccountContextAPI() api.roles = [ RolePermissionResponse( role: RoleInfo(id: 7, name: "运营"), scenic: [ScenicInfo(id: 88, name: "东湖景区")] ) ] api.scenicResponse = ScenicListAllResponse(total: 1, list: [ScenicListItem(id: 88, name: "东湖景区")]) api.storeError = APIError.networkFailed("门店接口失败") try await AccountContextLoader().refreshSupplementalContext( accountContext: accountContext, permissionContext: permissionContext, profileAPI: MockUserProfileAPI(), accountContextAPI: api, cachedCurrentScenicId: 88, cachedCurrentStoreId: 66 ) XCTAssertEqual(accountContext.storeScopes.map(\.name), ["东湖门店"]) XCTAssertEqual(accountContext.currentStore?.name, "东湖门店") } } @MainActor /// 用户资料服务测试替身,返回固定用户资料。 private final class MockUserProfileAPI: UserProfileServing { /// 获取固定用户资料。 func userInfo() async throws -> UserInfoResponse { UserInfoResponse(nickname: "测试用户", roleName: "运营") } } @MainActor /// 账号上下文服务测试替身,可分别控制各接口成功或失败。 private final class MockAccountContextAPI: AccountContextServing { var roles: [RolePermissionResponse] = [] var scenicResponse = ScenicListAllResponse(total: 0, list: []) var storeResponse = ListPayload(total: 0, list: [StoreItem]()) var scenicError: Error? var storeError: Error? /// 获取测试角色权限。 func rolePermissions() async throws -> [RolePermissionResponse] { roles } /// 获取测试景区列表,或按配置抛出错误。 func scenicListAll() async throws -> ScenicListAllResponse { if let scenicError { throw scenicError } return scenicResponse } /// 获取测试门店列表,或按配置抛出错误。 func storeAll() async throws -> ListPayload { if let storeError { throw storeError } return storeResponse } /// 获取测试景点列表。 func scenicSpotListAll(scenicId: Int) async throws -> ListPayload { ListPayload(total: 0, list: []) } }