From 9a9b13a11868fe58fa0390938a39483bf1f3cc9a Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Mon, 9 Sep 2024 09:40:06 +0100 Subject: test: refactor into binary subdirs in prep for splitting git_remote_nostr tests --- tests/git_remote_helper.rs | 2338 ---------------------------------------- tests/git_remote_nostr/main.rs | 2338 ++++++++++++++++++++++++++++++++++++++++ tests/init.rs | 706 ------------ tests/list.rs | 1549 -------------------------- tests/login.rs | 1168 -------------------- tests/ngit/init.rs | 706 ++++++++++++ tests/ngit/list.rs | 1549 ++++++++++++++++++++++++++ tests/ngit/login.rs | 1168 ++++++++++++++++++++ tests/ngit/main.rs | 6 + tests/ngit/pull.rs | 615 +++++++++++ tests/ngit/push.rs | 531 +++++++++ tests/ngit/send.rs | 1901 ++++++++++++++++++++++++++++++++ tests/pull.rs | 615 ----------- tests/push.rs | 531 --------- tests/send.rs | 1901 -------------------------------- 15 files changed, 8814 insertions(+), 8808 deletions(-) delete mode 100644 tests/git_remote_helper.rs create mode 100644 tests/git_remote_nostr/main.rs delete mode 100644 tests/init.rs delete mode 100644 tests/list.rs delete mode 100644 tests/login.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/main.rs create mode 100644 tests/ngit/pull.rs create mode 100644 tests/ngit/push.rs create mode 100644 tests/ngit/send.rs delete mode 100644 tests/pull.rs delete mode 100644 tests/push.rs delete mode 100644 tests/send.rs (limited to 'tests') diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs deleted file mode 100644 index 7251204..0000000 --- a/tests/git_remote_helper.rs +++ /dev/null @@ -1,2338 +0,0 @@ -use std::{collections::HashSet, env::current_dir}; - -use anyhow::{Context, Result}; -use futures::join; -use git2::Oid; -use nostr::nips::nip01::Coordinate; -use nostr_sdk::{secp256k1::rand, Event, JsonUtil, Kind, ToBech32}; -use relay::Relay; -use serial_test::serial; -use test_utils::{git::GitTestRepo, *}; - -static NOSTR_REMOTE_NAME: &str = "nostr"; -static STATE_KIND: nostr::Kind = Kind::Custom(30618); - -fn get_nostr_remote_url() -> Result { - let repo_event = generate_repo_ref_event(); - let naddr = Coordinate { - kind: Kind::GitRepoAnnouncement, - public_key: repo_event.author(), - identifier: repo_event.identifier().unwrap().to_string(), - relays: vec![ - "ws://localhost:8055".to_string(), - "ws://localhost:8056".to_string(), - ], - } - .to_bech32()?; - Ok(format!("nostr://{naddr}")) -} - -fn prep_git_repo() -> Result { - let test_repo = GitTestRepo::without_repo_in_git_config(); - set_git_nostr_login_config(&test_repo)?; - test_repo.add_remote(NOSTR_REMOTE_NAME, &get_nostr_remote_url()?)?; - test_repo.populate()?; - Ok(test_repo) -} - -fn set_git_nostr_login_config(test_repo: &GitTestRepo) -> Result<()> { - let mut config = test_repo - .git_repo - .config() - .context("cannot open git config")?; - config.set_str("nostr.nsec", TEST_KEY_2_NSEC)?; - config.set_str("nostr.npub", TEST_KEY_2_NPUB)?; - config.set_str("user.name", "test name")?; - config.set_str("user.email", "test@test.com")?; - config.set_bool("commit.gpgSign", false)?; - Ok(()) -} - -fn clone_git_repo_with_nostr_url() -> Result { - let path = current_dir()?.join(format!("tmpgit-clone{}", rand::random::())); - std::fs::create_dir(path.clone())?; - CliTester::new_git_with_remote_helper_from_dir(&path, ["clone", &get_nostr_remote_url()?, "."]) - .expect_end_eventually_and_print()?; - let test_repo = GitTestRepo::open(&path)?; - set_git_nostr_login_config(&test_repo)?; - Ok(test_repo) -} - -fn prep_git_repo_minus_1_commit() -> Result { - let test_repo = GitTestRepo::without_repo_in_git_config(); - set_git_nostr_login_config(&test_repo)?; - test_repo.add_remote(NOSTR_REMOTE_NAME, &get_nostr_remote_url()?)?; - test_repo.populate_minus_1()?; - Ok(test_repo) -} - -fn cli_tester(git_repo: &GitTestRepo) -> CliTester { - CliTester::new_remote_helper_from_dir(&git_repo.dir, &get_nostr_remote_url().unwrap()) -} - -fn cli_tester_after_fetch(git_repo: &GitTestRepo) -> Result { - let mut p = cli_tester(git_repo); - cli_expect_nostr_fetch(&mut p)?; - Ok(p) -} - -fn cli_expect_nostr_fetch(p: &mut CliTester) -> Result<()> { - p.expect("nostr: fetching...\r\n")?; - p.expect_eventually("updates")?; // some updates - p.expect_eventually("\r\n")?; - Ok(()) -} - -/// git runs `list for-push` before `push`. in `push` we use the git server -/// remote refs downloaded by `list` to assess how to push to git servers. -/// we are therefore running it this way in our tests -fn cli_tester_after_nostr_fetch_and_sent_list_for_push_responds( - git_repo: &GitTestRepo, -) -> Result { - let mut p = cli_tester_after_fetch(git_repo)?; - - p.send_line("list for-push")?; - p.expect_eventually_and_print("\r\n\r\n")?; - Ok(p) -} - -async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> { - let git_repo = prep_git_repo()?; - git_repo.create_branch("example-branch")?; - let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); - // TODO recreate_as_bare isn't creating other branches - let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; - let example_commit_id = source_git_repo - .get_tip_of_local_branch("example-branch")? - .to_string(); - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push refs/heads/main:refs/heads/main")?; - p.send_line("")?; - // p.expect("ok refs/heads/main\r\n")?; - p.expect_eventually_and_print("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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()?; - - let state_event = r56 - .events - .iter() - .find(|e| e.kind().eq(&STATE_KIND)) - .context("state event not created")?; - - assert_eq!( - state_event - .tags - .iter() - .filter(|t| t.kind().to_string().as_str().ne("d")) - .map(|t| t.as_vec().to_vec()) - .collect::>>(), - HashSet::from([ - vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], - vec!["refs/heads/main".to_string(), main_commit_id.clone(),], - vec!["refs/heads/example-branch".to_string(), example_commit_id,], - ]), - ); - - // wait for bigger timestamp - std::thread::sleep(std::time::Duration::from_millis(1000)); - - Ok((state_event.clone(), source_git_repo)) -} - -async fn prep_source_repo_and_events_including_proposals() --> Result<(Vec, GitTestRepo)> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![source_path.to_string()]), - state_event, - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - cli_tester_create_proposals()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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((r55.events, source_git_repo)) -} - -mod initially_runs_fetch { - - use super::*; - - #[tokio::test] - #[serial] - async fn runs_fetch_and_reports() -> Result<()> { - let source_git_repo = prep_git_repo()?; - let git_repo = prep_git_repo()?; - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - // // check relay had the right number of events - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_after_fetch(&git_repo)?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - - // launch relays - 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(()) - } -} - -mod list { - - use super::*; - - mod without_state_announcement { - - use super::*; - - #[tokio::test] - #[serial] - async fn lists_head_and_2_branches_and_commit_ids_from_git_server() -> Result<()> { - let source_git_repo = prep_git_repo()?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - std::fs::write(source_git_repo.dir.join("commit.md"), "some content")?; - let main_commit_id = source_git_repo.stage_and_commit("commit.md")?; - - source_git_repo.create_branch("vnext")?; - source_git_repo.checkout("vnext")?; - std::fs::write(source_git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = source_git_repo.stage_and_commit("vnext.md")?; - source_git_repo.checkout("main")?; - - let git_repo = prep_git_repo()?; - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_after_fetch(&git_repo)?; - p.send_line("list")?; - p.expect( - format!( - "fetching ref list over filesystem from {}...\r\n", - source_path - ) - .as_str(), - )?; - // println!("{}", p.expect_eventually("\r\n\r\n")?); - let res = p.expect_eventually("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - assert_eq!( - res.split("\r\n") - .map(|e| e.to_string()) - .collect::>(), - HashSet::from([ - "@refs/heads/main HEAD".to_string(), - format!("{} refs/heads/main", main_commit_id), - format!("{} refs/heads/vnext", vnext_commit_id), - ]), - ); - Ok(()) - }); - // launch relays - 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(()) - } - } - mod with_state_announcement { - - use super::*; - - mod when_announcement_matches_git_server { - - use super::*; - - #[tokio::test] - #[serial] - async fn lists_head_and_2_branches_and_commit_ids_announcement() -> Result<()> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?; - let example_commit_id = - source_git_repo.get_tip_of_local_branch("example-branch")?; - - let git_repo = prep_git_repo()?; - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - state_event, - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_after_fetch(&git_repo)?; - p.send_line("list")?; - p.expect( - format!( - "fetching ref list over filesystem from {}...\r\n", - source_path - ) - .as_str(), - )?; - // println!("{}", p.expect_eventually("\r\n\r\n")?); - let res = p.expect_eventually("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - assert_eq!( - res.split("\r\n") - .map(|e| e.to_string()) - .collect::>(), - HashSet::from([ - "@refs/heads/main HEAD".to_string(), - format!("{} refs/heads/main", main_commit_id), - format!("{} refs/heads/example-branch", example_commit_id), - ]), - ); - - Ok(()) - }); - // launch relays - 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(()) - } - } - mod when_announcement_doesnt_match_git_server { - - use super::*; - - #[tokio::test] - #[serial] - async fn anouncement_state_is_used() -> Result<()> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - let main_original_commit_id = source_git_repo.get_tip_of_local_branch("main")?; - - { - // add commit to main on git server - let tmp_repo = GitTestRepo::clone_repo(&source_git_repo)?; - std::fs::write(tmp_repo.dir.join("commitx.md"), "some content")?; - tmp_repo.stage_and_commit("commitx.md")?; - let mut remote = tmp_repo.git_repo.find_remote("origin")?; - remote.push(&["refs/heads/main:refs/heads/main"], None)?; - } - - let main_updated_commit_id = source_git_repo.get_tip_of_local_branch("main")?; - assert_ne!(main_original_commit_id, main_updated_commit_id); - let example_commit_id = - source_git_repo.get_tip_of_local_branch("example-branch")?; - - let git_repo = prep_git_repo()?; - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - state_event, - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_after_fetch(&git_repo)?; - p.send_line("list")?; - p.expect( - format!( - "fetching ref list over filesystem from {}...\r\n", - source_path - ) - .as_str(), - )?; - p.expect( - format!( - "WARNING: {} refs/heads/main is out of sync with nostr \r\n", - source_path - ) - .as_str(), - )?; - - // println!("{}", p.expect_eventually("\r\n\r\n")?); - let res = p.expect_eventually("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - assert_eq!( - res.split("\r\n") - .map(|e| e.to_string()) - .collect::>(), - HashSet::from([ - "@refs/heads/main HEAD".to_string(), - format!("{} refs/heads/main", main_original_commit_id), - format!("{} refs/heads/example-branch", example_commit_id), - ]), - ); - Ok(()) - }); - // launch relays - 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(()) - } - } - - mod when_there_are_open_proposals { - - use super::*; - - #[tokio::test] - #[serial] - async fn open_proposal_listed_in_prs_namespace() -> Result<()> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?; - let example_commit_id = - source_git_repo.get_tip_of_local_branch("example-branch")?; - - let git_repo = prep_git_repo()?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - state_event, - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result { - cli_tester_create_proposals()?; - - let mut p = cli_tester_after_fetch(&git_repo)?; - p.send_line("list")?; - p.expect( - format!( - "fetching ref list over filesystem from {}...\r\n", - source_path - ) - .as_str(), - )?; - // println!("{}", p.expect_eventually("\r\n\r\n")?); - let res = p.expect_eventually("\r\n\r\n")?; - - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(res) - }); - // launch relays - 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(), - ); - - let res = cli_tester_handle.join().unwrap()?; - - let proposal_creation_repo = cli_tester_create_proposal_branches_ready_to_send()?; - - let mut pr_refs = vec![]; - for name in [ - FEATURE_BRANCH_NAME_1, - FEATURE_BRANCH_NAME_2, - FEATURE_BRANCH_NAME_3, - ] { - pr_refs.push(format!( - "{} refs/heads/{}", - proposal_creation_repo.get_tip_of_local_branch(name)?, - get_proposal_branch_name_from_events(&r55.events, name)?, - )); - } - - assert_eq!( - res.split("\r\n") - .map(|e| e.to_string()) - .collect::>(), - [ - vec![ - "@refs/heads/main HEAD".to_string(), - format!("{} refs/heads/main", main_commit_id), - format!("{} refs/heads/example-branch", example_commit_id), - ], - pr_refs, - ] - .concat() - .iter() - .cloned() - .collect::>() - ); - - Ok(()) - } - } - } -} - -mod fetch { - - use super::*; - - #[tokio::test] - #[serial] - async fn fetch_downloads_speficied_commits_from_git_server() -> Result<()> { - let source_git_repo = prep_git_repo()?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - std::fs::write(source_git_repo.dir.join("commit.md"), "some content")?; - let main_commit_id = source_git_repo.stage_and_commit("commit.md")?; - - source_git_repo.create_branch("vnext")?; - source_git_repo.checkout("vnext")?; - std::fs::write(source_git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = source_git_repo.stage_and_commit("vnext.md")?; - - let git_repo = prep_git_repo()?; - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - assert!(git_repo.git_repo.find_commit(main_commit_id).is_err()); - assert!(git_repo.git_repo.find_commit(vnext_commit_id).is_err()); - - let mut p = cli_tester_after_fetch(&git_repo)?; - p.send_line(format!("fetch {main_commit_id} main").as_str())?; - p.send_line(format!("fetch {vnext_commit_id} vnext").as_str())?; - p.send_line("")?; - p.expect(format!("fetching over filesystem from {source_path}...\r\n").as_str())?; - p.expect_eventually_and_print("\r\n")?; - - assert!(git_repo.git_repo.find_commit(main_commit_id).is_ok()); - assert!(git_repo.git_repo.find_commit(vnext_commit_id).is_ok()); - - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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(()) - } - - mod when_first_git_server_fails_ { - use super::*; - - #[tokio::test] - #[serial] - async fn fetch_downloads_speficied_commits_from_second_git_server() -> Result<()> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - // let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - let error_path = "./path-doesnt-exist".to_string(); - - let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?; - - let git_repo = prep_git_repo_minus_1_commit()?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - error_path.to_string(), - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - state_event, - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - assert!(git_repo.git_repo.find_commit(main_commit_id).is_err()); - - let mut p = cli_tester_after_fetch(&git_repo)?; - p.send_line(format!("fetch {main_commit_id} main").as_str())?; - p.send_line("")?; - p.expect(format!("fetching over filesystem from {error_path}...\r\n").as_str())?; - // not sure why the below isn't appearing - // p.expect(format!("fetching over filesystem from - // {source_path}...\r\n").as_str())?; - p.expect_eventually_and_print("\r\n")?; - // p.expect("\r\n")?; - - assert!(git_repo.git_repo.find_commit(main_commit_id).is_ok()); - - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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 creates_commits_from_open_proposal_with_no_warngins_printed() -> Result<()> { - let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - 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), - Relay::new(8057, None, None), - ); - r51.events = events.clone(); - r55.events = events.clone(); - - let git_repo = prep_git_repo()?; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; - let proposal_tip = cli_tester_create_proposal_branches_ready_to_send()? - .get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?; - - assert!(git_repo.git_repo.find_commit(proposal_tip).is_err()); - - let mut p = cli_tester_after_fetch(&git_repo)?; - p.send_line(format!("fetch {proposal_tip} refs/heads/{branch_name}").as_str())?; - p.send_line("")?; - p.expect(format!("fetching over filesystem from {source_path}...\r\n").as_str())?; - // expect no errors - p.expect_after_whitespace("\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - - assert!(git_repo.git_repo.find_commit(proposal_tip).is_ok()); - - Ok(()) - }); - // launch relays - 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(()) - } -} - -mod push { - - use super::*; - - #[tokio::test] - #[serial] - async fn new_branch_when_no_state_event_exists() -> Result<()> { - generate_repo_with_state_event().await?; - Ok(()) - } - mod two_branches_in_batch_one_added_one_updated { - - use super::*; - - #[tokio::test] - #[serial] - async fn updates_branch_on_git_server() -> Result<()> { - let git_repo = prep_git_repo()?; - let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; - - std::fs::write(git_repo.dir.join("commit.md"), "some content")?; - let main_commit_id = git_repo.stage_and_commit("commit.md")?; - - git_repo.create_branch("vnext")?; - git_repo.checkout("vnext")?; - std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - assert_ne!( - source_git_repo.get_tip_of_local_branch("main")?, - main_commit_id - ); - - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - - p.send_line("push refs/heads/main:refs/heads/main")?; - p.send_line("push refs/heads/vnext:refs/heads/vnext")?; - p.send_line("")?; - p.expect_eventually("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - - assert_eq!( - source_git_repo.get_tip_of_local_branch("main")?, - main_commit_id - ); - - assert_eq!( - source_git_repo.get_tip_of_local_branch("vnext")?, - vnext_commit_id - ); - - Ok(()) - }); - // launch relays - 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 remote_refs_updated_in_local_git() -> Result<()> { - let git_repo = prep_git_repo()?; - let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; - - std::fs::write(git_repo.dir.join("commit.md"), "some content")?; - let main_commit_id = git_repo.stage_and_commit("commit.md")?; - - git_repo.create_branch("vnext")?; - git_repo.checkout("vnext")?; - std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - assert_ne!( - source_git_repo.get_tip_of_local_branch("main")?, - main_commit_id - ); - - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push refs/heads/main:refs/heads/main")?; - p.send_line("push refs/heads/vnext:refs/heads/vnext")?; - p.send_line("")?; - p.expect_eventually("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - - assert_eq!( - git_repo - .git_repo - .find_reference("refs/remotes/nostr/main")? - .peel_to_commit()? - .id(), - main_commit_id, - ); - - assert_eq!( - git_repo - .git_repo - .find_reference("refs/remotes/nostr/vnext")? - .peel_to_commit()? - .id(), - vnext_commit_id - ); - - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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 prints_git_helper_ok_respose() -> Result<()> { - let git_repo = prep_git_repo()?; - let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; - - std::fs::write(git_repo.dir.join("commit.md"), "some content")?; - let main_commit_id = git_repo.stage_and_commit("commit.md")?; - - git_repo.create_branch("vnext")?; - git_repo.checkout("vnext")?; - std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; - git_repo.stage_and_commit("vnext.md")?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - assert_ne!( - source_git_repo.get_tip_of_local_branch("main")?, - main_commit_id - ); - - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - - p.send_line("push refs/heads/main:refs/heads/main")?; - p.send_line("push refs/heads/vnext:refs/heads/vnext")?; - p.send_line("")?; - p.expect("ok refs/heads/main\r\n")?; - p.expect("ok refs/heads/vnext\r\n")?; - p.expect("\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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 when_no_existing_state_event_state_on_git_server_published_in_nostr_state_event() - -> Result<()> { - let git_repo = prep_git_repo()?; - let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; - - std::fs::write(git_repo.dir.join("commit.md"), "some content")?; - let main_commit_id = git_repo.stage_and_commit("commit.md")?; - - git_repo.create_branch("vnext")?; - git_repo.checkout("vnext")?; - std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push refs/heads/main:refs/heads/main")?; - p.send_line("push refs/heads/vnext:refs/heads/vnext")?; - p.send_line("")?; - p.expect_eventually_and_print("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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()?; - - let state_event = r56 - .events - .iter() - .find(|e| e.kind().eq(&STATE_KIND)) - .context("state event not created")?; - - assert_eq!( - state_event.identifier(), - generate_repo_ref_event().identifier(), - ); - // println!("{:#?}", state_event); - assert_eq!( - state_event - .tags - .iter() - .filter(|t| t.kind().to_string().as_str().ne("d")) - .map(|t| t.as_vec().to_vec()) - .collect::>>(), - HashSet::from([ - vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], - vec!["refs/heads/main".to_string(), main_commit_id.to_string()], - vec!["refs/heads/vnext".to_string(), vnext_commit_id.to_string()], - ]), - ); - Ok(()) - } - - #[tokio::test] - #[serial] - async fn existing_state_event_published_in_nostr_state_event() -> Result<()> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - - let git_repo = prep_git_repo()?; - let example_branch_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example - - std::fs::write(git_repo.dir.join("new.md"), "some content")?; - let main_commit_id = git_repo.stage_and_commit("new.md")?; - git_repo.create_branch("vnext")?; - git_repo.checkout("vnext")?; - std::fs::write(git_repo.dir.join("more.md"), "some content")?; - let vnext_commit_id = git_repo.stage_and_commit("more.md")?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - state_event.clone(), - ]; - - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push refs/heads/main:refs/heads/main")?; - p.send_line("push refs/heads/vnext:refs/heads/vnext")?; - p.send_line("")?; - p.expect_eventually_and_print("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - // local refs updated - assert_eq!( - git_repo - .git_repo - .find_reference("refs/remotes/nostr/main")? - .peel_to_commit()? - .id(), - main_commit_id, - ); - - assert_eq!( - git_repo - .git_repo - .find_reference("refs/remotes/nostr/vnext")? - .peel_to_commit()? - .id(), - vnext_commit_id - ); - Ok(()) - }); - // launch relays - 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()?; - - // git_server updated - assert_eq!( - source_git_repo.get_tip_of_local_branch("main")?, - main_commit_id - ); - - assert_eq!( - source_git_repo.get_tip_of_local_branch("vnext")?, - vnext_commit_id - ); - - // state annoucement updated - let state_event = r56 - .events - .iter() - .find(|e| e.kind().eq(&STATE_KIND)) - .context("state event not created")?; - - // println!("{:#?}", state_event); - assert_eq!( - state_event - .tags - .iter() - .filter(|t| t.kind().to_string().as_str().ne("d")) - .map(|t| t.as_vec().to_vec()) - .collect::>>(), - HashSet::from([ - vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], - vec!["refs/heads/main".to_string(), main_commit_id.to_string()], - vec![ - "refs/heads/example-branch".to_string(), - example_branch_commit_id.to_string() - ], - vec!["refs/heads/vnext".to_string(), vnext_commit_id.to_string()], - ]), - ); - Ok(()) - } - } - mod delete_one_branch { - - use super::*; - - #[tokio::test] - #[serial] - async fn deletes_branch_on_git_server() -> Result<()> { - let git_repo = prep_git_repo()?; - - git_repo.create_branch("vnext")?; - git_repo.checkout("vnext")?; - std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; - - let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - assert_eq!( - source_git_repo - .git_repo - .find_reference("refs/heads/vnext")? - .peel_to_commit()? - .id(), - vnext_commit_id - ); - - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push :refs/heads/vnext")?; - p.send_line("")?; - p.expect_eventually_and_print("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - - assert!( - source_git_repo - .git_repo - .find_reference("refs/heads/vnext") - .is_err() - ); - Ok(()) - }); - // launch relays - 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 remote_refs_updated_in_local_git() -> Result<()> { - let git_repo = prep_git_repo()?; - - git_repo.create_branch("vnext")?; - git_repo.checkout("vnext")?; - std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; - - let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; - - git_repo - .git_repo - .reference("refs/remotes/nostr/vnext", vnext_commit_id, true, "")?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - assert_eq!( - git_repo - .git_repo - .find_reference("refs/remotes/nostr/vnext")? - .peel_to_commit()? - .id(), - vnext_commit_id - ); - - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push :refs/heads/vnext")?; - p.send_line("")?; - p.expect_eventually("\r\n\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - assert!( - git_repo - .git_repo - .find_reference("refs/remotes/nostr/vnext") - .is_err() - ); - Ok(()) - }); - // launch relays - 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 prints_git_helper_ok_respose() -> Result<()> { - let git_repo = prep_git_repo()?; - - git_repo.create_branch("vnext")?; - git_repo.checkout("vnext")?; - std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; - - let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; - - git_repo - .git_repo - .reference("refs/remotes/nostr/vnext", vnext_commit_id, true, "")?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - ]; - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push :refs/heads/vnext")?; - p.send_line("")?; - // let res = p.expect_eventually("\r\n\r\n")?; - // println!("{res}"); - p.expect("ok refs/heads/vnext\r\n")?; - p.expect("\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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(()) - } - - mod when_existing_state_event { - use super::*; - - #[tokio::test] - #[serial] - async fn state_event_updated_and_branch_deleted_and_ok_printed() -> Result<()> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - - let git_repo = prep_git_repo()?; - let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - state_event.clone(), - ]; - - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = - cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push :refs/heads/example-branch")?; - p.send_line("")?; - p.expect("ok refs/heads/example-branch\r\n")?; - p.expect("\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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()?; - - let state_event = r56 - .events - .iter() - .find(|e| e.kind().eq(&STATE_KIND)) - .context("state event not created")?; - - // println!("{:#?}", state_event); - assert_eq!( - state_event - .tags - .iter() - .filter(|t| t.kind().to_string().as_str().ne("d")) - .map(|t| t.as_vec().to_vec()) - .collect::>>(), - HashSet::from([ - vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], - vec!["refs/heads/main".to_string(), main_commit_id.to_string()], - ]), - ); - Ok(()) - } - - mod already_deleted_on_git_server { - use super::*; - - #[tokio::test] - #[serial] - async fn existing_state_event_updated_and_ok_printed() -> Result<()> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - - { - // delete branch on git server - let tmp_repo = GitTestRepo::clone_repo(&source_git_repo)?; - let mut remote = tmp_repo.git_repo.find_remote("origin")?; - remote.push(&[":refs/heads/example-branch"], None)?; - } - - let git_repo = prep_git_repo()?; - let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - ]), - state_event.clone(), - ]; - - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds( - &git_repo, - )?; - p.send_line("push :refs/heads/example-branch")?; - p.send_line("")?; - p.expect("ok refs/heads/example-branch\r\n")?; - p.expect("\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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()?; - - let state_event = r56 - .events - .iter() - .find(|e| e.kind().eq(&STATE_KIND)) - .context("state event not created")?; - - // println!("{:#?}", state_event); - assert_eq!( - state_event - .tags - .iter() - .filter(|t| t.kind().to_string().as_str().ne("d")) - .map(|t| t.as_vec().to_vec()) - .collect::>>(), - HashSet::from([ - vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], - vec!["refs/heads/main".to_string(), main_commit_id.to_string()], - ]), - ); - Ok(()) - } - } - } - } - - #[tokio::test] - #[serial] - async fn pushes_to_all_git_servers_listed_and_ok_printed() -> Result<()> { - let (state_event, source_git_repo) = generate_repo_with_state_event().await?; - let second_source_git_repo = GitTestRepo::duplicate(&source_git_repo)?; - - // uppdate announcement with extra git server - - let git_repo = prep_git_repo()?; - - std::fs::write(git_repo.dir.join("new.md"), "some content")?; - let main_commit_id = git_repo.stage_and_commit("new.md")?; - - let events = vec![ - generate_test_key_1_metadata_event("fred"), - generate_test_key_1_relay_list_event(), - generate_repo_ref_event_with_git_server(vec![ - source_git_repo.dir.to_str().unwrap().to_string(), - second_source_git_repo.dir.to_str().unwrap().to_string(), - ]), - state_event.clone(), - ]; - - // 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, None), - 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), - ); - r51.events = events.clone(); - r55.events = events; - - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; - p.send_line("push refs/heads/main:refs/heads/main")?; - p.send_line("")?; - p.expect("ok refs/heads/main\r\n")?; - p.expect("\r\n")?; - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - Ok(()) - }); - // launch relays - 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()?; - - // git_server updated - assert_eq!( - source_git_repo.get_tip_of_local_branch("main")?, - main_commit_id - ); - assert_eq!( - second_source_git_repo.get_tip_of_local_branch("main")?, - main_commit_id - ); - - Ok(()) - } - - #[tokio::test] - #[serial] - async fn proposal_merge_commit_pushed_to_main_leads_to_status_event_issued() -> Result<()> { - // - let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - 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), - Relay::new(8057, None, None), - ); - r51.events = events.clone(); - r55.events = events.clone(); - - #[allow(clippy::mutable_key_type)] - let before = r55.events.iter().cloned().collect::>(); - - let cli_tester_handle = std::thread::spawn(move || -> Result<(String, Oid)> { - let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; - - let git_repo = clone_git_repo_with_nostr_url()?; - git_repo.checkout_remote_branch(&branch_name)?; - git_repo.checkout("refs/heads/main")?; - - std::fs::write(git_repo.dir.join("new.md"), "some content")?; - git_repo.stage_and_commit("new.md")?; - - CliTester::new_git_with_remote_helper_from_dir( - &git_repo.dir, - ["merge", &branch_name, "-m", "proposal merge commit message"], - ) - .expect_end_eventually_and_print()?; - - let oid = git_repo.get_tip_of_local_branch("main")?; - - let mut p = CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push"]); - cli_expect_nostr_fetch(&mut p)?; - p.expect( - format!( - "fetching ref list over filesystem from {}...\r\n", - source_path - ) - .as_str(), - )?; - - p.expect("merge commit ")?; - // shorthand merge commit id appears in this gap - p.expect_eventually(": create nostr proposal status event\r\n")?; - p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; - let output = p.expect_end_eventually()?; - - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - - Ok((output, oid)) - }); - // launch relays - 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(), - ); - - let (output, oid) = cli_tester_handle.join().unwrap()?; - - assert_eq!( - output, - format!(" 431b84e..{} main -> main\r\n", &oid.to_string()[..7]) - ); - - let new_events = r55 - .events - .iter() - .cloned() - .collect::>() - .difference(&before) - .cloned() - .collect::>(); - - assert_eq!(new_events.len(), 2, "{new_events:?}"); - - let proposal = r55 - .events - .iter() - .find(|e| { - e.tags() - .iter() - .find(|t| t.as_vec()[0].eq("branch-name")) - .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1)) - }) - .unwrap(); - - let merge_status = new_events - .iter() - .find(|e| e.kind().eq(&Kind::GitStatusApplied)) - .unwrap(); - - assert_eq!( - oid.to_string(), - merge_status - .tags - .iter() - .find(|t| t.as_vec()[0].eq("merge-commit-id")) - .unwrap() - .as_vec()[1], - "status sets correct merge-commit-id tag" - ); - - let proposal_tip = r55 - .events - .iter() - .filter(|e| { - e.tags() - .iter() - .any(|t| t.as_vec()[1].eq(&proposal.id().to_string())) - && e.kind().eq(&Kind::GitPatch) - }) - .last() - .unwrap(); - - assert_eq!( - proposal_tip.id().to_string(), - merge_status - .tags - .iter() - .find(|t| t.as_vec().len().eq(&4) && t.as_vec()[3].eq("mention")) - .unwrap() - .as_vec()[1], - "status mentions proposal tip event \r\nmerge status:\r\n{}\r\nproposal tip:\r\n{}", - merge_status.as_json(), - proposal_tip.as_json(), - ); - - assert_eq!( - proposal.id().to_string(), - merge_status - .tags - .iter() - .find(|t| t.is_root()) - .unwrap() - .as_vec()[1], - "status tags proposal id as root \r\nmerge status:\r\n{}\r\nproposal:\r\n{}", - merge_status.as_json(), - proposal.as_json(), - ); - - Ok(()) - } - - #[tokio::test] - #[serial] - async fn push_2_commits_to_existing_proposal() -> Result<()> { - let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - 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), - Relay::new(8057, None, None), - ); - r51.events = events.clone(); - r55.events = events.clone(); - - #[allow(clippy::mutable_key_type)] - let before = r55.events.iter().cloned().collect::>(); - - let cli_tester_handle = std::thread::spawn(move || -> Result<(String, String)> { - let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; - - let git_repo = clone_git_repo_with_nostr_url()?; - git_repo.checkout_remote_branch(&branch_name)?; - - std::fs::write(git_repo.dir.join("new.md"), "some content")?; - git_repo.stage_and_commit("new.md")?; - - std::fs::write(git_repo.dir.join("new2.md"), "some content")?; - git_repo.stage_and_commit("new2.md")?; - - let mut p = CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push"]); - cli_expect_nostr_fetch(&mut p)?; - p.expect( - format!( - "fetching ref list over filesystem from {}...\r\n", - source_path - ) - .as_str(), - )?; - p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; - let output = p.expect_end_eventually()?; - - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - - Ok((output, branch_name)) - }); - // launch relays - 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(), - ); - - let (output, branch_name) = cli_tester_handle.join().unwrap()?; - - assert_eq!( - output, - format!(" eb5d678..7de5e41 {branch_name} -> {branch_name}\r\n").as_str(), - ); - - let new_events = r55 - .events - .iter() - .cloned() - .collect::>() - .difference(&before) - .cloned() - .collect::>(); - assert_eq!(new_events.len(), 2); - let first_new_patch = new_events - .iter() - .find(|e| e.content.contains("new.md")) - .unwrap(); - let second_new_patch = new_events - .iter() - .find(|e| e.content.contains("new2.md")) - .unwrap(); - assert!( - first_new_patch.content.contains("[PATCH 3/4]"), - "first patch labeled with [PATCH 3/4]" - ); - assert!( - second_new_patch.content.contains("[PATCH 4/4]"), - "second patch labeled with [PATCH 4/4]" - ); - - let proposal = r55 - .events - .iter() - .find(|e| { - e.tags() - .iter() - .find(|t| t.as_vec()[0].eq("branch-name")) - .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1)) - }) - .unwrap(); - - assert_eq!( - proposal.id().to_string(), - first_new_patch - .tags - .iter() - .find(|t| t.is_root()) - .unwrap() - .as_vec()[1], - "first patch sets proposal id as root" - ); - - assert_eq!( - first_new_patch.id().to_string(), - second_new_patch - .tags - .iter() - .find(|t| t.is_reply()) - .unwrap() - .as_vec()[1], - "second new patch replies to the first new patch" - ); - - let previous_proposal_tip_event = r55 - .events - .iter() - .find(|e| { - e.tags() - .iter() - .any(|t| t.as_vec()[1].eq(&proposal.id().to_string())) - && e.content.contains("[PATCH 2/2]") - }) - .unwrap(); - - assert_eq!( - previous_proposal_tip_event.id().to_string(), - first_new_patch - .tags - .iter() - .find(|t| t.is_reply()) - .unwrap() - .as_vec()[1], - "first patch replies to the previous tip of proposal" - ); - - Ok(()) - } - - #[tokio::test] - #[serial] - async fn force_push_creates_proposal_revision() -> Result<()> { - let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - 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), - Relay::new(8057, None, None), - ); - r51.events = events.clone(); - r55.events = events.clone(); - - #[allow(clippy::mutable_key_type)] - let before = r55.events.iter().cloned().collect::>(); - - let cli_tester_handle = std::thread::spawn(move || -> Result<(String, String)> { - let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; - - let git_repo = clone_git_repo_with_nostr_url()?; - let oid = git_repo.checkout_remote_branch(&branch_name)?; - // remove last commit - git_repo.checkout("main")?; - git_repo.git_repo.branch( - &branch_name, - &git_repo.git_repo.find_commit(oid)?.parent(0)?, - true, - )?; - git_repo.checkout(&branch_name)?; - - std::fs::write(git_repo.dir.join("new.md"), "some content")?; - git_repo.stage_and_commit("new.md")?; - - std::fs::write(git_repo.dir.join("new2.md"), "some content")?; - git_repo.stage_and_commit("new2.md")?; - - let mut p = - CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push", "--force"]); - cli_expect_nostr_fetch(&mut p)?; - p.expect( - format!( - "fetching ref list over filesystem from {}...\r\n", - source_path - ) - .as_str(), - )?; - p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; - let output = p.expect_end_eventually()?; - - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - - Ok((output, branch_name)) - }); - // launch relays - 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(), - ); - - let (output, branch_name) = cli_tester_handle.join().unwrap()?; - - assert_eq!( - output, - format!(" + eb5d678...8a296c8 {branch_name} -> {branch_name} (forced update)\r\n") - .as_str(), - ); - - let new_events = r55 - .events - .iter() - .cloned() - .collect::>() - .difference(&before) - .cloned() - .collect::>(); - assert_eq!(new_events.len(), 3); - - let proposal = r55 - .events - .iter() - .find(|e| { - e.tags() - .iter() - .find(|t| t.as_vec()[0].eq("branch-name")) - .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1)) - }) - .unwrap(); - - let revision_root_patch = new_events - .iter() - .find(|e| e.tags().iter().any(|t| t.as_vec()[1].eq("revision-root"))) - .unwrap(); - - assert_eq!( - proposal.id().to_string(), - revision_root_patch - .tags - .iter() - .find(|t| t.is_reply()) - .unwrap() - .as_vec()[1], - "revision root patch replies to original proposal" - ); - - assert!( - revision_root_patch.content.contains("[PATCH 1/3]"), - "revision root labeled with [PATCH 1/3] event: {revision_root_patch:?}", - ); - - let second_patch = new_events - .iter() - .find(|e| e.content.contains("new.md")) - .unwrap(); - let third_patch = new_events - .iter() - .find(|e| e.content.contains("new2.md")) - .unwrap(); - assert!( - second_patch.content.contains("[PATCH 2/3]"), - "second patch labeled with [PATCH 2/3]" - ); - assert!( - third_patch.content.contains("[PATCH 3/3]"), - "third patch labeled with [PATCH 3/3]" - ); - - assert_eq!( - revision_root_patch.id().to_string(), - second_patch - .tags - .iter() - .find(|t| t.is_root()) - .unwrap() - .as_vec()[1], - "second patch sets revision id as root" - ); - - assert_eq!( - second_patch.id().to_string(), - third_patch - .tags - .iter() - .find(|t| t.is_reply()) - .unwrap() - .as_vec()[1], - "third patch replies to the second new patch" - ); - - Ok(()) - } - - #[tokio::test] - #[serial] - async fn push_new_pr_branch_creates_proposal() -> Result<()> { - let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; - let source_path = source_git_repo.dir.to_str().unwrap().to_string(); - - let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( - 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), - Relay::new(8057, None, None), - ); - r51.events = events.clone(); - r55.events = events.clone(); - - #[allow(clippy::mutable_key_type)] - let before = r55.events.iter().cloned().collect::>(); - let branch_name = "pr/my-new-proposal"; - - let cli_tester_handle = std::thread::spawn(move || -> Result { - let mut git_repo = clone_git_repo_with_nostr_url()?; - git_repo.delete_dir_on_drop = false; - git_repo.create_branch(branch_name)?; - git_repo.checkout(branch_name)?; - - std::fs::write(git_repo.dir.join("new.md"), "some content")?; - git_repo.stage_and_commit("new.md")?; - - std::fs::write(git_repo.dir.join("new2.md"), "some content")?; - git_repo.stage_and_commit("new2.md")?; - - let mut p = CliTester::new_git_with_remote_helper_from_dir( - &git_repo.dir, - ["push", "-u", "origin", branch_name], - ); - cli_expect_nostr_fetch(&mut p)?; - p.expect( - format!( - "fetching ref list over filesystem from {}...\r\n", - source_path - ) - .as_str(), - )?; - p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; - let output = p.expect_end_eventually()?; - - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } - - Ok(output) - }); - // launch relays - 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(), - ); - - let output = cli_tester_handle.join().unwrap()?; - - assert_eq!( - output, - format!(" * [new branch] {branch_name} -> {branch_name}\r\nbranch '{branch_name}' set up to track 'origin/{branch_name}'.\r\n").as_str(), - ); - - let new_events = r55 - .events - .iter() - .cloned() - .collect::>() - .difference(&before) - .cloned() - .collect::>(); - assert_eq!(new_events.len(), 2); - - let proposal = new_events - .iter() - .find(|e| e.tags().iter().any(|t| t.as_vec()[1].eq("root"))) - .unwrap(); - - assert!( - proposal.content.contains("new.md"), - "first patch is proposal root" - ); - - assert!( - proposal.content.contains("[PATCH 1/2]"), - "proposal root labeled with[PATCH 1/2] event: {proposal:?}", - ); - - assert_eq!( - proposal - .tags() - .iter() - .find(|t| t.as_vec()[0].eq("branch-name")) - .unwrap() - .as_vec()[1], - branch_name.replace("pr/", ""), - ); - - let second_patch = new_events - .iter() - .find(|e| e.content.contains("new2.md")) - .unwrap(); - - assert!( - second_patch.content.contains("[PATCH 2/2]"), - "second patch labeled with [PATCH 2/2]" - ); - - assert_eq!( - proposal.id().to_string(), - second_patch - .tags - .iter() - .find(|t| t.is_root()) - .unwrap() - .as_vec()[1], - "second patch sets proposal id as root" - ); - - Ok(()) - } -} diff --git a/tests/git_remote_nostr/main.rs b/tests/git_remote_nostr/main.rs new file mode 100644 index 0000000..7251204 --- /dev/null +++ b/tests/git_remote_nostr/main.rs @@ -0,0 +1,2338 @@ +use std::{collections::HashSet, env::current_dir}; + +use anyhow::{Context, Result}; +use futures::join; +use git2::Oid; +use nostr::nips::nip01::Coordinate; +use nostr_sdk::{secp256k1::rand, Event, JsonUtil, Kind, ToBech32}; +use relay::Relay; +use serial_test::serial; +use test_utils::{git::GitTestRepo, *}; + +static NOSTR_REMOTE_NAME: &str = "nostr"; +static STATE_KIND: nostr::Kind = Kind::Custom(30618); + +fn get_nostr_remote_url() -> Result { + let repo_event = generate_repo_ref_event(); + let naddr = Coordinate { + kind: Kind::GitRepoAnnouncement, + public_key: repo_event.author(), + identifier: repo_event.identifier().unwrap().to_string(), + relays: vec![ + "ws://localhost:8055".to_string(), + "ws://localhost:8056".to_string(), + ], + } + .to_bech32()?; + Ok(format!("nostr://{naddr}")) +} + +fn prep_git_repo() -> Result { + let test_repo = GitTestRepo::without_repo_in_git_config(); + set_git_nostr_login_config(&test_repo)?; + test_repo.add_remote(NOSTR_REMOTE_NAME, &get_nostr_remote_url()?)?; + test_repo.populate()?; + Ok(test_repo) +} + +fn set_git_nostr_login_config(test_repo: &GitTestRepo) -> Result<()> { + let mut config = test_repo + .git_repo + .config() + .context("cannot open git config")?; + config.set_str("nostr.nsec", TEST_KEY_2_NSEC)?; + config.set_str("nostr.npub", TEST_KEY_2_NPUB)?; + config.set_str("user.name", "test name")?; + config.set_str("user.email", "test@test.com")?; + config.set_bool("commit.gpgSign", false)?; + Ok(()) +} + +fn clone_git_repo_with_nostr_url() -> Result { + let path = current_dir()?.join(format!("tmpgit-clone{}", rand::random::())); + std::fs::create_dir(path.clone())?; + CliTester::new_git_with_remote_helper_from_dir(&path, ["clone", &get_nostr_remote_url()?, "."]) + .expect_end_eventually_and_print()?; + let test_repo = GitTestRepo::open(&path)?; + set_git_nostr_login_config(&test_repo)?; + Ok(test_repo) +} + +fn prep_git_repo_minus_1_commit() -> Result { + let test_repo = GitTestRepo::without_repo_in_git_config(); + set_git_nostr_login_config(&test_repo)?; + test_repo.add_remote(NOSTR_REMOTE_NAME, &get_nostr_remote_url()?)?; + test_repo.populate_minus_1()?; + Ok(test_repo) +} + +fn cli_tester(git_repo: &GitTestRepo) -> CliTester { + CliTester::new_remote_helper_from_dir(&git_repo.dir, &get_nostr_remote_url().unwrap()) +} + +fn cli_tester_after_fetch(git_repo: &GitTestRepo) -> Result { + let mut p = cli_tester(git_repo); + cli_expect_nostr_fetch(&mut p)?; + Ok(p) +} + +fn cli_expect_nostr_fetch(p: &mut CliTester) -> Result<()> { + p.expect("nostr: fetching...\r\n")?; + p.expect_eventually("updates")?; // some updates + p.expect_eventually("\r\n")?; + Ok(()) +} + +/// git runs `list for-push` before `push`. in `push` we use the git server +/// remote refs downloaded by `list` to assess how to push to git servers. +/// we are therefore running it this way in our tests +fn cli_tester_after_nostr_fetch_and_sent_list_for_push_responds( + git_repo: &GitTestRepo, +) -> Result { + let mut p = cli_tester_after_fetch(git_repo)?; + + p.send_line("list for-push")?; + p.expect_eventually_and_print("\r\n\r\n")?; + Ok(p) +} + +async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> { + let git_repo = prep_git_repo()?; + git_repo.create_branch("example-branch")?; + let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); + // TODO recreate_as_bare isn't creating other branches + let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; + let example_commit_id = source_git_repo + .get_tip_of_local_branch("example-branch")? + .to_string(); + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("")?; + // p.expect("ok refs/heads/main\r\n")?; + p.expect_eventually_and_print("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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()?; + + let state_event = r56 + .events + .iter() + .find(|e| e.kind().eq(&STATE_KIND)) + .context("state event not created")?; + + assert_eq!( + state_event + .tags + .iter() + .filter(|t| t.kind().to_string().as_str().ne("d")) + .map(|t| t.as_vec().to_vec()) + .collect::>>(), + HashSet::from([ + vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], + vec!["refs/heads/main".to_string(), main_commit_id.clone(),], + vec!["refs/heads/example-branch".to_string(), example_commit_id,], + ]), + ); + + // wait for bigger timestamp + std::thread::sleep(std::time::Duration::from_millis(1000)); + + Ok((state_event.clone(), source_git_repo)) +} + +async fn prep_source_repo_and_events_including_proposals() +-> Result<(Vec, GitTestRepo)> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![source_path.to_string()]), + state_event, + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + cli_tester_create_proposals()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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((r55.events, source_git_repo)) +} + +mod initially_runs_fetch { + + use super::*; + + #[tokio::test] + #[serial] + async fn runs_fetch_and_reports() -> Result<()> { + let source_git_repo = prep_git_repo()?; + let git_repo = prep_git_repo()?; + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + // // check relay had the right number of events + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_after_fetch(&git_repo)?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + + // launch relays + 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(()) + } +} + +mod list { + + use super::*; + + mod without_state_announcement { + + use super::*; + + #[tokio::test] + #[serial] + async fn lists_head_and_2_branches_and_commit_ids_from_git_server() -> Result<()> { + let source_git_repo = prep_git_repo()?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + std::fs::write(source_git_repo.dir.join("commit.md"), "some content")?; + let main_commit_id = source_git_repo.stage_and_commit("commit.md")?; + + source_git_repo.create_branch("vnext")?; + source_git_repo.checkout("vnext")?; + std::fs::write(source_git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = source_git_repo.stage_and_commit("vnext.md")?; + source_git_repo.checkout("main")?; + + let git_repo = prep_git_repo()?; + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("list")?; + p.expect( + format!( + "fetching ref list over filesystem from {}...\r\n", + source_path + ) + .as_str(), + )?; + // println!("{}", p.expect_eventually("\r\n\r\n")?); + let res = p.expect_eventually("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + assert_eq!( + res.split("\r\n") + .map(|e| e.to_string()) + .collect::>(), + HashSet::from([ + "@refs/heads/main HEAD".to_string(), + format!("{} refs/heads/main", main_commit_id), + format!("{} refs/heads/vnext", vnext_commit_id), + ]), + ); + Ok(()) + }); + // launch relays + 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(()) + } + } + mod with_state_announcement { + + use super::*; + + mod when_announcement_matches_git_server { + + use super::*; + + #[tokio::test] + #[serial] + async fn lists_head_and_2_branches_and_commit_ids_announcement() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?; + let example_commit_id = + source_git_repo.get_tip_of_local_branch("example-branch")?; + + let git_repo = prep_git_repo()?; + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event, + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("list")?; + p.expect( + format!( + "fetching ref list over filesystem from {}...\r\n", + source_path + ) + .as_str(), + )?; + // println!("{}", p.expect_eventually("\r\n\r\n")?); + let res = p.expect_eventually("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + assert_eq!( + res.split("\r\n") + .map(|e| e.to_string()) + .collect::>(), + HashSet::from([ + "@refs/heads/main HEAD".to_string(), + format!("{} refs/heads/main", main_commit_id), + format!("{} refs/heads/example-branch", example_commit_id), + ]), + ); + + Ok(()) + }); + // launch relays + 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(()) + } + } + mod when_announcement_doesnt_match_git_server { + + use super::*; + + #[tokio::test] + #[serial] + async fn anouncement_state_is_used() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + let main_original_commit_id = source_git_repo.get_tip_of_local_branch("main")?; + + { + // add commit to main on git server + let tmp_repo = GitTestRepo::clone_repo(&source_git_repo)?; + std::fs::write(tmp_repo.dir.join("commitx.md"), "some content")?; + tmp_repo.stage_and_commit("commitx.md")?; + let mut remote = tmp_repo.git_repo.find_remote("origin")?; + remote.push(&["refs/heads/main:refs/heads/main"], None)?; + } + + let main_updated_commit_id = source_git_repo.get_tip_of_local_branch("main")?; + assert_ne!(main_original_commit_id, main_updated_commit_id); + let example_commit_id = + source_git_repo.get_tip_of_local_branch("example-branch")?; + + let git_repo = prep_git_repo()?; + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event, + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("list")?; + p.expect( + format!( + "fetching ref list over filesystem from {}...\r\n", + source_path + ) + .as_str(), + )?; + p.expect( + format!( + "WARNING: {} refs/heads/main is out of sync with nostr \r\n", + source_path + ) + .as_str(), + )?; + + // println!("{}", p.expect_eventually("\r\n\r\n")?); + let res = p.expect_eventually("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + assert_eq!( + res.split("\r\n") + .map(|e| e.to_string()) + .collect::>(), + HashSet::from([ + "@refs/heads/main HEAD".to_string(), + format!("{} refs/heads/main", main_original_commit_id), + format!("{} refs/heads/example-branch", example_commit_id), + ]), + ); + Ok(()) + }); + // launch relays + 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(()) + } + } + + mod when_there_are_open_proposals { + + use super::*; + + #[tokio::test] + #[serial] + async fn open_proposal_listed_in_prs_namespace() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?; + let example_commit_id = + source_git_repo.get_tip_of_local_branch("example-branch")?; + + let git_repo = prep_git_repo()?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event, + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result { + cli_tester_create_proposals()?; + + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("list")?; + p.expect( + format!( + "fetching ref list over filesystem from {}...\r\n", + source_path + ) + .as_str(), + )?; + // println!("{}", p.expect_eventually("\r\n\r\n")?); + let res = p.expect_eventually("\r\n\r\n")?; + + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(res) + }); + // launch relays + 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(), + ); + + let res = cli_tester_handle.join().unwrap()?; + + let proposal_creation_repo = cli_tester_create_proposal_branches_ready_to_send()?; + + let mut pr_refs = vec![]; + for name in [ + FEATURE_BRANCH_NAME_1, + FEATURE_BRANCH_NAME_2, + FEATURE_BRANCH_NAME_3, + ] { + pr_refs.push(format!( + "{} refs/heads/{}", + proposal_creation_repo.get_tip_of_local_branch(name)?, + get_proposal_branch_name_from_events(&r55.events, name)?, + )); + } + + assert_eq!( + res.split("\r\n") + .map(|e| e.to_string()) + .collect::>(), + [ + vec![ + "@refs/heads/main HEAD".to_string(), + format!("{} refs/heads/main", main_commit_id), + format!("{} refs/heads/example-branch", example_commit_id), + ], + pr_refs, + ] + .concat() + .iter() + .cloned() + .collect::>() + ); + + Ok(()) + } + } + } +} + +mod fetch { + + use super::*; + + #[tokio::test] + #[serial] + async fn fetch_downloads_speficied_commits_from_git_server() -> Result<()> { + let source_git_repo = prep_git_repo()?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + std::fs::write(source_git_repo.dir.join("commit.md"), "some content")?; + let main_commit_id = source_git_repo.stage_and_commit("commit.md")?; + + source_git_repo.create_branch("vnext")?; + source_git_repo.checkout("vnext")?; + std::fs::write(source_git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = source_git_repo.stage_and_commit("vnext.md")?; + + let git_repo = prep_git_repo()?; + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + assert!(git_repo.git_repo.find_commit(main_commit_id).is_err()); + assert!(git_repo.git_repo.find_commit(vnext_commit_id).is_err()); + + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line(format!("fetch {main_commit_id} main").as_str())?; + p.send_line(format!("fetch {vnext_commit_id} vnext").as_str())?; + p.send_line("")?; + p.expect(format!("fetching over filesystem from {source_path}...\r\n").as_str())?; + p.expect_eventually_and_print("\r\n")?; + + assert!(git_repo.git_repo.find_commit(main_commit_id).is_ok()); + assert!(git_repo.git_repo.find_commit(vnext_commit_id).is_ok()); + + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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(()) + } + + mod when_first_git_server_fails_ { + use super::*; + + #[tokio::test] + #[serial] + async fn fetch_downloads_speficied_commits_from_second_git_server() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + // let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + let error_path = "./path-doesnt-exist".to_string(); + + let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?; + + let git_repo = prep_git_repo_minus_1_commit()?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + error_path.to_string(), + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event, + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + assert!(git_repo.git_repo.find_commit(main_commit_id).is_err()); + + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line(format!("fetch {main_commit_id} main").as_str())?; + p.send_line("")?; + p.expect(format!("fetching over filesystem from {error_path}...\r\n").as_str())?; + // not sure why the below isn't appearing + // p.expect(format!("fetching over filesystem from + // {source_path}...\r\n").as_str())?; + p.expect_eventually_and_print("\r\n")?; + // p.expect("\r\n")?; + + assert!(git_repo.git_repo.find_commit(main_commit_id).is_ok()); + + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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 creates_commits_from_open_proposal_with_no_warngins_printed() -> Result<()> { + let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + 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), + Relay::new(8057, None, None), + ); + r51.events = events.clone(); + r55.events = events.clone(); + + let git_repo = prep_git_repo()?; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; + let proposal_tip = cli_tester_create_proposal_branches_ready_to_send()? + .get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?; + + assert!(git_repo.git_repo.find_commit(proposal_tip).is_err()); + + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line(format!("fetch {proposal_tip} refs/heads/{branch_name}").as_str())?; + p.send_line("")?; + p.expect(format!("fetching over filesystem from {source_path}...\r\n").as_str())?; + // expect no errors + p.expect_after_whitespace("\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + + assert!(git_repo.git_repo.find_commit(proposal_tip).is_ok()); + + Ok(()) + }); + // launch relays + 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(()) + } +} + +mod push { + + use super::*; + + #[tokio::test] + #[serial] + async fn new_branch_when_no_state_event_exists() -> Result<()> { + generate_repo_with_state_event().await?; + Ok(()) + } + mod two_branches_in_batch_one_added_one_updated { + + use super::*; + + #[tokio::test] + #[serial] + async fn updates_branch_on_git_server() -> Result<()> { + let git_repo = prep_git_repo()?; + let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; + + std::fs::write(git_repo.dir.join("commit.md"), "some content")?; + let main_commit_id = git_repo.stage_and_commit("commit.md")?; + + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + assert_ne!( + source_git_repo.get_tip_of_local_branch("main")?, + main_commit_id + ); + + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("push refs/heads/vnext:refs/heads/vnext")?; + p.send_line("")?; + p.expect_eventually("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + + assert_eq!( + source_git_repo.get_tip_of_local_branch("main")?, + main_commit_id + ); + + assert_eq!( + source_git_repo.get_tip_of_local_branch("vnext")?, + vnext_commit_id + ); + + Ok(()) + }); + // launch relays + 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 remote_refs_updated_in_local_git() -> Result<()> { + let git_repo = prep_git_repo()?; + let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; + + std::fs::write(git_repo.dir.join("commit.md"), "some content")?; + let main_commit_id = git_repo.stage_and_commit("commit.md")?; + + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + assert_ne!( + source_git_repo.get_tip_of_local_branch("main")?, + main_commit_id + ); + + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("push refs/heads/vnext:refs/heads/vnext")?; + p.send_line("")?; + p.expect_eventually("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + + assert_eq!( + git_repo + .git_repo + .find_reference("refs/remotes/nostr/main")? + .peel_to_commit()? + .id(), + main_commit_id, + ); + + assert_eq!( + git_repo + .git_repo + .find_reference("refs/remotes/nostr/vnext")? + .peel_to_commit()? + .id(), + vnext_commit_id + ); + + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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 prints_git_helper_ok_respose() -> Result<()> { + let git_repo = prep_git_repo()?; + let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; + + std::fs::write(git_repo.dir.join("commit.md"), "some content")?; + let main_commit_id = git_repo.stage_and_commit("commit.md")?; + + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; + git_repo.stage_and_commit("vnext.md")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + assert_ne!( + source_git_repo.get_tip_of_local_branch("main")?, + main_commit_id + ); + + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("push refs/heads/vnext:refs/heads/vnext")?; + p.send_line("")?; + p.expect("ok refs/heads/main\r\n")?; + p.expect("ok refs/heads/vnext\r\n")?; + p.expect("\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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 when_no_existing_state_event_state_on_git_server_published_in_nostr_state_event() + -> Result<()> { + let git_repo = prep_git_repo()?; + let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; + + std::fs::write(git_repo.dir.join("commit.md"), "some content")?; + let main_commit_id = git_repo.stage_and_commit("commit.md")?; + + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("push refs/heads/vnext:refs/heads/vnext")?; + p.send_line("")?; + p.expect_eventually_and_print("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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()?; + + let state_event = r56 + .events + .iter() + .find(|e| e.kind().eq(&STATE_KIND)) + .context("state event not created")?; + + assert_eq!( + state_event.identifier(), + generate_repo_ref_event().identifier(), + ); + // println!("{:#?}", state_event); + assert_eq!( + state_event + .tags + .iter() + .filter(|t| t.kind().to_string().as_str().ne("d")) + .map(|t| t.as_vec().to_vec()) + .collect::>>(), + HashSet::from([ + vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], + vec!["refs/heads/main".to_string(), main_commit_id.to_string()], + vec!["refs/heads/vnext".to_string(), vnext_commit_id.to_string()], + ]), + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn existing_state_event_published_in_nostr_state_event() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + + let git_repo = prep_git_repo()?; + let example_branch_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example + + std::fs::write(git_repo.dir.join("new.md"), "some content")?; + let main_commit_id = git_repo.stage_and_commit("new.md")?; + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("more.md"), "some content")?; + let vnext_commit_id = git_repo.stage_and_commit("more.md")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event.clone(), + ]; + + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("push refs/heads/vnext:refs/heads/vnext")?; + p.send_line("")?; + p.expect_eventually_and_print("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + // local refs updated + assert_eq!( + git_repo + .git_repo + .find_reference("refs/remotes/nostr/main")? + .peel_to_commit()? + .id(), + main_commit_id, + ); + + assert_eq!( + git_repo + .git_repo + .find_reference("refs/remotes/nostr/vnext")? + .peel_to_commit()? + .id(), + vnext_commit_id + ); + Ok(()) + }); + // launch relays + 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()?; + + // git_server updated + assert_eq!( + source_git_repo.get_tip_of_local_branch("main")?, + main_commit_id + ); + + assert_eq!( + source_git_repo.get_tip_of_local_branch("vnext")?, + vnext_commit_id + ); + + // state annoucement updated + let state_event = r56 + .events + .iter() + .find(|e| e.kind().eq(&STATE_KIND)) + .context("state event not created")?; + + // println!("{:#?}", state_event); + assert_eq!( + state_event + .tags + .iter() + .filter(|t| t.kind().to_string().as_str().ne("d")) + .map(|t| t.as_vec().to_vec()) + .collect::>>(), + HashSet::from([ + vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], + vec!["refs/heads/main".to_string(), main_commit_id.to_string()], + vec![ + "refs/heads/example-branch".to_string(), + example_branch_commit_id.to_string() + ], + vec!["refs/heads/vnext".to_string(), vnext_commit_id.to_string()], + ]), + ); + Ok(()) + } + } + mod delete_one_branch { + + use super::*; + + #[tokio::test] + #[serial] + async fn deletes_branch_on_git_server() -> Result<()> { + let git_repo = prep_git_repo()?; + + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; + + let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + assert_eq!( + source_git_repo + .git_repo + .find_reference("refs/heads/vnext")? + .peel_to_commit()? + .id(), + vnext_commit_id + ); + + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push :refs/heads/vnext")?; + p.send_line("")?; + p.expect_eventually_and_print("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + + assert!( + source_git_repo + .git_repo + .find_reference("refs/heads/vnext") + .is_err() + ); + Ok(()) + }); + // launch relays + 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 remote_refs_updated_in_local_git() -> Result<()> { + let git_repo = prep_git_repo()?; + + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; + + let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; + + git_repo + .git_repo + .reference("refs/remotes/nostr/vnext", vnext_commit_id, true, "")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + assert_eq!( + git_repo + .git_repo + .find_reference("refs/remotes/nostr/vnext")? + .peel_to_commit()? + .id(), + vnext_commit_id + ); + + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push :refs/heads/vnext")?; + p.send_line("")?; + p.expect_eventually("\r\n\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + assert!( + git_repo + .git_repo + .find_reference("refs/remotes/nostr/vnext") + .is_err() + ); + Ok(()) + }); + // launch relays + 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 prints_git_helper_ok_respose() -> Result<()> { + let git_repo = prep_git_repo()?; + + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?; + + let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; + + git_repo + .git_repo + .reference("refs/remotes/nostr/vnext", vnext_commit_id, true, "")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + ]; + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push :refs/heads/vnext")?; + p.send_line("")?; + // let res = p.expect_eventually("\r\n\r\n")?; + // println!("{res}"); + p.expect("ok refs/heads/vnext\r\n")?; + p.expect("\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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(()) + } + + mod when_existing_state_event { + use super::*; + + #[tokio::test] + #[serial] + async fn state_event_updated_and_branch_deleted_and_ok_printed() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + + let git_repo = prep_git_repo()?; + let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event.clone(), + ]; + + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = + cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push :refs/heads/example-branch")?; + p.send_line("")?; + p.expect("ok refs/heads/example-branch\r\n")?; + p.expect("\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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()?; + + let state_event = r56 + .events + .iter() + .find(|e| e.kind().eq(&STATE_KIND)) + .context("state event not created")?; + + // println!("{:#?}", state_event); + assert_eq!( + state_event + .tags + .iter() + .filter(|t| t.kind().to_string().as_str().ne("d")) + .map(|t| t.as_vec().to_vec()) + .collect::>>(), + HashSet::from([ + vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], + vec!["refs/heads/main".to_string(), main_commit_id.to_string()], + ]), + ); + Ok(()) + } + + mod already_deleted_on_git_server { + use super::*; + + #[tokio::test] + #[serial] + async fn existing_state_event_updated_and_ok_printed() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + + { + // delete branch on git server + let tmp_repo = GitTestRepo::clone_repo(&source_git_repo)?; + let mut remote = tmp_repo.git_repo.find_remote("origin")?; + remote.push(&[":refs/heads/example-branch"], None)?; + } + + let git_repo = prep_git_repo()?; + let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event.clone(), + ]; + + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds( + &git_repo, + )?; + p.send_line("push :refs/heads/example-branch")?; + p.send_line("")?; + p.expect("ok refs/heads/example-branch\r\n")?; + p.expect("\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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()?; + + let state_event = r56 + .events + .iter() + .find(|e| e.kind().eq(&STATE_KIND)) + .context("state event not created")?; + + // println!("{:#?}", state_event); + assert_eq!( + state_event + .tags + .iter() + .filter(|t| t.kind().to_string().as_str().ne("d")) + .map(|t| t.as_vec().to_vec()) + .collect::>>(), + HashSet::from([ + vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], + vec!["refs/heads/main".to_string(), main_commit_id.to_string()], + ]), + ); + Ok(()) + } + } + } + } + + #[tokio::test] + #[serial] + async fn pushes_to_all_git_servers_listed_and_ok_printed() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + let second_source_git_repo = GitTestRepo::duplicate(&source_git_repo)?; + + // uppdate announcement with extra git server + + let git_repo = prep_git_repo()?; + + std::fs::write(git_repo.dir.join("new.md"), "some content")?; + let main_commit_id = git_repo.stage_and_commit("new.md")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + second_source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event.clone(), + ]; + + // 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, None), + 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), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result<()> { + let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("")?; + p.expect("ok refs/heads/main\r\n")?; + p.expect("\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // launch relays + 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()?; + + // git_server updated + assert_eq!( + source_git_repo.get_tip_of_local_branch("main")?, + main_commit_id + ); + assert_eq!( + second_source_git_repo.get_tip_of_local_branch("main")?, + main_commit_id + ); + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn proposal_merge_commit_pushed_to_main_leads_to_status_event_issued() -> Result<()> { + // + let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + 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), + Relay::new(8057, None, None), + ); + r51.events = events.clone(); + r55.events = events.clone(); + + #[allow(clippy::mutable_key_type)] + let before = r55.events.iter().cloned().collect::>(); + + let cli_tester_handle = std::thread::spawn(move || -> Result<(String, Oid)> { + let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; + + let git_repo = clone_git_repo_with_nostr_url()?; + git_repo.checkout_remote_branch(&branch_name)?; + git_repo.checkout("refs/heads/main")?; + + std::fs::write(git_repo.dir.join("new.md"), "some content")?; + git_repo.stage_and_commit("new.md")?; + + CliTester::new_git_with_remote_helper_from_dir( + &git_repo.dir, + ["merge", &branch_name, "-m", "proposal merge commit message"], + ) + .expect_end_eventually_and_print()?; + + let oid = git_repo.get_tip_of_local_branch("main")?; + + let mut p = CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push"]); + cli_expect_nostr_fetch(&mut p)?; + p.expect( + format!( + "fetching ref list over filesystem from {}...\r\n", + source_path + ) + .as_str(), + )?; + + p.expect("merge commit ")?; + // shorthand merge commit id appears in this gap + p.expect_eventually(": create nostr proposal status event\r\n")?; + p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; + let output = p.expect_end_eventually()?; + + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + + Ok((output, oid)) + }); + // launch relays + 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(), + ); + + let (output, oid) = cli_tester_handle.join().unwrap()?; + + assert_eq!( + output, + format!(" 431b84e..{} main -> main\r\n", &oid.to_string()[..7]) + ); + + let new_events = r55 + .events + .iter() + .cloned() + .collect::>() + .difference(&before) + .cloned() + .collect::>(); + + assert_eq!(new_events.len(), 2, "{new_events:?}"); + + let proposal = r55 + .events + .iter() + .find(|e| { + e.tags() + .iter() + .find(|t| t.as_vec()[0].eq("branch-name")) + .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1)) + }) + .unwrap(); + + let merge_status = new_events + .iter() + .find(|e| e.kind().eq(&Kind::GitStatusApplied)) + .unwrap(); + + assert_eq!( + oid.to_string(), + merge_status + .tags + .iter() + .find(|t| t.as_vec()[0].eq("merge-commit-id")) + .unwrap() + .as_vec()[1], + "status sets correct merge-commit-id tag" + ); + + let proposal_tip = r55 + .events + .iter() + .filter(|e| { + e.tags() + .iter() + .any(|t| t.as_vec()[1].eq(&proposal.id().to_string())) + && e.kind().eq(&Kind::GitPatch) + }) + .last() + .unwrap(); + + assert_eq!( + proposal_tip.id().to_string(), + merge_status + .tags + .iter() + .find(|t| t.as_vec().len().eq(&4) && t.as_vec()[3].eq("mention")) + .unwrap() + .as_vec()[1], + "status mentions proposal tip event \r\nmerge status:\r\n{}\r\nproposal tip:\r\n{}", + merge_status.as_json(), + proposal_tip.as_json(), + ); + + assert_eq!( + proposal.id().to_string(), + merge_status + .tags + .iter() + .find(|t| t.is_root()) + .unwrap() + .as_vec()[1], + "status tags proposal id as root \r\nmerge status:\r\n{}\r\nproposal:\r\n{}", + merge_status.as_json(), + proposal.as_json(), + ); + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn push_2_commits_to_existing_proposal() -> Result<()> { + let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + 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), + Relay::new(8057, None, None), + ); + r51.events = events.clone(); + r55.events = events.clone(); + + #[allow(clippy::mutable_key_type)] + let before = r55.events.iter().cloned().collect::>(); + + let cli_tester_handle = std::thread::spawn(move || -> Result<(String, String)> { + let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; + + let git_repo = clone_git_repo_with_nostr_url()?; + git_repo.checkout_remote_branch(&branch_name)?; + + std::fs::write(git_repo.dir.join("new.md"), "some content")?; + git_repo.stage_and_commit("new.md")?; + + std::fs::write(git_repo.dir.join("new2.md"), "some content")?; + git_repo.stage_and_commit("new2.md")?; + + let mut p = CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push"]); + cli_expect_nostr_fetch(&mut p)?; + p.expect( + format!( + "fetching ref list over filesystem from {}...\r\n", + source_path + ) + .as_str(), + )?; + p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; + let output = p.expect_end_eventually()?; + + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + + Ok((output, branch_name)) + }); + // launch relays + 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(), + ); + + let (output, branch_name) = cli_tester_handle.join().unwrap()?; + + assert_eq!( + output, + format!(" eb5d678..7de5e41 {branch_name} -> {branch_name}\r\n").as_str(), + ); + + let new_events = r55 + .events + .iter() + .cloned() + .collect::>() + .difference(&before) + .cloned() + .collect::>(); + assert_eq!(new_events.len(), 2); + let first_new_patch = new_events + .iter() + .find(|e| e.content.contains("new.md")) + .unwrap(); + let second_new_patch = new_events + .iter() + .find(|e| e.content.contains("new2.md")) + .unwrap(); + assert!( + first_new_patch.content.contains("[PATCH 3/4]"), + "first patch labeled with [PATCH 3/4]" + ); + assert!( + second_new_patch.content.contains("[PATCH 4/4]"), + "second patch labeled with [PATCH 4/4]" + ); + + let proposal = r55 + .events + .iter() + .find(|e| { + e.tags() + .iter() + .find(|t| t.as_vec()[0].eq("branch-name")) + .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1)) + }) + .unwrap(); + + assert_eq!( + proposal.id().to_string(), + first_new_patch + .tags + .iter() + .find(|t| t.is_root()) + .unwrap() + .as_vec()[1], + "first patch sets proposal id as root" + ); + + assert_eq!( + first_new_patch.id().to_string(), + second_new_patch + .tags + .iter() + .find(|t| t.is_reply()) + .unwrap() + .as_vec()[1], + "second new patch replies to the first new patch" + ); + + let previous_proposal_tip_event = r55 + .events + .iter() + .find(|e| { + e.tags() + .iter() + .any(|t| t.as_vec()[1].eq(&proposal.id().to_string())) + && e.content.contains("[PATCH 2/2]") + }) + .unwrap(); + + assert_eq!( + previous_proposal_tip_event.id().to_string(), + first_new_patch + .tags + .iter() + .find(|t| t.is_reply()) + .unwrap() + .as_vec()[1], + "first patch replies to the previous tip of proposal" + ); + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn force_push_creates_proposal_revision() -> Result<()> { + let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + 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), + Relay::new(8057, None, None), + ); + r51.events = events.clone(); + r55.events = events.clone(); + + #[allow(clippy::mutable_key_type)] + let before = r55.events.iter().cloned().collect::>(); + + let cli_tester_handle = std::thread::spawn(move || -> Result<(String, String)> { + let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; + + let git_repo = clone_git_repo_with_nostr_url()?; + let oid = git_repo.checkout_remote_branch(&branch_name)?; + // remove last commit + git_repo.checkout("main")?; + git_repo.git_repo.branch( + &branch_name, + &git_repo.git_repo.find_commit(oid)?.parent(0)?, + true, + )?; + git_repo.checkout(&branch_name)?; + + std::fs::write(git_repo.dir.join("new.md"), "some content")?; + git_repo.stage_and_commit("new.md")?; + + std::fs::write(git_repo.dir.join("new2.md"), "some content")?; + git_repo.stage_and_commit("new2.md")?; + + let mut p = + CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push", "--force"]); + cli_expect_nostr_fetch(&mut p)?; + p.expect( + format!( + "fetching ref list over filesystem from {}...\r\n", + source_path + ) + .as_str(), + )?; + p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; + let output = p.expect_end_eventually()?; + + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + + Ok((output, branch_name)) + }); + // launch relays + 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(), + ); + + let (output, branch_name) = cli_tester_handle.join().unwrap()?; + + assert_eq!( + output, + format!(" + eb5d678...8a296c8 {branch_name} -> {branch_name} (forced update)\r\n") + .as_str(), + ); + + let new_events = r55 + .events + .iter() + .cloned() + .collect::>() + .difference(&before) + .cloned() + .collect::>(); + assert_eq!(new_events.len(), 3); + + let proposal = r55 + .events + .iter() + .find(|e| { + e.tags() + .iter() + .find(|t| t.as_vec()[0].eq("branch-name")) + .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1)) + }) + .unwrap(); + + let revision_root_patch = new_events + .iter() + .find(|e| e.tags().iter().any(|t| t.as_vec()[1].eq("revision-root"))) + .unwrap(); + + assert_eq!( + proposal.id().to_string(), + revision_root_patch + .tags + .iter() + .find(|t| t.is_reply()) + .unwrap() + .as_vec()[1], + "revision root patch replies to original proposal" + ); + + assert!( + revision_root_patch.content.contains("[PATCH 1/3]"), + "revision root labeled with [PATCH 1/3] event: {revision_root_patch:?}", + ); + + let second_patch = new_events + .iter() + .find(|e| e.content.contains("new.md")) + .unwrap(); + let third_patch = new_events + .iter() + .find(|e| e.content.contains("new2.md")) + .unwrap(); + assert!( + second_patch.content.contains("[PATCH 2/3]"), + "second patch labeled with [PATCH 2/3]" + ); + assert!( + third_patch.content.contains("[PATCH 3/3]"), + "third patch labeled with [PATCH 3/3]" + ); + + assert_eq!( + revision_root_patch.id().to_string(), + second_patch + .tags + .iter() + .find(|t| t.is_root()) + .unwrap() + .as_vec()[1], + "second patch sets revision id as root" + ); + + assert_eq!( + second_patch.id().to_string(), + third_patch + .tags + .iter() + .find(|t| t.is_reply()) + .unwrap() + .as_vec()[1], + "third patch replies to the second new patch" + ); + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn push_new_pr_branch_creates_proposal() -> Result<()> { + let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + 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), + Relay::new(8057, None, None), + ); + r51.events = events.clone(); + r55.events = events.clone(); + + #[allow(clippy::mutable_key_type)] + let before = r55.events.iter().cloned().collect::>(); + let branch_name = "pr/my-new-proposal"; + + let cli_tester_handle = std::thread::spawn(move || -> Result { + let mut git_repo = clone_git_repo_with_nostr_url()?; + git_repo.delete_dir_on_drop = false; + git_repo.create_branch(branch_name)?; + git_repo.checkout(branch_name)?; + + std::fs::write(git_repo.dir.join("new.md"), "some content")?; + git_repo.stage_and_commit("new.md")?; + + std::fs::write(git_repo.dir.join("new2.md"), "some content")?; + git_repo.stage_and_commit("new2.md")?; + + let mut p = CliTester::new_git_with_remote_helper_from_dir( + &git_repo.dir, + ["push", "-u", "origin", branch_name], + ); + cli_expect_nostr_fetch(&mut p)?; + p.expect( + format!( + "fetching ref list over filesystem from {}...\r\n", + source_path + ) + .as_str(), + )?; + p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; + let output = p.expect_end_eventually()?; + + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + + Ok(output) + }); + // launch relays + 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(), + ); + + let output = cli_tester_handle.join().unwrap()?; + + assert_eq!( + output, + format!(" * [new branch] {branch_name} -> {branch_name}\r\nbranch '{branch_name}' set up to track 'origin/{branch_name}'.\r\n").as_str(), + ); + + let new_events = r55 + .events + .iter() + .cloned() + .collect::>() + .difference(&before) + .cloned() + .collect::>(); + assert_eq!(new_events.len(), 2); + + let proposal = new_events + .iter() + .find(|e| e.tags().iter().any(|t| t.as_vec()[1].eq("root"))) + .unwrap(); + + assert!( + proposal.content.contains("new.md"), + "first patch is proposal root" + ); + + assert!( + proposal.content.contains("[PATCH 1/2]"), + "proposal root labeled with[PATCH 1/2] event: {proposal:?}", + ); + + assert_eq!( + proposal + .tags() + .iter() + .find(|t| t.as_vec()[0].eq("branch-name")) + .unwrap() + .as_vec()[1], + branch_name.replace("pr/", ""), + ); + + let second_patch = new_events + .iter() + .find(|e| e.content.contains("new2.md")) + .unwrap(); + + assert!( + second_patch.content.contains("[PATCH 2/2]"), + "second patch labeled with [PATCH 2/2]" + ); + + assert_eq!( + proposal.id().to_string(), + second_patch + .tags + .iter() + .find(|t| t.is_root()) + .unwrap() + .as_vec()[1], + "second patch sets proposal id as root" + ); + + Ok(()) + } +} diff --git a/tests/init.rs b/tests/init.rs deleted file mode 100644 index c8390e3..0000000 --- a/tests/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/list.rs b/tests/list.rs deleted file mode 100644 index c145fa4..0000000 --- a/tests/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/login.rs b/tests/login.rs deleted file mode 100644 index 3bcfbf9..0000000 --- a/tests/login.rs +++ /dev/null @@ -1,1168 +0,0 @@ -use anyhow::Result; -use git::GitTestRepo; -use serial_test::serial; -use test_utils::*; - -static EXPECTED_NSEC_PROMPT: &str = "login with nostr address / nsec"; -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) -} -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"]); - - 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(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"]); - - 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(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"]); - - 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(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"]); - - 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(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"]); - - 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(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] - // 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/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..c145fa4 --- /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..3bcfbf9 --- /dev/null +++ b/tests/ngit/login.rs @@ -0,0 +1,1168 @@ +use anyhow::Result; +use git::GitTestRepo; +use serial_test::serial; +use test_utils::*; + +static EXPECTED_NSEC_PROMPT: &str = "login with nostr address / nsec"; +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) +} +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"]); + + 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(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"]); + + 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(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"]); + + 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(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"]); + + 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(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"]); + + 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(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] + // 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 new file mode 100644 index 0000000..fe852df --- /dev/null +++ b/tests/ngit/main.rs @@ -0,0 +1,6 @@ +mod init; +mod list; +mod login; +mod pull; +mod push; +mod send; 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..ef09425 --- /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.7.2\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.7.2\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/pull.rs b/tests/pull.rs deleted file mode 100644 index 6637859..0000000 --- a/tests/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/push.rs b/tests/push.rs deleted file mode 100644 index eb452cd..0000000 --- a/tests/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/send.rs b/tests/send.rs deleted file mode 100644 index ef09425..0000000 --- a/tests/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.7.2\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.7.2\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