e0108f7d57
- common 新增 Model3dGenerationSource、Model3dGenerationTarget、Model3dGenerationTargetRef 与产物元数据 - text/image 各自的参数类型独立成 params,API 请求为 generation + 平台字段 + target - 图片输入只接受站内引用,input 由服务端解析后注入 provider - 删除宽松的作业与结果占位 DTO,改为按端点的严格结果与端点 tagged enum - 新增契约严格性测试,覆盖分支必填字段、图片输入歧义与顶层未知字段
117 lines
4.1 KiB
Rust
117 lines
4.1 KiB
Rust
use serde_json::json;
|
|
use shared_contracts::model3d::Model3dGenerationResult;
|
|
use shared_contracts::model3d::common::{
|
|
Model3dGeneratedArtifact, Model3dGenerationTarget, Model3dGenerationTargetRef,
|
|
};
|
|
use shared_contracts::model3d::image_to_model::Model3dImageToModelRequest;
|
|
use shared_contracts::model3d::text_to_model::Model3dTextToModelRequest;
|
|
|
|
#[test]
|
|
fn text_to_model_request_accepts_generation_and_target() {
|
|
let request: Model3dTextToModelRequest = serde_json::from_value(json!({
|
|
"generation": { "prompt": "一把木椅", "model": "v3.1-20260211", "texture": true },
|
|
"target": { "kind": "assetLibrary", "folderId": "folder-1", "label": "木椅" }
|
|
}))
|
|
.expect("合法请求应可反序列化");
|
|
|
|
assert_eq!(request.generation.prompt, "一把木椅");
|
|
assert!(matches!(
|
|
request.target,
|
|
Model3dGenerationTarget::AssetLibrary { .. }
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn target_requires_branch_local_fields() {
|
|
for invalid in [
|
|
json!({ "kind": "assetLibrary", "label": "木椅" }),
|
|
json!({ "kind": "assetLibrary", "folderId": "folder-1" }),
|
|
json!({ "kind": "projectResource" }),
|
|
] {
|
|
let error = serde_json::from_value::<Model3dGenerationTarget>(invalid.clone())
|
|
.expect_err("缺少分支必填字段应被拒绝");
|
|
assert!(
|
|
error.to_string().contains("missing field"),
|
|
"缺少字段的报错应指出 missing field,输入 {invalid},实际为:{error}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn image_source_rejects_ambiguous_and_raw_input() {
|
|
let both_ids = json!({
|
|
"source": { "kind": "resource", "resourceId": "r1", "assetId": "a1" },
|
|
"generation": { "model": "v3.1-20260211" },
|
|
"target": { "kind": "assetLibrary", "folderId": "folder-1", "label": "木椅" }
|
|
});
|
|
let error = serde_json::from_value::<Model3dImageToModelRequest>(both_ids)
|
|
.expect_err("同时给出资源与素材 ID 应被拒绝");
|
|
assert!(
|
|
error.to_string().contains("unknown field"),
|
|
"同时给出两个 ID 应报 unknown field,实际为:{error}"
|
|
);
|
|
|
|
let raw_input = json!({
|
|
"source": "https://example.com/a.png",
|
|
"generation": { "model": "v3.1-20260211" },
|
|
"target": { "kind": "assetLibrary", "folderId": "folder-1", "label": "木椅" }
|
|
});
|
|
serde_json::from_value::<Model3dImageToModelRequest>(raw_input)
|
|
.expect_err("裸字符串图片输入应被拒绝");
|
|
}
|
|
|
|
#[test]
|
|
fn api_request_rejects_unknown_top_level_fields() {
|
|
let error = serde_json::from_value::<Model3dTextToModelRequest>(json!({
|
|
"generation": { "prompt": "一把木椅", "model": "v3.1-20260211" },
|
|
"target": { "kind": "assetLibrary", "folderId": "folder-1", "label": "木椅" },
|
|
"unexpected": true
|
|
}))
|
|
.expect_err("顶层未知字段应被拒绝");
|
|
|
|
assert!(
|
|
error.to_string().contains("unknown field"),
|
|
"顶层未知字段应报 unknown field,实际为:{error}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn generation_result_is_tagged_per_endpoint() {
|
|
let result: Model3dGenerationResult = serde_json::from_value(json!({
|
|
"kind": "textToModel",
|
|
"target": { "kind": "projectResource", "resourceId": "res-1" },
|
|
"model": {
|
|
"objectKey": "models/res-1.glb",
|
|
"contentType": "model/gltf-binary",
|
|
"contentLength": 41_636_076_u64,
|
|
"sha256": "0f1e"
|
|
},
|
|
"preview": {
|
|
"objectKey": "models/res-1.webp",
|
|
"contentType": "image/webp",
|
|
"contentLength": 20_480_u64,
|
|
"sha256": "2a3b"
|
|
}
|
|
}))
|
|
.expect("按端点的结果应可反序列化");
|
|
|
|
let Model3dGenerationResult::TextToModel(text) = result else {
|
|
panic!("kind=textToModel 不应解成 imageToModel 分支");
|
|
};
|
|
assert_eq!(
|
|
text.target,
|
|
Model3dGenerationTargetRef::ProjectResource {
|
|
resource_id: "res-1".into()
|
|
}
|
|
);
|
|
assert_eq!(
|
|
text.model,
|
|
Model3dGeneratedArtifact {
|
|
object_key: "models/res-1.glb".into(),
|
|
content_type: "model/gltf-binary".into(),
|
|
content_length: 41_636_076,
|
|
sha256: "0f1e".into(),
|
|
}
|
|
);
|
|
}
|