Merge remote-tracking branch 'origin/master' into codex/ddd
# Conflicts: # docs/technical/README.md # docs/technical/RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md # docs/technical/SPACETIMEDB_TABLE_CATALOG.md # scripts/generate-spacetime-bindings.mjs # server-rs/crates/api-server/src/app.rs # server-rs/crates/api-server/src/assets.rs # server-rs/crates/api-server/src/big_fish.rs # server-rs/crates/api-server/src/custom_world_ai.rs # server-rs/crates/api-server/src/llm.rs # server-rs/crates/api-server/src/main.rs # server-rs/crates/api-server/src/puzzle.rs # server-rs/crates/api-server/src/runtime_profile.rs # server-rs/crates/api-server/src/runtime_story/compat/ai.rs # server-rs/crates/api-server/src/runtime_story/compat/npc_actions.rs # server-rs/crates/api-server/src/runtime_story/compat/presentation.rs # server-rs/crates/api-server/src/runtime_story/compat/tests.rs # server-rs/crates/api-server/src/state.rs # server-rs/crates/module-auth/src/lib.rs # server-rs/crates/module-big-fish/src/lib.rs # server-rs/crates/module-custom-world/src/lib.rs # server-rs/crates/module-puzzle/src/lib.rs # server-rs/crates/module-runtime/src/lib.rs # server-rs/crates/spacetime-client/src/big_fish.rs # server-rs/crates/spacetime-client/src/lib.rs # server-rs/crates/spacetime-client/src/mapper.rs # server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/big_fish_runtime_run_type.rs # server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_auth_store_snapshot_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/mod.rs # server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_auth_store_snapshot_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs # server-rs/crates/spacetime-module/src/auth/procedures.rs # server-rs/crates/spacetime-module/src/custom_world/mod.rs # server-rs/crates/spacetime-module/src/lib.rs # server-rs/crates/spacetime-module/src/migration.rs # server-rs/crates/spacetime-module/src/puzzle.rs # server-rs/crates/spacetime-module/src/runtime/profile.rs # src/components/platform-entry/PlatformEntryFlowShellImpl.tsx # src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx # src/services/aiService.ts # src/services/puzzle-runtime/puzzleRuntimeClient.ts
This commit is contained in:
@@ -84,6 +84,34 @@ pub struct CustomWorldGalleryDetailByCodeInput {
|
||||
pub public_work_code: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CustomWorldProfileRemixInput {
|
||||
pub source_owner_user_id: String,
|
||||
pub source_profile_id: String,
|
||||
pub target_owner_user_id: String,
|
||||
pub target_profile_id: String,
|
||||
pub author_display_name: String,
|
||||
pub remixed_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CustomWorldProfilePlayRecordInput {
|
||||
pub owner_user_id: String,
|
||||
pub profile_id: String,
|
||||
pub played_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CustomWorldProfileLikeRecordInput {
|
||||
pub owner_user_id: String,
|
||||
pub profile_id: String,
|
||||
pub user_id: String,
|
||||
pub liked_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CustomWorldAgentSessionCreateInput {
|
||||
|
||||
@@ -155,6 +155,9 @@ pub struct CustomWorldProfileSnapshot {
|
||||
pub profile_payload_json: String,
|
||||
pub playable_npc_count: u32,
|
||||
pub landmark_count: u32,
|
||||
pub play_count: u32,
|
||||
pub remix_count: u32,
|
||||
pub like_count: u32,
|
||||
pub author_display_name: String,
|
||||
pub published_at_micros: Option<i64>,
|
||||
pub deleted_at_micros: Option<i64>,
|
||||
@@ -177,6 +180,10 @@ pub struct CustomWorldGalleryEntrySnapshot {
|
||||
pub theme_mode: CustomWorldThemeMode,
|
||||
pub playable_npc_count: u32,
|
||||
pub landmark_count: u32,
|
||||
pub play_count: u32,
|
||||
pub remix_count: u32,
|
||||
pub like_count: u32,
|
||||
pub recent_play_count_7d: u32,
|
||||
pub published_at_micros: i64,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
@@ -9,329 +9,3 @@ pub use commands::*;
|
||||
pub use domain::*;
|
||||
pub use errors::*;
|
||||
pub use events::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::Value;
|
||||
|
||||
#[test]
|
||||
fn profile_validation_rejects_blank_owner() {
|
||||
let error = validate_custom_world_profile_fields(
|
||||
"cwprof_001",
|
||||
" ",
|
||||
"裂潮边城",
|
||||
"{\"id\":\"cwprof_001\"}",
|
||||
)
|
||||
.expect_err("blank owner should fail");
|
||||
|
||||
assert_eq!(error, CustomWorldFieldError::MissingOwnerUserId);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_session_validation_rejects_progress_over_hundred() {
|
||||
let error = validate_custom_world_agent_session_fields(
|
||||
"custom-world-agent-session-001",
|
||||
"user_001",
|
||||
"{}",
|
||||
"{}",
|
||||
"[]",
|
||||
"{}",
|
||||
101,
|
||||
)
|
||||
.expect_err("progress greater than 100 should fail");
|
||||
|
||||
assert_eq!(error, CustomWorldFieldError::InvalidProgressPercent);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_string_values_match_current_contract() {
|
||||
assert_eq!(
|
||||
RpgAgentOperationType::PublishWorld.as_str(),
|
||||
"publish_world"
|
||||
);
|
||||
assert_eq!(RpgAgentStage::ReadyToPublish.as_str(), "ready_to_publish");
|
||||
assert_eq!(RpgAgentMessageRole::Assistant.as_str(), "assistant");
|
||||
assert_eq!(RpgAgentMessageKind::ActionResult.as_str(), "action_result");
|
||||
assert_eq!(
|
||||
RpgAgentDraftCardKind::SceneChapter.as_str(),
|
||||
"scene_chapter"
|
||||
);
|
||||
assert_eq!(
|
||||
CustomWorldRoleAssetStatus::VisualReady.as_str(),
|
||||
"visual_ready"
|
||||
);
|
||||
assert_eq!(CustomWorldThemeMode::Rift.as_str(), "rift");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_session_create_input_validates_required_json_shapes() {
|
||||
let input = CustomWorldAgentSessionCreateInput {
|
||||
session_id: "custom-world-agent-session-001".to_string(),
|
||||
owner_user_id: "user_001".to_string(),
|
||||
seed_text: "".to_string(),
|
||||
welcome_message_id: "message-001".to_string(),
|
||||
welcome_message_text: "你好!我是你的世界设定助手。".to_string(),
|
||||
anchor_content_json: empty_agent_anchor_content_json(),
|
||||
creator_intent_json: Some(empty_json_object()),
|
||||
creator_intent_readiness_json: empty_agent_creator_intent_readiness_json(),
|
||||
anchor_pack_json: Some(empty_json_object()),
|
||||
lock_state_json: Some(empty_json_object()),
|
||||
draft_profile_json: Some(empty_json_object()),
|
||||
pending_clarifications_json: empty_json_array(),
|
||||
suggested_actions_json: empty_json_array(),
|
||||
recommended_replies_json: empty_json_array(),
|
||||
quality_findings_json: empty_json_array(),
|
||||
asset_coverage_json: empty_agent_asset_coverage_json(),
|
||||
checkpoints_json: empty_json_array(),
|
||||
created_at_micros: 1,
|
||||
};
|
||||
|
||||
validate_custom_world_agent_session_create_input(&input)
|
||||
.expect("valid skeleton input should pass");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_upsert_input_requires_author_display_name() {
|
||||
let error = validate_custom_world_profile_upsert_input(&CustomWorldProfileUpsertInput {
|
||||
profile_id: "cwprof_001".to_string(),
|
||||
owner_user_id: "user_001".to_string(),
|
||||
public_work_code: None,
|
||||
author_public_user_code: None,
|
||||
source_agent_session_id: None,
|
||||
world_name: "裂潮边城".to_string(),
|
||||
subtitle: "港口余烬".to_string(),
|
||||
summary_text: "一座被裂潮与旧械共同撕扯的沿海城邦。".to_string(),
|
||||
theme_mode: CustomWorldThemeMode::Tide,
|
||||
cover_image_src: None,
|
||||
profile_payload_json: "{\"id\":\"cwprof_001\"}".to_string(),
|
||||
playable_npc_count: 3,
|
||||
landmark_count: 2,
|
||||
author_display_name: " ".to_string(),
|
||||
updated_at_micros: 1,
|
||||
})
|
||||
.expect_err("blank author display name should fail");
|
||||
|
||||
assert_eq!(error, CustomWorldFieldError::MissingAuthorDisplayName);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_profile_before_save_rebuilds_setting_text_from_creator_intent() {
|
||||
let mut profile = serde_json::json!({
|
||||
"id": "cwprof_001",
|
||||
"settingText": "前端旧草稿文案",
|
||||
"creatorIntent": {
|
||||
"rawSettingText": "早期输入",
|
||||
"worldHook": "海图会在午夜改写群岛航路",
|
||||
"themeKeywords": ["海雾", "旧灯塔"],
|
||||
"toneDirectives": ["克制", "悬疑"],
|
||||
"playerPremise": "玩家是失忆领航员",
|
||||
"openingSituation": "正在禁航区醒来",
|
||||
"coreConflicts": ["议会隐瞒沉船真相"],
|
||||
"keyCharacters": [{
|
||||
"name": "顾潮音",
|
||||
"role": "守灯人",
|
||||
"relationToPlayer": "旧识",
|
||||
"hiddenHook": "掌握伪造海图"
|
||||
}],
|
||||
"iconicElements": ["会说谎的罗盘"]
|
||||
}
|
||||
});
|
||||
|
||||
assert!(canonicalize_custom_world_profile_before_save(&mut profile));
|
||||
assert_eq!(
|
||||
profile.get("settingText").and_then(Value::as_str),
|
||||
Some(
|
||||
"世界一句话:海图会在午夜改写群岛航路\n玩家开局:玩家是失忆领航员;正在禁航区醒来\n主题气质:海雾、旧灯塔 / 克制、悬疑\n核心冲突:议会隐瞒沉船真相\n关键关系:顾潮音 · 守灯人 · 与玩家 旧识 · 暗线 掌握伪造海图\n标志元素:会说谎的罗盘"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_profile_before_save_keeps_profile_without_creator_intent() {
|
||||
let mut profile = serde_json::json!({
|
||||
"id": "cwprof_001",
|
||||
"settingText": "用户手写设定"
|
||||
});
|
||||
|
||||
assert!(!canonicalize_custom_world_profile_before_save(&mut profile));
|
||||
assert_eq!(
|
||||
profile.get("settingText").and_then(Value::as_str),
|
||||
Some("用户手写设定")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_list_input_requires_owner_user_id() {
|
||||
let error = validate_custom_world_profile_list_input(&CustomWorldProfileListInput {
|
||||
owner_user_id: " ".to_string(),
|
||||
})
|
||||
.expect_err("blank owner user id should fail");
|
||||
|
||||
assert_eq!(error, CustomWorldFieldError::MissingOwnerUserId);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_delete_input_requires_profile_and_owner() {
|
||||
let error = validate_custom_world_profile_delete_input(&CustomWorldProfileDeleteInput {
|
||||
profile_id: " ".to_string(),
|
||||
owner_user_id: "user_001".to_string(),
|
||||
deleted_at_micros: 1,
|
||||
})
|
||||
.expect_err("blank profile id should fail");
|
||||
|
||||
assert_eq!(error, CustomWorldFieldError::MissingProfileId);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_message_finalize_requires_valid_json_payloads() {
|
||||
let error = validate_custom_world_agent_message_finalize_input(
|
||||
&CustomWorldAgentMessageFinalizeInput {
|
||||
session_id: "session_001".to_string(),
|
||||
owner_user_id: "user_001".to_string(),
|
||||
operation_id: "operation_001".to_string(),
|
||||
assistant_message_id: Some("message_001".to_string()),
|
||||
assistant_reply_text: Some("已生成回复".to_string()),
|
||||
phase_label: "消息已处理".to_string(),
|
||||
phase_detail: "这一轮已完成推理并写回".to_string(),
|
||||
operation_status: RpgAgentOperationStatus::Completed,
|
||||
operation_progress: 100,
|
||||
stage: RpgAgentStage::FoundationReview,
|
||||
progress_percent: 100,
|
||||
focus_card_id: None,
|
||||
anchor_content_json: "[]".to_string(),
|
||||
creator_intent_json: Some("{}".to_string()),
|
||||
creator_intent_readiness_json: "{}".to_string(),
|
||||
anchor_pack_json: Some("{}".to_string()),
|
||||
draft_profile_json: Some("{}".to_string()),
|
||||
pending_clarifications_json: "[]".to_string(),
|
||||
suggested_actions_json: "[]".to_string(),
|
||||
recommended_replies_json: "[]".to_string(),
|
||||
quality_findings_json: "[]".to_string(),
|
||||
asset_coverage_json: "{}".to_string(),
|
||||
error_message: None,
|
||||
updated_at_micros: 1,
|
||||
},
|
||||
)
|
||||
.expect_err("invalid anchor content should fail");
|
||||
|
||||
assert_eq!(error, CustomWorldFieldError::InvalidJsonPayload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_message_finalize_allows_missing_assistant_reply_when_failed() {
|
||||
validate_custom_world_agent_message_finalize_input(&CustomWorldAgentMessageFinalizeInput {
|
||||
session_id: "session_001".to_string(),
|
||||
owner_user_id: "user_001".to_string(),
|
||||
operation_id: "operation_001".to_string(),
|
||||
assistant_message_id: None,
|
||||
assistant_reply_text: None,
|
||||
phase_label: "消息处理失败".to_string(),
|
||||
phase_detail: "当前模型不可用,请稍后重试。".to_string(),
|
||||
operation_status: RpgAgentOperationStatus::Failed,
|
||||
operation_progress: 100,
|
||||
stage: RpgAgentStage::Clarifying,
|
||||
progress_percent: 20,
|
||||
focus_card_id: None,
|
||||
anchor_content_json: "{}".to_string(),
|
||||
creator_intent_json: Some("{}".to_string()),
|
||||
creator_intent_readiness_json: "{}".to_string(),
|
||||
anchor_pack_json: Some("{}".to_string()),
|
||||
draft_profile_json: Some("{}".to_string()),
|
||||
pending_clarifications_json: "[]".to_string(),
|
||||
suggested_actions_json: "[]".to_string(),
|
||||
recommended_replies_json: "[]".to_string(),
|
||||
quality_findings_json: "[]".to_string(),
|
||||
asset_coverage_json: "{}".to_string(),
|
||||
error_message: Some("当前模型不可用,请稍后重试。".to_string()),
|
||||
updated_at_micros: 1,
|
||||
})
|
||||
.expect("failed finalize should allow empty assistant message");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn published_profile_compile_merges_legacy_theme_and_latest_assets() {
|
||||
let snapshot = build_custom_world_published_profile_compile_snapshot(
|
||||
CustomWorldPublishedProfileCompileInput {
|
||||
session_id: "session_001".to_string(),
|
||||
profile_id: "agent-draft-session_001".to_string(),
|
||||
owner_user_id: "user_001".to_string(),
|
||||
draft_profile_json: r#"{
|
||||
"name":"潮雾列岛",
|
||||
"subtitle":"旧灯塔与失控航路",
|
||||
"summary":"第一版世界底稿已经整理完成。",
|
||||
"tone":"压抑、潮湿、悬疑",
|
||||
"playerGoal":"查清沉船与禁航区异动的真相。",
|
||||
"playableNpcs":[{"id":"playable-1","name":"沈砺","imageSrc":"/generated/playable-1.png"}],
|
||||
"storyNpcs":[{"id":"story-1","name":"顾潮音"}],
|
||||
"landmarks":[{"id":"landmark-1","name":"回潮旧灯塔","imageSrc":"/generated/landmark-1.png"}],
|
||||
"camp":{"id":"camp-1","name":"回潮暂栖所","imageSrc":"/generated/camp.png"},
|
||||
"sceneChapters":[{"id":"scene-chapter-1","sceneId":"landmark-1","title":"灯塔初章"}]
|
||||
}"#.to_string(),
|
||||
legacy_result_profile_json: Some(
|
||||
r#"{
|
||||
"id":"legacy_profile",
|
||||
"themeMode":"tide",
|
||||
"themePack":{"id":"theme-pack:tide"},
|
||||
"storyGraph":{"visibleThreads":[{"id":"thread-1"}]}
|
||||
}"#
|
||||
.to_string(),
|
||||
),
|
||||
setting_text: "被海雾吞没的旧航路群岛".to_string(),
|
||||
author_display_name: "测试玩家".to_string(),
|
||||
updated_at_micros: 42,
|
||||
},
|
||||
)
|
||||
.expect("compile should succeed");
|
||||
|
||||
assert_eq!(snapshot.world_name, "潮雾列岛");
|
||||
assert_eq!(snapshot.theme_mode, CustomWorldThemeMode::Tide);
|
||||
assert_eq!(
|
||||
snapshot.cover_image_src.as_deref(),
|
||||
Some("/generated/camp.png")
|
||||
);
|
||||
assert_eq!(snapshot.playable_npc_count, 2);
|
||||
assert_eq!(snapshot.landmark_count, 1);
|
||||
assert!(
|
||||
snapshot
|
||||
.compiled_profile_payload_json
|
||||
.contains("\"sceneChapterBlueprints\"")
|
||||
);
|
||||
assert!(
|
||||
snapshot
|
||||
.compiled_profile_payload_json
|
||||
.contains("\"themePack\"")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn published_profile_compile_defaults_theme_to_mythic_without_legacy_theme() {
|
||||
let snapshot = build_custom_world_published_profile_compile_snapshot(
|
||||
CustomWorldPublishedProfileCompileInput {
|
||||
session_id: "session_002".to_string(),
|
||||
profile_id: "profile_002".to_string(),
|
||||
owner_user_id: "user_002".to_string(),
|
||||
draft_profile_json: r#"{
|
||||
"name":"裂帆荒湾",
|
||||
"subtitle":"雾岸残潮",
|
||||
"summary":"港湾里还剩最后一条能退走的潮沟。",
|
||||
"playableNpcs":[],
|
||||
"storyNpcs":[],
|
||||
"landmarks":[{"id":"landmark-1","name":"裂帆湾","imageSrc":"/generated/landmark-cover.png"}]
|
||||
}"#
|
||||
.to_string(),
|
||||
legacy_result_profile_json: None,
|
||||
setting_text: "被潮沟切开的荒湾".to_string(),
|
||||
author_display_name: "玩家二号".to_string(),
|
||||
updated_at_micros: 84,
|
||||
},
|
||||
)
|
||||
.expect("compile should succeed");
|
||||
|
||||
assert_eq!(snapshot.theme_mode, CustomWorldThemeMode::Mythic);
|
||||
assert_eq!(
|
||||
snapshot.cover_image_src.as_deref(),
|
||||
Some("/generated/landmark-cover.png")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user