fix(presenter): M114 错误分类只看结构化字段(status/kind/server_message),不再 Debug 整条错误

- `AuthError::Response` 的判据从 `format!("{detail:?}")`(连中文 what/digest 一起扫)
  改成读 `ResponseDetail{status, kind, server_message, digest}`
- 5xx 与拦截页(Html/Text)归 `Unreachable`(「连不上」而不是「服务器拒绝了你」),
  AADSTS/consent/whitelist 标记仍优先判 `NotWhitelisted`
- 补两条分类用例:AADSTS 优先于一般拒绝、5xx/拦截页归不可达
This commit is contained in:
AskaEth committed 2026-11-05 05:10:14 +08:00
1 parent 73badb8a02
commit 178f6ebbff
1 file changed
+59 -2
+59 -2
View File
@@ -5,6 +5,8 @@
use unlucky_core::auth::AuthError;
use unlucky_core::auth::BodyKind;
use crate::dto::{AccountNoticeCode, LoginFailure};
/// 账户写操作的失败:**结构化码 + 原文**(原文只进日志/详细信息,不上屏)。
@@ -44,10 +46,17 @@ pub(super) fn classify(error: &AuthError) -> LoginFailure {
LoginFailure::Denied
}
}
// 只看 `ResponseDetail` 的**结构化字段**(status / kind / server_message),
// 不把整条错误 Debug 成字符串再去扫——那会把中文 `what`/`digest` 也算进判据里。
AuthError::Response(detail) => {
let blob = format!("{detail:?}");
if mentions_whitelist(&blob) {
let server = detail.server_message.as_deref().unwrap_or_default();
let digest = detail.digest.as_str();
if mentions_whitelist(server) || mentions_whitelist(digest) {
LoginFailure::NotWhitelisted
} else if detail.status >= 500 || matches!(detail.kind, BodyKind::Html | BodyKind::Text)
{
// 5xx / 拦截页:服务器或链路出了问题,不是「它拒绝了你」。
LoginFailure::Unreachable
} else {
LoginFailure::Denied
}
@@ -67,3 +76,51 @@ pub(super) fn mentions_whitelist(text: &str) -> bool {
|| lowered.contains("whitelist")
|| lowered.contains("not approved")
}
#[cfg(test)]
mod tests {
use super::*;
use unlucky_core::auth::body::ResponseDetail;
fn response(status: u16, kind: BodyKind, server_message: Option<&str>) -> AuthError {
AuthError::Response(Box::new(ResponseDetail {
step: "xsts".to_string(),
what: "XSTS 授权".to_string(),
status,
content_type: None,
kind,
server_message: server_message.map(str::to_string),
digest: "digest".to_string(),
}))
}
/// 403 带 AADSTS 码 → 白名单未批;403 不带 → 一般性拒绝。
#[test]
fn aadsts_markers_win_over_the_generic_denial() {
assert_eq!(
classify(&response(
403,
BodyKind::Json,
Some("AADSTS7000218: app not approved")
)),
LoginFailure::NotWhitelisted
);
assert_eq!(
classify(&response(403, BodyKind::Json, Some("invalid_grant"))),
LoginFailure::Denied
);
}
/// 5xx 与「拦截页」(HTML/纯文本)归「连不上」,不是「服务器拒绝了你」。
#[test]
fn upstream_failures_and_intercept_pages_are_unreachable() {
assert_eq!(
classify(&response(503, BodyKind::Json, None)),
LoginFailure::Unreachable
);
assert_eq!(
classify(&response(200, BodyKind::Html, None)),
LoginFailure::Unreachable
);
}
}