diff options
Diffstat (limited to 'src/key_handling')
| -rw-r--r-- | src/key_handling/users.rs | 683 |
1 files changed, 667 insertions, 16 deletions
diff --git a/src/key_handling/users.rs b/src/key_handling/users.rs index 1d2cc34..91519bc 100644 --- a/src/key_handling/users.rs +++ b/src/key_handling/users.rs | |||
| @@ -1,11 +1,21 @@ | |||
| 1 | use std::time::SystemTime; | ||
| 2 | |||
| 1 | use anyhow::{Context, Result}; | 3 | use anyhow::{Context, Result}; |
| 4 | use async_trait::async_trait; | ||
| 2 | use nostr::prelude::*; | 5 | use nostr::prelude::*; |
| 3 | use zeroize::Zeroize; | 6 | use zeroize::Zeroize; |
| 4 | 7 | ||
| 5 | use super::encryption::{EncryptDecrypt, Encryptor}; | 8 | use super::encryption::{EncryptDecrypt, Encryptor}; |
| 9 | #[cfg(not(test))] | ||
| 10 | use crate::client::Client; | ||
| 11 | #[cfg(test)] | ||
| 12 | use crate::client::MockConnect; | ||
| 6 | use crate::{ | 13 | use crate::{ |
| 7 | cli_interactor::{Interactor, InteractorPrompt, PromptInputParms, PromptPasswordParms}, | 14 | cli_interactor::{Interactor, InteractorPrompt, PromptInputParms, PromptPasswordParms}, |
| 8 | config::{self, ConfigManagement, ConfigManager}, | 15 | client::Connect, |
| 16 | config::{ | ||
| 17 | self, ConfigManagement, ConfigManager, UserMetadata, UserRef, UserRelayRef, UserRelays, | ||
| 18 | }, | ||
| 9 | }; | 19 | }; |
| 10 | 20 | ||
| 11 | #[derive(Default)] | 21 | #[derive(Default)] |
| @@ -15,13 +25,29 @@ pub struct UserManager { | |||
| 15 | encryptor: Encryptor, | 25 | encryptor: Encryptor, |
| 16 | } | 26 | } |
| 17 | 27 | ||
| 28 | #[async_trait] | ||
| 18 | pub trait UserManagement { | 29 | pub trait UserManagement { |
| 19 | fn add(&self, nsec: &Option<String>, password: &Option<String>) -> Result<nostr::Keys>; | 30 | fn add(&self, nsec: &Option<String>, password: &Option<String>) -> Result<nostr::Keys>; |
| 31 | async fn get_user( | ||
| 32 | &self, | ||
| 33 | #[cfg(test)] client: &MockConnect, | ||
| 34 | #[cfg(not(test))] client: &Client, | ||
| 35 | public_key: &XOnlyPublicKey, | ||
| 36 | after: u64, | ||
| 37 | ) -> Result<UserRef>; | ||
| 38 | fn get_user_from_cache(&self, public_key: &XOnlyPublicKey) -> Result<UserRef>; | ||
| 39 | fn add_user_to_config( | ||
| 40 | &self, | ||
| 41 | public_key: XOnlyPublicKey, | ||
| 42 | encrypted_secret_key: Option<String>, | ||
| 43 | overwrite: bool, | ||
| 44 | ) -> Result<()>; | ||
| 20 | } | 45 | } |
| 21 | 46 | ||
| 22 | #[cfg(test)] | 47 | #[cfg(test)] |
| 23 | use duplicate::duplicate_item; | 48 | use duplicate::duplicate_item; |
| 24 | #[cfg_attr(test, duplicate_item(UserManager; [UserManager]; [self::tests::MockUserManager]))] | 49 | #[cfg_attr(test, duplicate_item(UserManager; [UserManager]; [self::tests::MockUserManager]))] |
| 50 | #[async_trait] | ||
| 25 | impl UserManagement for UserManager { | 51 | impl UserManagement for UserManager { |
| 26 | fn add(&self, nsec: &Option<String>, password: &Option<String>) -> Result<nostr::Keys> { | 52 | fn add(&self, nsec: &Option<String>, password: &Option<String>) -> Result<nostr::Keys> { |
| 27 | let mut prompt = "login with nsec (or hex private key)"; | 53 | let mut prompt = "login with nsec (or hex private key)"; |
| @@ -66,9 +92,152 @@ impl UserManagement for UserManager { | |||
| 66 | .context("failed to encrypt nsec with password.")?; | 92 | .context("failed to encrypt nsec with password.")?; |
| 67 | pass.zeroize(); | 93 | pass.zeroize(); |
| 68 | 94 | ||
| 69 | let user_ref = config::UserRef { | 95 | self.add_user_to_config(keys.public_key(), Some(encrypted_secret_key), true)?; |
| 70 | public_key: keys.public_key(), | 96 | |
| 71 | encrypted_key: encrypted_secret_key, | 97 | Ok(keys) |
| 98 | } | ||
| 99 | |||
| 100 | fn add_user_to_config( | ||
| 101 | &self, | ||
| 102 | public_key: XOnlyPublicKey, | ||
| 103 | encrypted_secret_key: Option<String>, | ||
| 104 | overwrite: bool, | ||
| 105 | ) -> Result<()> { | ||
| 106 | let user_ref = | ||
| 107 | config::UserRef::new(public_key, encrypted_secret_key.unwrap_or(String::new())); | ||
| 108 | |||
| 109 | 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")?; | ||
| 110 | // don't overwrite unless specified | ||
| 111 | if !overwrite | ||
| 112 | && cfg | ||
| 113 | .users | ||
| 114 | .clone() | ||
| 115 | .into_iter() | ||
| 116 | .any(|r| r.public_key.eq(&public_key)) | ||
| 117 | { | ||
| 118 | return Ok(()); | ||
| 119 | } | ||
| 120 | // if overwrite remove any duplicate entries for key before adding it to config | ||
| 121 | cfg.users = cfg | ||
| 122 | .users | ||
| 123 | .clone() | ||
| 124 | .into_iter() | ||
| 125 | .filter(|r| !r.public_key.eq(&public_key)) | ||
| 126 | .collect(); | ||
| 127 | cfg.users.push(user_ref); | ||
| 128 | self.config_manager | ||
| 129 | .save(&cfg) | ||
| 130 | .context("failed to save application configuration with new user details in") | ||
| 131 | } | ||
| 132 | |||
| 133 | fn get_user_from_cache(&self, public_key: &XOnlyPublicKey) -> Result<UserRef> { | ||
| 134 | let cfg = self | ||
| 135 | .config_manager | ||
| 136 | .load() | ||
| 137 | .context("failed to load application config")?; | ||
| 138 | Ok(cfg | ||
| 139 | .users | ||
| 140 | .iter() | ||
| 141 | .find(|u| u.public_key.eq(public_key)) | ||
| 142 | .context(format!("pubkey isn't a current user: {public_key}"))? | ||
| 143 | .clone()) | ||
| 144 | } | ||
| 145 | /// get UserRef fetching most recent user relays and metadata infomation | ||
| 146 | /// from relays | ||
| 147 | async fn get_user( | ||
| 148 | &self, | ||
| 149 | #[cfg(test)] client: &MockConnect, | ||
| 150 | #[cfg(not(test))] client: &Client, | ||
| 151 | public_key: &XOnlyPublicKey, | ||
| 152 | use_cache_unless_checked_more_than_x_secs_ago: u64, | ||
| 153 | ) -> Result<UserRef> { | ||
| 154 | let cfg = self | ||
| 155 | .config_manager | ||
| 156 | .load() | ||
| 157 | .context("failed to load application config")?; | ||
| 158 | let user_ref = cfg | ||
| 159 | .users | ||
| 160 | .iter() | ||
| 161 | .find(|u| u.public_key.eq(public_key)) | ||
| 162 | .context(format!("pubkey isn't a current user: {public_key}"))?; | ||
| 163 | // return cache if last fetched was within X minutes | ||
| 164 | if !unix_timestamp_after_now_plus_secs( | ||
| 165 | user_ref.last_checked, | ||
| 166 | use_cache_unless_checked_more_than_x_secs_ago, | ||
| 167 | ) { | ||
| 168 | return Ok(user_ref.clone()); | ||
| 169 | } | ||
| 170 | let events: Vec<Event> = match client | ||
| 171 | .get_events( | ||
| 172 | if user_ref.relays.write().is_empty() { | ||
| 173 | client.get_fallback_relays().clone() | ||
| 174 | } else { | ||
| 175 | user_ref.relays.write() | ||
| 176 | }, | ||
| 177 | vec![ | ||
| 178 | nostr::Filter::default() | ||
| 179 | .author(public_key.to_string()) | ||
| 180 | .since(nostr::Timestamp::from(user_ref.metadata.created_at + 1)) | ||
| 181 | .kind(Kind::Metadata), | ||
| 182 | nostr::Filter::default() | ||
| 183 | .author(public_key.to_string()) | ||
| 184 | .since(nostr::Timestamp::from(user_ref.relays.created_at + 1)) | ||
| 185 | .kind(Kind::RelayList), | ||
| 186 | ], | ||
| 187 | ) | ||
| 188 | .await | ||
| 189 | { | ||
| 190 | Ok(events) => events, | ||
| 191 | Err(_) => { | ||
| 192 | // TODO error reporting | ||
| 193 | return Ok(user_ref.clone()); | ||
| 194 | } | ||
| 195 | }; | ||
| 196 | |||
| 197 | let mut user_ref = user_ref.clone(); | ||
| 198 | |||
| 199 | user_ref.last_checked = SystemTime::now() | ||
| 200 | .duration_since(SystemTime::UNIX_EPOCH) | ||
| 201 | .context("system time should be after the year 1970")? | ||
| 202 | .as_secs(); | ||
| 203 | |||
| 204 | if let Some(new_metadata_event) = events | ||
| 205 | .iter() | ||
| 206 | .filter(|e| e.kind.eq(&nostr::Kind::Metadata) && e.pubkey.eq(public_key)) | ||
| 207 | .max_by_key(|e| e.created_at) | ||
| 208 | { | ||
| 209 | if new_metadata_event.created_at.as_u64() > user_ref.metadata.created_at { | ||
| 210 | let metadata = nostr::Metadata::from_json(new_metadata_event.content.clone()) | ||
| 211 | .context("metadata cannot be found in kind 0 event content")?; | ||
| 212 | user_ref.metadata = UserMetadata { | ||
| 213 | name: metadata | ||
| 214 | .name | ||
| 215 | .context("user metadata should always have name")?, | ||
| 216 | created_at: new_metadata_event.created_at.as_u64(), | ||
| 217 | }; | ||
| 218 | } | ||
| 219 | }; | ||
| 220 | |||
| 221 | if let Some(new_relays_event) = events | ||
| 222 | .iter() | ||
| 223 | .filter(|e| e.kind.eq(&nostr::Kind::RelayList) && e.pubkey.eq(public_key)) | ||
| 224 | .max_by_key(|e| e.created_at) | ||
| 225 | { | ||
| 226 | if new_relays_event.created_at.as_u64() > user_ref.relays.created_at { | ||
| 227 | user_ref.relays = UserRelays { | ||
| 228 | relays: new_relays_event | ||
| 229 | .tags | ||
| 230 | .iter() | ||
| 231 | .filter(|t| t.kind().eq(&nostr::TagKind::R)) | ||
| 232 | .map(|t| UserRelayRef { | ||
| 233 | url: t.as_vec()[1].clone(), | ||
| 234 | read: t.as_vec().len() == 2 || t.as_vec()[2].eq("read"), | ||
| 235 | write: t.as_vec().len() == 2 || t.as_vec()[2].eq("write"), | ||
| 236 | }) | ||
| 237 | .collect(), | ||
| 238 | created_at: new_relays_event.created_at.as_u64(), | ||
| 239 | }; | ||
| 240 | } | ||
| 72 | }; | 241 | }; |
| 73 | 242 | ||
| 74 | // remove any duplicate entries for key before adding it to config | 243 | // remove any duplicate entries for key before adding it to config |
| @@ -77,19 +246,27 @@ impl UserManagement for UserManager { | |||
| 77 | .users | 246 | .users |
| 78 | .clone() | 247 | .clone() |
| 79 | .into_iter() | 248 | .into_iter() |
| 80 | .filter(|r| !r.public_key.eq(&keys.public_key())) | 249 | .filter(|r| !r.public_key.eq(public_key)) |
| 81 | .collect(); | 250 | .collect(); |
| 82 | cfg.users.push(user_ref); | 251 | cfg.users.push(user_ref.clone()); |
| 83 | self.config_manager | 252 | self.config_manager |
| 84 | .save(&cfg) | 253 | .save(&cfg) |
| 85 | .context("failed to save application configuration with new user details in")?; | 254 | .context("failed to save application configuration with new user details in")?; |
| 255 | Ok(user_ref) | ||
| 256 | } | ||
| 257 | } | ||
| 86 | 258 | ||
| 87 | Ok(keys) | 259 | fn unix_timestamp_after_now_plus_secs(timestamp: u64, secs: u64) -> bool { |
| 260 | if let Ok(now) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { | ||
| 261 | now.as_secs() > (timestamp + secs) | ||
| 262 | } else { | ||
| 263 | true | ||
| 88 | } | 264 | } |
| 89 | } | 265 | } |
| 90 | 266 | ||
| 91 | #[cfg(test)] | 267 | #[cfg(test)] |
| 92 | mod tests { | 268 | mod tests { |
| 269 | use nostr; | ||
| 93 | use test_utils::*; | 270 | use test_utils::*; |
| 94 | 271 | ||
| 95 | use super::*; | 272 | use super::*; |
| @@ -278,11 +455,10 @@ mod tests { | |||
| 278 | m.config_manager = MockConfigManagement::default(); | 455 | m.config_manager = MockConfigManagement::default(); |
| 279 | m.config_manager.expect_load().returning(|| { | 456 | m.config_manager.expect_load().returning(|| { |
| 280 | Ok(MyConfig { | 457 | Ok(MyConfig { |
| 281 | users: vec![UserRef { | 458 | users: vec![UserRef::new( |
| 282 | public_key: TEST_KEY_1_KEYS.public_key(), | 459 | TEST_KEY_1_KEYS.public_key(), |
| 283 | // different key to TEST_KEY_1_ENCYPTED | 460 | TEST_KEY_2_ENCRYPTED.into(), |
| 284 | encrypted_key: TEST_KEY_2_ENCRYPTED.into(), | 461 | )], |
| 285 | }], | ||
| 286 | ..MyConfig::default() | 462 | ..MyConfig::default() |
| 287 | }) | 463 | }) |
| 288 | }); | 464 | }); |
| @@ -308,10 +484,10 @@ mod tests { | |||
| 308 | m.config_manager = MockConfigManagement::default(); | 484 | m.config_manager = MockConfigManagement::default(); |
| 309 | m.config_manager.expect_load().returning(|| { | 485 | m.config_manager.expect_load().returning(|| { |
| 310 | Ok(MyConfig { | 486 | Ok(MyConfig { |
| 311 | users: vec![UserRef { | 487 | users: vec![UserRef::new( |
| 312 | public_key: TEST_KEY_2_KEYS.public_key(), | 488 | TEST_KEY_2_KEYS.public_key(), |
| 313 | encrypted_key: TEST_KEY_2_ENCRYPTED.into(), | 489 | TEST_KEY_2_ENCRYPTED.into(), |
| 314 | }], | 490 | )], |
| 315 | ..MyConfig::default() | 491 | ..MyConfig::default() |
| 316 | }) | 492 | }) |
| 317 | }); | 493 | }); |
| @@ -329,4 +505,479 @@ mod tests { | |||
| 329 | } | 505 | } |
| 330 | } | 506 | } |
| 331 | } | 507 | } |
| 508 | |||
| 509 | fn now_timestamp() -> u64 { | ||
| 510 | SystemTime::now() | ||
| 511 | .duration_since(SystemTime::UNIX_EPOCH) | ||
| 512 | .unwrap() | ||
| 513 | .as_secs() | ||
| 514 | } | ||
| 515 | fn roughly_now(timestamp: u64) -> bool { | ||
| 516 | let now = now_timestamp(); | ||
| 517 | timestamp < now + 100 && timestamp > now - 100 | ||
| 518 | } | ||
| 519 | |||
| 520 | mod get_user { | ||
| 521 | use anyhow::anyhow; | ||
| 522 | |||
| 523 | use super::*; | ||
| 524 | use crate::client::MockConnect; | ||
| 525 | |||
| 526 | fn generate_relaylist_event() -> nostr::Event { | ||
| 527 | nostr::event::EventBuilder::new( | ||
| 528 | nostr::Kind::RelayList, | ||
| 529 | "", | ||
| 530 | &[ | ||
| 531 | nostr::Tag::RelayMetadata( | ||
| 532 | "wss://fredswrite1.relay".into(), | ||
| 533 | Some(nostr::RelayMetadata::Write), | ||
| 534 | ), | ||
| 535 | nostr::Tag::RelayMetadata( | ||
| 536 | "wss://fredsread1.relay".into(), | ||
| 537 | Some(nostr::RelayMetadata::Read), | ||
| 538 | ), | ||
| 539 | nostr::Tag::RelayMetadata("wss://fredsreadwrite.relay".into(), None), | ||
| 540 | ], | ||
| 541 | ) | ||
| 542 | .to_event(&TEST_KEY_1_KEYS) | ||
| 543 | .unwrap() | ||
| 544 | } | ||
| 545 | |||
| 546 | fn generate_relaylist_event_user_2() -> nostr::Event { | ||
| 547 | nostr::event::EventBuilder::new( | ||
| 548 | nostr::Kind::RelayList, | ||
| 549 | "", | ||
| 550 | &[ | ||
| 551 | nostr::Tag::RelayMetadata( | ||
| 552 | "wss://carolswrite1.relay".into(), | ||
| 553 | Some(nostr::RelayMetadata::Write), | ||
| 554 | ), | ||
| 555 | nostr::Tag::RelayMetadata( | ||
| 556 | "wss://carolsread1.relay".into(), | ||
| 557 | Some(nostr::RelayMetadata::Read), | ||
| 558 | ), | ||
| 559 | nostr::Tag::RelayMetadata("wss://carolsreadwrite.relay".into(), None), | ||
| 560 | ], | ||
| 561 | ) | ||
| 562 | .to_event(&TEST_KEY_2_KEYS) | ||
| 563 | .unwrap() | ||
| 564 | } | ||
| 565 | |||
| 566 | fn fallback_relays() -> Vec<String> { | ||
| 567 | vec!["ws://fallback1".to_string(), "ws://fallback2".to_string()].clone() | ||
| 568 | } | ||
| 569 | |||
| 570 | fn generate_mock_client() -> MockConnect { | ||
| 571 | let mut client = <MockConnect as std::default::Default>::default(); | ||
| 572 | client | ||
| 573 | .expect_get_fallback_relays() | ||
| 574 | .return_const(fallback_relays()); | ||
| 575 | client | ||
| 576 | } | ||
| 577 | |||
| 578 | fn generate_standard_config() -> MyConfig { | ||
| 579 | MyConfig { | ||
| 580 | users: vec![UserRef { | ||
| 581 | public_key: TEST_KEY_1_KEYS.public_key(), | ||
| 582 | encrypted_key: TEST_KEY_1_ENCRYPTED.to_string(), | ||
| 583 | metadata: UserMetadata { | ||
| 584 | name: "Fred".to_string(), | ||
| 585 | created_at: 10, | ||
| 586 | }, | ||
| 587 | relays: UserRelays { | ||
| 588 | relays: vec![ | ||
| 589 | UserRelayRef { | ||
| 590 | url: "ws://existingread".to_string(), | ||
| 591 | read: true, | ||
| 592 | write: false, | ||
| 593 | }, | ||
| 594 | UserRelayRef { | ||
| 595 | url: "ws://existingreadwrite".to_string(), | ||
| 596 | read: true, | ||
| 597 | write: true, | ||
| 598 | }, | ||
| 599 | UserRelayRef { | ||
| 600 | url: "ws://existingwrite".to_string(), | ||
| 601 | read: false, | ||
| 602 | write: true, | ||
| 603 | }, | ||
| 604 | ], | ||
| 605 | created_at: 10, | ||
| 606 | }, | ||
| 607 | last_checked: now_timestamp() - (60 * 60), // 1h ago | ||
| 608 | }], | ||
| 609 | ..MyConfig::default() | ||
| 610 | } | ||
| 611 | .clone() | ||
| 612 | } | ||
| 613 | |||
| 614 | fn expected_userrelayrefs() -> Vec<UserRelayRef> { | ||
| 615 | vec![ | ||
| 616 | UserRelayRef { | ||
| 617 | url: "wss://fredswrite1.relay".into(), | ||
| 618 | read: false, | ||
| 619 | write: true, | ||
| 620 | }, | ||
| 621 | UserRelayRef { | ||
| 622 | url: "wss://fredsread1.relay".into(), | ||
| 623 | read: true, | ||
| 624 | write: false, | ||
| 625 | }, | ||
| 626 | UserRelayRef { | ||
| 627 | url: "wss://fredsreadwrite.relay".into(), | ||
| 628 | read: true, | ||
| 629 | write: true, | ||
| 630 | }, | ||
| 631 | ] | ||
| 632 | } | ||
| 633 | |||
| 634 | mod when_within_caching_time_window { | ||
| 635 | use super::*; | ||
| 636 | |||
| 637 | #[test] | ||
| 638 | fn returns_cached_details_without_checking_relays_or_updaing_config() -> Result<()> { | ||
| 639 | let mut m = MockUserManager::default(); | ||
| 640 | let client = generate_mock_client(); | ||
| 641 | m.config_manager | ||
| 642 | .expect_load() | ||
| 643 | .returning(|| Ok(generate_standard_config())); | ||
| 644 | let res = futures::executor::block_on(m.get_user( | ||
| 645 | &client, | ||
| 646 | &TEST_KEY_1_KEYS.public_key(), | ||
| 647 | 24 * 60 * 60, // within 24 hours | ||
| 648 | ))?; | ||
| 649 | assert_eq!(res.metadata.name, "Fred"); | ||
| 650 | assert_eq!(res.relays.relays[0].url, "ws://existingread"); | ||
| 651 | Ok(()) | ||
| 652 | } | ||
| 653 | } | ||
| 654 | |||
| 655 | mod returns_userref_with_latest_details_from_events_on_relays { | ||
| 656 | use super::*; | ||
| 657 | |||
| 658 | #[test] | ||
| 659 | fn name() -> Result<()> { | ||
| 660 | let mut m = MockUserManager::default(); | ||
| 661 | let mut client = generate_mock_client(); | ||
| 662 | m.config_manager | ||
| 663 | .expect_load() | ||
| 664 | .returning(|| Ok(generate_standard_config())); | ||
| 665 | m.config_manager.expect_save().returning(|_| Ok(())); | ||
| 666 | client | ||
| 667 | .expect_get_events() | ||
| 668 | .returning(|_, _| Ok(vec![generate_test_key_1_metadata_event("fred")])); | ||
| 669 | |||
| 670 | let res = futures::executor::block_on(m.get_user( | ||
| 671 | &client, | ||
| 672 | &TEST_KEY_1_KEYS.public_key(), | ||
| 673 | 5 * 60, // 5 mins ago | ||
| 674 | ))?; | ||
| 675 | assert_eq!(res.metadata.name, "fred"); | ||
| 676 | Ok(()) | ||
| 677 | } | ||
| 678 | |||
| 679 | #[test] | ||
| 680 | fn name_ignoring_other_users_events() -> Result<()> { | ||
| 681 | let mut m = MockUserManager::default(); | ||
| 682 | let mut client = generate_mock_client(); | ||
| 683 | m.config_manager | ||
| 684 | .expect_load() | ||
| 685 | .returning(|| Ok(generate_standard_config())); | ||
| 686 | m.config_manager.expect_save().returning(|_| Ok(())); | ||
| 687 | client.expect_get_events().returning(|_, _| { | ||
| 688 | Ok(vec![ | ||
| 689 | generate_test_key_2_metadata_event("carole"), | ||
| 690 | generate_test_key_1_metadata_event_old("fred"), | ||
| 691 | ]) | ||
| 692 | }); | ||
| 693 | |||
| 694 | let res = futures::executor::block_on(m.get_user( | ||
| 695 | &client, | ||
| 696 | &TEST_KEY_1_KEYS.public_key(), | ||
| 697 | 5 * 60, // 5 mins ago | ||
| 698 | ))?; | ||
| 699 | assert_eq!(res.metadata.name, "fred"); | ||
| 700 | Ok(()) | ||
| 701 | } | ||
| 702 | |||
| 703 | #[test] | ||
| 704 | fn relays() -> Result<()> { | ||
| 705 | let mut m = MockUserManager::default(); | ||
| 706 | let mut client = generate_mock_client(); | ||
| 707 | m.config_manager | ||
| 708 | .expect_load() | ||
| 709 | .returning(|| Ok(generate_standard_config())); | ||
| 710 | m.config_manager.expect_save().returning(|_| Ok(())); | ||
| 711 | client.expect_get_events().returning(|_, _| { | ||
| 712 | Ok(vec![ | ||
| 713 | generate_test_key_1_metadata_event("fred"), | ||
| 714 | generate_relaylist_event(), | ||
| 715 | ]) | ||
| 716 | }); | ||
| 717 | |||
| 718 | let res = futures::executor::block_on(m.get_user( | ||
| 719 | &client, | ||
| 720 | &TEST_KEY_1_KEYS.public_key(), | ||
| 721 | 5 * 60, // 5 mins ago | ||
| 722 | ))?; | ||
| 723 | assert_eq!(res.relays.relays, expected_userrelayrefs(),); | ||
| 724 | Ok(()) | ||
| 725 | } | ||
| 726 | |||
| 727 | #[test] | ||
| 728 | fn relays_ignoring_other_users_events() -> Result<()> { | ||
| 729 | let mut m = MockUserManager::default(); | ||
| 730 | let mut client = generate_mock_client(); | ||
| 731 | m.config_manager | ||
| 732 | .expect_load() | ||
| 733 | .returning(|| Ok(generate_standard_config())); | ||
| 734 | m.config_manager.expect_save().returning(|_| Ok(())); | ||
| 735 | client.expect_get_events().returning(|_, _| { | ||
| 736 | Ok(vec![ | ||
| 737 | make_event_old_or_change_user( | ||
| 738 | generate_relaylist_event(), | ||
| 739 | &TEST_KEY_1_KEYS, | ||
| 740 | 10000, | ||
| 741 | ), | ||
| 742 | generate_relaylist_event_user_2(), | ||
| 743 | ]) | ||
| 744 | }); | ||
| 745 | |||
| 746 | let res = futures::executor::block_on(m.get_user( | ||
| 747 | &client, | ||
| 748 | &TEST_KEY_1_KEYS.public_key(), | ||
| 749 | 5 * 60, // 5 mins ago | ||
| 750 | ))?; | ||
| 751 | assert_eq!(res.relays.relays, expected_userrelayrefs(),); | ||
| 752 | Ok(()) | ||
| 753 | } | ||
| 754 | } | ||
| 755 | |||
| 756 | mod saves_updates_to_config { | ||
| 757 | use super::*; | ||
| 758 | |||
| 759 | #[test] | ||
| 760 | fn saves_name_to_config() -> Result<()> { | ||
| 761 | let mut m = MockUserManager::default(); | ||
| 762 | let mut client = generate_mock_client(); | ||
| 763 | m.config_manager | ||
| 764 | .expect_load() | ||
| 765 | .returning(|| Ok(generate_standard_config())); | ||
| 766 | m.config_manager | ||
| 767 | .expect_save() | ||
| 768 | .once() | ||
| 769 | .withf(|cfg| cfg.users[0].metadata.name.eq("fred")) | ||
| 770 | .returning(|_| Ok(())); | ||
| 771 | client | ||
| 772 | .expect_get_events() | ||
| 773 | .returning(|_, _| Ok(vec![generate_test_key_1_metadata_event("fred")])); | ||
| 774 | |||
| 775 | futures::executor::block_on(m.get_user( | ||
| 776 | &client, | ||
| 777 | &TEST_KEY_1_KEYS.public_key(), | ||
| 778 | 5 * 60, // 5 mins ago | ||
| 779 | ))?; | ||
| 780 | Ok(()) | ||
| 781 | } | ||
| 782 | |||
| 783 | #[test] | ||
| 784 | fn updates_metadata_created_at() -> Result<()> { | ||
| 785 | let mut m = MockUserManager::default(); | ||
| 786 | let mut client = generate_mock_client(); | ||
| 787 | m.config_manager | ||
| 788 | .expect_load() | ||
| 789 | .returning(|| Ok(generate_standard_config())); | ||
| 790 | m.config_manager | ||
| 791 | .expect_save() | ||
| 792 | .once() | ||
| 793 | .withf(|cfg| roughly_now(cfg.users[0].metadata.created_at)) | ||
| 794 | .returning(|_| Ok(())); | ||
| 795 | client | ||
| 796 | .expect_get_events() | ||
| 797 | .returning(|_, _| Ok(vec![generate_test_key_1_metadata_event("fred")])); | ||
| 798 | |||
| 799 | futures::executor::block_on(m.get_user( | ||
| 800 | &client, | ||
| 801 | &TEST_KEY_1_KEYS.public_key(), | ||
| 802 | 5 * 60, // 5 mins ago | ||
| 803 | ))?; | ||
| 804 | Ok(()) | ||
| 805 | } | ||
| 806 | |||
| 807 | #[test] | ||
| 808 | fn saves_relays_to_config() -> Result<()> { | ||
| 809 | let mut m = MockUserManager::default(); | ||
| 810 | let mut client = generate_mock_client(); | ||
| 811 | m.config_manager | ||
| 812 | .expect_load() | ||
| 813 | .returning(|| Ok(generate_standard_config())); | ||
| 814 | m.config_manager | ||
| 815 | .expect_save() | ||
| 816 | .once() | ||
| 817 | .withf(|cfg| expected_userrelayrefs().eq(&cfg.users[0].relays.relays)) | ||
| 818 | .returning(|_| Ok(())); | ||
| 819 | client | ||
| 820 | .expect_get_events() | ||
| 821 | .returning(|_, _| Ok(vec![generate_relaylist_event()])); | ||
| 822 | |||
| 823 | futures::executor::block_on(m.get_user( | ||
| 824 | &client, | ||
| 825 | &TEST_KEY_1_KEYS.public_key(), | ||
| 826 | 5 * 60, // 5 mins ago | ||
| 827 | ))?; | ||
| 828 | Ok(()) | ||
| 829 | } | ||
| 830 | |||
| 831 | #[test] | ||
| 832 | fn updates_relays_created_at() -> Result<()> { | ||
| 833 | let mut m = MockUserManager::default(); | ||
| 834 | let mut client = generate_mock_client(); | ||
| 835 | m.config_manager | ||
| 836 | .expect_load() | ||
| 837 | .returning(|| Ok(generate_standard_config())); | ||
| 838 | m.config_manager | ||
| 839 | .expect_save() | ||
| 840 | .once() | ||
| 841 | .withf(|cfg| roughly_now(cfg.users[0].relays.created_at)) | ||
| 842 | .returning(|_| Ok(())); | ||
| 843 | client | ||
| 844 | .expect_get_events() | ||
| 845 | .returning(|_, _| Ok(vec![generate_relaylist_event()])); | ||
| 846 | |||
| 847 | futures::executor::block_on(m.get_user( | ||
| 848 | &client, | ||
| 849 | &TEST_KEY_1_KEYS.public_key(), | ||
| 850 | 5 * 60, // 5 mins ago | ||
| 851 | ))?; | ||
| 852 | Ok(()) | ||
| 853 | } | ||
| 854 | |||
| 855 | #[test] | ||
| 856 | fn when_no_changes_updates_last_updated() -> Result<()> { | ||
| 857 | let mut m = MockUserManager::default(); | ||
| 858 | let mut client = generate_mock_client(); | ||
| 859 | m.config_manager | ||
| 860 | .expect_load() | ||
| 861 | .returning(|| Ok(generate_standard_config())); | ||
| 862 | m.config_manager | ||
| 863 | .expect_save() | ||
| 864 | .once() | ||
| 865 | .withf(|cfg| roughly_now(cfg.users[0].last_checked)) | ||
| 866 | .returning(|_| Ok(())); | ||
| 867 | client.expect_get_events().returning(|_, _| Ok(vec![])); | ||
| 868 | |||
| 869 | futures::executor::block_on(m.get_user( | ||
| 870 | &client, | ||
| 871 | &TEST_KEY_1_KEYS.public_key(), | ||
| 872 | 5 * 60, // 5 mins ago | ||
| 873 | ))?; | ||
| 874 | Ok(()) | ||
| 875 | } | ||
| 876 | |||
| 877 | #[test] | ||
| 878 | fn when_changes_updates_last_updated() -> Result<()> { | ||
| 879 | let mut m = MockUserManager::default(); | ||
| 880 | let mut client = generate_mock_client(); | ||
| 881 | m.config_manager | ||
| 882 | .expect_load() | ||
| 883 | .returning(|| Ok(generate_standard_config())); | ||
| 884 | m.config_manager | ||
| 885 | .expect_save() | ||
| 886 | .once() | ||
| 887 | .withf(|cfg| roughly_now(cfg.users[0].last_checked)) | ||
| 888 | .returning(|_| Ok(())); | ||
| 889 | client | ||
| 890 | .expect_get_events() | ||
| 891 | .returning(|_, _| Ok(vec![generate_test_key_1_metadata_event("fred")])); | ||
| 892 | |||
| 893 | futures::executor::block_on(m.get_user( | ||
| 894 | &client, | ||
| 895 | &TEST_KEY_1_KEYS.public_key(), | ||
| 896 | 5 * 60, // 5 mins ago | ||
| 897 | ))?; | ||
| 898 | Ok(()) | ||
| 899 | } | ||
| 900 | } | ||
| 901 | |||
| 902 | mod fetches_from_correct_relays { | ||
| 903 | use super::*; | ||
| 904 | #[test] | ||
| 905 | fn when_userref_write_relays_present_fetches_only_from_them() -> Result<()> { | ||
| 906 | let mut m = MockUserManager::default(); | ||
| 907 | let mut client = generate_mock_client(); | ||
| 908 | m.config_manager | ||
| 909 | .expect_load() | ||
| 910 | .returning(|| Ok(generate_standard_config())); | ||
| 911 | m.config_manager.expect_save().returning(|_| Ok(())); | ||
| 912 | client | ||
| 913 | .expect_get_events() | ||
| 914 | .once() | ||
| 915 | .withf(move |relays, _filters| { | ||
| 916 | vec![ | ||
| 917 | "ws://existingreadwrite".to_string(), | ||
| 918 | "ws://existingwrite".to_string(), | ||
| 919 | ] | ||
| 920 | .eq(relays) | ||
| 921 | }) | ||
| 922 | .returning(|_, _| Ok(vec![])); | ||
| 923 | |||
| 924 | futures::executor::block_on(m.get_user( | ||
| 925 | &client, | ||
| 926 | &TEST_KEY_1_KEYS.public_key(), | ||
| 927 | 5 * 60, // 5 mins ago | ||
| 928 | ))?; | ||
| 929 | Ok(()) | ||
| 930 | } | ||
| 931 | #[test] | ||
| 932 | fn when_userref_write_relays_not_present_fetches_from_fallback_relays() -> Result<()> { | ||
| 933 | let mut m = MockUserManager::default(); | ||
| 934 | let mut client = generate_mock_client(); | ||
| 935 | m.config_manager.expect_load().returning(|| { | ||
| 936 | Ok(MyConfig { | ||
| 937 | users: vec![UserRef { | ||
| 938 | relays: UserRelays { | ||
| 939 | relays: vec![], | ||
| 940 | created_at: 0, | ||
| 941 | }, | ||
| 942 | ..generate_standard_config().users[0].clone() | ||
| 943 | }], | ||
| 944 | ..generate_standard_config() | ||
| 945 | }) | ||
| 946 | }); | ||
| 947 | m.config_manager.expect_save().returning(|_| Ok(())); | ||
| 948 | client | ||
| 949 | .expect_get_events() | ||
| 950 | .once() | ||
| 951 | .withf(move |relays, _filters| fallback_relays().eq(relays)) | ||
| 952 | .returning(|_, _| Ok(vec![])); | ||
| 953 | |||
| 954 | futures::executor::block_on(m.get_user( | ||
| 955 | &client, | ||
| 956 | &TEST_KEY_1_KEYS.public_key(), | ||
| 957 | 5 * 60, // 5 mins ago | ||
| 958 | ))?; | ||
| 959 | Ok(()) | ||
| 960 | } | ||
| 961 | } | ||
| 962 | |||
| 963 | #[test] | ||
| 964 | fn when_failed_to_fetch_events_returns_cached_details() -> Result<()> { | ||
| 965 | let mut m = MockUserManager::default(); | ||
| 966 | let mut client = generate_mock_client(); | ||
| 967 | m.config_manager | ||
| 968 | .expect_load() | ||
| 969 | .returning(|| Ok(generate_standard_config())); | ||
| 970 | client | ||
| 971 | .expect_get_events() | ||
| 972 | .returning(|_, _| Err(anyhow!("test error"))); | ||
| 973 | |||
| 974 | let res = futures::executor::block_on(m.get_user( | ||
| 975 | &client, | ||
| 976 | &TEST_KEY_1_KEYS.public_key(), | ||
| 977 | 5 * 60, // 10 mins ago | ||
| 978 | ))?; | ||
| 979 | assert_eq!(res.metadata.name, "Fred"); | ||
| 980 | Ok(()) | ||
| 981 | } | ||
| 982 | } | ||
| 332 | } | 983 | } |