feat(application): HealthMonitor service and hardware integration test
Add HealthMonitor service for tracking system health status with comprehensive state transition logic and thread-safe operations. Includes 16 unit tests covering all functionality including concurrent access scenarios. Add optional Modbus hardware integration tests with 7 test cases for real device testing. Tests are marked as ignored and can be run with Ref: T034, T039, T040 (specs/001-modbus-relay-control/tasks.org)
This commit is contained in:
253
backend/tests/modbus_hardware_test.rs
Normal file
253
backend/tests/modbus_hardware_test.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user