// // HomeMenuRouterTests.swift // suixinkanTests // // Created by Codex on 2026/6/22. // import XCTest @testable import suixinkan /// 首页菜单路由测试,覆盖 URI 映射、别名和未知路由诊断。 final class HomeMenuRouterTests: XCTestCase { override func setUp() { super.setUp() HomeRouteDiagnostics.resetUnknownRoutes() } override func tearDown() { HomeRouteDiagnostics.resetUnknownRoutes() super.tearDown() } /// 测试已迁移的首页路由会解析到真实目标。 func testMigratedRoutesResolveToDestinations() { XCTAssertEqual(HomeMenuRouter.resolve(uri: "space_settings", title: ""), .destination(.profileSpace)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "scenicselection", title: ""), .destination(.scenicSelection)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "store", title: ""), .destination(.moreFunctions)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "wallet", title: ""), .destination(.wallet)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "payment_collection", title: ""), .destination(.paymentCollection)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "payment_qr", title: ""), .destination(.paymentCollection)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "payment_code", title: ""), .destination(.paymentCollection)) } /// 测试已知但未迁移的首页路由会进入安全占位。 func testKnownUnmigratedRoutesResolveToPlaceholders() { XCTAssertEqual( HomeMenuRouter.resolve(uri: "task_management", title: ""), .destination(.modulePlaceholder(uri: "task_management", title: "任务管理")) ) } /// 测试 Tab 路由仍集中在 HomeMenuRouter。 func testTabRoutesStayCentralized() { XCTAssertEqual(HomeMenuRouter.resolve(uri: "photographer_orders", title: ""), .orders(.storeOrders)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "/scenic-order-manage", title: ""), .orders(.storeOrders)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "verification_order", title: ""), .orders(.verificationOrders)) XCTAssertEqual(HomeMenuRouter.resolve(uri: "photographer_stats", title: ""), .tab(.statistics)) } /// 测试飞控相关入口属于已知不支持范围。 func testFlightControlRoutesAreKnownUnsupportedScope() { XCTAssertEqual( HomeMenuRouter.resolve(uri: "fly", title: ""), .unsupported(uri: "fly", title: "飞行管理", reason: "DJI/飞控模块已明确不纳入 iOS 迁移和后续开发范围。") ) } /// 测试未知 URI 会显式进入 placeholder。 func testUnknownRouteIsExplicitPlaceholder() { XCTAssertEqual( HomeMenuRouter.resolve(uri: "android_only_feature", title: "Android 专项"), .placeholder(uri: "android_only_feature", title: "Android 专项") ) } /// 测试 canonicalURI 会使用当前权限可用的同义 URI。 func testCanonicalURIUsesAvailableAlias() { let available: Set = ["task_management_editor", "photographer_invite", "pm_manager", "payment_qr"] XCTAssertEqual(HomeMenuRouter.canonicalURI(for: "task_management", availableURIs: available), "task_management_editor") XCTAssertEqual(HomeMenuRouter.canonicalURI(for: "registration_invitation", availableURIs: available), "photographer_invite") XCTAssertEqual(HomeMenuRouter.canonicalURI(for: "pm", availableURIs: available), "pm_manager") XCTAssertEqual(HomeMenuRouter.canonicalURI(for: "payment_code", availableURIs: available), "payment_qr") } /// 测试 menuAliasKey 会把同义入口分到同一组。 func testMenuAliasKeyGroupsDuplicateAndroidEntries() { XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "task_management_editor"), "task_management") XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "photographer_invite"), "registration_invitation") XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "pm_manager"), "pm") XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "payment_code"), "payment_collection") XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "deposit_order_shooting_info"), "deposit_order") XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "/scenic-order-manage"), "photographer_orders") XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "android_only_module"), "android_only_module") } /// 测试未知路由诊断会去重并更新标题。 func testUnknownRouteDiagnosticsDeduplicatesAndUpdatesTitle() { HomeRouteDiagnostics.recordUnknown(uri: "android_feature", title: "旧标题") HomeRouteDiagnostics.recordUnknown(uri: "android_feature", title: "新标题") HomeRouteDiagnostics.recordUnknown(uri: "another_feature", title: "") let records = HomeRouteDiagnostics.unknownRoutes() XCTAssertEqual(records.count, 2) XCTAssertEqual(records.first { $0.uri == "android_feature" }?.title, "新标题") XCTAssertEqual(records.first { $0.uri == "android_feature" }?.count, 2) XCTAssertEqual(records.first { $0.uri == "another_feature" }?.count, 1) } /// 测试未知路由诊断报告是 Markdown 且按出现次数排序。 func testUnknownRouteDiagnosticsReportIsMarkdownAndSortedByCount() { HomeRouteDiagnostics.recordUnknown(uri: "rare_feature", title: "低频入口") HomeRouteDiagnostics.recordUnknown(uri: "hot_feature", title: "高频入口") HomeRouteDiagnostics.recordUnknown(uri: "hot_feature", title: "高频入口") let report = HomeRouteDiagnostics.unknownRouteReport(generatedAt: Date(timeIntervalSince1970: 0)) XCTAssertTrue(report.contains("# Home Route Diagnostics")) XCTAssertTrue(report.contains("| hot_feature | 高频入口 | 2 |")) XCTAssertLessThan( report.range(of: "hot_feature")!.lowerBound, report.range(of: "rare_feature")!.lowerBound ) } /// 测试权限路由审计会区分可路由、不支持和未知入口。 func testPermissionRouteAuditSeparatesRoutableUnsupportedAndUnknownEntries() { let permissions = [ rolePermission( roleId: 1, permissions: [ permission(id: 1, name: "任务管理", uri: "task_management", children: [ permission(id: 2, name: "发布任务", uri: "task_create") ]), permission(id: 3, name: "飞控", uri: "pilot_controller"), permission(id: 4, name: "Android 专项入口", uri: "android_only_module"), permission(id: 5, name: "重复任务", uri: "task_management") ] ) ] let audit = HomePermissionRouteAuditor.audit(permissions: permissions, currentRoleId: 1) XCTAssertEqual(audit.routable.map(\.uri), ["task_management", "task_create"]) XCTAssertEqual(audit.unsupported.map(\.uri), ["pilot_controller"]) XCTAssertEqual(audit.unknown.map(\.uri), ["android_only_module"]) XCTAssertTrue(audit.hasUnknownRoutes) } /// 创建角色权限测试实体。 private func rolePermission(roleId: Int, permissions: [PermissionItem]) -> RolePermissionResponse { RolePermissionResponse( role: RoleInfo(id: roleId, name: "角色\(roleId)", permission: permissions), scenic: [] ) } /// 创建权限节点测试实体。 private func permission(id: Int, name: String, uri: String, children: [PermissionItem] = []) -> PermissionItem { PermissionItem(id: id, name: name, uri: uri, children: children) } }