diff --git a/crates/sargon/src/profile/v100/entity/has_security_state.rs b/crates/sargon/src/profile/v100/entity/has_security_state.rs index 3466f7126..2f79b4df2 100644 --- a/crates/sargon/src/profile/v100/entity/has_security_state.rs +++ b/crates/sargon/src/profile/v100/entity/has_security_state.rs @@ -4,6 +4,8 @@ pub trait HasSecurityState: HasFactorInstances + IsSecurityStateAware { fn security_state(&self) -> EntitySecurityState; fn set_security_state_unchecked(&mut self, new_state: EntitySecurityState); + // TODO: Should we check `provisional_securified_config` of `self` and/or + // of `new_state`? fn set_security_state( &mut self, new_state: EntitySecurityState, @@ -57,3 +59,129 @@ impl HasFactorInstances for T { self.security_state().unique_tx_signing_factor_instances() } } + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = AccountOrPersona; + + fn test_set_security_state_fail_cannot_unsecurify(sut: impl Into) { + let mut sut = sut.into(); + assert!(sut.is_securified()); + + let unsecurified = EntitySecurityState::sample(); + assert!(unsecurified.is_unsecured()); + + let result = sut.set_security_state(unsecurified); + assert_eq!( + result, + Err(CommonError::SecurityStateSecurifiedButExpectedUnsecurified) + ); + + // assert unchanged + assert!(sut.is_securified()); + } + + #[test] + fn set_security_state_fail_cannot_unsecurify_account() { + test_set_security_state_fail_cannot_unsecurify(Account::sample_at(2)) + } + + #[test] + fn set_security_state_fail_cannot_unsecurify_persona() { + test_set_security_state_fail_cannot_unsecurify(Persona::sample_at(2)) + } + + fn test_set_security_state_fail_can_change_unsecurified( + sut: impl Into, + ) { + let mut sut = sut.into(); + assert!(!sut.is_securified()); + + let unsecurified = EntitySecurityState::sample(); + assert!(unsecurified.is_unsecured()); + + let result = sut.set_security_state(unsecurified.clone()); + assert!(result.is_ok()); + assert_eq!(sut.security_state(), unsecurified); + } + + #[test] + fn set_security_state_fail_can_change_unsecurified_account() { + test_set_security_state_fail_can_change_unsecurified(Account::sample()); + } + + #[test] + fn set_security_state_fail_can_change_unsecurified_persona() { + test_set_security_state_fail_can_change_unsecurified(Persona::sample()); + } + + fn test_set_security_state_fail_access_controller_mismatch( + sut: impl Into, + ) { + let mut sut = sut.into(); + let entity_state = sut.security_state(); + assert!(sut.is_securified()); + + let other_securified = EntitySecurityState::Securified { + value: SecuredEntityControl::sample(), + }; + + let result = sut.set_security_state(other_securified); + assert_eq!( + result, + Err(CommonError::SecurityStateAccessControllerAddressMismatch) + ); + + // assert unchanged + assert_eq!(sut.security_state(), entity_state); + } + + #[test] + fn set_security_state_fail_access_controller_mismatch_account() { + test_set_security_state_fail_access_controller_mismatch( + Account::sample_at(2), + ); + } + + #[test] + fn set_security_state_fail_access_controller_mismatch_persona() { + test_set_security_state_fail_access_controller_mismatch( + Persona::sample_at(2), + ) + } + + fn test_set_security_state_can_change_securified(sut: impl Into) { + let mut sut = sut.into(); + let entity_state = sut.security_state(); + assert!(sut.is_securified()); + let access_controller_address = entity_state + .clone() + .as_securified() + .unwrap() + .access_controller_address; + + let mut value = SecuredEntityControl::sample(); + value.access_controller_address = access_controller_address; + let other_securified = EntitySecurityState::Securified { value }; + + let result = sut.set_security_state(other_securified); + + assert!(result.is_ok()); + assert!(sut.is_securified()); + assert_ne!(sut.security_state(), entity_state); + } + + #[test] + fn set_security_state_can_change_securified_account() { + test_set_security_state_can_change_securified(Account::sample_at(2)); + } + + #[test] + fn set_security_state_can_change_securified_persona() { + test_set_security_state_can_change_securified(Persona::sample_at(2)); + } +} diff --git a/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs b/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs index 789349428..e6b85d344 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs @@ -1409,26 +1409,64 @@ mod tests { } #[actix_rt::test] - async fn update_account_updates_in_memory_profile() { + async fn update_account_and_persona_updates_in_memory_profile() { // ARRANGE - let os = SUT::fast_boot().await; + let event_bus_driver = RustEventBusDriver::new(); + let drivers = Drivers::with_event_bus(event_bus_driver.clone()); + let clients = Clients::new(Bios::new(drivers)); + let interactors = Interactors::new_from_clients(&clients); + let os = timeout( + SARGON_OS_TEST_MAX_ASYNC_DURATION, + SUT::boot_with_clients_and_interactor(clients, interactors), + ) + .await + .unwrap(); + os.new_wallet(false).await.unwrap(); let mut account = Account::sample(); os.with_timeout(|x| x.add_account(account.clone())) .await .unwrap(); - // ACT - account.display_name = DisplayName::random(); - os.with_timeout(|x| x.update_account(account.clone())) + let mut persona = Persona::sample(); + os.with_timeout(|x| x.add_persona(persona.clone())) .await .unwrap(); + // ACT + account.display_name = DisplayName::random(); + persona.display_name = DisplayName::random(); + os.with_timeout(|x| { + x.update_entities_erased(IdentifiedVecOf::from_iter([ + AccountOrPersona::from(account.clone()), + AccountOrPersona::from(persona.clone()), + ])) + }) + .await + .unwrap(); + // ASSERT + assert_eq!(os.profile().unwrap().networks[0].accounts[0], account); + assert_eq!(os.profile().unwrap().networks[0].personas[0], persona); + use EventKind::*; assert_eq!( - os.profile().unwrap().networks[0].accounts[0], - account.clone() - ) + event_bus_driver + .recorded() + .into_iter() + .map(|e| e.event.kind()) + .collect_vec(), + vec![ + Booted, + ProfileSaved, + ProfileSaved, + AccountAdded, + ProfileSaved, + PersonaAdded, + ProfileSaved, + AccountUpdated, + PersonaUpdated + ] + ); } #[actix_rt::test]