// use crate::{ git::{ self, repository::{ factory::RepositoryFactory as _, open::otest::{OnFetch, OnPush}, Direction, RepositoryLike as _, }, tests::{given, then}, validation::positions::validate, }, GitDir, StoragePathType, }; use assert2::let_assert; mod repos { use super::*; #[test] fn where_repo_has_no_push_remote() { let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); let mut mock_open_repository = git::repository::open::mock(); mock_open_repository .expect_find_default_remote() .with(mockall::predicate::eq(Direction::Push)) .return_once(|_| None); let mut repository_factory = git::repository::factory::mock(); repository_factory .expect_open() .return_once(move |_| Ok(mock_open_repository)); let_assert!( Ok(open_repository) = repository_factory.open(&repo_details), "open repo" ); let result = git::validation::remotes::validate_default_remotes(&*open_repository, &repo_details); print!("{result:?}"); let_assert!(Err(err) = result); assert!(matches!( err, git::validation::remotes::Error::NoDefaultPushRemote )); } } mod positions { use super::*; mod validate { use super::*; #[test] fn where_fetch_fails_should_error() { let fs = given::a_filesystem(); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); let mut mock_open_repository = git::repository::open::mock(); mock_open_repository .expect_fetch() .return_once(|| Err(git::fetch::Error::TestFailureExpected)); let mut repository_factory = git::repository::factory::mock(); repository_factory .expect_open() .return_once(move |_| Ok(mock_open_repository)); let_assert!( Ok(repository) = repository_factory.open(&repo_details), "open repo" ); let repo_config = given::a_repo_config(); let result = validate(&*repository, &repo_details, &repo_config); println!("{result:?}"); let_assert!(Err(err) = result, "validate"); assert!(matches!( err, git::validation::positions::Error::Retryable(_) )); } #[test] fn where_main_branch_is_missing_or_commit_log_is_empty_should_error() { let fs = given::a_filesystem(); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); let repo_config = given::a_repo_config(); let main_branch = repo_config.branches().main(); let mut mock_open_repository = git::repository::open::mock(); mock_open_repository.expect_fetch().return_once(|| Ok(())); mock_open_repository .expect_commit_log() .returning(move |branch_name, _| { if branch_name == &main_branch { Err(git::commit::log::Error::Gix { branch: branch_name.clone(), error: "foo".to_string(), }) } else { Ok(vec![]) } }); let mut repository_factory = git::repository::factory::mock(); repository_factory .expect_open() .return_once(move |_| Ok(mock_open_repository)); let_assert!( Ok(open_repository) = repository_factory.open(&repo_details), "open repo" ); let result = validate(&*open_repository, &repo_details, &repo_config); println!("{result:?}"); assert!(matches!( result, Err(git::validation::positions::Error::Retryable(_)) )); } #[test] fn where_next_branch_is_missing_or_commit_log_is_empty_should_error() { let fs = given::a_filesystem(); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); let repo_config = given::a_repo_config(); let next_branch = repo_config.branches().next(); let mut mock_open_repository = git::repository::open::mock(); mock_open_repository.expect_fetch().return_once(|| Ok(())); mock_open_repository .expect_commit_log() .returning(move |branch_name, _| { if branch_name == &next_branch { Err(git::commit::log::Error::Gix { branch: branch_name.clone(), error: "foo".to_string(), }) } else { Ok(vec![given::a_commit()]) } }); let mut repository_factory = git::repository::factory::mock(); repository_factory .expect_open() .return_once(move |_| Ok(mock_open_repository)); let_assert!( Ok(open_repository) = repository_factory.open(&repo_details), "open repo" ); let result = validate(&*open_repository, &repo_details, &repo_config); println!("{result:?}"); assert!(matches!( result, Err(git::validation::positions::Error::Retryable(_)) )); } #[test] fn where_dev_branch_is_missing_or_commit_log_is_empty_should_error() { let fs = given::a_filesystem(); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); let repo_config = given::a_repo_config(); let dev_branch = repo_config.branches().dev(); let mut mock_open_repository = git::repository::open::mock(); mock_open_repository.expect_fetch().return_once(|| Ok(())); mock_open_repository .expect_commit_log() .returning(move |branch_name, _| { if branch_name == &dev_branch { Err(git::commit::log::Error::Gix { branch: branch_name.clone(), error: "foo".to_string(), }) } else { Ok(vec![given::a_commit()]) } }); let mut repository_factory = git::repository::factory::mock(); repository_factory .expect_open() .return_once(move |_| Ok(mock_open_repository)); let_assert!( Ok(open_repository) = repository_factory.open(&repo_details), "open repo" ); let result = validate(&*open_repository, &repo_details, &repo_config); println!("{result:?}"); assert!(matches!( result, Err(git::validation::positions::Error::Retryable(_)) )); } #[test] fn where_dev_branch_is_not_based_on_main_should_error() { //given let fs = given::a_filesystem(); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |branches, gitdir, fs| { // /--- 4 next // 0 --- 1 --- 3 main // \--- 2 dev // add a commit to main (0 -> 1) then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // add a commit to dev ( 1 -> 2) then::create_a_commit_on_branch(fs, gitdir, &branches.dev())?; // switch back to main then::git_switch(&branches.main(), gitdir)?; // add a second commit to main (1 -> 3) then::git_log_all(gitdir)?; then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // add a commit to next 1 -> 4 then::create_a_commit_on_branch(fs, gitdir, &branches.next())?; then::git_log_all(gitdir)?; git::fetch::Result::Ok(()) }, )); let_assert!( Ok(open_repository) = test_repository.open(&gitdir), "open repo" ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); //when let_assert!( Err(err) = validate(&*open_repository, &repo_details, &repo_config), "validate" ); //then assert!(matches!( err, git::validation::positions::Error::UserIntervention(_) )); } #[test] fn where_next_branch_is_not_based_on_main_should_reset_next_branch_to_main_and_then_error() { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |branches, gitdir, fs| { // /--- 4 dev // 0 --- 1 --- 3 main // \--- 2 next // add a commit to main (0 -> 1) then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // add a commit to next (1 -> 2) then::create_a_commit_on_branch(fs, gitdir, &branches.next())?; // switch back to main (1) then::git_switch(&branches.main(), gitdir)?; // add a second commit to main (1 -> 3) then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // add a commit to dev (3 -> 4) then::create_a_commit_on_branch(fs, gitdir, &branches.dev())?; then::git_log_all(gitdir)?; git::fetch::Result::Ok(()) }, )); // second fetch as prep to push test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |_branches, _gitdir, _fs| { // don't change anything git::fetch::Result::Ok(()) }, )); test_repository.on_push(OnPush::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |_repo_details, branch_name, gitref, force, repo_branches, gitdir, fs| { assert_eq!( branch_name, &repo_branches.next(), "branch to reset should be 'next'" ); let sha_main = then::get_sha_for_branch(fs, gitdir, &repo_branches.main())?; assert_eq!( gitref, &git::GitRef::from(sha_main), "should reset to the sha of the 'main' branch" ); let sha_next = then::get_sha_for_branch(fs, gitdir, &repo_branches.next())?; assert_eq!( force, &git::push::Force::From(sha_next.into()), "should force push only if next is on expected sha" ); git::push::Result::Ok(()) }, )); let_assert!( Ok(open_repository) = test_repository.open(&gitdir), "open repo" ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); //when let_assert!( Err(err) = validate(&*open_repository, &repo_details, &repo_config), "validate" ); //then println!("Got: {err:?}"); // NOTE: assertions for correct push are in on_push above assert!(matches!( err, git::validation::positions::Error::Retryable(_) )); } #[test] fn where_next_branch_is_not_based_on_main_and_reset_of_next_fails_should_error() { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |branches, gitdir, fs| { // /--- 4 dev // 0 --- 1 --- 3 main // \--- 2 next // add a commit to main (0 -> 1) then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // add a commit to next (1 -> 2) then::create_a_commit_on_branch(fs, gitdir, &branches.next())?; // switch back to main (1) then::git_switch(&branches.main(), gitdir)?; // add a second commit to main (1 -> 3) then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // add a commit to dev (3 -> 4) then::create_a_commit_on_branch(fs, gitdir, &branches.dev())?; then::git_log_all(gitdir)?; git::fetch::Result::Ok(()) }, )); // second fetch as prep to push test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |_branches, _gitdir, _fs| { // don't change anything git::fetch::Result::Ok(()) }, )); test_repository.on_push(OnPush::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |_repo_details, _branch_name, _gitref, _force, _repo_branches, _gitdir, _fs| { git::push::Result::Err(git::push::Error::Lock) }, )); let_assert!( Ok(open_repository) = test_repository.open(&gitdir), "open repo" ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir.clone()); //when let_assert!( Err(err) = validate(&*open_repository, &repo_details, &repo_config), "validate" ); //then println!("Got: {err:?}"); let_assert!( Ok(_) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().next()), "load next branch sha" ); assert!(matches!( err, git::validation::positions::Error::NonRetryable(_) )); } #[test] #[allow(clippy::expect_used)] fn where_dev_branch_is_on_main_and_next_is_not_should_reset_next_branch_to_main_and_retryable_error( ) { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |branches, gitdir, fs| { // /--- 3 next // 0 --- 1 main & dev // add a commit to main (0 -> 1) then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // create dev branch on main (1) then::git_switch(&branches.dev(), gitdir)?; // switch back to main (1) then::git_switch(&branches.main(), gitdir)?; // add a commit to next (1 -> 3) then::create_a_commit_on_branch(fs, gitdir, &branches.next())?; then::git_log_all(gitdir)?; git::fetch::Result::Ok(()) }, )); // second fetch as prep to push test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |_branches, _gitdir, _fs| { // don't change anything git::fetch::Result::Ok(()) }, )); test_repository.on_push(OnPush::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |_repo_details, branch_name, gitref, force, repo_branches, gitdir, fs| { assert_eq!( branch_name, &repo_branches.next(), "branch to reset should be 'next'" ); let sha_main = then::get_sha_for_branch(fs, gitdir, &repo_branches.main())?; assert_eq!( gitref, &git::GitRef::from(sha_main), "should reset to the sha of the 'main' branch" ); let sha_next = then::get_sha_for_branch(fs, gitdir, &repo_branches.next())?; assert_eq!( force, &git::push::Force::From(sha_next.into()), "should force push only if next is on expected sha" ); git::push::Result::Ok(()) }, )); let_assert!( Ok(open_repository) = test_repository.open(&gitdir), "open repo" ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); //when let_assert!( Err(err) = validate(&*open_repository, &repo_details, &repo_config), "validate" ); //then println!("Got: {err:?}"); // NOTE: assertions for correct push are in on_push above assert!(matches!( err, git::validation::positions::Error::Retryable(_) )); } #[test] #[allow(clippy::expect_used)] fn where_dev_branch_is_not_based_on_next_should_reset_next_branch_to_next_commit_on_dev_and_retryable_error( ) { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |branches, gitdir, fs| { // /--- 3 next // 0 --- 1 main // \--- 2 dev // add a commit to main (0 -> 1) then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // add a commit to dev (1 -> 2) then::create_a_commit_on_branch(fs, gitdir, &branches.dev())?; // switch back to main (1) then::git_switch(&branches.main(), gitdir)?; // add a commit to next (1 -> 3) then::create_a_commit_on_branch(fs, gitdir, &branches.next())?; then::git_log_all(gitdir)?; git::fetch::Result::Ok(()) }, )); // second fetch as prep to push test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |_branches, _gitdir, _fs| { // don't change anything git::fetch::Result::Ok(()) }, )); test_repository.on_push(OnPush::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |_repo_details, branch_name, gitref, force, repo_branches, gitdir, fs| { assert_eq!( branch_name, &repo_branches.next(), "branch to reset should be 'next'" ); let sha_dev = then::get_sha_for_branch(fs, gitdir, &repo_branches.dev())?; assert_eq!( gitref, &git::GitRef::from(sha_dev), "should reset to the sha of the next commit on 'dev' branch" ); let sha_next = then::get_sha_for_branch(fs, gitdir, &repo_branches.next())?; assert_eq!( force, &git::push::Force::From(sha_next.into()), "should force push only if next is on expected sha" ); git::push::Result::Ok(()) }, )); let_assert!( Ok(open_repository) = test_repository.open(&gitdir), "open repo" ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir.clone()); //when let_assert!( Ok((positions, _git_log)) = validate(&*open_repository, &repo_details, &repo_config), "validate" ); //then println!("positions: {positions:#?}"); let_assert!( Ok(main_sha) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().main()) ); assert_eq!(positions.main.sha(), &main_sha); let_assert!( Ok(next_sha) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().next()) ); assert_eq!(positions.next.sha(), &next_sha); let_assert!( Ok(dev_sha) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().dev()) ); assert_eq!(positions.dev.sha(), &dev_sha); } #[test] fn where_branches_are_all_valid_should_return_positions() { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal); let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); test_repository.on_fetch(OnFetch::new( repo_config.branches().clone(), gitdir.clone(), fs.clone(), |branches, gitdir, fs| { // 0 --- 1 main // \--- 2 next // \--- dev // add a commit to main (0 -> 1) then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; // add a commit to next (1 -> 2) then::create_a_commit_on_branch(fs, gitdir, &branches.next())?; // add a commit to dev (2 -> 3) then::create_a_commit_on_branch(fs, gitdir, &branches.dev())?; then::git_log_all(gitdir)?; git::fetch::Result::Ok(()) }, )); let_assert!( Ok(open_repository) = test_repository.open(&gitdir), "open repo" ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir.clone()); //when let_assert!( Ok((positions, _git_log)) = validate(&*open_repository, &repo_details, &repo_config), "validate" ); //then println!("positions: {positions:#?}"); let_assert!( Ok(main_sha) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().main()) ); assert_eq!(positions.main.sha(), &main_sha); let_assert!( Ok(next_sha) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().next()) ); assert_eq!(positions.next.sha(), &next_sha); let_assert!( Ok(dev_sha) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().dev()) ); assert_eq!(positions.dev.sha(), &dev_sha); } } }