koprogo_api/domain/entities/
vote.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7#[serde(rename_all = "snake_case")]
8pub enum VoteChoice {
9 Pour, Contre, Abstention, }
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct Vote {
17 pub id: Uuid,
18 pub resolution_id: Uuid,
19 pub owner_id: Uuid,
20 pub unit_id: Uuid,
21 pub vote_choice: VoteChoice,
22 pub voting_power: f64, pub proxy_owner_id: Option<Uuid>, pub voted_at: DateTime<Utc>,
25}
26
27impl Vote {
28 pub fn new(
30 resolution_id: Uuid,
31 owner_id: Uuid,
32 unit_id: Uuid,
33 vote_choice: VoteChoice,
34 voting_power: f64,
35 proxy_owner_id: Option<Uuid>,
36 ) -> Result<Self, String> {
37 if voting_power <= 0.0 {
39 return Err("Voting power must be positive".to_string());
40 }
41 if voting_power > 1000.0 {
42 return Err("Voting power exceeds maximum (1000 millièmes)".to_string());
43 }
44
45 if let Some(proxy_id) = proxy_owner_id {
47 if proxy_id == owner_id {
48 return Err("Owner cannot be their own proxy".to_string());
49 }
50 }
51
52 Ok(Self {
53 id: Uuid::new_v4(),
54 resolution_id,
55 owner_id,
56 unit_id,
57 vote_choice,
58 voting_power,
59 proxy_owner_id,
60 voted_at: Utc::now(),
61 })
62 }
63
64 pub fn is_proxy_vote(&self) -> bool {
66 self.proxy_owner_id.is_some()
67 }
68
69 pub fn effective_voter_id(&self) -> Uuid {
71 self.proxy_owner_id.unwrap_or(self.owner_id)
72 }
73
74 pub fn change_vote(&mut self, new_choice: VoteChoice) -> Result<(), String> {
76 self.vote_choice = new_choice;
80 self.voted_at = Utc::now();
81 Ok(())
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_create_vote_success() {
91 let resolution_id = Uuid::new_v4();
92 let owner_id = Uuid::new_v4();
93 let unit_id = Uuid::new_v4();
94
95 let vote = Vote::new(
96 resolution_id,
97 owner_id,
98 unit_id,
99 VoteChoice::Pour,
100 150.0, None,
102 );
103
104 assert!(vote.is_ok());
105 let vote = vote.unwrap();
106 assert_eq!(vote.resolution_id, resolution_id);
107 assert_eq!(vote.owner_id, owner_id);
108 assert_eq!(vote.unit_id, unit_id);
109 assert_eq!(vote.vote_choice, VoteChoice::Pour);
110 assert_eq!(vote.voting_power, 150.0);
111 assert!(!vote.is_proxy_vote());
112 assert_eq!(vote.effective_voter_id(), owner_id);
113 }
114
115 #[test]
116 fn test_create_vote_with_proxy() {
117 let resolution_id = Uuid::new_v4();
118 let owner_id = Uuid::new_v4();
119 let unit_id = Uuid::new_v4();
120 let proxy_id = Uuid::new_v4();
121
122 let vote = Vote::new(
123 resolution_id,
124 owner_id,
125 unit_id,
126 VoteChoice::Contre,
127 200.0,
128 Some(proxy_id),
129 );
130
131 assert!(vote.is_ok());
132 let vote = vote.unwrap();
133 assert!(vote.is_proxy_vote());
134 assert_eq!(vote.effective_voter_id(), proxy_id);
135 assert_eq!(vote.proxy_owner_id, Some(proxy_id));
136 }
137
138 #[test]
139 fn test_create_vote_zero_voting_power_fails() {
140 let resolution_id = Uuid::new_v4();
141 let owner_id = Uuid::new_v4();
142 let unit_id = Uuid::new_v4();
143
144 let vote = Vote::new(
145 resolution_id,
146 owner_id,
147 unit_id,
148 VoteChoice::Pour,
149 0.0,
150 None,
151 );
152
153 assert!(vote.is_err());
154 assert_eq!(vote.unwrap_err(), "Voting power must be positive");
155 }
156
157 #[test]
158 fn test_create_vote_negative_voting_power_fails() {
159 let resolution_id = Uuid::new_v4();
160 let owner_id = Uuid::new_v4();
161 let unit_id = Uuid::new_v4();
162
163 let vote = Vote::new(
164 resolution_id,
165 owner_id,
166 unit_id,
167 VoteChoice::Pour,
168 -50.0,
169 None,
170 );
171
172 assert!(vote.is_err());
173 assert_eq!(vote.unwrap_err(), "Voting power must be positive");
174 }
175
176 #[test]
177 fn test_create_vote_excessive_voting_power_fails() {
178 let resolution_id = Uuid::new_v4();
179 let owner_id = Uuid::new_v4();
180 let unit_id = Uuid::new_v4();
181
182 let vote = Vote::new(
183 resolution_id,
184 owner_id,
185 unit_id,
186 VoteChoice::Pour,
187 1500.0, None,
189 );
190
191 assert!(vote.is_err());
192 assert!(vote.unwrap_err().contains("exceeds maximum"));
193 }
194
195 #[test]
196 fn test_create_vote_self_proxy_fails() {
197 let resolution_id = Uuid::new_v4();
198 let owner_id = Uuid::new_v4();
199 let unit_id = Uuid::new_v4();
200
201 let vote = Vote::new(
202 resolution_id,
203 owner_id,
204 unit_id,
205 VoteChoice::Pour,
206 150.0,
207 Some(owner_id), );
209
210 assert!(vote.is_err());
211 assert_eq!(vote.unwrap_err(), "Owner cannot be their own proxy");
212 }
213
214 #[test]
215 fn test_change_vote() {
216 let resolution_id = Uuid::new_v4();
217 let owner_id = Uuid::new_v4();
218 let unit_id = Uuid::new_v4();
219
220 let mut vote = Vote::new(
221 resolution_id,
222 owner_id,
223 unit_id,
224 VoteChoice::Pour,
225 150.0,
226 None,
227 )
228 .unwrap();
229
230 assert_eq!(vote.vote_choice, VoteChoice::Pour);
231
232 let result = vote.change_vote(VoteChoice::Contre);
233 assert!(result.is_ok());
234 assert_eq!(vote.vote_choice, VoteChoice::Contre);
235 }
236
237 #[test]
238 fn test_vote_choice_serialization() {
239 let pour = VoteChoice::Pour;
241 let contre = VoteChoice::Contre;
242 let abstention = VoteChoice::Abstention;
243
244 let json_pour = serde_json::to_string(&pour).unwrap();
245 let json_contre = serde_json::to_string(&contre).unwrap();
246 let json_abstention = serde_json::to_string(&abstention).unwrap();
247
248 assert_eq!(json_pour, "\"pour\"");
249 assert_eq!(json_contre, "\"contre\"");
250 assert_eq!(json_abstention, "\"abstention\"");
251 }
252
253 #[test]
254 fn test_vote_choice_deserialization() {
255 let pour: VoteChoice = serde_json::from_str("\"pour\"").unwrap();
257 let contre: VoteChoice = serde_json::from_str("\"contre\"").unwrap();
258 let abstention: VoteChoice = serde_json::from_str("\"abstention\"").unwrap();
259
260 assert_eq!(pour, VoteChoice::Pour);
261 assert_eq!(contre, VoteChoice::Contre);
262 assert_eq!(abstention, VoteChoice::Abstention);
263 }
264}