restart_isolation.rs (5695B)
1 use std::fs; 2 3 use harvestcircle_application::{ 4 Clock, DurableRequestId, IdentityNamespaceRepository, IdentityPreferenceKey, 5 InMemorySecretStore, RelayConfiguration, SessionState, 6 }; 7 use harvestcircle_domain::{SecretKeyInput, UnixTimestamp}; 8 use harvestcircle_runtime::PersistentAppCore; 9 use harvestcircle_storage::HarvestCircleStorageContract; 10 use radroots_runtime_paths::{ 11 InstanceId, RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver, 12 RadrootsPlatform, RuntimeContext, RuntimeContextBootstrap, RuntimeContextSource, ServiceId, 13 }; 14 use radroots_service_sqlite::MigrationBuildIdentity; 15 use tempfile::tempdir; 16 17 const SECRET_A: &str = "7e7e9c42a91bfef19fa7ea99d52d8afdb67d893a8fefba1f5cb9793f2107f6d7"; 18 const SECRET_B: &str = "0101010101010101010101010101010101010101010101010101010101010101"; 19 20 struct FixedClock; 21 22 impl Clock for FixedClock { 23 fn now(&self) -> UnixTimestamp { 24 UnixTimestamp::from_seconds(200).expect("fixed timestamp") 25 } 26 } 27 28 fn context(root: &std::path::Path) -> RuntimeContext { 29 let context = RuntimeContext::resolve( 30 &RadrootsPathResolver::new( 31 RadrootsPlatform::current(), 32 RadrootsHostEnvironment::default(), 33 ), 34 RuntimeContextBootstrap::new( 35 RadrootsPathProfile::RepoLocal, 36 Some(root.canonicalize().expect("canonical root")), 37 RuntimeContextSource::BootstrapCli, 38 RuntimeContextSource::SafeDefault, 39 ) 40 .expect("bootstrap"), 41 ServiceId::new("harvestcircle").expect("service"), 42 InstanceId::new("desktop").expect("instance"), 43 ) 44 .expect("context"); 45 fs::create_dir_all(root.join("data")).expect("state root"); 46 context 47 } 48 49 fn build() -> MigrationBuildIdentity { 50 MigrationBuildIdentity::new( 51 "0.1.0-alpha", 52 "1111111111111111111111111111111111111111", 53 "2222222222222222222222222222222222222222", 54 "1.97.1", 55 "test", 56 "test", 57 1, 58 1, 59 1, 60 1, 61 1, 62 ) 63 .expect("build") 64 } 65 66 #[tokio::test] 67 async fn restart_restores_selection_and_keeps_identity_namespaces_isolated() { 68 let directory = tempdir().expect("temporary directory"); 69 let context = context(directory.path()); 70 let build = build(); 71 let path = HarvestCircleStorageContract::from_runtime_context(&context) 72 .expect("contract") 73 .paths() 74 .state_database() 75 .to_path_buf(); 76 let secrets = InMemorySecretStore::default(); 77 let (owner_a, owner_b); 78 79 { 80 let adapter = PersistentAppCore::open( 81 &context, 82 RelayConfiguration::default(), 83 200_000, 84 200, 85 &build, 86 ) 87 .await 88 .expect("persistent adapter"); 89 adapter 90 .bootstrap(&secrets, &FixedClock) 91 .await 92 .expect("bootstrap"); 93 owner_a = adapter 94 .import_secret_key_durable( 95 &DurableRequestId::parse("01890f3e-7b1c-7000-8000-000000000101").expect("request"), 96 adapter.core().snapshot().revision().value(), 97 SecretKeyInput::parse(SECRET_A.to_owned()).expect("secret A"), 98 &secrets, 99 &FixedClock, 100 ) 101 .await 102 .expect("identity A") 103 .identity() 104 .public_key(); 105 owner_b = adapter 106 .import_secret_key_durable( 107 &DurableRequestId::parse("01890f3e-7b1c-7000-8000-000000000102").expect("request"), 108 adapter.core().snapshot().revision().value(), 109 SecretKeyInput::parse(SECRET_B.to_owned()).expect("secret B"), 110 &secrets, 111 &FixedClock, 112 ) 113 .await 114 .expect("identity B") 115 .identity() 116 .public_key(); 117 adapter 118 .database() 119 .set_value(owner_a, IdentityPreferenceKey::NamespaceProbe, "identity-a") 120 .await 121 .expect("namespace A"); 122 adapter 123 .database() 124 .set_value(owner_b, IdentityPreferenceKey::NamespaceProbe, "identity-b") 125 .await 126 .expect("namespace B"); 127 adapter.select_identity(owner_b).await.expect("select B"); 128 adapter.close().await.expect("close"); 129 } 130 131 let reopened = PersistentAppCore::open( 132 &context, 133 RelayConfiguration::default(), 134 200_000, 135 200, 136 &build, 137 ) 138 .await 139 .expect("reopen adapter"); 140 let restored = reopened 141 .bootstrap(&secrets, &FixedClock) 142 .await 143 .expect("restore"); 144 assert_eq!(restored.identities().len(), 2); 145 assert_eq!(restored.selected_identity(), Some(owner_b)); 146 assert_eq!(restored.session(), SessionState::SignedOut); 147 assert_eq!( 148 reopened 149 .database() 150 .get_value(owner_a, IdentityPreferenceKey::NamespaceProbe) 151 .await 152 .expect("read A"), 153 Some("identity-a".to_owned()) 154 ); 155 assert_eq!( 156 reopened 157 .database() 158 .get_value(owner_b, IdentityPreferenceKey::NamespaceProbe) 159 .await 160 .expect("read B"), 161 Some("identity-b".to_owned()) 162 ); 163 164 reopened.close().await.expect("close reopened"); 165 let database = fs::read(path).expect("database bytes"); 166 assert!( 167 !database 168 .windows(SECRET_A.len()) 169 .any(|bytes| bytes == SECRET_A.as_bytes()) 170 ); 171 assert!( 172 !database 173 .windows(SECRET_B.len()) 174 .any(|bytes| bytes == SECRET_B.as_bytes()) 175 ); 176 assert!(!database.windows(5).any(|bytes| bytes == b"nsec1")); 177 }