koprogo_api/domain/copropriete/
board_alert.rs1use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14
15#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
17pub enum AlertSeverity {
18 Info,
19 Warning,
20 Critical,
21}
22
23impl std::fmt::Display for AlertSeverity {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 AlertSeverity::Info => write!(f, "info"),
27 AlertSeverity::Warning => write!(f, "warning"),
28 AlertSeverity::Critical => write!(f, "critical"),
29 }
30 }
31}
32
33impl std::str::FromStr for AlertSeverity {
34 type Err = String;
35
36 fn from_str(s: &str) -> Result<Self, Self::Err> {
37 match s.to_lowercase().as_str() {
38 "info" => Ok(AlertSeverity::Info),
39 "warning" => Ok(AlertSeverity::Warning),
40 "critical" => Ok(AlertSeverity::Critical),
41 _ => Err(format!("Invalid alert severity: {}", s)),
42 }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
49pub enum AlerteRefusee {
50 #[error("Le texte de l'alerte ne peut pas être vide")]
51 TexteVide,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct BoardAlert {
65 pub id: Uuid,
66 pub building_id: Uuid,
67 pub raised_by_board_member_id: Uuid,
71 pub text: String,
72 pub severity: AlertSeverity,
73 pub target_meeting_id: Uuid,
74 pub created_at: DateTime<Utc>,
75}
76
77impl BoardAlert {
78 pub fn new(
79 building_id: Uuid,
80 raised_by_board_member_id: Uuid,
81 text: String,
82 severity: AlertSeverity,
83 target_meeting_id: Uuid,
84 ) -> Result<Self, AlerteRefusee> {
85 if text.trim().is_empty() {
86 return Err(AlerteRefusee::TexteVide);
87 }
88
89 Ok(Self {
90 id: Uuid::new_v4(),
91 building_id,
92 raised_by_board_member_id,
93 text,
94 severity,
95 target_meeting_id,
96 created_at: Utc::now(),
97 })
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 fn ids() -> (Uuid, Uuid, Uuid) {
106 (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4())
107 }
108
109 #[test]
112 fn happy_create_alert_succeeds() {
113 let (building_id, member_id, meeting_id) = ids();
114 let alert = BoardAlert::new(
115 building_id,
116 member_id,
117 "L'ascenseur est bloqué depuis trois jours".to_string(),
118 AlertSeverity::Warning,
119 meeting_id,
120 )
121 .unwrap();
122
123 assert_eq!(alert.building_id, building_id);
124 assert_eq!(alert.raised_by_board_member_id, member_id);
125 assert_eq!(alert.target_meeting_id, meeting_id);
126 assert_eq!(alert.severity, AlertSeverity::Warning);
127 }
128
129 #[test]
134 fn edge_whitespace_only_text_is_refused() {
135 let (building_id, member_id, meeting_id) = ids();
136 let result = BoardAlert::new(
137 building_id,
138 member_id,
139 " \t ".to_string(),
140 AlertSeverity::Info,
141 meeting_id,
142 );
143 assert_eq!(result.unwrap_err(), AlerteRefusee::TexteVide);
144 }
145
146 #[test]
149 fn negative_empty_text_is_refused() {
150 let (building_id, member_id, meeting_id) = ids();
151 let result = BoardAlert::new(
152 building_id,
153 member_id,
154 String::new(),
155 AlertSeverity::Critical,
156 meeting_id,
157 );
158 assert_eq!(result.unwrap_err(), AlerteRefusee::TexteVide);
159 }
160
161 #[test]
162 fn negative_unknown_severity_string_fails_to_parse() {
163 let result: Result<AlertSeverity, String> = "urgentissime".parse();
164 assert!(result.is_err());
165 }
166
167 #[test]
168 fn happy_severity_display_and_from_str_round_trip() {
169 for s in [
170 AlertSeverity::Info,
171 AlertSeverity::Warning,
172 AlertSeverity::Critical,
173 ] {
174 let parsed: AlertSeverity = s.to_string().parse().unwrap();
175 assert_eq!(parsed, s);
176 }
177 }
178}