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:
@@ -10,6 +10,10 @@ use super::*;
|
||||
mod t025a_connection_setup_tests {
|
||||
use super::*;
|
||||
|
||||
static HOST: &str = "192.168.1.200";
|
||||
static PORT: u16 = 502;
|
||||
static SLAVE_ID: u8 = 1;
|
||||
|
||||
/// T025a Test 1: `new()` with valid config connects successfully
|
||||
///
|
||||
/// This test verifies that `ModbusRelayController::new()` can establish
|
||||
@@ -21,13 +25,10 @@ mod t025a_connection_setup_tests {
|
||||
#[ignore = "Requires running Modbus TCP server"]
|
||||
async fn test_new_with_valid_config_connects_successfully() {
|
||||
// Arrange: Use localhost test server
|
||||
let host = "127.0.0.1";
|
||||
let port = 5020; // Test Modbus TCP port
|
||||
let slave_id = 1;
|
||||
let timeout_secs = 5;
|
||||
|
||||
// Act: Attempt to create controller
|
||||
let result = ModbusRelayController::new(host, port, slave_id, timeout_secs).await;
|
||||
let result = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs).await;
|
||||
|
||||
// Assert: Connection should succeed
|
||||
assert!(
|
||||
@@ -45,12 +46,10 @@ mod t025a_connection_setup_tests {
|
||||
async fn test_new_with_invalid_host_returns_connection_error() {
|
||||
// Arrange: Use invalid host format
|
||||
let host = "not a valid host!!!";
|
||||
let port = 502;
|
||||
let slave_id = 1;
|
||||
let timeout_secs = 5;
|
||||
|
||||
// Act: Attempt to create controller
|
||||
let result = ModbusRelayController::new(host, port, slave_id, timeout_secs).await;
|
||||
let result = ModbusRelayController::new(host, PORT, SLAVE_ID, timeout_secs).await;
|
||||
|
||||
// Assert: Should return ConnectionError
|
||||
assert!(result.is_err(), "Expected ConnectionError for invalid host");
|
||||
@@ -74,13 +73,11 @@ mod t025a_connection_setup_tests {
|
||||
async fn test_new_with_unreachable_host_returns_connection_error() {
|
||||
// Arrange: Use localhost with a closed port (port 1 is typically closed)
|
||||
// This gives instant "connection refused" instead of waiting for TCP timeout
|
||||
let host = "127.0.0.1";
|
||||
let port = 1; // Closed port for instant connection failure
|
||||
let slave_id = 1;
|
||||
let timeout_secs = 1;
|
||||
|
||||
// Act: Attempt to create controller
|
||||
let result = ModbusRelayController::new(host, port, slave_id, timeout_secs).await;
|
||||
let result = ModbusRelayController::new(HOST, port, SLAVE_ID, timeout_secs).await;
|
||||
|
||||
// Assert: Should return ConnectionError
|
||||
assert!(
|
||||
@@ -100,13 +97,10 @@ mod t025a_connection_setup_tests {
|
||||
#[ignore = "Requires running Modbus TCP server or refactoring to expose timeout"]
|
||||
async fn test_new_stores_correct_timeout_duration() {
|
||||
// Arrange
|
||||
let host = "127.0.0.1";
|
||||
let port = 5020;
|
||||
let slave_id = 1;
|
||||
let timeout_secs = 10;
|
||||
|
||||
// Act
|
||||
let controller = ModbusRelayController::new(host, port, slave_id, timeout_secs)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, timeout_secs)
|
||||
.await
|
||||
.expect("Failed to create controller");
|
||||
|
||||
@@ -137,6 +131,10 @@ mod t025b_read_coils_timeout_tests {
|
||||
types::RelayId,
|
||||
};
|
||||
|
||||
static HOST: &str = "192.168.1.200";
|
||||
static PORT: u16 = 502;
|
||||
static SLAVE_ID: u8 = 1;
|
||||
|
||||
/// T025b Test 1: `read_coils_with_timeout()` returns coil values on success
|
||||
///
|
||||
/// This test verifies that reading coils succeeds when the Modbus server
|
||||
@@ -147,7 +145,7 @@ mod t025b_read_coils_timeout_tests {
|
||||
#[ignore = "Requires running Modbus TCP server with known state"]
|
||||
async fn test_read_coils_returns_coil_values_on_success() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -251,6 +249,10 @@ mod t025c_write_single_coil_timeout_tests {
|
||||
types::{RelayId, RelayState},
|
||||
};
|
||||
|
||||
static HOST: &str = "192.168.1.200";
|
||||
static PORT: u16 = 502;
|
||||
static SLAVE_ID: u8 = 1;
|
||||
|
||||
/// T025c Test 1: `write_single_coil_with_timeout()` succeeds for valid write
|
||||
///
|
||||
/// This test verifies that writing to a coil succeeds when the Modbus server
|
||||
@@ -261,7 +263,7 @@ mod t025c_write_single_coil_timeout_tests {
|
||||
#[ignore = "Requires running Modbus TCP server"]
|
||||
async fn test_write_single_coil_succeeds_for_valid_write() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -336,6 +338,10 @@ mod t025d_read_relay_state_tests {
|
||||
types::{RelayId, RelayState},
|
||||
};
|
||||
|
||||
static HOST: &str = "192.168.1.200";
|
||||
static PORT: u16 = 502;
|
||||
static SLAVE_ID: u8 = 1;
|
||||
|
||||
/// T025d Test 1: `read_relay_state(RelayId(1))` returns On when coil is true
|
||||
///
|
||||
/// This test verifies that a true coil value is correctly converted to `RelayState::On`.
|
||||
@@ -409,7 +415,7 @@ mod t025d_read_relay_state_tests {
|
||||
#[ignore = "Requires Modbus server with specific relay states"]
|
||||
async fn test_read_state_correctly_maps_relay_id_to_modbus_address() {
|
||||
// Arrange: Connect to test server with known relay states
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -434,6 +440,10 @@ mod t025e_write_relay_state_tests {
|
||||
types::{RelayId, RelayState},
|
||||
};
|
||||
|
||||
static HOST: &str = "192.168.1.200";
|
||||
static PORT: u16 = 502;
|
||||
static SLAVE_ID: u8 = 1;
|
||||
|
||||
/// T025e Test 1: `write_relay_state(RelayId(1)`, `RelayState::On`) writes true to coil
|
||||
///
|
||||
/// This test verifies that `RelayState::On` is correctly converted to a true coil value.
|
||||
@@ -441,7 +451,7 @@ mod t025e_write_relay_state_tests {
|
||||
#[ignore = "Requires Modbus server that can verify written values"]
|
||||
async fn test_write_state_on_writes_true_to_coil() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -475,7 +485,7 @@ mod t025e_write_relay_state_tests {
|
||||
#[ignore = "Requires Modbus server that can verify written values"]
|
||||
async fn test_write_state_off_writes_false_to_coil() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -509,7 +519,7 @@ mod t025e_write_relay_state_tests {
|
||||
#[ignore = "Requires Modbus server"]
|
||||
async fn test_write_state_correctly_maps_relay_id_to_modbus_address() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -537,7 +547,7 @@ mod t025e_write_relay_state_tests {
|
||||
#[ignore = "Requires Modbus server"]
|
||||
async fn test_write_state_can_toggle_relay_multiple_times() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -571,12 +581,16 @@ mod t025e_write_relay_state_tests {
|
||||
mod write_all_states_validation_tests {
|
||||
use super::*;
|
||||
|
||||
static HOST: &str = "192.168.1.200";
|
||||
static PORT: u16 = 502;
|
||||
static SLAVE_ID: u8 = 1;
|
||||
|
||||
/// Test: `write_all_states()` returns `InvalidInput` when given 0 states
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires Modbus server"]
|
||||
async fn test_write_all_states_with_empty_vector_returns_invalid_input() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -596,7 +610,7 @@ mod write_all_states_validation_tests {
|
||||
#[ignore = "Requires Modbus server"]
|
||||
async fn test_write_all_states_with_7_states_returns_invalid_input() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -626,7 +640,7 @@ mod write_all_states_validation_tests {
|
||||
#[ignore = "Requires Modbus server"]
|
||||
async fn test_write_all_states_with_9_states_returns_invalid_input() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
@@ -656,7 +670,7 @@ mod write_all_states_validation_tests {
|
||||
#[ignore = "Requires Modbus server"]
|
||||
async fn test_write_all_states_with_8_states_succeeds() {
|
||||
// Arrange: Connect to test server
|
||||
let controller = ModbusRelayController::new("127.0.0.1", 5020, 1, 5)
|
||||
let controller = ModbusRelayController::new(HOST, PORT, SLAVE_ID, 5)
|
||||
.await
|
||||
.expect("Failed to connect to test server");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user