// Integration tests for Modbus hardware // These tests require physical Modbus relay device to be connected // Run with: cargo test -- --ignored use std::time::Duration; #[cfg(test)] mod tests { use super::*; use sta::domain::relay::controller::RelayController; use sta::domain::relay::types::{RelayId, RelayState}; use sta::infrastructure::modbus::client::ModbusRelayController; static HOST: &str = "192.168.1.200"; static PORT: u16 = 502; static SLAVE_ID: u8 = 1; #[tokio::test] #[ignore = "Requires physical Modbus device"] async fn test_modbus_connection() { // This test verifies we can connect to the actual Modbus device // Configured with settings from settings/base.yaml let timeout_secs = 5; let _controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect to Modbus device"); // If we got here, connection was successful println!("✓ Successfully connected to Modbus device"); } #[tokio::test] #[ignore = "Requires physical Modbus device"] async fn test_read_relay_states() { let timeout_secs = 5; let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect to Modbus device"); // Test reading individual relay states for relay_id in 1..=8 { let relay_id = RelayId::new(relay_id).unwrap(); let state = controller .read_relay_state(relay_id) .await .expect("Failed to read relay state"); println!("Relay {}: {:?}", relay_id.as_u8(), state); } } #[tokio::test] #[ignore = "Requires physical Modbus device"] async fn test_read_all_relays() { let timeout_secs = 5; let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect to Modbus device"); let relays = controller .read_all_states() .await .expect("Failed to read all relay states"); assert_eq!(relays.len(), 8, "Should have exactly 8 relays"); for (i, state) in relays.iter().enumerate() { let relay_id = i + 1; println!("Relay {}: {:?}", relay_id, state); } } #[tokio::test] #[ignore = "Requires physical Modbus device"] async fn test_write_relay_state() { let timeout_secs = 5; let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect to Modbus device"); let relay_id = RelayId::new(1).unwrap(); // Turn relay on controller .write_relay_state(relay_id, RelayState::On) .await .expect("Failed to write relay state"); // Verify it's on let state = controller .read_relay_state(relay_id) .await .expect("Failed to read relay state"); assert_eq!(state, RelayState::On, "Relay should be ON"); // Turn relay off controller .write_relay_state(relay_id, RelayState::Off) .await .expect("Failed to write relay state"); // Verify it's off let state = controller .read_relay_state(relay_id) .await .expect("Failed to read relay state"); assert_eq!(state, RelayState::Off, "Relay should be OFF"); } #[tokio::test] #[ignore = "Requires physical Modbus device"] async fn test_write_all_relays() { let timeout_secs = 5; let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect to Modbus device"); // Turn all relays on let all_on_states = vec![RelayState::On; 8]; controller .write_all_states(all_on_states) .await .expect("Failed to write all relay states"); // Verify all are on let relays = controller .read_all_states() .await .expect("Failed to read all relay states"); for state in &relays { assert_eq!(*state, RelayState::On, "All relays should be ON"); } // Turn all relays off let all_off_states = vec![RelayState::Off; 8]; controller .write_all_states(all_off_states) .await .expect("Failed to write all relay states"); // Verify all are off let relays = controller .read_all_states() .await .expect("Failed to read all relay states"); for state in &relays { assert_eq!(*state, RelayState::Off, "All relays should be OFF"); } } #[tokio::test] #[ignore = "Requires physical Modbus device"] async fn test_timeout_handling() { let timeout_secs = 1; // Short timeout for testing let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect to Modbus device"); // This test verifies that timeout works correctly // We'll try to read a relay state with a very short timeout let relay_id = RelayId::new(1).unwrap(); // The operation should either succeed quickly or timeout let result = tokio::time::timeout( Duration::from_secs(2), controller.read_relay_state(relay_id), ) .await; match result { Ok(Ok(state)) => { println!("✓ Operation completed within timeout: {:?}", state); } Ok(Err(e)) => { println!("✓ Operation failed (expected): {}", e); } Err(_) => { println!("✓ Operation timed out (expected)"); } } } #[tokio::test] #[ignore = "Requires physical Modbus device"] async fn test_concurrent_access() { let timeout_secs = 5; let _controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect to Modbus device"); // Test concurrent access to the controller // We'll test a few relays concurrently using tokio::join! // Note: We can't clone the controller, so we'll just test sequential access // This is still valuable for testing the controller works with multiple relays let relay_id1 = RelayId::new(1).unwrap(); let relay_id2 = RelayId::new(2).unwrap(); let relay_id3 = RelayId::new(3).unwrap(); let relay_id4 = RelayId::new(4).unwrap(); let task1 = tokio::spawn(async move { let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect"); controller.read_relay_state(relay_id1).await }); let task2 = tokio::spawn(async move { let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect"); controller.read_relay_state(relay_id2).await }); let task3 = tokio::spawn(async move { let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect"); controller.read_relay_state(relay_id3).await }); let task4 = tokio::spawn(async move { let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs) .await .expect("Failed to connect"); controller.read_relay_state(relay_id4).await }); let (result1, result2, result3, result4) = tokio::join!(task1, task2, task3, task4); // Process results if let Ok(Ok(state)) = result1 { println!("Relay 1: {:?}", state); } if let Ok(Ok(state)) = result2 { println!("Relay 2: {:?}", state); } if let Ok(Ok(state)) = result3 { println!("Relay 3: {:?}", state); } if let Ok(Ok(state)) = result4 { println!("Relay 4: {:?}", state); } } }