1use crate::Result;
38use crate::models::{EventMeta, Memory, MemoryEvent, MemoryId, SearchFilter};
39use crate::observability::current_request_id;
40use crate::security::{AuditEntry, AuditOutcome, global_logger, record_event};
41use crate::storage::index::SqliteBackend;
42use crate::storage::traits::{IndexBackend, VectorBackend};
43use serde::{Deserialize, Serialize};
44use std::sync::Arc;
45use std::time::Instant;
46use tracing::instrument;
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct UserDataExport {
57 pub version: String,
59 pub exported_at: u64,
61 pub memory_count: usize,
63 pub memories: Vec<ExportedMemory>,
65 pub metadata: ExportMetadata,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct ExportedMemory {
74 pub id: String,
76 pub content: String,
78 pub namespace: String,
80 pub domain: String,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub project_id: Option<String>,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub branch: Option<String>,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub file_path: Option<String>,
91 pub status: String,
93 pub created_at: u64,
95 pub updated_at: u64,
97 pub tags: Vec<String>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub source: Option<String>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ExportMetadata {
107 pub format: String,
109 pub generator: String,
111 pub generator_version: String,
113}
114
115impl From<Memory> for ExportedMemory {
116 fn from(memory: Memory) -> Self {
117 Self {
118 id: memory.id.to_string(),
119 content: memory.content,
120 namespace: memory.namespace.as_str().to_string(),
121 domain: memory.domain.to_string(),
122 project_id: memory.project_id,
123 branch: memory.branch,
124 file_path: memory.file_path,
125 status: memory.status.as_str().to_string(),
126 created_at: memory.created_at,
127 updated_at: memory.updated_at,
128 tags: memory.tags,
129 source: memory.source,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum ConsentPurpose {
145 DataStorage,
147 DataProcessing,
149 EmbeddingGeneration,
151 LlmProcessing,
153 RemoteSync,
155 Analytics,
157}
158
159impl ConsentPurpose {
160 #[must_use]
162 pub const fn all() -> &'static [Self] {
163 &[
164 Self::DataStorage,
165 Self::DataProcessing,
166 Self::EmbeddingGeneration,
167 Self::LlmProcessing,
168 Self::RemoteSync,
169 Self::Analytics,
170 ]
171 }
172
173 #[must_use]
175 pub const fn as_str(&self) -> &'static str {
176 match self {
177 Self::DataStorage => "data_storage",
178 Self::DataProcessing => "data_processing",
179 Self::EmbeddingGeneration => "embedding_generation",
180 Self::LlmProcessing => "llm_processing",
181 Self::RemoteSync => "remote_sync",
182 Self::Analytics => "analytics",
183 }
184 }
185
186 #[must_use]
188 pub const fn description(&self) -> &'static str {
189 match self {
190 Self::DataStorage => "Store memory content in persistent storage",
191 Self::DataProcessing => "Process memories for search and recall functionality",
192 Self::EmbeddingGeneration => "Generate vector embeddings for semantic search",
193 Self::LlmProcessing => "Use language models for enrichment and analysis",
194 Self::RemoteSync => "Synchronize data to remote storage locations",
195 Self::Analytics => "Collect anonymized usage metrics and analytics",
196 }
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ConsentRecord {
208 pub purpose: ConsentPurpose,
210 pub granted: bool,
212 pub recorded_at: u64,
214 pub policy_version: String,
216 #[serde(skip_serializing_if = "Option::is_none")]
218 pub subject_id: Option<String>,
219 pub collection_method: String,
221}
222
223impl ConsentRecord {
224 #[must_use]
226 pub fn grant(purpose: ConsentPurpose, policy_version: impl Into<String>) -> Self {
227 Self {
228 purpose,
229 granted: true,
230 recorded_at: crate::current_timestamp(),
231 policy_version: policy_version.into(),
232 subject_id: None,
233 collection_method: "explicit".to_string(),
234 }
235 }
236
237 #[must_use]
239 pub fn revoke(purpose: ConsentPurpose, policy_version: impl Into<String>) -> Self {
240 Self {
241 purpose,
242 granted: false,
243 recorded_at: crate::current_timestamp(),
244 policy_version: policy_version.into(),
245 subject_id: None,
246 collection_method: "explicit".to_string(),
247 }
248 }
249
250 #[must_use]
252 pub fn with_subject(mut self, subject_id: impl Into<String>) -> Self {
253 self.subject_id = Some(subject_id.into());
254 self
255 }
256
257 #[must_use]
259 pub fn with_method(mut self, method: impl Into<String>) -> Self {
260 self.collection_method = method.into();
261 self
262 }
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct ConsentStatus {
268 pub consents: std::collections::HashMap<String, bool>,
270 pub last_updated: u64,
272 pub policy_version: String,
274}
275
276impl Default for ConsentStatus {
277 fn default() -> Self {
278 Self {
279 consents: std::collections::HashMap::new(),
280 last_updated: 0,
281 policy_version: "1.0".to_string(),
282 }
283 }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct DeletionResult {
293 pub deleted_count: usize,
295 pub deleted_ids: Vec<String>,
297 pub failed_count: usize,
299 pub failures: Vec<DeletionFailure>,
301 pub deleted_at: u64,
303 pub complete: bool,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct DeletionFailure {
310 pub id: String,
312 pub error: String,
314}
315
316pub struct DataSubjectService {
339 index: SqliteBackend,
341 vector: Option<Arc<dyn VectorBackend + Send + Sync>>,
343}
344
345impl DataSubjectService {
346 #[must_use]
352 pub const fn new(index: SqliteBackend) -> Self {
353 Self {
354 index,
355 vector: None,
356 }
357 }
358
359 #[must_use]
361 pub fn with_vector(mut self, vector: Arc<dyn VectorBackend + Send + Sync>) -> Self {
362 self.vector = Some(vector);
363 self
364 }
365
366 #[instrument(skip(self), fields(operation = "export_user_data"))]
395 pub fn export_user_data(&self) -> Result<UserDataExport> {
396 let start = Instant::now();
397
398 tracing::info!("Starting GDPR data export");
399
400 let filter = SearchFilter::new();
402 let memory_ids = self.index.list_all(&filter, usize::MAX)?;
403
404 let ids: Vec<MemoryId> = memory_ids.into_iter().map(|(id, _)| id).collect();
406 let memories_opt = self.index.get_memories_batch(&ids)?;
407
408 let memories: Vec<ExportedMemory> = memories_opt
410 .into_iter()
411 .flatten()
412 .map(ExportedMemory::from)
413 .collect();
414
415 let memory_count = memories.len();
416
417 let export = UserDataExport {
418 version: "1.0".to_string(),
419 exported_at: crate::current_timestamp(),
420 memory_count,
421 memories,
422 metadata: ExportMetadata {
423 format: "subcog-export-v1".to_string(),
424 generator: "subcog".to_string(),
425 generator_version: env!("CARGO_PKG_VERSION").to_string(),
426 },
427 };
428
429 Self::log_export_event(memory_count);
431
432 Self::record_metrics("export", start, memory_count, 0);
434
435 tracing::info!(
436 memory_count = memory_count,
437 duration_ms = start.elapsed().as_millis(),
438 "GDPR data export completed"
439 );
440
441 Ok(export)
442 }
443
444 #[instrument(skip(self), fields(operation = "delete_user_data"))]
490 pub fn delete_user_data(&self) -> Result<DeletionResult> {
491 let start = Instant::now();
492
493 tracing::warn!("Starting GDPR data deletion (irreversible)");
494
495 let filter = SearchFilter::new();
497 let memory_ids = self.index.list_all(&filter, usize::MAX)?;
498
499 let ids: Vec<MemoryId> = memory_ids.into_iter().map(|(id, _)| id).collect();
500 let total_count = ids.len();
501
502 if ids.is_empty() {
503 tracing::info!("No memories to delete");
504 return Ok(DeletionResult {
505 deleted_count: 0,
506 deleted_ids: Vec::new(),
507 failed_count: 0,
508 failures: Vec::new(),
509 deleted_at: crate::current_timestamp(),
510 complete: true,
511 });
512 }
513
514 let mut deleted_ids = Vec::with_capacity(total_count);
515 let mut failures = Vec::new();
516
517 for id in &ids {
519 match self.delete_memory_from_all_layers(id) {
520 Ok(()) => {
521 deleted_ids.push(id.to_string());
522 record_event(MemoryEvent::Deleted {
523 meta: EventMeta::new("gdpr", current_request_id()),
524 memory_id: id.clone(),
525 reason: "gdpr.delete_user_data".to_string(),
526 });
527 },
528 Err(e) => {
529 tracing::warn!(memory_id = %id, error = %e, "Failed to delete memory");
530 failures.push(DeletionFailure {
531 id: id.to_string(),
532 error: e.to_string(),
533 });
534 },
535 }
536 }
537
538 let deleted_count = deleted_ids.len();
539 let failed_count = failures.len();
540 let complete = failures.is_empty();
541
542 Self::log_deletion_event(deleted_count, failed_count);
544
545 Self::record_metrics("delete", start, deleted_count, failed_count);
547
548 tracing::info!(
549 deleted_count = deleted_count,
550 failed_count = failed_count,
551 duration_ms = start.elapsed().as_millis(),
552 complete = complete,
553 "GDPR data deletion completed"
554 );
555
556 Ok(DeletionResult {
557 deleted_count,
558 deleted_ids,
559 failed_count,
560 failures,
561 deleted_at: crate::current_timestamp(),
562 complete,
563 })
564 }
565
566 fn delete_memory_from_all_layers(&self, id: &MemoryId) -> Result<()> {
573 if let Some(ref vector) = self.vector
575 && let Err(e) = vector.remove(id)
576 {
577 tracing::debug!(memory_id = %id, error = %e, "Vector deletion failed (continuing)");
578 }
580
581 self.index.remove(id)?;
583
584 Ok(())
585 }
586
587 #[instrument(skip(self), fields(operation = "record_consent"))]
618 pub fn record_consent(&self, record: &ConsentRecord) -> Result<()> {
619 let purpose = record.purpose.as_str();
620 let granted = record.granted;
621
622 tracing::info!(
623 purpose = purpose,
624 granted = granted,
625 policy_version = %record.policy_version,
626 "Recording consent"
627 );
628
629 Self::log_consent_event(record);
632
633 metrics::counter!(
634 "gdpr_consent_recorded_total",
635 "purpose" => purpose.to_string(),
636 "granted" => granted.to_string()
637 )
638 .increment(1);
639
640 Ok(())
641 }
642
643 #[instrument(skip(self), fields(operation = "revoke_consent"))]
660 pub fn revoke_consent(&self, purpose: ConsentPurpose, policy_version: &str) -> Result<()> {
661 let record = ConsentRecord::revoke(purpose, policy_version);
662
663 tracing::warn!(
664 purpose = purpose.as_str(),
665 policy_version = policy_version,
666 "Revoking consent"
667 );
668
669 Self::log_consent_event(&record);
670
671 metrics::counter!(
672 "gdpr_consent_revoked_total",
673 "purpose" => purpose.as_str().to_string()
674 )
675 .increment(1);
676
677 Ok(())
678 }
679
680 #[must_use]
691 pub fn has_consent(&self, purpose: ConsentPurpose) -> bool {
692 tracing::debug!(purpose = purpose.as_str(), "Checking consent");
694
695 true
698 }
699
700 #[must_use]
706 pub fn consent_status(&self) -> ConsentStatus {
707 let mut status = ConsentStatus::default();
708
709 for purpose in ConsentPurpose::all() {
710 status
711 .consents
712 .insert(purpose.as_str().to_string(), self.has_consent(*purpose));
713 }
714
715 status.last_updated = crate::current_timestamp();
716 status
717 }
718
719 fn log_consent_event(record: &ConsentRecord) {
721 if let Some(logger) = global_logger() {
722 let action = if record.granted {
723 "grant_consent"
724 } else {
725 "revoke_consent"
726 };
727
728 let entry = AuditEntry::new("gdpr.consent", action)
729 .with_outcome(AuditOutcome::Success)
730 .with_metadata(serde_json::json!({
731 "purpose": record.purpose.as_str(),
732 "granted": record.granted,
733 "policy_version": record.policy_version,
734 "collection_method": record.collection_method,
735 "gdpr_article": if record.granted {
736 "Article 6 - Lawful Basis for Processing"
737 } else {
738 "Article 7(3) - Right to Withdraw Consent"
739 }
740 }));
741 logger.log_entry(entry);
742 }
743 }
744
745 fn log_export_event(memory_count: usize) {
747 if let Some(logger) = global_logger() {
748 let entry = AuditEntry::new("gdpr.export", "export_user_data")
749 .with_outcome(AuditOutcome::Success)
750 .with_metadata(serde_json::json!({
751 "memory_count": memory_count,
752 "gdpr_article": "Article 20 - Right to Data Portability"
753 }));
754 logger.log_entry(entry);
755 }
756 }
757
758 fn log_deletion_event(deleted_count: usize, failed_count: usize) {
760 if let Some(logger) = global_logger() {
761 let outcome = if failed_count == 0 {
762 AuditOutcome::Success
763 } else {
764 AuditOutcome::Failure
765 };
766
767 let entry = AuditEntry::new("gdpr.delete", "delete_user_data")
768 .with_outcome(outcome)
769 .with_metadata(serde_json::json!({
770 "deleted_count": deleted_count,
771 "failed_count": failed_count,
772 "gdpr_article": "Article 17 - Right to Erasure"
773 }));
774 logger.log_entry(entry);
775 }
776 }
777
778 fn record_metrics(operation: &str, start: Instant, success_count: usize, failure_count: usize) {
780 metrics::counter!(
781 "gdpr_operations_total",
782 "operation" => operation.to_string(),
783 "status" => if failure_count == 0 { "success" } else { "partial" }
784 )
785 .increment(1);
786
787 metrics::histogram!(
788 "gdpr_operation_duration_ms",
789 "operation" => operation.to_string()
790 )
791 .record(start.elapsed().as_secs_f64() * 1000.0);
792
793 metrics::gauge!(
794 "gdpr_last_operation_count",
795 "operation" => operation.to_string()
796 )
797 .set(success_count as f64);
798
799 if failure_count > 0 {
800 metrics::counter!(
801 "gdpr_operation_failures_total",
802 "operation" => operation.to_string()
803 )
804 .increment(failure_count as u64);
805 }
806 }
807}
808
809#[cfg(test)]
814mod tests {
815 use super::*;
816 use crate::models::{Domain, MemoryStatus, Namespace};
817 use crate::storage::index::SqliteBackend;
818
819 fn create_test_memory(id: &str, content: &str, namespace: Namespace) -> Memory {
820 Memory {
821 id: MemoryId::new(id),
822 content: content.to_string(),
823 namespace,
824 domain: Domain::new(),
825 project_id: None,
826 branch: None,
827 file_path: None,
828 status: MemoryStatus::Active,
829 created_at: 1_700_000_000,
830 updated_at: 1_700_000_000,
831 tombstoned_at: None,
832 expires_at: None,
833 embedding: None,
834 tags: vec!["test".to_string()],
835 #[cfg(feature = "group-scope")]
836 group_id: None,
837 source: Some("test.rs".to_string()),
838 is_summary: false,
839 source_memory_ids: None,
840 consolidation_timestamp: None,
841 }
842 }
843
844 #[test]
845 fn test_export_user_data_empty() {
846 let index = SqliteBackend::in_memory().expect("Failed to create index");
847 let service = DataSubjectService::new(index);
848
849 let export = service.export_user_data().expect("Export failed");
850
851 assert_eq!(export.version, "1.0");
852 assert_eq!(export.memory_count, 0);
853 assert!(export.memories.is_empty());
854 assert_eq!(export.metadata.format, "subcog-export-v1");
855 assert_eq!(export.metadata.generator, "subcog");
856 }
857
858 #[test]
859 fn test_export_user_data_with_memories() {
860 let index = SqliteBackend::in_memory().expect("Failed to create index");
861
862 let mem1 = create_test_memory("id1", "Decision: Use PostgreSQL", Namespace::Decisions);
864 let mem2 = create_test_memory("id2", "Learned: Connection pooling", Namespace::Learnings);
865 let mem3 = create_test_memory("id3", "Pattern: Repository pattern", Namespace::Patterns);
866
867 index.index(&mem1).expect("Index failed");
868 index.index(&mem2).expect("Index failed");
869 index.index(&mem3).expect("Index failed");
870
871 let service = DataSubjectService::new(index);
872 let export = service.export_user_data().expect("Export failed");
873
874 assert_eq!(export.memory_count, 3);
875 assert_eq!(export.memories.len(), 3);
876
877 let ids: Vec<_> = export.memories.iter().map(|m| m.id.as_str()).collect();
879 assert!(ids.contains(&"id1"));
880 assert!(ids.contains(&"id2"));
881 assert!(ids.contains(&"id3"));
882
883 let decision = export.memories.iter().find(|m| m.id == "id1").unwrap();
885 assert_eq!(decision.content, "Decision: Use PostgreSQL");
886 assert_eq!(decision.namespace, "decisions");
887 assert_eq!(decision.status, "active");
888 assert_eq!(decision.tags, vec!["test".to_string()]);
889 assert_eq!(decision.source, Some("test.rs".to_string()));
890 }
891
892 #[test]
893 fn test_delete_user_data_empty() {
894 let index = SqliteBackend::in_memory().expect("Failed to create index");
895 let service = DataSubjectService::new(index);
896
897 let result = service.delete_user_data().expect("Delete failed");
898
899 assert_eq!(result.deleted_count, 0);
900 assert!(result.deleted_ids.is_empty());
901 assert_eq!(result.failed_count, 0);
902 assert!(result.failures.is_empty());
903 assert!(result.complete);
904 }
905
906 #[test]
907 fn test_delete_user_data_with_memories() {
908 let index = SqliteBackend::in_memory().expect("Failed to create index");
909
910 let mem1 = create_test_memory("id1", "To be deleted 1", Namespace::Decisions);
912 let mem2 = create_test_memory("id2", "To be deleted 2", Namespace::Learnings);
913
914 index.index(&mem1).expect("Index failed");
915 index.index(&mem2).expect("Index failed");
916
917 let filter = SearchFilter::new();
919 let before = index.list_all(&filter, 100).expect("List failed");
920 assert_eq!(before.len(), 2);
921
922 let service = DataSubjectService::new(index);
923 let result = service.delete_user_data().expect("Delete failed");
924
925 assert_eq!(result.deleted_count, 2);
926 assert_eq!(result.deleted_ids.len(), 2);
927 assert!(result.deleted_ids.contains(&"id1".to_string()));
928 assert!(result.deleted_ids.contains(&"id2".to_string()));
929 assert_eq!(result.failed_count, 0);
930 assert!(result.complete);
931
932 assert!(result.complete);
935 }
936
937 #[test]
938 fn test_exported_memory_from_memory() {
939 let memory = create_test_memory("test-id", "Test content", Namespace::Security);
940 let exported = ExportedMemory::from(memory);
941
942 assert_eq!(exported.id, "test-id");
943 assert_eq!(exported.content, "Test content");
944 assert_eq!(exported.namespace, "security");
945 assert_eq!(exported.status, "active");
946 }
947
948 #[test]
949 fn test_export_serialization() {
950 let export = UserDataExport {
951 version: "1.0".to_string(),
952 exported_at: 1_700_000_000,
953 memory_count: 1,
954 memories: vec![ExportedMemory {
955 id: "test".to_string(),
956 content: "Content".to_string(),
957 namespace: "decisions".to_string(),
958 domain: "project".to_string(),
959 project_id: None,
960 branch: None,
961 file_path: None,
962 status: "active".to_string(),
963 created_at: 1_700_000_000,
964 updated_at: 1_700_000_000,
965 tags: vec!["tag1".to_string()],
966 source: None,
967 }],
968 metadata: ExportMetadata {
969 format: "subcog-export-v1".to_string(),
970 generator: "subcog".to_string(),
971 generator_version: "0.1.0".to_string(),
972 },
973 };
974
975 let json = serde_json::to_string(&export).expect("Serialization failed");
976 assert!(json.contains("subcog-export-v1"));
977 assert!(json.contains("decisions"));
978
979 let parsed: UserDataExport = serde_json::from_str(&json).expect("Deserialization failed");
981 assert_eq!(parsed.memory_count, 1);
982 assert_eq!(parsed.memories[0].id, "test");
983 }
984
985 #[test]
986 fn test_deletion_result_serialization() {
987 let result = DeletionResult {
988 deleted_count: 5,
989 deleted_ids: vec!["id1".to_string(), "id2".to_string()],
990 failed_count: 1,
991 failures: vec![DeletionFailure {
992 id: "id3".to_string(),
993 error: "Not found".to_string(),
994 }],
995 deleted_at: 1_700_000_000,
996 complete: false,
997 };
998
999 let json = serde_json::to_string(&result).expect("Serialization failed");
1000 assert!(json.contains("deleted_count"));
1001 assert!(json.contains("complete"));
1002
1003 let parsed: DeletionResult = serde_json::from_str(&json).expect("Deserialization failed");
1004 assert_eq!(parsed.deleted_count, 5);
1005 assert!(!parsed.complete);
1006 }
1007
1008 #[test]
1009 fn test_service_builder_pattern() {
1010 let index = SqliteBackend::in_memory().expect("Failed to create index");
1011
1012 let service = DataSubjectService::new(index);
1014
1015 let export = service.export_user_data();
1017 assert!(export.is_ok());
1018 }
1019
1020 #[test]
1021 fn test_export_preserves_all_namespaces() {
1022 let index = SqliteBackend::in_memory().expect("Failed to create index");
1023
1024 for ns in Namespace::user_namespaces() {
1026 let mem = create_test_memory(
1027 &format!("id-{}", ns.as_str()),
1028 &format!("Content for {}", ns.as_str()),
1029 *ns,
1030 );
1031 index.index(&mem).expect("Index failed");
1032 }
1033
1034 let service = DataSubjectService::new(index);
1035 let export = service.export_user_data().expect("Export failed");
1036
1037 assert_eq!(export.memory_count, Namespace::user_namespaces().len());
1039
1040 let namespaces: Vec<_> = export
1042 .memories
1043 .iter()
1044 .map(|m| m.namespace.as_str())
1045 .collect();
1046 for ns in Namespace::user_namespaces() {
1047 assert!(
1048 namespaces.contains(&ns.as_str()),
1049 "Missing namespace: {}",
1050 ns.as_str()
1051 );
1052 }
1053 }
1054
1055 #[test]
1056 fn test_delete_clears_all_memories() {
1057 let index = SqliteBackend::in_memory().expect("Failed to create index");
1058
1059 for i in 0..10 {
1061 let mem = create_test_memory(
1062 &format!("bulk-{i}"),
1063 &format!("Bulk memory {i}"),
1064 Namespace::Decisions,
1065 );
1066 index.index(&mem).expect("Index failed");
1067 }
1068
1069 let service = DataSubjectService::new(index);
1070
1071 let result = service.delete_user_data().expect("Delete failed");
1073
1074 assert_eq!(result.deleted_count, 10);
1075 assert!(result.complete);
1076
1077 let export = service.export_user_data().expect("Export failed");
1079 assert_eq!(export.memory_count, 0);
1080 }
1081
1082 #[test]
1087 fn test_consent_purpose_all() {
1088 let all = ConsentPurpose::all();
1089 assert_eq!(all.len(), 6);
1090 assert!(all.contains(&ConsentPurpose::DataStorage));
1091 assert!(all.contains(&ConsentPurpose::DataProcessing));
1092 assert!(all.contains(&ConsentPurpose::EmbeddingGeneration));
1093 assert!(all.contains(&ConsentPurpose::LlmProcessing));
1094 assert!(all.contains(&ConsentPurpose::RemoteSync));
1095 assert!(all.contains(&ConsentPurpose::Analytics));
1096 }
1097
1098 #[test]
1099 fn test_consent_purpose_as_str() {
1100 assert_eq!(ConsentPurpose::DataStorage.as_str(), "data_storage");
1101 assert_eq!(ConsentPurpose::DataProcessing.as_str(), "data_processing");
1102 assert_eq!(
1103 ConsentPurpose::EmbeddingGeneration.as_str(),
1104 "embedding_generation"
1105 );
1106 assert_eq!(ConsentPurpose::LlmProcessing.as_str(), "llm_processing");
1107 assert_eq!(ConsentPurpose::RemoteSync.as_str(), "remote_sync");
1108 assert_eq!(ConsentPurpose::Analytics.as_str(), "analytics");
1109 }
1110
1111 #[test]
1112 fn test_consent_purpose_description() {
1113 for purpose in ConsentPurpose::all() {
1115 let desc = purpose.description();
1116 assert!(!desc.is_empty());
1117 assert!(desc.len() > 10); }
1119 }
1120
1121 #[test]
1122 fn test_consent_record_grant() {
1123 let record = ConsentRecord::grant(ConsentPurpose::DataStorage, "1.0");
1124
1125 assert_eq!(record.purpose, ConsentPurpose::DataStorage);
1126 assert!(record.granted);
1127 assert_eq!(record.policy_version, "1.0");
1128 assert!(record.recorded_at > 0);
1129 assert_eq!(record.collection_method, "explicit");
1130 assert!(record.subject_id.is_none());
1131 }
1132
1133 #[test]
1134 fn test_consent_record_revoke() {
1135 let record = ConsentRecord::revoke(ConsentPurpose::Analytics, "2.0");
1136
1137 assert_eq!(record.purpose, ConsentPurpose::Analytics);
1138 assert!(!record.granted);
1139 assert_eq!(record.policy_version, "2.0");
1140 }
1141
1142 #[test]
1143 fn test_consent_record_builders() {
1144 let record = ConsentRecord::grant(ConsentPurpose::LlmProcessing, "1.0")
1145 .with_subject("user-123")
1146 .with_method("mcp");
1147
1148 assert_eq!(record.subject_id, Some("user-123".to_string()));
1149 assert_eq!(record.collection_method, "mcp");
1150 }
1151
1152 #[test]
1153 fn test_consent_record_serialization() {
1154 let record =
1155 ConsentRecord::grant(ConsentPurpose::DataStorage, "1.0").with_subject("test-user");
1156
1157 let json = serde_json::to_string(&record).expect("Serialization failed");
1158 assert!(json.contains("data_storage"));
1159 assert!(json.contains("test-user"));
1160 assert!(json.contains("\"granted\":true"));
1161
1162 let parsed: ConsentRecord = serde_json::from_str(&json).expect("Deserialization failed");
1163 assert_eq!(parsed.purpose, ConsentPurpose::DataStorage);
1164 assert!(parsed.granted);
1165 }
1166
1167 #[test]
1168 fn test_consent_status_default() {
1169 let status = ConsentStatus::default();
1170
1171 assert!(status.consents.is_empty());
1172 assert_eq!(status.last_updated, 0);
1173 assert_eq!(status.policy_version, "1.0");
1174 }
1175
1176 #[test]
1177 fn test_service_record_consent() {
1178 let index = SqliteBackend::in_memory().expect("Failed to create index");
1179 let service = DataSubjectService::new(index);
1180
1181 let record = ConsentRecord::grant(ConsentPurpose::DataStorage, "1.0");
1182 let result = service.record_consent(&record);
1183
1184 assert!(result.is_ok());
1185 }
1186
1187 #[test]
1188 fn test_service_revoke_consent() {
1189 let index = SqliteBackend::in_memory().expect("Failed to create index");
1190 let service = DataSubjectService::new(index);
1191
1192 let result = service.revoke_consent(ConsentPurpose::Analytics, "1.0");
1193
1194 assert!(result.is_ok());
1195 }
1196
1197 #[test]
1198 fn test_service_has_consent() {
1199 let index = SqliteBackend::in_memory().expect("Failed to create index");
1200 let service = DataSubjectService::new(index);
1201
1202 assert!(service.has_consent(ConsentPurpose::DataStorage));
1204 assert!(service.has_consent(ConsentPurpose::Analytics));
1205 }
1206
1207 #[test]
1208 fn test_service_consent_status() {
1209 let index = SqliteBackend::in_memory().expect("Failed to create index");
1210 let service = DataSubjectService::new(index);
1211
1212 let status = service.consent_status();
1213
1214 assert_eq!(status.consents.len(), 6);
1216 assert!(status.last_updated > 0);
1217
1218 for granted in status.consents.values() {
1220 assert!(*granted);
1221 }
1222 }
1223
1224 #[test]
1225 fn test_consent_status_serialization() {
1226 let mut status = ConsentStatus::default();
1227 status.consents.insert("data_storage".to_string(), true);
1228 status.consents.insert("analytics".to_string(), false);
1229 status.last_updated = 1_700_000_000;
1230
1231 let json = serde_json::to_string(&status).expect("Serialization failed");
1232 assert!(json.contains("data_storage"));
1233 assert!(json.contains("analytics"));
1234
1235 let parsed: ConsentStatus = serde_json::from_str(&json).expect("Deserialization failed");
1236 assert_eq!(parsed.consents.get("data_storage"), Some(&true));
1237 assert_eq!(parsed.consents.get("analytics"), Some(&false));
1238 }
1239}