codex_memory/security/
compliance.rs

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
10/// GDPR compliance manager
11pub struct ComplianceManager {
12    config: GdprConfig,
13    db_pool: Arc<PgPool>,
14    audit_manager: Option<Arc<AuditManager>>,
15}
16
17/// Data subject request types
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub enum DataSubjectRequestType {
20    Access,        // Right to access personal data
21    Rectification, // Right to rectify incorrect data
22    Erasure,       // Right to be forgotten
23    Portability,   // Right to data portability
24    Restriction,   // Right to restrict processing
25    Objection,     // Right to object to processing
26}
27
28/// Data subject request
29#[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>, // JSON string for portability requests
41}
42
43/// Request processing status
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub enum RequestStatus {
46    Pending,
47    InProgress,
48    Completed,
49    Rejected,
50    PartiallyCompleted,
51}
52
53/// Personal data category
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub enum DataCategory {
56    Identity,  // Name, email, etc.
57    Contact,   // Address, phone, etc.
58    Technical, // IP addresses, session data
59    Usage,     // Interaction data, preferences
60    Generated, // AI-generated content, embeddings
61    Metadata,  // Creation times, etc.
62}
63
64/// Data retention policy
65#[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/// Data processing record
74#[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/// Consent record
87#[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/// Data export for portability
99#[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    /// Initialize GDPR compliance system
123    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        // Create data subject requests table
132        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        // Create consent records table
149        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        // Create processing records table
163        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        // Create indexes
191        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    /// Submit a data subject request
213    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        // Audit log the request
260        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    /// Process a right to be forgotten request
294    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        // Get the request
306        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        // Update request status
324        self.update_request_status(request_id, RequestStatus::InProgress, Some(processor_id))
325            .await?;
326
327        // Perform data erasure
328        let erasure_result = self.erase_personal_data(&request.subject_id).await;
329
330        match erasure_result {
331            Ok(erased_count) => {
332                // Mark request as completed
333                let notes = format!("Successfully erased {erased_count} data records");
334                self.complete_request(request_id, processor_id, Some(&notes))
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                // Mark request as partially completed or rejected
344                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, &notes).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    /// Process a data portability request
364    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        // Update request status
384        self.update_request_status(request_id, RequestStatus::InProgress, Some(processor_id))
385            .await?;
386
387        // Export personal data
388        let data_export = self.export_personal_data(&request.subject_id).await?;
389
390        // Store export data in request
391        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        // Complete the request
406        let notes = format!(
407            "Data exported successfully ({} categories)",
408            data_export.data_categories.len()
409        );
410        self.complete_request(request_id, processor_id, Some(&notes))
411            .await?;
412
413        info!(
414            "Portability request completed for subject: {}",
415            request.subject_id
416        );
417        Ok(data_export)
418    }
419
420    /// Erase personal data for a subject
421    async fn erase_personal_data(&self, subject_id: &str) -> Result<u32> {
422        let mut total_erased = 0;
423
424        // Erase from memories table
425        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        // Erase from audit_events table (if it exists)
438        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        // Erase from backup_metadata table (if it exists)
449        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        // Log the erasure
461        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    /// Export personal data for portability
487    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        // Export memory data
492        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        // Export processing records
519        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        // Export consent records
547        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    /// Get a data subject request
587    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    /// Clean up expired data based on retention policies
694    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        // Clean up old memories
704        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        // Clean up old processing records
718        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        // Use a mock pool that doesn't actually connect
752        let pool = Arc::new(PgPool::connect_lazy("postgresql://localhost/test").unwrap());
753        let manager = ComplianceManager::new(config, pool);
754        assert!(!manager.is_enabled()); // disabled by default
755    }
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}