local_relay_e2e.rs (5424B)
1 use std::time::Duration; 2 3 use harvestcircle_application::{ 4 Clock, DurableRequestId, InMemorySecretStore, ProfileLoadState, ProfileRepository, 5 RelayConfiguration, RelayConnectionState, SecretStore, SessionState, 6 }; 7 use harvestcircle_domain::{SecretKeyInput, UnixTimestamp}; 8 use harvestcircle_nostr::SdkNostrClient; 9 use harvestcircle_runtime::PersistentAppCore; 10 use nostr::{EventBuilder, Keys, Metadata}; 11 use nostr_relay_builder::MockRelay; 12 use nostr_sdk::Client; 13 use radroots_runtime_paths::{ 14 InstanceId, RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver, 15 RadrootsPlatform, RuntimeContext, RuntimeContextBootstrap, RuntimeContextSource, ServiceId, 16 }; 17 use radroots_service_sqlite::MigrationBuildIdentity; 18 use radroots_transport_nostr::{RelayAccess, RelayEndpoint, RelayUrlPolicy}; 19 20 const SECRET_HEX: &str = "7e7e9c42a91bfef19fa7ea99d52d8afdb67d893a8fefba1f5cb9793f2107f6d7"; 21 22 struct FixedClock; 23 24 impl Clock for FixedClock { 25 fn now(&self) -> UnixTimestamp { 26 UnixTimestamp::from_seconds(100).expect("fixed timestamp") 27 } 28 } 29 30 #[tokio::test] 31 async fn local_relay_e2e_imports_activates_refreshes_and_caches_profile() { 32 let directory = tempfile::tempdir().expect("temporary directory"); 33 let context = RuntimeContext::resolve( 34 &RadrootsPathResolver::new( 35 RadrootsPlatform::current(), 36 RadrootsHostEnvironment::default(), 37 ), 38 RuntimeContextBootstrap::new( 39 RadrootsPathProfile::RepoLocal, 40 Some(directory.path().canonicalize().expect("canonical root")), 41 RuntimeContextSource::BootstrapCli, 42 RuntimeContextSource::SafeDefault, 43 ) 44 .expect("bootstrap"), 45 ServiceId::new("harvestcircle").expect("service"), 46 InstanceId::new("desktop").expect("instance"), 47 ) 48 .expect("context"); 49 std::fs::create_dir_all(directory.path().join("data")).expect("state root"); 50 let build = 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 let local_relay = MockRelay::run().await.expect("local relay"); 65 let relay_url = local_relay.url().await; 66 let keys = Keys::parse(SECRET_HEX).expect("known secret key"); 67 let publisher = Client::new(keys); 68 publisher 69 .add_relay(relay_url.clone()) 70 .await 71 .expect("publisher relay"); 72 publisher.connect().await; 73 publisher.wait_for_connection(Duration::from_secs(2)).await; 74 publisher 75 .send_event_builder(EventBuilder::metadata( 76 &Metadata::new() 77 .name("farmer") 78 .display_name("Farm Identity") 79 .about("Local food profile"), 80 )) 81 .await 82 .expect("publish profile"); 83 84 let relay = RelayEndpoint::new( 85 relay_url.as_str(), 86 RelayUrlPolicy::Local, 87 RelayAccess::ReadWrite, 88 ) 89 .expect("relay endpoint"); 90 let adapter = PersistentAppCore::open( 91 &context, 92 RelayConfiguration::new(vec![relay]).expect("relay configuration"), 93 100_000, 94 100, 95 &build, 96 ) 97 .await 98 .expect("persistent adapter"); 99 let secrets = InMemorySecretStore::default(); 100 adapter 101 .bootstrap(&secrets, &FixedClock) 102 .await 103 .expect("bootstrap"); 104 let imported = adapter 105 .import_secret_key_durable( 106 &DurableRequestId::parse("01890f3e-7b1c-7000-8000-000000000201").expect("request"), 107 adapter.core().snapshot().revision().value(), 108 SecretKeyInput::parse(SECRET_HEX.to_owned()).expect("secret input"), 109 &secrets, 110 &FixedClock, 111 ) 112 .await 113 .expect("import identity"); 114 let public_key = imported.identity().public_key(); 115 assert!( 116 secrets 117 .contains(public_key) 118 .await 119 .expect("credential exists") 120 ); 121 adapter 122 .activate_identity(public_key, &secrets, &FixedClock) 123 .await 124 .expect("activate identity"); 125 126 let refreshed = adapter 127 .core() 128 .refresh_active_profile( 129 adapter.database(), 130 &SdkNostrClient::new(Duration::from_secs(2)), 131 &FixedClock, 132 std::time::Instant::now() + Duration::from_secs(2), 133 ) 134 .await 135 .expect("refresh profile"); 136 137 assert_eq!(refreshed.session(), SessionState::Active); 138 let active = refreshed.active_identity().expect("active identity"); 139 assert_eq!(active.relay_state(), RelayConnectionState::Connected); 140 assert_eq!(active.profile_state(), ProfileLoadState::Fresh); 141 assert_eq!( 142 active.profile().and_then(|profile| profile.display_name()), 143 Some("Farm Identity") 144 ); 145 let cached = adapter 146 .database() 147 .load_profile(public_key) 148 .await 149 .expect("load cache") 150 .expect("cached profile"); 151 assert_eq!( 152 cached.candidate().metadata().preferred_name(), 153 Some("Farm Identity") 154 ); 155 let public_debug = format!("{refreshed:?}"); 156 assert!(!public_debug.contains(SECRET_HEX)); 157 assert!(!public_debug.contains("nsec1")); 158 adapter.close().await.expect("close"); 159 publisher.shutdown().await; 160 local_relay.shutdown(); 161 }