koprogo_api/application/use_cases/
consent_use_cases.rs1use crate::application::dto::consent_dto::{ConsentRecordedResponse, ConsentStatusResponse};
2use crate::application::ports::audit_log_repository::AuditLogRepository;
3use crate::application::ports::consent_repository::ConsentRepository;
4use crate::domain::entities::consent::ConsentRecord;
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use std::sync::Arc;
7use uuid::Uuid;
8
9pub struct ConsentUseCases {
10 consent_repository: Arc<dyn ConsentRepository>,
11 audit_repository: Arc<dyn AuditLogRepository>,
12}
13
14impl ConsentUseCases {
15 pub fn new(
16 consent_repository: Arc<dyn ConsentRepository>,
17 audit_repository: Arc<dyn AuditLogRepository>,
18 ) -> Self {
19 Self {
20 consent_repository,
21 audit_repository,
22 }
23 }
24
25 pub async fn record_consent(
29 &self,
30 user_id: Uuid,
31 organization_id: Uuid,
32 consent_type: &str,
33 ip_address: Option<String>,
34 user_agent: Option<String>,
35 policy_version: Option<String>,
36 ) -> Result<ConsentRecordedResponse, String> {
37 let record = ConsentRecord::new(
39 user_id,
40 organization_id,
41 consent_type,
42 ip_address.clone(),
43 user_agent.clone(),
44 policy_version,
45 )?;
46
47 let saved = self.consent_repository.create(&record).await?;
49
50 let audit_entry = AuditLogEntry::new(
52 AuditEventType::ConsentRecorded,
53 Some(user_id),
54 Some(organization_id),
55 )
56 .with_resource("ConsentRecord", saved.id)
57 .with_client_info(ip_address, user_agent)
58 .with_metadata(serde_json::json!({
59 "consent_type": saved.consent_type,
60 "policy_version": saved.policy_version,
61 }));
62
63 let audit_repo = self.audit_repository.clone();
64 tokio::spawn(async move {
65 let _ = audit_repo.create(&audit_entry).await;
66 });
67
68 Ok(ConsentRecordedResponse {
69 message: format!("Consent for {} recorded successfully", saved.consent_type),
70 consent_type: saved.consent_type,
71 accepted_at: saved.accepted_at,
72 policy_version: saved.policy_version,
73 })
74 }
75
76 pub async fn get_consent_status(&self, user_id: Uuid) -> Result<ConsentStatusResponse, String> {
80 let status = self.consent_repository.get_consent_status(user_id).await?;
81
82 Ok(ConsentStatusResponse {
83 privacy_policy_accepted: status.privacy_policy_accepted,
84 terms_accepted: status.terms_accepted,
85 privacy_policy_accepted_at: status.privacy_policy_accepted_at,
86 terms_accepted_at: status.terms_accepted_at,
87 privacy_policy_version: status.privacy_policy_version,
88 terms_version: status.terms_version,
89 })
90 }
91
92 pub async fn has_accepted(&self, user_id: Uuid, consent_type: &str) -> Result<bool, String> {
94 self.consent_repository
95 .has_accepted(user_id, consent_type)
96 .await
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::application::dto::PageRequest;
104 use crate::application::ports::audit_log_repository::AuditLogFilters;
105 use crate::domain::entities::consent::ConsentStatus;
106 use async_trait::async_trait;
107 use std::sync::Mutex;
108
109 struct MockConsentRepository {
111 records: Mutex<Vec<ConsentRecord>>,
112 }
113
114 impl MockConsentRepository {
115 fn new() -> Self {
116 Self {
117 records: Mutex::new(Vec::new()),
118 }
119 }
120 }
121
122 #[async_trait]
123 impl ConsentRepository for MockConsentRepository {
124 async fn create(&self, record: &ConsentRecord) -> Result<ConsentRecord, String> {
125 let mut records = self.records.lock().unwrap();
126 records.push(record.clone());
127 Ok(record.clone())
128 }
129
130 async fn find_latest_by_user_and_type(
131 &self,
132 user_id: Uuid,
133 consent_type: &str,
134 ) -> Result<Option<ConsentRecord>, String> {
135 let records = self.records.lock().unwrap();
136 Ok(records
137 .iter()
138 .filter(|r| r.user_id == user_id && r.consent_type == consent_type)
139 .last()
140 .cloned())
141 }
142
143 async fn find_all_by_user(&self, user_id: Uuid) -> Result<Vec<ConsentRecord>, String> {
144 let records = self.records.lock().unwrap();
145 Ok(records
146 .iter()
147 .filter(|r| r.user_id == user_id)
148 .cloned()
149 .collect())
150 }
151
152 async fn has_accepted(&self, user_id: Uuid, consent_type: &str) -> Result<bool, String> {
153 let records = self.records.lock().unwrap();
154 Ok(records
155 .iter()
156 .any(|r| r.user_id == user_id && r.consent_type == consent_type))
157 }
158
159 async fn get_consent_status(&self, user_id: Uuid) -> Result<ConsentStatus, String> {
160 let records = self.records.lock().unwrap();
161 let privacy = records
162 .iter()
163 .filter(|r| r.user_id == user_id && r.consent_type == "privacy_policy")
164 .last()
165 .cloned();
166 let terms = records
167 .iter()
168 .filter(|r| r.user_id == user_id && r.consent_type == "terms")
169 .last()
170 .cloned();
171
172 Ok(ConsentStatus {
173 privacy_policy_accepted: privacy.is_some(),
174 terms_accepted: terms.is_some(),
175 privacy_policy_accepted_at: privacy.as_ref().map(|r| r.accepted_at),
176 terms_accepted_at: terms.as_ref().map(|r| r.accepted_at),
177 privacy_policy_version: privacy.map(|r| r.policy_version),
178 terms_version: terms.map(|r| r.policy_version),
179 })
180 }
181 }
182
183 struct MockAuditLogRepository;
185
186 #[async_trait]
187 impl AuditLogRepository for MockAuditLogRepository {
188 async fn create(&self, _entry: &AuditLogEntry) -> Result<AuditLogEntry, String> {
189 Ok(AuditLogEntry::new(
190 AuditEventType::ConsentRecorded,
191 None,
192 None,
193 ))
194 }
195 async fn find_by_id(&self, _id: Uuid) -> Result<Option<AuditLogEntry>, String> {
196 Ok(None)
197 }
198 async fn find_all_paginated(
199 &self,
200 _page_request: &PageRequest,
201 _filters: &AuditLogFilters,
202 ) -> Result<(Vec<AuditLogEntry>, i64), String> {
203 Ok((vec![], 0))
204 }
205 async fn find_recent(&self, _limit: i64) -> Result<Vec<AuditLogEntry>, String> {
206 Ok(vec![])
207 }
208 async fn find_failed_operations(
209 &self,
210 _page_request: &PageRequest,
211 _organization_id: Option<Uuid>,
212 ) -> Result<(Vec<AuditLogEntry>, i64), String> {
213 Ok((vec![], 0))
214 }
215 async fn delete_older_than(
216 &self,
217 _timestamp: chrono::DateTime<chrono::Utc>,
218 ) -> Result<i64, String> {
219 Ok(0)
220 }
221 async fn count_by_filters(&self, _filters: &AuditLogFilters) -> Result<i64, String> {
222 Ok(0)
223 }
224 }
225
226 fn make_use_cases() -> ConsentUseCases {
227 ConsentUseCases::new(
228 Arc::new(MockConsentRepository::new()),
229 Arc::new(MockAuditLogRepository),
230 )
231 }
232
233 #[tokio::test]
234 async fn test_record_privacy_policy_consent() {
235 let uc = make_use_cases();
236 let user_id = Uuid::new_v4();
237 let org_id = Uuid::new_v4();
238
239 let result = uc
240 .record_consent(
241 user_id,
242 org_id,
243 "privacy_policy",
244 None,
245 None,
246 Some("1.0".to_string()),
247 )
248 .await;
249
250 assert!(result.is_ok());
251 let response = result.unwrap();
252 assert_eq!(response.consent_type, "privacy_policy");
253 assert_eq!(response.policy_version, "1.0");
254 assert!(response.message.contains("privacy_policy"));
255 }
256
257 #[tokio::test]
258 async fn test_record_terms_consent() {
259 let uc = make_use_cases();
260 let result = uc
261 .record_consent(Uuid::new_v4(), Uuid::new_v4(), "terms", None, None, None)
262 .await;
263
264 assert!(result.is_ok());
265 assert_eq!(result.unwrap().consent_type, "terms");
266 }
267
268 #[tokio::test]
269 async fn test_record_invalid_consent_type() {
270 let uc = make_use_cases();
271 let result = uc
272 .record_consent(Uuid::new_v4(), Uuid::new_v4(), "invalid", None, None, None)
273 .await;
274
275 assert!(result.is_err());
276 assert!(result.unwrap_err().contains("Invalid consent type"));
277 }
278
279 #[tokio::test]
280 async fn test_get_consent_status_empty() {
281 let uc = make_use_cases();
282 let result = uc.get_consent_status(Uuid::new_v4()).await;
283
284 assert!(result.is_ok());
285 let status = result.unwrap();
286 assert!(!status.privacy_policy_accepted);
287 assert!(!status.terms_accepted);
288 }
289
290 #[tokio::test]
291 async fn test_get_consent_status_after_recording() {
292 let uc = make_use_cases();
293 let user_id = Uuid::new_v4();
294 let org_id = Uuid::new_v4();
295
296 uc.record_consent(user_id, org_id, "privacy_policy", None, None, None)
297 .await
298 .unwrap();
299
300 let status = uc.get_consent_status(user_id).await.unwrap();
301 assert!(status.privacy_policy_accepted);
302 assert!(!status.terms_accepted);
303 }
304
305 #[tokio::test]
306 async fn test_has_accepted() {
307 let uc = make_use_cases();
308 let user_id = Uuid::new_v4();
309 let org_id = Uuid::new_v4();
310
311 assert!(!uc.has_accepted(user_id, "terms").await.unwrap());
312
313 uc.record_consent(user_id, org_id, "terms", None, None, None)
314 .await
315 .unwrap();
316
317 assert!(uc.has_accepted(user_id, "terms").await.unwrap());
318 }
319}