From 497bf71910f0f224ce66b154d58a228095a40c0a Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 1 Nov 2023 00:00:00 +0000 Subject: feat(login) fetch from discovered write relays immediately request metadata and relay list from any newly discovered user write relays --- src/client.rs | 31 +- src/key_handling/users.rs | 320 +++++++++++++----- test_utils/src/lib.rs | 19 ++ tests/login.rs | 831 +++++++++++++++++++++++++--------------------- 4 files changed, 727 insertions(+), 474 deletions(-) diff --git a/src/client.rs b/src/client.rs index 5ddf742..929f19a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -101,14 +101,7 @@ impl Connect for Client { filters.clone(), ) }) - .map(|(relay, filters)| { - relay.get_events_of( - filters, - // 20 is nostr_sdk default - std::time::Duration::from_secs(20), - nostr_sdk::FilterOptions::ExitOnEOSE, - ) - }), + .map(|(relay, filters)| get_events_of(relay, filters)), ) .await; @@ -116,6 +109,24 @@ impl Connect for Client { } } +async fn get_events_of( + relay: &nostr_sdk::Relay, + filters: Vec, +) -> Result> { + if !relay.is_connected().await { + relay.connect(true).await; + } + relay + .get_events_of( + filters, + // 20 is nostr_sdk default + std::time::Duration::from_secs(20), + nostr_sdk::FilterOptions::ExitOnEOSE, + ) + .await + .context("failed to get events from relay") +} + #[derive(Default)] pub struct Params { pub keys: Option, @@ -133,9 +144,7 @@ impl Params { } } -fn get_dedup_events( - relay_results: Vec, nostr_sdk::relay::Error>>, -) -> Vec { +fn get_dedup_events(relay_results: Vec>>) -> Vec { let mut dedup_events: Vec = vec![]; for events in relay_results.into_iter().flatten() { for event in events { diff --git a/src/key_handling/users.rs b/src/key_handling/users.rs index 91519bc..a486296 100644 --- a/src/key_handling/users.rs +++ b/src/key_handling/users.rs @@ -143,7 +143,8 @@ impl UserManagement for UserManager { .clone()) } /// get UserRef fetching most recent user relays and metadata infomation - /// from relays + /// from + #[allow(clippy::too_many_lines)] async fn get_user( &self, #[cfg(test)] client: &MockConnect, @@ -155,103 +156,127 @@ impl UserManagement for UserManager { .config_manager .load() .context("failed to load application config")?; - let user_ref = cfg + let mut user_ref = cfg .users .iter() .find(|u| u.public_key.eq(public_key)) - .context(format!("pubkey isn't a current user: {public_key}"))?; + .context(format!("pubkey isn't a current user: {public_key}"))? + .clone(); // return cache if last fetched was within X minutes if !unix_timestamp_after_now_plus_secs( user_ref.last_checked, use_cache_unless_checked_more_than_x_secs_ago, ) { - return Ok(user_ref.clone()); + return Ok(user_ref); } - let events: Vec = match client - .get_events( - if user_ref.relays.write().is_empty() { - client.get_fallback_relays().clone() - } else { - user_ref.relays.write() - }, - vec![ - nostr::Filter::default() - .author(public_key.to_string()) - .since(nostr::Timestamp::from(user_ref.metadata.created_at + 1)) - .kind(Kind::Metadata), - nostr::Filter::default() - .author(public_key.to_string()) - .since(nostr::Timestamp::from(user_ref.relays.created_at + 1)) - .kind(Kind::RelayList), - ], - ) - .await - { - Ok(events) => events, - Err(_) => { - // TODO error reporting - return Ok(user_ref.clone()); - } + + let mut relays_to_search = if user_ref.relays.write().is_empty() { + client.get_fallback_relays().clone() + } else { + user_ref.relays.write() }; - let mut user_ref = user_ref.clone(); + let mut relays_searched: Vec = vec![]; - user_ref.last_checked = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .context("system time should be after the year 1970")? - .as_secs(); - - if let Some(new_metadata_event) = events - .iter() - .filter(|e| e.kind.eq(&nostr::Kind::Metadata) && e.pubkey.eq(public_key)) - .max_by_key(|e| e.created_at) - { - if new_metadata_event.created_at.as_u64() > user_ref.metadata.created_at { - let metadata = nostr::Metadata::from_json(new_metadata_event.content.clone()) - .context("metadata cannot be found in kind 0 event content")?; - user_ref.metadata = UserMetadata { - name: metadata - .name - .context("user metadata should always have name")?, - created_at: new_metadata_event.created_at.as_u64(), - }; + loop { + for r in &relays_to_search { + if !relays_searched.iter().any(|sr| r.eq(sr)) { + relays_searched.push(r.clone()); + } } - }; - if let Some(new_relays_event) = events - .iter() - .filter(|e| e.kind.eq(&nostr::Kind::RelayList) && e.pubkey.eq(public_key)) - .max_by_key(|e| e.created_at) - { - if new_relays_event.created_at.as_u64() > user_ref.relays.created_at { - user_ref.relays = UserRelays { - relays: new_relays_event - .tags + let events: Vec = match client + .get_events( + relays_to_search, + vec![ + nostr::Filter::default() + .author(public_key.to_string()) + .since(nostr::Timestamp::from(user_ref.metadata.created_at + 1)) + .kind(Kind::Metadata), + nostr::Filter::default() + .author(public_key.to_string()) + .since(nostr::Timestamp::from(user_ref.relays.created_at + 1)) + .kind(Kind::RelayList), + ], + ) + .await + { + Ok(events) => events, + Err(_) => { + return Ok(user_ref.clone()); + } + }; + + user_ref.last_checked = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .context("system time should be after the year 1970")? + .as_secs(); + + if let Some(new_metadata_event) = events + .iter() + .filter(|e| e.kind.eq(&nostr::Kind::Metadata) && e.pubkey.eq(public_key)) + .max_by_key(|e| e.created_at) + { + if new_metadata_event.created_at.as_u64() > user_ref.metadata.created_at { + let metadata = nostr::Metadata::from_json(new_metadata_event.content.clone()) + .context("metadata cannot be found in kind 0 event content")?; + user_ref.metadata = UserMetadata { + name: metadata + .name + .context("user metadata should always have name")?, + created_at: new_metadata_event.created_at.as_u64(), + }; + } + }; + + if let Some(new_relays_event) = events + .iter() + .filter(|e| e.kind.eq(&nostr::Kind::RelayList) && e.pubkey.eq(public_key)) + .max_by_key(|e| e.created_at) + { + if new_relays_event.created_at.as_u64() > user_ref.relays.created_at { + let new_relay_list = UserRelays { + relays: new_relays_event + .tags + .iter() + .filter(|t| t.kind().eq(&nostr::TagKind::R)) + .map(|t| UserRelayRef { + url: t.as_vec()[1].clone(), + read: t.as_vec().len() == 2 || t.as_vec()[2].eq("read"), + write: t.as_vec().len() == 2 || t.as_vec()[2].eq("write"), + }) + .collect(), + created_at: new_relays_event.created_at.as_u64(), + }; + let new_relays: Vec = new_relay_list + .write() .iter() - .filter(|t| t.kind().eq(&nostr::TagKind::R)) - .map(|t| UserRelayRef { - url: t.as_vec()[1].clone(), - read: t.as_vec().len() == 2 || t.as_vec()[2].eq("read"), - write: t.as_vec().len() == 2 || t.as_vec()[2].eq("write"), - }) - .collect(), - created_at: new_relays_event.created_at.as_u64(), - }; - } - }; + .filter(|r| !relays_searched.iter().any(|or| r.eq(&or))) + .map(std::clone::Clone::clone) + .collect(); + user_ref.relays = new_relay_list; + + if !new_relays.is_empty() { + relays_to_search = new_relays; + continue; + } + } + }; - // remove any duplicate entries for key before adding it to config - let mut cfg = self.config_manager.load().context("failed to load application config to find and remove any old versions of the user's encrypted key")?; - cfg.users = cfg - .users - .clone() - .into_iter() - .filter(|r| !r.public_key.eq(public_key)) - .collect(); - cfg.users.push(user_ref.clone()); - self.config_manager - .save(&cfg) - .context("failed to save application configuration with new user details in")?; + // remove any duplicate entries for key before adding it to config + let mut cfg = self.config_manager.load().context("failed to load application config to find and remove any old versions of the user's encrypted key")?; + cfg.users = cfg + .users + .clone() + .into_iter() + .filter(|r| !r.public_key.eq(public_key)) + .collect(); + cfg.users.push(user_ref.clone()); + self.config_manager + .save(&cfg) + .context("failed to save application configuration with new user details in")?; + break; + } Ok(user_ref) } } @@ -611,23 +636,33 @@ mod tests { .clone() } + fn expected_userrelayrefs_write1() -> UserRelayRef { + UserRelayRef { + url: "wss://fredswrite1.relay".into(), + read: false, + write: true, + } + .clone() + } + + fn expected_userrelayrefs_read_write1() -> UserRelayRef { + UserRelayRef { + url: "wss://fredsreadwrite.relay".into(), + read: true, + write: true, + } + .clone() + } + fn expected_userrelayrefs() -> Vec { vec![ - UserRelayRef { - url: "wss://fredswrite1.relay".into(), - read: false, - write: true, - }, + expected_userrelayrefs_write1(), UserRelayRef { url: "wss://fredsread1.relay".into(), read: true, write: false, }, - UserRelayRef { - url: "wss://fredsreadwrite.relay".into(), - read: true, - write: true, - }, + expected_userrelayrefs_read_write1(), ] } @@ -958,6 +993,107 @@ mod tests { ))?; Ok(()) } + + mod fetches_from_new_relays_discovered_in_incoming_relay_list { + use super::*; + + #[test] + fn when_all_relays_in_list_are_new_finds_name() -> Result<()> { + let mut m = MockUserManager::default(); + let mut client = generate_mock_client(); + m.config_manager.expect_load().returning(|| { + Ok(MyConfig { + users: vec![UserRef { + relays: UserRelays { + relays: vec![], + created_at: 0, + }, + ..generate_standard_config().users[0].clone() + }], + ..generate_standard_config() + }) + }); + m.config_manager.expect_save().returning(|_| Ok(())); + client + .expect_get_events() + .times(2) + .withf(move |relays, _filters| { + fallback_relays().eq(relays) + || UserRelays { + relays: expected_userrelayrefs(), + created_at: 0, + } + .write() + .eq(relays) + }) + .returning(|relays, _| { + if fallback_relays().eq(&relays) { + Ok(vec![generate_relaylist_event()]) + } else if (UserRelays { + relays: expected_userrelayrefs(), + created_at: 0, + }) + .write() + .eq(&relays) + { + Ok(vec![generate_test_key_1_metadata_event("fred")]) + } else { + Ok(vec![]) + } + }); + + let res = futures::executor::block_on(m.get_user( + &client, + &TEST_KEY_1_KEYS.public_key(), + 5 * 60, // 5 mins ago + ))?; + assert_eq!(res.metadata.name, "fred"); + Ok(()) + } + + #[test] + fn only_fetches_from_newly_added_relays() -> Result<()> { + let mut m = MockUserManager::default(); + let mut client = generate_mock_client(); + m.config_manager.expect_load().returning(|| { + Ok(MyConfig { + users: vec![UserRef { + relays: UserRelays { + relays: vec![expected_userrelayrefs_write1()], + created_at: 0, + }, + ..generate_standard_config().users[0].clone() + }], + ..generate_standard_config() + }) + }); + m.config_manager.expect_save().returning(|_| Ok(())); + client + .expect_get_events() + .times(2) + .withf(move |relays, _filters| { + vec![expected_userrelayrefs_write1().url].eq(relays) + || vec![expected_userrelayrefs_read_write1().url].eq(relays) + }) + .returning(|relays, _| { + if vec![expected_userrelayrefs_write1().url].eq(&relays) { + Ok(vec![generate_relaylist_event()]) + } else if vec![expected_userrelayrefs_read_write1().url].eq(&relays) { + Ok(vec![generate_test_key_1_metadata_event("fred")]) + } else { + Ok(vec![]) + } + }); + + let res = futures::executor::block_on(m.get_user( + &client, + &TEST_KEY_1_KEYS.public_key(), + 5 * 60, // 5 mins ago + ))?; + assert_eq!(res.metadata.name, "fred"); + Ok(()) + } + } } #[test] diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 2a06357..3cb5d65 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -56,6 +56,25 @@ pub fn generate_test_key_1_relay_list_event() -> nostr::Event { .unwrap() } +pub fn generate_test_key_1_relay_list_event_same_as_fallback() -> nostr::Event { + nostr::event::EventBuilder::new( + nostr::Kind::RelayList, + "", + &[ + nostr::Tag::RelayMetadata( + "ws://localhost:8051".into(), + Some(nostr::RelayMetadata::Write), + ), + nostr::Tag::RelayMetadata( + "ws://localhost:8052".into(), + Some(nostr::RelayMetadata::Write), + ), + ], + ) + .to_event(&TEST_KEY_1_KEYS) + .unwrap() +} + pub static TEST_KEY_2_NSEC: &str = "nsec1ypglg6nj6ep0g2qmyfqcv2al502gje3jvpwye6mthmkvj93tqkesknv6qm"; pub static TEST_KEY_2_NPUB: &str = diff --git a/tests/login.rs b/tests/login.rs index d565620..e6ead6b 100644 --- a/tests/login.rs +++ b/tests/login.rs @@ -27,220 +27,343 @@ mod with_relays { use super::*; - mod when_first_time_login { + mod when_user_relay_list_aligns_with_fallback_relays { + // this simplifies testing use super::*; - // falls_back_to_fallback_relays - this is implict in the tests + mod when_first_time_login { + use super::*; - mod dislays_logged_in_with_correct_name { + // falls_back_to_fallback_relays - this is implict in the tests - use super::*; + mod dislays_logged_in_with_correct_name { - async fn run_test_displays_correct_name( - relay_listener1: Option>, - relay_listener2: Option>, - ) -> Result<()> { - let (mut r51, mut r52) = ( - Relay::new(8051, None, relay_listener1), - Relay::new(8052, None, relay_listener2), - ); + use super::*; - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - with_fresh_config(|| { - let mut p = CliTester::new(["login"]); + async fn run_test_displays_correct_name( + relay_listener1: Option>, + relay_listener2: Option>, + ) -> Result<()> { + let (mut r51, mut r52) = ( + Relay::new(8051, None, relay_listener1), + Relay::new(8052, None, relay_listener2), + ); - p.expect_input(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + with_fresh_config(|| { + let mut p = CliTester::new(["login"]); - p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? - .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? - .succeeds_with(TEST_PASSWORD)?; + p.expect_input(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; - p.expect("searching for your details...\r\n")?; - p.expect("\r")?; + p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? + .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? + .succeeds_with(TEST_PASSWORD)?; - p.expect_end_with("logged in as fred\r\n")?; - for p in [51, 52] { - shutdown_relay(8000 + p)?; - } - Ok(()) - }) - }); + p.expect("searching for your details...\r\n")?; + p.expect("\r")?; - // launch relay - let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); + p.expect_end_with("logged in as fred\r\n")?; + for p in [51, 52] { + shutdown_relay(8000 + p)?; + } + Ok(()) + }) + }); - cli_tester_handle.join().unwrap()?; - Ok(()) - } + // launch relay + let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); - #[test] - #[serial] - fn when_latest_metadata_and_relay_list_on_all_relays() -> Result<()> { - futures::executor::block_on(run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - ], - )?; - Ok(()) - }), - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - ], - )?; - Ok(()) - }), - )) - } + cli_tester_handle.join().unwrap()?; + Ok(()) + } - #[test] - #[serial] - fn when_latest_metadata_and_relay_list_on_some_relays_but_others_have_none() - -> Result<()> { - futures::executor::block_on(run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - ], - )?; - Ok(()) - }), - None, - )) - } + #[test] + #[serial] + fn when_latest_metadata_and_relay_list_on_all_relays() -> Result<()> { + futures::executor::block_on(run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + )) + } - #[test] - #[serial] - fn when_latest_metadata_only_on_relay_and_relay_list_on_another() -> Result<()> { - futures::executor::block_on(run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_test_key_1_metadata_event("fred")], - )?; - Ok(()) - }), - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_test_key_1_relay_list_event()], - )?; - Ok(()) - }), - )) - } + #[test] + #[serial] + fn when_latest_metadata_and_relay_list_on_some_relays_but_others_have_none() + -> Result<()> { + futures::executor::block_on(run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + None, + )) + } - #[test] - #[serial] - fn when_some_relays_return_old_metadata_event() -> Result<()> { - futures::executor::block_on(run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - ], - )?; - Ok(()) - }), - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_test_key_1_metadata_event_old("fred old")], - )?; - Ok(()) - }), - )) - } + #[test] + #[serial] + fn when_latest_metadata_only_on_relay_and_relay_list_on_another() -> Result<()> { + futures::executor::block_on(run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_test_key_1_metadata_event("fred")], + )?; + Ok(()) + }), + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_test_key_1_relay_list_event_same_as_fallback()], + )?; + Ok(()) + }), + )) + } - #[test] - #[serial] - fn when_some_relays_return_other_users_metadata() -> Result<()> { - futures::executor::block_on(run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_test_key_2_metadata_event("carole")], - )?; - Ok(()) - }), - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - generate_test_key_1_metadata_event_old("fred"), - generate_test_key_1_relay_list_event(), - ], - )?; - Ok(()) - }), - )) - } + #[test] + #[serial] + fn when_some_relays_return_old_metadata_event() -> Result<()> { + futures::executor::block_on(run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_test_key_1_metadata_event_old("fred old")], + )?; + Ok(()) + }), + )) + } - #[test] - #[serial] - fn when_some_relays_return_other_event_kinds() -> Result<()> { - futures::executor::block_on(run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - let mut event = generate_test_key_1_metadata_event("Fred"); - event.kind = nostr::Kind::TextNote; - relay.respond_events( - client_id, - &subscription_id, - &vec![make_event_old_or_change_user(event, &TEST_KEY_1_KEYS, 0)], - )?; + #[test] + #[serial] + fn when_some_relays_return_other_users_metadata() -> Result<()> { + futures::executor::block_on(run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_test_key_2_metadata_event("carole")], + )?; + Ok(()) + }), + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event_old("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + )) + } + + #[test] + #[serial] + fn when_some_relays_return_other_event_kinds() -> Result<()> { + futures::executor::block_on(run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + let mut event = generate_test_key_1_metadata_event("Fred"); + event.kind = nostr::Kind::TextNote; + relay.respond_events( + client_id, + &subscription_id, + &vec![make_event_old_or_change_user(event, &TEST_KEY_1_KEYS, 0)], + )?; + Ok(()) + }), + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event_old("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + )) + } + + mod when_specifying_command_line_nsec_only { + use super::*; + + #[test] + #[serial] + fn displays_correct_name() -> Result<()> { + futures::executor::block_on( + run_test_when_specifying_command_line_nsec_only_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + None, + ), + ) + } + async fn run_test_when_specifying_command_line_nsec_only_displays_correct_name( + relay_listener1: Option>, + relay_listener2: Option>, + ) -> Result<()> { + let (mut r51, mut r52) = ( + Relay::new(8051, None, relay_listener1), + Relay::new(8052, None, relay_listener2), + ); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + with_fresh_config(|| { + let mut p = CliTester::new(["login", "--nsec", TEST_KEY_1_NSEC]); + + p.expect("searching for your details...\r\n")?; + p.expect("\r")?; + + p.expect_end_with("logged in as fred\r\n")?; + for p in [51, 52] { + shutdown_relay(8000 + p)?; + } + Ok(()) + }) + }); + + // launch relay + let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); + + cli_tester_handle.join().unwrap()?; Ok(()) - }), - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - generate_test_key_1_metadata_event_old("fred"), - generate_test_key_1_relay_list_event(), - ], - )?; + } + } + mod when_specifying_command_line_password_only { + use super::*; + + #[test] + #[serial] + fn displays_correct_name() -> Result<()> { + futures::executor::block_on( + run_test_when_specifying_command_line_password_only_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + None, + ), + ) + } + async fn run_test_when_specifying_command_line_password_only_displays_correct_name( + relay_listener1: Option>, + relay_listener2: Option>, + ) -> Result<()> { + let (mut r51, mut r52) = ( + Relay::new(8051, None, relay_listener1), + Relay::new(8052, None, relay_listener2), + ); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + with_fresh_config(|| { + CliTester::new([ + "login", + "--offline", + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + ]) + .expect_end_eventually()?; + + let mut p = CliTester::new(["login", "--password", TEST_PASSWORD]); + + p.expect("searching for your details...\r\n")?; + p.expect("\r")?; + + p.expect_end_with("logged in as fred\r\n")?; + for p in [51, 52] { + shutdown_relay(8000 + p)?; + } + Ok(()) + }) + }); + + // launch relay + let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); + + cli_tester_handle.join().unwrap()?; Ok(()) - }), - )) - } + } + } - mod when_specifying_command_line_nsec_only { - use super::*; + mod when_specifying_command_line_nsec_and_password { + use super::*; - #[test] - #[serial] - fn displays_correct_name() -> Result<()> { - futures::executor::block_on( - run_test_when_specifying_command_line_nsec_only_displays_correct_name( + #[test] + #[serial] + fn displays_correct_name() -> Result<()> { + futures::executor::block_on( + run_test_when_specifying_command_line_nsec_and_password_displays_correct_name( Some(&|relay, client_id, subscription_id, _| -> Result<()> { relay.respond_events( client_id, &subscription_id, &vec![ generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), + generate_test_key_1_relay_list_event_same_as_fallback(), ], )?; Ok(()) @@ -248,8 +371,58 @@ mod with_relays { None, ), ) + } + async fn run_test_when_specifying_command_line_nsec_and_password_displays_correct_name( + relay_listener1: Option>, + relay_listener2: Option>, + ) -> Result<()> { + let (mut r51, mut r52) = ( + Relay::new(8051, None, relay_listener1), + Relay::new(8052, None, relay_listener2), + ); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + with_fresh_config(|| { + let mut p = CliTester::new([ + "login", + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + ]); + + p.expect("searching for your details...\r\n")?; + p.expect("\r")?; + + p.expect_end_with("logged in as fred\r\n")?; + for p in [51, 52] { + shutdown_relay(8000 + p)?; + } + Ok(()) + }) + }); + + // launch relay + let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); + + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + } + + mod when_no_metadata_found { + use super::*; + + #[test] + #[serial] + fn warm_user_and_displays_npub() -> Result<()> { + futures::executor::block_on( + run_test_when_no_metadata_found_warns_user_and_uses_npub(None, None), + ) } - async fn run_test_when_specifying_command_line_nsec_only_displays_correct_name( + + async fn run_test_when_no_metadata_found_warns_user_and_uses_npub( relay_listener1: Option>, relay_listener2: Option>, ) -> Result<()> { @@ -260,12 +433,24 @@ mod with_relays { let cli_tester_handle = std::thread::spawn(move || -> Result<()> { with_fresh_config(|| { - let mut p = CliTester::new(["login", "--nsec", TEST_KEY_1_NSEC]); + let mut p = CliTester::new(["login"]); + + p.expect_input(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? + .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? + .succeeds_with(TEST_PASSWORD)?; p.expect("searching for your details...\r\n")?; p.expect("\r")?; + p.expect( + "cannot find your account metadata (name, etc) on relays\r\n", + )?; - p.expect_end_with("logged in as fred\r\n")?; + p.expect_end_with( + format!("logged in as {TEST_KEY_1_NPUB}\r\n").as_str(), + )?; for p in [51, 52] { shutdown_relay(8000 + p)?; } @@ -280,22 +465,20 @@ mod with_relays { Ok(()) } } - mod when_specifying_command_line_password_only { + + mod when_metadata_but_no_relay_list_found { use super::*; #[test] #[serial] - fn displays_correct_name() -> Result<()> { + fn warm_user_and_displays_name() -> Result<()> { futures::executor::block_on( - run_test_when_specifying_command_line_password_only_displays_correct_name( + run_test_when_no_relay_list_found_warns_user_and_uses_npub( Some(&|relay, client_id, subscription_id, _| -> Result<()> { relay.respond_events( client_id, &subscription_id, - &vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - ], + &vec![generate_test_key_1_metadata_event("fred")], )?; Ok(()) }), @@ -303,7 +486,8 @@ mod with_relays { ), ) } - async fn run_test_when_specifying_command_line_password_only_displays_correct_name( + + async fn run_test_when_no_relay_list_found_warns_user_and_uses_npub( relay_listener1: Option>, relay_listener2: Option>, ) -> Result<()> { @@ -314,20 +498,18 @@ mod with_relays { let cli_tester_handle = std::thread::spawn(move || -> Result<()> { with_fresh_config(|| { - CliTester::new([ - "login", - "--offline", - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - ]) - .expect_end_eventually()?; + let mut p = CliTester::new(["login"]); - let mut p = CliTester::new(["login", "--password", TEST_PASSWORD]); + p.expect_input(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? + .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? + .succeeds_with(TEST_PASSWORD)?; p.expect("searching for your details...\r\n")?; p.expect("\r")?; + p.expect("cannot find your relay list. consider using another nostr client to create one to enhance your nostr experience.\r\n")?; p.expect_end_with("logged in as fred\r\n")?; for p in [51, 52] { @@ -344,37 +526,42 @@ mod with_relays { Ok(()) } } + } + + mod when_second_time_login_and_details_already_fetched { + use super::*; + + // TODO: the following two tests would require a fake config file or + // fake time + // - uses_relays_from_user_relay_list + // - dislays_correct_name - when_local_metadata_is_the_most_recent - mod when_specifying_command_line_nsec_and_password { + mod uses_cache { use super::*; #[test] #[serial] - fn displays_correct_name() -> Result<()> { - futures::executor::block_on( - run_test_when_specifying_command_line_nsec_and_password_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - ], - )?; - Ok(()) - }), - None, - ), - ) + fn dislays_logged_in_with_correct_name() -> Result<()> { + futures::executor::block_on(run_test_dislays_logged_in_with_correct_name(Some( + &|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }, + ))) } - async fn run_test_when_specifying_command_line_nsec_and_password_displays_correct_name( - relay_listener1: Option>, - relay_listener2: Option>, + async fn run_test_dislays_logged_in_with_correct_name( + relay_listener: Option>, ) -> Result<()> { let (mut r51, mut r52) = ( - Relay::new(8051, None, relay_listener1), - Relay::new(8052, None, relay_listener2), + Relay::new(8051, None, relay_listener), + Relay::new(8052, None, None), ); let cli_tester_handle = std::thread::spawn(move || -> Result<()> { @@ -387,13 +574,19 @@ mod with_relays { TEST_PASSWORD, ]); - p.expect("searching for your details...\r\n")?; - p.expect("\r")?; + p.expect_end_eventually_with("logged in as fred\r\n")?; - p.expect_end_with("logged in as fred\r\n")?; for p in [51, 52] { shutdown_relay(8000 + p)?; } + + let mut p = CliTester::new(["login", "--password", TEST_PASSWORD]); + + p.expect("searching for your details...\r\n")?; + p.expect("\r")?; + + p.expect_end_eventually_with("logged in as fred\r\n")?; + Ok(()) }) }); @@ -402,90 +595,25 @@ mod with_relays { let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); cli_tester_handle.join().unwrap()?; + Ok(()) } } } - - mod when_no_metadata_found { - use super::*; - - #[test] - #[serial] - fn warm_user_and_displays_npub() -> Result<()> { - futures::executor::block_on( - run_test_when_no_metadata_found_warns_user_and_uses_npub(None, None), - ) - } - - async fn run_test_when_no_metadata_found_warns_user_and_uses_npub( - relay_listener1: Option>, - relay_listener2: Option>, - ) -> Result<()> { - let (mut r51, mut r52) = ( - Relay::new(8051, None, relay_listener1), - Relay::new(8052, None, relay_listener2), - ); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - with_fresh_config(|| { - let mut p = CliTester::new(["login"]); - - p.expect_input(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? - .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? - .succeeds_with(TEST_PASSWORD)?; - - p.expect("searching for your details...\r\n")?; - p.expect("\r")?; - p.expect("cannot find your account metadata (name, etc) on relays\r\n")?; - - p.expect_end_with(format!("logged in as {TEST_KEY_1_NPUB}\r\n").as_str())?; - for p in [51, 52] { - shutdown_relay(8000 + p)?; - } - Ok(()) - }) - }); - - // launch relay - let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); - - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - - mod when_metadata_but_no_relay_list_found { + } + mod when_user_relay_list_contains_write_relays_not_in_fallback_list { + use super::*; + mod when_latest_metadata_not_on_fallback_relays_only_on_relays_in_user_list { use super::*; - - #[test] - #[serial] - fn warm_user_and_displays_name() -> Result<()> { - futures::executor::block_on( - run_test_when_no_relay_list_found_warns_user_and_uses_npub( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_test_key_1_metadata_event("fred")], - )?; - Ok(()) - }), - None, - ), - ) - } - - async fn run_test_when_no_relay_list_found_warns_user_and_uses_npub( + async fn run_test_displays_correct_name( relay_listener1: Option>, relay_listener2: Option>, ) -> Result<()> { - let (mut r51, mut r52) = ( + let (mut r51, mut r52, mut r53, mut r55) = ( Relay::new(8051, None, relay_listener1), - Relay::new(8052, None, relay_listener2), + Relay::new(8052, None, None), + Relay::new(8053, None, relay_listener2), + Relay::new(8055, None, None), ); let cli_tester_handle = std::thread::spawn(move || -> Result<()> { @@ -501,10 +629,9 @@ mod with_relays { p.expect("searching for your details...\r\n")?; p.expect("\r")?; - p.expect("cannot find your relay list. consider using another nostr client to create one to enhance your nostr experience.\r\n")?; p.expect_end_with("logged in as fred\r\n")?; - for p in [51, 52] { + for p in [51, 52, 53, 55] { shutdown_relay(8000 + p)?; } Ok(()) @@ -512,83 +639,45 @@ mod with_relays { }); // launch relay - let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + ); cli_tester_handle.join().unwrap()?; Ok(()) } - } - } - - mod when_second_time_login_and_details_already_fetched { - use super::*; - - // TODO: the following two tests would require a fake config file or - // fake time - // - uses_relays_from_user_relay_list - // - dislays_correct_name - when_local_metadata_is_the_most_recent - - mod uses_cache { - use super::*; + /// this also tests that additional relays are queried #[test] #[serial] - fn dislays_logged_in_with_correct_name() -> Result<()> { - futures::executor::block_on(run_test_dislays_logged_in_with_correct_name(Some( - &|relay, client_id, subscription_id, _| -> Result<()> { + fn displays_correct_name() -> Result<()> { + futures::executor::block_on(run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { relay.respond_events( client_id, &subscription_id, &vec![ - generate_test_key_1_metadata_event("fred"), + generate_test_key_1_metadata_event_old("Fred"), generate_test_key_1_relay_list_event(), ], )?; Ok(()) - }, - ))) - } - async fn run_test_dislays_logged_in_with_correct_name( - relay_listener: Option>, - ) -> Result<()> { - let (mut r51, mut r52) = ( - Relay::new(8051, None, relay_listener), - Relay::new(8052, None, None), - ); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - with_fresh_config(|| { - let mut p = CliTester::new([ - "login", - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - ]); - - p.expect_end_eventually_with("logged in as fred\r\n")?; - - for p in [51, 52] { - shutdown_relay(8000 + p)?; - } - - let mut p = CliTester::new(["login", "--password", TEST_PASSWORD]); - - p.expect("searching for your details...\r\n")?; - p.expect("\r")?; - - p.expect_end_eventually_with("logged in as fred\r\n")?; - + }), + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + ], + )?; Ok(()) - }) - }); - - // launch relay - let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); - - cli_tester_handle.join().unwrap()?; - - Ok(()) + }), + )) } } } -- cgit v1.2.3