diff --git a/server-rs/crates/shared-contracts/tests/model3d_api_request_contract.rs b/server-rs/crates/shared-contracts/tests/model3d_api_request_contract.rs index 3802c0cd5..e598d41ee 100644 --- a/server-rs/crates/shared-contracts/tests/model3d_api_request_contract.rs +++ b/server-rs/crates/shared-contracts/tests/model3d_api_request_contract.rs @@ -83,11 +83,13 @@ fn image_source_rejects_ambiguous_and_raw_input() { }); let error = serde_json::from_value::(raw_input) .expect_err("裸字符串图片输入应被拒绝"); - // 拒绝必须发生在 source 本身:错误里点名 Model3dGenerationSource,避免将来 - // generation / 落点字段的变化把这条负例“顶替”成通过。 - assert!( - error.to_string().contains("Model3dGenerationSource"), - "裸字符串 source 的报错应点名 Model3dGenerationSource,实际为:{error}" + // 拒绝必须发生在 source 本身,不能被 generation / 落点字段的变化“顶替”成通过。 + // 这里只锁结构化分类(数据错),不锁 serde 的 Display 文案 —— 文案会随 serde + // 版本与枚举表示法变化,而「拒的是 source 的形状」这个契约不变。 + assert_eq!( + error.classify(), + serde_json::error::Category::Data, + "裸字符串 source 的报错应判为数据错误,实际为:{error}" ); // 同样把 source 单独反序列化一遍,锁死被拒的是图片输入的形状本身。 diff --git a/server-rs/crates/spacetime-module/src/migration.rs b/server-rs/crates/spacetime-module/src/migration.rs index 99c7930fc..2402b8bb7 100644 --- a/server-rs/crates/spacetime-module/src/migration.rs +++ b/server-rs/crates/spacetime-module/src/migration.rs @@ -1546,8 +1546,12 @@ mod migration_bootstrap_secret_tests { &serde_json::json!({ "job_id": "task-1" }), ); - assert_eq!(normalized["provider_kind"], serde_json::Value::Null); - assert_eq!(normalized["provider_task_id"], serde_json::Value::Null); + // serde_json 的 `Index<&str>` 在键缺失时返回 `Null` 而不是报错,直接断言 + // `normalized["provider_kind"] == Null` 在归一化整段被删掉时也照样通过。 + // 先按对象取键,再锁 null 默认值,才真正守住这两列被插进去这件事。 + let row = normalized.as_object().expect("归一化后的行必须是对象"); + assert_eq!(row.get("provider_kind"), Some(&serde_json::Value::Null)); + assert_eq!(row.get("provider_task_id"), Some(&serde_json::Value::Null)); } #[test]