From 967de64e19c05a2f14e7c97da000c1f3b8ccc4ed Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 24 Sep 2024 19:22:23 +0100 Subject: test: restructure ngit integration tests so that each file is ran as a seperate crate this makes it easier to see which tests are causing other tests to fail as they are in a smaller group. --- tests/ngit/init.rs | 706 ------------------- tests/ngit/list.rs | 1549 ----------------------------------------- tests/ngit/login.rs | 1186 -------------------------------- tests/ngit/main.rs | 6 - tests/ngit/pull.rs | 615 ----------------- tests/ngit/push.rs | 531 -------------- tests/ngit/send.rs | 1901 --------------------------------------------------- tests/ngit_init.rs | 706 +++++++++++++++++++ tests/ngit_list.rs | 1549 +++++++++++++++++++++++++++++++++++++++++ tests/ngit_login.rs | 1186 ++++++++++++++++++++++++++++++++ tests/ngit_pull.rs | 615 +++++++++++++++++ tests/ngit_push.rs | 531 ++++++++++++++ tests/ngit_send.rs | 1901 +++++++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 6488 insertions(+), 6494 deletions(-) delete mode 100644 tests/ngit/init.rs delete mode 100644 tests/ngit/list.rs delete mode 100644 tests/ngit/login.rs delete mode 100644 tests/ngit/main.rs delete mode 100644 tests/ngit/pull.rs delete mode 100644 tests/ngit/push.rs delete mode 100644 tests/ngit/send.rs create mode 100644 tests/ngit_init.rs create mode 100644 tests/ngit_list.rs create mode 100644 tests/ngit_login.rs create mode 100644 tests/ngit_pull.rs create mode 100644 tests/ngit_push.rs create mode 100644 tests/ngit_send.rs diff --git a/tests/ngit/init.rs b/tests/ngit/init.rs deleted file mode 100644 index c8390e3..0000000 --- a/tests/ngit/init.rs +++ /dev/null @@ -1,706 +0,0 @@ -use anyhow::Result; -use nostr_sdk::Kind; -use serial_test::serial; -use test_utils::{git::GitTestRepo, *}; - -fn expect_msgs_first(p: &mut CliTester) -> Result<()> { - p.expect("searching for profile...\r\n")?; - p.expect("logged in as fred\r\n")?; - // // p.expect("searching for existing claims on repository...\r\n")?; - p.expect("publishing repostory reference...\r\n")?; - Ok(()) -} - -fn expect_msgs_after(p: &mut CliTester) -> Result<()> { - p.expect_after_whitespace("maintainers.yaml created. commit and push.\r\n")?; - p.expect( - "this optional file helps in identifying who the maintainers are over time through the commit history\r\n", - )?; - Ok(()) -} - -fn get_cli_args() -> Vec<&'static str> { - vec![ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "init", - "--title", - "example-name", - "--identifier", - "example-identifier", - "--description", - "example-description", - "--web", - "https://exampleproject.xyz", - "https://gitworkshop.dev/123", - "--relays", - "ws://localhost:8055", - "ws://localhost:8056", - "--clone-url", - "https://git.myhosting.com/my-repo.git", - "--earliest-unique-commit", - "9ee507fc4357d7ee16a5d8901bedcd103f23c17d", - "--other-maintainers", - TEST_KEY_1_NPUB, - ] -} - -mod when_repo_not_previously_claimed { - use super::*; - - mod when_repo_relays_specified_as_arguments { - use futures::join; - use test_utils::relay::Relay; - - use super::*; - - fn prep_git_repo() -> Result { - let test_repo = GitTestRepo::without_repo_in_git_config(); - test_repo.populate()?; - test_repo.add_remote("origin", "https://localhost:1000")?; - Ok(test_repo) - } - - fn cli_tester_init(git_repo: &GitTestRepo) -> CliTester { - CliTester::new_from_dir(&git_repo.dir, get_cli_args()) - } - - async fn prep_run_init() -> Result<( - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - )> { - let git_repo = prep_git_repo()?; - // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - Relay::new(8057, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_init(&git_repo); - p.expect_end_eventually()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - r57.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok((r51, r52, r53, r55, r56, r57)) - } - - mod sent_to_correct_relays { - - use super::*; - - #[tokio::test] - #[serial] - async fn only_1_repository_kind_event_sent_to_user_relays() -> Result<()> { - let (_, _, r53, r55, _, _) = prep_run_init().await?; - for relay in [&r53, &r55] { - assert_eq!( - relay - .events - .iter() - .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .count(), - 1, - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn only_1_repository_kind_event_sent_to_specified_repo_relays() -> Result<()> { - let (_, _, _, r55, r56, _) = prep_run_init().await?; - for relay in [&r55, &r56] { - assert_eq!( - relay - .events - .iter() - .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .count(), - 1, - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn only_1_repository_kind_event_sent_to_fallback_relays() -> Result<()> { - let (r51, r52, _, _, _, _) = prep_run_init().await?; - for relay in [&r51, &r52] { - assert_eq!( - relay - .events - .iter() - .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .count(), - 1, - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn only_1_repository_kind_event_sent_to_blaster_relays() -> Result<()> { - let (_, _, _, _, _, r57) = prep_run_init().await?; - assert_eq!( - r57.events - .iter() - .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .count(), - 1, - ); - Ok(()) - } - } - - mod yaml_file { - use std::{fs, io::Read}; - - use super::*; - - async fn async_run_test() -> Result<()> { - let git_repo = prep_git_repo()?; - // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - Relay::new(8057, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_init(&git_repo); - p.expect_end_eventually()?; - - let yaml_path = git_repo.dir.join("maintainers.yaml"); - - assert!(yaml_path.exists()); - - let mut file = fs::File::open(yaml_path).expect("no such file"); - let mut file_contents = "".to_string(); - let _ = file.read_to_string(&mut file_contents); - - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - assert_eq!( - file_contents, - format!( - "\ - identifier: example-identifier\n\ - maintainers:\n\ - - {TEST_KEY_1_NPUB}\n\ - relays:\n\ - - ws://localhost:8055\n\ - - ws://localhost:8056\n\ - " - ), - ); - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - r57.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - - #[tokio::test] - #[serial] - async fn contains_identifier_maintainers_and_relays() -> Result<()> { - async_run_test().await - } - mod updates_existing_with_missing_identifier { - use std::io::Write; - - use super::*; - async fn async_run_test() -> Result<()> { - let git_repo = prep_git_repo()?; - // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - Relay::new(8057, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let yaml_path = git_repo.dir.join("maintainers.yaml"); - let mut file = std::fs::File::create(&yaml_path) - .expect("cannot create maintainers.yaml file"); - write!( - file, - "\ - maintainers:\n\ - - {TEST_KEY_1_NPUB}\n\ - relays:\n\ - - ws://localhost:8055\n\ - - ws://localhost:8056\n\ - " - )?; - - let mut p = cli_tester_init(&git_repo); - p.expect_end_eventually()?; - - assert!(yaml_path.exists()); - - let mut file = fs::File::open(yaml_path).expect("no such file"); - let mut file_contents = "".to_string(); - let _ = file.read_to_string(&mut file_contents); - - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - assert_eq!( - file_contents, - format!( - "\ - identifier: example-identifier\n\ - maintainers:\n\ - - {TEST_KEY_1_NPUB}\n\ - relays:\n\ - - ws://localhost:8055\n\ - - ws://localhost:8056\n\ - " - ), - ); - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - r57.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - - #[tokio::test] - #[serial] - async fn adds_missing_identifier() -> Result<()> { - async_run_test().await - } - } - } - - mod git_config_updated { - - use nostr::nips::nip01::Coordinate; - use nostr_sdk::ToBech32; - - use super::*; - - async fn async_run_test() -> Result<()> { - let git_repo = prep_git_repo()?; - // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - Relay::new(8057, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_init(&git_repo); - p.expect_end_eventually()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - assert_eq!( - git_repo - .git_repo - .config()? - .get_entry("nostr.repo")? - .value() - .unwrap(), - Coordinate { - kind: nostr_sdk::Kind::GitRepoAnnouncement, - identifier: "example-identifier".to_string(), - public_key: TEST_KEY_1_KEYS.public_key(), - relays: vec![], - } - .to_bech32()?, - ); - - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - r57.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - - #[tokio::test] - #[serial] - async fn with_nostr_repo_set_to_user_and_identifer_naddr() -> Result<()> { - async_run_test().await?; - Ok(()) - } - } - - mod tags_as_specified_in_args { - use super::*; - - #[tokio::test] - #[serial] - async fn d_replaceable_event_identifier() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - - assert!( - event.tags.iter().any( - |t| t.as_vec()[0].eq("d") && t.as_vec()[1].eq("example-identifier") - ) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn earliest_unique_commit_as_reference_with_euc_marker() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - - assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("r") - && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d") - && t.as_vec()[2].eq("euc"))); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn name() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - - assert!( - event - .tags - .iter() - .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("example-name")) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn alt() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - - assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("alt") - && t.as_vec()[1].eq("git repository: example-name"))); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn description() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - - assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("description") - && t.as_vec()[1].eq("example-description"))); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn git_server() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - - assert!( - event.tags.iter().any(|t| t.as_vec()[0].eq("clone") - && t.as_vec()[1].eq("https://git.myhosting.com/my-repo.git")) /* todo check it defaults to origin */ - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn relays() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - let relays_tag = event - .tags - .iter() - .find(|t| t.as_vec()[0].eq("relays")) - .unwrap() - .as_vec(); - assert_eq!(relays_tag[1], "ws://localhost:8055",); - assert_eq!(relays_tag[2], "ws://localhost:8056",); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn web() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - let web_tag = event - .tags - .iter() - .find(|t| t.as_vec()[0].eq("web")) - .unwrap() - .as_vec(); - assert_eq!(web_tag[1], "https://exampleproject.xyz",); - assert_eq!(web_tag[2], "https://gitworkshop.dev/123",); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn maintainers() -> Result<()> { - let (_, _, r53, r55, r56, r57) = prep_run_init().await?; - for relay in [&r53, &r55, &r56, &r57] { - let event: &nostr::Event = relay - .events - .iter() - .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) - .unwrap(); - let maintainers_tag = event - .tags - .iter() - .find(|t| t.as_vec()[0].eq("maintainers")) - .unwrap() - .as_vec(); - assert_eq!(maintainers_tag[1], TEST_KEY_1_KEYS.public_key().to_string()); - } - Ok(()) - } - } - - mod cli_ouput { - use super::*; - - #[tokio::test] - #[serial] - async fn check_cli_output() -> Result<()> { - let git_repo = prep_git_repo()?; - - // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - Relay::new(8057, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_init(&git_repo); - expect_msgs_first(&mut p)?; - relay::expect_send_with_progress( - &mut p, - vec![ - (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), - (" [my-relay] ws://localhost:8053", true, ""), - (" [repo-relay] ws://localhost:8056", true, ""), - (" [default] ws://localhost:8051", true, ""), - (" [default] ws://localhost:8052", true, ""), - (" [default] ws://localhost:8057", true, ""), - ], - 1, - )?; - expect_msgs_after(&mut p)?; - p.expect_end()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - r57.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - } - // TODO: cli caputuring input -} -// TODO: when_updating_existing_repoistory correct defaults are used diff --git a/tests/ngit/list.rs b/tests/ngit/list.rs deleted file mode 100644 index 26cf717..0000000 --- a/tests/ngit/list.rs +++ /dev/null @@ -1,1549 +0,0 @@ -use anyhow::Result; -use futures::join; -use serial_test::serial; -use test_utils::{git::GitTestRepo, relay::Relay, *}; - -async fn prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout( - proposal_number: u16, -) -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { - let (originating_repo, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(proposal_number)?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok(res) -} - -mod cannot_find_repo_event { - use super::*; - mod cli_prompts { - use nostr::{nips::nip01::Coordinate, ToBech32}; - - use super::*; - async fn run_async_repo_event_ref_needed(invalid_input: bool, naddr: bool) -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - - r55.events.push(generate_test_key_1_relay_list_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - - let repo_event = generate_repo_ref_event(); - r56.events.push(repo_event.clone()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let test_repo = GitTestRepo::without_repo_in_git_config(); - test_repo.populate()?; - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect( - "hint: https://gitworkshop.dev/repos lists repositories and their naddr\r\n", - )?; - if invalid_input { - let mut input = p.expect_input("repository naddr")?; - input.succeeds_with("dfgvfvfzadvd")?; - p.expect("not a valid naddr\r\n")?; - let _ = p.expect_input("repository naddr")?; - p.exit()?; - } - if naddr { - let mut input = p.expect_input("repository naddr")?; - input.succeeds_with( - &Coordinate { - kind: nostr::Kind::GitRepoAnnouncement, - public_key: TEST_KEY_1_KEYS.public_key(), - identifier: repo_event.identifier().unwrap().to_string(), - relays: vec!["ws://localhost:8056".to_string()], - } - .to_bech32()?, - )?; - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?; - } - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - - #[tokio::test] - #[serial] - async fn warns_not_valid_input_and_asks_again() -> Result<()> { - run_async_repo_event_ref_needed(true, false).await - } - - #[tokio::test] - #[serial] - async fn finds_based_on_naddr_on_embeded_relay() -> Result<()> { - run_async_repo_event_ref_needed(false, true).await - } - } -} -mod when_main_branch_is_uptodate { - use super::*; - - mod when_proposal_branch_doesnt_exist { - use super::*; - - mod when_main_is_checked_out { - use super::*; - - mod when_first_proposal_selected { - use super::*; - - // TODO: test when other proposals with the same name but from other - // repositories are present on relays - - mod cli_prompts { - use super::*; - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - cli_tester_create_proposals()?; - - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!( - "create and checkout proposal branch (2 ahead 0 behind 'main')" ), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, None)?; - p.expect(format!( - "checked out proposal as 'pr/{}(", - FEATURE_BRANCH_NAME_1, - ))?; - p.expect_end_eventually_with(")' branch\r\n")?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_created_with_correct_name() -> Result<()> { - let (_, test_repo) = - prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?; - assert_eq!( - vec![ - "main", - &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, - ], - test_repo.get_local_branch_names()? - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_branch_checked_out() -> Result<()> { - let (_, test_repo) = - prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?; - assert_eq!( - get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, - test_repo.get_checked_out_branch_name()?, - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { - let (originating_repo, test_repo) = - prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?; - assert_eq!( - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_1 - )?)?, - ); - Ok(()) - } - } - mod when_third_proposal_selected { - use super::*; - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - cli_tester_create_proposals()?; - - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(0, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!( - "create and checkout proposal branch (2 ahead 0 behind 'main')" ), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect(format!( - "checked out proposal as 'pr/{}(", - FEATURE_BRANCH_NAME_3, - ))?; - p.expect_end_eventually_with(")' branch\r\n")?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_created_with_correct_name() -> Result<()> { - let (_, test_repo) = - prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?; - assert_eq!( - vec![ - "main", - &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_3)?, - ], - test_repo.get_local_branch_names()? - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_branch_checked_out() -> Result<()> { - let (_, test_repo) = - prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?; - assert_eq!( - get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_3)?, - test_repo.get_checked_out_branch_name()?, - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { - let (originating_repo, test_repo) = - prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?; - assert_eq!( - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_3)?, - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_3 - )?)?, - ); - Ok(()) - } - } - mod when_forth_proposal_has_no_cover_letter { - use super::*; - - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn( - move || -> Result<(GitTestRepo, GitTestRepo)> { - let originating_repo = cli_tester_create_proposals()?; - cli_tester_create_proposal( - &originating_repo, - FEATURE_BRANCH_NAME_4, - "d", - None, - None, - )?; - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("add d3.md"), // commit msg title - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(0, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!( - "create and checkout proposal branch (2 ahead 0 behind 'main')" ), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect_end_eventually_and_print()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }, - ); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let originating_repo = cli_tester_create_proposals()?; - std::thread::sleep(std::time::Duration::from_millis(1000)); - cli_tester_create_proposal( - &originating_repo, - FEATURE_BRANCH_NAME_4, - "d", - None, - None, - )?; - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("add d3.md"), // commit msg title - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(0, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!( - "create and checkout proposal branch (2 ahead 0 behind 'main')" ), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect(format!( - "checked out proposal as 'pr/{}(", - FEATURE_BRANCH_NAME_4, - ))?; - p.expect_end_eventually_with(")' branch\r\n")?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_created_with_correct_name() -> Result<()> { - let (_, test_repo) = prep_and_run().await?; - assert_eq!( - vec![ - "main", - &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_4)?, - ], - test_repo.get_local_branch_names()? - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_branch_checked_out() -> Result<()> { - let (_, test_repo) = prep_and_run().await?; - assert_eq!( - get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_4)?, - test_repo.get_checked_out_branch_name()?, - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { - let (originating_repo, test_repo) = prep_and_run().await?; - assert_eq!( - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_4)?, - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_4 - )?)?, - ); - Ok(()) - } - } - } - } - - mod when_proposal_branch_exists { - use super::*; - - mod when_main_is_checked_out { - use super::*; - - mod when_branch_is_up_to_date { - use super::*; - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn( - move || -> Result<(GitTestRepo, GitTestRepo)> { - let originating_repo = cli_tester_create_proposals()?; - - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - // create proposal branch - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!("create and checkout proposal branch (2 ahead 0 behind 'main')"), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect_end_eventually()?; - - test_repo.checkout("main")?; - // run test - p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!("checkout proposal branch (2 ahead 0 behind 'main')"), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect_end_eventually_and_print()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }, - ); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - cli_tester_create_proposals()?; - - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - // create proposal branch - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!("create and checkout proposal branch (2 ahead 0 behind 'main')"), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect_end_eventually()?; - - test_repo.checkout("main")?; - // run test - p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!("checkout proposal branch (2 ahead 0 behind 'main')"), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect(format!( - "checked out proposal as 'pr/{}(", - FEATURE_BRANCH_NAME_1, - ))?; - p.expect_end_eventually_with(")' branch\r\n")?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_checked_out() -> Result<()> { - let (_, test_repo) = prep_and_run().await?; - assert_eq!( - get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, - test_repo.get_checked_out_branch_name()?, - ); - Ok(()) - } - } - - mod when_branch_is_behind { - use super::*; - - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn( - move || -> Result<(GitTestRepo, GitTestRepo)> { - let (originating_repo, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( - &test_repo, - )?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!("checkout proposal branch and apply 1 appendments"), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect("checked out proposal branch and applied 1 appendments (2 ahead 0 behind 'main')\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }, - ); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( - &test_repo, - )?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - let mut c = p.expect_choice( - "", - vec![ - format!("checkout proposal branch and apply 1 appendments"), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect("checked out proposal branch and applied 1 appendments (2 ahead 0 behind 'main')\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_checked_out() -> Result<()> { - let (_, test_repo) = prep_and_run().await?; - assert_eq!( - get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, - test_repo.get_checked_out_branch_name()?, - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { - let (originating_repo, test_repo) = prep_and_run().await?; - assert_eq!( - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_1 - )?)?, - ); - Ok(()) - } - } - - mod when_latest_proposal_amended_locally { - // other rebase scenarios should work if this test passes - use super::*; - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = - std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { - let (originating_repo, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - let branch_name = test_repo.get_checked_out_branch_name()?; - - remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( - &test_repo, - )?; - - // add another commit (so we have an ammened local branch) - test_repo.checkout(&branch_name)?; - std::fs::write( - test_repo.dir.join("ammended-commit.md"), - "some content", - )?; - test_repo.stage_and_commit("add ammended-commit.md")?; - test_repo.checkout("main")?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - p.expect_eventually("--force`\r\n")?; - - let mut c = p.expect_choice( - "", - vec![ - format!("checkout local branch with unpublished changes"), - format!( - "discard unpublished changes and checkout new revision" - ), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - "back".to_string(), - ], - )?; - c.succeeds_with(1, false, Some(0))?; - - p.expect_end_eventually_and_print()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }); - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn out_reflects_second_choice_discarding_old_and_applying_new() - -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - amend_last_commit(&test_repo, "add ammended-commit.md")?; - test_repo.checkout("main")?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - p.expect("you have an amended/rebase version the proposal that is unpublished\r\n")?; - p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?; - p.expect("to view the latest proposal but retain your changes:\r\n")?; - p.expect(" 1) create a new branch off the tip commit of this one to store your changes\r\n")?; - p.expect(" 2) run `ngit list` and checkout the latest published version of this proposal\r\n")?; - p.expect("if you are confident in your changes consider running `ngit push --force`\r\n")?; - - let mut c = p.expect_choice( - "", - vec![ - format!("checkout local branch with unpublished changes"), - format!( - "discard unpublished changes and checkout new revision" - ), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - "back".to_string(), - ], - )?; - c.succeeds_with(1, false, Some(1))?; - p.expect_end_with("checked out latest version of proposal (2 ahead 0 behind 'main'), replacing unpublished version (2 ahead 0 behind 'main')\r\n")?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn second_choice_discarded_unpublished_commits_and_checked_out_latest_revision() - -> Result<()> { - let (originating_repo, test_repo) = prep_and_run().await?; - println!("test_dir: {:?}", test_repo.dir); - assert_eq!( - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_1 - )?)?, - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, - ); - Ok(()) - } - } - - mod when_local_commits_on_uptodate_proposal { - use super::*; - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn( - move || -> Result<(GitTestRepo, GitTestRepo)> { - let (originating_repo, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - // add another commit (so we have a local branch 1 ahead) - std::fs::write( - test_repo.dir.join("ammended-commit.md"), - "some content", - )?; - test_repo.stage_and_commit("add ammended-commit.md")?; - test_repo.checkout("main")?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - p.expect( - "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", - )?; - - let mut c = p.expect_choice( - "", - vec![ - format!("checkout proposal branch with 1 unpublished commits"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect("checked out proposal branch with 1 unpublished commits (3 ahead 0 behind 'main')\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }, - ); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - // add another commit (so we have a local branch 1 ahead) - std::fs::write( - test_repo.dir.join("ammended-commit.md"), - "some content", - )?; - test_repo.stage_and_commit("add ammended-commit.md")?; - test_repo.checkout("main")?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - p.expect( - "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", - )?; - - let mut c = p.expect_choice( - "", - vec![ - format!("checkout proposal branch with 1 unpublished commits"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect("checked out proposal branch with 1 unpublished commits (3 ahead 0 behind 'main')\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_checked_out() -> Result<()> { - let (_, test_repo) = prep_and_run().await?; - assert_eq!( - get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, - test_repo.get_checked_out_branch_name()?, - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn didnt_overwrite_local_appendments() -> Result<()> { - let (originating_repo, test_repo) = prep_and_run().await?; - assert_ne!( - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_1 - )?)?, - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, - ); - Ok(()) - } - } - - mod when_latest_revision_rebases_branch { - - use tokio::task::JoinHandle; - - use super::*; - - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle: JoinHandle> = - tokio::task::spawn_blocking(move || { - let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; - test_repo.checkout("main")?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; - let mut c = p.expect_choice( - "", - vec![ - format!("checkout and overwrite existing proposal branch"), - format!("checkout existing outdated proposal branch"), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.await??; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle: JoinHandle> = tokio::task::spawn_blocking( - move || { - let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; - test_repo.checkout("main")?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - let mut c = p.expect_choice( - "all proposals", - vec![ - format!("\"{PROPOSAL_TITLE_3}\""), - format!("\"{PROPOSAL_TITLE_2}\""), - format!("\"{PROPOSAL_TITLE_1}\""), - ], - )?; - c.succeeds_with(2, true, None)?; - p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; - let mut c = p.expect_choice( - "", - vec![ - format!("checkout and overwrite existing proposal branch"), - format!("checkout existing outdated proposal branch"), - format!("apply to current branch with `git am`"), - format!("download to ./patches"), - format!("back"), - ], - )?; - c.succeeds_with(0, false, Some(0))?; - p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }, - ); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.await??; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_checked_out() -> Result<()> { - let (_, test_repo) = prep_and_run().await?; - assert_eq!( - get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, - test_repo.get_checked_out_branch_name()?, - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> { - let (originating_repo, test_repo) = prep_and_run().await?; - assert_eq!( - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_1 - )?)?, - ); - Ok(()) - } - } - } - } -} diff --git a/tests/ngit/login.rs b/tests/ngit/login.rs deleted file mode 100644 index 477b25b..0000000 --- a/tests/ngit/login.rs +++ /dev/null @@ -1,1186 +0,0 @@ -use anyhow::Result; -use git::GitTestRepo; -use serial_test::serial; -use test_utils::*; - -static EXPECTED_NSEC_PROMPT: &str = "login with nsec / bunker url / nostr address"; -static EXPECTED_LOCAL_REPOSITORY_PROMPT: &str = "just for this repository?"; -static EXPECTED_REQUIRE_PASSWORD_PROMPT: &str = "require password?"; -static EXPECTED_SET_PASSWORD_PROMPT: &str = "encrypt with password"; -static EXPECTED_SET_PASSWORD_CONFIRM_PROMPT: &str = "confirm password"; - -fn standard_first_time_login_encrypting_nsec() -> Result { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); - - p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? - .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? - .succeeds_with(TEST_PASSWORD)?; - - p.expect_end_eventually()?; - Ok(p) -} - -fn expect_qr_prompt_opt_for_other_methods(p: &mut CliTester) -> Result<()> { - p.expect_eventually("scan QR or paste into remote signer")?; - p.expect_eventually("\r\n")?; - p.expect_eventually("login with nsec / bunker url / nostr address instead")?; - p.expect_eventually("\r\n")?; - p.send_line("")?; - // p.expect_eventually("\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r")?; - // p.expect_eventually("\r\r\r\r\r\r\r\r\r\r\r\r\r")?; - - Ok(()) -} -mod with_relays { - use anyhow::Ok; - use futures::join; - use test_utils::relay::{shutdown_relay, ListenerReqFunc, Relay}; - - use super::*; - - mod when_user_relay_list_aligns_with_fallback_relays { - // this simplifies testing - use super::*; - - mod when_first_time_login { - use super::*; - - // falls_back_to_fallback_relays - this is implict in the tests - - mod dislays_logged_in_with_correct_name { - - use super::*; - - 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), - ); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); - expect_qr_prompt_opt_for_other_methods(&mut p)?; - p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(false))?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect("searching for profile...\r\n")?; - - 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(()) - } - - async fn run_test_displays_fallback_to_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<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); - - expect_qr_prompt_opt_for_other_methods(&mut p)?; - p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(false))?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect("searching for profile...\r\n")?; - - p.expect("cannot extract account name from account metadata...\r\n")?; - - p.expect_end_with( - format!("logged in as {}\r\n", TEST_KEY_1_NPUB).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(()) - } - - #[tokio::test] - #[serial] - async fn when_latest_metadata_and_relay_list_on_all_relays() -> Result<()> { - 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(()) - }), - ) - .await - } - - mod poorly_quality_metadata_event { - use super::*; - - #[tokio::test] - #[serial] - async fn when_metadata_contains_only_display_name() -> Result<()> { - run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - nostr::event::EventBuilder::metadata( - &nostr::Metadata::new().display_name("fred"), - ) - .to_event(&TEST_KEY_1_KEYS) - .unwrap(), - generate_test_key_1_relay_list_event_same_as_fallback(), - ], - )?; - Ok(()) - }), - None, - ) - .await - } - - #[tokio::test] - #[serial] - async fn when_metadata_contains_only_displayname() -> Result<()> { - println!( - "displayName: {}", - nostr::Metadata::new() - .custom_field("displayName", "fred") - .custom - .get("displayName") - .unwrap() - ); - println!( - "name: {}", - nostr::Metadata::new().name("fred").name.unwrap() - ); - - run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - nostr::event::EventBuilder::metadata( - &nostr::Metadata::new() - .custom_field("displayName", "fred"), - ) - .to_event(&TEST_KEY_1_KEYS) - .unwrap(), - generate_test_key_1_relay_list_event_same_as_fallback(), - ], - )?; - Ok(()) - }), - None, - ) - .await - } - - #[tokio::test] - #[serial] - async fn displays_npub_when_metadata_contains_no_name_displayname_or_display_name() - -> Result<()> { - run_test_displays_fallback_to_npub( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![ - nostr::event::EventBuilder::metadata( - &nostr::Metadata::new().about("other info in metadata"), - ) - .to_event(&TEST_KEY_1_KEYS) - .unwrap(), - generate_test_key_1_relay_list_event_same_as_fallback(), - ], - )?; - Ok(()) - }), - None, - ) - .await - } - } - - #[tokio::test] - #[serial] - async fn when_latest_metadata_and_relay_list_on_some_relays_but_others_have_none() - -> Result<()> { - 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, - ) - .await - } - - #[tokio::test] - #[serial] - async fn when_latest_metadata_only_on_relay_and_relay_list_on_another() -> Result<()> - { - 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(()) - }), - ) - .await - } - - #[tokio::test] - #[serial] - async fn when_some_relays_return_old_metadata_event() -> Result<()> { - 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(()) - }), - ) - .await - } - - #[tokio::test] - #[serial] - async fn when_some_relays_return_other_users_metadata() -> Result<()> { - 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(()) - }), - ) - .await - } - - #[tokio::test] - #[serial] - async fn when_some_relays_return_other_event_kinds() -> Result<()> { - run_test_displays_correct_name( - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - let event = generate_test_key_1_kind_event(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(()) - }), - ) - .await - } - - mod when_specifying_command_line_nsec_only { - use super::*; - - #[tokio::test] - #[serial] - async fn displays_correct_name() -> Result<()> { - 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, - ) - .await - } - 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<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - ["login", "--nsec", TEST_KEY_1_NSEC], - ); - - p.expect("saved login details to local git config\r\n")?; - - p.expect("searching for profile...\r\n")?; - - 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_password_only { - use super::*; - - #[tokio::test] - #[serial] - async fn displays_correct_name() -> Result<()> { - 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, - ) - .await - } - 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<()> { - let test_repo = GitTestRepo::default(); - CliTester::new_from_dir( - &test_repo.dir, - [ - "login", - "--offline", - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - ], - ) - .expect_end_eventually()?; - - let mut p = CliTester::new_from_dir( - &test_repo.dir, - ["login", "--password", TEST_PASSWORD], - ); - - p.expect("searching for profile...\r\n")?; - - 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_and_password { - use super::*; - - #[tokio::test] - #[serial] - async fn displays_correct_name() -> Result<()> { - 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_same_as_fallback(), - ], - )?; - Ok(()) - }), - None, - ).await - } - 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<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "login", - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - ], - ); - - p.expect("saved login details to local git config\r\n")?; - - p.expect("searching for profile...\r\n")?; - - 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::*; - - #[tokio::test] - #[serial] - async fn warm_user_and_displays_npub() -> Result<()> { - run_test_when_no_metadata_found_warns_user_and_uses_npub(None, None).await - } - - 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<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); - - expect_qr_prompt_opt_for_other_methods(&mut p)?; - p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(false))?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect("searching for profile...\r\n")?; - - p.expect("cannot find profile...\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 { - use super::*; - - #[tokio::test] - #[serial] - async fn warm_user_and_displays_name() -> Result<()> { - 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, - ) - .await - } - - async fn run_test_when_no_relay_list_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<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); - - expect_qr_prompt_opt_for_other_methods(&mut p)?; - p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(false))?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect("searching for profile...\r\n")?; - - 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] { - 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_second_time_login_and_details_already_fetched { - use super::*; - - mod uses_cache_and_stores_and_retrieves_ncryptsec_from_local_git_config { - use super::*; - - #[tokio::test] - #[serial] - async fn dislays_logged_in_with_correct_name() -> Result<()> { - 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(()) - }, - )) - .await - } - 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<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "login", - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - ], - ); - - p.expect("saved login details to local git config\r\n")?; - - 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_from_dir( - &test_repo.dir, - ["login", "--password", TEST_PASSWORD], - ); - - p.expect_end_eventually_with("logged in as fred\r\n")?; - - Ok(()) - }); - - // launch relay - let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); - - cli_tester_handle.join().unwrap()?; - - Ok(()) - } - } - } - } - 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::*; - async fn run_test_displays_correct_name( - relay_listener1: Option>, - relay_listener2: Option>, - ) -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55) = ( - Relay::new(8051, None, relay_listener1), - 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<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); - - expect_qr_prompt_opt_for_other_methods(&mut p)?; - p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(false))?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect("searching for profile...\r\n")?; - - p.expect_end_with("logged in as fred\r\n")?; - for p in [51, 52, 53, 55] { - shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - ); - - cli_tester_handle.join().unwrap()?; - Ok(()) - } - - /// this also tests that additional relays are queried - #[tokio::test] - #[serial] - async fn displays_correct_name() -> Result<()> { - 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_old("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(()) - }), - ) - .await - } - } - } -} - -/// using the offline flag simplifies the test. relay interaction is tested -/// seperately -mod with_offline_flag { - use super::*; - mod when_first_time_login { - use super::*; - - #[test] - fn prompts_for_nsec_and_password() -> Result<()> { - standard_first_time_login_encrypting_nsec()?; - Ok(()) - } - - #[test] - fn succeeds_with_text_logged_in_as_npub() -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); - - p.expect_input(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? - .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? - .succeeds_with(TEST_PASSWORD)?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) - } - - #[test] - fn succeeds_with_hex_secret_key_in_place_of_nsec() -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); - - p.expect_input(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_SK_HEX)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? - .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? - .succeeds_with(TEST_PASSWORD)?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) - } - - mod when_invalid_nsec { - use super::*; - - #[test] - fn prompts_for_nsec_until_valid() -> Result<()> { - let invalid_nsec_response = - "invalid. try again with nostr address / bunker uri / nsec"; - - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); - - p.expect_input(EXPECTED_NSEC_PROMPT)? - // this behaviour is intentional. rejecting the response with dialoguer - // hides the original input from the user so they - // cannot see the mistake they made. - .succeeds_with(TEST_INVALID_NSEC)?; - - p.expect_input(invalid_nsec_response)? - .succeeds_with(TEST_INVALID_NSEC)?; - - p.expect_input(invalid_nsec_response)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? - .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? - .succeeds_with(TEST_PASSWORD)?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) - } - } - } - - mod when_called_with_nsec_parameter_only { - use super::*; - - #[test] - fn valid_nsec_param_succeeds_without_prompts() -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - ["login", "--offline", "--nsec", TEST_KEY_1_NSEC], - ); - - p.expect("saved login details to local git config\r\n")?; - - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) - } - - #[test] - fn forgets_identity() -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - ["login", "--offline", "--nsec", TEST_KEY_1_NSEC], - ); - - p.expect("saved login details to local git config\r\n")?; - - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?; - - p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); - - p.expect_input(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.exit() - } - - mod when_logging_in_as_different_nsec { - use super::*; - - #[test] - fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> { - standard_first_time_login_encrypting_nsec()?.exit()?; - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - ["login", "--offline", "--nsec", TEST_KEY_2_NSEC], - ); - - p.expect("saved login details to local git config\r\n")?; - - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str()) - } - } - #[test] - fn invalid_nsec_param_fails_without_prompts() -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - ["login", "--offline", "--nsec", TEST_INVALID_NSEC], - ); - - p.expect_end_with( - "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n", - ) - } - } - - mod when_called_with_nsec_and_password_parameter { - use super::*; - - #[test] - fn valid_nsec_param_succeeds_without_prompts() -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "login", - "--offline", - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - ], - ); - p.expect("saved login details to local git config\r\n")?; - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) - } - - #[test] - fn parameters_can_be_called_globally() -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "login", - "--offline", - ], - ); - p.expect("saved login details to local git config\r\n")?; - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) - } - - mod when_logging_in_as_different_nsec { - use super::*; - - #[test] - fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> { - standard_first_time_login_encrypting_nsec()?.exit()?; - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "login", - "--offline", - "--nsec", - TEST_KEY_2_NSEC, - "--password", - TEST_PASSWORD, - ], - ); - p.expect("saved login details to local git config\r\n")?; - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str()) - } - } - - mod when_provided_with_new_password { - use super::*; - - #[test] - fn password_changes() -> Result<()> { - standard_first_time_login_encrypting_nsec()?.exit()?; - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "login", - "--offline", - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_INVALID_PASSWORD, - ], - ); - p.expect("saved login details to local git config\r\n")?; - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?; - - CliTester::new_from_dir( - &test_repo.dir, - ["--password", TEST_INVALID_PASSWORD, "login", "--offline"], - ) - .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) - } - } - - #[test] - fn invalid_nsec_param_fails_without_prompts() -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "login", - "--offline", - "--nsec", - TEST_INVALID_NSEC, - "--password", - TEST_PASSWORD, - ], - ); - p.expect_end_with( - "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n", - ) - } - } - - mod when_weak_password { - use super::*; - - #[test] - #[serial] - // combined into a single test as it is computationally expensive to run - fn warns_it_might_take_a_few_seconds_then_succeeds_then_second_login_prompts_for_password_then_warns_again_then_succeeds() - -> Result<()> { - let test_repo = GitTestRepo::default(); - let mut p = - CliTester::new_with_timeout_from_dir(15000, &test_repo.dir, ["login", "--offline"]); - - p.expect_input(EXPECTED_NSEC_PROMPT)? - .succeeds_with(TEST_KEY_1_NSEC)?; - - p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? - .succeeds_with(Some(true))?; - - p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? - .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? - .succeeds_with(TEST_WEAK_PASSWORD)?; - - p.expect("this may take a few seconds...\r\n")?; - - p.expect("saved login details to local git config\r\n")?; - - p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) - - // commented out as 'login' command now assumes you want to - // login as a new user - // p = CliTester::new_with_timeout(10000, ["login", - // "--offline"]); - - // p.expect(format!("login as {}\r\n", - // TEST_KEY_1_NPUB).as_str())? - // .expect_password(EXPECTED_PASSWORD_PROMPT)? - // .succeeds_with(TEST_WEAK_PASSWORD)?; - - // p.expect("this may take a few seconds...\r\n")?; - - // p.expect_end_with(format!("logged in as {}\r\n", - // TEST_KEY_1_NPUB).as_str()) - } - } -} diff --git a/tests/ngit/main.rs b/tests/ngit/main.rs deleted file mode 100644 index fe852df..0000000 --- a/tests/ngit/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod init; -mod list; -mod login; -mod pull; -mod push; -mod send; diff --git a/tests/ngit/pull.rs b/tests/ngit/pull.rs deleted file mode 100644 index 6637859..0000000 --- a/tests/ngit/pull.rs +++ /dev/null @@ -1,615 +0,0 @@ -use anyhow::Result; -use futures::join; -use serial_test::serial; -use test_utils::{git::GitTestRepo, relay::Relay, *}; - -mod when_main_is_checked_out { - use super::*; - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn cli_show_error() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - cli_tester_create_proposals()?; - - let test_repo = create_repo_with_proposal_branch_pulled_and_checkedout(1)?; - - test_repo.checkout("main")?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect("Error: checkout a branch associated with a proposal first\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } -} - -mod when_branch_doesnt_exist { - use super::*; - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn cli_show_error() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - cli_tester_create_proposals()?; - - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - - test_repo.create_branch("random-name")?; - test_repo.checkout("random-name")?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect("Error: cannot find proposal that matches the current branch name\r\n")?; - - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } -} - -mod when_branch_is_checked_out { - use super::*; - - mod when_branch_is_up_to_date { - use super::*; - - mod cli_prompts { - use super::*; - #[tokio::test] - #[serial] - async fn cli_show_up_to_date() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect("branch already up-to-date\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - } - - mod when_branch_is_behind { - use super::*; - - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = - std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { - let (originating_repo, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - let branch_name = - remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( - &test_repo, - )?; - test_repo.checkout(&branch_name)?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect_end_eventually()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn cli_applied_1_commit() -> Result<()> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = - std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { - let (originating_repo, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - let branch_name = - remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( - &test_repo, - )?; - test_repo.checkout(&branch_name)?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect_end_with("applied 1 new commits\r\n")?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { - let (originating_repo, test_repo) = prep_and_run().await?; - assert_eq!( - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_1 - )?)?, - ); - Ok(()) - } - } - - mod when_latest_proposal_amended_locally { - use super::*; - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn cli_output_correct() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - amend_last_commit(&test_repo, "add ammended-commit.md")?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect( - "you have an amended/rebase version the proposal that is unpublished\r\n", - )?; - p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?; - p.expect("to view the latest proposal but retain your changes:\r\n")?; - p.expect(" 1) create a new branch off the tip commit of this one to store your changes\r\n")?; - p.expect(" 2) run `ngit list` and checkout the latest published version of this proposal\r\n")?; - p.expect("if you are confident in your changes consider running `ngit push --force`\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - } - - mod when_local_commits_on_uptodate_proposal { - use super::*; - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn( - move || -> Result<(GitTestRepo, GitTestRepo)> { - let (originating_repo, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - // add another commit (so we have a local branch 1 ahead) - std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?; - test_repo.stage_and_commit("add ammended-commit.md")?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect("local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }, - ); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - // add another commit (so we have a local branch 1 ahead) - std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?; - test_repo.stage_and_commit("add ammended-commit.md")?; - - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect("local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn didnt_overwrite_local_appendments() -> Result<()> { - let (originating_repo, test_repo) = prep_and_run().await?; - assert_ne!( - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_1 - )?)?, - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, - ); - Ok(()) - } - } - mod when_latest_event_rebases_branch { - use tokio::task::JoinHandle; - - use super::*; - - async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle: JoinHandle> = - tokio::task::spawn_blocking(move || { - let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect_end_eventually_and_print()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.await??; - - Ok(res) - } - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn prompts_to_choose_from_proposal_titles() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle: JoinHandle> = tokio::task::spawn_blocking( - move || { - let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect_end_with("pulled new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }, - ); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.await??; - println!("{:?}", r55.events); - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> { - let (originating_repo, test_repo) = prep_and_run().await?; - assert_eq!( - originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, - test_repo.get_tip_of_local_branch(&get_proposal_branch_name( - &test_repo, - FEATURE_BRANCH_NAME_1 - )?)?, - ); - Ok(()) - } - } -} diff --git a/tests/ngit/push.rs b/tests/ngit/push.rs deleted file mode 100644 index eb452cd..0000000 --- a/tests/ngit/push.rs +++ /dev/null @@ -1,531 +0,0 @@ -use anyhow::Result; -use futures::join; -use serial_test::serial; -use test_utils::{git::GitTestRepo, relay::Relay, *}; - -mod when_main_is_checked_out { - use super::*; - - #[test] - fn cli_returns_error() -> Result<()> { - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", false)?; - test_repo.checkout("main")?; - let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); - p.expect("Error: checkout a branch associated with a proposal first\r\n")?; - p.expect_end()?; - Ok(()) - } -} - -mod when_proposal_isnt_associated_with_branch_name { - use super::*; - - mod cli_prompts { - - use super::*; - - #[tokio::test] - #[serial] - async fn cli_show_error() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - cli_tester_create_proposals()?; - - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - - test_repo.create_branch("random-name")?; - test_repo.checkout("random-name")?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect_end_with( - "Error: cannot find proposal that matches the current branch name\r\n", - )?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } -} - -mod when_branch_is_checked_out { - use super::*; - - mod when_branch_is_up_to_date { - use super::*; - - mod cli_prompts { - use super::*; - #[tokio::test] - #[serial] - async fn cli_show_up_to_date() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect_end_with("Error: proposal already up-to-date with local branch\r\n")?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - } - - mod when_branch_is_behind { - use super::*; - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn cli_show_proposal_ahead_error() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - let branch_name = - remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( - &test_repo, - )?; - - test_repo.checkout(&branch_name)?; - // run test - let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect("Error: proposal is ahead of local branch\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - } - - mod when_branch_is_ahead { - use super::*; - - mod cli_prompts { - use test_utils::relay::expect_send_with_progress; - - use super::*; - - #[tokio::test] - #[serial] - async fn cli_applied_1_commit() -> Result<()> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = - std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { - let (originating_repo, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - // add another commit (so we have an ammened local branch) - std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?; - test_repo.stage_and_commit("add ammended-commit.md")?; - - // run test - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "push", - ], - ); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect( - "1 commits ahead. preparing to create creating patch events.\r\n", - )?; - p.expect("logged in as fred\r\n")?; - p.expect("pushing 1 commits\r\n")?; - - expect_send_with_progress( - &mut p, - vec![ - (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), - (" [my-relay] ws://localhost:8053", true, ""), - (" [repo-relay] ws://localhost:8056", true, ""), - (" [default] ws://localhost:8051", true, ""), - (" [default] ws://localhost:8052", true, ""), - ], - 1, - )?; - p.expect_eventually("pushed 1 commits\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok((originating_repo, test_repo)) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - - Ok(()) - } - } - - async fn prep_and_run() -> Result<(GitTestRepo, Vec)> { - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result { - let (_, test_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - - // add another commit (so we have an ammened local branch) - std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?; - test_repo.stage_and_commit("add ammended-commit.md")?; - - // run test - - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "push", - ], - ); - p.expect_end_eventually()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(test_repo) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - let res = cli_tester_handle.join().unwrap()?; - - Ok((res, r55.events.clone())) - } - #[tokio::test] - #[serial] - async fn commits_issued_as_patch_event() -> Result<()> { - let (test_repo, r55_events) = prep_and_run().await?; - - let commit_id = test_repo - .get_tip_of_local_branch(&test_repo.get_checked_out_branch_name()?)? - .to_string(); - assert!(r55_events.iter().any(|e| { - e.tags - .iter() - .any(|t| t.as_vec()[0].eq("commit") && t.as_vec()[1].eq(&commit_id)) - })); - Ok(()) - } - } - - mod when_branch_has_been_rebased { - use super::*; - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn cli_shows_unpublished_rebase_error() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, tmp_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - let branch_name = tmp_repo.get_checked_out_branch_name()?; - - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - - // simulate rebase - std::fs::write(test_repo.dir.join("amazing.md"), "some content")?; - test_repo.stage_and_commit("commit for rebasing on top of")?; - create_and_populate_branch(&test_repo, &branch_name, "a", true)?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); - // p.expect_end_eventually_and_print()?; - - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect( - "Error: local unpublished proposal has been rebased. consider force pushing\r\n", - )?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - mod with_force_flag { - use super::*; - - mod cli_prompts { - use super::*; - - #[tokio::test] - #[serial] - async fn cli_shows_revision_sent() -> Result<()> { - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new(8051, None, None), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new(8055, None, None), - Relay::new(8056, None, None), - ); - - r51.events.push(generate_test_key_1_relay_list_event()); - r51.events.push(generate_test_key_1_metadata_event("fred")); - r51.events.push(generate_repo_ref_event()); - - r55.events.push(generate_repo_ref_event()); - r55.events.push(generate_test_key_1_metadata_event("fred")); - r55.events.push(generate_test_key_1_relay_list_event()); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let (_, tmp_repo) = - create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; - let branch_name = tmp_repo.get_checked_out_branch_name()?; - - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - - // simulate rebase - std::fs::write(test_repo.dir.join("amazing.md"), "some content")?; - test_repo.stage_and_commit("commit for rebasing on top of")?; - create_and_populate_branch(&test_repo, &branch_name, "a", false)?; - let mut p = CliTester::new_from_dir( - &test_repo.dir, - [ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "push", - "--force", - ], - ); - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // some updates listed here - p.expect("preparing to force push proposal revision...\r\n")?; - // standard output from `ngit send` - p.expect("creating proposal revision for: ")?; - // proposal id will be printed in this gap - p.expect_eventually("\r\n")?; - p.expect("creating proposal from 2 commits:\r\n")?; - p.expect("355bdf1 add a4.md\r\n")?; - p.expect("dbd1115 add a3.md\r\n")?; - p.expect("logged in as fred\r\n")?; - p.expect("posting 2 patches without a covering letter...\r\n")?; - - relay::expect_send_with_progress( - &mut p, - vec![ - (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), - (" [my-relay] ws://localhost:8053", true, ""), - (" [repo-relay] ws://localhost:8056", true, ""), - (" [default] ws://localhost:8051", true, ""), - (" [default] ws://localhost:8052", true, ""), - ], - 2, - )?; - // end standard `ngit send output` - p.expect_after_whitespace("force pushed proposal revision\r\n")?; - p.expect_end()?; - - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - } - } -} diff --git a/tests/ngit/send.rs b/tests/ngit/send.rs deleted file mode 100644 index 2aad232..0000000 --- a/tests/ngit/send.rs +++ /dev/null @@ -1,1901 +0,0 @@ -use anyhow::Result; -use futures::join; -use nostr_sdk::Kind; -use serial_test::serial; -use test_utils::{git::GitTestRepo, relay::Relay, *}; - -#[test] -fn when_no_main_or_master_branch_return_error() -> Result<()> { - let test_repo = GitTestRepo::new("notmain")?; - test_repo.populate()?; - let mut p = CliTester::new_from_dir(&test_repo.dir, ["send"]); - p.expect("Error: the default branches (main or master) do not exist")?; - Ok(()) -} - -// TODO when commits ahead of origin/master - test ask to proceed -// TODO when commits in origin/master - test ask to proceed -mod when_commits_behind_ask_to_proceed { - use super::*; - - fn prep_test_repo() -> Result { - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - // create feature branch with 2 commit ahead - test_repo.create_branch("feature")?; - test_repo.checkout("feature")?; - std::fs::write(test_repo.dir.join("t3.md"), "some content")?; - test_repo.stage_and_commit("add t3.md")?; - std::fs::write(test_repo.dir.join("t4.md"), "some content")?; - test_repo.stage_and_commit("add t4.md")?; - // checkout main and add 1 commit - test_repo.checkout("main")?; - std::fs::write(test_repo.dir.join("t5.md"), "some content")?; - test_repo.stage_and_commit("add t5.md")?; - // checkout feature branch - test_repo.checkout("feature")?; - Ok(test_repo) - } - - fn expect_confirm_prompt(p: &mut CliTester) -> Result { - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // may be 'no updates' or some updates - p.expect("creating proposal from 2 commits:\r\n")?; - p.expect("fe973a8 add t4.md\r\n")?; - p.expect("232efb3 add t3.md\r\n")?; - p.expect_confirm( - "proposal is 1 behind 'main'. consider rebasing before submission. proceed anyway?", - Some(false), - ) - } - - #[test] - fn asked_with_default_no() -> Result<()> { - let test_repo = prep_test_repo()?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]); - expect_confirm_prompt(&mut p)?; - p.exit()?; - Ok(()) - } - - #[test] - fn when_response_is_false_aborts() -> Result<()> { - let test_repo = prep_test_repo()?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]); - - expect_confirm_prompt(&mut p)?.succeeds_with(Some(false))?; - - p.expect_end_with("Error: aborting so commits can be rebased\r\n")?; - - Ok(()) - } - #[test] - #[serial] - fn when_response_is_true_proceeds() -> Result<()> { - let test_repo = prep_test_repo()?; - - let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]); - expect_confirm_prompt(&mut p)?.succeeds_with(Some(true))?; - p.expect("? include cover letter")?; - p.exit()?; - Ok(()) - } -} - -fn is_cover_letter(event: &nostr::Event) -> bool { - event.kind.eq(&Kind::GitPatch) - && event - .tags() - .iter() - .any(|t| t.as_vec()[1].eq("cover-letter")) -} - -fn is_patch(event: &nostr::Event) -> bool { - event.kind.eq(&Kind::GitPatch) - && !event - .tags() - .iter() - .any(|t| t.as_vec()[1].eq("cover-letter")) -} - -fn prep_git_repo() -> Result { - let test_repo = GitTestRepo::default(); - test_repo.populate()?; - // create feature branch with 2 commit ahead - test_repo.create_branch("feature")?; - test_repo.checkout("feature")?; - std::fs::write(test_repo.dir.join("t3.md"), "some content")?; - test_repo.stage_and_commit("add t3.md")?; - std::fs::write(test_repo.dir.join("t4.md"), "some content")?; - test_repo.stage_and_commit("add t4.md")?; - Ok(test_repo) -} - -fn cli_tester_create_proposal(git_repo: &GitTestRepo, include_cover_letter: bool) -> CliTester { - let mut args = vec![ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "send", - "HEAD~2", - ]; - if include_cover_letter { - for arg in [ - "--title", - "exampletitle", - "--description", - "exampledescription", - ] { - args.push(arg); - } - } else { - args.push("--no-cover-letter"); - } - CliTester::new_from_dir(&git_repo.dir, args) -} - -fn expect_msgs_first(p: &mut CliTester, include_cover_letter: bool) -> Result<()> { - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // may be 'no updates' or some updates - p.expect("creating proposal from 2 commits:\r\n")?; - p.expect("fe973a8 add t4.md\r\n")?; - p.expect("232efb3 add t3.md\r\n")?; - // sometimes there will be a 'searching for profile...' msg - p.expect_eventually("logged in as fred\r\n")?; - p.expect(format!( - "posting 2 patches {} a covering letter...\r\n", - if include_cover_letter { - "with" - } else { - "without" - } - ))?; - Ok(()) -} - -fn expect_msgs_after(p: &mut CliTester) -> Result<()> { - p.expect_after_whitespace("view in gitworkshop.dev: https://gitworkshop.dev/repo")?; - p.expect_eventually("\r\n")?; - p.expect("view in another client: https://njump.me/")?; - p.expect_eventually("\r\n")?; - Ok(()) -} - -async fn prep_run_create_proposal( - include_cover_letter: bool, -) -> Result<( - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, -)> { - let git_repo = prep_git_repo()?; - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo, include_cover_letter); - p.expect_end_eventually()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok((r51, r52, r53, r55, r56)) -} - -mod when_cover_letter_details_specified_with_range_of_head_2_sends_cover_letter_and_2_patches_to_3_relays { - - use super::*; - #[tokio::test] - #[serial] - async fn only_1_cover_letter_event_sent_to_each_relay() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - assert_eq!( - relay.events.iter().filter(|e| is_cover_letter(e)).count(), - 1, - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn only_1_cover_letter_event_sent_to_user_relays() -> Result<()> { - let (_, _, r53, r55, _) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55] { - assert_eq!( - relay.events.iter().filter(|e| is_cover_letter(e)).count(), - 1, - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn only_1_cover_letter_event_sent_to_repo_relays() -> Result<()> { - let (_, _, _, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r55, &r56] { - assert_eq!( - relay.events.iter().filter(|e| is_cover_letter(e)).count(), - 1 - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn only_1_cover_letter_event_sent_to_fallback_relays() -> Result<()> { - let (r51, r52, _, _, _) = prep_run_create_proposal(true).await?; - for relay in [&r51, &r52] { - assert_eq!( - relay.events.iter().filter(|e| is_cover_letter(e)).count(), - 1, - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn only_2_patch_kind_events_sent_to_each_relay() -> Result<()> { - let (r51, r52, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r51, &r52, &r53, &r55, &r56] { - assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2,); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn patch_content_contains_patch_in_email_format_with_patch_series_numbers() -> Result<()> - { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let patch_events: Vec<&nostr::Event> = - relay.events.iter().filter(|e| is_patch(e)).collect(); - - assert_eq!( - patch_events[1].content, - "\ - From fe973a840fba2a8ab37dd505c154854a69a6505c Mon Sep 17 00:00:00 2001\n\ - From: Joe Bloggs \n\ - Date: Thu, 1 Jan 1970 00:00:00 +0000\n\ - Subject: [PATCH 2/2] add t4.md\n\ - \n\ - ---\n \ - t4.md | 1 +\n \ - 1 file changed, 1 insertion(+)\n \ - create mode 100644 t4.md\n\ - \n\ - diff --git a/t4.md b/t4.md\n\ - new file mode 100644\n\ - index 0000000..f0eec86\n\ - --- /dev/null\n\ - +++ b/t4.md\n\ - @@ -0,0 +1 @@\n\ - +some content\n\\ \ - No newline at end of file\n\ - --\n\ - libgit2 1.8.1\n\ - \n\ - ", - ); - assert_eq!( - patch_events[0].content, - "\ - From 232efb37ebc67692c9e9ff58b83c0d3d63971a0a Mon Sep 17 00:00:00 2001\n\ - From: Joe Bloggs \n\ - Date: Thu, 1 Jan 1970 00:00:00 +0000\n\ - Subject: [PATCH 1/2] add t3.md\n\ - \n\ - ---\n \ - t3.md | 1 +\n \ - 1 file changed, 1 insertion(+)\n \ - create mode 100644 t3.md\n\ - \n\ - diff --git a/t3.md b/t3.md\n\ - new file mode 100644\n\ - index 0000000..f0eec86\n\ - --- /dev/null\n\ - +++ b/t3.md\n\ - @@ -0,0 +1 @@\n\ - +some content\n\\ \ - No newline at end of file\n\ - --\n\ - libgit2 1.8.1\n\ - \n\ - ", - ); - } - Ok(()) - } - - mod cover_letter_tags { - use super::*; - - #[tokio::test] - #[serial] - async fn root_commit_as_r() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - - assert_eq!( - cover_letter_event - .tags() - .iter() - .find(|t| t.as_vec()[0].eq("r")) - .unwrap() - .as_vec()[1], - "9ee507fc4357d7ee16a5d8901bedcd103f23c17d" - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn a_tag_for_repo_event_of_each_maintainer() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!( - cover_letter_event - .tags() - .iter() - .any(|t| t.as_vec()[0].eq("a") - && t.as_vec()[1].eq(&format!( - "{}:{TEST_KEY_1_PUBKEY_HEX}:{}", - Kind::GitRepoAnnouncement, - generate_repo_ref_event().identifier().unwrap() - ))) - ); - assert!( - cover_letter_event - .tags() - .iter() - .any(|t| t.as_vec()[0].eq("a") - && t.as_vec()[1].eq(&format!( - "{}:{TEST_KEY_2_PUBKEY_HEX}:{}", - Kind::GitRepoAnnouncement, - generate_repo_ref_event().identifier().unwrap() - ))) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn p_tags_for_maintainers() -> Result<()> { - let event = generate_repo_ref_event(); - let maintainers = &event - .tags() - .iter() - .find(|t| t.as_vec()[0].eq(&"maintainers")) - .unwrap() - .as_vec()[1..]; - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - for m in maintainers { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!( - cover_letter_event - .tags() - .iter() - .any(|t| { t.as_vec()[0].eq("p") && t.as_vec()[1].eq(m) }) - ); - } - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn t_tag_cover_letter() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!( - cover_letter_event - .tags() - .iter() - .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"cover-letter") }) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn t_tag_root() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!( - cover_letter_event - .tags() - .iter() - .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"root") }) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn cover_letter_tags_branch_name() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - - // branch-name tag - assert_eq!( - cover_letter_event - .tags() - .iter() - .find(|t| t.as_vec()[0].eq("branch-name")) - .unwrap() - .as_vec()[1], - "feature" - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn cover_letter_tags_alt() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - - // branch-name tag - assert_eq!( - cover_letter_event - .tags() - .iter() - .find(|t| t.as_vec()[0].eq("alt")) - .unwrap() - .as_vec()[1], - "git patch cover letter: exampletitle" - ); - } - Ok(()) - } - } - - mod patch_tags { - use super::*; - - async fn prep() -> Result { - let (_, _, r53, _, _) = prep_run_create_proposal(true).await?; - Ok(r53.events.iter().find(|e| is_patch(e)).unwrap().clone()) - } - - #[tokio::test] - #[serial] - async fn commit_and_commit_r() -> Result<()> { - static COMMIT_ID: &str = "232efb37ebc67692c9e9ff58b83c0d3d63971a0a"; - let most_recent_patch = prep().await?; - assert!( - most_recent_patch - .tags - .iter() - .any(|t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq(COMMIT_ID)) - ); - assert!( - most_recent_patch - .tags - .iter() - .any(|t| t.as_vec()[0].eq("commit") && t.as_vec()[1].eq(COMMIT_ID)) - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn parent_commit() -> Result<()> { - // commit parent 'r' and 'parent-commit' tag - static COMMIT_PARENT_ID: &str = "431b84edc0d2fa118d63faa3c2db9c73d630a5ae"; - let most_recent_patch = prep().await?; - assert_eq!( - most_recent_patch - .tags - .iter() - .find(|t| t.as_vec()[0].eq("parent-commit")) - .unwrap() - .as_vec()[1], - COMMIT_PARENT_ID, - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn root_commit_as_r() -> Result<()> { - assert!(prep().await?.tags.iter().any(|t| t.as_vec()[0].eq("r") - && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn p_tags_for_maintainers() -> Result<()> { - let event = generate_repo_ref_event(); - let maintainers = &event - .tags() - .iter() - .find(|t| t.as_vec()[0].eq(&"maintainers")) - .unwrap() - .as_vec()[1..]; - for m in maintainers { - assert!( - prep() - .await? - .tags() - .iter() - .any(|t| { t.as_vec()[0].eq("p") && t.as_vec()[1].eq(m) }) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn a_tag_for_repo_event_of_each_maintainer() -> Result<()> { - assert!(prep().await?.tags.iter().any(|t| { - t.as_vec()[0].eq("a") - && t.as_vec()[1].eq(&format!( - "{}:{TEST_KEY_1_PUBKEY_HEX}:{}", - Kind::GitRepoAnnouncement, - generate_repo_ref_event().identifier().unwrap() - )) - })); - assert!(prep().await?.tags.iter().any(|t| { - t.as_vec()[0].eq("a") - && t.as_vec()[1].eq(&format!( - "{}:{TEST_KEY_2_PUBKEY_HEX}:{}", - Kind::GitRepoAnnouncement, - generate_repo_ref_event().identifier().unwrap() - )) - })); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn description_with_commit_message() -> Result<()> { - assert_eq!( - prep() - .await? - .tags - .iter() - .find(|t| t.as_vec()[0].eq("description")) - .unwrap() - .as_vec()[1], - "add t3.md" - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn commit_author() -> Result<()> { - assert_eq!( - prep() - .await? - .tags - .iter() - .find(|t| t.as_vec()[0].eq("author")) - .unwrap() - .as_vec(), - vec!["author", "Joe Bloggs", "joe.bloggs@pm.me", "0", "0"], - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn commit_committer() -> Result<()> { - assert_eq!( - prep() - .await? - .tags - .iter() - .find(|t| t.as_vec()[0].eq("committer")) - .unwrap() - .as_vec(), - vec!["committer", "Joe Bloggs", "joe.bloggs@pm.me", "0", "0"], - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn alt() -> Result<()> { - assert_eq!( - prep() - .await? - .tags - .iter() - .find(|t| t.as_vec()[0].eq("alt")) - .unwrap() - .as_vec(), - vec!["alt", "git patch: add t3.md"], - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn patch_tags_cover_letter_event_as_root() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let patch_events: Vec<&nostr::Event> = - relay.events.iter().filter(|e| is_patch(e)).collect(); - - let most_recent_patch = patch_events[0]; - let cover_letter_event = relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - - let root_event_tag = most_recent_patch - .tags - .iter() - .find(|t| { - t.as_vec()[0].eq("e") && t.as_vec().len().eq(&4) && t.as_vec()[3].eq("root") - }) - .unwrap(); - - assert_eq!( - root_event_tag.as_vec()[1], - cover_letter_event.id.to_string() - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn second_patch_tags_first_with_reply() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; - for relay in [&r53, &r55, &r56] { - let patch_events = relay - .events - .iter() - .filter(|e| is_patch(e)) - .collect::>(); - assert_eq!( - patch_events[1] - .tags() - .iter() - .find(|t| t.as_vec()[0].eq("e") - && t.as_vec().len().eq(&4) - && t.as_vec()[3].eq("reply")) - .unwrap() - .as_vec()[1], - patch_events[0].id.to_string(), - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn no_t_root_tag() -> Result<()> { - assert!( - !prep() - .await? - .tags - .iter() - .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root")) - ); - Ok(()) - } - } - mod cli_ouput { - use super::*; - - #[tokio::test] - #[serial] - async fn check_cli_output() -> Result<()> { - let git_repo = prep_git_repo()?; - - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo, true); - expect_msgs_first(&mut p, true)?; - relay::expect_send_with_progress( - &mut p, - vec![ - (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), - (" [my-relay] ws://localhost:8053", true, ""), - (" [repo-relay] ws://localhost:8056", true, ""), - (" [default] ws://localhost:8051", true, ""), - (" [default] ws://localhost:8052", true, ""), - ], - 3, - )?; - expect_msgs_after(&mut p)?; - p.expect_end_with_whitespace()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - - mod first_event_rejected_by_1_relay { - use super::*; - - mod only_first_rejected_event_sent_to_relay { - use super::*; - - #[tokio::test] - #[serial] - async fn only_first_rejected_event_sent_to_relay() -> Result<()> { - let git_repo = prep_git_repo()?; - - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event()], - )?; - Ok(()) - }), - ), - Relay::new( - 8056, - Some(&|relay, client_id, event| -> Result<()> { - relay.respond_ok(client_id, event, Some("Payment Required"))?; - Ok(()) - }), - None, - ), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo, true); - p.expect_end_eventually()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - - assert_eq!(r56.events.len(), 1); - - Ok(()) - } - } - - mod cli_show_rejection_with_comment { - use super::*; - - #[tokio::test] - #[serial] - async fn cli_show_rejection_with_comment() -> Result<()> { - let git_repo = prep_git_repo()?; - - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event()], - )?; - Ok(()) - }), - ), - Relay::new( - 8056, - Some(&|relay, client_id, event| -> Result<()> { - relay.respond_ok(client_id, event, Some("Payment Required"))?; - Ok(()) - }), - None, - ), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo, true); - expect_msgs_first(&mut p, true)?; - relay::expect_send_with_progress( - &mut p, - vec![ - (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), - (" [my-relay] ws://localhost:8053", true, ""), - ( - " [repo-relay] ws://localhost:8056", - false, - "error: Payment Required", - ), - (" [default] ws://localhost:8051", true, ""), - (" [default] ws://localhost:8052", true, ""), - ], - 3, - )?; - expect_msgs_after(&mut p)?; - p.expect_end_with_whitespace()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - } -} - -mod when_no_cover_letter_flag_set_with_range_of_head_2_sends_2_patches_without_cover_letter { - use super::*; - - mod cli_ouput { - use super::*; - - #[tokio::test] - #[serial] - async fn check_cli_output() -> Result<()> { - let git_repo = prep_git_repo()?; - - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo, false); - - expect_msgs_first(&mut p, false)?; - relay::expect_send_with_progress( - &mut p, - vec![ - (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), - (" [my-relay] ws://localhost:8053", true, ""), - (" [repo-relay] ws://localhost:8056", true, ""), - (" [default] ws://localhost:8051", true, ""), - (" [default] ws://localhost:8052", true, ""), - ], - 2, - )?; - expect_msgs_after(&mut p)?; - p.expect_end_with_whitespace()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn no_cover_letter_event() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; - for relay in [&r53, &r55, &r56] { - assert_eq!( - relay.events.iter().filter(|e| is_cover_letter(e)).count(), - 0, - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn two_patch_events() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; - for relay in [&r53, &r55, &r56] { - assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2); - } - Ok(()) - } - - #[tokio::test] - #[serial] - // TODO check this is the ancestor - async fn first_patch_with_root_t_tag() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; - for relay in [&r53, &r55, &r56] { - let patch_events = relay - .events - .iter() - .filter(|e| is_patch(e)) - .collect::>(); - - // first patch tagged as root - assert!( - patch_events[0] - .tags() - .iter() - .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root")) - ); - // second patch not tagged as root - assert!( - !patch_events[1] - .tags() - .iter() - .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root")) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn root_patch_tags_branch_name() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; - for relay in [&r53, &r55, &r56] { - let patch_events = relay - .events - .iter() - .filter(|e| is_patch(e)) - .collect::>(); - - // branch-name tag - assert_eq!( - patch_events[0] - .tags() - .iter() - .find(|t| t.as_vec()[0].eq("branch-name")) - .unwrap() - .as_vec()[1], - "feature" - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn second_patch_lists_first_as_root() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; - for relay in [&r53, &r55, &r56] { - let patch_events = relay - .events - .iter() - .filter(|e| is_patch(e)) - .collect::>(); - - assert_eq!( - patch_events[1] - .tags() - .iter() - .find(|t| t.as_vec()[0].eq("e") - && t.as_vec().len().eq(&4) - && t.as_vec()[3].eq("root")) - .unwrap() - .as_vec()[1], - patch_events[0].id.to_string(), - ); - } - Ok(()) - } -} - -mod when_range_ommited_prompts_for_selection_defaulting_ahead_of_main { - use super::*; - - fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester { - let args = vec![ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "send", - "--no-cover-letter", - ]; - CliTester::new_from_dir(&git_repo.dir, args) - } - fn expect_msgs_first(p: &mut CliTester) -> Result<()> { - p.expect("fetching updates...\r\n")?; - p.expect_eventually("\r\n")?; // may be 'no updates' or some updates - let mut selector = p.expect_multi_select( - "select commits for proposal", - vec![ - "(Joe Bloggs) add t4.md [feature] fe973a8".to_string(), - "(Joe Bloggs) add t3.md 232efb3".to_string(), - "(Joe Bloggs) add t2.md [main] 431b84e".to_string(), - "(Joe Bloggs) add t1.md af474d8".to_string(), - "(Joe Bloggs) Initial commit 9ee507f".to_string(), - ], - )?; - selector.succeeds_with(vec![0, 1], false, vec![0, 1])?; - p.expect("creating proposal from 2 commits:\r\n")?; - p.expect("fe973a8 add t4.md\r\n")?; - p.expect("232efb3 add t3.md\r\n")?; - p.expect("searching for profile...\r\n")?; - p.expect("logged in as fred\r\n")?; - p.expect("posting 2 patches without a covering letter...\r\n")?; - Ok(()) - } - async fn prep_run_create_proposal() -> Result<( - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - )> { - let git_repo = prep_git_repo()?; - - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo); - expect_msgs_first(&mut p)?; - p.expect_end_eventually()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok((r51, r52, r53, r55, r56)) - } - mod cli_ouput { - use super::*; - - #[tokio::test] - #[serial] - async fn check_cli_output() -> Result<()> { - let git_repo = prep_git_repo()?; - - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo); - - expect_msgs_first(&mut p)?; - relay::expect_send_with_progress( - &mut p, - vec![ - (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), - (" [my-relay] ws://localhost:8053", true, ""), - (" [repo-relay] ws://localhost:8056", true, ""), - (" [default] ws://localhost:8051", true, ""), - (" [default] ws://localhost:8052", true, ""), - ], - 2, - )?; - expect_msgs_after(&mut p)?; - p.expect_end_with_whitespace()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn two_patch_events_sent() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; - for relay in [&r53, &r55, &r56] { - assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2); - } - Ok(()) - } -} - -mod root_proposal_specified_using_in_reply_to_with_range_of_head_2_and_cover_letter_details_specified { - - use nostr::ToBech32; - - use super::*; - - fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester { - let proposal_root_bech32 = get_pretend_proposal_root_event().id.to_bech32().unwrap(); - let args = vec![ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "send", - "HEAD~2", - "--in-reply-to", - &proposal_root_bech32, - "--title", - "exampletitle", - "--description", - "exampledescription", - ]; - CliTester::new_from_dir(&git_repo.dir, args) - } - fn expect_msgs_first(p: &mut CliTester, include_cover_letter: bool) -> Result<()> { - p.expect("fetching updates...\r\n")?; - p.expect("updates: 1 new maintainer, 1 announcement update, 1 proposal\r\n")?; - let proposal_root_bech32 = get_pretend_proposal_root_event().id.to_bech32().unwrap(); - p.expect(format!( - "creating proposal revision for: {}\r\n", - proposal_root_bech32, - ))?; - p.expect("creating proposal from 2 commits:\r\n")?; - p.expect("fe973a8 add t4.md\r\n")?; - p.expect("232efb3 add t3.md\r\n")?; - p.expect("logged in as fred\r\n")?; - p.expect(format!( - "posting 2 patches {} a covering letter...\r\n", - if include_cover_letter { - "with" - } else { - "without" - } - ))?; - Ok(()) - } - - async fn prep_run_create_proposal() -> Result<( - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - )> { - let git_repo = prep_git_repo()?; - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(), - get_pretend_proposal_root_event(), - ], - )?; - Ok(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event(), get_pretend_proposal_root_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo); - p.expect_end_eventually()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok((r51, r52, r53, r55, r56)) - } - mod cli_ouput { - use super::*; - - #[tokio::test] - #[serial] - async fn check_cli_output() -> Result<()> { - let git_repo = prep_git_repo()?; - - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(), - get_pretend_proposal_root_event(), - ], - )?; - Ok(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event(), get_pretend_proposal_root_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo); - expect_msgs_first(&mut p, true)?; - relay::expect_send_with_progress( - &mut p, - vec![ - (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), - (" [my-relay] ws://localhost:8053", true, ""), - (" [repo-relay] ws://localhost:8056", true, ""), - (" [default] ws://localhost:8051", true, ""), - (" [default] ws://localhost:8052", true, ""), - ], - 3, - )?; - p.expect_end_with_whitespace()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok(()) - } - } - - mod cover_letter_tags { - use super::*; - - #[tokio::test] - #[serial] - async fn t_tag_root() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!( - cover_letter_event - .tags() - .iter() - .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"root") }) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn t_tag_revision_root() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!( - cover_letter_event - .tags() - .iter() - .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"revision-root") }) - ); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn e_tag_in_reply_to_event_as_reply() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert_eq!( - cover_letter_event - .tags() - .iter() - .find(|t| { - t.as_vec()[0].eq("e") - && t.as_vec().len().eq(&4) - && t.as_vec()[3].eq("reply") - }) - .unwrap() - .as_vec()[1], - // id of state nevent - "431e58eb8e1b4e20292d1d5bbe81d5cfb042e1bc165de32eddfdd52245a4cce4", - ); - } - Ok(()) - } - } - - #[tokio::test] - #[serial] - async fn patch_tags_cover_letter_event_as_root() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; - for relay in [&r53, &r55, &r56] { - let patch_events: Vec<&nostr::Event> = - relay.events.iter().filter(|e| is_patch(e)).collect(); - - let cover_letter_event = relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - - for patch in patch_events { - assert_eq!( - patch - .tags - .iter() - .find(|t| { - t.as_vec()[0].eq("e") - && t.as_vec().len().eq(&4) - && t.as_vec()[3].eq("root") - }) - .unwrap() - .as_vec()[1], - cover_letter_event.id.to_string() - ); - } - } - Ok(()) - } -} - -mod in_reply_to_mentions_issue { - use nostr::ToBech32; - - use super::*; - pub fn get_pretend_issue_event() -> nostr::Event { - serde_json::from_str(r#"{"created_at":1709286372,"content":"please provide feedback\nthis is an example ngit issue to demonstrate gitworkshop.dev.\n\nplease provide feedback with in reply to this issue or by creating a new issue.","tags":[["r","26689f97810fc656c7134c76e2a37d33b2e40ce7"],["a","30617:a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d:ngit","wss://relay.damus.io","root"],["p","a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d"]],"kind":1621,"pubkey":"a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d","id":"e944765d625ae7323d080da0df069c726a0e5490a17b452f854d85e18f781588","sig":"a1af9e89a35f1f7ef93e3de33986bd86cb7c4d7d9abb233c0c6405f32b5788171e47f84551afe8515b3107d12f03472721ea784b8791ff3f25e66a3169a54c20"}"#).unwrap() - } - - fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester { - let issue_bech32 = get_pretend_issue_event().id.to_bech32().unwrap(); - let args = vec![ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "send", - "HEAD~2", - "--in-reply-to", - &issue_bech32, - // "note1a9z8vhtzttnny0ggpksd7p5uwf4qu4ys59a52tu9fkz7rrmczkyqc46ngg", - "--title", - "exampletitle", - "--description", - "exampledescription", - ]; - CliTester::new_from_dir(&git_repo.dir, args) - } - - async fn prep_run_create_proposal() -> Result<( - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - )> { - let git_repo = prep_git_repo()?; - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(), - get_pretend_issue_event(), - ], - )?; - Ok(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event(), get_pretend_issue_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo); - p.expect_end_eventually()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok((r51, r52, r53, r55, r56)) - } - - #[tokio::test] - #[serial] - async fn issue_event_mentioned_in_tagged_cover_letter() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!(cover_letter_event.tags().iter().any(|t| { - t.as_vec()[0].eq("e") - && t.as_vec()[1].eq(&get_pretend_issue_event().id.to_hex()) - && t.as_vec()[3].eq(&"mention") - })); - } - Ok(()) - } - - #[tokio::test] - #[serial] - async fn isnt_tagged_as_revision() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!( - !cover_letter_event - .tags() - .iter() - .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"revision-root") }) - ); - } - Ok(()) - } -} -mod in_reply_to_mentions_npub_and_nprofile_which_get_mentioned_in_proposal_root { - - use super::*; - - fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester { - let args = vec![ - "--nsec", - TEST_KEY_1_NSEC, - "--password", - TEST_PASSWORD, - "--disable-cli-spinners", - "send", - "HEAD~2", - "--in-reply-to", - // nsec1q3c5xnsm5m4wgsrhwnz04p0d5mevkryyggqgdpa9jwulpq9gldhswgtxvq - "npub1knxeegzqg0xqflsryvg7l7x7nmpe7kd7pl7zazug0a7t99tdsphszuyapx", - // nsec1nx5ulvcndhcuu8k6q8fenw50l6y75sec7pj8vr0r68l6a44w3lqspvj02k - "nprofile1qqsvru3yqrec6dxjn06f8cjh79jcu9wyaxu4y6v47yzpsx7vjm4xcuc33z2n3", - "--title", - "exampletitle", - "--description", - "exampledescription", - ]; - CliTester::new_from_dir(&git_repo.dir, args) - } - - async fn prep_run_create_proposal() -> Result<( - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - Relay<'static>, - )> { - let git_repo = prep_git_repo()?; - // fallback (51,52) user write (53, 55) repo (55, 56) - let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( - Relay::new( - 8051, - None, - 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(()) - }), - ), - Relay::new(8052, None, None), - Relay::new(8053, None, None), - Relay::new( - 8055, - None, - Some(&|relay, client_id, subscription_id, _| -> Result<()> { - relay.respond_events( - client_id, - &subscription_id, - &vec![generate_repo_ref_event()], - )?; - Ok(()) - }), - ), - Relay::new(8056, None, None), - ); - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_create_proposal(&git_repo); - p.expect_end_eventually()?; - for p in [51, 52, 53, 55, 56] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relay - let _ = join!( - r51.listen_until_close(), - r52.listen_until_close(), - r53.listen_until_close(), - r55.listen_until_close(), - r56.listen_until_close(), - ); - cli_tester_handle.join().unwrap()?; - Ok((r51, r52, r53, r55, r56)) - } - - #[tokio::test] - #[serial] - async fn npub_and_nprofile_mentioned_in_tagged_cover_letter() -> Result<()> { - let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; - for relay in [&r53, &r55, &r56] { - let cover_letter_event: &nostr::Event = - relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); - assert!(cover_letter_event.tags().iter().any(|t| { - t.as_vec()[0].eq("p") - && t.as_vec()[1].eq(&nostr::Keys::parse( - "nsec1q3c5xnsm5m4wgsrhwnz04p0d5mevkryyggqgdpa9jwulpq9gldhswgtxvq", - ) - .unwrap() - .public_key() - .to_hex()) - })); - assert!(cover_letter_event.tags().iter().any(|t| { - t.as_vec()[0].eq("p") - && t.as_vec()[1].eq(&nostr::Keys::parse( - "nsec1nx5ulvcndhcuu8k6q8fenw50l6y75sec7pj8vr0r68l6a44w3lqspvj02k", - ) - .unwrap() - .public_key() - .to_hex()) - })); - } - Ok(()) - } -} diff --git a/tests/ngit_init.rs b/tests/ngit_init.rs new file mode 100644 index 0000000..c8390e3 --- /dev/null +++ b/tests/ngit_init.rs @@ -0,0 +1,706 @@ +use anyhow::Result; +use nostr_sdk::Kind; +use serial_test::serial; +use test_utils::{git::GitTestRepo, *}; + +fn expect_msgs_first(p: &mut CliTester) -> Result<()> { + p.expect("searching for profile...\r\n")?; + p.expect("logged in as fred\r\n")?; + // // p.expect("searching for existing claims on repository...\r\n")?; + p.expect("publishing repostory reference...\r\n")?; + Ok(()) +} + +fn expect_msgs_after(p: &mut CliTester) -> Result<()> { + p.expect_after_whitespace("maintainers.yaml created. commit and push.\r\n")?; + p.expect( + "this optional file helps in identifying who the maintainers are over time through the commit history\r\n", + )?; + Ok(()) +} + +fn get_cli_args() -> Vec<&'static str> { + vec![ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "init", + "--title", + "example-name", + "--identifier", + "example-identifier", + "--description", + "example-description", + "--web", + "https://exampleproject.xyz", + "https://gitworkshop.dev/123", + "--relays", + "ws://localhost:8055", + "ws://localhost:8056", + "--clone-url", + "https://git.myhosting.com/my-repo.git", + "--earliest-unique-commit", + "9ee507fc4357d7ee16a5d8901bedcd103f23c17d", + "--other-maintainers", + TEST_KEY_1_NPUB, + ] +} + +mod when_repo_not_previously_claimed { + use super::*; + + mod when_repo_relays_specified_as_arguments { + use futures::join; + use test_utils::relay::Relay; + + use super::*; + + fn prep_git_repo() -> Result { + let test_repo = GitTestRepo::without_repo_in_git_config(); + test_repo.populate()?; + test_repo.add_remote("origin", "https://localhost:1000")?; + Ok(test_repo) + } + + fn cli_tester_init(git_repo: &GitTestRepo) -> CliTester { + CliTester::new_from_dir(&git_repo.dir, get_cli_args()) + } + + async fn prep_run_init() -> Result<( + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + )> { + let git_repo = prep_git_repo()?; + // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + Relay::new(8057, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_init(&git_repo); + p.expect_end_eventually()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + r57.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok((r51, r52, r53, r55, r56, r57)) + } + + mod sent_to_correct_relays { + + use super::*; + + #[tokio::test] + #[serial] + async fn only_1_repository_kind_event_sent_to_user_relays() -> Result<()> { + let (_, _, r53, r55, _, _) = prep_run_init().await?; + for relay in [&r53, &r55] { + assert_eq!( + relay + .events + .iter() + .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .count(), + 1, + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn only_1_repository_kind_event_sent_to_specified_repo_relays() -> Result<()> { + let (_, _, _, r55, r56, _) = prep_run_init().await?; + for relay in [&r55, &r56] { + assert_eq!( + relay + .events + .iter() + .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .count(), + 1, + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn only_1_repository_kind_event_sent_to_fallback_relays() -> Result<()> { + let (r51, r52, _, _, _, _) = prep_run_init().await?; + for relay in [&r51, &r52] { + assert_eq!( + relay + .events + .iter() + .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .count(), + 1, + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn only_1_repository_kind_event_sent_to_blaster_relays() -> Result<()> { + let (_, _, _, _, _, r57) = prep_run_init().await?; + assert_eq!( + r57.events + .iter() + .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .count(), + 1, + ); + Ok(()) + } + } + + mod yaml_file { + use std::{fs, io::Read}; + + use super::*; + + async fn async_run_test() -> Result<()> { + let git_repo = prep_git_repo()?; + // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + Relay::new(8057, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_init(&git_repo); + p.expect_end_eventually()?; + + let yaml_path = git_repo.dir.join("maintainers.yaml"); + + assert!(yaml_path.exists()); + + let mut file = fs::File::open(yaml_path).expect("no such file"); + let mut file_contents = "".to_string(); + let _ = file.read_to_string(&mut file_contents); + + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + assert_eq!( + file_contents, + format!( + "\ + identifier: example-identifier\n\ + maintainers:\n\ + - {TEST_KEY_1_NPUB}\n\ + relays:\n\ + - ws://localhost:8055\n\ + - ws://localhost:8056\n\ + " + ), + ); + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + r57.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + + #[tokio::test] + #[serial] + async fn contains_identifier_maintainers_and_relays() -> Result<()> { + async_run_test().await + } + mod updates_existing_with_missing_identifier { + use std::io::Write; + + use super::*; + async fn async_run_test() -> Result<()> { + let git_repo = prep_git_repo()?; + // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + Relay::new(8057, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let yaml_path = git_repo.dir.join("maintainers.yaml"); + let mut file = std::fs::File::create(&yaml_path) + .expect("cannot create maintainers.yaml file"); + write!( + file, + "\ + maintainers:\n\ + - {TEST_KEY_1_NPUB}\n\ + relays:\n\ + - ws://localhost:8055\n\ + - ws://localhost:8056\n\ + " + )?; + + let mut p = cli_tester_init(&git_repo); + p.expect_end_eventually()?; + + assert!(yaml_path.exists()); + + let mut file = fs::File::open(yaml_path).expect("no such file"); + let mut file_contents = "".to_string(); + let _ = file.read_to_string(&mut file_contents); + + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + assert_eq!( + file_contents, + format!( + "\ + identifier: example-identifier\n\ + maintainers:\n\ + - {TEST_KEY_1_NPUB}\n\ + relays:\n\ + - ws://localhost:8055\n\ + - ws://localhost:8056\n\ + " + ), + ); + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + r57.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + + #[tokio::test] + #[serial] + async fn adds_missing_identifier() -> Result<()> { + async_run_test().await + } + } + } + + mod git_config_updated { + + use nostr::nips::nip01::Coordinate; + use nostr_sdk::ToBech32; + + use super::*; + + async fn async_run_test() -> Result<()> { + let git_repo = prep_git_repo()?; + // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + Relay::new(8057, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_init(&git_repo); + p.expect_end_eventually()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + assert_eq!( + git_repo + .git_repo + .config()? + .get_entry("nostr.repo")? + .value() + .unwrap(), + Coordinate { + kind: nostr_sdk::Kind::GitRepoAnnouncement, + identifier: "example-identifier".to_string(), + public_key: TEST_KEY_1_KEYS.public_key(), + relays: vec![], + } + .to_bech32()?, + ); + + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + r57.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + + #[tokio::test] + #[serial] + async fn with_nostr_repo_set_to_user_and_identifer_naddr() -> Result<()> { + async_run_test().await?; + Ok(()) + } + } + + mod tags_as_specified_in_args { + use super::*; + + #[tokio::test] + #[serial] + async fn d_replaceable_event_identifier() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + + assert!( + event.tags.iter().any( + |t| t.as_vec()[0].eq("d") && t.as_vec()[1].eq("example-identifier") + ) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn earliest_unique_commit_as_reference_with_euc_marker() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + + assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("r") + && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d") + && t.as_vec()[2].eq("euc"))); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn name() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + + assert!( + event + .tags + .iter() + .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("example-name")) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn alt() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + + assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("alt") + && t.as_vec()[1].eq("git repository: example-name"))); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn description() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + + assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("description") + && t.as_vec()[1].eq("example-description"))); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn git_server() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + + assert!( + event.tags.iter().any(|t| t.as_vec()[0].eq("clone") + && t.as_vec()[1].eq("https://git.myhosting.com/my-repo.git")) /* todo check it defaults to origin */ + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn relays() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + let relays_tag = event + .tags + .iter() + .find(|t| t.as_vec()[0].eq("relays")) + .unwrap() + .as_vec(); + assert_eq!(relays_tag[1], "ws://localhost:8055",); + assert_eq!(relays_tag[2], "ws://localhost:8056",); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn web() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + let web_tag = event + .tags + .iter() + .find(|t| t.as_vec()[0].eq("web")) + .unwrap() + .as_vec(); + assert_eq!(web_tag[1], "https://exampleproject.xyz",); + assert_eq!(web_tag[2], "https://gitworkshop.dev/123",); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn maintainers() -> Result<()> { + let (_, _, r53, r55, r56, r57) = prep_run_init().await?; + for relay in [&r53, &r55, &r56, &r57] { + let event: &nostr::Event = relay + .events + .iter() + .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) + .unwrap(); + let maintainers_tag = event + .tags + .iter() + .find(|t| t.as_vec()[0].eq("maintainers")) + .unwrap() + .as_vec(); + assert_eq!(maintainers_tag[1], TEST_KEY_1_KEYS.public_key().to_string()); + } + Ok(()) + } + } + + mod cli_ouput { + use super::*; + + #[tokio::test] + #[serial] + async fn check_cli_output() -> Result<()> { + let git_repo = prep_git_repo()?; + + // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + Relay::new(8057, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_init(&git_repo); + expect_msgs_first(&mut p)?; + relay::expect_send_with_progress( + &mut p, + vec![ + (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), + (" [my-relay] ws://localhost:8053", true, ""), + (" [repo-relay] ws://localhost:8056", true, ""), + (" [default] ws://localhost:8051", true, ""), + (" [default] ws://localhost:8052", true, ""), + (" [default] ws://localhost:8057", true, ""), + ], + 1, + )?; + expect_msgs_after(&mut p)?; + p.expect_end()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + r57.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + } + // TODO: cli caputuring input +} +// TODO: when_updating_existing_repoistory correct defaults are used diff --git a/tests/ngit_list.rs b/tests/ngit_list.rs new file mode 100644 index 0000000..26cf717 --- /dev/null +++ b/tests/ngit_list.rs @@ -0,0 +1,1549 @@ +use anyhow::Result; +use futures::join; +use serial_test::serial; +use test_utils::{git::GitTestRepo, relay::Relay, *}; + +async fn prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout( + proposal_number: u16, +) -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { + let (originating_repo, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(proposal_number)?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok(res) +} + +mod cannot_find_repo_event { + use super::*; + mod cli_prompts { + use nostr::{nips::nip01::Coordinate, ToBech32}; + + use super::*; + async fn run_async_repo_event_ref_needed(invalid_input: bool, naddr: bool) -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + + r55.events.push(generate_test_key_1_relay_list_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + + let repo_event = generate_repo_ref_event(); + r56.events.push(repo_event.clone()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let test_repo = GitTestRepo::without_repo_in_git_config(); + test_repo.populate()?; + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect( + "hint: https://gitworkshop.dev/repos lists repositories and their naddr\r\n", + )?; + if invalid_input { + let mut input = p.expect_input("repository naddr")?; + input.succeeds_with("dfgvfvfzadvd")?; + p.expect("not a valid naddr\r\n")?; + let _ = p.expect_input("repository naddr")?; + p.exit()?; + } + if naddr { + let mut input = p.expect_input("repository naddr")?; + input.succeeds_with( + &Coordinate { + kind: nostr::Kind::GitRepoAnnouncement, + public_key: TEST_KEY_1_KEYS.public_key(), + identifier: repo_event.identifier().unwrap().to_string(), + relays: vec!["ws://localhost:8056".to_string()], + } + .to_bech32()?, + )?; + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?; + } + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + + #[tokio::test] + #[serial] + async fn warns_not_valid_input_and_asks_again() -> Result<()> { + run_async_repo_event_ref_needed(true, false).await + } + + #[tokio::test] + #[serial] + async fn finds_based_on_naddr_on_embeded_relay() -> Result<()> { + run_async_repo_event_ref_needed(false, true).await + } + } +} +mod when_main_branch_is_uptodate { + use super::*; + + mod when_proposal_branch_doesnt_exist { + use super::*; + + mod when_main_is_checked_out { + use super::*; + + mod when_first_proposal_selected { + use super::*; + + // TODO: test when other proposals with the same name but from other + // repositories are present on relays + + mod cli_prompts { + use super::*; + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + cli_tester_create_proposals()?; + + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!( + "create and checkout proposal branch (2 ahead 0 behind 'main')" ), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, None)?; + p.expect(format!( + "checked out proposal as 'pr/{}(", + FEATURE_BRANCH_NAME_1, + ))?; + p.expect_end_eventually_with(")' branch\r\n")?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_created_with_correct_name() -> Result<()> { + let (_, test_repo) = + prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?; + assert_eq!( + vec![ + "main", + &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, + ], + test_repo.get_local_branch_names()? + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_branch_checked_out() -> Result<()> { + let (_, test_repo) = + prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?; + assert_eq!( + get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, + test_repo.get_checked_out_branch_name()?, + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { + let (originating_repo, test_repo) = + prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?; + assert_eq!( + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_1 + )?)?, + ); + Ok(()) + } + } + mod when_third_proposal_selected { + use super::*; + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + cli_tester_create_proposals()?; + + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(0, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!( + "create and checkout proposal branch (2 ahead 0 behind 'main')" ), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect(format!( + "checked out proposal as 'pr/{}(", + FEATURE_BRANCH_NAME_3, + ))?; + p.expect_end_eventually_with(")' branch\r\n")?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_created_with_correct_name() -> Result<()> { + let (_, test_repo) = + prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?; + assert_eq!( + vec![ + "main", + &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_3)?, + ], + test_repo.get_local_branch_names()? + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_branch_checked_out() -> Result<()> { + let (_, test_repo) = + prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?; + assert_eq!( + get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_3)?, + test_repo.get_checked_out_branch_name()?, + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { + let (originating_repo, test_repo) = + prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?; + assert_eq!( + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_3)?, + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_3 + )?)?, + ); + Ok(()) + } + } + mod when_forth_proposal_has_no_cover_letter { + use super::*; + + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn( + move || -> Result<(GitTestRepo, GitTestRepo)> { + let originating_repo = cli_tester_create_proposals()?; + cli_tester_create_proposal( + &originating_repo, + FEATURE_BRANCH_NAME_4, + "d", + None, + None, + )?; + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("add d3.md"), // commit msg title + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(0, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!( + "create and checkout proposal branch (2 ahead 0 behind 'main')" ), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect_end_eventually_and_print()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }, + ); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let originating_repo = cli_tester_create_proposals()?; + std::thread::sleep(std::time::Duration::from_millis(1000)); + cli_tester_create_proposal( + &originating_repo, + FEATURE_BRANCH_NAME_4, + "d", + None, + None, + )?; + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("add d3.md"), // commit msg title + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(0, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!( + "create and checkout proposal branch (2 ahead 0 behind 'main')" ), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect(format!( + "checked out proposal as 'pr/{}(", + FEATURE_BRANCH_NAME_4, + ))?; + p.expect_end_eventually_with(")' branch\r\n")?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_created_with_correct_name() -> Result<()> { + let (_, test_repo) = prep_and_run().await?; + assert_eq!( + vec![ + "main", + &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_4)?, + ], + test_repo.get_local_branch_names()? + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_branch_checked_out() -> Result<()> { + let (_, test_repo) = prep_and_run().await?; + assert_eq!( + get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_4)?, + test_repo.get_checked_out_branch_name()?, + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { + let (originating_repo, test_repo) = prep_and_run().await?; + assert_eq!( + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_4)?, + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_4 + )?)?, + ); + Ok(()) + } + } + } + } + + mod when_proposal_branch_exists { + use super::*; + + mod when_main_is_checked_out { + use super::*; + + mod when_branch_is_up_to_date { + use super::*; + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn( + move || -> Result<(GitTestRepo, GitTestRepo)> { + let originating_repo = cli_tester_create_proposals()?; + + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + // create proposal branch + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!("create and checkout proposal branch (2 ahead 0 behind 'main')"), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect_end_eventually()?; + + test_repo.checkout("main")?; + // run test + p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!("checkout proposal branch (2 ahead 0 behind 'main')"), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect_end_eventually_and_print()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }, + ); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + cli_tester_create_proposals()?; + + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + // create proposal branch + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!("create and checkout proposal branch (2 ahead 0 behind 'main')"), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect_end_eventually()?; + + test_repo.checkout("main")?; + // run test + p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!("checkout proposal branch (2 ahead 0 behind 'main')"), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect(format!( + "checked out proposal as 'pr/{}(", + FEATURE_BRANCH_NAME_1, + ))?; + p.expect_end_eventually_with(")' branch\r\n")?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_checked_out() -> Result<()> { + let (_, test_repo) = prep_and_run().await?; + assert_eq!( + get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, + test_repo.get_checked_out_branch_name()?, + ); + Ok(()) + } + } + + mod when_branch_is_behind { + use super::*; + + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn( + move || -> Result<(GitTestRepo, GitTestRepo)> { + let (originating_repo, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( + &test_repo, + )?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!("checkout proposal branch and apply 1 appendments"), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect("checked out proposal branch and applied 1 appendments (2 ahead 0 behind 'main')\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }, + ); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( + &test_repo, + )?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + let mut c = p.expect_choice( + "", + vec![ + format!("checkout proposal branch and apply 1 appendments"), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect("checked out proposal branch and applied 1 appendments (2 ahead 0 behind 'main')\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_checked_out() -> Result<()> { + let (_, test_repo) = prep_and_run().await?; + assert_eq!( + get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, + test_repo.get_checked_out_branch_name()?, + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { + let (originating_repo, test_repo) = prep_and_run().await?; + assert_eq!( + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_1 + )?)?, + ); + Ok(()) + } + } + + mod when_latest_proposal_amended_locally { + // other rebase scenarios should work if this test passes + use super::*; + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = + std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { + let (originating_repo, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + let branch_name = test_repo.get_checked_out_branch_name()?; + + remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( + &test_repo, + )?; + + // add another commit (so we have an ammened local branch) + test_repo.checkout(&branch_name)?; + std::fs::write( + test_repo.dir.join("ammended-commit.md"), + "some content", + )?; + test_repo.stage_and_commit("add ammended-commit.md")?; + test_repo.checkout("main")?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + p.expect_eventually("--force`\r\n")?; + + let mut c = p.expect_choice( + "", + vec![ + format!("checkout local branch with unpublished changes"), + format!( + "discard unpublished changes and checkout new revision" + ), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + "back".to_string(), + ], + )?; + c.succeeds_with(1, false, Some(0))?; + + p.expect_end_eventually_and_print()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }); + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn out_reflects_second_choice_discarding_old_and_applying_new() + -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + amend_last_commit(&test_repo, "add ammended-commit.md")?; + test_repo.checkout("main")?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + p.expect("you have an amended/rebase version the proposal that is unpublished\r\n")?; + p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?; + p.expect("to view the latest proposal but retain your changes:\r\n")?; + p.expect(" 1) create a new branch off the tip commit of this one to store your changes\r\n")?; + p.expect(" 2) run `ngit list` and checkout the latest published version of this proposal\r\n")?; + p.expect("if you are confident in your changes consider running `ngit push --force`\r\n")?; + + let mut c = p.expect_choice( + "", + vec![ + format!("checkout local branch with unpublished changes"), + format!( + "discard unpublished changes and checkout new revision" + ), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + "back".to_string(), + ], + )?; + c.succeeds_with(1, false, Some(1))?; + p.expect_end_with("checked out latest version of proposal (2 ahead 0 behind 'main'), replacing unpublished version (2 ahead 0 behind 'main')\r\n")?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn second_choice_discarded_unpublished_commits_and_checked_out_latest_revision() + -> Result<()> { + let (originating_repo, test_repo) = prep_and_run().await?; + println!("test_dir: {:?}", test_repo.dir); + assert_eq!( + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_1 + )?)?, + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, + ); + Ok(()) + } + } + + mod when_local_commits_on_uptodate_proposal { + use super::*; + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn( + move || -> Result<(GitTestRepo, GitTestRepo)> { + let (originating_repo, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + // add another commit (so we have a local branch 1 ahead) + std::fs::write( + test_repo.dir.join("ammended-commit.md"), + "some content", + )?; + test_repo.stage_and_commit("add ammended-commit.md")?; + test_repo.checkout("main")?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + p.expect( + "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", + )?; + + let mut c = p.expect_choice( + "", + vec![ + format!("checkout proposal branch with 1 unpublished commits"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect("checked out proposal branch with 1 unpublished commits (3 ahead 0 behind 'main')\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }, + ); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + // add another commit (so we have a local branch 1 ahead) + std::fs::write( + test_repo.dir.join("ammended-commit.md"), + "some content", + )?; + test_repo.stage_and_commit("add ammended-commit.md")?; + test_repo.checkout("main")?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + p.expect( + "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", + )?; + + let mut c = p.expect_choice( + "", + vec![ + format!("checkout proposal branch with 1 unpublished commits"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect("checked out proposal branch with 1 unpublished commits (3 ahead 0 behind 'main')\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_checked_out() -> Result<()> { + let (_, test_repo) = prep_and_run().await?; + assert_eq!( + get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, + test_repo.get_checked_out_branch_name()?, + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn didnt_overwrite_local_appendments() -> Result<()> { + let (originating_repo, test_repo) = prep_and_run().await?; + assert_ne!( + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_1 + )?)?, + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, + ); + Ok(()) + } + } + + mod when_latest_revision_rebases_branch { + + use tokio::task::JoinHandle; + + use super::*; + + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle: JoinHandle> = + tokio::task::spawn_blocking(move || { + let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; + test_repo.checkout("main")?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; + let mut c = p.expect_choice( + "", + vec![ + format!("checkout and overwrite existing proposal branch"), + format!("checkout existing outdated proposal branch"), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.await??; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle: JoinHandle> = tokio::task::spawn_blocking( + move || { + let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; + test_repo.checkout("main")?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + let mut c = p.expect_choice( + "all proposals", + vec![ + format!("\"{PROPOSAL_TITLE_3}\""), + format!("\"{PROPOSAL_TITLE_2}\""), + format!("\"{PROPOSAL_TITLE_1}\""), + ], + )?; + c.succeeds_with(2, true, None)?; + p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; + let mut c = p.expect_choice( + "", + vec![ + format!("checkout and overwrite existing proposal branch"), + format!("checkout existing outdated proposal branch"), + format!("apply to current branch with `git am`"), + format!("download to ./patches"), + format!("back"), + ], + )?; + c.succeeds_with(0, false, Some(0))?; + p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }, + ); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.await??; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_checked_out() -> Result<()> { + let (_, test_repo) = prep_and_run().await?; + assert_eq!( + get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?, + test_repo.get_checked_out_branch_name()?, + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> { + let (originating_repo, test_repo) = prep_and_run().await?; + assert_eq!( + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_1 + )?)?, + ); + Ok(()) + } + } + } + } +} diff --git a/tests/ngit_login.rs b/tests/ngit_login.rs new file mode 100644 index 0000000..477b25b --- /dev/null +++ b/tests/ngit_login.rs @@ -0,0 +1,1186 @@ +use anyhow::Result; +use git::GitTestRepo; +use serial_test::serial; +use test_utils::*; + +static EXPECTED_NSEC_PROMPT: &str = "login with nsec / bunker url / nostr address"; +static EXPECTED_LOCAL_REPOSITORY_PROMPT: &str = "just for this repository?"; +static EXPECTED_REQUIRE_PASSWORD_PROMPT: &str = "require password?"; +static EXPECTED_SET_PASSWORD_PROMPT: &str = "encrypt with password"; +static EXPECTED_SET_PASSWORD_CONFIRM_PROMPT: &str = "confirm password"; + +fn standard_first_time_login_encrypting_nsec() -> Result { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); + + p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? + .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? + .succeeds_with(TEST_PASSWORD)?; + + p.expect_end_eventually()?; + Ok(p) +} + +fn expect_qr_prompt_opt_for_other_methods(p: &mut CliTester) -> Result<()> { + p.expect_eventually("scan QR or paste into remote signer")?; + p.expect_eventually("\r\n")?; + p.expect_eventually("login with nsec / bunker url / nostr address instead")?; + p.expect_eventually("\r\n")?; + p.send_line("")?; + // p.expect_eventually("\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r")?; + // p.expect_eventually("\r\r\r\r\r\r\r\r\r\r\r\r\r")?; + + Ok(()) +} +mod with_relays { + use anyhow::Ok; + use futures::join; + use test_utils::relay::{shutdown_relay, ListenerReqFunc, Relay}; + + use super::*; + + mod when_user_relay_list_aligns_with_fallback_relays { + // this simplifies testing + use super::*; + + mod when_first_time_login { + use super::*; + + // falls_back_to_fallback_relays - this is implict in the tests + + mod dislays_logged_in_with_correct_name { + + use super::*; + + 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), + ); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); + expect_qr_prompt_opt_for_other_methods(&mut p)?; + p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(false))?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect("searching for profile...\r\n")?; + + 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(()) + } + + async fn run_test_displays_fallback_to_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<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); + + expect_qr_prompt_opt_for_other_methods(&mut p)?; + p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(false))?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect("searching for profile...\r\n")?; + + p.expect("cannot extract account name from account metadata...\r\n")?; + + p.expect_end_with( + format!("logged in as {}\r\n", TEST_KEY_1_NPUB).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(()) + } + + #[tokio::test] + #[serial] + async fn when_latest_metadata_and_relay_list_on_all_relays() -> Result<()> { + 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(()) + }), + ) + .await + } + + mod poorly_quality_metadata_event { + use super::*; + + #[tokio::test] + #[serial] + async fn when_metadata_contains_only_display_name() -> Result<()> { + run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + nostr::event::EventBuilder::metadata( + &nostr::Metadata::new().display_name("fred"), + ) + .to_event(&TEST_KEY_1_KEYS) + .unwrap(), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + None, + ) + .await + } + + #[tokio::test] + #[serial] + async fn when_metadata_contains_only_displayname() -> Result<()> { + println!( + "displayName: {}", + nostr::Metadata::new() + .custom_field("displayName", "fred") + .custom + .get("displayName") + .unwrap() + ); + println!( + "name: {}", + nostr::Metadata::new().name("fred").name.unwrap() + ); + + run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + nostr::event::EventBuilder::metadata( + &nostr::Metadata::new() + .custom_field("displayName", "fred"), + ) + .to_event(&TEST_KEY_1_KEYS) + .unwrap(), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + None, + ) + .await + } + + #[tokio::test] + #[serial] + async fn displays_npub_when_metadata_contains_no_name_displayname_or_display_name() + -> Result<()> { + run_test_displays_fallback_to_npub( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![ + nostr::event::EventBuilder::metadata( + &nostr::Metadata::new().about("other info in metadata"), + ) + .to_event(&TEST_KEY_1_KEYS) + .unwrap(), + generate_test_key_1_relay_list_event_same_as_fallback(), + ], + )?; + Ok(()) + }), + None, + ) + .await + } + } + + #[tokio::test] + #[serial] + async fn when_latest_metadata_and_relay_list_on_some_relays_but_others_have_none() + -> Result<()> { + 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, + ) + .await + } + + #[tokio::test] + #[serial] + async fn when_latest_metadata_only_on_relay_and_relay_list_on_another() -> Result<()> + { + 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(()) + }), + ) + .await + } + + #[tokio::test] + #[serial] + async fn when_some_relays_return_old_metadata_event() -> Result<()> { + 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(()) + }), + ) + .await + } + + #[tokio::test] + #[serial] + async fn when_some_relays_return_other_users_metadata() -> Result<()> { + 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(()) + }), + ) + .await + } + + #[tokio::test] + #[serial] + async fn when_some_relays_return_other_event_kinds() -> Result<()> { + run_test_displays_correct_name( + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + let event = generate_test_key_1_kind_event(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(()) + }), + ) + .await + } + + mod when_specifying_command_line_nsec_only { + use super::*; + + #[tokio::test] + #[serial] + async fn displays_correct_name() -> Result<()> { + 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, + ) + .await + } + 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<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + ["login", "--nsec", TEST_KEY_1_NSEC], + ); + + p.expect("saved login details to local git config\r\n")?; + + p.expect("searching for profile...\r\n")?; + + 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_password_only { + use super::*; + + #[tokio::test] + #[serial] + async fn displays_correct_name() -> Result<()> { + 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, + ) + .await + } + 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<()> { + let test_repo = GitTestRepo::default(); + CliTester::new_from_dir( + &test_repo.dir, + [ + "login", + "--offline", + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + ], + ) + .expect_end_eventually()?; + + let mut p = CliTester::new_from_dir( + &test_repo.dir, + ["login", "--password", TEST_PASSWORD], + ); + + p.expect("searching for profile...\r\n")?; + + 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_and_password { + use super::*; + + #[tokio::test] + #[serial] + async fn displays_correct_name() -> Result<()> { + 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_same_as_fallback(), + ], + )?; + Ok(()) + }), + None, + ).await + } + 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<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "login", + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + ], + ); + + p.expect("saved login details to local git config\r\n")?; + + p.expect("searching for profile...\r\n")?; + + 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::*; + + #[tokio::test] + #[serial] + async fn warm_user_and_displays_npub() -> Result<()> { + run_test_when_no_metadata_found_warns_user_and_uses_npub(None, None).await + } + + 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<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); + + expect_qr_prompt_opt_for_other_methods(&mut p)?; + p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(false))?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect("searching for profile...\r\n")?; + + p.expect("cannot find profile...\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 { + use super::*; + + #[tokio::test] + #[serial] + async fn warm_user_and_displays_name() -> Result<()> { + 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, + ) + .await + } + + async fn run_test_when_no_relay_list_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<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); + + expect_qr_prompt_opt_for_other_methods(&mut p)?; + p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(false))?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect("searching for profile...\r\n")?; + + 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] { + 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_second_time_login_and_details_already_fetched { + use super::*; + + mod uses_cache_and_stores_and_retrieves_ncryptsec_from_local_git_config { + use super::*; + + #[tokio::test] + #[serial] + async fn dislays_logged_in_with_correct_name() -> Result<()> { + 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(()) + }, + )) + .await + } + 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<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "login", + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + ], + ); + + p.expect("saved login details to local git config\r\n")?; + + 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_from_dir( + &test_repo.dir, + ["login", "--password", TEST_PASSWORD], + ); + + p.expect_end_eventually_with("logged in as fred\r\n")?; + + Ok(()) + }); + + // launch relay + let _ = join!(r51.listen_until_close(), r52.listen_until_close(),); + + cli_tester_handle.join().unwrap()?; + + Ok(()) + } + } + } + } + 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::*; + async fn run_test_displays_correct_name( + relay_listener1: Option>, + relay_listener2: Option>, + ) -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55) = ( + Relay::new(8051, None, relay_listener1), + 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<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]); + + expect_qr_prompt_opt_for_other_methods(&mut p)?; + p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(false))?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect("searching for profile...\r\n")?; + + p.expect_end_with("logged in as fred\r\n")?; + for p in [51, 52, 53, 55] { + shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + ); + + cli_tester_handle.join().unwrap()?; + Ok(()) + } + + /// this also tests that additional relays are queried + #[tokio::test] + #[serial] + async fn displays_correct_name() -> Result<()> { + 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_old("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(()) + }), + ) + .await + } + } + } +} + +/// using the offline flag simplifies the test. relay interaction is tested +/// seperately +mod with_offline_flag { + use super::*; + mod when_first_time_login { + use super::*; + + #[test] + fn prompts_for_nsec_and_password() -> Result<()> { + standard_first_time_login_encrypting_nsec()?; + Ok(()) + } + + #[test] + fn succeeds_with_text_logged_in_as_npub() -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); + + p.expect_input(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? + .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? + .succeeds_with(TEST_PASSWORD)?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) + } + + #[test] + fn succeeds_with_hex_secret_key_in_place_of_nsec() -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); + + p.expect_input(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_SK_HEX)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? + .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? + .succeeds_with(TEST_PASSWORD)?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) + } + + mod when_invalid_nsec { + use super::*; + + #[test] + fn prompts_for_nsec_until_valid() -> Result<()> { + let invalid_nsec_response = + "invalid. try again with nostr address / bunker uri / nsec"; + + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); + + p.expect_input(EXPECTED_NSEC_PROMPT)? + // this behaviour is intentional. rejecting the response with dialoguer + // hides the original input from the user so they + // cannot see the mistake they made. + .succeeds_with(TEST_INVALID_NSEC)?; + + p.expect_input(invalid_nsec_response)? + .succeeds_with(TEST_INVALID_NSEC)?; + + p.expect_input(invalid_nsec_response)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? + .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? + .succeeds_with(TEST_PASSWORD)?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) + } + } + } + + mod when_called_with_nsec_parameter_only { + use super::*; + + #[test] + fn valid_nsec_param_succeeds_without_prompts() -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + ["login", "--offline", "--nsec", TEST_KEY_1_NSEC], + ); + + p.expect("saved login details to local git config\r\n")?; + + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) + } + + #[test] + fn forgets_identity() -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + ["login", "--offline", "--nsec", TEST_KEY_1_NSEC], + ); + + p.expect("saved login details to local git config\r\n")?; + + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?; + + p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]); + + p.expect_input(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.exit() + } + + mod when_logging_in_as_different_nsec { + use super::*; + + #[test] + fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> { + standard_first_time_login_encrypting_nsec()?.exit()?; + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + ["login", "--offline", "--nsec", TEST_KEY_2_NSEC], + ); + + p.expect("saved login details to local git config\r\n")?; + + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str()) + } + } + #[test] + fn invalid_nsec_param_fails_without_prompts() -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + ["login", "--offline", "--nsec", TEST_INVALID_NSEC], + ); + + p.expect_end_with( + "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n", + ) + } + } + + mod when_called_with_nsec_and_password_parameter { + use super::*; + + #[test] + fn valid_nsec_param_succeeds_without_prompts() -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "login", + "--offline", + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + ], + ); + p.expect("saved login details to local git config\r\n")?; + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) + } + + #[test] + fn parameters_can_be_called_globally() -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "login", + "--offline", + ], + ); + p.expect("saved login details to local git config\r\n")?; + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) + } + + mod when_logging_in_as_different_nsec { + use super::*; + + #[test] + fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> { + standard_first_time_login_encrypting_nsec()?.exit()?; + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "login", + "--offline", + "--nsec", + TEST_KEY_2_NSEC, + "--password", + TEST_PASSWORD, + ], + ); + p.expect("saved login details to local git config\r\n")?; + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str()) + } + } + + mod when_provided_with_new_password { + use super::*; + + #[test] + fn password_changes() -> Result<()> { + standard_first_time_login_encrypting_nsec()?.exit()?; + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "login", + "--offline", + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_INVALID_PASSWORD, + ], + ); + p.expect("saved login details to local git config\r\n")?; + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?; + + CliTester::new_from_dir( + &test_repo.dir, + ["--password", TEST_INVALID_PASSWORD, "login", "--offline"], + ) + .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) + } + } + + #[test] + fn invalid_nsec_param_fails_without_prompts() -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "login", + "--offline", + "--nsec", + TEST_INVALID_NSEC, + "--password", + TEST_PASSWORD, + ], + ); + p.expect_end_with( + "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n", + ) + } + } + + mod when_weak_password { + use super::*; + + #[test] + #[serial] + // combined into a single test as it is computationally expensive to run + fn warns_it_might_take_a_few_seconds_then_succeeds_then_second_login_prompts_for_password_then_warns_again_then_succeeds() + -> Result<()> { + let test_repo = GitTestRepo::default(); + let mut p = + CliTester::new_with_timeout_from_dir(15000, &test_repo.dir, ["login", "--offline"]); + + p.expect_input(EXPECTED_NSEC_PROMPT)? + .succeeds_with(TEST_KEY_1_NSEC)?; + + p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))? + .succeeds_with(Some(true))?; + + p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)? + .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)? + .succeeds_with(TEST_WEAK_PASSWORD)?; + + p.expect("this may take a few seconds...\r\n")?; + + p.expect("saved login details to local git config\r\n")?; + + p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str()) + + // commented out as 'login' command now assumes you want to + // login as a new user + // p = CliTester::new_with_timeout(10000, ["login", + // "--offline"]); + + // p.expect(format!("login as {}\r\n", + // TEST_KEY_1_NPUB).as_str())? + // .expect_password(EXPECTED_PASSWORD_PROMPT)? + // .succeeds_with(TEST_WEAK_PASSWORD)?; + + // p.expect("this may take a few seconds...\r\n")?; + + // p.expect_end_with(format!("logged in as {}\r\n", + // TEST_KEY_1_NPUB).as_str()) + } + } +} diff --git a/tests/ngit_pull.rs b/tests/ngit_pull.rs new file mode 100644 index 0000000..6637859 --- /dev/null +++ b/tests/ngit_pull.rs @@ -0,0 +1,615 @@ +use anyhow::Result; +use futures::join; +use serial_test::serial; +use test_utils::{git::GitTestRepo, relay::Relay, *}; + +mod when_main_is_checked_out { + use super::*; + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn cli_show_error() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + cli_tester_create_proposals()?; + + let test_repo = create_repo_with_proposal_branch_pulled_and_checkedout(1)?; + + test_repo.checkout("main")?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect("Error: checkout a branch associated with a proposal first\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } +} + +mod when_branch_doesnt_exist { + use super::*; + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn cli_show_error() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + cli_tester_create_proposals()?; + + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + + test_repo.create_branch("random-name")?; + test_repo.checkout("random-name")?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect("Error: cannot find proposal that matches the current branch name\r\n")?; + + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } +} + +mod when_branch_is_checked_out { + use super::*; + + mod when_branch_is_up_to_date { + use super::*; + + mod cli_prompts { + use super::*; + #[tokio::test] + #[serial] + async fn cli_show_up_to_date() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect("branch already up-to-date\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + } + + mod when_branch_is_behind { + use super::*; + + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = + std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { + let (originating_repo, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + let branch_name = + remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( + &test_repo, + )?; + test_repo.checkout(&branch_name)?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect_end_eventually()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn cli_applied_1_commit() -> Result<()> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = + std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { + let (originating_repo, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + let branch_name = + remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( + &test_repo, + )?; + test_repo.checkout(&branch_name)?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect_end_with("applied 1 new commits\r\n")?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> { + let (originating_repo, test_repo) = prep_and_run().await?; + assert_eq!( + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_1 + )?)?, + ); + Ok(()) + } + } + + mod when_latest_proposal_amended_locally { + use super::*; + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn cli_output_correct() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + amend_last_commit(&test_repo, "add ammended-commit.md")?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect( + "you have an amended/rebase version the proposal that is unpublished\r\n", + )?; + p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?; + p.expect("to view the latest proposal but retain your changes:\r\n")?; + p.expect(" 1) create a new branch off the tip commit of this one to store your changes\r\n")?; + p.expect(" 2) run `ngit list` and checkout the latest published version of this proposal\r\n")?; + p.expect("if you are confident in your changes consider running `ngit push --force`\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + } + + mod when_local_commits_on_uptodate_proposal { + use super::*; + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn( + move || -> Result<(GitTestRepo, GitTestRepo)> { + let (originating_repo, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + // add another commit (so we have a local branch 1 ahead) + std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?; + test_repo.stage_and_commit("add ammended-commit.md")?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect("local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }, + ); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + // add another commit (so we have a local branch 1 ahead) + std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?; + test_repo.stage_and_commit("add ammended-commit.md")?; + + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect("local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn didnt_overwrite_local_appendments() -> Result<()> { + let (originating_repo, test_repo) = prep_and_run().await?; + assert_ne!( + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_1 + )?)?, + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, + ); + Ok(()) + } + } + mod when_latest_event_rebases_branch { + use tokio::task::JoinHandle; + + use super::*; + + async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle: JoinHandle> = + tokio::task::spawn_blocking(move || { + let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect_end_eventually_and_print()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.await??; + + Ok(res) + } + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn prompts_to_choose_from_proposal_titles() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle: JoinHandle> = tokio::task::spawn_blocking( + move || { + let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect_end_with("pulled new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }, + ); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.await??; + println!("{:?}", r55.events); + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> { + let (originating_repo, test_repo) = prep_and_run().await?; + assert_eq!( + originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, + test_repo.get_tip_of_local_branch(&get_proposal_branch_name( + &test_repo, + FEATURE_BRANCH_NAME_1 + )?)?, + ); + Ok(()) + } + } +} diff --git a/tests/ngit_push.rs b/tests/ngit_push.rs new file mode 100644 index 0000000..eb452cd --- /dev/null +++ b/tests/ngit_push.rs @@ -0,0 +1,531 @@ +use anyhow::Result; +use futures::join; +use serial_test::serial; +use test_utils::{git::GitTestRepo, relay::Relay, *}; + +mod when_main_is_checked_out { + use super::*; + + #[test] + fn cli_returns_error() -> Result<()> { + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", false)?; + test_repo.checkout("main")?; + let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); + p.expect("Error: checkout a branch associated with a proposal first\r\n")?; + p.expect_end()?; + Ok(()) + } +} + +mod when_proposal_isnt_associated_with_branch_name { + use super::*; + + mod cli_prompts { + + use super::*; + + #[tokio::test] + #[serial] + async fn cli_show_error() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + cli_tester_create_proposals()?; + + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + + test_repo.create_branch("random-name")?; + test_repo.checkout("random-name")?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect_end_with( + "Error: cannot find proposal that matches the current branch name\r\n", + )?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } +} + +mod when_branch_is_checked_out { + use super::*; + + mod when_branch_is_up_to_date { + use super::*; + + mod cli_prompts { + use super::*; + #[tokio::test] + #[serial] + async fn cli_show_up_to_date() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect_end_with("Error: proposal already up-to-date with local branch\r\n")?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + } + + mod when_branch_is_behind { + use super::*; + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn cli_show_proposal_ahead_error() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + let branch_name = + remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main( + &test_repo, + )?; + + test_repo.checkout(&branch_name)?; + // run test + let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect("Error: proposal is ahead of local branch\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + } + + mod when_branch_is_ahead { + use super::*; + + mod cli_prompts { + use test_utils::relay::expect_send_with_progress; + + use super::*; + + #[tokio::test] + #[serial] + async fn cli_applied_1_commit() -> Result<()> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = + std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { + let (originating_repo, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + // add another commit (so we have an ammened local branch) + std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?; + test_repo.stage_and_commit("add ammended-commit.md")?; + + // run test + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "push", + ], + ); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect( + "1 commits ahead. preparing to create creating patch events.\r\n", + )?; + p.expect("logged in as fred\r\n")?; + p.expect("pushing 1 commits\r\n")?; + + expect_send_with_progress( + &mut p, + vec![ + (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), + (" [my-relay] ws://localhost:8053", true, ""), + (" [repo-relay] ws://localhost:8056", true, ""), + (" [default] ws://localhost:8051", true, ""), + (" [default] ws://localhost:8052", true, ""), + ], + 1, + )?; + p.expect_eventually("pushed 1 commits\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok((originating_repo, test_repo)) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + + Ok(()) + } + } + + async fn prep_and_run() -> Result<(GitTestRepo, Vec)> { + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result { + let (_, test_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + + // add another commit (so we have an ammened local branch) + std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?; + test_repo.stage_and_commit("add ammended-commit.md")?; + + // run test + + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "push", + ], + ); + p.expect_end_eventually()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(test_repo) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + let res = cli_tester_handle.join().unwrap()?; + + Ok((res, r55.events.clone())) + } + #[tokio::test] + #[serial] + async fn commits_issued_as_patch_event() -> Result<()> { + let (test_repo, r55_events) = prep_and_run().await?; + + let commit_id = test_repo + .get_tip_of_local_branch(&test_repo.get_checked_out_branch_name()?)? + .to_string(); + assert!(r55_events.iter().any(|e| { + e.tags + .iter() + .any(|t| t.as_vec()[0].eq("commit") && t.as_vec()[1].eq(&commit_id)) + })); + Ok(()) + } + } + + mod when_branch_has_been_rebased { + use super::*; + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn cli_shows_unpublished_rebase_error() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, tmp_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + let branch_name = tmp_repo.get_checked_out_branch_name()?; + + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + + // simulate rebase + std::fs::write(test_repo.dir.join("amazing.md"), "some content")?; + test_repo.stage_and_commit("commit for rebasing on top of")?; + create_and_populate_branch(&test_repo, &branch_name, "a", true)?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]); + // p.expect_end_eventually_and_print()?; + + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect( + "Error: local unpublished proposal has been rebased. consider force pushing\r\n", + )?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + mod with_force_flag { + use super::*; + + mod cli_prompts { + use super::*; + + #[tokio::test] + #[serial] + async fn cli_shows_revision_sent() -> Result<()> { + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + ); + + r51.events.push(generate_test_key_1_relay_list_event()); + r51.events.push(generate_test_key_1_metadata_event("fred")); + r51.events.push(generate_repo_ref_event()); + + r55.events.push(generate_repo_ref_event()); + r55.events.push(generate_test_key_1_metadata_event("fred")); + r55.events.push(generate_test_key_1_relay_list_event()); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let (_, tmp_repo) = + create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?; + let branch_name = tmp_repo.get_checked_out_branch_name()?; + + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + + // simulate rebase + std::fs::write(test_repo.dir.join("amazing.md"), "some content")?; + test_repo.stage_and_commit("commit for rebasing on top of")?; + create_and_populate_branch(&test_repo, &branch_name, "a", false)?; + let mut p = CliTester::new_from_dir( + &test_repo.dir, + [ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "push", + "--force", + ], + ); + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // some updates listed here + p.expect("preparing to force push proposal revision...\r\n")?; + // standard output from `ngit send` + p.expect("creating proposal revision for: ")?; + // proposal id will be printed in this gap + p.expect_eventually("\r\n")?; + p.expect("creating proposal from 2 commits:\r\n")?; + p.expect("355bdf1 add a4.md\r\n")?; + p.expect("dbd1115 add a3.md\r\n")?; + p.expect("logged in as fred\r\n")?; + p.expect("posting 2 patches without a covering letter...\r\n")?; + + relay::expect_send_with_progress( + &mut p, + vec![ + (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), + (" [my-relay] ws://localhost:8053", true, ""), + (" [repo-relay] ws://localhost:8056", true, ""), + (" [default] ws://localhost:8051", true, ""), + (" [default] ws://localhost:8052", true, ""), + ], + 2, + )?; + // end standard `ngit send output` + p.expect_after_whitespace("force pushed proposal revision\r\n")?; + p.expect_end()?; + + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + } + } +} diff --git a/tests/ngit_send.rs b/tests/ngit_send.rs new file mode 100644 index 0000000..2aad232 --- /dev/null +++ b/tests/ngit_send.rs @@ -0,0 +1,1901 @@ +use anyhow::Result; +use futures::join; +use nostr_sdk::Kind; +use serial_test::serial; +use test_utils::{git::GitTestRepo, relay::Relay, *}; + +#[test] +fn when_no_main_or_master_branch_return_error() -> Result<()> { + let test_repo = GitTestRepo::new("notmain")?; + test_repo.populate()?; + let mut p = CliTester::new_from_dir(&test_repo.dir, ["send"]); + p.expect("Error: the default branches (main or master) do not exist")?; + Ok(()) +} + +// TODO when commits ahead of origin/master - test ask to proceed +// TODO when commits in origin/master - test ask to proceed +mod when_commits_behind_ask_to_proceed { + use super::*; + + fn prep_test_repo() -> Result { + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + // create feature branch with 2 commit ahead + test_repo.create_branch("feature")?; + test_repo.checkout("feature")?; + std::fs::write(test_repo.dir.join("t3.md"), "some content")?; + test_repo.stage_and_commit("add t3.md")?; + std::fs::write(test_repo.dir.join("t4.md"), "some content")?; + test_repo.stage_and_commit("add t4.md")?; + // checkout main and add 1 commit + test_repo.checkout("main")?; + std::fs::write(test_repo.dir.join("t5.md"), "some content")?; + test_repo.stage_and_commit("add t5.md")?; + // checkout feature branch + test_repo.checkout("feature")?; + Ok(test_repo) + } + + fn expect_confirm_prompt(p: &mut CliTester) -> Result { + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // may be 'no updates' or some updates + p.expect("creating proposal from 2 commits:\r\n")?; + p.expect("fe973a8 add t4.md\r\n")?; + p.expect("232efb3 add t3.md\r\n")?; + p.expect_confirm( + "proposal is 1 behind 'main'. consider rebasing before submission. proceed anyway?", + Some(false), + ) + } + + #[test] + fn asked_with_default_no() -> Result<()> { + let test_repo = prep_test_repo()?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]); + expect_confirm_prompt(&mut p)?; + p.exit()?; + Ok(()) + } + + #[test] + fn when_response_is_false_aborts() -> Result<()> { + let test_repo = prep_test_repo()?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]); + + expect_confirm_prompt(&mut p)?.succeeds_with(Some(false))?; + + p.expect_end_with("Error: aborting so commits can be rebased\r\n")?; + + Ok(()) + } + #[test] + #[serial] + fn when_response_is_true_proceeds() -> Result<()> { + let test_repo = prep_test_repo()?; + + let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]); + expect_confirm_prompt(&mut p)?.succeeds_with(Some(true))?; + p.expect("? include cover letter")?; + p.exit()?; + Ok(()) + } +} + +fn is_cover_letter(event: &nostr::Event) -> bool { + event.kind.eq(&Kind::GitPatch) + && event + .tags() + .iter() + .any(|t| t.as_vec()[1].eq("cover-letter")) +} + +fn is_patch(event: &nostr::Event) -> bool { + event.kind.eq(&Kind::GitPatch) + && !event + .tags() + .iter() + .any(|t| t.as_vec()[1].eq("cover-letter")) +} + +fn prep_git_repo() -> Result { + let test_repo = GitTestRepo::default(); + test_repo.populate()?; + // create feature branch with 2 commit ahead + test_repo.create_branch("feature")?; + test_repo.checkout("feature")?; + std::fs::write(test_repo.dir.join("t3.md"), "some content")?; + test_repo.stage_and_commit("add t3.md")?; + std::fs::write(test_repo.dir.join("t4.md"), "some content")?; + test_repo.stage_and_commit("add t4.md")?; + Ok(test_repo) +} + +fn cli_tester_create_proposal(git_repo: &GitTestRepo, include_cover_letter: bool) -> CliTester { + let mut args = vec![ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "send", + "HEAD~2", + ]; + if include_cover_letter { + for arg in [ + "--title", + "exampletitle", + "--description", + "exampledescription", + ] { + args.push(arg); + } + } else { + args.push("--no-cover-letter"); + } + CliTester::new_from_dir(&git_repo.dir, args) +} + +fn expect_msgs_first(p: &mut CliTester, include_cover_letter: bool) -> Result<()> { + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // may be 'no updates' or some updates + p.expect("creating proposal from 2 commits:\r\n")?; + p.expect("fe973a8 add t4.md\r\n")?; + p.expect("232efb3 add t3.md\r\n")?; + // sometimes there will be a 'searching for profile...' msg + p.expect_eventually("logged in as fred\r\n")?; + p.expect(format!( + "posting 2 patches {} a covering letter...\r\n", + if include_cover_letter { + "with" + } else { + "without" + } + ))?; + Ok(()) +} + +fn expect_msgs_after(p: &mut CliTester) -> Result<()> { + p.expect_after_whitespace("view in gitworkshop.dev: https://gitworkshop.dev/repo")?; + p.expect_eventually("\r\n")?; + p.expect("view in another client: https://njump.me/")?; + p.expect_eventually("\r\n")?; + Ok(()) +} + +async fn prep_run_create_proposal( + include_cover_letter: bool, +) -> Result<( + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, +)> { + let git_repo = prep_git_repo()?; + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo, include_cover_letter); + p.expect_end_eventually()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok((r51, r52, r53, r55, r56)) +} + +mod when_cover_letter_details_specified_with_range_of_head_2_sends_cover_letter_and_2_patches_to_3_relays { + + use super::*; + #[tokio::test] + #[serial] + async fn only_1_cover_letter_event_sent_to_each_relay() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + assert_eq!( + relay.events.iter().filter(|e| is_cover_letter(e)).count(), + 1, + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn only_1_cover_letter_event_sent_to_user_relays() -> Result<()> { + let (_, _, r53, r55, _) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55] { + assert_eq!( + relay.events.iter().filter(|e| is_cover_letter(e)).count(), + 1, + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn only_1_cover_letter_event_sent_to_repo_relays() -> Result<()> { + let (_, _, _, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r55, &r56] { + assert_eq!( + relay.events.iter().filter(|e| is_cover_letter(e)).count(), + 1 + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn only_1_cover_letter_event_sent_to_fallback_relays() -> Result<()> { + let (r51, r52, _, _, _) = prep_run_create_proposal(true).await?; + for relay in [&r51, &r52] { + assert_eq!( + relay.events.iter().filter(|e| is_cover_letter(e)).count(), + 1, + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn only_2_patch_kind_events_sent_to_each_relay() -> Result<()> { + let (r51, r52, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r51, &r52, &r53, &r55, &r56] { + assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2,); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn patch_content_contains_patch_in_email_format_with_patch_series_numbers() -> Result<()> + { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let patch_events: Vec<&nostr::Event> = + relay.events.iter().filter(|e| is_patch(e)).collect(); + + assert_eq!( + patch_events[1].content, + "\ + From fe973a840fba2a8ab37dd505c154854a69a6505c Mon Sep 17 00:00:00 2001\n\ + From: Joe Bloggs \n\ + Date: Thu, 1 Jan 1970 00:00:00 +0000\n\ + Subject: [PATCH 2/2] add t4.md\n\ + \n\ + ---\n \ + t4.md | 1 +\n \ + 1 file changed, 1 insertion(+)\n \ + create mode 100644 t4.md\n\ + \n\ + diff --git a/t4.md b/t4.md\n\ + new file mode 100644\n\ + index 0000000..f0eec86\n\ + --- /dev/null\n\ + +++ b/t4.md\n\ + @@ -0,0 +1 @@\n\ + +some content\n\\ \ + No newline at end of file\n\ + --\n\ + libgit2 1.8.1\n\ + \n\ + ", + ); + assert_eq!( + patch_events[0].content, + "\ + From 232efb37ebc67692c9e9ff58b83c0d3d63971a0a Mon Sep 17 00:00:00 2001\n\ + From: Joe Bloggs \n\ + Date: Thu, 1 Jan 1970 00:00:00 +0000\n\ + Subject: [PATCH 1/2] add t3.md\n\ + \n\ + ---\n \ + t3.md | 1 +\n \ + 1 file changed, 1 insertion(+)\n \ + create mode 100644 t3.md\n\ + \n\ + diff --git a/t3.md b/t3.md\n\ + new file mode 100644\n\ + index 0000000..f0eec86\n\ + --- /dev/null\n\ + +++ b/t3.md\n\ + @@ -0,0 +1 @@\n\ + +some content\n\\ \ + No newline at end of file\n\ + --\n\ + libgit2 1.8.1\n\ + \n\ + ", + ); + } + Ok(()) + } + + mod cover_letter_tags { + use super::*; + + #[tokio::test] + #[serial] + async fn root_commit_as_r() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + + assert_eq!( + cover_letter_event + .tags() + .iter() + .find(|t| t.as_vec()[0].eq("r")) + .unwrap() + .as_vec()[1], + "9ee507fc4357d7ee16a5d8901bedcd103f23c17d" + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn a_tag_for_repo_event_of_each_maintainer() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!( + cover_letter_event + .tags() + .iter() + .any(|t| t.as_vec()[0].eq("a") + && t.as_vec()[1].eq(&format!( + "{}:{TEST_KEY_1_PUBKEY_HEX}:{}", + Kind::GitRepoAnnouncement, + generate_repo_ref_event().identifier().unwrap() + ))) + ); + assert!( + cover_letter_event + .tags() + .iter() + .any(|t| t.as_vec()[0].eq("a") + && t.as_vec()[1].eq(&format!( + "{}:{TEST_KEY_2_PUBKEY_HEX}:{}", + Kind::GitRepoAnnouncement, + generate_repo_ref_event().identifier().unwrap() + ))) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn p_tags_for_maintainers() -> Result<()> { + let event = generate_repo_ref_event(); + let maintainers = &event + .tags() + .iter() + .find(|t| t.as_vec()[0].eq(&"maintainers")) + .unwrap() + .as_vec()[1..]; + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + for m in maintainers { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!( + cover_letter_event + .tags() + .iter() + .any(|t| { t.as_vec()[0].eq("p") && t.as_vec()[1].eq(m) }) + ); + } + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn t_tag_cover_letter() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!( + cover_letter_event + .tags() + .iter() + .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"cover-letter") }) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn t_tag_root() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!( + cover_letter_event + .tags() + .iter() + .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"root") }) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn cover_letter_tags_branch_name() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + + // branch-name tag + assert_eq!( + cover_letter_event + .tags() + .iter() + .find(|t| t.as_vec()[0].eq("branch-name")) + .unwrap() + .as_vec()[1], + "feature" + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn cover_letter_tags_alt() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + + // branch-name tag + assert_eq!( + cover_letter_event + .tags() + .iter() + .find(|t| t.as_vec()[0].eq("alt")) + .unwrap() + .as_vec()[1], + "git patch cover letter: exampletitle" + ); + } + Ok(()) + } + } + + mod patch_tags { + use super::*; + + async fn prep() -> Result { + let (_, _, r53, _, _) = prep_run_create_proposal(true).await?; + Ok(r53.events.iter().find(|e| is_patch(e)).unwrap().clone()) + } + + #[tokio::test] + #[serial] + async fn commit_and_commit_r() -> Result<()> { + static COMMIT_ID: &str = "232efb37ebc67692c9e9ff58b83c0d3d63971a0a"; + let most_recent_patch = prep().await?; + assert!( + most_recent_patch + .tags + .iter() + .any(|t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq(COMMIT_ID)) + ); + assert!( + most_recent_patch + .tags + .iter() + .any(|t| t.as_vec()[0].eq("commit") && t.as_vec()[1].eq(COMMIT_ID)) + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn parent_commit() -> Result<()> { + // commit parent 'r' and 'parent-commit' tag + static COMMIT_PARENT_ID: &str = "431b84edc0d2fa118d63faa3c2db9c73d630a5ae"; + let most_recent_patch = prep().await?; + assert_eq!( + most_recent_patch + .tags + .iter() + .find(|t| t.as_vec()[0].eq("parent-commit")) + .unwrap() + .as_vec()[1], + COMMIT_PARENT_ID, + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn root_commit_as_r() -> Result<()> { + assert!(prep().await?.tags.iter().any(|t| t.as_vec()[0].eq("r") + && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn p_tags_for_maintainers() -> Result<()> { + let event = generate_repo_ref_event(); + let maintainers = &event + .tags() + .iter() + .find(|t| t.as_vec()[0].eq(&"maintainers")) + .unwrap() + .as_vec()[1..]; + for m in maintainers { + assert!( + prep() + .await? + .tags() + .iter() + .any(|t| { t.as_vec()[0].eq("p") && t.as_vec()[1].eq(m) }) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn a_tag_for_repo_event_of_each_maintainer() -> Result<()> { + assert!(prep().await?.tags.iter().any(|t| { + t.as_vec()[0].eq("a") + && t.as_vec()[1].eq(&format!( + "{}:{TEST_KEY_1_PUBKEY_HEX}:{}", + Kind::GitRepoAnnouncement, + generate_repo_ref_event().identifier().unwrap() + )) + })); + assert!(prep().await?.tags.iter().any(|t| { + t.as_vec()[0].eq("a") + && t.as_vec()[1].eq(&format!( + "{}:{TEST_KEY_2_PUBKEY_HEX}:{}", + Kind::GitRepoAnnouncement, + generate_repo_ref_event().identifier().unwrap() + )) + })); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn description_with_commit_message() -> Result<()> { + assert_eq!( + prep() + .await? + .tags + .iter() + .find(|t| t.as_vec()[0].eq("description")) + .unwrap() + .as_vec()[1], + "add t3.md" + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn commit_author() -> Result<()> { + assert_eq!( + prep() + .await? + .tags + .iter() + .find(|t| t.as_vec()[0].eq("author")) + .unwrap() + .as_vec(), + vec!["author", "Joe Bloggs", "joe.bloggs@pm.me", "0", "0"], + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn commit_committer() -> Result<()> { + assert_eq!( + prep() + .await? + .tags + .iter() + .find(|t| t.as_vec()[0].eq("committer")) + .unwrap() + .as_vec(), + vec!["committer", "Joe Bloggs", "joe.bloggs@pm.me", "0", "0"], + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn alt() -> Result<()> { + assert_eq!( + prep() + .await? + .tags + .iter() + .find(|t| t.as_vec()[0].eq("alt")) + .unwrap() + .as_vec(), + vec!["alt", "git patch: add t3.md"], + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn patch_tags_cover_letter_event_as_root() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let patch_events: Vec<&nostr::Event> = + relay.events.iter().filter(|e| is_patch(e)).collect(); + + let most_recent_patch = patch_events[0]; + let cover_letter_event = relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + + let root_event_tag = most_recent_patch + .tags + .iter() + .find(|t| { + t.as_vec()[0].eq("e") && t.as_vec().len().eq(&4) && t.as_vec()[3].eq("root") + }) + .unwrap(); + + assert_eq!( + root_event_tag.as_vec()[1], + cover_letter_event.id.to_string() + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn second_patch_tags_first_with_reply() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?; + for relay in [&r53, &r55, &r56] { + let patch_events = relay + .events + .iter() + .filter(|e| is_patch(e)) + .collect::>(); + assert_eq!( + patch_events[1] + .tags() + .iter() + .find(|t| t.as_vec()[0].eq("e") + && t.as_vec().len().eq(&4) + && t.as_vec()[3].eq("reply")) + .unwrap() + .as_vec()[1], + patch_events[0].id.to_string(), + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn no_t_root_tag() -> Result<()> { + assert!( + !prep() + .await? + .tags + .iter() + .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root")) + ); + Ok(()) + } + } + mod cli_ouput { + use super::*; + + #[tokio::test] + #[serial] + async fn check_cli_output() -> Result<()> { + let git_repo = prep_git_repo()?; + + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo, true); + expect_msgs_first(&mut p, true)?; + relay::expect_send_with_progress( + &mut p, + vec![ + (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), + (" [my-relay] ws://localhost:8053", true, ""), + (" [repo-relay] ws://localhost:8056", true, ""), + (" [default] ws://localhost:8051", true, ""), + (" [default] ws://localhost:8052", true, ""), + ], + 3, + )?; + expect_msgs_after(&mut p)?; + p.expect_end_with_whitespace()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + + mod first_event_rejected_by_1_relay { + use super::*; + + mod only_first_rejected_event_sent_to_relay { + use super::*; + + #[tokio::test] + #[serial] + async fn only_first_rejected_event_sent_to_relay() -> Result<()> { + let git_repo = prep_git_repo()?; + + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event()], + )?; + Ok(()) + }), + ), + Relay::new( + 8056, + Some(&|relay, client_id, event| -> Result<()> { + relay.respond_ok(client_id, event, Some("Payment Required"))?; + Ok(()) + }), + None, + ), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo, true); + p.expect_end_eventually()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + + assert_eq!(r56.events.len(), 1); + + Ok(()) + } + } + + mod cli_show_rejection_with_comment { + use super::*; + + #[tokio::test] + #[serial] + async fn cli_show_rejection_with_comment() -> Result<()> { + let git_repo = prep_git_repo()?; + + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event()], + )?; + Ok(()) + }), + ), + Relay::new( + 8056, + Some(&|relay, client_id, event| -> Result<()> { + relay.respond_ok(client_id, event, Some("Payment Required"))?; + Ok(()) + }), + None, + ), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo, true); + expect_msgs_first(&mut p, true)?; + relay::expect_send_with_progress( + &mut p, + vec![ + (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), + (" [my-relay] ws://localhost:8053", true, ""), + ( + " [repo-relay] ws://localhost:8056", + false, + "error: Payment Required", + ), + (" [default] ws://localhost:8051", true, ""), + (" [default] ws://localhost:8052", true, ""), + ], + 3, + )?; + expect_msgs_after(&mut p)?; + p.expect_end_with_whitespace()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + } +} + +mod when_no_cover_letter_flag_set_with_range_of_head_2_sends_2_patches_without_cover_letter { + use super::*; + + mod cli_ouput { + use super::*; + + #[tokio::test] + #[serial] + async fn check_cli_output() -> Result<()> { + let git_repo = prep_git_repo()?; + + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo, false); + + expect_msgs_first(&mut p, false)?; + relay::expect_send_with_progress( + &mut p, + vec![ + (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), + (" [my-relay] ws://localhost:8053", true, ""), + (" [repo-relay] ws://localhost:8056", true, ""), + (" [default] ws://localhost:8051", true, ""), + (" [default] ws://localhost:8052", true, ""), + ], + 2, + )?; + expect_msgs_after(&mut p)?; + p.expect_end_with_whitespace()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn no_cover_letter_event() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; + for relay in [&r53, &r55, &r56] { + assert_eq!( + relay.events.iter().filter(|e| is_cover_letter(e)).count(), + 0, + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn two_patch_events() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; + for relay in [&r53, &r55, &r56] { + assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2); + } + Ok(()) + } + + #[tokio::test] + #[serial] + // TODO check this is the ancestor + async fn first_patch_with_root_t_tag() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; + for relay in [&r53, &r55, &r56] { + let patch_events = relay + .events + .iter() + .filter(|e| is_patch(e)) + .collect::>(); + + // first patch tagged as root + assert!( + patch_events[0] + .tags() + .iter() + .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root")) + ); + // second patch not tagged as root + assert!( + !patch_events[1] + .tags() + .iter() + .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root")) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn root_patch_tags_branch_name() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; + for relay in [&r53, &r55, &r56] { + let patch_events = relay + .events + .iter() + .filter(|e| is_patch(e)) + .collect::>(); + + // branch-name tag + assert_eq!( + patch_events[0] + .tags() + .iter() + .find(|t| t.as_vec()[0].eq("branch-name")) + .unwrap() + .as_vec()[1], + "feature" + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn second_patch_lists_first_as_root() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?; + for relay in [&r53, &r55, &r56] { + let patch_events = relay + .events + .iter() + .filter(|e| is_patch(e)) + .collect::>(); + + assert_eq!( + patch_events[1] + .tags() + .iter() + .find(|t| t.as_vec()[0].eq("e") + && t.as_vec().len().eq(&4) + && t.as_vec()[3].eq("root")) + .unwrap() + .as_vec()[1], + patch_events[0].id.to_string(), + ); + } + Ok(()) + } +} + +mod when_range_ommited_prompts_for_selection_defaulting_ahead_of_main { + use super::*; + + fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester { + let args = vec![ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "send", + "--no-cover-letter", + ]; + CliTester::new_from_dir(&git_repo.dir, args) + } + fn expect_msgs_first(p: &mut CliTester) -> Result<()> { + p.expect("fetching updates...\r\n")?; + p.expect_eventually("\r\n")?; // may be 'no updates' or some updates + let mut selector = p.expect_multi_select( + "select commits for proposal", + vec![ + "(Joe Bloggs) add t4.md [feature] fe973a8".to_string(), + "(Joe Bloggs) add t3.md 232efb3".to_string(), + "(Joe Bloggs) add t2.md [main] 431b84e".to_string(), + "(Joe Bloggs) add t1.md af474d8".to_string(), + "(Joe Bloggs) Initial commit 9ee507f".to_string(), + ], + )?; + selector.succeeds_with(vec![0, 1], false, vec![0, 1])?; + p.expect("creating proposal from 2 commits:\r\n")?; + p.expect("fe973a8 add t4.md\r\n")?; + p.expect("232efb3 add t3.md\r\n")?; + p.expect("searching for profile...\r\n")?; + p.expect("logged in as fred\r\n")?; + p.expect("posting 2 patches without a covering letter...\r\n")?; + Ok(()) + } + async fn prep_run_create_proposal() -> Result<( + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + )> { + let git_repo = prep_git_repo()?; + + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo); + expect_msgs_first(&mut p)?; + p.expect_end_eventually()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok((r51, r52, r53, r55, r56)) + } + mod cli_ouput { + use super::*; + + #[tokio::test] + #[serial] + async fn check_cli_output() -> Result<()> { + let git_repo = prep_git_repo()?; + + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo); + + expect_msgs_first(&mut p)?; + relay::expect_send_with_progress( + &mut p, + vec![ + (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), + (" [my-relay] ws://localhost:8053", true, ""), + (" [repo-relay] ws://localhost:8056", true, ""), + (" [default] ws://localhost:8051", true, ""), + (" [default] ws://localhost:8052", true, ""), + ], + 2, + )?; + expect_msgs_after(&mut p)?; + p.expect_end_with_whitespace()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn two_patch_events_sent() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; + for relay in [&r53, &r55, &r56] { + assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2); + } + Ok(()) + } +} + +mod root_proposal_specified_using_in_reply_to_with_range_of_head_2_and_cover_letter_details_specified { + + use nostr::ToBech32; + + use super::*; + + fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester { + let proposal_root_bech32 = get_pretend_proposal_root_event().id.to_bech32().unwrap(); + let args = vec![ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "send", + "HEAD~2", + "--in-reply-to", + &proposal_root_bech32, + "--title", + "exampletitle", + "--description", + "exampledescription", + ]; + CliTester::new_from_dir(&git_repo.dir, args) + } + fn expect_msgs_first(p: &mut CliTester, include_cover_letter: bool) -> Result<()> { + p.expect("fetching updates...\r\n")?; + p.expect("updates: 1 new maintainer, 1 announcement update, 1 proposal\r\n")?; + let proposal_root_bech32 = get_pretend_proposal_root_event().id.to_bech32().unwrap(); + p.expect(format!( + "creating proposal revision for: {}\r\n", + proposal_root_bech32, + ))?; + p.expect("creating proposal from 2 commits:\r\n")?; + p.expect("fe973a8 add t4.md\r\n")?; + p.expect("232efb3 add t3.md\r\n")?; + p.expect("logged in as fred\r\n")?; + p.expect(format!( + "posting 2 patches {} a covering letter...\r\n", + if include_cover_letter { + "with" + } else { + "without" + } + ))?; + Ok(()) + } + + async fn prep_run_create_proposal() -> Result<( + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + )> { + let git_repo = prep_git_repo()?; + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(), + get_pretend_proposal_root_event(), + ], + )?; + Ok(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event(), get_pretend_proposal_root_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo); + p.expect_end_eventually()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok((r51, r52, r53, r55, r56)) + } + mod cli_ouput { + use super::*; + + #[tokio::test] + #[serial] + async fn check_cli_output() -> Result<()> { + let git_repo = prep_git_repo()?; + + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(), + get_pretend_proposal_root_event(), + ], + )?; + Ok(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event(), get_pretend_proposal_root_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo); + expect_msgs_first(&mut p, true)?; + relay::expect_send_with_progress( + &mut p, + vec![ + (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), + (" [my-relay] ws://localhost:8053", true, ""), + (" [repo-relay] ws://localhost:8056", true, ""), + (" [default] ws://localhost:8051", true, ""), + (" [default] ws://localhost:8052", true, ""), + ], + 3, + )?; + p.expect_end_with_whitespace()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok(()) + } + } + + mod cover_letter_tags { + use super::*; + + #[tokio::test] + #[serial] + async fn t_tag_root() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!( + cover_letter_event + .tags() + .iter() + .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"root") }) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn t_tag_revision_root() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!( + cover_letter_event + .tags() + .iter() + .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"revision-root") }) + ); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn e_tag_in_reply_to_event_as_reply() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert_eq!( + cover_letter_event + .tags() + .iter() + .find(|t| { + t.as_vec()[0].eq("e") + && t.as_vec().len().eq(&4) + && t.as_vec()[3].eq("reply") + }) + .unwrap() + .as_vec()[1], + // id of state nevent + "431e58eb8e1b4e20292d1d5bbe81d5cfb042e1bc165de32eddfdd52245a4cce4", + ); + } + Ok(()) + } + } + + #[tokio::test] + #[serial] + async fn patch_tags_cover_letter_event_as_root() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; + for relay in [&r53, &r55, &r56] { + let patch_events: Vec<&nostr::Event> = + relay.events.iter().filter(|e| is_patch(e)).collect(); + + let cover_letter_event = relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + + for patch in patch_events { + assert_eq!( + patch + .tags + .iter() + .find(|t| { + t.as_vec()[0].eq("e") + && t.as_vec().len().eq(&4) + && t.as_vec()[3].eq("root") + }) + .unwrap() + .as_vec()[1], + cover_letter_event.id.to_string() + ); + } + } + Ok(()) + } +} + +mod in_reply_to_mentions_issue { + use nostr::ToBech32; + + use super::*; + pub fn get_pretend_issue_event() -> nostr::Event { + serde_json::from_str(r#"{"created_at":1709286372,"content":"please provide feedback\nthis is an example ngit issue to demonstrate gitworkshop.dev.\n\nplease provide feedback with in reply to this issue or by creating a new issue.","tags":[["r","26689f97810fc656c7134c76e2a37d33b2e40ce7"],["a","30617:a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d:ngit","wss://relay.damus.io","root"],["p","a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d"]],"kind":1621,"pubkey":"a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d","id":"e944765d625ae7323d080da0df069c726a0e5490a17b452f854d85e18f781588","sig":"a1af9e89a35f1f7ef93e3de33986bd86cb7c4d7d9abb233c0c6405f32b5788171e47f84551afe8515b3107d12f03472721ea784b8791ff3f25e66a3169a54c20"}"#).unwrap() + } + + fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester { + let issue_bech32 = get_pretend_issue_event().id.to_bech32().unwrap(); + let args = vec![ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "send", + "HEAD~2", + "--in-reply-to", + &issue_bech32, + // "note1a9z8vhtzttnny0ggpksd7p5uwf4qu4ys59a52tu9fkz7rrmczkyqc46ngg", + "--title", + "exampletitle", + "--description", + "exampledescription", + ]; + CliTester::new_from_dir(&git_repo.dir, args) + } + + async fn prep_run_create_proposal() -> Result<( + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + )> { + let git_repo = prep_git_repo()?; + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(), + get_pretend_issue_event(), + ], + )?; + Ok(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event(), get_pretend_issue_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo); + p.expect_end_eventually()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok((r51, r52, r53, r55, r56)) + } + + #[tokio::test] + #[serial] + async fn issue_event_mentioned_in_tagged_cover_letter() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!(cover_letter_event.tags().iter().any(|t| { + t.as_vec()[0].eq("e") + && t.as_vec()[1].eq(&get_pretend_issue_event().id.to_hex()) + && t.as_vec()[3].eq(&"mention") + })); + } + Ok(()) + } + + #[tokio::test] + #[serial] + async fn isnt_tagged_as_revision() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!( + !cover_letter_event + .tags() + .iter() + .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"revision-root") }) + ); + } + Ok(()) + } +} +mod in_reply_to_mentions_npub_and_nprofile_which_get_mentioned_in_proposal_root { + + use super::*; + + fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester { + let args = vec![ + "--nsec", + TEST_KEY_1_NSEC, + "--password", + TEST_PASSWORD, + "--disable-cli-spinners", + "send", + "HEAD~2", + "--in-reply-to", + // nsec1q3c5xnsm5m4wgsrhwnz04p0d5mevkryyggqgdpa9jwulpq9gldhswgtxvq + "npub1knxeegzqg0xqflsryvg7l7x7nmpe7kd7pl7zazug0a7t99tdsphszuyapx", + // nsec1nx5ulvcndhcuu8k6q8fenw50l6y75sec7pj8vr0r68l6a44w3lqspvj02k + "nprofile1qqsvru3yqrec6dxjn06f8cjh79jcu9wyaxu4y6v47yzpsx7vjm4xcuc33z2n3", + "--title", + "exampletitle", + "--description", + "exampledescription", + ]; + CliTester::new_from_dir(&git_repo.dir, args) + } + + async fn prep_run_create_proposal() -> Result<( + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + Relay<'static>, + )> { + let git_repo = prep_git_repo()?; + // fallback (51,52) user write (53, 55) repo (55, 56) + let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( + Relay::new( + 8051, + None, + 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(()) + }), + ), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new( + 8055, + None, + Some(&|relay, client_id, subscription_id, _| -> Result<()> { + relay.respond_events( + client_id, + &subscription_id, + &vec![generate_repo_ref_event()], + )?; + Ok(()) + }), + ), + Relay::new(8056, None, None), + ); + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_create_proposal(&git_repo); + p.expect_end_eventually()?; + for p in [51, 52, 53, 55, 56] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relay + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + ); + cli_tester_handle.join().unwrap()?; + Ok((r51, r52, r53, r55, r56)) + } + + #[tokio::test] + #[serial] + async fn npub_and_nprofile_mentioned_in_tagged_cover_letter() -> Result<()> { + let (_, _, r53, r55, r56) = prep_run_create_proposal().await?; + for relay in [&r53, &r55, &r56] { + let cover_letter_event: &nostr::Event = + relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); + assert!(cover_letter_event.tags().iter().any(|t| { + t.as_vec()[0].eq("p") + && t.as_vec()[1].eq(&nostr::Keys::parse( + "nsec1q3c5xnsm5m4wgsrhwnz04p0d5mevkryyggqgdpa9jwulpq9gldhswgtxvq", + ) + .unwrap() + .public_key() + .to_hex()) + })); + assert!(cover_letter_event.tags().iter().any(|t| { + t.as_vec()[0].eq("p") + && t.as_vec()[1].eq(&nostr::Keys::parse( + "nsec1nx5ulvcndhcuu8k6q8fenw50l6y75sec7pj8vr0r68l6a44w3lqspvj02k", + ) + .unwrap() + .public_key() + .to_hex()) + })); + } + Ok(()) + } +} -- cgit v1.2.3