优化冷启动流程:Launch Screen 对齐 Splash,仅阻塞 rolePermissions。
移除 SplashCoordinator,bootstrap 期间用 SplashView 覆盖避免空白闪屏,其余账号数据改为后台补充刷新。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
144
suixinkanTests/SessionBootstrapperTests.swift
Normal file
144
suixinkanTests/SessionBootstrapperTests.swift
Normal file
@ -0,0 +1,144 @@
|
||||
//
|
||||
// SessionBootstrapperTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
// Created by Codex on 2026/6/30.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 冷启动恢复测试,覆盖阻塞权限恢复与后台补充刷新。
|
||||
final class SessionBootstrapperTests: XCTestCase {
|
||||
/// 测试无 token 时直接进入未登录状态。
|
||||
func testRestoreWithoutTokenLogsOut() async {
|
||||
let defaults = makeDefaults()
|
||||
let tokenStore = SessionTokenStore(defaults: defaults, key: "test.token")
|
||||
let snapshotStore = AccountSnapshotStore(defaults: defaults, key: "test.snapshot")
|
||||
let bootstrapper = SessionBootstrapper(tokenStore: tokenStore, snapshotStore: snapshotStore)
|
||||
let appSession = AppSession()
|
||||
let accountContext = AccountContext()
|
||||
let permissionContext = PermissionContext()
|
||||
let toastCenter = ToastCenter()
|
||||
|
||||
await bootstrapper.restore(
|
||||
appSession: appSession,
|
||||
accountContext: accountContext,
|
||||
permissionContext: permissionContext,
|
||||
profileAPI: MockUserProfileAPI(),
|
||||
accountContextAPI: MockAccountContextAPI(),
|
||||
toastCenter: toastCenter
|
||||
)
|
||||
|
||||
XCTAssertEqual(appSession.phase, .loggedOut)
|
||||
}
|
||||
|
||||
/// 测试有 token 时阻塞等待 rolePermissions 后立即进入 loggedIn。
|
||||
func testRestoreMarksLoggedInAfterRolePermissions() async {
|
||||
let defaults = makeDefaults()
|
||||
let tokenStore = SessionTokenStore(defaults: defaults, key: "test.token")
|
||||
try? tokenStore.save("token-1")
|
||||
let snapshotStore = AccountSnapshotStore(defaults: defaults, key: "test.snapshot")
|
||||
let bootstrapper = SessionBootstrapper(tokenStore: tokenStore, snapshotStore: snapshotStore)
|
||||
let appSession = AppSession()
|
||||
let accountContext = AccountContext()
|
||||
let permissionContext = PermissionContext()
|
||||
let toastCenter = ToastCenter()
|
||||
let api = MockAccountContextAPI()
|
||||
api.roles = [
|
||||
RolePermissionResponse(
|
||||
role: RoleInfo(id: 7, name: "运营", permission: [PermissionItem(id: 1, name: "首页", uri: "/home")]),
|
||||
scenic: [ScenicInfo(id: 88, name: "东湖景区")]
|
||||
)
|
||||
]
|
||||
|
||||
await bootstrapper.restore(
|
||||
appSession: appSession,
|
||||
accountContext: accountContext,
|
||||
permissionContext: permissionContext,
|
||||
profileAPI: MockUserProfileAPI(),
|
||||
accountContextAPI: api,
|
||||
toastCenter: toastCenter
|
||||
)
|
||||
|
||||
XCTAssertEqual(appSession.phase, .loggedIn)
|
||||
XCTAssertEqual(permissionContext.currentRole?.id, 7)
|
||||
XCTAssertTrue(permissionContext.canAccess("/home"))
|
||||
|
||||
let profileExpectation = expectation(description: "profile loaded in background")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
if accountContext.profile?.displayName == "测试用户" {
|
||||
profileExpectation.fulfill()
|
||||
}
|
||||
}
|
||||
await fulfillment(of: [profileExpectation], timeout: 2)
|
||||
}
|
||||
|
||||
/// 测试 rolePermissions 鉴权失败时会清空本地登录态。
|
||||
func testRestoreLogsOutWhenRolePermissionsExpired() async {
|
||||
let defaults = makeDefaults()
|
||||
let tokenStore = SessionTokenStore(defaults: defaults, key: "test.token")
|
||||
try? tokenStore.save("token-1")
|
||||
let snapshotStore = AccountSnapshotStore(defaults: defaults, key: "test.snapshot")
|
||||
snapshotStore.save(AccountSnapshot(profile: AccountProfile(userId: "1", displayName: "本地用户")))
|
||||
let bootstrapper = SessionBootstrapper(tokenStore: tokenStore, snapshotStore: snapshotStore)
|
||||
let appSession = AppSession()
|
||||
let accountContext = AccountContext()
|
||||
let permissionContext = PermissionContext()
|
||||
let toastCenter = ToastCenter()
|
||||
let api = MockAccountContextAPI()
|
||||
api.roleError = APIError.httpStatus(401, "登录状态已失效")
|
||||
|
||||
await bootstrapper.restore(
|
||||
appSession: appSession,
|
||||
accountContext: accountContext,
|
||||
permissionContext: permissionContext,
|
||||
profileAPI: MockUserProfileAPI(),
|
||||
accountContextAPI: api,
|
||||
toastCenter: toastCenter
|
||||
)
|
||||
|
||||
XCTAssertEqual(appSession.phase, .loggedOut)
|
||||
XCTAssertNil(tokenStore.load())
|
||||
XCTAssertNil(snapshotStore.load())
|
||||
}
|
||||
|
||||
private func makeDefaults() -> UserDefaults {
|
||||
let suiteName = "SessionBootstrapperTests.\(UUID().uuidString)"
|
||||
let defaults = UserDefaults(suiteName: suiteName)!
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
return defaults
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class MockUserProfileAPI: UserProfileServing {
|
||||
func userInfo() async throws -> UserInfoResponse {
|
||||
try await Task.sleep(nanoseconds: 100_000_000)
|
||||
return UserInfoResponse(nickname: "测试用户", roleName: "运营")
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class MockAccountContextAPI: AccountContextServing {
|
||||
var roles: [RolePermissionResponse] = []
|
||||
var roleError: Error?
|
||||
|
||||
func rolePermissions() async throws -> [RolePermissionResponse] {
|
||||
if let roleError { throw roleError }
|
||||
return roles
|
||||
}
|
||||
|
||||
func scenicListAll() async throws -> ScenicListAllResponse {
|
||||
ScenicListAllResponse(total: 0, list: [])
|
||||
}
|
||||
|
||||
func storeAll() async throws -> ListPayload<StoreItem> {
|
||||
ListPayload(total: 0, list: [])
|
||||
}
|
||||
|
||||
func scenicSpotListAll(scenicId: Int) async throws -> ListPayload<ScenicSpotItem> {
|
||||
ListPayload(total: 0, list: [])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user