修复AGC Native测试夹具
Project CI / Repository checks (push) Successful in 2m46s
Project CI / Frontend tests (push) Successful in 3m10s
Project CI / Backend tests (push) Successful in 4m21s
Project CI / Native shell tests (push) Failing after 14m44s

固定普通 JSON Provider mock 为非流式

为平台账号画布与视觉门禁测试安装隔离会话

同步现役路由、默认配置与提示词断言
This commit is contained in:
2026-08-19 14:42:41 +08:00
parent 9fe800da80
commit 925aefd934
11 changed files with 151 additions and 18 deletions
@@ -7,7 +7,13 @@ pub(crate) async fn request_llm_game_draft_with_client(
short_memory: &str,
long_memory: &str,
) -> Result<LlmGameDraft, String> {
let llm = GameCreatorLlmConfig::default();
// This helper exercises the one-shot OpenAI-compatible response fixture.
// Streaming has its own response-stream coverage; keeping this explicit
// prevents the production default from making the mock parse JSON as SSE.
let llm = GameCreatorLlmConfig {
stream: false,
..GameCreatorLlmConfig::default()
};
request_generator_game_draft_with_client(
client,
&llm,
@@ -1217,6 +1217,11 @@ async fn visual_specialists_reject_overriding_their_fixed_image_contract() {
#[test]
fn design_foundation_rejects_scene_image_stale_run_and_stale_sha_visual_proofs() {
let _platform_session = crate::platform_session::install_test_platform_session(
"visual-semantic-gate-test-user",
"visual-semantic-gate-test-key",
"https://dev.genarrative.world",
);
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"visual-semantic-gate-test-key"}}"#.to_string(),
);
@@ -1608,6 +1613,11 @@ async fn canvas_replacement_rejects_parent_run_that_terminates_during_external_r
request_sender,
generation_release_receiver,
);
let _platform_session = crate::platform_session::install_test_platform_session(
"canvas-replacement-user",
"editor-runtime-key",
&canvas_base_url,
);
init_local_game_project_at(&root, "project-canvas-repair-atomic", "月光厨房")
.expect("project init");
register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec");
@@ -1800,7 +1810,7 @@ async fn canvas_replacement_rejects_parent_run_that_terminates_during_external_r
let captured = request_receiver
.recv_timeout(Duration::from_secs(15))
.expect("canvas request before generation response");
if captured.starts_with("POST /api/external/v1/editor/icon-spritesheets/generations ") {
if captured.starts_with("POST /api/editor/icon-spritesheets/generations ") {
saw_generation_request = true;
break;
}
@@ -2231,6 +2241,11 @@ fn gui_and_cli_autonomous_runs_still_allow_publish_delegates() {
#[test]
fn visual_specialist_delegations_require_image_artifacts_but_read_only_work_allows_none() {
let _platform_session = crate::platform_session::install_test_platform_session(
"visual-delegation-contract-user",
"visual-delegation-contract-key",
"https://dev.genarrative.world",
);
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"visual-delegation-contract-key"}}"#.to_string(),
);
@@ -38,6 +38,11 @@ fn project_supervisor_runtime_id_is_normalized_and_collected() {
#[test]
fn visual_specialist_prompts_require_real_registered_image_deliveries() {
let _platform_session = crate::platform_session::install_test_platform_session(
"visual-prompt-test-user",
"visual-prompt-test-key",
"https://dev.genarrative.world",
);
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"visual-prompt-test-key"}}"#.to_string(),
);
@@ -154,6 +159,11 @@ fn visual_prompts_degrade_to_text_contracts_without_editor_api_key() {
#[tokio::test]
async fn project_supervisor_prompts_are_total_control_and_reject_isolated_template() {
let planning_prompt = {
let _platform_session = crate::platform_session::install_test_platform_session(
"supervisor-prompt-test-user",
"supervisor-prompt-test-key",
"https://dev.genarrative.world",
);
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"supervisor-prompt-test-key"}}"#.to_string(),
);
@@ -307,7 +317,7 @@ fn project_supervisor_llm_config_prefers_specific_patch_and_falls_back_to_legacy
assert_eq!(specialist.api_key, "global-key");
assert_eq!(specialist.base_url, "https://global.example.test/v1");
assert_eq!(specialist.model, "global-model");
assert!(!specialist.stream);
assert!(specialist.stream);
}
#[test]
@@ -531,7 +531,7 @@ fn runtime_config_read_returns_defaults_when_file_is_missing() {
result.config.llm.reasoning_effort,
DEFAULT_GAME_CREATOR_LLM_REASONING_EFFORT
);
assert!(!result.config.llm.stream);
assert!(result.config.llm.stream);
assert!(!result.config.llm.web_search_enabled);
assert_eq!(
result.config.llm.context_window_tokens,
@@ -1230,6 +1230,14 @@ pub(crate) fn write_test_local_config(content: String) -> TestConfigGuard {
config
.entry("agentMode")
.or_insert_with(|| serde_json::json!(GAME_CREATOR_AGENT_MODE_PROVIDER));
let llm = config.entry("llm").or_insert_with(|| serde_json::json!({}));
if !llm.is_object() {
*llm = serde_json::json!({});
}
llm.as_object_mut()
.expect("test config LLM value must be an object")
.entry("stream")
.or_insert_with(|| serde_json::json!(false));
}
serde_json::to_vec_pretty(&config).expect("serialize explicit Provider test config")
}
@@ -1243,6 +1251,27 @@ pub(crate) fn write_test_local_config(content: String) -> TestConfigGuard {
}
}
#[test]
fn test_local_config_defaults_mock_provider_to_non_streaming_and_preserves_explicit_stream() {
let guard =
write_test_local_config(r#"{"agentMode":"codex_cli","llm":{"stream":true}}"#.to_string());
let explicit_stream = serde_json::from_slice::<serde_json::Value>(
&fs::read(test_local_config_path()).expect("read explicit streaming test config"),
)
.expect("parse explicit streaming test config");
assert_eq!(explicit_stream["agentMode"], "codex_cli");
assert_eq!(explicit_stream["llm"]["stream"], true);
drop(guard);
let _guard = write_test_local_config(r#"{"agentLlm":{}}"#.to_string());
let defaulted = serde_json::from_slice::<serde_json::Value>(
&fs::read(test_local_config_path()).expect("read defaulted mock provider test config"),
)
.expect("parse defaulted mock provider test config");
assert_eq!(defaulted["agentMode"], GAME_CREATOR_AGENT_MODE_PROVIDER);
assert_eq!(defaulted["llm"]["stream"], false);
}
fn use_test_runtime_config_dir(path: PathBuf) -> TestRuntimeConfigDirGuard {
let lock = TEST_CONFIG_LOCK
.lock()
@@ -3103,17 +3132,23 @@ fn spawn_mock_external_canvas_api_server_with_capture_and_generation_gate(
let normalized_request = request.to_ascii_lowercase();
let (status, content_type, body) = if request
.starts_with("GET /api/external/v1/editor/projects ")
|| request.starts_with("GET /api/editor/projects ")
{
assert!(normalized_request.contains("authorization: bearer "));
("200 OK", "application/json", projects_body.as_bytes().to_vec())
} else if request.starts_with("GET /api/external/v1/editor/assets/library ") {
} else if request.starts_with("GET /api/external/v1/editor/assets/library ")
|| request.starts_with("GET /api/editor/assets/library ")
{
assert!(normalized_request.contains("authorization: bearer "));
("200 OK", "application/json", library_body.as_bytes().to_vec())
} else if request.starts_with("GET /api/external/v1/editor/projects/canvas-project-1 ")
|| request.starts_with("GET /api/editor/projects/canvas-project-1 ")
{
assert!(normalized_request.contains("authorization: bearer "));
("200 OK", "application/json", project_body.as_bytes().to_vec())
} else if request.starts_with("POST /api/external/v1/editor/images/generations ") {
} else if request.starts_with("POST /api/external/v1/editor/images/generations ")
|| request.starts_with("POST /api/editor/images/generations ")
{
assert!(normalized_request.contains("authorization: bearer "));
let idempotency_key = request
.lines()
@@ -3134,6 +3169,7 @@ fn spawn_mock_external_canvas_api_server_with_capture_and_generation_gate(
)
} else if request
.starts_with("POST /api/external/v1/editor/icon-spritesheets/generations ")
|| request.starts_with("POST /api/editor/icon-spritesheets/generations ")
{
assert!(normalized_request.contains("authorization: bearer "));
let idempotency_key = request
@@ -3155,6 +3191,8 @@ fn spawn_mock_external_canvas_api_server_with_capture_and_generation_gate(
)
} else if request.starts_with(
"GET /api/external/v1/generations/task-external-fixture-1 ",
) || request.starts_with(
"GET /api/runtime/external-generation/jobs/task-external-fixture-1 ",
) {
assert!(normalized_request.contains("authorization: bearer "));
if let Some(gate) = generation_response_gate.take() {
@@ -3196,11 +3234,15 @@ fn spawn_mock_external_canvas_api_server_with_capture_and_generation_gate(
)
} else if request.starts_with(
"GET /api/external/v1/assets/read-url?objectKey=generated%2Fcanvas%2Fhero.png ",
) || request.starts_with(
"GET /api/assets/read-url?objectKey=generated%2Fcanvas%2Fhero.png ",
) {
assert!(normalized_request.contains("authorization: bearer "));
("200 OK", "application/json", read_body.as_bytes().to_vec())
} else if request.starts_with(
"GET /api/external/v1/assets/read-url?objectKey=generated%2Fcanvas%2Fspritesheet.png ",
) || request.starts_with(
"GET /api/assets/read-url?objectKey=generated%2Fcanvas%2Fspritesheet.png ",
) {
assert!(normalized_request.contains("authorization: bearer "));
(
@@ -3211,6 +3253,8 @@ fn spawn_mock_external_canvas_api_server_with_capture_and_generation_gate(
} else if let Some(index) = (0..4).find(|index| {
request.starts_with(&format!(
"GET /api/external/v1/assets/read-url?objectKey=generated%2Fcanvas%2Fspritesheet-slice-{index}.png "
)) || request.starts_with(&format!(
"GET /api/assets/read-url?objectKey=generated%2Fcanvas%2Fspritesheet-slice-{index}.png "
))
}) {
assert!(normalized_request.contains("authorization: bearer "));
@@ -3285,7 +3329,10 @@ fn spawn_mock_external_canvas_generation_failure_server() -> String {
.contains("authorization: bearer "));
let (status, body) = match index {
0 => {
assert!(request.starts_with("GET /api/external/v1/editor/projects "));
assert!(
request.starts_with("GET /api/external/v1/editor/projects ")
|| request.starts_with("GET /api/editor/projects ")
);
(
"200 OK",
serde_json::json!({
@@ -3298,7 +3345,10 @@ fn spawn_mock_external_canvas_generation_failure_server() -> String {
)
}
1 => {
assert!(request.starts_with("GET /api/external/v1/editor/assets/library "));
assert!(
request.starts_with("GET /api/external/v1/editor/assets/library ")
|| request.starts_with("GET /api/editor/assets/library ")
);
(
"200 OK",
serde_json::json!({
@@ -3311,7 +3361,10 @@ fn spawn_mock_external_canvas_generation_failure_server() -> String {
)
}
_ => {
assert!(request.starts_with("POST /api/external/v1/editor/images/generations "));
assert!(
request.starts_with("POST /api/external/v1/editor/images/generations ")
|| request.starts_with("POST /api/editor/images/generations ")
);
(
"500 Internal Server Error",
"{\"error\":\"generation failed\"}".to_string(),
@@ -651,6 +651,11 @@ async fn background_agent_runtime_can_generate_platform_art_asset() {
let config_dir = unique_project_path();
let (canvas_sender, canvas_receiver) = mpsc::channel();
let canvas_base_url = spawn_mock_external_canvas_generation_api_server(Some(canvas_sender));
let _platform_session = crate::platform_session::install_test_platform_session(
"background-art-user",
"editor-runtime-key",
&canvas_base_url,
);
init_local_game_project_at(&root, "project-1", "月光厨房").expect("project init");
register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec");
write_project_permission_policy_at(
@@ -688,6 +693,7 @@ async fn background_agent_runtime_can_generate_platform_art_asset() {
config_dir.join(GAME_CREATOR_CONFIG_FILE_NAME),
serde_json::json!({
"agentMode": "provider",
"llm": { "stream": false },
"editorApi": {
"baseUrl": canvas_base_url,
"apiKey": "editor-runtime-key"
@@ -835,14 +841,12 @@ async fn background_agent_runtime_can_generate_platform_art_asset() {
.collect::<Vec<_>>();
let generation_request = canvas_requests
.iter()
.find(|request| {
request.starts_with("POST /api/external/v1/editor/icon-spritesheets/generations ")
})
.find(|request| request.starts_with("POST /api/editor/icon-spritesheets/generations "))
.expect("canvas generation request");
assert_eq!(
canvas_requests
.iter()
.filter(|request| request.starts_with("POST /api/external/v1/editor/"))
.filter(|request| request.starts_with("POST /api/editor/"))
.count(),
1,
"queued/running polling must not submit generation again"
@@ -850,7 +854,7 @@ async fn background_agent_runtime_can_generate_platform_art_asset() {
assert_eq!(
canvas_requests
.iter()
.filter(|request| request.starts_with("GET /api/external/v1/generations/"))
.filter(|request| request.starts_with("GET /api/runtime/external-generation/jobs/"))
.count(),
3,
"fixture should exercise queued, running, and completed states"
@@ -889,6 +893,11 @@ async fn canonical_art_spec_and_ui_requests_use_the_shared_reference_chain() {
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(
"canonical-art-user",
"editor-runtime-key",
&canvas_base_url,
);
fs::create_dir_all(config_dir).expect("create runtime config dir");
fs::write(
config_dir.join(GAME_CREATOR_CONFIG_FILE_NAME),
@@ -938,7 +947,7 @@ async fn canonical_art_spec_and_ui_requests_use_the_shared_reference_chain() {
},
)
.await;
assert!(spec_request.starts_with("POST /api/external/v1/editor/images/generations "));
assert!(spec_request.starts_with("POST /api/editor/images/generations "));
assert!(spec_request.contains(r#""kind":"spec""#));
assert!(spec_request.contains(r#""assetKind":"icon-spec""#));
assert!(spec_request.contains(r#""referenceImageSrcs":[]"#));
@@ -958,7 +967,7 @@ async fn canonical_art_spec_and_ui_requests_use_the_shared_reference_chain() {
},
)
.await;
assert!(ui_request.starts_with("POST /api/external/v1/editor/images/generations "));
assert!(ui_request.starts_with("POST /api/editor/images/generations "));
assert!(ui_request.contains(r#""kind":"ui-design""#));
assert!(ui_request.contains(r#""referenceImageSrcs":["resource-icon-spec"]"#));
@@ -977,6 +986,11 @@ async fn platform_art_external_request_does_not_hold_project_lock_or_overwrite_m
request_sender,
generation_release_receiver,
);
let _platform_session = crate::platform_session::install_test_platform_session(
"project-lock-art-user",
"editor-runtime-key",
&canvas_base_url,
);
init_local_game_project_at(&root, "project-canvas-request-lock", "月光厨房")
.expect("project init");
register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec");
@@ -1025,7 +1039,7 @@ async fn platform_art_external_request_does_not_hold_project_lock_or_overwrite_m
let captured = request_receiver
.recv_timeout(Duration::from_secs(15))
.expect("canvas request before generation response");
if captured.starts_with("POST /api/external/v1/editor/icon-spritesheets/generations ") {
if captured.starts_with("POST /api/editor/icon-spritesheets/generations ") {
saw_generation_request = true;
break;
}
@@ -1871,6 +1885,11 @@ fn import_canvas_export_zip_copies_files_and_registers_assets() {
async fn sync_canvas_project_assets_downloads_external_resources() {
let root = unique_project_path();
let base_url = spawn_mock_external_canvas_api_server();
let _platform_session = crate::platform_session::install_test_platform_session(
"canvas-sync-user",
"test-editor-api-key",
&base_url,
);
let result = sync_canvas_project_assets_at(
&root,
@@ -1918,6 +1937,11 @@ async fn generate_platform_art_asset_downloads_and_registers_external_image() {
let root = unique_project_path();
let config_dir = unique_project_path();
let base_url = spawn_mock_external_canvas_generation_api_server(None);
let _platform_session = crate::platform_session::install_test_platform_session(
"platform-art-user",
"editor-unit-key",
&base_url,
);
init_local_game_project_at(&root, "project-platform-art", "未命名游戏原型")
.expect("project init");
fs::create_dir_all(&config_dir).expect("runtime config dir");
@@ -1998,6 +2022,11 @@ async fn platform_art_generation_step_falls_back_without_leaking_editor_key() {
.expect("project init");
let config_dir = unique_project_path();
let base_url = spawn_mock_external_canvas_generation_failure_server();
let _platform_session = crate::platform_session::install_test_platform_session(
"platform-art-fallback-user",
"editor-fallback-secret",
&base_url,
);
fs::create_dir_all(&config_dir).expect("runtime config dir");
fs::write(
config_dir.join(GAME_CREATOR_CONFIG_FILE_NAME),
@@ -6729,6 +6729,7 @@ async fn agent_loop_writes_spec_findings_and_retries_generator() {
api_key: "test-key".to_string(),
base_url,
model: "mock-game-model".to_string(),
stream: false,
..GameCreatorLlmConfig::default()
},
..GameCreatorAppConfig::default()
@@ -982,6 +982,11 @@ async fn provider_retry_final_reply_thinking_only_response_retries_before_handof
#[test]
fn visual_specialist_finalization_requires_existing_registered_canvas_image() {
let _platform_session = crate::platform_session::install_test_platform_session(
"visual-finalization-test-user",
"visual-finalization-test-key",
"https://dev.genarrative.world",
);
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"visual-finalization-test-key"}}"#.to_string(),
);
@@ -2184,6 +2184,11 @@ fn static_smoke_failure_receipt_round_trips_owner_diagnostic_from_agent_db() {
#[test]
fn seed_refresh_downgrades_completed_visual_tasks_when_registered_file_is_missing() {
let _platform_session = crate::platform_session::install_test_platform_session(
"visual-seed-refresh-test-user",
"visual-seed-refresh-test-key",
"https://dev.genarrative.world",
);
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"visual-seed-refresh-test-key"}}"#.to_string(),
);
@@ -467,7 +467,11 @@ fn autonomous_visual_gate_degrades_to_text_without_key_and_requires_images_with_
Some(&GameCreationAppTaskStatus::Completed)
);
write_autonomous_editor_api_config_for_test(&config_dir, "editor-runtime-test-key");
let _platform_session = crate::platform_session::install_test_platform_session(
"autonomous-visual-gate-test-user",
"autonomous-visual-gate-test-key",
"https://dev.genarrative.world",
);
let with_key_missing_images = autonomous_seed_task_statuses_for_test(&root);
assert_eq!(
with_key_missing_images.get("art-director"),
@@ -3851,6 +3851,11 @@ async fn background_task_recovers_when_assistant_message_cannot_persist() {
#[tokio::test]
async fn task_update_requires_registered_visual_asset_before_completion() {
let _platform_session = crate::platform_session::install_test_platform_session(
"visual-task-update-test-user",
"visual-task-update-test-key",
"https://dev.genarrative.world",
);
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"visual-task-update-test-key"}}"#.to_string(),
);