删除 Mock 模块、图片资源与路由;从常用菜单白名单剔除该权限,后端仍下发时客户端静默忽略。 Co-authored-by: Cursor <cursoragent@cursor.com>
242 lines
14 KiB
Swift
242 lines
14 KiB
Swift
//
|
||
// 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() {
|
||
let expectations: [(String, HomeMenuResolvedRoute)] = [
|
||
("space_settings", .destination(.profileSpaceSettings)),
|
||
("scenicselection", .destination(.scenicSelection)),
|
||
("store", .destination(.moreFunctions)),
|
||
("wallet", .destination(.wallet)),
|
||
("payment_collection", .destination(.paymentCollection)),
|
||
("payment_qr", .destination(.paymentCollection)),
|
||
("payment_code", .destination(.paymentCollection)),
|
||
("permission_apply", .destination(.permissionApply)),
|
||
("permission_apply_status", .destination(.permissionApplyStatus)),
|
||
("scenicapplication", .destination(.scenicApplication)),
|
||
("task_management", .destination(.taskManagement)),
|
||
("task_management_editor", .destination(.taskManagement)),
|
||
("task_create", .destination(.taskCreate)),
|
||
("cloud_management", .destination(.cloudStorage)),
|
||
("cloud_storage_transit", .destination(.cloudStorageTransit)),
|
||
("asset_management", .destination(.materialLibrary)),
|
||
("material_upload", .destination(.materialUpload)),
|
||
("album_list", .destination(.albumList)),
|
||
("album_trailer", .destination(.albumTrailer)),
|
||
("sample_management", .destination(.sampleLibrary)),
|
||
("sample_upload", .destination(.sampleUpload)),
|
||
("checkin_points", .destination(.punchPointList)),
|
||
("location_report", .destination(.locationReport)),
|
||
("location_report_history", .destination(.locationReportHistory)),
|
||
("report_photographer", .destination(.photographerReport)),
|
||
("pm", .destination(.projectManagement)),
|
||
("project_edit", .destination(.projectManagement)),
|
||
("pm_manager", .destination(.pmProjectManagement)),
|
||
("schedule_management", .destination(.scheduleManagement)),
|
||
("registration_invitation", .destination(.photographerInvite)),
|
||
("photographer_invite", .destination(.photographerInvite)),
|
||
("invite_record", .destination(.inviteRecord)),
|
||
("deposit_order_detail", .destination(.depositOrders)),
|
||
("deposit_order", .destination(.depositOrders)),
|
||
("deposit_order_shooting_info", .destination(.depositOrders)),
|
||
("withdrawal_audit", .destination(.withdrawalAudit)),
|
||
("cooperation_order", .destination(.cooperationOrder)),
|
||
("scenic_settlement", .destination(.scenicSettlement)),
|
||
("scenic_settlement_review", .destination(.scenicSettlementReview)),
|
||
("message_center", .destination(.messageCenter)),
|
||
("/scenic-queue", .destination(.queueManagement)),
|
||
("queue_management", .destination(.queueManagement)),
|
||
("live_stream_management", .destination(.liveManagement)),
|
||
("live_album", .destination(.liveAlbum)),
|
||
("travel_album", .destination(.travelAlbumEntry)),
|
||
("operating-area", .destination(.operatingArea)),
|
||
("pilot_cert", .destination(.pilotCertification))
|
||
]
|
||
|
||
for (uri, expected) in expectations {
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: uri, title: ""), expected, "URI \(uri) should resolve to migrated destination")
|
||
}
|
||
}
|
||
|
||
/// 测试 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: ""), .orderPush(.writeOffList))
|
||
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<String> = ["task_management_editor", "photographer_invite", "project_edit", "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), "project_edit")
|
||
XCTAssertEqual(HomeMenuRouter.canonicalURI(for: "pm_manager", 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: "project_edit"), "pm")
|
||
XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "pm_manager"), "pm_manager")
|
||
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: "/scenic-queue"), "queue_management")
|
||
XCTAssertEqual(HomeMenuRouter.menuAliasKey(for: "queue_management"), "queue_management")
|
||
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(
|
||
roleCode: "photographer",
|
||
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, currentRoleCode: "photographer")
|
||
|
||
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)
|
||
}
|
||
|
||
#if DEBUG
|
||
/// 测试 DEBUG 菜单列表不依赖权限,包含已迁移和未迁移入口。
|
||
func testDebugAllMenuItemsExposeKnownMenusWithoutPermissions() {
|
||
let items = HomeMenuRouter.debugAllMenuItems()
|
||
let uris = items.map(\.uri)
|
||
|
||
XCTAssertTrue(uris.contains("album_list"))
|
||
XCTAssertTrue(uris.contains("wallet"))
|
||
XCTAssertTrue(uris.contains("sample_management"))
|
||
XCTAssertTrue(uris.contains("checkin_points"))
|
||
XCTAssertTrue(uris.contains("location_report"))
|
||
XCTAssertTrue(uris.contains("report_photographer"))
|
||
XCTAssertTrue(uris.contains("pm"))
|
||
XCTAssertTrue(uris.contains("pm_manager"))
|
||
XCTAssertTrue(uris.contains("schedule_management"))
|
||
XCTAssertTrue(uris.contains("registration_invitation"))
|
||
XCTAssertTrue(uris.contains("invite_record"))
|
||
XCTAssertTrue(uris.contains("deposit_order"))
|
||
XCTAssertTrue(uris.contains("deposit_order_shooting_info"))
|
||
XCTAssertTrue(uris.contains("message_center"))
|
||
XCTAssertTrue(uris.contains("/scenic-queue"))
|
||
XCTAssertTrue(uris.contains("queue_management"))
|
||
XCTAssertTrue(uris.contains("live_stream_management"))
|
||
XCTAssertTrue(uris.contains("live_album"))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "album_list", title: ""), .destination(.albumList))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "sample_management", title: ""), .destination(.sampleLibrary))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "sample_upload", title: ""), .destination(.sampleUpload))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "checkin_points", title: ""), .destination(.punchPointList))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "location_report", title: ""), .destination(.locationReport))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "report_photographer", title: ""), .destination(.photographerReport))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "pm", title: ""), .destination(.projectManagement))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "pm_manager", title: ""), .destination(.pmProjectManagement))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "schedule_management", title: ""), .destination(.scheduleManagement))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "registration_invitation", title: ""), .destination(.photographerInvite))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "invite_record", title: ""), .destination(.inviteRecord))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "deposit_order", title: ""), .destination(.depositOrders))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "deposit_order_shooting_info", title: ""), .destination(.depositOrders))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "message_center", title: ""), .destination(.messageCenter))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "/scenic-queue", title: ""), .destination(.queueManagement))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "queue_management", title: ""), .destination(.queueManagement))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "live_stream_management", title: ""), .destination(.liveManagement))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "live_album", title: ""), .destination(.liveAlbum))
|
||
XCTAssertEqual(HomeMenuRouter.resolve(uri: "travel_album", title: ""), .destination(.travelAlbumEntry))
|
||
}
|
||
#endif
|
||
|
||
/// 创建角色权限测试实体。
|
||
private func rolePermission(roleCode: String, roleId: Int, permissions: [PermissionItem]) -> RolePermissionResponse {
|
||
RolePermissionResponse(
|
||
role: RoleInfo(id: roleId, name: "角色\(roleId)", roleCode: roleCode, permission: permissions),
|
||
scenic: []
|
||
)
|
||
}
|
||
|
||
/// 创建权限节点测试实体。
|
||
private func permission(id: Int, name: String, uri: String, children: [PermissionItem] = []) -> PermissionItem {
|
||
PermissionItem(id: id, name: name, uri: uri, children: children)
|
||
}
|
||
}
|