读body失败保留已确认状态码
- auth_session:拿到 status 后 response.text() 失败不再一律报网络不可达;非 2xx 按状态码走既有分类(serverMessage 为 None,如 503 → authServiceUnavailable),只有 2xx 响应没收完才归传输层故障 - 抽出 classify_unreadable_body 并补单测(503 / 会话 401 / 400 / 2xx) - 决策记录追加该口径
This commit is contained in:
@@ -573,6 +573,20 @@ fn map_auth_failure(status: StatusCode, body: &str, route: AuthRoute) -> ClientA
|
||||
})
|
||||
}
|
||||
|
||||
/// body 读取失败时的归类。
|
||||
///
|
||||
/// 状态码此时已经拿到:非 2xx 按状态码走已有分类(body 没读到,服务端原文就是 `None`),
|
||||
/// 不把服务端失败误报成传输层故障;只有 2xx 响应没收完才归网络不可达。
|
||||
fn classify_unreadable_body(status: StatusCode, route: AuthRoute) -> ClientAuthError {
|
||||
if status.is_success() {
|
||||
ClientAuthError::AuthNetworkFailure(AuthNetworkFailure {
|
||||
reason: AuthNetworkReason::Unreachable,
|
||||
})
|
||||
} else {
|
||||
map_auth_failure(status, "", route)
|
||||
}
|
||||
}
|
||||
|
||||
fn response_data(body: &str) -> Result<Value, ClientAuthError> {
|
||||
let value: Value = serde_json::from_str(body)
|
||||
.map_err(|_| response_invalid(AuthResponseInvalidReason::NotJson))?;
|
||||
@@ -656,12 +670,10 @@ async fn request_auth(
|
||||
CookiePolicy::Capture | CookiePolicy::Require => refresh_cookie_from_response(&response),
|
||||
CookiePolicy::Ignore => None,
|
||||
};
|
||||
// 拿到状态码后读 body 失败属于传输层故障:按网络不可达上报,不猜契约。
|
||||
let text = response.text().await.map_err(|_| {
|
||||
ClientAuthError::AuthNetworkFailure(AuthNetworkFailure {
|
||||
reason: AuthNetworkReason::Unreachable,
|
||||
})
|
||||
})?;
|
||||
let text = match response.text().await {
|
||||
Ok(text) => text,
|
||||
Err(_) => return Err(classify_unreadable_body(status, auth_route(route))),
|
||||
};
|
||||
if !status.is_success() {
|
||||
return Err(map_auth_failure(status, &text, auth_route(route)));
|
||||
}
|
||||
@@ -1407,6 +1419,32 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// 读 body 失败时状态码已经拿到,不能被压成「网络不可达」。
|
||||
#[test]
|
||||
fn unreadable_body_keeps_the_known_status() {
|
||||
assert_eq!(
|
||||
classify_unreadable_body(StatusCode::SERVICE_UNAVAILABLE, AuthRoute::PhoneLogin),
|
||||
ClientAuthError::AuthServiceUnavailable(AuthServiceUnavailable { status: 503 })
|
||||
);
|
||||
assert_eq!(
|
||||
classify_unreadable_body(StatusCode::UNAUTHORIZED, AuthRoute::Session),
|
||||
ClientAuthError::SessionInvalidated
|
||||
);
|
||||
assert_eq!(
|
||||
classify_unreadable_body(StatusCode::BAD_REQUEST, AuthRoute::PasswordEntry),
|
||||
ClientAuthError::PasswordLoginRejected(PasswordLoginRejected {
|
||||
server_message: None
|
||||
})
|
||||
);
|
||||
// 只有 2xx 响应没收完才是传输层故障。
|
||||
assert_eq!(
|
||||
classify_unreadable_body(StatusCode::OK, AuthRoute::Other),
|
||||
ClientAuthError::AuthNetworkFailure(AuthNetworkFailure {
|
||||
reason: AuthNetworkReason::Unreachable,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keepalive_only_refreshes_after_the_freshness_window() {
|
||||
let issued = 1_000_000_u64;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
- 决策(2026-10-01,Rust 侧不再降级):`refresh_session_inner` 的非权威失败直接 `Err(ClientAuthError)`,`ClientAuthStateView` / `ClientAuthRefreshView` 删除 `errorMessage`,续期结果删除 `failed`;`ClientAuthState` 收敛为 `authenticated | unauthenticated`,`ClientAuthRefreshResult` 收敛为 `refreshed | unauthenticated | stale`。
|
||||
- 追加(2026-10-02,429 按路由判定):`/api/auth/phone/login` 验证码错误次数耗尽返回的 429 是用户可修正的输入问题,映射为 `phoneCodeLoginRejected`(复用现有业务变体、不进错误池);发码路由仍是 `smsCodeThrottled`,其余路由的 429 仍是 `unexpectedRejection`。
|
||||
- 追加(2026-10-02,401 归 phoneCodeLoginRejected):`/api/auth/phone/login` 的 401 只来自「用户不存在」(验证码错误/失效/过期在服务端都是 400,已由 `phoneCodeLoginRejected { serverMessage }` 带原文);顶层变体 `smsCodeRejected` 退役删除,前端三个 catch 去掉了它那个「验证码错误或已过期」的固定分支,`phoneCodeLoginRejected` 的文案统一为「验证码登录失败:<服务端原文>」。
|
||||
- 追加(2026-10-02,读 body 失败按已确认状态码归类):AGC 认证请求拿到 `status` 后 `response.text()` 失败,不再一律压成 `authNetworkFailure { unreachable }`;非 2xx 走既有分类(`serverMessage` 为 `None`,如 503 → `authServiceUnavailable { 503 }`),只有 2xx 响应没收完才算传输层故障。分类收敛在 `classify_unreadable_body`。
|
||||
- 追加(2026-10-02,系统类失败保留原始错误载荷):`clientSessionPersistFailed` / `runtimeSessionInstallFailed` / `authClientInitFailed` 都带 `detail: string`(原始 error),既让调用方有机会分流处理,也让报告包带够诊断信息;原始 error 同时经 `app_log!`(落盘前过 `sanitize_diagnostic_message`)记一行本地日志。`detail` 不贴到界面上:三个 catch 用本操作的固定文案(登录检查 / 发码 / 登录各自不同)。取代上一版"本机 IO 失败不进载荷、原始 error 只进日志"。
|
||||
- 影响范围(2026-10-01 第二轮):`apps/ai-game-creator-shell/src/services/{clientAuth.ts,platformSession.ts}`(`clientAuthError.ts` 删除)、`apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx`、`apps/ai-game-creator-shell/src-tauri/src/auth_session.rs`、对应 vitest 用例。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user