fix(m197): 复审 r1 的 P1 + 三条 P2(描述对齐行为 / 内部编号下屏 / kind 守卫 / gh-proxy 可覆盖)
P1(阻断)`java.auto_install` 的描述与行为不一致——那句会进 AI 工具的 JSON Schema `description`,等于让助手对用户说一件实现刻意不做的事: - `crates/core/src/setting/keys.rs`:改成「缺 Java 时在 Java 页标出推荐版本并可一键安装; 不会自动下载」,去掉 `(D13 基准 JDK17)`。 - `SettingsJavaAutoInstallHint` 中英两侧各补一句显式否定(「不会自己开始下载 —— 点「安装」 才会下」/ "Nothing is downloaded on its own — you press Install."),标签从「自动准备」改成 「缺 JDK 时推荐一档」;金表同步。 - 新增守卫 `the_auto_install_description_says_it_never_downloads_by_itself`(描述必须含 「不会自动下载」、不含旧说法与内部编号)。 P2-1 内部决策编号印在界面上:`SettingsJavaDownloadBaseline` 的 `Baseline (D13)` / `基准版本(D13)` → `Recommended baseline` / `推荐基准版本`(金表同步)。另加两条目录级守卫 (`no_user_visible_string_mentions_an_internal_decision_id` / `no_user_visible_string_carries_markdown_backticks`);后者顺带清掉 M168 遗留的 `FontCjkStatusHint` 里的 `ui.font_cdn` 反引号——全仓最后一条。 P2-2 `ProgressEvent::SourceFailed.kind` 没有守卫:`engine_mock_test` - `reports_progress_events_and_errors_when_all_sources_fail` 的解构补 `kind`,断言 `ServerError`; - 新增 `a_404_is_classified_as_not_found_and_never_cools_the_source_down`(404 → `NotFound`, 与「源坏了」分开)。 P2-3 交付任务书那句「(默认值可改)」:新增 `download.gh_proxy`(空 = 跟随内置默认 / `off` = 关 / 其它 = 覆盖前缀),`engine_with` 是它的真消费者(改 `RegionProfile`,因为 `gh_proxy_candidates` 读的就是档案里的前缀);Java 页不画 UI(归 M193)。**内置默认值没动**。 schema minor 1.7.0 → 1.8.0(新键必须推 minor),三处硬编码 minor 的用例同步。 另:真下载用例改成 panic 也清理(复审顺带项 7④);`downloads/refusal.rs` 的「四档」改「五档」。
This commit is contained in:
1 parent
9a7b138d08
commit
a14b00aced
15 files changed
+387
-28
No files matched your search
@@ -173,7 +173,17 @@ fn the_real_download_installs_a_usable_jdk() {
|
||||
cache_root: dir.join("cache"),
|
||||
install_root: JavaInstallRoot::custom(dir.join("jdks")),
|
||||
priority: Vec::new(),
|
||||
gh_proxy: super::GhProxy::Builtin,
|
||||
}));
|
||||
// 失败/panic 也要清理:这条用例会真下 ~180 MB,留在系统 temp 里是最贵的垃圾。
|
||||
struct Cleanup(std::path::PathBuf);
|
||||
impl Drop for Cleanup {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
let _cleanup = Cleanup(dir.clone());
|
||||
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(900);
|
||||
while slot.busy() && std::time::Instant::now() < deadline {
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
@@ -189,7 +199,6 @@ fn the_real_download_installs_a_usable_jdk() {
|
||||
"bin/java 应存在:{}",
|
||||
success.binary
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// 失败要留档:分类 + 大版本 + 最后试到的源(界面靠这三样说清「为什么没装成」)。
|
||||
@@ -298,3 +307,117 @@ fn every_download_template_renders_without_leftover_placeholders() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- P2-3:`download.gh_proxy` 的取值与真消费者
|
||||
|
||||
/// 设置原文 → 三态:空 = 跟随内置默认,`off` 一族 = 关,其余 = 覆盖前缀。
|
||||
///
|
||||
/// 变异证明:删掉 `gh_proxy_from` 里那段 `matches!(lowered, "off" | …)` ⇒ 本用例红
|
||||
/// (`off` 会被当成一个前缀 `off`,拼出 `offhttps://…` 这种 URL)。
|
||||
#[test]
|
||||
fn the_gh_proxy_setting_maps_to_three_states() {
|
||||
use super::engine::gh_proxy_from;
|
||||
use super::GhProxy;
|
||||
|
||||
assert_eq!(gh_proxy_from(""), GhProxy::Builtin, "空 = 跟随内置默认");
|
||||
assert_eq!(gh_proxy_from(" "), GhProxy::Builtin);
|
||||
for off in [
|
||||
"off", "OFF", " none ", "direct", "Disable", "disabled", "no",
|
||||
] {
|
||||
assert_eq!(gh_proxy_from(off), GhProxy::Off, "{off} 应表示关掉");
|
||||
}
|
||||
assert_eq!(
|
||||
gh_proxy_from(" https://gh.example/ "),
|
||||
GhProxy::Prefix("https://gh.example/".to_string()),
|
||||
"前后空白要去掉,值原样保留"
|
||||
);
|
||||
}
|
||||
|
||||
/// **真消费者**:`download.gh_proxy` 落盘 → 引擎档案 → **候选链里真的出现该前缀**。
|
||||
///
|
||||
/// 三步都要成立才算「接了」:① 键读得出来;② 档案被覆盖;③ `expand()` 用它生成候选。
|
||||
/// 只断言 ① 会漏掉「读了但没用」(本 mission 修的就是这一类),只断言 ② 会漏掉「expand 不认
|
||||
/// 档案里的前缀」。
|
||||
///
|
||||
/// 变异证明:让 `engine_with` 忽略 `gh_proxy` 参数(`let profile = profile;`)⇒ ②③ 两条红;
|
||||
/// 把 `gh_proxy_of_config` 写成恒 `Builtin` ⇒ ① 先红。
|
||||
#[test]
|
||||
fn the_gh_proxy_setting_reaches_the_candidate_chain() {
|
||||
use super::engine::engine_with;
|
||||
use super::{gh_proxy_of_config, GhProxy};
|
||||
use unlucky_core::download::cache::now_ms;
|
||||
use unlucky_core::download::region::{store_cached, RegionProfile};
|
||||
use unlucky_core::download::types::{DownloadRequest, ResourceKind};
|
||||
use unlucky_core::download::ProgressEmitter;
|
||||
use unlucky_core::setting::keys::launcher_schema;
|
||||
use unlucky_core::setting::store::SettingStore;
|
||||
|
||||
let dir = std::env::temp_dir().join(format!("ull-gh-proxy-{}-{}", std::process::id(), line!()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(dir.join("cache")).expect("cache");
|
||||
std::fs::create_dir_all(dir.join("config")).expect("config");
|
||||
let config = dir.join("config").join("launcher.json");
|
||||
|
||||
// ① 键读得出来
|
||||
let mut store = SettingStore::load(&config, launcher_schema());
|
||||
store
|
||||
.set(
|
||||
"download.gh_proxy",
|
||||
"https://gh.example/".to_string().into(),
|
||||
)
|
||||
.expect("schema 认识 download.gh_proxy");
|
||||
store.save().expect("写盘");
|
||||
assert_eq!(
|
||||
gh_proxy_of_config(&config),
|
||||
GhProxy::Prefix("https://gh.example/".to_string())
|
||||
);
|
||||
|
||||
// region.json 钉成镜像优先:否则 gh-proxy 候选本来就不生成,测不出「设了有没有用」。
|
||||
let cache = dir.join("cache");
|
||||
store_cached(
|
||||
&cache.join("region.json"),
|
||||
&RegionProfile::mirror_first(now_ms()),
|
||||
)
|
||||
.expect("写 region.json");
|
||||
|
||||
let engine = engine_with(
|
||||
&cache,
|
||||
ProgressEmitter::silent(),
|
||||
&gh_proxy_of_config(&config),
|
||||
);
|
||||
// ② 档案被覆盖
|
||||
assert_eq!(
|
||||
engine.region().gh_proxy.as_deref(),
|
||||
Some("https://gh.example/"),
|
||||
"设置值必须覆盖档案里的前缀"
|
||||
);
|
||||
|
||||
// ③ 候选链里真的出现这个前缀
|
||||
let request = DownloadRequest::new(
|
||||
ResourceKind::Library,
|
||||
"https://github.com/owner/repo/releases/download/v1/a.jar".to_string(),
|
||||
dir.join("a.jar"),
|
||||
);
|
||||
let plan = engine.plan(&request, 0);
|
||||
assert!(
|
||||
plan.iter()
|
||||
.any(|candidate| candidate.url.starts_with("https://gh.example/")),
|
||||
"gh-proxy 候选没出现在候选链里:{:?}",
|
||||
plan.iter()
|
||||
.map(|candidate| candidate.url.clone())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// 关掉那一档:一个代理候选都不生成(同一份 region.json,只换设置值)。
|
||||
let off = engine_with(&cache, ProgressEmitter::silent(), &GhProxy::Off);
|
||||
assert_eq!(off.region().gh_proxy, None, "off 必须把前缀清掉");
|
||||
let plan = off.plan(&request, 0);
|
||||
assert!(
|
||||
!plan
|
||||
.iter()
|
||||
.any(|candidate| candidate.url.starts_with("https://gh.example/")),
|
||||
"关掉之后不该再有 gh-proxy 候选"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
Reference in new issue
Block a user