Files
Unlucky-Launcher/crates/app-iced/src/pages/account/tests_text.rs
T
AskaEth a40b3a5940 fix(msa): 浏览器登录按钮在生产路径真的置位 browser_first(复审 r1 收口)
复审 r1 的 P1-1:「用浏览器登录」这颗按钮在生产二进制里是哑的——`start_browser_login` 用的是
共享的 `MicrosoftAuth::from_env()`(`browser_first=false`),于是 `browser_then_device_login`
里整段预检 / 回退 / 说明编排不执行,按钮静默退化成设备码,任务③(authcode 可选加速路)
实际未交付。

- P1-1:入口改成 `msa_auth(env, MsaEntry::{DeviceCode,Browser})`,浏览器入口走
  `.with_browser_first(true)`。守卫钉两段:①调用点(按 `LoginJob` 分桶记下生产入口实际请求的
  模式,避开同进程并发用例串台)②模式真的落到 `MicrosoftAuth::browser_first`。变异实测:
  把浏览器入口改回 `DeviceCode` ⇒ 该用例红。核心侧另有报文级证据:浏览器优先路必然对
  `/authorize` 发一次预检(`unregistered_redirect_falls_back_to_the_device_code` 断言 GET 计数 == 1)。
- P2-1:`listeners_opened` 改成**绑定现场**计数——`CallbackServer::bind_counting` 在
  `TcpListener::bind` 前 +1,`login_counting` 把 per-login 的 `AtomicUsize` 传下去;
  删掉「进了授权码分支就记 1」的代理指标。复做变异(预检之前偷绑一个监听口)⇒ 3 条
  msalogin 用例红,包括两条 `listeners_opened == 0`。两处 stale 文档(引用不存在的
  `callback::bind_attempts()`)改成引用 `listeners_opened` / `bind_counting`。
- P2-2:预检加整体 deadline(10s,`PREFLIGHT_TIMEOUT`),超时归 `PreflightUnavailable` 并
  回退设备码;新增「接了连接却不出响应」的假服务器单测(200ms deadline 内必须收场)。
  (下载引擎的 HTTP 客户端只设了 connect_timeout,这条兜住「header 之后停住」。)
- P2-3:client_id 守卫加严到三种拼法(`concat!` / 跨行两段 / `format!` 逐段拼),做法是
  「先收字符串字面量再拼」+「连续纯 hex 字面量累计 ≥32 且 ≥2 段」,并跳过注释(否则本模块
  文档里的示例会自指假红);每条一个自检向量,且不误杀 `session.rs` 的 profile UUID。
- P2-4:页面级断言补中文(`Lang::ZhCn` 走 `Page::build`),并把回退说明的文案出口收敛成
  视图自己的 `login::browser_note_text`(视图与取样共用,不再测试另写一份映射)。
- P2-5:主按钮文案改成「使用 Microsoft 账户登录」/「Sign in with your Microsoft account」,
  下面补一行「浏览器打不开就用这个:它会给一串用户码,在任何设备上输入即可。」。

门禁:`cargo fmt --check`、`cargo clippy --workspace --all-targets -- -D warnings`、
`cargo test --workspace --all-targets`(51 个 test 目标全绿)通过;`client_id.rs` 的判据拆到
`client_id/tests.rs` 守 400 行红线。截图三张(入口 / 设备码 / 回退)已重出。
2026-11-08 20:23:44 +08:00

109 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 「**模型这一层会给出什么**」的取样器:视图模型字段 + **按码做的 i18n 查表**,拼成一串。
//!
//! ## 边界(复审 r2 P2-2 订正,别把这条读成「视图覆盖」)
//!
//! 这是**平行实现**:它自己调 `catalog` 查表,**不调用** `login::panel` / `forms::status_line`,
//! 也不遍历 iced 的元素树。所以它证明的是「模型 + i18n 表里没有中文」,**不证明**「视图真的照它画」。
//!
//! 视图层靠两道别的闸:
//! 1. **类型**:`DevicePanel` / `AuthlibPanel` 里**没有** `message`/`detail` 字段(r3 删掉了),
//! 想画后端原文是编译错误,不是测试问题;
//! 2. **出图**:`DevDocs/artifacts/m114/after/*` 由人(tower/复审)逐张看。
//!
//! 为什么单独一个文件:`pages/account/tests.rs` 贴着 400 行硬线,而这段是**纯取样**
//! (不给任何断言)——断言都在 `tests.rs` 里。
use unlucky_i18n::{Key, Lang};
use unlucky_presenter::dto::AccountNoticeCode;
use super::login::{browser_note_text, failure_body_key, failure_title_key};
use super::model::{AuthlibPanel, DevicePanel, Hint, Page};
/// 页面**会画什么**:模型里的数据 + 视图层对码的 i18n 查表结果。
pub(super) fn on_screen(page: &Page) -> String {
let catalog = Lang::En.catalog();
let mut out = String::new();
for row in &page.rows {
out.push_str(&row.name);
out.push_str(&row.monogram);
out.push_str(&row.kind_label);
out.push_str(&row.added);
out.push_str(&row.detail);
}
out.push_str(&page.form.offline_name);
out.push_str(&page.form.server);
if let Some(notice) = &page.notice {
// 与 `widgets::banner` 同源:码 → i18n(主题名拼进去)
out.push_str(match notice.code {
AccountNoticeCode::OfflineNameInvalid => {
catalog.text(Key::AccountNoticeOfflineNameInvalid)
}
AccountNoticeCode::AccountExists => catalog.text(Key::AccountNoticeAccountExists),
AccountNoticeCode::StoreFailed => catalog.text(Key::AccountNoticeStoreFailed),
AccountNoticeCode::NotFound => catalog.text(Key::AccountNoticeNotFound),
AccountNoticeCode::Other => catalog.text(Key::AccountNoticeOther),
AccountNoticeCode::Added => catalog.text(Key::AccountNoticeAdded),
AccountNoticeCode::Removed => catalog.text(Key::AccountNoticeRemoved),
AccountNoticeCode::Switched => catalog.text(Key::AccountNoticeSwitched),
});
out.push_str(&notice.subject);
}
if let Some(hint) = page.hint {
out.push_str(match hint {
Hint::AuthlibReauth => catalog.text(Key::AccountHintAuthlibReauth),
Hint::OfflineNoRelogin => catalog.text(Key::AccountHintOfflineNoRelogin),
});
}
match &page.device {
DevicePanel::Hidden => {}
DevicePanel::Requesting => out.push_str(catalog.text(Key::AccountDeviceRequesting)),
DevicePanel::Code {
code,
link,
countdown,
note,
..
} => {
out.push_str(code);
out.push_str(link);
// 倒计时文案由模型拼好(M178:owned 文字路径画不出中文)。
out.push_str(countdown);
// M178:回退说明走**视图自己的**文案出口(`login::browser_note_text`),
// 不是测试另写一份映射——否则视图哪天不画这一行,取样仍会绿。
if let Some(note) = note {
out.push_str(browser_note_text(*note, catalog));
}
}
DevicePanel::Browser {
link, countdown, ..
} => {
out.push_str(link);
out.push_str(countdown);
}
DevicePanel::Failed { failure, note } => {
out.push_str(catalog.text(failure_title_key(*failure)));
out.push_str(catalog.text(failure_body_key(*failure)));
out.push_str(&catalog.render(Key::AccountFailureCode, &[("code", failure.code())]));
if let Some(note) = note {
out.push_str(browser_note_text(*note, catalog));
}
}
DevicePanel::Succeeded { name } => {
out.push_str(&catalog.render(Key::AccountLoginSucceeded, &[("name", name)]));
}
}
match &page.authlib {
AuthlibPanel::Idle => {}
AuthlibPanel::Running => out.push_str(catalog.text(Key::AccountServerChecking)),
AuthlibPanel::Ready { server_name } => out.push_str(server_name),
AuthlibPanel::Failed { failure } => out.push_str(failure.code()),
AuthlibPanel::Succeeded { name } => out.push_str(name),
}
out
}
/// 有没有中日韩统一表意文字(语言泄漏的最直接判据)。
pub(super) fn has_cjk(text: &str) -> bool {
text.chars().any(|c| matches!(c, '\u{3400}'..='\u{4DBF}' | '\u{4E00}'..='\u{9FFF}' | '\u{3000}'..='\u{303F}' | '\u{FF00}'..='\u{FFEF}'))
}