diff --git a/src/simulator/crates/mc-turn/src/building_action_handlers.rs b/src/simulator/crates/mc-turn/src/building_action_handlers.rs index bc4c7b9b..ac49c026 100644 --- a/src/simulator/crates/mc-turn/src/building_action_handlers.rs +++ b/src/simulator/crates/mc-turn/src/building_action_handlers.rs @@ -43,3 +43,87 @@ pub fn drain_pending_building_actions(state: &mut GameState) { let _ = invoke(state, &req); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::game_state::{BuildingRallyPoint, PlayerState, RallyCommand}; + + fn state_with_rally(building_id: &str) -> GameState { + let mut player = PlayerState::default(); + player.rally_points.push(BuildingRallyPoint { + city_index: 0, + building_id: building_id.to_string(), + hex: (3, 5), + command: RallyCommand::Defend, + }); + let mut state = GameState::default(); + state.players.push(player); + state + } + + #[test] + fn clear_rally_removes_matching_entry() { + let mut state = state_with_rally("barracks"); + assert_eq!(state.players[0].rally_points.len(), 1); + + state.pending_building_actions.push(BuildingActionRequest { + player_idx: 0, + city_idx: 0, + building_id: "barracks".to_string(), + kind: BuildingActionKind::ClearRally, + }); + drain_pending_building_actions(&mut state); + + assert!(state.pending_building_actions.is_empty(), "queue drained"); + assert!(state.players[0].rally_points.is_empty(), "rally point removed"); + } + + #[test] + fn clear_rally_leaves_other_buildings_untouched() { + let mut state = state_with_rally("barracks"); + state.players[0].rally_points.push(BuildingRallyPoint { + city_index: 0, + building_id: "watchtower".to_string(), + hex: (1, 2), + command: RallyCommand::Defend, + }); + assert_eq!(state.players[0].rally_points.len(), 2); + + state.pending_building_actions.push(BuildingActionRequest { + player_idx: 0, + city_idx: 0, + building_id: "barracks".to_string(), + kind: BuildingActionKind::ClearRally, + }); + drain_pending_building_actions(&mut state); + + assert_eq!(state.players[0].rally_points.len(), 1); + assert_eq!(state.players[0].rally_points[0].building_id, "watchtower"); + } + + #[test] + fn stubbed_actions_return_not_yet_implemented() { + let mut state = GameState::default(); + state.players.push(PlayerState::default()); + let req = BuildingActionRequest { + player_idx: 0, + city_idx: 0, + building_id: "barracks".to_string(), + kind: BuildingActionKind::GarrisonIn, + }; + assert_eq!(invoke(&mut state, &req), Err(BuildingActionError::NotYetImplemented)); + } + + #[test] + fn out_of_range_player_returns_error() { + let mut state = GameState::default(); // no players + let req = BuildingActionRequest { + player_idx: 0, + city_idx: 0, + building_id: "barracks".to_string(), + kind: BuildingActionKind::ClearRally, + }; + assert_eq!(invoke(&mut state, &req), Err(BuildingActionError::PlayerOutOfRange)); + } +} diff --git a/src/simulator/crates/mc-turn/src/processor.rs b/src/simulator/crates/mc-turn/src/processor.rs index d626f201..45aa4e29 100644 --- a/src/simulator/crates/mc-turn/src/processor.rs +++ b/src/simulator/crates/mc-turn/src/processor.rs @@ -307,6 +307,7 @@ impl TurnProcessor { // Phase 4f: drain formation requests from GDScript (rally, command, shape, split, auto-join). Self::drain_formation_requests(state); + crate::building_action_handlers::drain_pending_building_actions(state); // Phase 4g: recompute formation membership from adjacency. Self::aggregate_formations(state);