fix(addon): 复审 r2 —— 下载阶段抽出可测边界并补守门
- 阻塞项:下载循环从 `install::run` 抽到新文件 `addon/downloads.rs`
(`download_failure()` + `download_stage()`),假下载器就能驱动它;三条用例分别钉
①失败→Unreachable+叶子成因、②第 2 个失败不中断且已装不回滚、③解析完就取消一份都不下。
实测删掉 `record_failure` 那行会红(failures 0 vs 1),把 `cause` 丢掉也会红;
- nit① 补 core 用例:夹具里 `{"version_id":"vid-only","dependency_type":"required"}` ⇒
`plan.unresolved_required == [UnresolvedRequired{parent:"MIXED001",…}]`,同版本的
`project_id` required 仍被跟;删掉收集那段即红(left: [] vs 一条);
- nit② 取消检查加回解析之后(`download_stage` 入口),并用例钉「解析后取消 ⇒ 不下任何文件」;
- nit③ `fail()` 文档写明去重按**请求键**而非规范 id 的边界(先 slug 后 project_id 仍会两条),
不消掉但不再让人误以为全覆盖;
- `install.rs` 只留解析 + 收口(275 行),模块表登记 `downloads`。
This commit is contained in:
1 parent
aa33210bb6
commit
ff2f229b80
5 files changed
+383
-56
No files matched your search
@@ -196,6 +196,14 @@ pub async fn resolve<T: Transport + ?Sized>(
|
||||
/// 标键这一步是**失败侧的去重网**:成功路径靠 `alias` 短路,失败路径以前直接 `continue`,
|
||||
/// 于是同一个项目被两条路径要求时会各取一次版本、各记一条失败,重试名单里出现两次。
|
||||
/// 标记之后,后续同键请求在①处短路,走现成的 [`fold_parents`] 把父边折进那条失败。
|
||||
///
|
||||
/// # 这道去重按**请求键**,不是规范 id(已知边界,别当成全覆盖)
|
||||
///
|
||||
/// 规范 id(`project_id`)要拿到响应才知道;失败时没有响应,能用的只有请求键。所以同一个
|
||||
/// 失败项目如果**先以 slug、后以 project_id** 被两个不同的父项目要求,仍会被取两次、名单里
|
||||
/// 出现两个名字。真实载荷里 `dependencies[].project_id` 与用户点的 slug 混用的情况没有证据,
|
||||
/// 且这一档本来就是「取不到」,重复一条的代价是名单多一行、不是多装一个东西——故本轮不消掉,
|
||||
/// 只把边界写在这里(复审 r2 nit③)。
|
||||
fn fail(
|
||||
plan: &mut DependencyPlan,
|
||||
alias: &mut BTreeMap<String, String>,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
use crate::addon::online::dependency::{present_key, DependencyPlan, ResolveRequest};
|
||||
use crate::addon::online::model::parse_versions;
|
||||
use crate::addon::online::{resolve, DependencyType, Modrinth, PresentFile};
|
||||
use crate::addon::online::{resolve, DependencyType, Modrinth, PresentFile, UnresolvedRequired};
|
||||
use crate::download::blocking::block_on;
|
||||
|
||||
use super::fixtures_deps as deps;
|
||||
@@ -187,3 +187,46 @@ fn required_project_ids_are_sorted_and_deduplicated_on_their_own() {
|
||||
"既要去重(非相邻)也要排序"
|
||||
);
|
||||
}
|
||||
|
||||
/// 只有 `version_id` 的必备前置:**core 自己**要把它们收进 `unresolved_required`(r2 nit①)。
|
||||
///
|
||||
/// presenter 那条用例是直接赋值 `plan.unresolved_required`,绕过了 core;这里从夹具走一遍解析,
|
||||
/// 顺带钉住「别为了收集把正常的 required 也吞掉」——同一版本里带 `project_id` 的那条仍要被跟。
|
||||
#[test]
|
||||
fn a_required_dependency_with_only_a_version_id_is_collected_by_the_resolver() {
|
||||
let body = r#"[{"id": "ver-mixed", "project_id": "MIXED001", "version_number": "1.0",
|
||||
"files": [{"url": "https://cdn.modrinth.com/data/MIXED001/1.0/mixed.jar",
|
||||
"filename": "mixed.jar", "size": 16, "primary": true}],
|
||||
"dependencies": [
|
||||
{"version_id": "vid-only", "dependency_type": "required"},
|
||||
{"project_id": "FOLLOW01", "dependency_type": "required"}
|
||||
]}]"#;
|
||||
let transport = FixtureTransport::new()
|
||||
.with(&format!("{API}/mixed/version"), body)
|
||||
.with(
|
||||
&format!("{API}/FOLLOW01/version"),
|
||||
&version_json("FOLLOW01", "follow.jar", &[]),
|
||||
);
|
||||
|
||||
let plan = resolve_with(&transport, &request("mixed"));
|
||||
|
||||
assert_eq!(
|
||||
plan.unresolved_required,
|
||||
[UnresolvedRequired {
|
||||
parent: "MIXED001".to_string(),
|
||||
version_id: Some("vid-only".to_string()),
|
||||
file_name: None,
|
||||
}],
|
||||
"只给版本 id 的 required 必须被记下来"
|
||||
);
|
||||
let projects: Vec<&str> = plan
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|node| node.project.as_str())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
projects,
|
||||
["MIXED001", "FOLLOW01"],
|
||||
"同一版本里带 project_id 的 required 仍要被跟"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user