test(presenter): M132 把「缺 client id → 原因码」那行钉进测试(r1 P2-1)
复审 r1 实测:把 `start_login` / `start_browser_login` 里的 `auth_failure(&error)` 改回 `LoginFailure::Other`,编译通过、884 条测试全绿——也就是说「缺 id 会变成 MsaNotConfigured」 这件事此前只有代码可读。这条正是本 mission 要防的回退,补一条能抓它的锚。 新锚 `a_missing_client_id_reaches_the_login_panel_as_a_config_gap` 不只测 `auth_failure`, 而是**真的起两条登录流**(设备码 + 授权码),从 `JobSender` 收到的第一条 `HostTask::Login` 上断言 `failure == MsaNotConfigured`,并断言原文(含 `ULL_MSA_CLIENT_ID`)仍只在 `message` 里、页面画不出来。零网络:`from_env` 在 id 判定处就地早退,事件立刻到,不需要任何桩。 环境变量是进程级的,所以照 core 的做法串行 + panic 也还原;构建期注入过 id 的构建里 (本仓库 CI/本地都不注入)显式断言「只能是这个原因」后返回,不是 ignore/开关。 测试模块从 `accounts.rs` 拆成 `host/accounts/tests.rs`(`#[cfg(test)] mod tests;`,先例 `dto/accounts.rs`、`host/install.rs`):`accounts.rs` 400 → 299 行,拆完两边都远离 400 行硬线。 scope 已由塔台批准扩到 `crates/presenter/src/host/accounts/**`。 变异验证(最终代码上重跑): - 两处 `auth_failure(&error)` → `LoginFailure::Other`:`221 → 220 passed / 1 failed`(只有新锚红); - 还原:222 全绿。 顺手把 fn 指针数组换成一个 `assert_config_gap(name, start)` 辅助函数, `cargo clippy -D warnings` 的 `type_complexity` 不放过它。
This commit is contained in:
1 parent
af39efd23b
commit
f2149048fb
2 files changed
+181
-102
No files matched your search
@@ -296,105 +296,4 @@ fn store_session(env: &HostEnv, session: MicrosoftSession) -> Result<String, Str
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use unlucky_core::auth::AuthError;
|
||||
|
||||
use super::failure::mentions_whitelist;
|
||||
|
||||
#[test]
|
||||
fn time_display_matches_known_timestamps() {
|
||||
assert_eq!(display_ms(0), "");
|
||||
// 1_769_000_000_000 ms = 2026-01-21T12:53:20Z(取整到分钟)
|
||||
assert_eq!(display_ms(1_769_000_000_000), "2026-01-21 12:53");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offline_add_list_remove_round_trips() {
|
||||
let dir = std::env::temp_dir().join(format!("ull-host-accounts-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let env = HostEnv::at_root(&dir);
|
||||
std::fs::create_dir_all(env.layout.auth_data()).expect("auth 目录");
|
||||
|
||||
let name = add_offline(&env, "Steve").expect("加账户");
|
||||
assert_eq!(name, "Steve");
|
||||
let list = accounts(&env, None);
|
||||
assert_eq!(list.len(), 1);
|
||||
assert_eq!(list[0].kind, AccountKind::Offline);
|
||||
assert!(!list[0].active);
|
||||
|
||||
let active = accounts(&env, Some(list[0].id.as_str()));
|
||||
assert!(active[0].active);
|
||||
|
||||
let removed = remove(&env, "Steve").expect("删账户");
|
||||
assert_eq!(removed, "Steve");
|
||||
assert!(accounts(&env, None).is_empty());
|
||||
assert!(add_offline(&env, " ").is_err());
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
fn denied(code: &str) -> AuthError {
|
||||
AuthError::Denied {
|
||||
code: code.to_string(),
|
||||
description: "bad".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn network() -> AuthError {
|
||||
AuthError::Network {
|
||||
url: "https://x".to_string(),
|
||||
detail: "dns".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failure_codes_are_classified_from_variants_not_chinese_text() {
|
||||
assert_eq!(classify(&network()), LoginFailure::Unreachable);
|
||||
let whitelist = classify(&denied("AADSTS7000218"));
|
||||
assert_eq!(whitelist, LoginFailure::NotWhitelisted);
|
||||
assert_eq!(classify(&denied("invalid_grant")), LoginFailure::Denied);
|
||||
assert_eq!(classify(&AuthError::Canceled), LoginFailure::Other);
|
||||
assert!(mentions_whitelist("AADSTS7000218"));
|
||||
assert!(!mentions_whitelist("invalid_grant"));
|
||||
}
|
||||
|
||||
/// **M132**:只有「缺 client id」变成「需要配置」,网络/凭据失败不许跟着变(否则成噪音)。
|
||||
#[test]
|
||||
fn only_a_missing_client_id_becomes_the_config_gap_code() {
|
||||
let missing = auth_failure(&AuthError::Unsupported(String::new()));
|
||||
assert_eq!(missing, LoginFailure::MsaNotConfigured);
|
||||
assert_eq!(auth_failure(&network()), LoginFailure::Unreachable);
|
||||
assert_eq!(auth_failure(&denied("invalid_grant")), LoginFailure::Denied);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offline_name_rule_matches_the_ui_contract() {
|
||||
assert!(valid_offline_name("Steve"));
|
||||
assert!(valid_offline_name("a_1"));
|
||||
assert!(!valid_offline_name("ab"), "太短");
|
||||
assert!(!valid_offline_name("中文名字"), "非 ascii");
|
||||
assert!(!valid_offline_name("has space"));
|
||||
assert!(!valid_offline_name("x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bad_offline_name_is_classified_not_stringified() {
|
||||
let dir = std::env::temp_dir().join(format!("ull-name-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let env = HostEnv::at_root(&dir);
|
||||
std::fs::create_dir_all(env.layout.auth_data()).expect("auth 目录");
|
||||
let failure = add_offline(&env, "ab").expect_err("太短要报错");
|
||||
assert_eq!(failure.code, AccountNoticeCode::OfflineNameInvalid);
|
||||
assert!(!failure.detail.is_empty());
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn login_job_cancel_flag_is_shared() {
|
||||
let job = LoginJob::new();
|
||||
let clone = job.clone();
|
||||
assert!(!clone.is_cancelled());
|
||||
job.cancel();
|
||||
assert!(clone.is_cancelled());
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
Reference in new issue
Block a user