From b04c17dc15913f20274c43562ba50e8dba1f8a8c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 19 May 2024 20:02:06 +0100 Subject: [PATCH] WIP: mock repository --- Cargo.toml | 1 + crates/config/Cargo.toml | 5 +- crates/config/src/api_token.rs | 5 + crates/config/src/branch_name.rs | 2 +- crates/config/src/forge_details.rs | 7 +- crates/config/src/forge_name.rs | 2 +- crates/config/src/forge_type.rs | 3 +- crates/config/src/git_dir.rs | 10 +- crates/config/src/host_name.rs | 2 +- crates/config/src/repo_alias.rs | 2 +- crates/config/src/repo_path.rs | 2 +- crates/config/src/user.rs | 2 +- crates/git/Cargo.toml | 7 +- crates/git/src/repo_details.rs | 2 +- crates/git/src/repository/mock.rs | 117 ++++++++++++++++++-- crates/git/src/repository/mod.rs | 20 ++-- crates/git/src/repository/open/mod.rs | 11 +- crates/git/src/repository/real.rs | 4 +- crates/git/src/tests.rs | 37 +++++++ crates/server/src/actors/server.rs | 11 +- crates/server/src/gitforge/tests/forgejo.rs | 4 +- 21 files changed, 209 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ee1bcbc..7f991a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ derive_more = { version = "1.0.0-beta.6", features = [ "deref", "from", ] } +derive-with = "0.5" # file watcher inotify = "0.10" diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 17b41f2..53f7463 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -40,9 +40,10 @@ secrecy = { workspace = true } # bytes = { workspace = true } # ulid = { workspace = true } # warp = { workspace = true } -# -# # error handling + +# boilerplate derive_more = { workspace = true } +derive-with = { workspace = true } # # # file watcher # inotify = { workspace = true } diff --git a/crates/config/src/api_token.rs b/crates/config/src/api_token.rs index e07d1af..42dc049 100644 --- a/crates/config/src/api_token.rs +++ b/crates/config/src/api_token.rs @@ -9,3 +9,8 @@ impl secrecy::ExposeSecret for ApiToken { self.0.expose_secret() } } +impl Default for ApiToken { + fn default() -> Self { + Self("".to_string().into()) + } +} diff --git a/crates/config/src/branch_name.rs b/crates/config/src/branch_name.rs index b2f360f..70df7c6 100644 --- a/crates/config/src/branch_name.rs +++ b/crates/config/src/branch_name.rs @@ -1,5 +1,5 @@ /// The name of a Branch -#[derive(Clone, Debug, Hash, PartialEq, Eq, derive_more::Display)] +#[derive(Clone, Default, Debug, Hash, PartialEq, Eq, derive_more::Display)] pub struct BranchName(String); impl BranchName { pub fn new(str: impl Into) -> Self { diff --git a/crates/config/src/forge_details.rs b/crates/config/src/forge_details.rs index 6754ca6..22d0e5b 100644 --- a/crates/config/src/forge_details.rs +++ b/crates/config/src/forge_details.rs @@ -1,7 +1,7 @@ use crate::{ApiToken, ForgeConfig, ForgeName, ForgeType, Hostname, User}; /// The derived information about a Forge, used to create interactions with it -#[derive(Clone, Debug, derive_more::Constructor)] +#[derive(Clone, Default, Debug, derive_more::Constructor, derive_with::With)] pub struct ForgeDetails { forge_name: ForgeName, forge_type: ForgeType, @@ -27,11 +27,6 @@ impl ForgeDetails { pub const fn token(&self) -> &ApiToken { &self.token } - pub fn with_hostname(self, hostname: Hostname) -> Self { - let mut me = self; - me.hostname = hostname; - me - } } impl From<(&ForgeName, &ForgeConfig)> for ForgeDetails { fn from(forge: (&ForgeName, &ForgeConfig)) -> Self { diff --git a/crates/config/src/forge_name.rs b/crates/config/src/forge_name.rs index bea4284..b74406c 100644 --- a/crates/config/src/forge_name.rs +++ b/crates/config/src/forge_name.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; /// The name of a Forge to connect to -#[derive(Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] +#[derive(Clone, Default, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] pub struct ForgeName(String); impl From<&ForgeName> for PathBuf { fn from(value: &ForgeName) -> Self { diff --git a/crates/config/src/forge_type.rs b/crates/config/src/forge_type.rs index 4922595..a92c477 100644 --- a/crates/config/src/forge_type.rs +++ b/crates/config/src/forge_type.rs @@ -1,5 +1,5 @@ /// Identifier for the type of Forge -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize)] +#[derive(Clone, Default, Copy, Debug, PartialEq, Eq, serde::Deserialize)] pub enum ForgeType { #[cfg(feature = "forgejo")] ForgeJo, @@ -7,6 +7,7 @@ pub enum ForgeType { // GitHub, // GitLab, // BitBucket, + #[default] MockForge, } impl std::fmt::Display for ForgeType { diff --git a/crates/config/src/git_dir.rs b/crates/config/src/git_dir.rs index fb5b23a..f27596d 100644 --- a/crates/config/src/git_dir.rs +++ b/crates/config/src/git_dir.rs @@ -1,7 +1,15 @@ use std::path::PathBuf; #[derive( - Debug, Clone, PartialEq, Eq, serde::Deserialize, derive_more::Deref, derive_more::From, + Clone, + Default, + Debug, + Hash, + PartialEq, + Eq, + serde::Deserialize, + derive_more::Deref, + derive_more::From, )] pub struct GitDir(PathBuf); impl GitDir { diff --git a/crates/config/src/host_name.rs b/crates/config/src/host_name.rs index a1b7b15..cb2967a 100644 --- a/crates/config/src/host_name.rs +++ b/crates/config/src/host_name.rs @@ -1,5 +1,5 @@ /// The hostname of a forge -#[derive(Clone, Debug, PartialEq, Eq, derive_more::Display)] +#[derive(Clone, Default, Debug, PartialEq, Eq, derive_more::Display)] pub struct Hostname(String); impl Hostname { pub fn new(str: impl Into) -> Self { diff --git a/crates/config/src/repo_alias.rs b/crates/config/src/repo_alias.rs index 38091fd..18e83da 100644 --- a/crates/config/src/repo_alias.rs +++ b/crates/config/src/repo_alias.rs @@ -1,6 +1,6 @@ /// The alias of a repo /// This is the alias for the repo within `git-next-server.toml` -#[derive(Clone, Debug, PartialEq, Eq, Hash, derive_more::Display)] +#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, derive_more::Display)] pub struct RepoAlias(String); impl RepoAlias { pub fn new(str: impl Into) -> Self { diff --git a/crates/config/src/repo_path.rs b/crates/config/src/repo_path.rs index 832f9be..f3079af 100644 --- a/crates/config/src/repo_path.rs +++ b/crates/config/src/repo_path.rs @@ -1,5 +1,5 @@ /// The path for the repo within the forge. /// Typically this is composed of the user or organisation and the name of the repo /// e.g. `{user}/{repo}` -#[derive(Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] +#[derive(Clone, Default, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] pub struct RepoPath(String); diff --git a/crates/config/src/user.rs b/crates/config/src/user.rs index 48eea03..d3b9437 100644 --- a/crates/config/src/user.rs +++ b/crates/config/src/user.rs @@ -1,3 +1,3 @@ /// The user within the forge to connect as -#[derive(Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] +#[derive(Clone, Default, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] pub struct User(String); diff --git a/crates/git/Cargo.toml b/crates/git/Cargo.toml index 65cbb65..dbbf622 100644 --- a/crates/git/Cargo.toml +++ b/crates/git/Cargo.toml @@ -45,6 +45,7 @@ secrecy = { workspace = true } # error handling derive_more = { workspace = true } +derive-with = { workspace = true } # # file watcher # inotify = { workspace = true } @@ -54,9 +55,9 @@ derive_more = { workspace = true } # actix-rt = { workspace = true } # tokio = { workspace = true } # -# [dev-dependencies] -# # Testing -# assert2 = { workspace = true } +[dev-dependencies] +# Testing +assert2 = { workspace = true } [lints.clippy] nursery = { level = "warn", priority = -1 } diff --git a/crates/git/src/repo_details.rs b/crates/git/src/repo_details.rs index 93e83cc..f771fb9 100644 --- a/crates/git/src/repo_details.rs +++ b/crates/git/src/repo_details.rs @@ -6,7 +6,7 @@ use git_next_config::{ use super::{Generation, GitRemote}; /// The derived information about a repo, used to interact with it -#[derive(Clone, Debug, derive_more::Display)] +#[derive(Clone, Default, Debug, derive_more::Display, derive_with::With)] #[display("gen-{}:{}:{}/{}:{}@{}/{}@{}", generation, forge.forge_type(), forge.forge_name(), repo_alias, forge.user(), forge.hostname(), repo_path, branch)] diff --git a/crates/git/src/repository/mock.rs b/crates/git/src/repository/mock.rs index c074826..de755e1 100644 --- a/crates/git/src/repository/mock.rs +++ b/crates/git/src/repository/mock.rs @@ -1,17 +1,116 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + use git_next_config::GitDir; use crate::{ - repository::{open::OpenRepository, Error, RepositoryLike}, - RepoDetails, + repository::{ + open::{OpenRepository, OpenRepositoryLike}, + Direction, RepositoryLike, Result, + }, + GitRemote, RepoDetails, }; -pub struct MockRepository; -impl RepositoryLike for MockRepository { - fn open(&self, _gitdir: &GitDir) -> Result { - Ok(OpenRepository::Mock) - } +use super::Error; - fn git_clone(&self, _repo_details: &RepoDetails) -> Result { - Ok(OpenRepository::Mock) +#[derive(Debug, Default, Clone)] +pub struct MockRepository(Arc>); +impl MockRepository { + pub fn new() -> Self { + Self(Arc::new(Mutex::new(Reality::default()))) + } + pub fn can_open_repo(&mut self, gitdir: &GitDir) -> Result { + self.0 + .lock() + .map_err(|_| Error::MockLock) + .map(|mut r| r.can_open_repo(gitdir)) + } + fn open_repository( + &self, + gitdir: &GitDir, + ) -> std::result::Result { + self.0.lock().map_err(|_| Error::MockLock).and_then(|r| { + r.open_repository(gitdir) + .ok_or_else(|| Error::Open(format!("mock - could not open: {}", gitdir))) + }) + } + fn clone_repository( + &self, + ) -> std::result::Result { + todo!() + } +} +impl RepositoryLike for MockRepository { + fn open( + &self, + gitdir: &GitDir, + ) -> std::result::Result { + Ok(OpenRepository::Mock(self.open_repository(gitdir)?)) + } + + fn git_clone( + &self, + _repo_details: &RepoDetails, + ) -> std::result::Result { + Ok(OpenRepository::Mock(self.clone_repository()?)) + } +} + +#[derive(Debug, Default)] +pub struct Reality { + openable_repos: HashMap, +} +impl Reality { + pub fn can_open_repo(&mut self, gitdir: &GitDir) -> MockOpenRepository { + let mor = self.openable_repos.get(gitdir); + match mor { + Some(mor) => mor.clone(), + None => { + let mor = MockOpenRepository::default(); + self.openable_repos.insert(gitdir.clone(), mor.clone()); + mor + } + } + } + pub fn open_repository(&self, gitdir: &GitDir) -> Option { + self.openable_repos.get(gitdir).cloned() + } +} + +#[derive(Clone, Debug, Default)] +pub struct MockOpenRepository { + default_push_remote: Option, + default_fetch_remote: Option, +} +impl MockOpenRepository { + pub fn has_default_remote(&mut self, direction: Direction, git_remote: GitRemote) { + match direction { + Direction::Push => self.default_push_remote.replace(git_remote), + Direction::Fetch => self.default_fetch_remote.replace(git_remote), + }; + } +} +impl OpenRepositoryLike for MockOpenRepository { + fn find_default_remote(&self, direction: Direction) -> Option { + match direction { + Direction::Push => self.default_push_remote.clone(), + Direction::Fetch => self.default_fetch_remote.clone(), + } + } + + fn fetch(&self) -> std::prelude::v1::Result<(), crate::fetch::Error> { + todo!() + } + + fn push( + &self, + _repo_details: &RepoDetails, + _branch_name: git_next_config::BranchName, + _to_commit: crate::GitRef, + _force: crate::push::Force, + ) -> std::prelude::v1::Result<(), crate::push::Error> { + todo!() } } diff --git a/crates/git/src/repository/mod.rs b/crates/git/src/repository/mod.rs index 5e858eb..b4b3807 100644 --- a/crates/git/src/repository/mod.rs +++ b/crates/git/src/repository/mod.rs @@ -9,23 +9,26 @@ use git_next_config::GitDir; pub use open::OpenRepository; +use crate::repository::mock::MockRepository; + use super::RepoDetails; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Repository { Real, - Mock, + Mock(MockRepository), } pub const fn new() -> Repository { Repository::Real } -pub const fn mock() -> Repository { - Repository::Mock +pub fn mock() -> (Repository, MockRepository) { + let mock_repository = MockRepository::new(); + (Repository::Mock(mock_repository.clone()), mock_repository) } pub trait RepositoryLike { - fn open(&self, gitdir: &GitDir) -> Result; - fn git_clone(&self, repo_details: &RepoDetails) -> Result; + fn open(&self, gitdir: &GitDir) -> Result; + fn git_clone(&self, repo_details: &RepoDetails) -> Result; } impl std::ops::Deref for Repository { type Target = dyn RepositoryLike; @@ -33,7 +36,7 @@ impl std::ops::Deref for Repository { fn deref(&self) -> &Self::Target { match self { Self::Real => &real::RealRepository, - Self::Mock => &mock::MockRepository, + Self::Mock(mock_repository) => mock_repository, } } } @@ -54,6 +57,8 @@ impl From for gix::remote::Direction { } } +pub type Result = core::result::Result; + #[derive(Debug, derive_more::Display)] pub enum Error { InvalidGitDir(git_next_config::GitDir), @@ -64,6 +69,7 @@ pub enum Error { Clone(String), Open(String), Fetch(String), + MockLock, } impl std::error::Error for Error {} impl From for Error { diff --git a/crates/git/src/repository/open/mod.rs b/crates/git/src/repository/open/mod.rs index 2922f9b..090f788 100644 --- a/crates/git/src/repository/open/mod.rs +++ b/crates/git/src/repository/open/mod.rs @@ -7,19 +7,22 @@ use git_next_config::BranchName; use crate::{ fetch, push, - repository::{open::oreal::RealOpenRepository, Direction}, + repository::{mock::MockOpenRepository, open::oreal::RealOpenRepository, Direction}, GitRef, GitRemote, RepoDetails, }; #[derive(Clone, Debug)] pub enum OpenRepository { Real(oreal::RealOpenRepository), - Mock, // TODO: (#38) contain a mock model of a repo + Mock(MockOpenRepository), // TODO: (#38) contain a mock model of a repo } impl OpenRepository { - pub fn new(gix_repo: gix::Repository) -> Self { + pub fn real(gix_repo: gix::Repository) -> Self { Self::Real(RealOpenRepository::new(Arc::new(Mutex::new(gix_repo)))) } + pub const fn mock(mock: MockOpenRepository) -> Self { + Self::Mock(mock) + } } pub trait OpenRepositoryLike { fn find_default_remote(&self, direction: Direction) -> Option; @@ -38,7 +41,7 @@ impl std::ops::Deref for OpenRepository { fn deref(&self) -> &Self::Target { match self { Self::Real(real) => real, - Self::Mock => todo!(), + Self::Mock(mock) => mock, } } } diff --git a/crates/git/src/repository/real.rs b/crates/git/src/repository/real.rs index 4d04fd7..b693c7a 100644 --- a/crates/git/src/repository/real.rs +++ b/crates/git/src/repository/real.rs @@ -12,7 +12,7 @@ pub struct RealRepository; impl RepositoryLike for RealRepository { fn open(&self, gitdir: &GitDir) -> Result { let gix_repo = gix::ThreadSafeRepository::open(gitdir.to_path_buf())?.to_thread_local(); - Ok(OpenRepository::new(gix_repo)) + Ok(OpenRepository::real(gix_repo)) } fn git_clone(&self, repo_details: &RepoDetails) -> Result { @@ -23,6 +23,6 @@ impl RepositoryLike for RealRepository { )? .fetch_only(gix::progress::Discard, &AtomicBool::new(false))?; - Ok(OpenRepository::new(gix_repo)) + Ok(OpenRepository::real(gix_repo)) } } diff --git a/crates/git/src/tests.rs b/crates/git/src/tests.rs index c75fb47..c5a052f 100644 --- a/crates/git/src/tests.rs +++ b/crates/git/src/tests.rs @@ -160,3 +160,40 @@ mod repo_details { ); } } +mod repository { + use assert2::let_assert; + use git_next_config::{ForgeDetails, GitDir, Hostname, RepoPath}; + + use crate::{ + repository::{self, Direction}, + validate, GitRemote, RepoDetails, + }; + + #[test] + fn validate_should_ok_a_valid_repo() { + let forge_details = + ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())); + let repo_details = RepoDetails::default() + .with_forge(forge_details) + .with_repo_path(RepoPath::new("kemitix/test".to_string())); + let gitdir = GitDir::from("foo"); + let remote = GitRemote::new( + Hostname::new("localhost"), + RepoPath::new("kemitix/test".to_string()), + ); + + let (repository, mut reality) = repository::mock(); + let_assert!( + Ok(_) = reality.can_open_repo(&gitdir).map(|mut open_repo| { + open_repo.has_default_remote(Direction::Push, remote.clone()); + open_repo.has_default_remote(Direction::Fetch, remote); + }) + ); + + let_assert!(Ok(open_repository) = repository.open(&gitdir)); + + // FIXME: flaky test - failing 1 in 4 times - NoDefaultPushRemote + let_assert!(Ok(_) = validate(&open_repository, &repo_details)); + } + // TODO: validate_should_fail_where_???? +} diff --git a/crates/server/src/actors/server.rs b/crates/server/src/actors/server.rs index c111bf8..eb6f926 100644 --- a/crates/server/src/actors/server.rs +++ b/crates/server/src/actors/server.rs @@ -177,7 +177,7 @@ impl Server { let server_storage = server_storage.clone(); let webhook = webhook.clone(); let net = self.net.clone(); - let repo = self.repo; + let repo = self.repo.clone(); let generation = self.generation; move |(repo_alias, server_repo_config)| { let span = tracing::info_span!("create_actor", alias = %repo_alias, config = %server_repo_config); @@ -205,8 +205,13 @@ impl Server { gitdir, ); info!("Starting Repo Actor"); - let actor = - RepoActor::new(repo_details, webhook.clone(), generation, net.clone(), repo); + let actor = RepoActor::new( + repo_details, + webhook.clone(), + generation, + net.clone(), + repo.clone(), + ); (forge_name.clone(), repo_alias, actor) } } diff --git a/crates/server/src/gitforge/tests/forgejo.rs b/crates/server/src/gitforge/tests/forgejo.rs index ebd77ca..3edf231 100644 --- a/crates/server/src/gitforge/tests/forgejo.rs +++ b/crates/server/src/gitforge/tests/forgejo.rs @@ -13,7 +13,7 @@ fn test_name() { panic!("fs") }; let net = Network::new_mock(); - let repo = git::repository::mock(); + let (repo, _reality) = git::repository::mock(); let repo_details = common::repo_details( 1, Generation::new(), @@ -40,7 +40,7 @@ async fn test_branches_get() { let body = include_str!("./data-forgejo-branches-get.json"); net.add_get_response(&url, StatusCode::OK, body); let net = Network::from(net); - let repo = git::repository::mock(); + let (repo, _reality) = git::repository::mock(); let repo_details = common::repo_details( 1,