From 79671fbb9e67068397c7fae6d58a31025bd61670 Mon Sep 17 00:00:00 2001 From: emarc99 <57766083+emarc99@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:17:21 +0100 Subject: [PATCH 1/5] test: add modular test structure and utilities --- .../patient-consent-management-system/src/lib.rs | 3 +++ .../src/tests/mod.rs | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 soroban/contracts/patient-consent-management-system/src/tests/mod.rs diff --git a/soroban/contracts/patient-consent-management-system/src/lib.rs b/soroban/contracts/patient-consent-management-system/src/lib.rs index 755ea87..d21502b 100644 --- a/soroban/contracts/patient-consent-management-system/src/lib.rs +++ b/soroban/contracts/patient-consent-management-system/src/lib.rs @@ -268,3 +268,6 @@ impl PatientConsentManagementSystem { #[cfg(test)] mod test; + +#[cfg(test)] +mod tests; diff --git a/soroban/contracts/patient-consent-management-system/src/tests/mod.rs b/soroban/contracts/patient-consent-management-system/src/tests/mod.rs new file mode 100644 index 0000000..cc583e1 --- /dev/null +++ b/soroban/contracts/patient-consent-management-system/src/tests/mod.rs @@ -0,0 +1,13 @@ +#![cfg(test)] + +/// Test utilities and helper functions +pub mod utils; + +/// Comprehensive consent creation and validation tests +mod consent; + +/// Consent revocation, suspension, and state transition tests +mod revocation; + +/// Audit logging and compliance tracking tests +mod audit; From d3d006450a2dbb4b0b096e2caf5ff0dcf1fa4767 Mon Sep 17 00:00:00 2001 From: emarc99 <57766083+emarc99@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:18:51 +0100 Subject: [PATCH 2/5] test: add consent creation and validation tests - Add 8 unique consent tests covering edge cases - Include error handling for empty scopes, empty purpose, past expiration - Test multiple specific scopes and non-existent consent checks - Add consent age tracking and days until expiration tests --- .../src/tests/consent.rs | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 soroban/contracts/patient-consent-management-system/src/tests/consent.rs diff --git a/soroban/contracts/patient-consent-management-system/src/tests/consent.rs b/soroban/contracts/patient-consent-management-system/src/tests/consent.rs new file mode 100644 index 0000000..c5cd219 --- /dev/null +++ b/soroban/contracts/patient-consent-management-system/src/tests/consent.rs @@ -0,0 +1,178 @@ +#![cfg(test)] + +use soroban_sdk::{ + testutils::{Address as _, Ledger}, + Address, Env, String, +}; + +use crate::consent::DataScope; +use super::utils::*; + +#[test] +fn test_create_consent_without_expiration() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let scopes = create_data_scopes(&env, &[DataScope::Treatment]); + let purpose = create_purpose(&env, "Ongoing treatment"); + + let consent_id = contract.create_consent( + &patient, + &authorized_party, + &scopes, + &purpose, + &None, + ); + + let consent = contract.get_consent(&consent_id).unwrap(); + assert!(consent.expires_at.is_none()); +} + +#[test] +#[should_panic(expected = "At least one data scope required")] +fn test_create_consent_empty_scopes() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let empty_scopes = soroban_sdk::Vec::new(&env); + let purpose = create_purpose(&env, "Treatment"); + + // Should panic + contract.create_consent(&patient, &authorized_party, &empty_scopes, &purpose, &None); +} + +#[test] +#[should_panic(expected = "Purpose is required")] +fn test_create_consent_empty_purpose() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let scopes = create_data_scopes(&env, &[DataScope::Treatment]); + let empty_purpose = String::from_str(&env, ""); + + // Should panic + contract.create_consent(&patient, &authorized_party, &scopes, &empty_purpose, &None); +} + +#[test] +#[should_panic(expected = "Expiration must be in the future")] +fn test_create_consent_past_expiration() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let scopes = create_data_scopes(&env, &[DataScope::Research]); + let purpose = create_purpose(&env, "Research study"); + + // Set ledger timestamp first, then use 0 as past time + env.ledger().set_timestamp(1000); + let past_expiry = Some(0); // Past time (0 is before current timestamp of 1000) + + // Should panic + contract.create_consent(&patient, &authorized_party, &scopes, &purpose, &past_expiry); +} + +#[test] +fn test_create_consent_multiple_specific_scopes() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let scopes = create_data_scopes( + &env, + &[ + DataScope::Diagnostics, + DataScope::LabResults, + DataScope::Imaging, + DataScope::Prescriptions, + ], + ); + let purpose = create_purpose(&env, "Comprehensive care"); + + let consent_id = contract.create_consent(&patient, &authorized_party, &scopes, &purpose, &None); + + // Verify granted scopes work + assert!(contract.check_consent(&consent_id, &authorized_party, &DataScope::Diagnostics)); + assert!(contract.check_consent(&consent_id, &authorized_party, &DataScope::LabResults)); + assert!(contract.check_consent(&consent_id, &authorized_party, &DataScope::Imaging)); + assert!(contract.check_consent(&consent_id, &authorized_party, &DataScope::Prescriptions)); + + // Verify non-granted scopes don't work + assert!(!contract.check_consent(&consent_id, &authorized_party, &DataScope::Research)); + assert!(!contract.check_consent(&consent_id, &authorized_party, &DataScope::Treatment)); +} + +#[test] +fn test_check_consent_nonexistent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let party = Address::generate(&env); + + // Check non-existent consent ID + assert!(!contract.check_consent(&999, &party, &DataScope::Treatment)); +} + +#[test] +fn test_consent_age_tracking() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Initial age should be 0 + let age = contract.get_consent_age(&consent_id); + assert_eq!(age, 0); + + // Fast forward time + env.ledger().set_timestamp(env.ledger().timestamp() + 3600); // 1 hour + + let age = contract.get_consent_age(&consent_id); + assert_eq!(age, 3600); +} + +#[test] +fn test_consent_days_until_expiration() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + // Consent expiring in 7 days + let seven_days = 7 * 24 * 60 * 60; + let consent_id = create_expiring_consent(&contract, &env, &patient, &authorized_party, seven_days); + + let days = contract.days_until_expiration(&consent_id); + assert!(days.is_some()); + assert_eq!(days.unwrap(), 7); + + // Consent without expiration + let consent_id2 = create_default_consent(&contract, &env, &patient, &authorized_party); + let days2 = contract.days_until_expiration(&consent_id2); + assert!(days2.is_none()); +} From 9ad29ecddcbae0d363547ada3f21967f3114304a Mon Sep 17 00:00:00 2001 From: emarc99 <57766083+emarc99@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:19:34 +0100 Subject: [PATCH 3/5] test: add comprehensive consent revocation and state transition tests - Add 17 revocation and state management tests - Include 9 should_panic tests for invalid state transitions - Test revoke, suspend, and resume operations with edge cases - Add unauthorized access and wrong patient validation tests - Test complete consent lifecycle state machine --- .../src/tests/revocation.rs | 380 ++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 soroban/contracts/patient-consent-management-system/src/tests/revocation.rs diff --git a/soroban/contracts/patient-consent-management-system/src/tests/revocation.rs b/soroban/contracts/patient-consent-management-system/src/tests/revocation.rs new file mode 100644 index 0000000..0cedeb1 --- /dev/null +++ b/soroban/contracts/patient-consent-management-system/src/tests/revocation.rs @@ -0,0 +1,380 @@ +#![cfg(test)] + +use soroban_sdk::{ + testutils::{Address as _, Ledger}, + Address, Env, +}; + +use crate::consent::{ConsentStatus, DataScope}; +use super::utils::*; + +#[test] +fn test_revoke_suspended_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Suspend consent first + contract.suspend_consent(&consent_id, &patient); + let consent = contract.get_consent(&consent_id).unwrap(); + assert_eq!(consent.status, ConsentStatus::Suspended); + + // Revoke suspended consent - should work + contract.revoke_consent(&consent_id, &patient); + + let consent = contract.get_consent(&consent_id).unwrap(); + assert_eq!(consent.status, ConsentStatus::Revoked); + assert!(consent.revoked_at.is_some()); +} + +#[test] +#[should_panic(expected = "Can only revoke active or suspended consents")] +fn test_revoke_already_revoked_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Revoke once + contract.revoke_consent(&consent_id, &patient); + + // Try to revoke again - should panic + contract.revoke_consent(&consent_id, &patient); +} + +#[test] +#[should_panic(expected = "Consent not found")] +fn test_revoke_nonexistent_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + + // Try to revoke non-existent consent + contract.revoke_consent(&999, &patient); +} + +#[test] +#[should_panic(expected = "Unauthorized: not consent owner")] +fn test_revoke_consent_wrong_patient() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let other_patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Try to revoke with different patient - should panic + contract.revoke_consent(&consent_id, &other_patient); +} + +#[test] +fn test_revoked_consent_check_fails() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let scopes = create_data_scopes(&env, &[DataScope::Treatment]); + let purpose = create_purpose(&env, "Treatment"); + + let consent_id = contract.create_consent(&patient, &authorized_party, &scopes, &purpose, &None); + + // Verify consent works before revocation + assert!(contract.check_consent(&consent_id, &authorized_party, &DataScope::Treatment)); + + // Revoke consent + contract.revoke_consent(&consent_id, &patient); + + // Verify consent check fails after revocation + assert!(!contract.check_consent(&consent_id, &authorized_party, &DataScope::Treatment)); + assert!(!contract.is_consent_active(&consent_id)); +} + +#[test] +fn test_revoke_multiple_consents() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let party1 = Address::generate(&env); + let party2 = Address::generate(&env); + let party3 = Address::generate(&env); + + // Create multiple consents + let id1 = create_default_consent(&contract, &env, &patient, &party1); + let id2 = create_default_consent(&contract, &env, &patient, &party2); + let id3 = create_default_consent(&contract, &env, &patient, &party3); + + // Revoke some consents + contract.revoke_consent(&id1, &patient); + contract.revoke_consent(&id3, &patient); + + // Verify correct consents are revoked + let consent1 = contract.get_consent(&id1).unwrap(); + assert_eq!(consent1.status, ConsentStatus::Revoked); + + let consent2 = contract.get_consent(&id2).unwrap(); + assert_eq!(consent2.status, ConsentStatus::Active); + + let consent3 = contract.get_consent(&id3).unwrap(); + assert_eq!(consent3.status, ConsentStatus::Revoked); +} + +#[test] +fn test_suspend_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Suspend consent + contract.suspend_consent(&consent_id, &patient); + + let consent = contract.get_consent(&consent_id).unwrap(); + assert_eq!(consent.status, ConsentStatus::Suspended); + assert!(!contract.is_consent_active(&consent_id)); +} + +#[test] +#[should_panic(expected = "Can only suspend active consents")] +fn test_suspend_already_suspended_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Suspend once + contract.suspend_consent(&consent_id, &patient); + + // Try to suspend again - should panic + contract.suspend_consent(&consent_id, &patient); +} + +#[test] +#[should_panic(expected = "Can only suspend active consents")] +fn test_suspend_revoked_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Revoke consent + contract.revoke_consent(&consent_id, &patient); + + // Try to suspend revoked consent - should panic + contract.suspend_consent(&consent_id, &patient); +} + +#[test] +#[should_panic(expected = "Unauthorized: not consent owner")] +fn test_suspend_consent_wrong_patient() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let other_patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Try to suspend with different patient - should panic + contract.suspend_consent(&consent_id, &other_patient); +} + +#[test] +#[should_panic(expected = "Can only resume suspended consents")] +fn test_resume_active_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Try to resume already active consent - should panic + contract.resume_consent(&consent_id, &patient); +} + +#[test] +#[should_panic(expected = "Can only resume suspended consents")] +fn test_resume_revoked_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Revoke consent + contract.revoke_consent(&consent_id, &patient); + + // Try to resume revoked consent - should panic + contract.resume_consent(&consent_id, &patient); +} + +#[test] +#[should_panic(expected = "Cannot resume expired consent")] +fn test_resume_expired_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + // Create consent expiring in 1 hour + let consent_id = create_expiring_consent(&contract, &env, &patient, &authorized_party, 3600); + + // Suspend consent + contract.suspend_consent(&consent_id, &patient); + + // Fast forward past expiration + env.ledger().set_timestamp(env.ledger().timestamp() + 7200); + + // Try to resume expired consent - should panic + contract.resume_consent(&consent_id, &patient); +} + +#[test] +#[should_panic(expected = "Unauthorized: not consent owner")] +fn test_resume_consent_wrong_patient() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let other_patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Suspend consent + contract.suspend_consent(&consent_id, &patient); + + // Try to resume with different patient - should panic + contract.resume_consent(&consent_id, &other_patient); +} + +#[test] +fn test_suspended_consent_check_fails() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let scopes = create_data_scopes(&env, &[DataScope::Treatment]); + let purpose = create_purpose(&env, "Treatment"); + + let consent_id = contract.create_consent(&patient, &authorized_party, &scopes, &purpose, &None); + + // Verify consent works before suspension + assert!(contract.check_consent(&consent_id, &authorized_party, &DataScope::Treatment)); + + // Suspend consent + contract.suspend_consent(&consent_id, &patient); + + // Verify consent check fails during suspension + assert!(!contract.check_consent(&consent_id, &authorized_party, &DataScope::Treatment)); + assert!(!contract.is_consent_active(&consent_id)); + + // Resume and verify it works again + contract.resume_consent(&consent_id, &patient); + assert!(contract.check_consent(&consent_id, &authorized_party, &DataScope::Treatment)); + assert!(contract.is_consent_active(&consent_id)); +} + +#[test] +fn test_consent_lifecycle_transitions() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Active -> Suspended -> Active -> Revoked + assert_eq!( + contract.get_consent(&consent_id).unwrap().status, + ConsentStatus::Active + ); + + contract.suspend_consent(&consent_id, &patient); + assert_eq!( + contract.get_consent(&consent_id).unwrap().status, + ConsentStatus::Suspended + ); + + contract.resume_consent(&consent_id, &patient); + assert_eq!( + contract.get_consent(&consent_id).unwrap().status, + ConsentStatus::Active + ); + + contract.revoke_consent(&consent_id, &patient); + assert_eq!( + contract.get_consent(&consent_id).unwrap().status, + ConsentStatus::Revoked + ); +} + +#[test] +fn test_expiration_auto_status_change() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + // Create consent expiring in 1 hour + let consent_id = create_expiring_consent(&contract, &env, &patient, &authorized_party, 3600); + + // Initially active + assert!(contract.is_consent_active(&consent_id)); + assert!(!contract.is_expired(&consent_id)); + + // Fast forward past expiration + env.ledger().set_timestamp(env.ledger().timestamp() + 7200); + + // Should be expired now + assert!(!contract.is_consent_active(&consent_id)); + assert!(contract.is_expired(&consent_id)); +} From c69498ee13386cca5f9d8f29e2c52898db10d471 Mon Sep 17 00:00:00 2001 From: emarc99 <57766083+emarc99@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:19:51 +0100 Subject: [PATCH 4/5] test: add audit logging and compliance tests - Add 13 audit and compliance tests - Test individual audit events (created, updated, revoked, suspended, resumed) - Add audit immutability and timestamp ordering tests - Include security tests for unauthorized patient access - Test compliance checks for non-existent consents --- .../src/tests/audit.rs | 343 ++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 soroban/contracts/patient-consent-management-system/src/tests/audit.rs diff --git a/soroban/contracts/patient-consent-management-system/src/tests/audit.rs b/soroban/contracts/patient-consent-management-system/src/tests/audit.rs new file mode 100644 index 0000000..cf682b6 --- /dev/null +++ b/soroban/contracts/patient-consent-management-system/src/tests/audit.rs @@ -0,0 +1,343 @@ +#![cfg(test)] + +use soroban_sdk::{ + testutils::{Address as _, Ledger}, + Address, Env, String, +}; + +use crate::{ + audit::AuditEventType, + consent::DataScope, +}; +use super::utils::*; + +#[test] +fn test_audit_log_consent_created() { + let env = Env::default(); + env.mock_all_auths(); + + // Set ledger timestamp to non-zero value + env.ledger().set_timestamp(1000); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Check audit log + let audit_log = contract.audit_log(&consent_id); + assert_eq!(audit_log.len(), 1); + + let event = audit_log.get(0).unwrap(); + assert_eq!(event.consent_id, consent_id); + assert_eq!(event.event_type, AuditEventType::ConsentCreated); + assert_eq!(event.actor, patient); + assert!(event.timestamp >= 1000); +} + +#[test] +fn test_audit_log_consent_updated() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Update consent + let new_purpose = String::from_str(&env, "Updated purpose"); + contract.update_consent(&consent_id, &patient, &None, &Some(new_purpose), &None); + + // Check audit log + let audit_log = contract.audit_log(&consent_id); + assert_eq!(audit_log.len(), 2); // Created + Updated + + let update_event = audit_log.get(1).unwrap(); + assert_eq!(update_event.event_type, AuditEventType::ConsentUpdated); + assert_eq!(update_event.actor, patient); +} + +#[test] +fn test_audit_log_consent_revoked() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Revoke consent + contract.revoke_consent(&consent_id, &patient); + + // Check audit log + let audit_log = contract.audit_log(&consent_id); + assert_eq!(audit_log.len(), 2); // Created + Revoked + + let revoke_event = audit_log.get(1).unwrap(); + assert_eq!(revoke_event.event_type, AuditEventType::ConsentRevoked); + assert_eq!(revoke_event.actor, patient); +} + +#[test] +fn test_audit_log_consent_suspended_and_resumed() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Suspend consent + contract.suspend_consent(&consent_id, &patient); + + // Resume consent + contract.resume_consent(&consent_id, &patient); + + // Check audit log + let audit_log = contract.audit_log(&consent_id); + assert_eq!(audit_log.len(), 3); // Created + Suspended + Resumed + + let suspend_event = audit_log.get(1).unwrap(); + assert_eq!(suspend_event.event_type, AuditEventType::ConsentSuspended); + + let resume_event = audit_log.get(2).unwrap(); + assert_eq!(resume_event.event_type, AuditEventType::ConsentResumed); +} + +#[test] +fn test_multiple_access_logging() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let scopes = create_data_scopes(&env, &[DataScope::Diagnostics, DataScope::LabResults]); + let purpose = create_purpose(&env, "Treatment"); + + let consent_id = contract.create_consent(&patient, &authorized_party, &scopes, &purpose, &None); + + // Log multiple accesses + contract.log_access( + &consent_id, + &authorized_party, + &DataScope::Diagnostics, + &String::from_str(&env, "Access 1"), + ); + + contract.log_access( + &consent_id, + &authorized_party, + &DataScope::LabResults, + &String::from_str(&env, "Access 2"), + ); + + contract.log_access( + &consent_id, + &authorized_party, + &DataScope::Diagnostics, + &String::from_str(&env, "Access 3"), + ); + + // Check access logs + let access_logs = contract.get_access_logs(&consent_id); + assert_eq!(access_logs.len(), 3); + + // Verify different scopes were logged + assert_eq!(access_logs.get(0).unwrap().data_scope, DataScope::Diagnostics); + assert_eq!(access_logs.get(1).unwrap().data_scope, DataScope::LabResults); + assert_eq!(access_logs.get(2).unwrap().data_scope, DataScope::Diagnostics); +} + +#[test] +fn test_access_logging_creates_audit_event() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Log access + contract.log_access( + &consent_id, + &authorized_party, + &DataScope::Treatment, + &String::from_str(&env, "Accessing treatment data"), + ); + + // Check that access created an audit event + let audit_log = contract.audit_log(&consent_id); + assert_eq!(audit_log.len(), 2); // Created + Accessed + + let access_event = audit_log.get(1).unwrap(); + assert_eq!(access_event.event_type, AuditEventType::ConsentAccessed); + assert_eq!(access_event.actor, authorized_party); +} + +#[test] +fn test_audit_summary_unauthorized_patient() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let other_patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Try to get summary with wrong patient + let summary = contract.get_audit_summary(&other_patient, &consent_id); + assert!(summary.is_none()); +} + +#[test] +fn test_audit_summary_complex_lifecycle() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Perform various operations + contract.log_access( + &consent_id, + &authorized_party, + &DataScope::Treatment, + &String::from_str(&env, "Access 1"), + ); + + contract.suspend_consent(&consent_id, &patient); + contract.resume_consent(&consent_id, &patient); + + contract.log_access( + &consent_id, + &authorized_party, + &DataScope::Treatment, + &String::from_str(&env, "Access 2"), + ); + + contract.revoke_consent(&consent_id, &patient); + + // Get summary + let summary = contract.get_audit_summary(&patient, &consent_id); + assert!(summary.is_some()); + + let summary = summary.unwrap(); + assert_eq!(summary.total_events, 6); // Created, Accessed, Suspended, Resumed, Accessed, Revoked + assert_eq!(summary.total_accesses, 2); +} + +#[test] +fn test_audit_event_timestamps_sequential() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Perform operations with time progression + env.ledger().set_timestamp(env.ledger().timestamp() + 100); + contract.suspend_consent(&consent_id, &patient); + + env.ledger().set_timestamp(env.ledger().timestamp() + 100); + contract.resume_consent(&consent_id, &patient); + + env.ledger().set_timestamp(env.ledger().timestamp() + 100); + contract.revoke_consent(&consent_id, &patient); + + // Check timestamps are sequential + let audit_log = contract.audit_log(&consent_id); + assert_eq!(audit_log.len(), 4); + + let mut last_timestamp = 0; + for i in 0..audit_log.len() { + let event = audit_log.get(i).unwrap(); + assert!(event.timestamp >= last_timestamp); + last_timestamp = event.timestamp; + } +} + +#[test] +fn test_access_log_nonexistent_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + + // Get access logs for non-existent consent + let access_logs = contract.get_access_logs(&999); + assert_eq!(access_logs.len(), 0); +} + +#[test] +fn test_audit_log_nonexistent_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + + // Get audit log for non-existent consent + let audit_log = contract.audit_log(&999); + assert_eq!(audit_log.len(), 0); +} + +#[test] +fn test_audit_immutability() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + let patient = Address::generate(&env); + let authorized_party = Address::generate(&env); + + let consent_id = create_default_consent(&contract, &env, &patient, &authorized_party); + + // Get initial audit log + let initial_log = contract.audit_log(&consent_id); + assert_eq!(initial_log.len(), 1); + let initial_event = initial_log.get(0).unwrap(); + + // Perform more operations + contract.suspend_consent(&consent_id, &patient); + contract.resume_consent(&consent_id, &patient); + + // Get updated audit log + let updated_log = contract.audit_log(&consent_id); + assert_eq!(updated_log.len(), 3); + + // Verify original event remains unchanged + let first_event = updated_log.get(0).unwrap(); + assert_eq!(first_event.event_id, initial_event.event_id); + assert_eq!(first_event.event_type, initial_event.event_type); + assert_eq!(first_event.timestamp, initial_event.timestamp); +} + +#[test] +fn test_compliance_nonexistent_consent() { + let env = Env::default(); + env.mock_all_auths(); + + let contract = create_test_contract(&env); + + // Check compliance for non-existent consent + assert!(!contract.check_gdpr_compliance(&999)); + assert!(!contract.check_hipaa_compliance(&999)); +} From c31874d67274cc69adb5d6b4220996f12df51dd1 Mon Sep 17 00:00:00 2001 From: emarc99 <57766083+emarc99@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:21:28 +0100 Subject: [PATCH 5/5] chore: add tests helper --- .../src/tests/utils.rs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 soroban/contracts/patient-consent-management-system/src/tests/utils.rs diff --git a/soroban/contracts/patient-consent-management-system/src/tests/utils.rs b/soroban/contracts/patient-consent-management-system/src/tests/utils.rs new file mode 100644 index 0000000..4901d1d --- /dev/null +++ b/soroban/contracts/patient-consent-management-system/src/tests/utils.rs @@ -0,0 +1,79 @@ +use soroban_sdk::{testutils::Address as _, Address, Env, String, Vec}; +use crate::{PatientConsentManagementSystem, PatientConsentManagementSystemClient, consent::DataScope}; + +/// Helper function to create and initialize a test contract +pub fn create_test_contract(env: &Env) -> PatientConsentManagementSystemClient { + let client = PatientConsentManagementSystemClient::new( + env, + &env.register(PatientConsentManagementSystem, ()), + ); + client.initialize(); + client +} + +/// Helper to create a vector of data scopes +pub fn create_data_scopes(env: &Env, scopes: &[DataScope]) -> Vec { + let mut vec = Vec::new(env); + for scope in scopes { + vec.push_back(scope.clone()); + } + vec +} + +/// Helper to create a standard test purpose string +pub fn create_purpose(env: &Env, text: &str) -> String { + String::from_str(env, text) +} + +/// Helper to generate multiple addresses for testing +pub fn generate_addresses(env: &Env, count: usize) -> Vec
{ + let mut addresses = Vec::new(env); + for _ in 0..count { + addresses.push_back(Address::generate(env)); + } + addresses +} + +/// Helper to create a consent with default parameters +pub fn create_default_consent( + contract: &PatientConsentManagementSystemClient, + env: &Env, + patient: &Address, + authorized_party: &Address, +) -> u64 { + let scopes = create_data_scopes(env, &[DataScope::Treatment]); + let purpose = create_purpose(env, "Standard medical treatment"); + + contract.create_consent(patient, authorized_party, &scopes, &purpose, &None) +} + +/// Helper to create a consent with expiration +pub fn create_expiring_consent( + contract: &PatientConsentManagementSystemClient, + env: &Env, + patient: &Address, + authorized_party: &Address, + expires_in_seconds: u64, +) -> u64 { + let scopes = create_data_scopes(env, &[DataScope::Treatment]); + let purpose = create_purpose(env, "Temporary treatment consent"); + let expires_at = Some(env.ledger().timestamp() + expires_in_seconds); + + contract.create_consent(patient, authorized_party, &scopes, &purpose, &expires_at) +} + +/// Helper to verify consent is active +pub fn assert_consent_active( + contract: &PatientConsentManagementSystemClient, + consent_id: u64, +) { + assert!(contract.is_consent_active(&consent_id), "Consent should be active"); +} + +/// Helper to verify consent is not active +pub fn assert_consent_not_active( + contract: &PatientConsentManagementSystemClient, + consent_id: u64, +) { + assert!(!contract.is_consent_active(&consent_id), "Consent should not be active"); +}