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 05c4a14ba..50aa7c121 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 @@ -1,7 +1,8 @@ use serde_json::json; use shared_contracts::model3d::Model3dGenerationResult; use shared_contracts::model3d::common::{ - Model3dGeneratedArtifact, Model3dGenerationTarget, Model3dGenerationTargetRef, + Model3dGeneratedArtifact, Model3dGenerationSource, Model3dGenerationTarget, + Model3dGenerationTargetRef, }; use shared_contracts::model3d::image_to_model::Model3dImageToModelRequest; use shared_contracts::model3d::text_to_model::Model3dTextToModelRequest; @@ -56,8 +57,18 @@ fn image_source_rejects_ambiguous_and_raw_input() { "generation": { "model": "v3.1-20260211" }, "target": { "kind": "assetLibrary", "folderId": "folder-1", "label": "木椅" } }); - serde_json::from_value::(raw_input) + let error = serde_json::from_value::(raw_input) .expect_err("裸字符串图片输入应被拒绝"); + // 拒绝必须发生在 source 本身:错误里点名 Model3dGenerationSource,避免将来 + // generation / target 的必填变化把这条负例“顶替”成通过。 + assert!( + error.to_string().contains("Model3dGenerationSource"), + "裸字符串 source 的报错应点名 Model3dGenerationSource,实际为:{error}" + ); + + // 同样把 source 单独反序列化一遍,锁死被拒的是图片输入的形状本身。 + serde_json::from_value::(json!("https://example.com/a.png")) + .expect_err("裸字符串 source 值应被拒绝"); } #[test] @@ -113,4 +124,60 @@ fn generation_result_is_tagged_per_endpoint() { sha256: "0f1e".into(), } ); + assert_eq!( + text.preview, + Model3dGeneratedArtifact { + object_key: "models/res-1.webp".into(), + content_type: "image/webp".into(), + content_length: 20_480, + sha256: "2a3b".into(), + } + ); + + // 对称覆盖 imageToModel 分支:tag 路由与分支字段都要单独守住。 + let image_result: Model3dGenerationResult = serde_json::from_value(json!({ + "kind": "imageToModel", + "target": { "kind": "projectResource", "resourceId": "res-2" }, + "model": { + "objectKey": "models/res-2.glb", + "contentType": "model/gltf-binary", + "contentLength": 41_636_077_u64, + "sha256": "3c4d" + }, + "preview": { + "objectKey": "models/res-2.webp", + "contentType": "image/webp", + "contentLength": 20_481_u64, + "sha256": "4e5f" + } + })) + .expect("按端点的结果应可反序列化"); + + let Model3dGenerationResult::ImageToModel(image) = image_result else { + panic!("kind=imageToModel 不应解成 textToModel 分支"); + }; + assert_eq!( + image.target, + Model3dGenerationTargetRef::ProjectResource { + resource_id: "res-2".into() + } + ); + assert_eq!( + image.model, + Model3dGeneratedArtifact { + object_key: "models/res-2.glb".into(), + content_type: "model/gltf-binary".into(), + content_length: 41_636_077, + sha256: "3c4d".into(), + } + ); + assert_eq!( + image.preview, + Model3dGeneratedArtifact { + object_key: "models/res-2.webp".into(), + content_type: "image/webp".into(), + content_length: 20_481, + sha256: "4e5f".into(), + } + ); }