upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bin/ngit/sub_commands/login.rs2
-rw-r--r--src/lib/login/existing.rs17
-rw-r--r--src/lib/login/fresh.rs18
-rw-r--r--test_utils/src/lib.rs70
-rw-r--r--tests/ngit_login.rs562
5 files changed, 179 insertions, 490 deletions
diff --git a/src/bin/ngit/sub_commands/login.rs b/src/bin/ngit/sub_commands/login.rs
index 29b4e81..ff58ec6 100644
--- a/src/bin/ngit/sub_commands/login.rs
+++ b/src/bin/ngit/sub_commands/login.rs
@@ -61,7 +61,7 @@ pub async fn launch(args: &Cli, command_args: &SubCommandArgs) -> Result<()> {
61 61
62/// return ( bool - logged out, bool - log in to local git locally) 62/// return ( bool - logged out, bool - log in to local git locally)
63async fn logout(git_repo: Option<&Repo>, local_only: bool) -> Result<(bool, bool)> { 63async fn logout(git_repo: Option<&Repo>, local_only: bool) -> Result<(bool, bool)> {
64 for source in if local_only { 64 for source in if local_only || std::env::var("NGITTEST").is_ok() {
65 vec![SignerInfoSource::GitLocal] 65 vec![SignerInfoSource::GitLocal]
66 } else { 66 } else {
67 vec![SignerInfoSource::GitLocal, SignerInfoSource::GitGlobal] 67 vec![SignerInfoSource::GitLocal, SignerInfoSource::GitGlobal]
diff --git a/src/lib/login/existing.rs b/src/lib/login/existing.rs
index 342f792..872e459 100644
--- a/src/lib/login/existing.rs
+++ b/src/lib/login/existing.rs
@@ -69,11 +69,18 @@ fn get_signer_info(
69 Ok(match source { 69 Ok(match source {
70 None => { 70 None => {
71 let mut result = None; 71 let mut result = None;
72 for source in &[ 72 for source in if std::env::var("NGITTEST").is_ok() {
73 SignerInfoSource::CommandLineArguments, 73 vec![
74 SignerInfoSource::GitLocal, 74 SignerInfoSource::CommandLineArguments,
75 SignerInfoSource::GitGlobal, 75 SignerInfoSource::GitLocal,
76 ] { 76 ]
77 } else {
78 vec![
79 SignerInfoSource::CommandLineArguments,
80 SignerInfoSource::GitLocal,
81 SignerInfoSource::GitGlobal,
82 ]
83 } {
77 if let Ok(res) = 84 if let Ok(res) =
78 get_signer_info(git_repo, signer_info, password, &Some(source.clone())) 85 get_signer_info(git_repo, signer_info, password, &Some(source.clone()))
79 { 86 {
diff --git a/src/lib/login/fresh.rs b/src/lib/login/fresh.rs
index 59026bd..b874992 100644
--- a/src/lib/login/fresh.rs
+++ b/src/lib/login/fresh.rs
@@ -25,7 +25,7 @@ use crate::{
25 Interactor, InteractorPrompt, Printer, PromptChoiceParms, PromptConfirmParms, 25 Interactor, InteractorPrompt, Printer, PromptChoiceParms, PromptConfirmParms,
26 PromptInputParms, PromptPasswordParms, 26 PromptInputParms, PromptPasswordParms,
27 }, 27 },
28 client::{fetch_public_key, send_events, Connect}, 28 client::{send_events, Connect},
29 git::{remove_git_config_item, save_git_config_item, Repo, RepoActions}, 29 git::{remove_git_config_item, save_git_config_item, Repo, RepoActions},
30}; 30};
31 31
@@ -38,7 +38,7 @@ pub async fn fresh_login_or_signup(
38) -> Result<(Arc<dyn NostrSigner>, UserRef, SignerInfoSource)> { 38) -> Result<(Arc<dyn NostrSigner>, UserRef, SignerInfoSource)> {
39 let (signer, public_key, signer_info, source) = loop { 39 let (signer, public_key, signer_info, source) = loop {
40 if let Some(signer_info) = signer_info { 40 if let Some(signer_info) = signer_info {
41 let (signer, _user_ref, source) = load_existing_login( 41 let (signer, user_ref, source) = load_existing_login(
42 git_repo, 42 git_repo,
43 &Some(signer_info.clone()), 43 &Some(signer_info.clone()),
44 &None, 44 &None,
@@ -48,8 +48,7 @@ pub async fn fresh_login_or_signup(
48 true, 48 true,
49 ) 49 )
50 .await?; 50 .await?;
51 let public_key = fetch_public_key(&signer).await?; 51 break (signer, user_ref.public_key, signer_info, source);
52 break (signer, public_key, signer_info, source);
53 } 52 }
54 match Interactor::default().choice( 53 match Interactor::default().choice(
55 PromptChoiceParms::default() 54 PromptChoiceParms::default()
@@ -176,7 +175,7 @@ pub async fn get_fresh_nsec_signer() -> Result<
176 (keys, signer_info) 175 (keys, signer_info)
177 } else if let Ok(keys) = nostr::Keys::from_str(&input) { 176 } else if let Ok(keys) = nostr::Keys::from_str(&input) {
178 let nsec = keys.secret_key().to_bech32()?; 177 let nsec = keys.secret_key().to_bech32()?;
179 show_prompt_success("nsec", &shorten_string(&nsec)); 178 show_prompt_success("nsec", &shorten_string(&input));
180 let signer_info = SignerInfo::Nsec { 179 let signer_info = SignerInfo::Nsec {
181 nsec, 180 nsec,
182 password: None, 181 password: None,
@@ -223,11 +222,11 @@ fn show_prompt_error(label: &str, value: &str) {
223 let _ = ColorfulTheme::default().format_error( 222 let _ = ColorfulTheme::default().format_error(
224 &mut s, 223 &mut s,
225 &format!( 224 &format!(
226 "{label}: {}", 225 "{label}: \"{}\"",
227 if value.is_empty() { 226 if value.is_empty() {
228 "empty".to_string() 227 "empty".to_string()
229 } else { 228 } else {
230 shorten_string(&format!("\"{}\"", &value)) 229 shorten_string(value)
231 } 230 }
232 ), 231 ),
233 ); 232 );
@@ -490,6 +489,11 @@ async fn save_to_git_config(
490 signer_info: &SignerInfo, 489 signer_info: &SignerInfo,
491 global: bool, 490 global: bool,
492) -> Result<()> { 491) -> Result<()> {
492 let global = if std::env::var("NGITTEST").is_ok() {
493 false
494 } else {
495 global
496 };
493 let err_msg = format!( 497 let err_msg = format!(
494 "failed to save login details to {} git config", 498 "failed to save login details to {} git config",
495 if global { "global" } else { "local" } 499 if global { "global" } else { "local" }
diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs
index 6ca0a43..6708c81 100644
--- a/test_utils/src/lib.rs
+++ b/test_utils/src/lib.rs
@@ -336,6 +336,49 @@ impl CliTesterInputPrompt<'_> {
336 } 336 }
337 337
338 pub fn succeeds_with(&mut self, input: &str) -> Result<&mut Self> { 338 pub fn succeeds_with(&mut self, input: &str) -> Result<&mut Self> {
339 self.succeeds_with_optional_shortened_report(input, false)
340 }
341
342 pub fn succeeds_with_optional_shortened_report(
343 &mut self,
344 input: &str,
345 shorten_report_to_15_chars: bool,
346 ) -> Result<&mut Self> {
347 self.tester.send_line(input)?;
348 self.tester
349 .expect(input)
350 .context("expect input to be printed")?;
351 self.tester
352 .expect("\r")
353 .context("expect new line after input to be printed")?;
354
355 let mut s = String::new();
356 let printed_input = if shorten_report_to_15_chars {
357 shorten_string(input)
358 } else {
359 input.to_string()
360 };
361 self.tester
362 .formatter
363 .format_input_prompt_selection(&mut s, self.prompt.as_str(), &printed_input)
364 .expect("diagluer theme formatter should succeed");
365 if !s.contains(self.prompt.as_str()) {
366 panic!("dialoguer must be broken as formatted prompt success doesnt contain prompt");
367 }
368 let formatted_success = format!("{}\r\n", sanatize(s));
369
370 self.tester
371 .expect(formatted_success.as_str())
372 .context("expect immediate prompt success")?;
373 Ok(self)
374 }
375
376 pub fn fails_with_optional_shortened_report(
377 &mut self,
378 input: &str,
379 prefix: Option<&str>,
380 shorten_report_to_15_chars: bool,
381 ) -> Result<&mut Self> {
339 self.tester.send_line(input)?; 382 self.tester.send_line(input)?;
340 self.tester 383 self.tester
341 .expect(input) 384 .expect(input)
@@ -345,9 +388,26 @@ impl CliTesterInputPrompt<'_> {
345 .context("expect new line after input to be printed")?; 388 .context("expect new line after input to be printed")?;
346 389
347 let mut s = String::new(); 390 let mut s = String::new();
391 let printed_input = if shorten_report_to_15_chars {
392 shorten_string(input)
393 } else {
394 input.to_string()
395 };
348 self.tester 396 self.tester
349 .formatter 397 .formatter
350 .format_input_prompt_selection(&mut s, self.prompt.as_str(), input) 398 .format_error(
399 &mut s,
400 &format!(
401 "{}{}: {}",
402 prefix.unwrap_or_default(),
403 &self.prompt,
404 if input.is_empty() {
405 "empty".to_string()
406 } else {
407 format!("\"{printed_input}\"")
408 }
409 ),
410 )
351 .expect("diagluer theme formatter should succeed"); 411 .expect("diagluer theme formatter should succeed");
352 if !s.contains(self.prompt.as_str()) { 412 if !s.contains(self.prompt.as_str()) {
353 panic!("dialoguer must be broken as formatted prompt success doesnt contain prompt"); 413 panic!("dialoguer must be broken as formatted prompt success doesnt contain prompt");
@@ -361,6 +421,14 @@ impl CliTesterInputPrompt<'_> {
361 } 421 }
362} 422}
363 423
424fn shorten_string(s: &str) -> String {
425 if s.len() < 15 {
426 s.to_string()
427 } else {
428 format!("{}...", &s[..15])
429 }
430}
431
364pub struct CliTesterPasswordPrompt<'a> { 432pub struct CliTesterPasswordPrompt<'a> {
365 tester: &'a mut CliTester, 433 tester: &'a mut CliTester,
366 prompt: String, 434 prompt: String,
diff --git a/tests/ngit_login.rs b/tests/ngit_login.rs
index 4cfe4e6..0a8bc19 100644
--- a/tests/ngit_login.rs
+++ b/tests/ngit_login.rs
@@ -3,44 +3,49 @@ use git::GitTestRepo;
3use serial_test::serial; 3use serial_test::serial;
4use test_utils::*; 4use test_utils::*;
5 5
6static EXPECTED_NSEC_PROMPT: &str = "login with nsec / bunker url / nostr address"; 6static EXPECTED_NSEC_PROMPT: &str = "nsec";
7static EXPECTED_LOCAL_REPOSITORY_PROMPT: &str = "just for this repository?"; 7
8static EXPECTED_REQUIRE_PASSWORD_PROMPT: &str = "require password?"; 8fn show_first_time_login_choices(p: &mut CliTester) -> Result<CliTesterChoicePrompt> {
9static EXPECTED_SET_PASSWORD_PROMPT: &str = "encrypt with password"; 9 p.expect_choice(
10static EXPECTED_SET_PASSWORD_CONFIRM_PROMPT: &str = "confirm password"; 10 "login to nostr",
11 vec![
12 "secret key (nsec / ncryptsec)".to_string(),
13 "nostr connect (remote signer)".to_string(),
14 "create account".to_string(),
15 "help".to_string(),
16 ],
17 )
18}
11 19
12fn standard_first_time_login_encrypting_nsec() -> Result<CliTester> { 20fn first_time_login_choices_succeeds_with_nsec(p: &mut CliTester, nsec: &str) -> Result<()> {
21 p.expect_choice(
22 "login to nostr",
23 vec![
24 "secret key (nsec / ncryptsec)".to_string(),
25 "nostr connect (remote signer)".to_string(),
26 "create account".to_string(),
27 "help".to_string(),
28 ],
29 )?
30 .succeeds_with(0, false, Some(0))?;
31
32 p.expect_input(EXPECTED_NSEC_PROMPT)?
33 .succeeds_with_optional_shortened_report(nsec, true)?;
34
35 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
36 Ok(())
37}
38
39fn standard_first_time_login_with_nsec() -> Result<CliTester> {
13 let test_repo = GitTestRepo::default(); 40 let test_repo = GitTestRepo::default();
14 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); 41 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
15 42
16 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? 43 first_time_login_choices_succeeds_with_nsec(&mut p, TEST_KEY_1_NSEC)?;
17 .succeeds_with(TEST_KEY_1_NSEC)?;
18
19 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
20 .succeeds_with(Some(true))?;
21
22 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
23 .succeeds_with(Some(true))?;
24
25 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
26 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
27 .succeeds_with(TEST_PASSWORD)?;
28 44
29 p.expect_end_eventually()?; 45 p.expect_end_eventually()?;
30 Ok(p) 46 Ok(p)
31} 47}
32 48
33fn expect_qr_prompt_opt_for_other_methods(p: &mut CliTester) -> Result<()> {
34 p.expect_eventually("scan QR or paste into remote signer")?;
35 p.expect_eventually("\r\n")?;
36 p.expect_eventually("login with nsec / bunker url / nostr address instead")?;
37 p.expect_eventually("\r\n")?;
38 p.send_line("")?;
39 // p.expect_eventually("\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r")?;
40 // p.expect_eventually("\r\r\r\r\r\r\r\r\r\r\r\r\r")?;
41
42 Ok(())
43}
44mod with_relays { 49mod with_relays {
45 use anyhow::Ok; 50 use anyhow::Ok;
46 use futures::join; 51 use futures::join;
@@ -73,21 +78,12 @@ mod with_relays {
73 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 78 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
74 let test_repo = GitTestRepo::default(); 79 let test_repo = GitTestRepo::default();
75 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); 80 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
76 expect_qr_prompt_opt_for_other_methods(&mut p)?;
77 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)?
78 .succeeds_with(TEST_KEY_1_NSEC)?;
79
80 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
81 .succeeds_with(Some(true))?;
82
83 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
84 .succeeds_with(Some(false))?;
85 81
86 p.expect("saved login details to local git config\r\n")?; 82 first_time_login_choices_succeeds_with_nsec(&mut p, TEST_KEY_1_NSEC)?;
87 83
88 p.expect("searching for profile...\r\n")?; 84 p.expect("searching for profile...\r\n")?;
89 85
90 p.expect_end_with("logged in as fred via cli arguments\r\n")?; 86 p.expect_end_with("logged in as fred\r\n")?;
91 for p in [51, 52] { 87 for p in [51, 52] {
92 shutdown_relay(8000 + p)?; 88 shutdown_relay(8000 + p)?;
93 } 89 }
@@ -114,17 +110,7 @@ mod with_relays {
114 let test_repo = GitTestRepo::default(); 110 let test_repo = GitTestRepo::default();
115 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); 111 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
116 112
117 expect_qr_prompt_opt_for_other_methods(&mut p)?; 113 first_time_login_choices_succeeds_with_nsec(&mut p, TEST_KEY_1_NSEC)?;
118 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)?
119 .succeeds_with(TEST_KEY_1_NSEC)?;
120
121 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
122 .succeeds_with(Some(true))?;
123
124 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
125 .succeeds_with(Some(false))?;
126
127 p.expect("saved login details to local git config\r\n")?;
128 114
129 p.expect("searching for profile...\r\n")?; 115 p.expect("searching for profile...\r\n")?;
130 116
@@ -395,7 +381,7 @@ mod with_relays {
395 .await 381 .await
396 } 382 }
397 383
398 mod when_specifying_command_line_nsec_only { 384 mod when_specifying_command_line_nsec {
399 use super::*; 385 use super::*;
400 386
401 #[tokio::test] 387 #[tokio::test]
@@ -433,136 +419,7 @@ mod with_relays {
433 ["login", "--nsec", TEST_KEY_1_NSEC], 419 ["login", "--nsec", TEST_KEY_1_NSEC],
434 ); 420 );
435 421
436 p.expect("saved login details to local git config\r\n")?; 422 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
437
438 p.expect("searching for profile...\r\n")?;
439
440 p.expect_end_with("logged in as fred via cli arguments\r\n")?;
441 for p in [51, 52] {
442 shutdown_relay(8000 + p)?;
443 }
444 Ok(())
445 });
446
447 // launch relay
448 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
449
450 cli_tester_handle.join().unwrap()?;
451 Ok(())
452 }
453 }
454 mod when_specifying_command_line_password_only {
455 use super::*;
456
457 #[tokio::test]
458 #[serial]
459 async fn displays_correct_name() -> Result<()> {
460 run_test_when_specifying_command_line_password_only_displays_correct_name(
461 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
462 relay.respond_events(
463 client_id,
464 &subscription_id,
465 &vec![
466 generate_test_key_1_metadata_event("fred"),
467 generate_test_key_1_relay_list_event_same_as_fallback(),
468 ],
469 )?;
470 Ok(())
471 }),
472 None,
473 )
474 .await
475 }
476 async fn run_test_when_specifying_command_line_password_only_displays_correct_name(
477 relay_listener1: Option<ListenerReqFunc<'_>>,
478 relay_listener2: Option<ListenerReqFunc<'_>>,
479 ) -> Result<()> {
480 let (mut r51, mut r52) = (
481 Relay::new(8051, None, relay_listener1),
482 Relay::new(8052, None, relay_listener2),
483 );
484
485 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
486 let test_repo = GitTestRepo::default();
487 CliTester::new_from_dir(
488 &test_repo.dir,
489 [
490 "login",
491 "--offline",
492 "--nsec",
493 TEST_KEY_1_NSEC,
494 "--password",
495 TEST_PASSWORD,
496 ],
497 )
498 .expect_end_eventually()?;
499
500 let mut p = CliTester::new_from_dir(
501 &test_repo.dir,
502 ["login", "--password", TEST_PASSWORD],
503 );
504
505 p.expect("searching for profile...\r\n")?;
506
507 p.expect_end_with("logged in as fred via cli arguments\r\n")?;
508 for p in [51, 52] {
509 shutdown_relay(8000 + p)?;
510 }
511 Ok(())
512 });
513
514 // launch relay
515 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
516
517 cli_tester_handle.join().unwrap()?;
518 Ok(())
519 }
520 }
521
522 mod when_specifying_command_line_nsec_and_password {
523 use super::*;
524
525 #[tokio::test]
526 #[serial]
527 async fn displays_correct_name() -> Result<()> {
528 run_test_when_specifying_command_line_nsec_and_password_displays_correct_name(
529 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
530 relay.respond_events(
531 client_id,
532 &subscription_id,
533 &vec![
534 generate_test_key_1_metadata_event("fred"),
535 generate_test_key_1_relay_list_event_same_as_fallback(),
536 ],
537 )?;
538 Ok(())
539 }),
540 None,
541 ).await
542 }
543 async fn run_test_when_specifying_command_line_nsec_and_password_displays_correct_name(
544 relay_listener1: Option<ListenerReqFunc<'_>>,
545 relay_listener2: Option<ListenerReqFunc<'_>>,
546 ) -> Result<()> {
547 let (mut r51, mut r52) = (
548 Relay::new(8051, None, relay_listener1),
549 Relay::new(8052, None, relay_listener2),
550 );
551
552 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
553 let test_repo = GitTestRepo::default();
554 let mut p = CliTester::new_from_dir(
555 &test_repo.dir,
556 [
557 "login",
558 "--nsec",
559 TEST_KEY_1_NSEC,
560 "--password",
561 TEST_PASSWORD,
562 ],
563 );
564
565 p.expect("saved login details to local git config\r\n")?;
566 423
567 p.expect("searching for profile...\r\n")?; 424 p.expect("searching for profile...\r\n")?;
568 425
@@ -581,7 +438,6 @@ mod with_relays {
581 } 438 }
582 } 439 }
583 } 440 }
584
585 mod when_no_metadata_found { 441 mod when_no_metadata_found {
586 use super::*; 442 use super::*;
587 443
@@ -604,17 +460,7 @@ mod with_relays {
604 let test_repo = GitTestRepo::default(); 460 let test_repo = GitTestRepo::default();
605 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); 461 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
606 462
607 expect_qr_prompt_opt_for_other_methods(&mut p)?; 463 first_time_login_choices_succeeds_with_nsec(&mut p, TEST_KEY_1_NSEC)?;
608 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)?
609 .succeeds_with(TEST_KEY_1_NSEC)?;
610
611 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
612 .succeeds_with(Some(true))?;
613
614 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
615 .succeeds_with(Some(false))?;
616
617 p.expect("saved login details to local git config\r\n")?;
618 464
619 p.expect("searching for profile...\r\n")?; 465 p.expect("searching for profile...\r\n")?;
620 466
@@ -668,17 +514,7 @@ mod with_relays {
668 let test_repo = GitTestRepo::default(); 514 let test_repo = GitTestRepo::default();
669 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); 515 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
670 516
671 expect_qr_prompt_opt_for_other_methods(&mut p)?; 517 first_time_login_choices_succeeds_with_nsec(&mut p, TEST_KEY_1_NSEC)?;
672 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)?
673 .succeeds_with(TEST_KEY_1_NSEC)?;
674
675 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
676 .succeeds_with(Some(true))?;
677
678 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
679 .succeeds_with(Some(false))?;
680
681 p.expect("saved login details to local git config\r\n")?;
682 518
683 p.expect("searching for profile...\r\n")?; 519 p.expect("searching for profile...\r\n")?;
684 520
@@ -699,79 +535,6 @@ mod with_relays {
699 } 535 }
700 } 536 }
701 } 537 }
702
703 mod when_second_time_login_and_details_already_fetched {
704 use super::*;
705
706 mod uses_cache_and_stores_and_retrieves_ncryptsec_from_local_git_config {
707 use super::*;
708
709 #[tokio::test]
710 #[serial]
711 async fn dislays_logged_in_with_correct_name() -> Result<()> {
712 run_test_dislays_logged_in_with_correct_name(Some(
713 &|relay, client_id, subscription_id, _| -> Result<()> {
714 relay.respond_events(
715 client_id,
716 &subscription_id,
717 &vec![
718 generate_test_key_1_metadata_event("fred"),
719 generate_test_key_1_relay_list_event_same_as_fallback(),
720 ],
721 )?;
722 Ok(())
723 },
724 ))
725 .await
726 }
727 async fn run_test_dislays_logged_in_with_correct_name(
728 relay_listener: Option<ListenerReqFunc<'_>>,
729 ) -> Result<()> {
730 let (mut r51, mut r52) = (
731 Relay::new(8051, None, relay_listener),
732 Relay::new(8052, None, None),
733 );
734
735 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
736 let test_repo = GitTestRepo::default();
737 let mut p = CliTester::new_from_dir(
738 &test_repo.dir,
739 [
740 "login",
741 "--nsec",
742 TEST_KEY_1_NSEC,
743 "--password",
744 TEST_PASSWORD,
745 ],
746 );
747
748 p.expect("saved login details to local git config\r\n")?;
749
750 p.expect_end_eventually_with("logged in as fred via cli arguments\r\n")?;
751
752 for p in [51, 52] {
753 shutdown_relay(8000 + p)?;
754 }
755
756 let mut p = CliTester::new_from_dir(
757 &test_repo.dir,
758 ["login", "--password", TEST_PASSWORD],
759 );
760
761 p.expect_end_eventually_with("logged in as fred\r\n")?;
762
763 Ok(())
764 });
765
766 // launch relay
767 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
768
769 cli_tester_handle.join().unwrap()?;
770
771 Ok(())
772 }
773 }
774 }
775 } 538 }
776 mod when_user_relay_list_contains_write_relays_not_in_fallback_list { 539 mod when_user_relay_list_contains_write_relays_not_in_fallback_list {
777 use super::*; 540 use super::*;
@@ -792,17 +555,7 @@ mod with_relays {
792 let test_repo = GitTestRepo::default(); 555 let test_repo = GitTestRepo::default();
793 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); 556 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
794 557
795 expect_qr_prompt_opt_for_other_methods(&mut p)?; 558 first_time_login_choices_succeeds_with_nsec(&mut p, TEST_KEY_1_NSEC)?;
796 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)?
797 .succeeds_with(TEST_KEY_1_NSEC)?;
798
799 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
800 .succeeds_with(Some(true))?;
801
802 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
803 .succeeds_with(Some(false))?;
804
805 p.expect("saved login details to local git config\r\n")?;
806 559
807 p.expect("searching for profile...\r\n")?; 560 p.expect("searching for profile...\r\n")?;
808 561
@@ -868,7 +621,7 @@ mod with_offline_flag {
868 621
869 #[test] 622 #[test]
870 fn prompts_for_nsec_and_password() -> Result<()> { 623 fn prompts_for_nsec_and_password() -> Result<()> {
871 standard_first_time_login_encrypting_nsec()?; 624 standard_first_time_login_with_nsec()?;
872 Ok(()) 625 Ok(())
873 } 626 }
874 627
@@ -877,20 +630,12 @@ mod with_offline_flag {
877 let test_repo = GitTestRepo::default(); 630 let test_repo = GitTestRepo::default();
878 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); 631 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
879 632
880 p.expect_input(EXPECTED_NSEC_PROMPT)? 633 show_first_time_login_choices(&mut p)?.succeeds_with(0, false, Some(0))?;
881 .succeeds_with(TEST_KEY_1_NSEC)?;
882
883 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
884 .succeeds_with(Some(true))?;
885
886 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
887 .succeeds_with(Some(true))?;
888 634
889 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? 635 p.expect_input(EXPECTED_NSEC_PROMPT)?
890 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? 636 .succeeds_with_optional_shortened_report(TEST_KEY_1_NSEC, true)?;
891 .succeeds_with(TEST_PASSWORD)?;
892 637
893 p.expect("saved login details to local git config\r\n")?; 638 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
894 639
895 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) 640 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
896 } 641 }
@@ -900,20 +645,12 @@ mod with_offline_flag {
900 let test_repo = GitTestRepo::default(); 645 let test_repo = GitTestRepo::default();
901 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); 646 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
902 647
903 p.expect_input(EXPECTED_NSEC_PROMPT)? 648 show_first_time_login_choices(&mut p)?.succeeds_with(0, false, Some(0))?;
904 .succeeds_with(TEST_KEY_1_SK_HEX)?;
905
906 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
907 .succeeds_with(Some(true))?;
908 649
909 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? 650 p.expect_input(EXPECTED_NSEC_PROMPT)?
910 .succeeds_with(Some(true))?; 651 .succeeds_with_optional_shortened_report(TEST_KEY_1_SK_HEX, true)?;
911
912 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
913 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
914 .succeeds_with(TEST_PASSWORD)?;
915 652
916 p.expect("saved login details to local git config\r\n")?; 653 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
917 654
918 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) 655 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
919 } 656 }
@@ -923,35 +660,30 @@ mod with_offline_flag {
923 660
924 #[test] 661 #[test]
925 fn prompts_for_nsec_until_valid() -> Result<()> { 662 fn prompts_for_nsec_until_valid() -> Result<()> {
926 let invalid_nsec_response =
927 "invalid. try again with nostr address / bunker uri / nsec";
928
929 let test_repo = GitTestRepo::default(); 663 let test_repo = GitTestRepo::default();
930 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); 664 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
931 665
932 p.expect_input(EXPECTED_NSEC_PROMPT)? 666 show_first_time_login_choices(&mut p)?.succeeds_with(0, false, Some(0))?;
933 // this behaviour is intentional. rejecting the response with dialoguer
934 // hides the original input from the user so they
935 // failed to see the mistake they made.
936 .succeeds_with(TEST_INVALID_NSEC)?;
937
938 p.expect_input(invalid_nsec_response)?
939 .succeeds_with(TEST_INVALID_NSEC)?;
940
941 p.expect_input(invalid_nsec_response)?
942 .succeeds_with(TEST_KEY_1_NSEC)?;
943 667
944 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? 668 for _ in 0..2 {
945 .succeeds_with(Some(true))?; 669 p.expect_input(EXPECTED_NSEC_PROMPT)?
670 .fails_with_optional_shortened_report(
671 TEST_INVALID_NSEC,
672 Some("invalid "),
673 true,
674 )?;
946 675
947 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? 676 p.expect_choice(
948 .succeeds_with(Some(true))?; 677 "login to nostr",
678 vec!["try again with nsec".to_string(), "back".to_string()],
679 )?
680 .succeeds_with(0, false, Some(0))?;
681 }
949 682
950 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? 683 p.expect_input(EXPECTED_NSEC_PROMPT)?
951 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? 684 .succeeds_with_optional_shortened_report(TEST_KEY_1_NSEC, true)?;
952 .succeeds_with(TEST_PASSWORD)?;
953 685
954 p.expect("saved login details to local git config\r\n")?; 686 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
955 687
956 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) 688 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
957 } 689 }
@@ -969,49 +701,14 @@ mod with_offline_flag {
969 ["login", "--offline", "--nsec", TEST_KEY_1_NSEC], 701 ["login", "--offline", "--nsec", TEST_KEY_1_NSEC],
970 ); 702 );
971 703
972 p.expect("saved login details to local git config\r\n")?; 704 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
973 705
974 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) 706 p.expect_end_with(
707 format!("logged in as {} via cli arguments\r\n", TEST_KEY_1_NPUB).as_str(),
708 )
975 } 709 }
976 710
977 #[test] 711 #[test]
978 fn forgets_identity() -> Result<()> {
979 let test_repo = GitTestRepo::default();
980 let mut p = CliTester::new_from_dir(
981 &test_repo.dir,
982 ["login", "--offline", "--nsec", TEST_KEY_1_NSEC],
983 );
984
985 p.expect("saved login details to local git config\r\n")?;
986
987 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?;
988
989 p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
990
991 p.expect_input(EXPECTED_NSEC_PROMPT)?
992 .succeeds_with(TEST_KEY_1_NSEC)?;
993
994 p.exit()
995 }
996
997 mod when_logging_in_as_different_nsec {
998 use super::*;
999
1000 #[test]
1001 fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> {
1002 standard_first_time_login_encrypting_nsec()?.exit()?;
1003 let test_repo = GitTestRepo::default();
1004 let mut p = CliTester::new_from_dir(
1005 &test_repo.dir,
1006 ["login", "--offline", "--nsec", TEST_KEY_2_NSEC],
1007 );
1008
1009 p.expect("saved login details to local git config\r\n")?;
1010
1011 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str())
1012 }
1013 }
1014 #[test]
1015 fn invalid_nsec_param_fails_without_prompts() -> Result<()> { 712 fn invalid_nsec_param_fails_without_prompts() -> Result<()> {
1016 let test_repo = GitTestRepo::default(); 713 let test_repo = GitTestRepo::default();
1017 let mut p = CliTester::new_from_dir( 714 let mut p = CliTester::new_from_dir(
@@ -1042,8 +739,11 @@ mod with_offline_flag {
1042 TEST_PASSWORD, 739 TEST_PASSWORD,
1043 ], 740 ],
1044 ); 741 );
1045 p.expect("saved login details to local git config\r\n")?; 742 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
1046 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) 743
744 p.expect_end_with(
745 format!("logged in as {} via cli arguments\r\n", TEST_KEY_1_NPUB).as_str(),
746 )
1047 } 747 }
1048 748
1049 #[test] 749 #[test]
@@ -1060,8 +760,11 @@ mod with_offline_flag {
1060 "--offline", 760 "--offline",
1061 ], 761 ],
1062 ); 762 );
1063 p.expect("saved login details to local git config\r\n")?; 763 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
1064 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) 764
765 p.expect_end_with(
766 format!("logged in as {} via cli arguments\r\n", TEST_KEY_1_NPUB).as_str(),
767 )
1065 } 768 }
1066 769
1067 mod when_logging_in_as_different_nsec { 770 mod when_logging_in_as_different_nsec {
@@ -1069,7 +772,7 @@ mod with_offline_flag {
1069 772
1070 #[test] 773 #[test]
1071 fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> { 774 fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> {
1072 standard_first_time_login_encrypting_nsec()?.exit()?; 775 standard_first_time_login_with_nsec()?.exit()?;
1073 let test_repo = GitTestRepo::default(); 776 let test_repo = GitTestRepo::default();
1074 let mut p = CliTester::new_from_dir( 777 let mut p = CliTester::new_from_dir(
1075 &test_repo.dir, 778 &test_repo.dir,
@@ -1082,105 +785,12 @@ mod with_offline_flag {
1082 TEST_PASSWORD, 785 TEST_PASSWORD,
1083 ], 786 ],
1084 ); 787 );
1085 p.expect("saved login details to local git config\r\n")?; 788 p.expect("saved login details to local git config. you are only logged in to this local repository.\r\n")?;
1086 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str())
1087 }
1088 }
1089
1090 mod when_provided_with_new_password {
1091 use super::*;
1092
1093 #[test]
1094 fn password_changes() -> Result<()> {
1095 standard_first_time_login_encrypting_nsec()?.exit()?;
1096 let test_repo = GitTestRepo::default();
1097 let mut p = CliTester::new_from_dir(
1098 &test_repo.dir,
1099 [
1100 "login",
1101 "--offline",
1102 "--nsec",
1103 TEST_KEY_1_NSEC,
1104 "--password",
1105 TEST_INVALID_PASSWORD,
1106 ],
1107 );
1108 p.expect("saved login details to local git config\r\n")?;
1109 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?;
1110 789
1111 CliTester::new_from_dir( 790 p.expect_end_with(
1112 &test_repo.dir, 791 format!("logged in as {} via cli arguments\r\n", TEST_KEY_2_NPUB).as_str(),
1113 ["--password", TEST_INVALID_PASSWORD, "login", "--offline"],
1114 ) 792 )
1115 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
1116 } 793 }
1117 } 794 }
1118
1119 #[test]
1120 fn invalid_nsec_param_fails_without_prompts() -> Result<()> {
1121 let test_repo = GitTestRepo::default();
1122 let mut p = CliTester::new_from_dir(
1123 &test_repo.dir,
1124 [
1125 "login",
1126 "--offline",
1127 "--nsec",
1128 TEST_INVALID_NSEC,
1129 "--password",
1130 TEST_PASSWORD,
1131 ],
1132 );
1133 p.expect_end_with(
1134 "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n",
1135 )
1136 }
1137 }
1138
1139 mod when_weak_password {
1140 use super::*;
1141
1142 #[test]
1143 #[serial]
1144 // combined into a single test as it is computationally expensive to run
1145 fn warns_it_might_take_a_few_seconds_then_succeeds_then_second_login_prompts_for_password_then_warns_again_then_succeeds()
1146 -> Result<()> {
1147 let test_repo = GitTestRepo::default();
1148 let mut p =
1149 CliTester::new_with_timeout_from_dir(15000, &test_repo.dir, ["login", "--offline"]);
1150
1151 p.expect_input(EXPECTED_NSEC_PROMPT)?
1152 .succeeds_with(TEST_KEY_1_NSEC)?;
1153
1154 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
1155 .succeeds_with(Some(true))?;
1156
1157 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
1158 .succeeds_with(Some(true))?;
1159
1160 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
1161 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
1162 .succeeds_with(TEST_WEAK_PASSWORD)?;
1163
1164 p.expect("this may take a few seconds...\r\n")?;
1165
1166 p.expect("saved login details to local git config\r\n")?;
1167
1168 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
1169
1170 // commented out as 'login' command now assumes you want to
1171 // login as a new user
1172 // p = CliTester::new_with_timeout(10000, ["login",
1173 // "--offline"]);
1174
1175 // p.expect(format!("login as {}\r\n",
1176 // TEST_KEY_1_NPUB).as_str())?
1177 // .expect_password(EXPECTED_PASSWORD_PROMPT)?
1178 // .succeeds_with(TEST_WEAK_PASSWORD)?;
1179
1180 // p.expect("this may take a few seconds...\r\n")?;
1181
1182 // p.expect_end_with(format!("logged in as {}\r\n",
1183 // TEST_KEY_1_NPUB).as_str())
1184 }
1185 } 795 }
1186} 796}