From 6e9245542f070c39a1975f0d53d88913c4ac667d Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Sun, 1 Oct 2023 00:00:00 +0100 Subject: feat(prs-create) find commits and create events - identify commits - create pull request event - create patch events --- test_utils/src/git.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++++ test_utils/src/lib.rs | 130 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 test_utils/src/git.rs (limited to 'test_utils/src') diff --git a/test_utils/src/git.rs b/test_utils/src/git.rs new file mode 100644 index 0000000..166693d --- /dev/null +++ b/test_utils/src/git.rs @@ -0,0 +1,123 @@ +//create + +// implement drop? +use std::{env::current_dir, fs, path::PathBuf}; + +use anyhow::Result; +use git2::{Oid, RepositoryInitOptions, Signature, Time}; + +pub struct GitTestRepo { + pub dir: PathBuf, + pub git_repo: git2::Repository, +} + +impl Default for GitTestRepo { + fn default() -> Self { + Self::new("main").unwrap() + } +} +impl GitTestRepo { + pub fn new(main_branch_name: &str) -> Result { + let path = current_dir()?.join(format!("tmpgit-{}", rand::random::())); + let git_repo = git2::Repository::init_opts( + &path, + RepositoryInitOptions::new() + .initial_head(main_branch_name) + .mkpath(true), + )?; + Ok(Self { + dir: path, + git_repo, + }) + } + + pub fn initial_commit(&self) -> Result { + let oid = self.git_repo.index()?.write_tree()?; + let tree = self.git_repo.find_tree(oid)?; + let commit_oid = self.git_repo.commit( + Some("HEAD"), + &joe_signature(), + &joe_signature(), + "Initial commit", + &tree, + &[], + )?; + Ok(commit_oid) + } + + pub fn populate(&self) -> Result { + self.initial_commit()?; + fs::write(self.dir.join("t1.md"), "some content")?; + self.stage_and_commit("add t1.md")?; + fs::write(self.dir.join("t2.md"), "some content1")?; + self.stage_and_commit("add t2.md") + } + + pub fn stage_and_commit(&self, message: &str) -> Result { + let prev_oid = self.git_repo.head().unwrap().peel_to_commit()?; + + let mut index = self.git_repo.index()?; + index.add_all(["."], git2::IndexAddOption::DEFAULT, None)?; + index.write()?; + + let oid = self.git_repo.commit( + Some("HEAD"), + &joe_signature(), + &joe_signature(), + message, + &self.git_repo.find_tree(index.write_tree()?)?, + &[&prev_oid], + )?; + + Ok(oid) + } + + pub fn create_branch(&self, branch_name: &str) -> Result<()> { + self.git_repo + .branch(branch_name, &self.git_repo.head()?.peel_to_commit()?, false)?; + Ok(()) + } + + pub fn checkout(&self, ref_name: &str) -> Result { + let (object, reference) = self.git_repo.revparse_ext(ref_name)?; + + self.git_repo.checkout_tree(&object, None)?; + + match reference { + // gref is an actual reference like branches or tags + Some(gref) => self.git_repo.set_head(gref.name().unwrap()), + // this is a commit, not a reference + None => self.git_repo.set_head_detached(object.id()), + }?; + let oid = self.git_repo.head()?.peel_to_commit()?.id(); + Ok(oid) + } +} + +impl Drop for GitTestRepo { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.dir); + } +} +pub fn joe_signature() -> Signature<'static> { + Signature::new("Joe Bloggs", "joe.bloggs@pm.me", &Time::new(0, 0)).unwrap() +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn methods_do_not_throw() -> Result<()> { + let repo = GitTestRepo::new("main")?; + + repo.populate()?; + repo.create_branch("feature")?; + repo.checkout("feature")?; + fs::write(repo.dir.join("t3.md"), "some content")?; + repo.stage_and_commit("add t3.md")?; + + Ok(()) + } +} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 1a4231a..0f870f6 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -1,4 +1,4 @@ -use std::ffi::OsStr; +use std::{ffi::OsStr, path::PathBuf}; use anyhow::{ensure, Context, Result}; use dialoguer::theme::{ColorfulTheme, Theme}; @@ -8,6 +8,8 @@ use once_cell::sync::Lazy; use rexpect::session::{Options, PtySession}; use strip_ansi_escapes::strip_str; +pub mod git; + pub static TEST_KEY_1_NSEC: &str = "nsec1ppsg5sm2aexq06juxmu9evtutr6jkwkhp98exxxvwamhru9lyx9s3rwseq"; pub static TEST_KEY_1_SK_HEX: &str = @@ -75,6 +77,34 @@ impl CliTester { i.prompt().context("initial password prompt")?; Ok(i) } + + pub fn expect_confirm( + &mut self, + prompt: &str, + default: Option, + ) -> Result { + let mut i = CliTesterConfirmPrompt { + tester: self, + prompt: prompt.to_string(), + default, + }; + i.prompt(false, default).context("initial confirm prompt")?; + Ok(i) + } + + pub fn expect_confirm_eventually( + &mut self, + prompt: &str, + default: Option, + ) -> Result { + let mut i = CliTesterConfirmPrompt { + tester: self, + prompt: prompt.to_string(), + default, + }; + i.prompt(true, default).context("initial confirm prompt")?; + Ok(i) + } } pub struct CliTesterInputPrompt<'a> { @@ -199,6 +229,71 @@ impl CliTesterPasswordPrompt<'_> { } } +pub struct CliTesterConfirmPrompt<'a> { + tester: &'a mut CliTester, + prompt: String, + default: Option, +} + +impl CliTesterConfirmPrompt<'_> { + fn prompt(&mut self, eventually: bool, default: Option) -> Result<&mut Self> { + let mut s = String::new(); + self.tester + .formatter + .format_confirm_prompt(&mut s, self.prompt.as_str(), default) + .expect("diagluer theme formatter should succeed"); + ensure!( + s.contains(self.prompt.as_str()), + "dialoguer must be broken as formatted prompt success doesnt contain prompt" + ); + + if eventually { + self.tester + .expect_eventually(sanatize(s).as_str()) + .context("expect input prompt eventually")?; + } else { + self.tester + .expect(sanatize(s).as_str()) + .context("expect confirm prompt")?; + } + + Ok(self) + } + + pub fn succeeds_with(&mut self, input: Option) -> Result<&mut Self> { + self.tester.send_line(match input { + None => "", + Some(true) => "y", + Some(false) => "n", + })?; + self.tester + .expect("\r") + .context("expect new line after confirm input to be printed")?; + + let mut s = String::new(); + self.tester + .formatter + .format_confirm_prompt_selection( + &mut s, + self.prompt.as_str(), + match input { + None => self.default, + Some(_) => input, + }, + ) + .expect("diagluer theme formatter should succeed"); + if !s.contains(self.prompt.as_str()) { + panic!("dialoguer must be broken as formatted prompt success doesnt contain prompt"); + } + let formatted_success = format!("{}\r\n", sanatize(s)); + + self.tester + .expect(formatted_success.as_str()) + .context("expect immediate prompt success")?; + Ok(self) + } +} + impl CliTester { pub fn new(args: I) -> Self where @@ -210,6 +305,17 @@ impl CliTester { formatter: ColorfulTheme::default(), } } + pub fn new_from_dir(dir: &PathBuf, args: I) -> Self + where + I: IntoIterator, + S: AsRef, + { + Self { + rexpect_session: rexpect_with_from_dir(dir, args, 2000) + .expect("rexpect to spawn new process"), + formatter: ColorfulTheme::default(), + } + } pub fn new_with_timeout(timeout_ms: u64, args: I) -> Self where I: IntoIterator, @@ -338,6 +444,28 @@ where ) } +pub fn rexpect_with_from_dir( + dir: &PathBuf, + args: I, + timeout_ms: u64, +) -> Result +where + I: IntoIterator, + S: AsRef, +{ + let mut cmd = std::process::Command::new(assert_cmd::cargo::cargo_bin("ngit")); + cmd.current_dir(dir); + cmd.args(args); + // using branch for PR https://github.com/rust-cli/rexpect/pull/103 to strip ansi escape codes + rexpect::session::spawn_with_options( + cmd, + Options { + timeout_ms: Some(timeout_ms), + strip_ansi_escape_codes: true, + }, + ) +} + /// backup and remove application config and data pub fn before() -> Result<()> { backup_existing_config() -- cgit v1.2.3