fix(m174): 复审 r1 五项 —— 失败带 target、密钥消息掩码、请求体断言、桥写前重读、结构化分类
P2-1(假绿截图):「测试连接」失败类也带上被请求的 target。`ProbeOutcome` 的五个失败变体
改成 `{ target, detail }`,`ProbeResult::from_outcome` 不再把端点/模型填成空串;view 另加一条
「目标为空就整行不画」的保险。**截图种子改走 `from_outcome`**(新增
`ProbeResult::screenshot_seed`):种子只伪造「结论是哪一档」,字段形状由产品路径决定——上一版
手填字段,于是「失败态目标为空」只在产品上出现、截图永远看不见。
P2-2(明文进日志):`Message::EditKey` 的载荷从 `String` 换成 `ai::Secret`(`Debug` 掩码)。
用例 `the_key_message_never_prints_the_key` 同时钉住裸消息与整条 `pages::Message` 漏斗。
P2-3(模型没被证上线):假服务器按 `Content-Length` 读请求正文,新用例
`the_request_carries_the_configured_model` 断言正文里 `"model":"gpt-test"`。变异实测暴露出
一处真相:`ChatRequest.model` 被 `RigProvider::build_request` 静默忽略,线上模型来自
`ProviderConfig.model`(已另开 finding,并写进 probe 的注释)。
P2-4(数据丢失):`SettingsBridge::persist` 写前 `store.reload()`。`SettingStore::save` 是整份
文档落盘,不重读就会拿 ≤1 秒前的旧快照盖掉刚写的 `ai.*`。两条回归用例:
`presenter::settings_bridge::persist_keeps_keys_another_writer_just_saved` 与 app-iced 侧的
`ai::store::appearance_write_back_keeps_the_ai_keys`(保存 AI → 改主题 → 值还在)。
P2-5(子串匹配):删掉 `classify()`。分类改由 `brain::rig::map_error` 从 rig 0.45 的结构化
报告(`ErrorReport` 的 `kind` / `http_status` / `code`)翻译成新的 `ProviderFailure`
(`brain/provider/failure.rs`),`ProviderError` 因此带上 `kind`。`closed_port_is_unreachable`
不再依赖错误文本:连接被拒恒为 `ErrorKind::Http` ⇒ unreachable。防退化用例
`factory_error_carries_the_structured_classification` 断言「纯文本 401 不许被猜成 auth」。
顺带(400 行硬线):`provider.rs` / `rig.rs` 的用例各自拆到 `provider/tests.rs` /
`rig/tests.rs`(纯搬移)。
This commit is contained in:
1 parent
9cb605f316
commit
93d02ab12b
18 files changed
+840
-412
No files matched your search
@@ -120,10 +120,19 @@ impl SettingsBridge {
|
||||
}
|
||||
|
||||
/// 落盘(只写出现过的键;失败静默——配置写不进去不该把 UI 卡住,诊断走日志)。
|
||||
///
|
||||
/// **写前先 `reload()`**(M174 r1 复审 P2-4):本桥的 store 是长生命的(启动时 load 一次),
|
||||
/// 而 `SettingStore::save` 是**整份文档**落盘(`core/src/setting/store.rs` 的 `write_document`
|
||||
/// 序列化 `doc.to_value()`,不是只写脏键)。不先重读就会拿「≤1 秒前的旧快照」整体覆盖文件:
|
||||
/// 用户刚在设置页存下的 `ai.base_url` / `ai.model` / `ai.api_key` 会在下一秒被外观写回抹掉
|
||||
/// (R1 复审给的是这个现场:保存 AI 配置后 1 秒内改一次主题,密钥连影子都不剩)。
|
||||
///
|
||||
/// 与 `HostEnv::store()` 的口径对齐:**写之前现读**,不信任任何长生命快照。
|
||||
pub fn persist(&mut self, appearance: &AppearancePrefs, theme: ThemeMode) {
|
||||
let Some(store) = self.store.as_mut() else {
|
||||
return;
|
||||
};
|
||||
store.reload();
|
||||
let mut snapshot = Appearance::from_store(store);
|
||||
snapshot.theme_mode = theme.as_str().to_owned();
|
||||
snapshot.bg_image_enabled = appearance.bg_image_enabled;
|
||||
@@ -288,6 +297,42 @@ mod tests {
|
||||
panic!("外部修改没有被 mtime 热切捕获");
|
||||
}
|
||||
|
||||
/// **P2-4 回归**:外观写回不许盖掉别人刚写的键(`ai.api_key` 最贵)。修之前必红。
|
||||
#[test]
|
||||
fn persist_keeps_keys_another_writer_just_saved() {
|
||||
let path = temp_config("persist-keeps-ai");
|
||||
let mut bridge = SettingsBridge::with_path(&path);
|
||||
// 先让本桥真的加载过一份文件(旧快照从这里来)。
|
||||
bridge.persist(&AppearancePrefs::factory(), ThemeMode::Light);
|
||||
|
||||
{
|
||||
use unlucky_core::setting::keys::launcher_schema;
|
||||
use unlucky_core::setting::store::SettingStore;
|
||||
// 另一个写者(M174 的助手设置页 / `ull config set`)写进三个 AI 键。
|
||||
let mut store = SettingStore::load(&path, launcher_schema());
|
||||
for (key, value) in [
|
||||
("ai.base_url", "https://api.deepseek.com/v1"),
|
||||
("ai.model", "deepseek-chat"),
|
||||
("ai.api_key", "sk-must-survive"),
|
||||
] {
|
||||
store.set(key, value.to_string().into()).expect("写 AI 键");
|
||||
}
|
||||
store.save().expect("落盘");
|
||||
}
|
||||
|
||||
// 同一秒内改主题:外观写回。
|
||||
bridge.persist(&AppearancePrefs::factory(), ThemeMode::Dark);
|
||||
|
||||
let text = std::fs::read_to_string(&path).expect("读文件");
|
||||
assert!(
|
||||
text.contains("sk-must-survive"),
|
||||
"外观写回把刚保存的密钥盖掉了:{text}"
|
||||
);
|
||||
assert!(text.contains("deepseek-chat"), "model 也被盖掉了:{text}");
|
||||
assert!(text.contains("\"dark\""), "主题这一项该写进去:{text}");
|
||||
std::fs::remove_dir_all(path.parent().unwrap().parent().unwrap()).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accent_preset_and_validation() {
|
||||
let mut appearance = AppearancePrefs::factory();
|
||||
|
||||
Reference in new issue
Block a user