3D 定价整段兜底也要过端点校验

- server-rs/crates/api-server/src/editor_generation_model3d_records.rs:记录整段缺失时回退到本地配置前先跑 endpoint.validate(),缺键分支与兜底分支走同一条 fail-closed 路径,不再依赖调用方事后补 config.validate()
- server-rs/crates/api-server/src/editor_generation_model3d_records.rs:补用例锁住「整段缺失 + 兜底配置不合法」必须被拒绝
This commit is contained in:
2026-09-24 17:53:34 +08:00
parent a8dc0d8870
commit 98f2c21872
@@ -126,7 +126,17 @@ fn endpoint_pricing_from_record(
// 记录整段缺失(老行、或迁移前从未配过 3D)时直接用本地配置:这才是「覆盖文件只做
// 种子与兜底」的落点,缺键补齐与整段兜底走同一条路径。
let Some(record) = record else {
return Ok(fallback.cloned());
// 兜底值同样过一遍端点校验:否则这条分支的正确性要靠调用方事后补一次
// `config.validate()`,模块自己就不再是 fail-closed 的。
let Some(fallback) = fallback else {
return Ok(None);
};
fallback.validate(endpoint).map_err(|message| {
EditorGenerationPricingError::Invalid(format!(
"SpacetimeDB 模型定价配置 3D 段回退到本地配置,但本地配置不合法:{message}"
))
})?;
return Ok(Some(fallback.clone()));
};
let mut version_prices = BTreeMap::new();
@@ -337,6 +347,25 @@ mod tests {
);
}
#[test]
fn missing_section_with_incomplete_local_fallback_fails_closed() {
let mut sections =
model3d_sections_from_config(Some(&config())).expect("完整配置应能写成记录");
sections.text_to_model_pricing = None;
let mut incomplete_fallback = config();
incomplete_fallback
.text_to_model_pricing
.version_prices
.remove(&Model3dModelVersion::P2);
let error = model3d_config_from_sections(sections, Some(&incomplete_fallback))
.expect_err("整段缺失但与兜底配置不合法时不能放行");
assert!(
error.to_string().contains("本地配置不合法"),
"报错应点名兜底配置不合法,实际为:{error}"
);
}
#[test]
fn half_configured_sections_are_rejected() {
let mut sections =