mod application; mod commands; mod domain; mod errors; mod events; pub use application::*; pub use commands::*; pub use domain::*; pub use errors::*; pub use events::*; #[cfg(test)] mod tests { use super::*; use module_runtime_item::RuntimeItemRewardItemSnapshot; fn build_fight_snapshot() -> BattleStateSnapshot { build_battle_state_snapshot(BattleStateInput { battle_state_id: "battle_001".to_string(), story_session_id: "storysess_001".to_string(), runtime_session_id: "runtime_001".to_string(), actor_user_id: "user_001".to_string(), chapter_id: Some("chapter_001".to_string()), target_npc_id: "npc_001".to_string(), target_name: "黑爪狼".to_string(), battle_mode: BattleMode::Fight, player_hp: 60, player_max_hp: 60, player_mana: 20, player_max_mana: 20, target_hp: 30, target_max_hp: 30, experience_reward: 18, reward_items: vec![], created_at_micros: 10, }) } #[test] fn validate_battle_state_input_accepts_minimal_contract() { let result = validate_battle_state_input(&BattleStateInput { battle_state_id: "battle_001".to_string(), story_session_id: "storysess_001".to_string(), runtime_session_id: "runtime_001".to_string(), actor_user_id: "user_001".to_string(), chapter_id: Some("chapter_001".to_string()), target_npc_id: "npc_001".to_string(), target_name: "黑爪狼".to_string(), battle_mode: BattleMode::Fight, player_hp: 50, player_max_hp: 60, player_mana: 10, player_max_mana: 20, target_hp: 30, target_max_hp: 30, experience_reward: 12, reward_items: vec![], created_at_micros: 1, }); assert!(result.is_ok()); } #[test] fn validate_battle_state_input_rejects_invalid_reward_items() { let error = validate_battle_state_input(&BattleStateInput { battle_state_id: "battle_001".to_string(), story_session_id: "storysess_001".to_string(), runtime_session_id: "runtime_001".to_string(), actor_user_id: "user_001".to_string(), chapter_id: Some("chapter_001".to_string()), target_npc_id: "npc_001".to_string(), target_name: "黑爪狼".to_string(), battle_mode: BattleMode::Fight, player_hp: 50, player_max_hp: 60, player_mana: 10, player_max_mana: 20, target_hp: 30, target_max_hp: 30, experience_reward: 12, reward_items: vec![RuntimeItemRewardItemSnapshot { item_id: String::new(), category: "遗物".to_string(), item_name: "铜钥残片".to_string(), description: None, quantity: 1, rarity: module_runtime_item::RuntimeItemRewardItemRarity::Rare, tags: vec![], stackable: false, stack_key: String::new(), equipment_slot_id: None, }], created_at_micros: 1, }) .expect_err("invalid reward item should be rejected"); assert_eq!( error, CombatFieldError::InvalidRewardItem( "battle_state.reward_items[].item_id 不能为空".to_string() ) ); } #[test] fn build_battle_state_query_input_trims_and_validates_id() { let input = build_battle_state_query_input(" battle_001 ".to_string()) .expect("query input should build"); assert_eq!(input.battle_state_id, "battle_001"); } #[test] fn build_battle_state_query_input_rejects_empty_id() { let error = build_battle_state_query_input(" ".to_string()).expect_err("empty id should fail"); assert_eq!(error, CombatFieldError::MissingBattleStateId); } #[test] fn resolve_basic_attack_advances_turn_and_applies_counter_damage() { let result = resolve_combat_action( build_fight_snapshot(), ResolveCombatActionInput { battle_state_id: "battle_001".to_string(), function_id: "battle_attack_basic".to_string(), action_text: "普通攻击".to_string(), base_damage: 10, mana_cost: 0, heal: 0, mana_restore: 0, counter_multiplier_basis_points: 10_000, updated_at_micros: 20, }, ) .expect("basic attack should succeed"); assert_eq!(result.snapshot.turn_index, 1); assert_eq!(result.snapshot.target_hp, 20); assert_eq!(result.snapshot.player_hp, 56); assert_eq!(result.snapshot.last_damage_dealt, 10); assert_eq!(result.snapshot.last_damage_taken, 4); assert_eq!(result.outcome, CombatOutcome::Ongoing); } #[test] fn resolve_escape_marks_battle_resolved() { let result = resolve_combat_action( build_fight_snapshot(), ResolveCombatActionInput { battle_state_id: "battle_001".to_string(), function_id: "battle_escape_breakout".to_string(), action_text: "逃跑".to_string(), base_damage: 0, mana_cost: 0, heal: 0, mana_restore: 0, counter_multiplier_basis_points: 0, updated_at_micros: 20, }, ) .expect("escape should succeed"); assert_eq!(result.snapshot.status, BattleStatus::Resolved); assert_eq!(result.snapshot.last_outcome, CombatOutcome::Escaped); assert_eq!(result.damage_dealt, 0); assert_eq!(result.damage_taken, 0); } #[test] fn resolve_skill_can_finish_fight() { let result = resolve_combat_action( build_fight_snapshot(), ResolveCombatActionInput { battle_state_id: "battle_001".to_string(), function_id: "battle_use_skill".to_string(), action_text: "试锋斩".to_string(), base_damage: 35, mana_cost: 8, heal: 0, mana_restore: 0, counter_multiplier_basis_points: 9_500, updated_at_micros: 20, }, ) .expect("skill should succeed"); assert_eq!(result.snapshot.status, BattleStatus::Resolved); assert_eq!(result.snapshot.target_hp, 0); assert_eq!(result.snapshot.player_mana, 12); assert_eq!(result.outcome, CombatOutcome::Victory); assert_eq!(result.damage_taken, 0); } #[test] fn spar_mode_keeps_hp_floor_at_one() { let snapshot = build_battle_state_snapshot(BattleStateInput { battle_state_id: "battle_002".to_string(), story_session_id: "storysess_001".to_string(), runtime_session_id: "runtime_001".to_string(), actor_user_id: "user_001".to_string(), chapter_id: Some("chapter_spar".to_string()), target_npc_id: "npc_002".to_string(), target_name: "卫队长".to_string(), battle_mode: BattleMode::Spar, player_hp: 5, player_max_hp: 5, player_mana: 10, player_max_mana: 10, target_hp: 3, target_max_hp: 3, experience_reward: 0, reward_items: vec![], created_at_micros: 10, }); let result = resolve_combat_action( snapshot, ResolveCombatActionInput { battle_state_id: "battle_002".to_string(), function_id: "battle_attack_basic".to_string(), action_text: "普通攻击".to_string(), base_damage: 5, mana_cost: 0, heal: 0, mana_restore: 0, counter_multiplier_basis_points: 10_000, updated_at_micros: 20, }, ) .expect("spar attack should succeed"); assert_eq!(result.snapshot.target_hp, 1); assert_eq!(result.snapshot.status, BattleStatus::Resolved); assert_eq!(result.outcome, CombatOutcome::SparComplete); } #[test] fn resolve_rejects_unsupported_function() { let error = resolve_combat_action( build_fight_snapshot(), ResolveCombatActionInput { battle_state_id: "battle_001".to_string(), function_id: "inventory_use".to_string(), action_text: "使用物品".to_string(), base_damage: 0, mana_cost: 0, heal: 0, mana_restore: 0, counter_multiplier_basis_points: 7_200, updated_at_micros: 20, }, ) .expect_err("inventory_use should be deferred for now"); assert_eq!(error, CombatFieldError::UnsupportedFunctionId); } }