use crate as git; use git_next_config as config; mod commit { use super::*; #[test] fn should_return_sha() { let sha = given::a_commit_sha(); let commit = given::a_commit_with_sha(&sha); assert_eq!(commit.sha(), &sha); } #[test] fn should_return_message() { let message = given::a_commit_message(); let commit = given::a_commit_with_message(&message); assert_eq!(commit.message(), &message); } #[test] fn should_convert_from_push() { let sha = given::a_commit_sha(); let message = given::a_commit_message(); let push = given::a_webhook_push(&sha, &message); let commit = git::Commit::from(push); let expected = git::Commit::new( git::commit::Sha::new(sha.to_string()), git::commit::Message::new(message.to_string()), ); assert_eq!(commit, expected); } } mod generation { use crate::Generation; #[test] fn should_increment() { let mut g = Generation::new(); assert_eq!(g.to_string(), "0"); g.inc(); assert_eq!(g.to_string(), "1"); } } mod gitref { use crate::{commit, Commit, GitRef}; #[test] fn should_convert_from_commit() { let commit = Commit::new( commit::Sha::new("sha".to_string()), commit::Message::new("message".to_string()), ); let gitref = GitRef::from(commit); assert_eq!(gitref.to_string(), "sha"); } } mod gitremote { use git_next_config::{Hostname, RepoPath}; use crate::GitRemote; #[test] fn should_return_hostname() { let host = Hostname::new("localhost".to_string()); let repo_path = RepoPath::new("kemitix/git-next".to_string()); let gr = GitRemote::new(host.clone(), repo_path); assert_eq!(gr.host(), &host); } #[test] fn should_return_repo_path() { let host = Hostname::new("localhost".to_string()); let repo_path = RepoPath::new("kemitix/git-next".to_string()); let gr = GitRemote::new(host, repo_path.clone()); assert_eq!(gr.repo_path(), &repo_path); } } mod push { use super::*; use crate::GitRef; #[test] fn force_no_should_display() { assert_eq!(git::push::Force::No.to_string(), "fast-forward") } #[test] fn force_from_should_display() { let sha = given::a_name(); let commit = given::a_commit_with_sha(&git::commit::Sha::new(sha.clone())); assert_eq!( git::push::Force::From(GitRef::from(commit)).to_string(), format!("force-if-from:{sha}") ) } mod reset { use super::*; use crate::{tests::given, OpenRepository}; use assert2::let_assert; #[test] fn should_perform_a_fetch_then_push() { let fs = given::a_filesystem(); let (mock_open_repository, gitdir, mock_repository) = given::an_open_repository(&fs); let open_repository: OpenRepository = mock_open_repository.into(); let repo_details = given::repo_details(&fs); let branch_name = &repo_details.branch; let commit = given::a_commit(); let gitref = GitRef::from(commit); let_assert!( Ok(_) = git::push::reset( &open_repository, &repo_details, branch_name, &gitref, &git::push::Force::No ) ); let_assert!(Some(mock_open_repository) = mock_repository.get(&gitdir)); let operations = mock_open_repository.operations(); let forge_alias = repo_details.forge.forge_alias(); let repo_alias = &repo_details.repo_alias; let to_commit = gitref; let force = "fast-forward"; assert_eq!( operations, vec![format!("fetch"), format!("push fa:{forge_alias} ra:{repo_alias} bn:{branch_name} tc:{to_commit} f:{force}")] ); } } } mod repo_details { use std::{collections::BTreeMap, path::PathBuf}; use git_next_config::{ ForgeAlias, ForgeConfig, ForgeType, GitDir, Hostname, RepoAlias, RepoPath, ServerRepoConfig, }; use secrecy::ExposeSecret; use crate::{Generation, GitRemote, RepoDetails}; #[test] fn should_return_origin() { let rd = RepoDetails::new( Generation::new(), &RepoAlias::new("foo"), &ServerRepoConfig::new( "repo".to_string(), "branch".to_string(), None, None, None, None, ), &ForgeAlias::new("default".to_string()), &ForgeConfig::new( ForgeType::MockForge, "host".to_string(), "user".to_string(), "token".to_string(), BTreeMap::new(), ), GitDir::from(PathBuf::default().join("foo")), ); assert_eq!( rd.origin().expose_secret(), "https://user:token@host/repo.git" ); } #[test] fn should_return_git_remote() { let rd = RepoDetails::new( Generation::new(), &RepoAlias::new("foo"), &ServerRepoConfig::new( "user/repo".to_string(), "branch".to_string(), None, None, None, None, ), &ForgeAlias::new("default".to_string()), &ForgeConfig::new( ForgeType::MockForge, "host".to_string(), "user".to_string(), "token".to_string(), BTreeMap::new(), ), GitDir::from(PathBuf::default().join("foo")), ); assert_eq!( rd.git_remote(), GitRemote::new( Hostname::new("host".to_string()), RepoPath::new("user/repo".to_string()) ) ); } } pub mod given { use std::path::PathBuf; // use crate::{ self as git, repository::{MockOpenRepository, MockRepository}, tests::given, }; use config::{ BranchName, ForgeAlias, ForgeConfig, ForgeType, GitDir, RepoAlias, RepoBranches, RepoConfig, ServerRepoConfig, }; use git_next_config as config; use crate::RepoDetails; pub fn repo_branches() -> RepoBranches { RepoBranches::new( format!("main-{}", a_name()), format!("next-{}", a_name()), format!("dev-{}", a_name()), ) } pub fn a_forge_alias() -> ForgeAlias { ForgeAlias::new(a_name()) } pub fn a_repo_alias() -> RepoAlias { RepoAlias::new(a_name()) } pub fn a_pathbuf() -> PathBuf { PathBuf::from(given::a_name()) } pub fn a_name() -> String { use rand::Rng; use std::iter; fn generate(len: usize) -> String { const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let mut rng = rand::thread_rng(); let one_char = || CHARSET[rng.gen_range(0..CHARSET.len())] as char; iter::repeat_with(one_char).take(len).collect() } generate(5) } pub fn a_branch_name() -> BranchName { BranchName::new(a_name()) } pub fn a_git_dir(fs: &kxio::fs::FileSystem) -> GitDir { let dir_name = a_name(); let dir = fs.base().join(dir_name); GitDir::new(dir) } pub fn a_forge_config() -> ForgeConfig { ForgeConfig::new( ForgeType::MockForge, a_name(), a_name(), a_name(), Default::default(), // no repos ) } pub fn a_server_repo_config() -> ServerRepoConfig { let main = a_branch_name().unwrap(); let next = a_branch_name().unwrap(); let dev = a_branch_name().unwrap(); ServerRepoConfig::new( format!("{}/{}", a_name(), a_name()), main.clone(), None, Some(main), Some(next), Some(dev), ) } pub fn a_repo_config() -> RepoConfig { RepoConfig::new(given::repo_branches(), config::RepoConfigSource::Repo) } pub fn a_commit() -> git::Commit { git::Commit::new(a_commit_sha(), a_commit_message()) } pub fn a_commit_with_message(message: &git::commit::Message) -> git::Commit { git::Commit::new(a_commit_sha(), message.to_owned()) } pub fn a_commit_with_sha(sha: &git::commit::Sha) -> git::Commit { git::Commit::new(sha.to_owned(), a_commit_message()) } pub fn a_commit_message() -> git::commit::Message { git::commit::Message::new(a_name()) } pub fn a_commit_sha() -> git::commit::Sha { git::commit::Sha::new(a_name()) } pub fn a_webhook_push( sha: &git::commit::Sha, message: &git::commit::Message, ) -> config::webhook::Push { let branch = a_branch_name(); config::webhook::Push::new(branch, sha.to_string(), message.to_string()) } pub fn a_filesystem() -> kxio::fs::FileSystem { kxio::fs::temp().unwrap_or_else(|e| panic!("{}", e)) } pub fn repo_details(fs: &kxio::fs::FileSystem) -> git::RepoDetails { let generation = git::Generation::new(); let repo_alias = a_repo_alias(); let server_repo_config = a_server_repo_config(); let forge_alias = a_forge_alias(); let forge_config = a_forge_config(); let gitdir = a_git_dir(fs); RepoDetails::new( generation, &repo_alias, &server_repo_config, &forge_alias, &forge_config, gitdir, ) } pub fn an_open_repository( fs: &kxio::fs::FileSystem, ) -> (MockOpenRepository, GitDir, MockRepository) { let mut mock = git::repository::mock(); let gitdir = a_git_dir(fs); let or = mock.given_can_be_opened(&gitdir); (or, gitdir, mock) } } pub mod then { use std::path::{Path, PathBuf}; type TestResult = Result<(), Box>; use super::*; pub fn commit_named_file_to_branch( file_name: &Path, contents: &str, fs: &kxio::fs::FileSystem, gitdir: &config::GitDir, branch_name: &config::BranchName, ) -> TestResult { // git checkout ${branch_name} git_checkout_new_branch(branch_name, gitdir)?; // echo ${word} > file-${word} let pathbuf = PathBuf::from(gitdir); let file = fs.base().join(pathbuf).join(file_name); #[allow(clippy::expect_used)] fs.file_write(&file, contents)?; // git add ${file} git_add_file(gitdir, &file)?; // git commit -m"Added ${file}" git_commit(gitdir, &file)?; then::push_branch(fs, gitdir, branch_name)?; Ok(()) } pub fn create_a_commit_on_branch( fs: &kxio::fs::FileSystem, gitdir: &config::GitDir, branch_name: &config::BranchName, ) -> TestResult { // git checkout ${branch_name} git_checkout_new_branch(branch_name, gitdir)?; // echo ${word} > file-${word} let word = given::a_name(); let pathbuf = PathBuf::from(gitdir); let file = fs.base().join(pathbuf).join(&word); fs.file_write(&file, &word)?; // git add ${file} git_add_file(gitdir, &file)?; // git commit -m"Added ${file}" git_commit(gitdir, &file)?; then::push_branch(fs, gitdir, branch_name)?; Ok(()) } fn push_branch( fs: &kxio::fs::FileSystem, gitdir: &config::GitDir, branch_name: &config::BranchName, ) -> TestResult { let gitrefs = fs .base() .join(gitdir.to_path_buf()) .join(".git") .join("refs"); let local_branch = gitrefs.join("heads").join(branch_name.to_string().as_str()); let origin_heads = gitrefs.join("remotes").join("origin"); let remote_branch = origin_heads.join(branch_name.to_string().as_str()); let contents = fs.file_read_to_string(&local_branch)?; fs.dir_create_all(&origin_heads)?; fs.file_write(&remote_branch, &contents)?; Ok(()) } pub fn git_checkout_new_branch( branch_name: &git_next_config::BranchName, gitdir: &git_next_config::GitDir, ) -> TestResult { exec( format!("git checkout -b {}", branch_name), std::process::Command::new("/usr/bin/git") .current_dir(gitdir.to_path_buf()) .args(["checkout", "-b", branch_name.to_string().as_str()]) .output(), )?; Ok(()) } pub fn git_switch( branch_name: &git_next_config::BranchName, gitdir: &git_next_config::GitDir, ) -> TestResult { exec( format!("git switch {}", branch_name), std::process::Command::new("/usr/bin/git") .current_dir(gitdir.to_path_buf()) .args(["switch", branch_name.to_string().as_str()]) .output(), ) } fn exec(label: String, output: Result) -> TestResult { eprintln!("== {label}"); match output { Ok(output) => { eprintln!( "\nstdout:\n{}", String::from_utf8_lossy(output.stdout.as_slice()) ); eprintln!( "\nstderr:\n{}", String::from_utf8_lossy(output.stderr.as_slice()) ); eprintln!("============================="); Ok(()) } Err(err) => { eprintln!("ERROR: {err:#?}"); Ok(Err(err)?) } } } fn git_add_file(gitdir: &git_next_config::GitDir, file: &Path) -> TestResult { exec( format!("git add {file:?}"), std::process::Command::new("/usr/bin/git") .current_dir(gitdir.to_path_buf()) .args(["add", file.display().to_string().as_str()]) .output(), ) } fn git_commit(gitdir: &git_next_config::GitDir, file: &Path) -> TestResult { exec( format!(r#"git commit -m"Added {file:?}""#), std::process::Command::new("/usr/bin/git") .current_dir(gitdir.to_path_buf()) .args([ "commit", format!(r#"-m"Added {}"#, file.display().to_string().as_str()).as_str(), ]) .output(), ) } pub fn git_log_all(gitdir: &config::GitDir) -> TestResult { exec( "git log --all --oneline --decorate --graph".to_string(), std::process::Command::new("/usr/bin/git") .current_dir(gitdir.to_path_buf()) .args(["log", "--all", "--oneline", "--decorate", "--graph"]) .output(), ) } pub fn get_sha_for_branch( fs: &kxio::fs::FileSystem, gitdir: &git_next_config::GitDir, branch_name: &git_next_config::BranchName, ) -> Result> { let main_ref = fs .base() .join(gitdir.to_path_buf()) .join(".git") .join("refs") .join("heads") .join(branch_name.to_string().as_str()); let sha = fs.file_read_to_string(&main_ref)?; Ok(git::commit::Sha::new(sha.trim().to_string())) } }