1use crate::security::{AuditManager, AuditSeverity, GdprConfig, Result, SecurityError};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use sqlx::{PgPool, Row};
5use std::collections::HashMap;
6use std::sync::Arc;
7use tracing::{debug, info, warn};
8use uuid::Uuid;
9
10pub struct ComplianceManager {
12 config: GdprConfig,
13 db_pool: Arc<PgPool>,
14 audit_manager: Option<Arc<AuditManager>>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub enum DataSubjectRequestType {
20 Access, Rectification, Erasure, Portability, Restriction, Objection, }
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct DataSubjectRequest {
31 pub id: String,
32 pub request_type: DataSubjectRequestType,
33 pub subject_id: String,
34 pub subject_email: Option<String>,
35 pub requested_at: DateTime<Utc>,
36 pub status: RequestStatus,
37 pub processed_at: Option<DateTime<Utc>>,
38 pub processed_by: Option<String>,
39 pub notes: Option<String>,
40 pub data_export: Option<String>, }
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub enum RequestStatus {
46 Pending,
47 InProgress,
48 Completed,
49 Rejected,
50 PartiallyCompleted,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub enum DataCategory {
56 Identity, Contact, Technical, Usage, Generated, Metadata, }
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct RetentionPolicy {
67 pub category: DataCategory,
68 pub retention_days: u32,
69 pub auto_delete: bool,
70 pub legal_basis: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct ProcessingRecord {
76 pub id: String,
77 pub subject_id: String,
78 pub category: DataCategory,
79 pub purpose: String,
80 pub legal_basis: String,
81 pub processed_at: DateTime<Utc>,
82 pub retention_until: Option<DateTime<Utc>>,
83 pub consent_given: Option<bool>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ConsentRecord {
89 pub id: String,
90 pub subject_id: String,
91 pub purpose: String,
92 pub consent_given: bool,
93 pub given_at: DateTime<Utc>,
94 pub withdrawn_at: Option<DateTime<Utc>>,
95 pub version: String,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct DataExport {
101 pub subject_id: String,
102 pub exported_at: DateTime<Utc>,
103 pub data_categories: Vec<DataCategory>,
104 pub personal_data: HashMap<String, serde_json::Value>,
105 pub metadata: HashMap<String, String>,
106}
107
108impl ComplianceManager {
109 pub fn new(config: GdprConfig, db_pool: Arc<PgPool>) -> Self {
110 Self {
111 config,
112 db_pool,
113 audit_manager: None,
114 }
115 }
116
117 pub fn with_audit_manager(mut self, audit_manager: Arc<AuditManager>) -> Self {
118 self.audit_manager = Some(audit_manager);
119 self
120 }
121
122 pub async fn initialize(&self) -> Result<()> {
124 if !self.config.enabled {
125 debug!("GDPR compliance is disabled");
126 return Ok(());
127 }
128
129 info!("Initializing GDPR compliance system");
130
131 let create_requests_table = r#"
133 CREATE TABLE IF NOT EXISTS gdpr_requests (
134 id UUID PRIMARY KEY,
135 request_type TEXT NOT NULL,
136 subject_id TEXT NOT NULL,
137 subject_email TEXT,
138 requested_at TIMESTAMPTZ NOT NULL,
139 status TEXT NOT NULL,
140 processed_at TIMESTAMPTZ,
141 processed_by TEXT,
142 notes TEXT,
143 data_export JSONB,
144 created_at TIMESTAMPTZ DEFAULT NOW()
145 );
146 "#;
147
148 let create_consent_table = r#"
150 CREATE TABLE IF NOT EXISTS gdpr_consent (
151 id UUID PRIMARY KEY,
152 subject_id TEXT NOT NULL,
153 purpose TEXT NOT NULL,
154 consent_given BOOLEAN NOT NULL,
155 given_at TIMESTAMPTZ NOT NULL,
156 withdrawn_at TIMESTAMPTZ,
157 version TEXT NOT NULL,
158 created_at TIMESTAMPTZ DEFAULT NOW()
159 );
160 "#;
161
162 let create_processing_table = r#"
164 CREATE TABLE IF NOT EXISTS gdpr_processing (
165 id UUID PRIMARY KEY,
166 subject_id TEXT NOT NULL,
167 category TEXT NOT NULL,
168 purpose TEXT NOT NULL,
169 legal_basis TEXT NOT NULL,
170 processed_at TIMESTAMPTZ NOT NULL,
171 retention_until TIMESTAMPTZ,
172 consent_given BOOLEAN,
173 created_at TIMESTAMPTZ DEFAULT NOW()
174 );
175 "#;
176
177 for sql in [
178 create_requests_table,
179 create_consent_table,
180 create_processing_table,
181 ] {
182 sqlx::query(sql)
183 .execute(self.db_pool.as_ref())
184 .await
185 .map_err(|e| SecurityError::GdprError {
186 message: format!("Failed to create GDPR table: {e}"),
187 })?;
188 }
189
190 let create_indexes = vec![
192 "CREATE INDEX IF NOT EXISTS idx_gdpr_requests_subject_id ON gdpr_requests (subject_id);",
193 "CREATE INDEX IF NOT EXISTS idx_gdpr_requests_status ON gdpr_requests (status);",
194 "CREATE INDEX IF NOT EXISTS idx_gdpr_consent_subject_id ON gdpr_consent (subject_id);",
195 "CREATE INDEX IF NOT EXISTS idx_gdpr_processing_subject_id ON gdpr_processing (subject_id);",
196 "CREATE INDEX IF NOT EXISTS idx_gdpr_processing_retention ON gdpr_processing (retention_until);",
197 ];
198
199 for sql in create_indexes {
200 sqlx::query(sql)
201 .execute(self.db_pool.as_ref())
202 .await
203 .map_err(|e| SecurityError::GdprError {
204 message: format!("Failed to create GDPR index: {e}"),
205 })?;
206 }
207
208 info!("GDPR compliance system initialized");
209 Ok(())
210 }
211
212 pub async fn submit_request(
214 &self,
215 request_type: DataSubjectRequestType,
216 subject_id: &str,
217 subject_email: Option<&str>,
218 ) -> Result<String> {
219 if !self.config.enabled {
220 return Err(SecurityError::GdprError {
221 message: "GDPR compliance is disabled".to_string(),
222 });
223 }
224
225 let request_id = Uuid::new_v4().to_string();
226 let now = Utc::now();
227
228 let request = DataSubjectRequest {
229 id: request_id.clone(),
230 request_type: request_type.clone(),
231 subject_id: subject_id.to_string(),
232 subject_email: subject_email.map(|s| s.to_string()),
233 requested_at: now,
234 status: RequestStatus::Pending,
235 processed_at: None,
236 processed_by: None,
237 notes: None,
238 data_export: None,
239 };
240
241 let insert_sql = r#"
242 INSERT INTO gdpr_requests (id, request_type, subject_id, subject_email, requested_at, status)
243 VALUES ($1, $2, $3, $4, $5, $6)
244 "#;
245
246 sqlx::query(insert_sql)
247 .bind(&request.id)
248 .bind(format!("{:?}", request.request_type))
249 .bind(&request.subject_id)
250 .bind(&request.subject_email)
251 .bind(request.requested_at)
252 .bind(format!("{:?}", request.status))
253 .execute(self.db_pool.as_ref())
254 .await
255 .map_err(|e| SecurityError::GdprError {
256 message: format!("Failed to submit GDPR request: {e}"),
257 })?;
258
259 if let Some(audit_manager) = &self.audit_manager {
261 let mut details = HashMap::new();
262 details.insert(
263 "request_id".to_string(),
264 serde_json::Value::String(request_id.clone()),
265 );
266 details.insert(
267 "request_type".to_string(),
268 serde_json::Value::String(format!("{request_type:?}")),
269 );
270 details.insert(
271 "subject_id".to_string(),
272 serde_json::Value::String(subject_id.to_string()),
273 );
274
275 let _ = audit_manager
276 .log_security_event(
277 "gdpr_request_submitted",
278 AuditSeverity::Medium,
279 Some(subject_id),
280 None,
281 details,
282 )
283 .await;
284 }
285
286 info!(
287 "GDPR request submitted: {:?} for subject: {}",
288 request_type, subject_id
289 );
290 Ok(request_id)
291 }
292
293 pub async fn process_erasure_request(
295 &self,
296 request_id: &str,
297 processor_id: &str,
298 ) -> Result<()> {
299 if !self.config.enabled || !self.config.right_to_be_forgotten {
300 return Err(SecurityError::GdprError {
301 message: "Right to be forgotten is not enabled".to_string(),
302 });
303 }
304
305 let request = self.get_request(request_id).await?;
307
308 if !matches!(request.request_type, DataSubjectRequestType::Erasure) {
309 return Err(SecurityError::GdprError {
310 message: "Request is not an erasure request".to_string(),
311 });
312 }
313
314 if !matches!(
315 request.status,
316 RequestStatus::Pending | RequestStatus::InProgress
317 ) {
318 return Err(SecurityError::GdprError {
319 message: "Request is not in a processable state".to_string(),
320 });
321 }
322
323 self.update_request_status(request_id, RequestStatus::InProgress, Some(processor_id))
325 .await?;
326
327 let erasure_result = self.erase_personal_data(&request.subject_id).await;
329
330 match erasure_result {
331 Ok(erased_count) => {
332 let notes = format!("Successfully erased {erased_count} data records");
334 self.complete_request(request_id, processor_id, Some(¬es))
335 .await?;
336
337 info!(
338 "Erasure request completed for subject: {} ({} records erased)",
339 request.subject_id, erased_count
340 );
341 }
342 Err(e) => {
343 let notes = format!("Erasure partially failed: {e}");
345 self.update_request_status(
346 request_id,
347 RequestStatus::PartiallyCompleted,
348 Some(processor_id),
349 )
350 .await?;
351 self.update_request_notes(request_id, ¬es).await?;
352
353 warn!(
354 "Erasure request partially failed for subject: {}: {}",
355 request.subject_id, e
356 );
357 }
358 }
359
360 Ok(())
361 }
362
363 pub async fn process_portability_request(
365 &self,
366 request_id: &str,
367 processor_id: &str,
368 ) -> Result<DataExport> {
369 if !self.config.enabled {
370 return Err(SecurityError::GdprError {
371 message: "GDPR compliance is disabled".to_string(),
372 });
373 }
374
375 let request = self.get_request(request_id).await?;
376
377 if !matches!(request.request_type, DataSubjectRequestType::Portability) {
378 return Err(SecurityError::GdprError {
379 message: "Request is not a portability request".to_string(),
380 });
381 }
382
383 self.update_request_status(request_id, RequestStatus::InProgress, Some(processor_id))
385 .await?;
386
387 let data_export = self.export_personal_data(&request.subject_id).await?;
389
390 let export_json =
392 serde_json::to_string(&data_export).map_err(|e| SecurityError::GdprError {
393 message: format!("Failed to serialize data export: {e}"),
394 })?;
395
396 sqlx::query("UPDATE gdpr_requests SET data_export = $1 WHERE id = $2")
397 .bind(&export_json)
398 .bind(request_id)
399 .execute(self.db_pool.as_ref())
400 .await
401 .map_err(|e| SecurityError::GdprError {
402 message: format!("Failed to store data export: {e}"),
403 })?;
404
405 let notes = format!(
407 "Data exported successfully ({} categories)",
408 data_export.data_categories.len()
409 );
410 self.complete_request(request_id, processor_id, Some(¬es))
411 .await?;
412
413 info!(
414 "Portability request completed for subject: {}",
415 request.subject_id
416 );
417 Ok(data_export)
418 }
419
420 async fn erase_personal_data(&self, subject_id: &str) -> Result<u32> {
422 let mut total_erased = 0;
423
424 let memory_result: i64 =
426 sqlx::query_scalar("DELETE FROM memories WHERE user_id = $1 RETURNING COUNT(*)")
427 .bind(subject_id)
428 .fetch_optional(self.db_pool.as_ref())
429 .await
430 .map_err(|e| SecurityError::GdprError {
431 message: format!("Failed to erase memory data: {e}"),
432 })?
433 .unwrap_or(0);
434
435 total_erased += memory_result as u32;
436
437 let audit_result =
439 sqlx::query_scalar::<_, i64>("DELETE FROM audit_events WHERE user_id = $1")
440 .bind(subject_id)
441 .fetch_optional(self.db_pool.as_ref())
442 .await;
443
444 if let Ok(Some(count)) = audit_result {
445 total_erased += count as u32;
446 }
447
448 let backup_result = sqlx::query_scalar::<_, i64>(
450 "UPDATE backup_metadata SET user_info = NULL WHERE user_info::jsonb ? $1",
451 )
452 .bind(subject_id)
453 .fetch_optional(self.db_pool.as_ref())
454 .await;
455
456 if let Ok(Some(count)) = backup_result {
457 total_erased += count as u32;
458 }
459
460 if let Some(audit_manager) = &self.audit_manager {
462 let mut details = HashMap::new();
463 details.insert(
464 "subject_id".to_string(),
465 serde_json::Value::String(subject_id.to_string()),
466 );
467 details.insert(
468 "records_erased".to_string(),
469 serde_json::Value::Number(total_erased.into()),
470 );
471
472 let _ = audit_manager
473 .log_security_event(
474 "personal_data_erased",
475 AuditSeverity::High,
476 Some(subject_id),
477 None,
478 details,
479 )
480 .await;
481 }
482
483 Ok(total_erased)
484 }
485
486 async fn export_personal_data(&self, subject_id: &str) -> Result<DataExport> {
488 let mut personal_data = HashMap::new();
489 let mut data_categories = Vec::new();
490
491 let memory_rows = sqlx::query(
493 "SELECT id, content, tier, created_at, last_accessed_at FROM memories WHERE user_id = $1"
494 )
495 .bind(subject_id)
496 .fetch_all(self.db_pool.as_ref())
497 .await
498 .map_err(|e| SecurityError::GdprError {
499 message: format!("Failed to export memory data: {e}"),
500 })?;
501
502 if !memory_rows.is_empty() {
503 let mut memories = Vec::new();
504 for row in memory_rows {
505 let memory_data = serde_json::json!({
506 "id": row.get::<String, _>("id"),
507 "content": row.get::<String, _>("content"),
508 "tier": row.get::<String, _>("tier"),
509 "created_at": row.get::<DateTime<Utc>, _>("created_at"),
510 "last_accessed_at": row.get::<Option<DateTime<Utc>>, _>("last_accessed_at")
511 });
512 memories.push(memory_data);
513 }
514 personal_data.insert("memories".to_string(), serde_json::Value::Array(memories));
515 data_categories.push(DataCategory::Generated);
516 }
517
518 let processing_rows = sqlx::query("SELECT * FROM gdpr_processing WHERE subject_id = $1")
520 .bind(subject_id)
521 .fetch_all(self.db_pool.as_ref())
522 .await
523 .unwrap_or_default();
524
525 if !processing_rows.is_empty() {
526 let mut processing_records = Vec::new();
527 for row in processing_rows {
528 let record = serde_json::json!({
529 "id": row.get::<String, _>("id"),
530 "category": row.get::<String, _>("category"),
531 "purpose": row.get::<String, _>("purpose"),
532 "legal_basis": row.get::<String, _>("legal_basis"),
533 "processed_at": row.get::<DateTime<Utc>, _>("processed_at"),
534 "retention_until": row.get::<Option<DateTime<Utc>>, _>("retention_until"),
535 "consent_given": row.get::<Option<bool>, _>("consent_given")
536 });
537 processing_records.push(record);
538 }
539 personal_data.insert(
540 "processing_records".to_string(),
541 serde_json::Value::Array(processing_records),
542 );
543 data_categories.push(DataCategory::Metadata);
544 }
545
546 let consent_rows = sqlx::query("SELECT * FROM gdpr_consent WHERE subject_id = $1")
548 .bind(subject_id)
549 .fetch_all(self.db_pool.as_ref())
550 .await
551 .unwrap_or_default();
552
553 if !consent_rows.is_empty() {
554 let mut consent_records = Vec::new();
555 for row in consent_rows {
556 let record = serde_json::json!({
557 "id": row.get::<String, _>("id"),
558 "purpose": row.get::<String, _>("purpose"),
559 "consent_given": row.get::<bool, _>("consent_given"),
560 "given_at": row.get::<DateTime<Utc>, _>("given_at"),
561 "withdrawn_at": row.get::<Option<DateTime<Utc>>, _>("withdrawn_at"),
562 "version": row.get::<String, _>("version")
563 });
564 consent_records.push(record);
565 }
566 personal_data.insert(
567 "consent_records".to_string(),
568 serde_json::Value::Array(consent_records),
569 );
570 data_categories.push(DataCategory::Identity);
571 }
572
573 let mut metadata = HashMap::new();
574 metadata.insert("export_format".to_string(), "JSON".to_string());
575 metadata.insert("gdpr_version".to_string(), "2018".to_string());
576
577 Ok(DataExport {
578 subject_id: subject_id.to_string(),
579 exported_at: Utc::now(),
580 data_categories,
581 personal_data,
582 metadata,
583 })
584 }
585
586 async fn get_request(&self, request_id: &str) -> Result<DataSubjectRequest> {
588 let row = sqlx::query("SELECT * FROM gdpr_requests WHERE id = $1")
589 .bind(request_id)
590 .fetch_optional(self.db_pool.as_ref())
591 .await
592 .map_err(|e| SecurityError::GdprError {
593 message: format!("Failed to fetch GDPR request: {e}"),
594 })?
595 .ok_or_else(|| SecurityError::GdprError {
596 message: format!("GDPR request not found: {request_id}"),
597 })?;
598
599 self.row_to_request(row)
600 }
601
602 fn row_to_request(&self, row: sqlx::postgres::PgRow) -> Result<DataSubjectRequest> {
603 let request_type_str: String = row.get("request_type");
604 let request_type = match request_type_str.as_str() {
605 "Access" => DataSubjectRequestType::Access,
606 "Rectification" => DataSubjectRequestType::Rectification,
607 "Erasure" => DataSubjectRequestType::Erasure,
608 "Portability" => DataSubjectRequestType::Portability,
609 "Restriction" => DataSubjectRequestType::Restriction,
610 "Objection" => DataSubjectRequestType::Objection,
611 _ => DataSubjectRequestType::Access,
612 };
613
614 let status_str: String = row.get("status");
615 let status = match status_str.as_str() {
616 "Pending" => RequestStatus::Pending,
617 "InProgress" => RequestStatus::InProgress,
618 "Completed" => RequestStatus::Completed,
619 "Rejected" => RequestStatus::Rejected,
620 "PartiallyCompleted" => RequestStatus::PartiallyCompleted,
621 _ => RequestStatus::Pending,
622 };
623
624 Ok(DataSubjectRequest {
625 id: row.get("id"),
626 request_type,
627 subject_id: row.get("subject_id"),
628 subject_email: row.get("subject_email"),
629 requested_at: row.get("requested_at"),
630 status,
631 processed_at: row.get("processed_at"),
632 processed_by: row.get("processed_by"),
633 notes: row.get("notes"),
634 data_export: row.get("data_export"),
635 })
636 }
637
638 async fn update_request_status(
639 &self,
640 request_id: &str,
641 status: RequestStatus,
642 processor_id: Option<&str>,
643 ) -> Result<()> {
644 sqlx::query("UPDATE gdpr_requests SET status = $1, processed_by = $2 WHERE id = $3")
645 .bind(format!("{status:?}"))
646 .bind(processor_id)
647 .bind(request_id)
648 .execute(self.db_pool.as_ref())
649 .await
650 .map_err(|e| SecurityError::GdprError {
651 message: format!("Failed to update request status: {e}"),
652 })?;
653
654 Ok(())
655 }
656
657 async fn complete_request(
658 &self,
659 request_id: &str,
660 processor_id: &str,
661 notes: Option<&str>,
662 ) -> Result<()> {
663 sqlx::query(
664 "UPDATE gdpr_requests SET status = $1, processed_at = $2, processed_by = $3, notes = $4 WHERE id = $5"
665 )
666 .bind(format!("{:?}", RequestStatus::Completed))
667 .bind(Utc::now())
668 .bind(processor_id)
669 .bind(notes)
670 .bind(request_id)
671 .execute(self.db_pool.as_ref())
672 .await
673 .map_err(|e| SecurityError::GdprError {
674 message: format!("Failed to complete request: {e}"),
675 })?;
676
677 Ok(())
678 }
679
680 async fn update_request_notes(&self, request_id: &str, notes: &str) -> Result<()> {
681 sqlx::query("UPDATE gdpr_requests SET notes = $1 WHERE id = $2")
682 .bind(notes)
683 .bind(request_id)
684 .execute(self.db_pool.as_ref())
685 .await
686 .map_err(|e| SecurityError::GdprError {
687 message: format!("Failed to update request notes: {e}"),
688 })?;
689
690 Ok(())
691 }
692
693 pub async fn cleanup_expired_data(&self) -> Result<u32> {
695 if !self.config.enabled || !self.config.auto_cleanup {
696 return Ok(0);
697 }
698
699 let cutoff_date =
700 Utc::now() - chrono::Duration::days(self.config.data_retention_days as i64);
701 let mut total_cleaned = 0;
702
703 let memory_result: i64 = sqlx::query_scalar(
705 "DELETE FROM memories WHERE created_at < $1 AND tier = 'Cold' RETURNING COUNT(*)",
706 )
707 .bind(cutoff_date)
708 .fetch_optional(self.db_pool.as_ref())
709 .await
710 .map_err(|e| SecurityError::GdprError {
711 message: format!("Failed to cleanup memory data: {e}"),
712 })?
713 .unwrap_or(0);
714
715 total_cleaned += memory_result as u32;
716
717 let processing_result: i64 = sqlx::query_scalar(
719 "DELETE FROM gdpr_processing WHERE retention_until IS NOT NULL AND retention_until < NOW() RETURNING COUNT(*)"
720 )
721 .fetch_optional(self.db_pool.as_ref())
722 .await
723 .unwrap_or(Some(0))
724 .unwrap_or(0);
725
726 total_cleaned += processing_result as u32;
727
728 if total_cleaned > 0 {
729 info!("Cleaned up {} expired data records", total_cleaned);
730 }
731
732 Ok(total_cleaned)
733 }
734
735 pub fn is_enabled(&self) -> bool {
736 self.config.enabled
737 }
738
739 pub fn right_to_be_forgotten_enabled(&self) -> bool {
740 self.config.enabled && self.config.right_to_be_forgotten
741 }
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747
748 #[tokio::test]
749 async fn test_compliance_manager_creation() {
750 let config = GdprConfig::default();
751 let pool = Arc::new(PgPool::connect_lazy("postgresql://localhost/test").unwrap());
753 let manager = ComplianceManager::new(config, pool);
754 assert!(!manager.is_enabled()); }
756
757 #[test]
758 fn test_data_subject_request_serialization() {
759 let request = DataSubjectRequest {
760 id: "test-id".to_string(),
761 request_type: DataSubjectRequestType::Erasure,
762 subject_id: "user123".to_string(),
763 subject_email: Some("user@example.com".to_string()),
764 requested_at: Utc::now(),
765 status: RequestStatus::Pending,
766 processed_at: None,
767 processed_by: None,
768 notes: None,
769 data_export: None,
770 };
771
772 let serialized = serde_json::to_string(&request).unwrap();
773 let deserialized: DataSubjectRequest = serde_json::from_str(&serialized).unwrap();
774
775 assert_eq!(request.id, deserialized.id);
776 assert_eq!(request.subject_id, deserialized.subject_id);
777 assert!(matches!(
778 deserialized.request_type,
779 DataSubjectRequestType::Erasure
780 ));
781 assert!(matches!(deserialized.status, RequestStatus::Pending));
782 }
783
784 #[test]
785 fn test_data_export_creation() {
786 let mut personal_data = HashMap::new();
787 personal_data.insert("test_key".to_string(), serde_json::json!({"value": "test"}));
788
789 let mut metadata = HashMap::new();
790 metadata.insert("format".to_string(), "JSON".to_string());
791
792 let export = DataExport {
793 subject_id: "user123".to_string(),
794 exported_at: Utc::now(),
795 data_categories: vec![DataCategory::Identity, DataCategory::Usage],
796 personal_data,
797 metadata,
798 };
799
800 assert_eq!(export.subject_id, "user123");
801 assert_eq!(export.data_categories.len(), 2);
802 assert_eq!(export.personal_data.len(), 1);
803 assert_eq!(export.metadata.len(), 1);
804 }
805
806 #[test]
807 fn test_retention_policy() {
808 let policy = RetentionPolicy {
809 category: DataCategory::Usage,
810 retention_days: 90,
811 auto_delete: true,
812 legal_basis: "Legitimate interest".to_string(),
813 };
814
815 assert!(matches!(policy.category, DataCategory::Usage));
816 assert_eq!(policy.retention_days, 90);
817 assert!(policy.auto_delete);
818 }
819
820 #[test]
821 fn test_consent_record() {
822 let consent = ConsentRecord {
823 id: "consent-123".to_string(),
824 subject_id: "user123".to_string(),
825 purpose: "Marketing communications".to_string(),
826 consent_given: true,
827 given_at: Utc::now(),
828 withdrawn_at: None,
829 version: "1.0".to_string(),
830 };
831
832 assert_eq!(consent.subject_id, "user123");
833 assert!(consent.consent_given);
834 assert!(consent.withdrawn_at.is_none());
835 }
836
837 #[test]
838 fn test_processing_record() {
839 let record = ProcessingRecord {
840 id: "proc-123".to_string(),
841 subject_id: "user123".to_string(),
842 category: DataCategory::Technical,
843 purpose: "Service delivery".to_string(),
844 legal_basis: "Contract".to_string(),
845 processed_at: Utc::now(),
846 retention_until: Some(Utc::now() + chrono::Duration::days(365)),
847 consent_given: Some(true),
848 };
849
850 assert!(matches!(record.category, DataCategory::Technical));
851 assert!(record.retention_until.is_some());
852 assert_eq!(record.consent_given, Some(true));
853 }
854}