diff --git a/src-tauri/src/commands/ai/audit/mod.rs b/src-tauri/src/commands/ai/audit/mod.rs index dc62513..cd819c2 100644 --- a/src-tauri/src/commands/ai/audit/mod.rs +++ b/src-tauri/src/commands/ai/audit/mod.rs @@ -893,6 +893,8 @@ mod tests { /// 锁定此差异:grep 与 read_file 同款授权弹窗语义。 #[test] fn test_grep_unauthorized_path_triggers_auth_not_denied() { + // 途经 is_authorized 读进程级 bypass 标志,持串行闸防与写标志的测试并行互踩 + let _guard = BLACKLIST_BYPASS_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); // 空白名单(不含 workspace_root 的目录):任何路径都未授权 → NeedsAuth let allowed = crate::state::AllowedDirs::default(); let args = serde_json::json!({ "pattern": "foo", "path": "C:/some/unauthorized/dir" }); @@ -916,6 +918,8 @@ mod tests { /// grep 黑名单路径(.ssh 等)直接 Denied(不申请授权),对齐 read_file。 #[test] fn test_grep_blacklisted_path_denied() { + // 途经 is_authorized 读进程级 bypass 标志,持串行闸防与写标志的测试并行互踩 + let _guard = BLACKLIST_BYPASS_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let allowed = crate::state::AllowedDirs::default_with_root(); // .ssh 命中系统黑名单(is_in_system_blacklist) let args = serde_json::json!({ "pattern": "foo", "path": "/home/user/.ssh/config" }); diff --git a/src-tauri/src/commands/ai/audit/path_auth.rs b/src-tauri/src/commands/ai/audit/path_auth.rs index 57f9582..1e0e0a1 100644 --- a/src-tauri/src/commands/ai/audit/path_auth.rs +++ b/src-tauri/src/commands/ai/audit/path_auth.rs @@ -181,6 +181,8 @@ mod tests { /// bypass 关:黑名单行为不变(Denied 基线,与改造前一致)。 #[test] fn test_bypass_off_blacklist_still_denied() { + // 途经 is_authorized 读进程级 bypass 标志,持串行闸防与写标志的测试并行互踩 + let _guard = BLACKLIST_BYPASS_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let allowed = crate::state::AllowedDirs::default_with_root(); let args = serde_json::json!({ "path": "/home/user/.ssh/config" }); match check_file_tool_auth("read_file", &args, &allowed, false) { diff --git a/src-tauri/src/state/allowed_dirs.rs b/src-tauri/src/state/allowed_dirs.rs index 0c8b091..b81f24a 100644 --- a/src-tauri/src/state/allowed_dirs.rs +++ b/src-tauri/src/state/allowed_dirs.rs @@ -276,9 +276,18 @@ mod tests { .unwrap_or_else(|| PathBuf::from(".")) } + /// 途经 is_authorized 的用例统一持此串行闸:is_authorized 首行读进程级 bypass 标志, + /// 与写标志的测试并行运行会互踩误判(path_auth::BLACKLIST_BYPASS_TEST_LOCK 全局单例)。 + fn bypass_test_lock() -> std::sync::MutexGuard<'static, ()> { + crate::commands::ai::audit::path_auth::BLACKLIST_BYPASS_TEST_LOCK + .lock() + .unwrap_or_else(|e| e.into_inner()) + } + /// 显式授权目录后命中(模拟用户在 Settings 页添加目录) #[test] fn test_allowed_dirs_workspace_root_authorized() { + let _guard = bypass_test_lock(); let mut set = HashSet::new(); set.insert(workspace_root_path()); let allowed = AllowedDirs { persistent: set, session: HashSet::new(), once: HashSet::new() }; @@ -292,6 +301,7 @@ mod tests { /// 自定义授权目录命中放行(模拟用户授权 E:/some/external/dir) #[test] fn test_allowed_dirs_custom_authorized() { + let _guard = bypass_test_lock(); let mut set = HashSet::new(); let custom = PathBuf::from("E:/wk-test-external-dir"); set.insert(custom.clone()); @@ -304,6 +314,7 @@ mod tests { /// 未授权目录被拒绝 #[test] fn test_allowed_dirs_unauthorized_rejected() { + let _guard = bypass_test_lock(); let mut set = HashSet::new(); set.insert(PathBuf::from("E:/wk-test-authorized")); let allowed = AllowedDirs { persistent: set, session: HashSet::new(), once: HashSet::new() }; @@ -314,6 +325,7 @@ mod tests { /// 默认(空 persistent)只授权 workspace_root #[test] fn test_allowed_dirs_default_empty_persistent() { + let _guard = bypass_test_lock(); let allowed = AllowedDirs::default(); // 空 persistent,无 workspace_root → 任何路径都不授权 // (default_with_root 才含 workspace_root;Default 不含,用于边界测试) @@ -333,6 +345,7 @@ mod tests { /// Phase B: session 命中放行(persistent 未命中但 session 命中) #[test] fn test_allowed_dirs_session_authorized() { + let _guard = bypass_test_lock(); let mut session = HashSet::new(); session.insert(PathBuf::from("E:/wk-temp-session")); let allowed = AllowedDirs { persistent: HashSet::new(), session, once: HashSet::new() }; @@ -342,6 +355,7 @@ mod tests { /// Phase B: persistent + session 任一命中即放行 #[test] fn test_allowed_dirs_persistent_or_session() { + let _guard = bypass_test_lock(); let mut persistent = HashSet::new(); persistent.insert(PathBuf::from("E:/wk-persist")); let mut session = HashSet::new(); @@ -359,6 +373,7 @@ mod tests { /// Phase C: Windows System32 黑名单命中(即使在白名单内也拒) #[test] fn test_blacklist_windows_system32() { + let _guard = bypass_test_lock(); let mut persistent = HashSet::new(); // 用户误把整个 C:\ 加入白名单 persistent.insert(PathBuf::from("C:\\")); @@ -370,6 +385,7 @@ mod tests { /// Phase C: Windows Program Files 黑名单命中 #[test] fn test_blacklist_windows_program_files() { + let _guard = bypass_test_lock(); let mut persistent = HashSet::new(); persistent.insert(PathBuf::from("C:\\")); let allowed = AllowedDirs { persistent, session: HashSet::new(), once: HashSet::new() }; @@ -403,6 +419,7 @@ mod tests { /// Phase B/C: 显式授权 workspace_root 后,内路径 → Authorized(方案①:不再默认) #[test] fn test_check_path_authorized_workspace() { + let _guard = bypass_test_lock(); let mut set = HashSet::new(); set.insert(workspace_root_path()); let allowed = AllowedDirs { persistent: set, session: HashSet::new(), once: HashSet::new() }; @@ -416,6 +433,7 @@ mod tests { /// Phase B/C: 未授权路径 → NeedsAuthorization(附父目录) #[test] fn test_check_path_needs_auth() { + let _guard = bypass_test_lock(); let allowed = AllowedDirs::default(); // 空,无 workspace_root match check_path_authorization("E:/wk-external/file.txt", &allowed) { PathAuthDecision::NeedsAuthorization { dir } => { @@ -428,6 +446,7 @@ mod tests { /// Phase B: session 命中 → Authorized(check_path_authorization 读 session 字段) #[test] fn test_check_path_session_hit() { + let _guard = bypass_test_lock(); let mut session = HashSet::new(); session.insert(PathBuf::from("E:/wk-session")); let allowed = AllowedDirs { persistent: HashSet::new(), session, once: HashSet::new() }; @@ -440,6 +459,7 @@ mod tests { /// Phase C: 黑名单路径 → Denied(不弹窗直接拒) #[test] fn test_check_path_denied_blacklist() { + let _guard = bypass_test_lock(); let allowed = AllowedDirs::default_with_root(); match check_path_authorization("C:/Windows/System32/config/sam", &allowed) { PathAuthDecision::Denied { .. } => {} @@ -455,6 +475,7 @@ mod tests { /// 此测试锁死该修复:空 persistent + Unix 风格 .ssh 路径 → 必 Denied。 #[test] fn test_check_path_blacklist_unix_style_ssh_on_windows() { + let _guard = bypass_test_lock(); let allowed = AllowedDirs::default_with_root(); match check_path_authorization("/home/user/.ssh/config", &allowed) { PathAuthDecision::Denied { .. } => {} @@ -471,6 +492,7 @@ mod tests { /// 修复黑名单提前后,需确认非黑名单相对路径行为不变。 #[test] fn test_check_path_non_blacklist_relative_still_needs_auth() { + let _guard = bypass_test_lock(); let allowed = AllowedDirs::default_with_root(); match check_path_authorization("some/regular/path", &allowed) { PathAuthDecision::NeedsAuthorization { .. } => {} @@ -489,6 +511,7 @@ mod tests { /// persistent 用动态 workspace_root_path(非硬编码开发机路径),candidate 拼 verbatim 前缀。 #[test] fn test_is_authorized_strips_verbatim_prefix() { + let _guard = bypass_test_lock(); let mut set = HashSet::new(); set.insert(workspace_root_path()); let allowed = AllowedDirs { persistent: set, session: HashSet::new(), once: HashSet::new() }; @@ -535,9 +558,7 @@ mod tests { /// 先取结果再复位再断言,防 panic 泄漏标志污染并行测试。 #[test] fn test_bypass_on_is_authorized_allows_all() { - let _guard = crate::commands::ai::audit::path_auth::BLACKLIST_BYPASS_TEST_LOCK - .lock() - .unwrap_or_else(|e| e.into_inner()); + let _guard = bypass_test_lock(); crate::commands::ai::audit::path_auth::set_blacklist_bypass(true); let allowed = AllowedDirs::default(); let outside = PathBuf::from("E:/wk-test-bypass-outside/file.txt"); @@ -552,9 +573,7 @@ mod tests { /// 总开关关:黑名单拒且白名单外拒,与接入旁路前行为一致。 #[test] fn test_bypass_off_is_authorized_unchanged() { - let _guard = crate::commands::ai::audit::path_auth::BLACKLIST_BYPASS_TEST_LOCK - .lock() - .unwrap_or_else(|e| e.into_inner()); + let _guard = bypass_test_lock(); crate::commands::ai::audit::path_auth::set_blacklist_bypass(false); let allowed = AllowedDirs::default(); let outside_ok = allowed.is_authorized(&PathBuf::from("E:/wk-test-bypass-outside/file.txt"));