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 .rfind(|r| r.user_id == user_id && r.consent_type == consent_type)
139 .cloned())
140 }
141
142 async fn find_all_by_user(&self, user_id: Uuid) -> Result<Vec<ConsentRecord>, String> {
143 let records = self.records.lock().unwrap();
144 Ok(records
145 .iter()
146 .filter(|r| r.user_id == user_id)
147 .cloned()
148 .collect())
149 }
150
151 async fn has_accepted(&self, user_id: Uuid, consent_type: &str) -> Result<bool, String> {
152 let records = self.records.lock().unwrap();
153 Ok(records
154 .iter()
155 .any(|r| r.user_id == user_id && r.consent_type == consent_type))
156 }
157
158 async fn get_consent_status(&self, user_id: Uuid) -> Result<ConsentStatus, String> {
159 let records = self.records.lock().unwrap();
160 let privacy = records
161 .iter()
162 .rfind(|r| r.user_id == user_id && r.consent_type == "privacy_policy")
163 .cloned();
164 let terms = records
165 .iter()
166 .rfind(|r| r.user_id == user_id && r.consent_type == "terms")
167 .cloned();
168
169 Ok(ConsentStatus {
170 privacy_policy_accepted: privacy.is_some(),
171 terms_accepted: terms.is_some(),
172 privacy_policy_accepted_at: privacy.as_ref().map(|r| r.accepted_at),
173 terms_accepted_at: terms.as_ref().map(|r| r.accepted_at),
174 privacy_policy_version: privacy.map(|r| r.policy_version),
175 terms_version: terms.map(|r| r.policy_version),
176 })
177 }
178 }
179
180 struct MockAuditLogRepository;
182
183 #[async_trait]
184 impl AuditLogRepository for MockAuditLogRepository {
185 async fn create(&self, _entry: &AuditLogEntry) -> Result<AuditLogEntry, String> {
186 Ok(AuditLogEntry::new(
187 AuditEventType::ConsentRecorded,
188 None,
189 None,
190 ))
191 }
192 async fn find_by_id(&self, _id: Uuid) -> Result<Option<AuditLogEntry>, String> {
193 Ok(None)
194 }
195 async fn find_all_paginated(
196 &self,
197 _page_request: &PageRequest,
198 _filters: &AuditLogFilters,
199 ) -> Result<(Vec<AuditLogEntry>, i64), String> {
200 Ok((vec![], 0))
201 }
202 async fn find_recent(&self, _limit: i64) -> Result<Vec<AuditLogEntry>, String> {
203 Ok(vec![])
204 }
205 async fn find_failed_operations(
206 &self,
207 _page_request: &PageRequest,
208 _organization_id: Option<Uuid>,
209 ) -> Result<(Vec<AuditLogEntry>, i64), String> {
210 Ok((vec![], 0))
211 }
212 async fn delete_older_than(
213 &self,
214 _timestamp: chrono::DateTime<chrono::Utc>,
215 ) -> Result<i64, String> {
216 Ok(0)
217 }
218 async fn count_by_filters(&self, _filters: &AuditLogFilters) -> Result<i64, String> {
219 Ok(0)
220 }
221 }
222
223 fn make_use_cases() -> ConsentUseCases {
224 ConsentUseCases::new(
225 Arc::new(MockConsentRepository::new()),
226 Arc::new(MockAuditLogRepository),
227 )
228 }
229
230 #[tokio::test]
231 async fn test_record_privacy_policy_consent() {
232 let uc = make_use_cases();
233 let user_id = Uuid::new_v4();
234 let org_id = Uuid::new_v4();
235
236 let result = uc
237 .record_consent(
238 user_id,
239 org_id,
240 "privacy_policy",
241 None,
242 None,
243 Some("1.0".to_string()),
244 )
245 .await;
246
247 assert!(result.is_ok());
248 let response = result.unwrap();
249 assert_eq!(response.consent_type, "privacy_policy");
250 assert_eq!(response.policy_version, "1.0");
251 assert!(response.message.contains("privacy_policy"));
252 }
253
254 #[tokio::test]
255 async fn test_record_terms_consent() {
256 let uc = make_use_cases();
257 let result = uc
258 .record_consent(Uuid::new_v4(), Uuid::new_v4(), "terms", None, None, None)
259 .await;
260
261 assert!(result.is_ok());
262 assert_eq!(result.unwrap().consent_type, "terms");
263 }
264
265 #[tokio::test]
266 async fn test_record_invalid_consent_type() {
267 let uc = make_use_cases();
268 let result = uc
269 .record_consent(Uuid::new_v4(), Uuid::new_v4(), "invalid", None, None, None)
270 .await;
271
272 assert!(result.is_err());
273 assert!(result.unwrap_err().contains("Invalid consent type"));
274 }
275
276 #[tokio::test]
277 async fn test_get_consent_status_empty() {
278 let uc = make_use_cases();
279 let result = uc.get_consent_status(Uuid::new_v4()).await;
280
281 assert!(result.is_ok());
282 let status = result.unwrap();
283 assert!(!status.privacy_policy_accepted);
284 assert!(!status.terms_accepted);
285 }
286
287 #[tokio::test]
288 async fn test_get_consent_status_after_recording() {
289 let uc = make_use_cases();
290 let user_id = Uuid::new_v4();
291 let org_id = Uuid::new_v4();
292
293 uc.record_consent(user_id, org_id, "privacy_policy", None, None, None)
294 .await
295 .unwrap();
296
297 let status = uc.get_consent_status(user_id).await.unwrap();
298 assert!(status.privacy_policy_accepted);
299 assert!(!status.terms_accepted);
300 }
301
302 #[tokio::test]
303 async fn test_has_accepted() {
304 let uc = make_use_cases();
305 let user_id = Uuid::new_v4();
306 let org_id = Uuid::new_v4();
307
308 assert!(!uc.has_accepted(user_id, "terms").await.unwrap());
309
310 uc.record_consent(user_id, org_id, "terms", None, None, None)
311 .await
312 .unwrap();
313
314 assert!(uc.has_accepted(user_id, "terms").await.unwrap());
315 }
316}