测试: bypass 标志测试统一串行闸

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