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
running 21 tests
test infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_returns_error_on_failure ... FAILED
test infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_returns_timeout_on_slow_device ... FAILED
test infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_timeout_on_slow_response ... FAILED
test infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_modbus_exception_on_protocol_error ... FAILED
test infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_returns_on_when_coil_is_true ... FAILED
test infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_propagates_controller_error ... FAILED
test infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_returns_off_when_coil_is_false ... FAILED
test infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_connection_error_on_io_error ... FAILED
test infrastructure::modbus::client::tests::t025a_connection_setup_tests::test_new_with_valid_config_connects_successfully ... ok
test infrastructure::modbus::client::tests::t025a_connection_setup_tests::test_new_stores_correct_timeout_duration ... ok
test infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_coil_values_on_success ... ok
test infrastructure::modbus::client::tests::write_all_states_validation_tests::test_write_all_states_with_9_states_returns_invalid_input ... ok
test infrastructure::modbus::client::tests::write_all_states_validation_tests::test_write_all_states_with_empty_vector_returns_invalid_input ... ok
test infrastructure::modbus::client::tests::t025e_write_relay_state_tests::test_write_state_can_toggle_relay_multiple_times ... ok
test infrastructure::modbus::client::tests::write_all_states_validation_tests::test_write_all_states_with_8_states_succeeds ... ok
test infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_succeeds_for_valid_write ... ok
test infrastructure::modbus::client::tests::t025e_write_relay_state_tests::test_write_state_off_writes_false_to_coil ... FAILED
test infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_correctly_maps_relay_id_to_modbus_address ... ok
test infrastructure::modbus::client::tests::write_all_states_validation_tests::test_write_all_states_with_7_states_returns_invalid_input ... ok
test infrastructure::modbus::client::tests::t025e_write_relay_state_tests::test_write_state_on_writes_true_to_coil ... ok
test infrastructure::modbus::client::tests::t025e_write_relay_state_tests::test_write_state_correctly_maps_relay_id_to_modbus_address ... ok
failures:
---- infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_returns_error_on_failure stdout ----
thread 'infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_returns_error_on_failure' (1157113) panicked at backend/src/infrastructure/modbus/client_test.rs:320:14:
Failed to connect: ConnectionError("Connection refused (os error 111)")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
---- infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_returns_timeout_on_slow_device stdout ----
thread 'infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_returns_timeout_on_slow_device' (1157114) panicked at backend/src/infrastructure/modbus/client_test.rs:293:14:
Failed to connect: ConnectionError("Connection refused (os error 111)")
---- infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_timeout_on_slow_response stdout ----
thread 'infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_timeout_on_slow_response' (1157112) panicked at backend/src/infrastructure/modbus/client_test.rs:176:14:
Failed to connect: ConnectionError("Connection refused (os error 111)")
---- infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_modbus_exception_on_protocol_error stdout ----
thread 'infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_modbus_exception_on_protocol_error' (1157111) panicked at backend/src/infrastructure/modbus/client_test.rs:227:14:
Failed to connect: ConnectionError("Connection refused (os error 111)")
---- infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_returns_on_when_coil_is_true stdout ----
thread 'infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_returns_on_when_coil_is_true' (1157119) panicked at backend/src/infrastructure/modbus/client_test.rs:354:14:
Failed to connect to test server: ConnectionError("Connection refused (os error 111)")
---- infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_propagates_controller_error stdout ----
thread 'infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_propagates_controller_error' (1157117) panicked at backend/src/infrastructure/modbus/client_test.rs:396:14:
Failed to connect to test server: ConnectionError("Connection refused (os error 111)")
---- infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_returns_off_when_coil_is_false stdout ----
thread 'infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_returns_off_when_coil_is_false' (1157118) panicked at backend/src/infrastructure/modbus/client_test.rs:375:14:
Failed to connect to test server: ConnectionError("Connection refused (os error 111)")
---- infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_connection_error_on_io_error stdout ----
thread 'infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_connection_error_on_io_error' (1157110) panicked at backend/src/infrastructure/modbus/client_test.rs:202:14:
Failed to connect: ConnectionError("Connection refused (os error 111)")
---- infrastructure::modbus::client::tests::t025e_write_relay_state_tests::test_write_state_off_writes_false_to_coil stdout ----
thread 'infrastructure::modbus::client::tests::t025e_write_relay_state_tests::test_write_state_off_writes_false_to_coil' (1157122) panicked at backend/src/infrastructure/modbus/client_test.rs:508:9:
assertion `left == right` failed: Relay should be Off after writing Off state
left: On
right: Off
failures:
infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_connection_error_on_io_error
infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_modbus_exception_on_protocol_error
infrastructure::modbus::client::tests::t025b_read_coils_timeout_tests::test_read_coils_returns_timeout_on_slow_response
infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_returns_error_on_failure
infrastructure::modbus::client::tests::t025c_write_single_coil_timeout_tests::test_write_single_coil_returns_timeout_on_slow_device
infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_propagates_controller_error
infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_returns_off_when_coil_is_false
infrastructure::modbus::client::tests::t025d_read_relay_state_tests::test_read_state_returns_on_when_coil_is_true
infrastructure::modbus::client::tests::t025e_write_relay_state_tests::test_write_state_off_writes_false_to_coil
test result: FAILED. 12 passed; 9 failed; 0 ignored; 0 measured; 128 filtered out; finished in 3.27s.
Ref: T034, T039, T040 (specs/001-modbus-relay-control/tasks.org)
254 lines
8.3 KiB
Rust
254 lines
8.3 KiB
Rust
// 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);
|
|
}
|
|
}
|
|
}
|