// // HomeCommonMenuStoreTests.swift // suixinkanTests // // Created by Codex on 2026/6/22. // import XCTest @testable import suixinkan /// 首页常用应用存储测试,覆盖默认值、过滤和 Android 对齐行为。 final class HomeCommonMenuStoreTests: XCTestCase { /// 测试首次无配置时会按顶层权限生成默认常用应用。 func testLoadCreatesDefaultCommonMenusFromTopLevelPermissions() { let defaults = makeIsolatedDefaults() let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("space_settings"), menu("wallet"), menu("cloud_management"), menu("message_center") ] let uris = store.load( menuItems: menus, roleCode: "photographer", topLevelPermissionURIs: ["space_settings", "wallet", "cloud_management", "message_center"] ) XCTAssertEqual(uris, ["space_settings", "wallet", "cloud_management", "message_center"]) XCTAssertEqual( defaults.stringArray(forKey: HomeCommonMenuStore.storageKeyPrefix + "photographer"), ["space_settings", "wallet", "cloud_management", "message_center"] ) } /// 测试顶层权限超过 4 个时只取前 4 个默认常用应用。 func testDefaultCommonURIsTakeFirstFourWhenMoreThanFourPermissions() { let uris = HomeCommonMenuStore.defaultCommonURIs( orderedTopLevelURIs: [ "space_settings", "wallet", "cloud_management", "message_center", "task_management" ] ) XCTAssertEqual(uris, ["space_settings", "wallet", "cloud_management", "message_center"]) } /// 测试角色没有旧硬编码默认 URI 时仍能从权限列表生成常用应用。 func testLoadCreatesDefaultsWithoutLegacyPreferredURIs() { let defaults = makeIsolatedDefaults() let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("space_settings"), menu("wallet"), menu("cloud_management") ] let uris = store.load( menuItems: menus, roleCode: "editor", topLevelPermissionURIs: ["space_settings", "wallet", "cloud_management"] ) XCTAssertEqual(uris, ["space_settings", "wallet", "cloud_management"]) } /// 测试已保存 URI 会按当前顶层权限和白名单精确过滤。 func testLoadFiltersSavedUrisByCurrentPermissions() { let defaults = makeIsolatedDefaults() defaults.set(["task_management_editor", "unknown", "pm"], forKey: HomeCommonMenuStore.storageKeyPrefix + "photographer") let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("task_management_editor"), menu("project_edit") ] let uris = store.load( menuItems: menus, roleCode: "photographer", topLevelPermissionURIs: ["task_management_editor", "project_edit"] ) XCTAssertEqual(uris, ["task_management_editor"]) } /// 测试已保存 URI 与当前权限不匹配时不会回退默认。 func testSavedCommonDoesNotFallbackWhenAllInvalid() { let defaults = makeIsolatedDefaults() defaults.set(["task_management", "unknown"], forKey: HomeCommonMenuStore.storageKeyPrefix + "photographer") let store = HomeCommonMenuStore(defaults: defaults) let menus = [menu("task_management_editor")] let uris = store.load( menuItems: menus, roleCode: "photographer", topLevelPermissionURIs: ["task_management_editor"] ) XCTAssertTrue(uris.isEmpty) XCTAssertEqual( defaults.stringArray(forKey: HomeCommonMenuStore.storageKeyPrefix + "photographer"), ["task_management", "unknown"] ) } /// 测试同一角色不同账号使用独立的常用应用缓存。 func testLoadUsesAccountScopedStorageKeys() { let defaults = makeIsolatedDefaults() defaults.set( ["wallet"], forKey: HomeCommonMenuStore.storageKey(roleCode: "photographer", accountScope: "scenic_user_101") ) defaults.set( ["cloud_management"], forKey: HomeCommonMenuStore.storageKey(roleCode: "photographer", accountScope: "store_user_101") ) let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("wallet"), menu("cloud_management") ] let scenic = store.load( menuItems: menus, roleCode: "photographer", accountScope: "scenic_user_101", topLevelPermissionURIs: ["wallet", "cloud_management"] ) let storeAccount = store.load( menuItems: menus, roleCode: "photographer", accountScope: "store_user_101", topLevelPermissionURIs: ["wallet", "cloud_management"] ) XCTAssertEqual(scenic, ["wallet"]) XCTAssertEqual(storeAccount, ["cloud_management"]) } /// 测试不同角色使用独立存储 key。 func testLoadUsesRoleScopedStorageKeys() { let defaults = makeIsolatedDefaults() defaults.set(["wallet"], forKey: HomeCommonMenuStore.storageKeyPrefix + "photographer") defaults.set(["cloud_management"], forKey: HomeCommonMenuStore.storageKeyPrefix + "editor") let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("wallet"), menu("cloud_management") ] let roleOne = store.load( menuItems: menus, roleCode: "photographer", topLevelPermissionURIs: ["wallet", "cloud_management"] ) let roleTwo = store.load( menuItems: menus, roleCode: "editor", topLevelPermissionURIs: ["wallet", "cloud_management"] ) XCTAssertEqual(roleOne, ["wallet"]) XCTAssertEqual(roleTwo, ["cloud_management"]) } /// 测试添加常用应用时使用精确 URI 匹配。 func testAddUsesExactURIMatching() { let defaults = makeIsolatedDefaults() let store = HomeCommonMenuStore(defaults: defaults) let uris = store.add( "task_management", current: ["task_management_editor"], topLevelPermissionURIs: ["task_management_editor"], roleCode: "photographer" ) XCTAssertEqual(uris, ["task_management_editor"]) } /// 测试移除常用应用时使用精确 URI 匹配。 func testRemoveUsesExactURIMatching() { let defaults = makeIsolatedDefaults() let store = HomeCommonMenuStore(defaults: defaults) let uris = store.remove("pm", current: ["project_edit", "pm_manager", "location_report"], roleCode: "photographer") XCTAssertEqual(uris, ["project_edit", "pm_manager", "location_report"]) } /// 测试无 role_code 时不读写常用应用。 func testLoadReturnsEmptyWhenRoleCodeIsMissing() { let defaults = makeIsolatedDefaults() let store = HomeCommonMenuStore(defaults: defaults) let uris = store.load( menuItems: [menu("wallet")], roleCode: nil, topLevelPermissionURIs: ["wallet"] ) XCTAssertTrue(uris.isEmpty) XCTAssertNil(defaults.stringArray(forKey: HomeCommonMenuStore.storageKeyPrefix + "photographer")) } /// 测试从 legacy role id key 一次性迁移常用菜单。 func testLoadMigratesLegacyRoleIdStorageKey() { let defaults = makeIsolatedDefaults() defaults.set(["wallet", "cloud_management"], forKey: HomeCommonMenuStore.storageKeyPrefix + "46") let store = HomeCommonMenuStore(defaults: defaults) let menus = [menu("wallet"), menu("cloud_management")] let uris = store.load( menuItems: menus, roleCode: "store_admin", legacyRoleId: 46, topLevelPermissionURIs: ["wallet", "cloud_management"] ) XCTAssertEqual(uris, ["wallet", "cloud_management"]) XCTAssertEqual(defaults.stringArray(forKey: HomeCommonMenuStore.storageKeyPrefix + "store_admin"), ["wallet", "cloud_management"]) XCTAssertNil(defaults.stringArray(forKey: HomeCommonMenuStore.storageKeyPrefix + "46")) } /// 测试旧 roleCode key 会迁移到账号作用域 key。 func testLoadMigratesLegacyRoleCodeStorageKeyToAccountScopedKey() { let defaults = makeIsolatedDefaults() defaults.set(["wallet", "cloud_management"], forKey: HomeCommonMenuStore.storageKeyPrefix + "photographer") let store = HomeCommonMenuStore(defaults: defaults) let menus = [menu("wallet"), menu("cloud_management")] let uris = store.load( menuItems: menus, roleCode: "photographer", accountScope: "scenic_user_101", topLevelPermissionURIs: ["wallet", "cloud_management"] ) XCTAssertEqual(uris, ["wallet", "cloud_management"]) XCTAssertEqual( defaults.stringArray(forKey: HomeCommonMenuStore.storageKey(roleCode: "photographer", accountScope: "scenic_user_101")), ["wallet", "cloud_management"] ) XCTAssertNil(defaults.stringArray(forKey: HomeCommonMenuStore.storageKeyPrefix + "photographer")) } /// 测试默认常用先取顶层前 4 个,再与 menuList 白名单求交。 func testDefaultCommonURIsExcludeMenusMissingFromAndroidCatalog() { let defaults = makeIsolatedDefaults() let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("wallet"), menu("album_list"), menu("cloud_storage_transit"), menu("message_center") ] let uris = store.load( menuItems: menus, roleCode: "photographer", accountScope: "scenic_user_101", topLevelPermissionURIs: ["wallet", "album_list", "cloud_storage_transit", "message_center"] ) XCTAssertEqual(uris, ["wallet", "message_center"]) } /// 测试非白名单顶层项占用前 4 名额时,默认常用不会用后续白名单项补位。 func testDefaultCommonDoesNotBackfillAfterNonCatalogSlots() { let uris = HomeCommonMenuStore.defaultCommonURIs( orderedTopLevelURIs: [ "album_list", "wallet", "cloud_management", "asset_management", "task_management" ] ) XCTAssertEqual(uris, ["wallet", "cloud_management", "asset_management"]) } /// 测试已保存常用应用超过 4 个时只过滤权限,不会被强制截断。 func testSavedCommonURIsAreNotTruncatedToFour() { let defaults = makeIsolatedDefaults() defaults.set( ["space_settings", "wallet", "cloud_management", "asset_management", "message_center"], forKey: HomeCommonMenuStore.storageKey(roleCode: "photographer", accountScope: "scenic_user_101") ) let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("space_settings"), menu("wallet"), menu("cloud_management"), menu("asset_management"), menu("message_center") ] let uris = store.load( menuItems: menus, roleCode: "photographer", accountScope: "scenic_user_101", topLevelPermissionURIs: menus.map(\.uri) ) XCTAssertEqual(uris, ["space_settings", "wallet", "cloud_management", "asset_management", "message_center"]) } /// 测试默认常用只取顶层权限,不会用扁平化子权限补位。 func testDefaultCommonUsesTopLevelOnlyNotFlattenedChildren() { let defaults = makeIsolatedDefaults() let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("task_management"), menu("message_center") ] let uris = store.load( menuItems: menus, roleCode: "photographer", accountScope: "scenic_user_101", topLevelPermissionURIs: ["task_management"] ) XCTAssertEqual(uris, ["task_management"]) } /// 测试扁平化 menuItems 中的子权限不会扩大可用常用范围。 func testAvailableFunctionsIgnoreFlattenedChildPermissions() { let defaults = makeIsolatedDefaults() let store = HomeCommonMenuStore(defaults: defaults) let menus = [ menu("wallet"), menu("material_upload") ] let uris = store.load( menuItems: menus, roleCode: "photographer", accountScope: "scenic_user_101", topLevelPermissionURIs: ["wallet"] ) XCTAssertEqual(uris, ["wallet"]) } /// 创建测试菜单实体。 private func menu(_ uri: String) -> HomeMenuItem { HomeMenuItem(title: HomeMenuRouter.title(for: uri), uri: uri, iconSrc: nil) } /// 创建独立 UserDefaults,避免测试污染真实 App 偏好。 private func makeIsolatedDefaults() -> UserDefaults { let suiteName = "suixinkan.home.tests.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suiteName)! defaults.removePersistentDomain(forName: suiteName) return defaults } }