参考素材改为提交前预检并补参考链路用例
参考解析前增加纯本地预检:清单归属、受控路径与文件、媒体类型、可解码位图全部通过后才允许产生任何上传副作用,避免第一张参考已上传、第二张坏图才失败 明确拒绝 SVG 等矢量参考,媒体类型或文件扩展名任一命中即拒,本次不做矢量转换,坏图同样在本地失败关闭 asset.upload 门禁提前到预检阶段,显式拒绝该命令的项目连第一个上传凭证都不会签发 预检与实际上传共用同一份本地判据 helper,不新建平行流程,上传侧仍按同一判据重新读取与解码 补参考合同与恢复的账本用例:icon-spec 带用户参考、game-background 规范图加用户参考、图集超限、空白引用写入失败 补提交侧用例:拒绝路径、未登记、非图片、缺失文件、超限、SVG 与坏图,并断言零上传凭证与零生成 POST,反向验证同一张合格参考单独提交确实会签发凭证 补 asset.upload 拒绝用例,以及清单校验与 Direct 身份校验接受用户参考、拒绝用户参考顶替规范引用的覆盖
This commit is contained in:
@@ -9808,6 +9808,78 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_taonier_art_package_accepts_manifest_user_references() {
|
||||
let root = tempfile::tempdir().expect("temp dir");
|
||||
init_local_game_project_at(root.path(), "direct-art-references", "直连美术参考")
|
||||
.expect("init project");
|
||||
register_direct_taonier_art_package_fixture(root.path());
|
||||
assert!(direct_taonier_art_package_is_valid(root.path()));
|
||||
|
||||
// 规范图与背景图带用户参考:参考只是风格输入,规范身份仍由参考序列首项承担。
|
||||
mutate_manifest_at(root.path(), |manifest| {
|
||||
let art_spec = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == DIRECT_CODEX_ART_SPEC_ASSET_PATH)
|
||||
.expect("art spec asset");
|
||||
art_spec.source.reference_resource_ids = vec![
|
||||
"user-reference-1".to_string(),
|
||||
"user-reference-2".to_string(),
|
||||
];
|
||||
let background = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == DIRECT_CODEX_BACKGROUND_ASSET_PATH)
|
||||
.expect("background asset");
|
||||
background
|
||||
.source
|
||||
.reference_resource_ids
|
||||
.push("user-reference-1".to_string());
|
||||
Ok(())
|
||||
})
|
||||
.expect("apply user references to the art base");
|
||||
assert!(
|
||||
direct_taonier_art_package_is_valid(root.path()),
|
||||
"user references must not invalidate the art package"
|
||||
);
|
||||
|
||||
// 图集仍只接受唯一规范引用:多一项用户参考必须失败关闭。
|
||||
mutate_manifest_at(root.path(), |manifest| {
|
||||
let spritesheet = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == DIRECT_CODEX_SPRITESHEET_ASSET_PATH)
|
||||
.expect("spritesheet asset");
|
||||
spritesheet
|
||||
.source
|
||||
.reference_resource_ids
|
||||
.push("user-reference-1".to_string());
|
||||
Ok(())
|
||||
})
|
||||
.expect("add an extra spritesheet reference");
|
||||
assert!(
|
||||
!direct_taonier_art_package_is_valid(root.path()),
|
||||
"art spritesheet must reject extra user references"
|
||||
);
|
||||
|
||||
// 用户参考不能顶替图集的规范前置。
|
||||
mutate_manifest_at(root.path(), |manifest| {
|
||||
let spritesheet = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == DIRECT_CODEX_SPRITESHEET_ASSET_PATH)
|
||||
.expect("spritesheet asset");
|
||||
spritesheet.source.reference_resource_ids = vec!["user-reference-1".to_string()];
|
||||
Ok(())
|
||||
})
|
||||
.expect("replace the spritesheet canonical reference");
|
||||
assert!(
|
||||
!direct_taonier_art_package_is_valid(root.path()),
|
||||
"a user reference must not replace the art spritesheet canonical spec"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_output_sync_accepts_a_complete_spritesheet_without_slices() {
|
||||
let root = tempfile::tempdir().expect("temp dir");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -378,6 +378,78 @@ fn canonical_visual_completion_requires_persisted_route_kind_and_current_spec_re
|
||||
.unwrap_or_else(|error| panic!("{task_id} provenance should pass: {error}"));
|
||||
}
|
||||
|
||||
// 规范图与派生素材都允许用户参考(同一项目已登记的图片素材):参考只是风格输入,
|
||||
// 规范身份仍由参考序列第一项承担,多出的用户参考不能让校验失败。
|
||||
let art_spec = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == "assets/art-spec.png")
|
||||
.expect("art spec asset");
|
||||
art_spec.source.reference_resource_ids = vec!["user-reference-1".to_string()];
|
||||
let ui = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == "assets/ui-prototype.png")
|
||||
.expect("ui prototype asset");
|
||||
ui.source.reference_resource_ids = vec![
|
||||
"resource-icon-spec".to_string(),
|
||||
"user-reference-1".to_string(),
|
||||
];
|
||||
for task_id in ["art-director", "design-foundation"] {
|
||||
validate_manifest_required_visual_asset(&root, &manifest, task_id)
|
||||
.unwrap_or_else(|error| panic!("{task_id} must accept user references: {error}"));
|
||||
}
|
||||
|
||||
// 用户参考不能顶替规范图:首项不是当前规范引用时仍必须失败关闭。
|
||||
let ui = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == "assets/ui-prototype.png")
|
||||
.expect("ui prototype asset");
|
||||
ui.source.reference_resource_ids = vec![
|
||||
"user-reference-1".to_string(),
|
||||
"resource-icon-spec".to_string(),
|
||||
];
|
||||
assert!(
|
||||
validate_manifest_required_visual_asset(&root, &manifest, "design-foundation")
|
||||
.expect_err("a leading user reference must not replace the canonical spec")
|
||||
.contains("未绑定当前统一视觉规范图的本地内容身份")
|
||||
);
|
||||
|
||||
// 只接受单规范引用的图集不接受额外用户参考。
|
||||
let ui = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == "assets/ui-prototype.png")
|
||||
.expect("ui prototype asset");
|
||||
ui.source.reference_resource_ids = vec!["resource-icon-spec".to_string()];
|
||||
let spritesheet = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == "assets/art-spritesheet.png")
|
||||
.expect("art spritesheet asset");
|
||||
spritesheet.source.reference_resource_ids = vec![
|
||||
"resource-icon-spec".to_string(),
|
||||
"user-reference-1".to_string(),
|
||||
];
|
||||
assert!(
|
||||
validate_manifest_required_visual_asset(&root, &manifest, "art-asset-plan")
|
||||
.expect_err("art spritesheet must reject extra user references")
|
||||
.contains("未精确引用当前统一视觉规范图")
|
||||
);
|
||||
let spritesheet = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == "assets/art-spritesheet.png")
|
||||
.expect("art spritesheet asset");
|
||||
spritesheet.source.reference_resource_ids = vec!["resource-icon-spec".to_string()];
|
||||
let art_spec = manifest
|
||||
.assets
|
||||
.iter_mut()
|
||||
.find(|asset| asset.local_path == "assets/art-spec.png")
|
||||
.expect("art spec asset");
|
||||
art_spec.source.reference_resource_ids = Vec::new();
|
||||
|
||||
let art_spec_path = root.join("assets/art-spec.png");
|
||||
let valid_art_spec = fs::read(&art_spec_path).expect("read valid art spec fixture");
|
||||
fs::write(&art_spec_path, &valid_art_spec[..valid_art_spec.len() / 2])
|
||||
@@ -1019,6 +1091,13 @@ async fn canonical_art_spec_and_ui_requests_use_the_shared_reference_chain() {
|
||||
"canvas-project-1",
|
||||
);
|
||||
}
|
||||
if root.join("assets/user-reference.png").is_file() {
|
||||
bind_canvas_visual_asset_fixture_to_current_editor(
|
||||
root,
|
||||
"assets/user-reference.png",
|
||||
"canvas-project-1",
|
||||
);
|
||||
}
|
||||
request_platform_art_asset_with_options_for_test(root, "原创贪吃蛇视觉", &options)
|
||||
.await
|
||||
.expect("prepare canonical visual request");
|
||||
@@ -1087,9 +1166,385 @@ async fn canonical_art_spec_and_ui_requests_use_the_shared_reference_chain() {
|
||||
assert!(ui_request.contains(r#""kind":"ui-design""#));
|
||||
assert!(ui_request.contains(r#""referenceImageSrcs":["resource-icon-spec"]"#));
|
||||
|
||||
// 用户参考只接受当前项目已登记图片素材 id,并换成当前账号绑定下的远端资源 ID。
|
||||
register_canvas_visual_asset_fixture(&root, "assets/user-reference.png", "image");
|
||||
let user_reference_asset_id = manifest_asset_id(&root, "assets/user-reference.png");
|
||||
let icon_spec_config_dir = unique_project_path();
|
||||
let icon_spec_with_reference = capture_generation_request(
|
||||
&root,
|
||||
&icon_spec_config_dir,
|
||||
PlatformArtAssetGenerationOptions {
|
||||
output_path: Some("assets/icon-spec-custom.png".to_string()),
|
||||
asset_kind: "icon-spec".to_string(),
|
||||
asset_label: "带用户参考的图标规范".to_string(),
|
||||
reference_asset_ids: vec![
|
||||
user_reference_asset_id.clone(),
|
||||
user_reference_asset_id.clone(),
|
||||
],
|
||||
..PlatformArtAssetGenerationOptions::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(icon_spec_with_reference.starts_with("POST /api/editor/images/generations "));
|
||||
// 图标规范没有规范前置:用户参考原样提交,且不带任何伪造的规范引用。
|
||||
assert!(
|
||||
icon_spec_with_reference.contains(r#""referenceImageSrcs":["resource-image"]"#),
|
||||
"{icon_spec_with_reference}"
|
||||
);
|
||||
|
||||
let ui_with_reference_config_dir = unique_project_path();
|
||||
let ui_with_reference = capture_generation_request(
|
||||
&root,
|
||||
&ui_with_reference_config_dir,
|
||||
PlatformArtAssetGenerationOptions {
|
||||
output_path: Some("assets/ui-prototype-custom.png".to_string()),
|
||||
aspect_ratio: "16:9".to_string(),
|
||||
image_size: "2K".to_string(),
|
||||
asset_kind: "ui-prototype".to_string(),
|
||||
asset_label: "带用户参考的界面原型图".to_string(),
|
||||
reference_asset_ids: vec![user_reference_asset_id.clone()],
|
||||
..PlatformArtAssetGenerationOptions::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
// 有规范前置的生成:规范图始终是第一项,用户参考按给出顺序追加在后。
|
||||
assert!(
|
||||
ui_with_reference
|
||||
.contains(r#""referenceImageSrcs":["resource-icon-spec","resource-image"]"#),
|
||||
"{ui_with_reference}"
|
||||
);
|
||||
|
||||
fs::remove_dir_all(root).ok();
|
||||
fs::remove_dir_all(spec_config_dir).ok();
|
||||
fs::remove_dir_all(ui_config_dir).ok();
|
||||
fs::remove_dir_all(icon_spec_config_dir).ok();
|
||||
fs::remove_dir_all(ui_with_reference_config_dir).ok();
|
||||
}
|
||||
|
||||
/// 参考上传凭证请求:同一路由在 External v1 与平台会话下会落到两种前缀,两边的写操作都要看住。
|
||||
fn is_reference_upload_ticket_request(request: &str) -> bool {
|
||||
request.starts_with("POST /api/assets/direct-upload-tickets ")
|
||||
|| request.starts_with("POST /api/external/v1/assets/direct-upload-tickets ")
|
||||
}
|
||||
|
||||
/// 图片生成提交:同样两种前缀都要算。
|
||||
fn is_image_generation_request(request: &str) -> bool {
|
||||
request.starts_with("POST /api/editor/images/generations ")
|
||||
|| request.starts_with("POST /api/external/v1/editor/images/generations ")
|
||||
|| request.starts_with("POST /api/editor/icon-spritesheets/generations ")
|
||||
|| request.starts_with("POST /api/external/v1/editor/icon-spritesheets/generations ")
|
||||
}
|
||||
|
||||
/// 当前项目清单里某条素材的 manifest 资产 id(参考选择只接受这个身份)。
|
||||
fn manifest_asset_id(root: &Path, local_path: &str) -> String {
|
||||
read_manifest_for_project(root)
|
||||
.expect("read manifest for asset id")
|
||||
.assets
|
||||
.into_iter()
|
||||
.find(|asset| asset.local_path == local_path)
|
||||
.unwrap_or_else(|| panic!("registered asset is present: {local_path}"))
|
||||
.id
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn reference_selection_rejects_unsupported_inputs_before_any_generation_post() {
|
||||
let root = unique_project_path();
|
||||
let config_dir = unique_project_path();
|
||||
let (request_sender, request_receiver) = mpsc::channel();
|
||||
// 这条用例要连续跑十几次「画布上下文 + 参考预检」,超过默认 20 次请求预算会被判成 502。
|
||||
let canvas_base_url =
|
||||
spawn_mock_external_canvas_api_server_with_capture(200, Some(request_sender));
|
||||
let _platform_session = crate::platform_session::install_test_platform_session(
|
||||
"reference-guard-user",
|
||||
"editor-runtime-key",
|
||||
&canvas_base_url,
|
||||
);
|
||||
fs::create_dir_all(&config_dir).expect("create reference guard config dir");
|
||||
fs::write(
|
||||
config_dir.join(GAME_CREATOR_CONFIG_FILE_NAME),
|
||||
serde_json::json!({
|
||||
"editorApi": { "baseUrl": canvas_base_url, "apiKey": "editor-runtime-key" }
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("write reference guard config");
|
||||
let _config_guard = use_test_runtime_config_dir(config_dir.clone());
|
||||
init_local_game_project_at(&root, "reference-guard", "参考素材门禁")
|
||||
.expect("init reference guard project");
|
||||
write_project_permission_policy_at(
|
||||
&root,
|
||||
ProjectPermissionPolicy {
|
||||
denied_commands: Vec::new(),
|
||||
confirm_commands: Vec::new(),
|
||||
agent_policies: BTreeMap::new(),
|
||||
},
|
||||
)
|
||||
.expect("allow reference guard generation");
|
||||
let options =
|
||||
|asset_kind: &str, reference_asset_ids: Vec<String>| PlatformArtAssetGenerationOptions {
|
||||
output_path: Some(format!("assets/reference-guard-{asset_kind}.png")),
|
||||
asset_kind: asset_kind.to_string(),
|
||||
asset_label: "参考素材门禁".to_string(),
|
||||
reference_asset_ids,
|
||||
..PlatformArtAssetGenerationOptions::default()
|
||||
};
|
||||
async fn reject(root: &Path, options: PlatformArtAssetGenerationOptions) -> String {
|
||||
request_platform_art_asset_with_options_for_test(root, "参考素材门禁", &options)
|
||||
.await
|
||||
.expect_err("reference selection must fail closed")
|
||||
}
|
||||
let canvas_source = || GameCreationAppAssetSource {
|
||||
kind: GameCreationAppAssetSourceKind::Canvas,
|
||||
canvas_project_id: None,
|
||||
resource_id: None,
|
||||
asset_object_id: None,
|
||||
task_id: None,
|
||||
prompt: None,
|
||||
model: None,
|
||||
generation_route: None,
|
||||
generation_kind: None,
|
||||
reference_resource_ids: Vec::new(),
|
||||
};
|
||||
|
||||
// 只接受单规范引用的图集不接受用户参考,且必须明确拒绝而不是静默丢弃。
|
||||
let error = reject(
|
||||
&root,
|
||||
options("art-spritesheet", vec!["user-asset".to_string()]),
|
||||
)
|
||||
.await;
|
||||
assert!(error.contains("透明美术图集只接受规范图引用"), "{error}");
|
||||
|
||||
// 路径、URL 等形状不是素材身份。
|
||||
for rejected in [
|
||||
"assets/hero.png",
|
||||
"..\\hero.png",
|
||||
"https://example.com/hero.png",
|
||||
] {
|
||||
let error = reject(&root, options("icon-spec", vec![rejected.to_string()])).await;
|
||||
assert!(error.contains("不接受路径或远端资源 ID"), "{error}");
|
||||
}
|
||||
|
||||
// 未登记的 id 不能冒充当前项目素材(历史账号的远端资源 ID 也不在此列)。
|
||||
let error = reject(
|
||||
&root,
|
||||
options(
|
||||
"icon-spec",
|
||||
vec!["editor-resource-from-older-account".to_string()],
|
||||
),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
error.contains("参考素材不在当前项目已登记清单中"),
|
||||
"{error}"
|
||||
);
|
||||
|
||||
// 非图片素材不能当参考。
|
||||
fs::create_dir_all(root.join("assets")).expect("create reference guard asset dir");
|
||||
fs::write(root.join("assets/document.json"), b"{}").expect("write non image reference fixture");
|
||||
register_local_asset_at(
|
||||
&root,
|
||||
"assets/document.json",
|
||||
"image",
|
||||
"application/json",
|
||||
"canvas",
|
||||
canvas_source(),
|
||||
)
|
||||
.expect("register non image reference fixture");
|
||||
let error = reject(
|
||||
&root,
|
||||
options(
|
||||
"icon-spec",
|
||||
vec![manifest_asset_id(&root, "assets/document.json")],
|
||||
),
|
||||
)
|
||||
.await;
|
||||
assert!(error.contains("参考素材必须是图片"), "{error}");
|
||||
|
||||
// 已登记但本地文件缺失的素材不能被引用。
|
||||
register_canvas_visual_asset_fixture(&root, "assets/missing-reference.png", "image");
|
||||
let missing_asset_id = manifest_asset_id(&root, "assets/missing-reference.png");
|
||||
fs::remove_file(root.join("assets/missing-reference.png"))
|
||||
.expect("remove missing reference fixture file");
|
||||
let error = reject(&root, options("icon-spec", vec![missing_asset_id])).await;
|
||||
assert!(error.contains("不存在;请重新登记后再引用"), "{error}");
|
||||
|
||||
// 超过总上限:无规范前置最多 5 张。
|
||||
let error = reject(
|
||||
&root,
|
||||
options(
|
||||
"icon-spec",
|
||||
(0..6).map(|index| format!("reference-{index}")).collect(),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
assert!(error.contains("普通图片生成最多 5 张参考素材"), "{error}");
|
||||
|
||||
// SVG 与坏图同样必须在提交前失败:本次不做 SVG 转换,也不允许把「已登记」当成可解码。
|
||||
register_canvas_visual_asset_fixture(&root, "assets/plain-reference.png", "image");
|
||||
let plain_reference_asset_id = manifest_asset_id(&root, "assets/plain-reference.png");
|
||||
fs::write(
|
||||
root.join("assets/vector-reference.svg"),
|
||||
b"<svg xmlns=\"http://www.w3.org/2000/svg\"/>",
|
||||
)
|
||||
.expect("write svg reference fixture");
|
||||
register_local_asset_at(
|
||||
&root,
|
||||
"assets/vector-reference.svg",
|
||||
"image",
|
||||
"image/svg+xml",
|
||||
"canvas",
|
||||
canvas_source(),
|
||||
)
|
||||
.expect("register svg reference fixture");
|
||||
let vector_asset_id = manifest_asset_id(&root, "assets/vector-reference.svg");
|
||||
let error = reject(
|
||||
&root,
|
||||
options(
|
||||
"icon-spec",
|
||||
vec![plain_reference_asset_id.clone(), vector_asset_id],
|
||||
),
|
||||
)
|
||||
.await;
|
||||
assert!(error.contains("不支持 SVG 等矢量格式"), "{error}");
|
||||
|
||||
// 声明成位图、实际是矢量扩展名的素材同样要按矢量拒绝。
|
||||
fs::write(
|
||||
root.join("assets/mislabeled-reference.svg"),
|
||||
valid_test_png_bytes(),
|
||||
)
|
||||
.expect("write mislabeled svg reference fixture");
|
||||
register_local_asset_at(
|
||||
&root,
|
||||
"assets/mislabeled-reference.svg",
|
||||
"image",
|
||||
"image/png",
|
||||
"canvas",
|
||||
canvas_source(),
|
||||
)
|
||||
.expect("register mislabeled svg reference fixture");
|
||||
let mislabeled_asset_id = manifest_asset_id(&root, "assets/mislabeled-reference.svg");
|
||||
let error = reject(
|
||||
&root,
|
||||
options(
|
||||
"icon-spec",
|
||||
vec![plain_reference_asset_id.clone(), mislabeled_asset_id],
|
||||
),
|
||||
)
|
||||
.await;
|
||||
assert!(error.contains("不支持 SVG 等矢量格式"), "{error}");
|
||||
|
||||
fs::write(root.join("assets/broken-reference.png"), b"not-a-png")
|
||||
.expect("write broken reference fixture");
|
||||
register_local_asset_at(
|
||||
&root,
|
||||
"assets/broken-reference.png",
|
||||
"image",
|
||||
"image/png",
|
||||
"canvas",
|
||||
canvas_source(),
|
||||
)
|
||||
.expect("register broken reference fixture");
|
||||
let broken_asset_id = manifest_asset_id(&root, "assets/broken-reference.png");
|
||||
let error = reject(
|
||||
&root,
|
||||
options(
|
||||
"icon-spec",
|
||||
vec![plain_reference_asset_id.clone(), broken_asset_id],
|
||||
),
|
||||
)
|
||||
.await;
|
||||
assert!(error.contains("不是可解析图片"), "{error}");
|
||||
|
||||
// 以上全部在提交前失败:没有生成 POST,也没有任何参考上传凭证被签发。
|
||||
// 合格参考故意不做 binding:旧路径会先为它签发凭证,所以这条断言对半完成上传有实际约束。
|
||||
while let Ok(request) = request_receiver.recv_timeout(Duration::from_millis(200)) {
|
||||
assert!(!is_image_generation_request(&request), "{request}");
|
||||
assert!(!is_reference_upload_ticket_request(&request), "{request}");
|
||||
}
|
||||
|
||||
// 反证:同一张合格参考单独提交时确实会去签发上传凭证,说明上面的「零上传」不是空断言。
|
||||
let differential = request_platform_art_asset_with_options_for_test(
|
||||
&root,
|
||||
"参考素材门禁",
|
||||
&options("icon-spec", vec![plain_reference_asset_id.clone()]),
|
||||
)
|
||||
.await;
|
||||
let mut upload_ticket_attempted = false;
|
||||
while let Ok(request) = request_receiver.recv_timeout(Duration::from_millis(300)) {
|
||||
if is_reference_upload_ticket_request(&request) {
|
||||
upload_ticket_attempted = true;
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
upload_ticket_attempted,
|
||||
"the same single reference must attempt an upload ticket: {differential:?}"
|
||||
);
|
||||
|
||||
fs::remove_dir_all(root).ok();
|
||||
fs::remove_dir_all(config_dir).ok();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn reference_upload_permission_gate_fails_closed_before_any_upload() {
|
||||
let root = unique_project_path();
|
||||
let config_dir = unique_project_path();
|
||||
let (request_sender, request_receiver) = mpsc::channel();
|
||||
let canvas_base_url = spawn_mock_external_canvas_generation_api_server(Some(request_sender));
|
||||
let _platform_session = crate::platform_session::install_test_platform_session(
|
||||
"reference-permission-user",
|
||||
"editor-runtime-key",
|
||||
&canvas_base_url,
|
||||
);
|
||||
fs::create_dir_all(&config_dir).expect("create reference permission config dir");
|
||||
fs::write(
|
||||
config_dir.join(GAME_CREATOR_CONFIG_FILE_NAME),
|
||||
serde_json::json!({
|
||||
"editorApi": { "baseUrl": canvas_base_url, "apiKey": "editor-runtime-key" }
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("write reference permission config");
|
||||
let _config_guard = use_test_runtime_config_dir(config_dir.clone());
|
||||
init_local_game_project_at(&root, "reference-permission", "参考上传门禁")
|
||||
.expect("init reference permission project");
|
||||
// 只拒绝 asset.upload:参考素材要上传到平台账号,这个门禁必须在本地失败关闭。
|
||||
write_project_permission_policy_at(
|
||||
&root,
|
||||
ProjectPermissionPolicy {
|
||||
denied_commands: vec!["asset.upload".to_string()],
|
||||
confirm_commands: Vec::new(),
|
||||
agent_policies: BTreeMap::new(),
|
||||
},
|
||||
)
|
||||
.expect("deny reference upload");
|
||||
register_canvas_visual_asset_fixture(&root, "assets/plain-reference.png", "image");
|
||||
let plain_reference_asset_id = manifest_asset_id(&root, "assets/plain-reference.png");
|
||||
|
||||
let error = request_platform_art_asset_with_options_for_test(
|
||||
&root,
|
||||
"参考素材门禁",
|
||||
&PlatformArtAssetGenerationOptions {
|
||||
output_path: Some("assets/reference-permission.png".to_string()),
|
||||
asset_kind: "icon-spec".to_string(),
|
||||
asset_label: "参考素材门禁".to_string(),
|
||||
reference_asset_ids: vec![plain_reference_asset_id],
|
||||
..PlatformArtAssetGenerationOptions::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect_err("a denied asset.upload must fail closed");
|
||||
assert!(
|
||||
error.contains("项目权限策略拒绝执行:asset.upload"),
|
||||
"{error}"
|
||||
);
|
||||
|
||||
while let Ok(request) = request_receiver.recv_timeout(Duration::from_millis(200)) {
|
||||
assert!(!is_reference_upload_ticket_request(&request), "{request}");
|
||||
assert!(!is_image_generation_request(&request), "{request}");
|
||||
}
|
||||
|
||||
fs::remove_dir_all(root).ok();
|
||||
fs::remove_dir_all(config_dir).ok();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
Reference in New Issue
Block a user