koprogo_api/domain/entities/
vote.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// Choix de vote d'un copropriétaire
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7#[serde(rename_all = "snake_case")]
8pub enum VoteChoice {
9    Pour,       // Vote en faveur (For)
10    Contre,     // Vote contre (Against)
11    Abstention, // Abstention
12}
13
14/// Vote d'un propriétaire sur une résolution
15#[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,            // Tantièmes/millièmes du lot
23    pub proxy_owner_id: Option<Uuid>, // ID du mandataire si vote par procuration
24    pub voted_at: DateTime<Utc>,
25}
26
27impl Vote {
28    /// Crée un nouveau vote
29    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        // Validation du pouvoir de vote
38        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        // Validation de la procuration
46        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    /// Vérifie si le vote est exprimé par procuration
65    pub fn is_proxy_vote(&self) -> bool {
66        self.proxy_owner_id.is_some()
67    }
68
69    /// Retourne l'ID du votant effectif (propriétaire ou mandataire)
70    pub fn effective_voter_id(&self) -> Uuid {
71        self.proxy_owner_id.unwrap_or(self.owner_id)
72    }
73
74    /// Modifie le choix de vote (seulement si pas encore enregistré)
75    pub fn change_vote(&mut self, new_choice: VoteChoice) -> Result<(), String> {
76        // En pratique, cette méthode ne serait appelée que pendant une fenêtre de temps limitée
77        // Ici on autorise le changement, mais dans l'application on pourrait ajouter une validation
78        // basée sur le timing (ex: vote modifiable uniquement dans les 5 minutes)
79        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, // 150 millièmes
101            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, // Exceeds max
188            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), // Self as proxy
208        );
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        // Test serialization of VoteChoice enum
240        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        // Test deserialization of VoteChoice enum
256        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}