koprogo_api/domain/entities/
poll_vote.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// PollVote - Individual vote cast on a poll
6///
7/// Tracks votes from owners on poll questions.
8/// Can be anonymous or linked to specific owner.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct PollVote {
11    pub id: Uuid,
12    pub poll_id: Uuid,
13    pub owner_id: Option<Uuid>, // None if anonymous voting
14    pub building_id: Uuid,
15
16    // Vote details
17    pub selected_option_ids: Vec<Uuid>, // Multiple for MultipleChoice with allow_multiple_votes
18    pub rating_value: Option<i32>,      // For Rating polls (1-5)
19    pub open_text: Option<String>,      // For OpenEnded polls
20
21    // Metadata
22    pub voted_at: DateTime<Utc>,
23    pub ip_address: Option<String>, // For audit trail
24}
25
26impl PollVote {
27    pub fn new(
28        poll_id: Uuid,
29        owner_id: Option<Uuid>,
30        building_id: Uuid,
31        selected_option_ids: Vec<Uuid>,
32        rating_value: Option<i32>,
33        open_text: Option<String>,
34    ) -> Result<Self, String> {
35        // Validation
36        if selected_option_ids.is_empty() && rating_value.is_none() && open_text.is_none() {
37            return Err("Vote must have at least one selection".to_string());
38        }
39
40        if let Some(rating) = rating_value {
41            if !(1..=5).contains(&rating) {
42                return Err("Rating must be between 1 and 5".to_string());
43            }
44        }
45
46        Ok(Self {
47            id: Uuid::new_v4(),
48            poll_id,
49            owner_id,
50            building_id,
51            selected_option_ids,
52            rating_value,
53            open_text,
54            voted_at: Utc::now(),
55            ip_address: None,
56        })
57    }
58
59    /// Check if this is an anonymous vote
60    pub fn is_anonymous(&self) -> bool {
61        self.owner_id.is_none()
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_poll_vote_creation() {
71        let vote = PollVote::new(
72            Uuid::new_v4(),
73            Some(Uuid::new_v4()),
74            Uuid::new_v4(),
75            vec![Uuid::new_v4()],
76            None,
77            None,
78        );
79
80        assert!(vote.is_ok());
81        let vote = vote.unwrap();
82        assert!(!vote.is_anonymous());
83        assert_eq!(vote.selected_option_ids.len(), 1);
84    }
85
86    #[test]
87    fn test_anonymous_vote() {
88        let vote = PollVote::new(
89            Uuid::new_v4(),
90            None, // Anonymous
91            Uuid::new_v4(),
92            vec![Uuid::new_v4()],
93            None,
94            None,
95        );
96
97        assert!(vote.is_ok());
98        let vote = vote.unwrap();
99        assert!(vote.is_anonymous());
100    }
101
102    #[test]
103    fn test_rating_vote() {
104        let vote = PollVote::new(
105            Uuid::new_v4(),
106            Some(Uuid::new_v4()),
107            Uuid::new_v4(),
108            vec![],
109            Some(4),
110            None,
111        );
112
113        assert!(vote.is_ok());
114        let vote = vote.unwrap();
115        assert_eq!(vote.rating_value, Some(4));
116    }
117
118    #[test]
119    fn test_rating_validation() {
120        // Rating too low
121        let result = PollVote::new(
122            Uuid::new_v4(),
123            Some(Uuid::new_v4()),
124            Uuid::new_v4(),
125            vec![],
126            Some(0),
127            None,
128        );
129        assert!(result.is_err());
130
131        // Rating too high
132        let result = PollVote::new(
133            Uuid::new_v4(),
134            Some(Uuid::new_v4()),
135            Uuid::new_v4(),
136            vec![],
137            Some(6),
138            None,
139        );
140        assert!(result.is_err());
141    }
142
143    #[test]
144    fn test_open_ended_vote() {
145        let vote = PollVote::new(
146            Uuid::new_v4(),
147            Some(Uuid::new_v4()),
148            Uuid::new_v4(),
149            vec![],
150            None,
151            Some("Je préfère le bleu pour le hall".to_string()),
152        );
153
154        assert!(vote.is_ok());
155        let vote = vote.unwrap();
156        assert!(vote.open_text.is_some());
157    }
158
159    #[test]
160    fn test_empty_vote() {
161        let result = PollVote::new(
162            Uuid::new_v4(),
163            Some(Uuid::new_v4()),
164            Uuid::new_v4(),
165            vec![],
166            None,
167            None,
168        );
169
170        assert!(result.is_err());
171    }
172}