app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

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 }