Commit Graph

24 Commits

Author SHA1 Message Date
ce186095fa 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

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)
2026-01-22 00:57:11 +01:00
8c1d5433de test(infrastructure): write RelayLabelRepository trait tests
Add reusable test suite with 18 test functions covering get_label(),
save_label(), delete_label(), and get_all_labels() methods. Tests
verify contract compliance for any repository implementation.

Added delete_label() method to trait interface and implemented it in
MockRelayLabelRepository to support complete CRUD operations.

TDD phase: RED - Tests written before SQLite implementation (T036)

Ref: T035 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
306fa38935 test(infrastructure): implement MockRelayLabelRepository for testing
Create in-memory mock implementation of RelayLabelRepository trait
using HashMap with Arc<Mutex<>> for thread-safe concurrent access.
Includes 8 comprehensive tests covering all trait methods and edge
cases.

Also refactor domain repository module structure to support multiple
repository types (repository.rs → repository/label.rs + mod.rs).

TDD phase: Combined red-green (tests + implementation)

Ref: T037, T038
2026-01-22 00:57:11 +01:00
ed1485cc16 feat(infrastructure): implement ModbusRelayController with timeout handling
Add real Modbus TCP communication through ModbusRelayController:
- T025a: Connection setup with Arc<Mutex<Context>> and configurable timeout
- T025b: read_coils_with_timeout() helper wrapping tokio::time::timeout
- T025c: write_single_coil_with_timeout() with nested Result handling
- T025d: RelayController::read_relay_state() using timeout helper
- T025e: RelayController::write_relay_state() with state conversion
- Additional: Complete RelayController trait with all required methods
- Domain support: RelayId::to_modbus_address(), RelayState conversion helpers

Implements hexagonal architecture with infrastructure layer properly
depending on domain types. Includes structured logging at key operations.

TDD phase: green (implementation following test stubs from T023-T024)

Ref: T025a-T025e (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
1842ca25e3 test(modbus): implement working MockRelayController tests
Replace 6 stubbed test implementations with fully functional tests that validate:
- read_relay_state() returns correctly mocked state
- write_relay_state() updates internal mocked state
- read_all_states() returns 8 relays in known state
- Independent relay state management for all 8 channel indices
- Thread-safe concurrent state access with Arc<Mutex<>>

Tests now pass after T029-T031 completed MockRelayController implementation.

TDD phase: GREEN - tests validate implementation

Ref: T032 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
e8e6a1e702 feat(domain): implement RelayController trait and error handling
Add RelayController async trait (T030) defining interface for Modbus
relay operations with methods for read/write state, bulk operations,
connection checks, and firmware queries.

Implement ControllerError enum (T031) with variants for connection
failures, timeouts, Modbus exceptions, and invalid relay IDs.

Provide MockRelayController (T029) in-memory implementation using
Arc<Mutex<HashMap>> for thread-safe state storage with timeout
simulation for error handling tests.

Add RelayLabelRepository trait abstraction.

Ref: T029 T030 T031 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
036be64d3c test(modbus): implement MockRelayController with failing tests
Ref: T028 (specs/001-modbus-relay-control)
2026-01-22 00:57:11 +01:00
2b913ba049 style: format backend 2026-01-22 00:57:11 +01:00
7c89d48ac0 feat(domain): add ModbusAddress type and HealthStatus enum
Implements T025-T027 from TDD workflow (red-green-refactor):
- T025 (red): Tests for ModbusAddress with From<RelayId> conversion
- T026 (green): ModbusAddress newtype (#[repr(transparent)]) with offset mapping
- T027 (red+green): HealthStatus enum with state transitions

ModbusAddress wraps u16 and converts user-facing relay IDs (1-8) to
Modbus addresses (0-7) at the domain boundary. HealthStatus tracks
relay health with Healthy, Degraded, and Unhealthy states supporting
error tracking and recovery monitoring.

Ref: T025, T026, T027 (specs/001-modbus-relay-control)
2026-01-22 00:57:11 +01:00
39a609dec6 feat(domain): implement Relay aggregate and RelayLabel newtype
Implemented the Relay aggregate as the primary domain entity for relay
control operations. Added RelayLabel newtype for validated human-readable
relay labels.

Relay aggregate features:
- Construction with id, state, and optional label
- State control methods: toggle(), turn_on(), turn_off()
- Accessor methods: id(), state(), label()
- All methods use const where possible for compile-time optimization

RelayLabel newtype features:
- Validation: non-empty, max 50 characters
- Smart constructor with Result-based error handling
- Default implementation: "Unlabeled"
- Transparent representation for zero-cost abstraction

Additional changes:
- Made RelayId derive Copy for ergonomic value semantics
- All public APIs include documentation and #[must_use] attributes

TDD phase: GREEN - Tests pass for Relay aggregate (T021 tests now pass)

Ref: T022, T024 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
a7a7e7ef18 test(domain): write failing tests for Relay aggregate
Created test suite for Relay entity covering construction, state toggling,
and explicit state control methods. Tests intentionally fail as Relay
struct is not yet implemented.

Tests cover:
- Relay::new() with id, state, and optional label
- toggle() flipping state between On/Off
- turn_on() setting state to On
- turn_off() setting state to Off

TDD phase: RED - Tests written, implementation pending (T022)

Ref: T021 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
e81a128e7f feat(domain): implement RelayState enum with serialization support
Add RelayState enum to domain layer with:
- Display, Debug, Clone, Copy, PartialEq, Eq derives
- serde Serialize/Deserialize traits for API JSON handling
- Type-safe representation of relay on/off states

TDD green phase: Tests from T019 now pass.

Ref: T020 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
d6cbf0f4ae test(domain/relay): write failing tests for RelayState serialization
Tests verify serialization and deserialization of RelayState enum with
"on" and "off" states. Red phase of TDD - tests define expected behavior
before implementation.

Ref: T019 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
0ec7fdf11b feat(domain): implement RelayId newtype with validation
Implement smart constructor that validates relay IDs are within valid 
range (1-8 for 8-channel relay controller). Add accessor method as_u8() 
for safe access to inner value. Add comprehensive documentation to satisfy 
clippy requirements.

TDD green phase: Tests from T017 now pass.

Ref: T018 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
86b194ad74 test(domain): write failing tests for RelayId newtype validation
Tests cover validation requirements for the RelayId newtype:
- Valid relay IDs (1-8 for 8-channel controller)
- Invalid IDs outside valid range
- Smart constructor error handling
- Type-safe ID representation

TDD red phase: Tests fail until RelayId is implemented.

Ref: T017 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:11 +01:00
4929266a8e style: format backend 2026-01-22 00:57:11 +01:00
e16e214b74 test(cors): write integration tests for CORS headers
Added 9 comprehensive integration tests covering:
- Preflight OPTIONS requests
- Actual requests with CORS headers
- Max-age header validation
- Credentials configuration
- Allowed methods configuration
- Wildcard origins
- Multiple origins
- Unauthorized origin rejection

All tests pass successfully.

Ref: T016
2026-01-22 00:57:11 +01:00
b620c3d638 feat(middleware): configure CORS from settings in middleware chain
Replace Cors::new() with Cors::from(value.settings.cors.clone()) in the
From<Application> for RunnableApplication implementation to use CORS
settings from configuration instead of hardcoded defaults.

Changes:
- Use From<CorsSettings> for Cors trait to build CORS middleware
- Add unit test verifying CORS middleware uses settings
- Maintain correct middleware order: RateLimit → CORS → Data

Ref: T015
2026-01-22 00:57:11 +01:00
5d6c3208cc feat(cors): implement CORS configuration with From trait
Implement From<CorsSettings> for Cors trait to configure CORS middleware
with production-ready security validation.

- Move CorsSettings to backend/src/settings/cors.rs module
- Validate wildcard + credentials constraint (browser security policy)
- Configure allowed methods, headers, credentials, and max_age
- Add structured logging for CORS configuration
- Move tests from settings/mod.rs and startup.rs to cors module

Ref: T014
2026-01-22 00:57:11 +01:00
e577fb5095 test(cors): write tests for build_cors() function (TDD red)
Add failing test cases for the CORS configuration builder function.
Tests verify correct initialization of CorsSettings with allowed origins,
credentials, and max age configuration. These tests fail until build_cors()
is implemented in the green phase.

Ref: T013 (specs/001-modbus-relay-control)
2026-01-22 00:57:11 +01:00
5f0aaacb74 chore(config): configure CORS and update frontend URL in development settings
Set up CORS policy to allow requests from frontend development server and
update development.yaml with proper frontend origin URL configuration.

Ref: T011 (specs/001-modbus-relay-control)
2026-01-22 00:57:11 +01:00
9a55aa433c feat(settings): add CorsSettings struct for CORS configuration
Implements CorsSettings struct with validation and deserialization support
for configuring Cross-Origin Resource Sharing in the application settings.

Ref: T010 (specs/001-modbus-relay-control)
2026-01-22 00:57:10 +01:00
b1fd30af67 test(settings): write tests for CorsSettings struct deserialization
Add 5 comprehensive tests for CorsSettings in TDD red phase:
- cors_settings_deserialize_from_yaml: YAML deserialization
- cors_settings_default_has_empty_origins: Default values
- cors_settings_with_wildcard_deserializes: Wildcard origin handling
- settings_loads_cors_section_from_yaml: Settings integration
- cors_settings_deserialize_with_defaults: Partial deserialization

Tests are intentionally failing (red phase) as CorsSettings struct
will be implemented in T010. Follows TDD requirement to write tests
before implementation.

Ref: T009 (specs/001-modbus-relay-control/tasks.md)
2026-01-22 00:57:10 +01:00
837a49fc58 refactor: reorganize project into monorepo with frontend scaffolding
Convert project from single backend to monorepo structure with separate
frontend (Vue 3 + TypeScript + Vite) and backend directories. Updates
all configuration files and build system to support both workspaces.

Ref: T007 (specs/001-modbus-relay-control)
2026-01-22 00:57:10 +01:00