From 133f14ba0beb730d3fcb23f54f918636566c795e Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 9 Nov 2026 09:32:58 +0800 Subject: [PATCH] =?UTF-8?q?test(presenter):=20=E5=9D=8F=E6=B8=85=E5=8D=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E6=8C=89=E9=94=99=E8=AF=AF=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E5=8C=BA=E5=88=86=E3=80=8CJSON=20=E5=9D=8F=E3=80=8D=E4=B8=8E?= =?UTF-8?q?=E3=80=8C=E7=AD=BE=E5=90=8D=E8=A2=AB=E6=8B=92=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 台账 §10 第 7 条 / M199 复审 P3-2:原来只断言「结果是 Failed」,而它喂的每一份清单都签对了 ——验签一旦 fail-open 或签名错误被误分类,这条用例照样绿。 现在两组各钉自己的分类: - 内容坏(5 个变体,签名都对):kind 必须是 invalid_manifest / invalid_version; - 字节好、只有签名对不上(放行就会报「有更新 0.9.0」):kind 必须是 signature_invalid 且不可重试。 变异证明(只改实现侧,跑完已还原): - A core::update::signature::verify_manifest fail-open → ② 分支红,实际 Available(0.9.0); - B core::update::manifest::from_json 把解析失败误分类成 SignatureInvalid → ① 分支红, 「not json:坏内容分类是 signature_invalid」。 两个变异各只打红对应分支,说明两类坏输入确实各走各的分类。 文件仍在 400 行红线内(394 行)。 --- crates/presenter/src/host/update_tests.rs | 41 +++++++++++++++++------ 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/crates/presenter/src/host/update_tests.rs b/crates/presenter/src/host/update_tests.rs index 58057d5..077c144 100644 --- a/crates/presenter/src/host/update_tests.rs +++ b/crates/presenter/src/host/update_tests.rs @@ -6,7 +6,7 @@ //! | 用例 | 守什么 | //! |---|---| //! | [`version_matrix_decides_who_gets_prompted`] | 版本更高才提示;同版本/更低一律不提示;`v0.2.3` 这类真实 tag 形态 | -//! | [`a_broken_manifest_degrades_to_failure_not_up_to_date`] | 清单坏 JSON → 「检查失败」,**不是**「已是最新」 | +//! | [`a_broken_manifest_degrades_to_failure_not_up_to_date`] | 坏 JSON → `invalid_*`、签名被拒 → `signature_*`;两种都是「检查失败」,**不是**「已是最新」 | //! | [`a_higher_version_without_our_platform_is_not_up_to_date`] | 有新版但没本平台产物 → 单独的变体 | //! | [`sha256_mismatch_refuses_the_apply_and_leaves_no_temp_file`] | 摘要不符拒绝应用、暂存文件不残留、目标没被换掉 | //! | [`a_successful_apply_leaves_the_new_file_and_a_backup`] | 换上去的是新字节、旧字节留在备份里、回报是「就位」 | @@ -191,11 +191,16 @@ fn version_matrix_decides_who_gets_prompted() { // ------------------------------------------------------------- 清单契约降级 -/// 坏 JSON / 缺字段 ⇒ **检查失败**,绝不是「已是最新」。 +/// 坏 JSON / 缺字段 / 签名被拒 ⇒ **检查失败**,绝不是「已是最新」;而且两种坏法**分类不同**。 /// -/// 两个变体的文案与后果完全不同:说成「已是最新」会让用户以为网络通、版本新,实际一个字节都没取到。 +/// M199 复审 P3-2:旧版只断言「结果是 Failed」,而它喂的清单都**签对了**——验签一旦 fail-open, +/// 坏签名清单会一路走到解析,坏 JSON 照样解析失败,断言全绿。现在内容坏必须落 `invalid_*`, +/// 签名坏必须落 `signature_*` 且不可重试。 +/// 抓得住:验签被跳过/放行(② 会拿到 `Available 0.9.0`)、签名与解析互相误分类、失败被折叠成「已是最新」; +/// 抓不住:签名算法细节(信封字段、`manifest_sha256` 冗余校验、钥表)——那在 core 的 `signature_cases.rs`。 #[test] fn a_broken_manifest_degrades_to_failure_not_up_to_date() { + // ① 签名是对的(`write_manifest` 成对落盘),内容坏:分类只能是「清单结构 / 版本号不合法」。 for (label, manifest) in [ ("not json", "{ this is not json".to_string()), ( @@ -221,16 +226,32 @@ fn a_broken_manifest_degrades_to_failure_not_up_to_date() { let tmp = TempDir::new("badmanifest"); write_manifest(&tmp, &manifest); let outcome = outcome_of(job(&tmp).check(&version("0.2.2"), Channel::Stable)); + let Some(failure) = outcome.failure() else { + panic!("{label}:坏清单必须降级成「检查失败」,实际 {outcome:?}"); + }; + let kind = failure.kind.as_str(); assert!( - matches!(outcome, UpdateOutcome::Failed(_)), - "{label}:坏清单必须降级成「检查失败」,实际 {outcome:?}" - ); - let failure = outcome.failure().expect("失败带原因"); - assert!( - !failure.message.trim().is_empty(), - "{label}:失败要有人话原因" + matches!(kind, "invalid_manifest" | "invalid_version"), + "{label}:坏内容分类是 {kind}" ); + assert!(!failure.message.trim().is_empty(), "{label}:失败没原因"); } + // ② 清单字节是好的(放行就会报「有更新 0.9.0」),只有签名对不上:必须落 `signature_invalid`。 + let tmp = TempDir::new("badsignature"); + tmp.write( + "dist/stable.json", + manifest_for("0.9.0", b"new", "").as_bytes(), + ); + tmp.write( + "dist/stable.json.sig", + signature_envelope("another").as_bytes(), + ); + let outcome = outcome_of(job(&tmp).check(&version("0.2.2"), Channel::Stable)); + let Some(failure) = outcome.failure() else { + panic!("签名验不过必须拒装(放行就会报「有更新 0.9.0」),实际 {outcome:?}"); + }; + assert_eq!(failure.kind, "signature_invalid", "签名被拒的分类"); + assert!(!failure.retryable, "签名问题不可重试"); } /// 清单里**一条发布都没有**(`{"channel":"stable"}` / 空 `releases`)⇒ 已是最新,不是失败。