feat(domain,presentation,tests): implement Relay entity, DTOs, and API errors
- Add Relay entity with constructors and business logic methods - Add RelayDto for API responses with From<Relay> conversion - Add ApiError with ResponseError trait for unified error handling - Add dependency injection tests for mock infrastructure in test mode
This commit is contained in:
6
backend/src/presentation/dto/mod.rs
Normal file
6
backend/src/presentation/dto/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
/// Relay-specific Data Transfer Objects.
|
||||
///
|
||||
/// This module contains DTO structures for relay-related API responses,
|
||||
/// providing serialized representations of relay domain objects for
|
||||
/// external consumption.
|
||||
pub mod relay_dto;
|
||||
197
backend/src/presentation/dto/relay_dto.rs
Normal file
197
backend/src/presentation/dto/relay_dto.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
use poem_openapi::Object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::domain::relay::Relay;
|
||||
|
||||
/// Data Transfer Object for relay information.
|
||||
///
|
||||
/// This struct represents a relay in a serialized format suitable for API
|
||||
/// responses. It contains the relay's ID, current state, and label in a
|
||||
/// format that can be easily serialized to JSON.
|
||||
#[derive(Object, Serialize, Deserialize)]
|
||||
pub struct RelayDto {
|
||||
/// The relay's unique identifier (1-8).
|
||||
id: u8,
|
||||
/// The relay's current state as a string ("on" or "off").
|
||||
state: String,
|
||||
/// The relay's user-friendly label.
|
||||
label: String,
|
||||
}
|
||||
|
||||
impl From<Relay> for RelayDto {
|
||||
/// Converts a domain Relay object to a `RelayDto`.
|
||||
///
|
||||
/// This conversion extracts the relay's ID, state, and label from the
|
||||
/// domain object and formats them for API consumption.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The Relay domain object to convert
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `RelayDto` containing the relay's data in serialized format
|
||||
fn from(value: Relay) -> Self {
|
||||
let id = value.id().as_u8();
|
||||
let state = value.state().to_string();
|
||||
let label = value.label().to_string();
|
||||
Self { id, state, label }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::domain::relay::types::{RelayId, RelayLabel, RelayState};
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_from_relay_with_default_label() {
|
||||
// Test: Relay with default label converts to RelayDto with None label
|
||||
let relay_id = RelayId::new(1).unwrap();
|
||||
let relay = Relay::new(relay_id);
|
||||
let dto = RelayDto::from(relay);
|
||||
|
||||
assert_eq!(dto.id, 1);
|
||||
assert_eq!(dto.state, "off");
|
||||
assert_eq!(dto.label, "Unlabeled".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_from_relay_with_custom_label() {
|
||||
// Test: Relay with custom label converts to RelayDto with Some(label)
|
||||
let relay_id = RelayId::new(2).unwrap();
|
||||
let label = RelayLabel::new("Water Pump".to_string()).unwrap();
|
||||
let relay = Relay::with_label(relay_id, RelayState::On, label);
|
||||
let dto = RelayDto::from(relay);
|
||||
|
||||
assert_eq!(dto.id, 2);
|
||||
assert_eq!(dto.state, "on");
|
||||
assert_eq!(dto.label, "Water Pump".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_from_relay_with_on_state() {
|
||||
// Test: Relay with On state converts to RelayDto with "on" state
|
||||
let relay_id = RelayId::new(3).unwrap();
|
||||
let relay = Relay::with_state(relay_id, RelayState::On);
|
||||
let dto = RelayDto::from(relay);
|
||||
|
||||
assert_eq!(dto.id, 3);
|
||||
assert_eq!(dto.state, "on");
|
||||
assert_eq!(dto.label, "Unlabeled".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_from_relay_with_off_state() {
|
||||
// Test: Relay with Off state converts to RelayDto with "off" state
|
||||
let relay_id = RelayId::new(4).unwrap();
|
||||
let relay = Relay::with_state(relay_id, RelayState::Off);
|
||||
let dto = RelayDto::from(relay);
|
||||
|
||||
assert_eq!(dto.id, 4);
|
||||
assert_eq!(dto.state, "off");
|
||||
assert_eq!(dto.label, "Unlabeled".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_from_relay_with_max_length_label() {
|
||||
// Test: Relay with maximum length label (50 chars) converts correctly
|
||||
let relay_id = RelayId::new(5).unwrap();
|
||||
let max_label = RelayLabel::new("A".repeat(50)).unwrap();
|
||||
let relay = Relay::with_label(relay_id, RelayState::Off, max_label);
|
||||
let dto = RelayDto::from(relay);
|
||||
|
||||
assert_eq!(dto.id, 5);
|
||||
assert_eq!(dto.state, "off");
|
||||
assert_eq!(dto.label, "A".repeat(50));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_from_relay_with_empty_label_becomes_none() {
|
||||
let relay_id = RelayId::new(6).unwrap();
|
||||
let relay = Relay::new(relay_id);
|
||||
let dto = RelayDto::from(relay);
|
||||
|
||||
assert_eq!(dto.id, 6);
|
||||
assert_eq!(dto.state, "off");
|
||||
assert_eq!(dto.label, "Unlabeled".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_serialization() {
|
||||
// Test: RelayDto can be serialized to JSON
|
||||
let relay_id = RelayId::new(7).unwrap();
|
||||
let label = RelayLabel::new("Test Relay".to_string()).unwrap();
|
||||
let relay = Relay::with_label(relay_id, RelayState::On, label);
|
||||
let dto = RelayDto::from(relay);
|
||||
|
||||
let json = serde_json::to_string(&dto).unwrap();
|
||||
assert_eq!(
|
||||
json,
|
||||
r#"{"id":7,"state":"on","label":"Test Relay"}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_deserialization() {
|
||||
// Test: RelayDto can be deserialized from JSON
|
||||
let json = r#"{"id":8,"state":"off","label":"Another Relay"}"#;
|
||||
let dto: RelayDto = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert_eq!(dto.id, 8);
|
||||
assert_eq!(dto.state, "off");
|
||||
assert_eq!(dto.label, "Another Relay".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_all_valid_relay_ids() {
|
||||
// Test: All valid relay IDs (1-8) convert correctly
|
||||
for id_val in 1..=8 {
|
||||
let relay_id = RelayId::new(id_val).unwrap();
|
||||
let relay = Relay::new(relay_id);
|
||||
let dto = RelayDto::from(relay);
|
||||
|
||||
assert_eq!(dto.id, id_val);
|
||||
assert_eq!(dto.state, "off");
|
||||
assert_eq!(dto.label, "Unlabeled".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_state_toggle_reflected() {
|
||||
// Test: Relay state changes are reflected in DTO
|
||||
let relay_id = RelayId::new(1).unwrap();
|
||||
let mut relay = Relay::with_state(relay_id, RelayState::Off);
|
||||
|
||||
// Initial state
|
||||
let dto1 = RelayDto::from(relay.clone());
|
||||
assert_eq!(dto1.state, "off");
|
||||
|
||||
// After toggle
|
||||
relay.toggle();
|
||||
let dto2 = RelayDto::from(relay.clone());
|
||||
assert_eq!(dto2.state, "on");
|
||||
|
||||
// After another toggle
|
||||
relay.toggle();
|
||||
let dto3 = RelayDto::from(relay);
|
||||
assert_eq!(dto3.state, "off");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relay_dto_label_change_reflected() {
|
||||
// Test: Relay label changes are reflected in DTO
|
||||
let relay_id = RelayId::new(2).unwrap();
|
||||
let mut relay = Relay::new(relay_id);
|
||||
|
||||
// Initial label (default)
|
||||
let dto1 = RelayDto::from(relay.clone());
|
||||
assert_eq!(dto1.label, "Unlabeled".to_string());
|
||||
|
||||
// After setting custom label
|
||||
let new_label = RelayLabel::new("Custom Label".to_string()).unwrap();
|
||||
relay.set_label(new_label);
|
||||
let dto2 = RelayDto::from(relay);
|
||||
assert_eq!(dto2.label, "Custom Label".to_string());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user