diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 11d47e7..93156d5 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -57,6 +57,7 @@ actix = { workspace = true } [dev-dependencies] # # Testing assert2 = { workspace = true } +rand = { workspace = true } pretty_assertions = { workspace = true } [lints.clippy] diff --git a/crates/config/src/forge_name.rs b/crates/config/src/forge_name.rs index aa3c47e..db29d9a 100644 --- a/crates/config/src/forge_name.rs +++ b/crates/config/src/forge_name.rs @@ -1,9 +1,9 @@ use std::path::PathBuf; +use derive_more::{AsRef, Constructor, Display}; + /// The name of a Forge to connect to -#[derive( - Clone, Default, Debug, Hash, PartialEq, Eq, derive_more::Constructor, derive_more::Display, -)] +#[derive(Clone, Default, Debug, Hash, PartialEq, Eq, Constructor, Display, AsRef)] pub struct ForgeAlias(String); impl From<&ForgeAlias> for PathBuf { fn from(value: &ForgeAlias) -> Self { diff --git a/crates/config/src/forge_type.rs b/crates/config/src/forge_type.rs index fda6c1d..2449b3d 100644 --- a/crates/config/src/forge_type.rs +++ b/crates/config/src/forge_type.rs @@ -13,6 +13,6 @@ pub enum ForgeType { } impl std::fmt::Display for ForgeType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", format!("{:?}", self).to_lowercase()) + write!(f, "{:?}", self) } } diff --git a/crates/config/src/git_dir.rs b/crates/config/src/git_dir.rs index 57c0cc4..7069890 100644 --- a/crates/config/src/git_dir.rs +++ b/crates/config/src/git_dir.rs @@ -13,8 +13,8 @@ use std::path::PathBuf; )] pub struct GitDir(PathBuf); impl GitDir { - pub fn new(pathbuf: &std::path::Path) -> Self { - Self(pathbuf.to_path_buf()) + pub fn new(path: &std::path::Path) -> Self { + Self(path.to_path_buf()) } pub const fn pathbuf(&self) -> &PathBuf { diff --git a/crates/config/src/server.rs b/crates/config/src/server.rs index d139c1a..068a2c3 100644 --- a/crates/config/src/server.rs +++ b/crates/config/src/server.rs @@ -91,6 +91,9 @@ impl Webhook { let base_url = &self.url; WebhookUrl(format!("{base_url}/{forge_alias}/{repo_alias}")) } + pub fn base_url(&self) -> &str { + &self.url + } } /// The URL for the webhook where forges should send their updates diff --git a/crates/config/src/tests.rs b/crates/config/src/tests.rs index 2091829..ae8f356 100644 --- a/crates/config/src/tests.rs +++ b/crates/config/src/tests.rs @@ -8,77 +8,85 @@ mod server_repo_config { use assert2::let_assert; - use crate::{BranchName, GitDir, RepoBranches, RepoConfig, RepoConfigSource, RepoPath}; + use crate::{ + tests::given, BranchName, GitDir, RepoBranches, RepoConfig, RepoConfigSource, RepoPath, + }; use super::super::server_repo_config::*; #[test] fn should_not_return_repo_config_when_no_branches() { - let src = ServerRepoConfig::new("".to_string(), "".to_string(), None, None, None, None); + let main = None; + let next = None; + let dev = None; + let src = ServerRepoConfig::new(given::a_name(), given::a_name(), None, main, next, dev); let_assert!(None = src.repo_config()); } #[test] fn should_return_repo_config_when_branches() { + let main = given::a_name(); + let next = given::a_name(); + let dev = given::a_name(); let src = ServerRepoConfig::new( - "".to_string(), - "".to_string(), + given::a_name(), + given::a_name(), None, - Some("main".to_string()), - Some("next".to_string()), - Some("dev".to_string()), + Some(main.clone()), + Some(next.clone()), + Some(dev.clone()), ); let_assert!(Some(rc) = src.repo_config()); assert_eq!( rc, - RepoConfig::new( - RepoBranches::new("main".to_string(), "next".to_string(), "dev".to_string()), - RepoConfigSource::Server - ) + RepoConfig::new(RepoBranches::new(main, next, dev), RepoConfigSource::Server) ); } #[test] fn should_return_repo() { + let repo_path = given::a_name(); let src = ServerRepoConfig::new( - "repo".to_string(), - "branch".to_string(), + repo_path.clone(), + given::a_name(), None, - Some("main".to_string()), - Some("next".to_string()), - Some("dev".to_string()), + Some(given::a_name()), + Some(given::a_name()), + Some(given::a_name()), ); - assert_eq!(src.repo(), RepoPath::new("repo".to_string())); + assert_eq!(src.repo(), RepoPath::new(repo_path)); } #[test] fn should_return_branch() { + let branch = given::a_name(); let src = ServerRepoConfig::new( - "repo".to_string(), - "branch".to_string(), + given::a_name(), + branch.clone(), None, - Some("main".to_string()), - Some("next".to_string()), - Some("dev".to_string()), + Some(given::a_name()), + Some(given::a_name()), + Some(given::a_name()), ); - assert_eq!(src.branch(), BranchName::new("branch".to_string())); + assert_eq!(src.branch(), BranchName::new(branch)); } #[test] fn should_return_gitdir() { + let gitdir = given::a_name(); let src = ServerRepoConfig::new( - "repo".to_string(), - "branch".to_string(), - Some("gitdir".into()), - Some("main".to_string()), - Some("next".to_string()), - Some("dev".to_string()), + given::a_name(), + given::a_name(), + Some(gitdir.clone().into()), + Some(given::a_name()), + Some(given::a_name()), + Some(given::a_name()), ); assert_eq!( src.gitdir(), - Some(GitDir::new(&PathBuf::default().join("gitdir"))) + Some(GitDir::new(&PathBuf::default().join(gitdir))) ); } } @@ -90,19 +98,24 @@ mod repo_config { #[test] fn should_parse_toml() -> TestResult { - let toml = r#" + let main = given::a_name(); + let next = given::a_name(); + let dev = given::a_name(); + let toml = format!( + r#" [branches] - main = "main" - next = "next" - dev = "dev" - "#; + main = "{main}" + next = "{next}" + dev = "{dev}" + "# + ); - let rc = RepoConfig::load(toml)?; + let rc = RepoConfig::load(toml.as_str())?; assert_eq!( rc, RepoConfig::new( - RepoBranches::new("main".to_string(), "next".to_string(), "dev".to_string(),), + RepoBranches::new(main, next, dev), RepoConfigSource::Repo // reading from repo is the default ) ); @@ -111,17 +124,21 @@ mod repo_config { } #[test] fn should_return_branches() { - let branches = RepoBranches::new("main".to_string(), "next".to_string(), "dev".to_string()); + let main = given::a_name(); + let next = given::a_name(); + let dev = given::a_name(); + let branches = RepoBranches::new(main, next, dev); let repo_config = RepoConfig::new(branches.clone(), RepoConfigSource::Repo); assert_eq!(repo_config.branches(), &branches); } #[test] fn should_return_source() { - let repo_config = RepoConfig::new( - RepoBranches::new("main".to_string(), "next".to_string(), "dev".to_string()), - RepoConfigSource::Repo, - ); + let main = given::a_name(); + let next = given::a_name(); + let dev = given::a_name(); + let repo_config = + RepoConfig::new(RepoBranches::new(main, next, dev), RepoConfigSource::Repo); assert_eq!(repo_config.source(), RepoConfigSource::Repo); } @@ -131,33 +148,25 @@ mod forge_config { use secrecy::ExposeSecret; - use crate::{ForgeConfig, ForgeType, Hostname, RepoAlias, ServerRepoConfig, User}; + use crate::{ + tests::given, ForgeConfig, ForgeType, Hostname, RepoAlias, ServerRepoConfig, User, + }; #[test] fn should_return_repos() { let forge_type = ForgeType::MockForge; - let hostname = "localhost".to_string(); - let user = "bob".to_string(); - let token = "alpha".to_string(); - let red = ServerRepoConfig::new( - "red".to_string(), - "main".to_string(), - None, - None, - None, - None, - ); - let blue = ServerRepoConfig::new( - "blue".to_string(), - "main".to_string(), - None, - None, - None, - None, - ); + let hostname = given::a_name(); + let user = given::a_name(); + let token = given::a_name(); + // alphabetical order by key + let red_name = format!("a-{}", given::a_name()); + let blue_name = format!("b-{}", given::a_name()); + let red = ServerRepoConfig::new(red_name.clone(), given::a_name(), None, None, None, None); + let blue = + ServerRepoConfig::new(blue_name.clone(), given::a_name(), None, None, None, None); let mut repos = BTreeMap::new(); - repos.insert("red".to_string(), red.clone()); - repos.insert("blue".to_string(), blue.clone()); + repos.insert(red_name.clone(), red.clone()); + repos.insert(blue_name.clone(), blue.clone()); let fc = ForgeConfig::new(forge_type, hostname, user, token, repos); let returned_repos = fc.repos().collect::>(); @@ -166,17 +175,17 @@ mod forge_config { returned_repos, vec![ // alphabetical order by key - (RepoAlias::new("blue"), &blue), - (RepoAlias::new("red"), &red), + (RepoAlias::new(red_name.as_str()), &red), + (RepoAlias::new(blue_name.as_str()), &blue), ] ); } #[test] fn should_return_forge_type() { let forge_type = ForgeType::MockForge; - let hostname = "localhost".to_string(); - let user = "bob".to_string(); - let token = "alpha".to_string(); + let hostname = given::a_name(); + let user = given::a_name(); + let token = given::a_name(); let repos = BTreeMap::new(); let fc = ForgeConfig::new(forge_type, hostname, user, token, repos); @@ -185,64 +194,53 @@ mod forge_config { #[test] fn should_return_hostname() { let forge_type = ForgeType::MockForge; - let hostname = "localhost".to_string(); - let user = "bob".to_string(); - let token = "alpha".to_string(); + let hostname = given::a_name(); + let user = given::a_name(); + let token = given::a_name(); let repos = BTreeMap::new(); - let fc = ForgeConfig::new(forge_type, hostname, user, token, repos); + let fc = ForgeConfig::new(forge_type, hostname.clone(), user, token, repos); - assert_eq!(fc.hostname(), Hostname::new("localhost")); + assert_eq!(fc.hostname(), Hostname::new(hostname)); } #[test] fn should_return_user() { let forge_type = ForgeType::MockForge; - let hostname = "localhost".to_string(); - let user = "bob".to_string(); - let token = "alpha".to_string(); + let hostname = given::a_name(); + let user = given::a_name(); + let token = given::a_name(); let repos = BTreeMap::new(); - let fc = ForgeConfig::new(forge_type, hostname, user, token, repos); + let fc = ForgeConfig::new(forge_type, hostname, user.clone(), token, repos); - assert_eq!(fc.user(), User::new("bob".to_string())); + assert_eq!(fc.user(), User::new(user)); } #[test] fn should_return_token() { let forge_type = ForgeType::MockForge; - let hostname = "localhost".to_string(); - let user = "bob".to_string(); - let token = "alpha".to_string(); + let hostname = given::a_name(); + let user = given::a_name(); + let token = given::a_name(); let repos = BTreeMap::new(); - let fc = ForgeConfig::new(forge_type, hostname, user, token, repos); + let fc = ForgeConfig::new(forge_type, hostname, user, token.clone(), repos); - assert_eq!(fc.token().expose_secret(), "alpha"); + assert_eq!(fc.token().expose_secret(), token.as_str()); } #[test] fn should_return_repo() { let forge_type = ForgeType::MockForge; - let hostname = "localhost".to_string(); - let user = "bob".to_string(); - let token = "alpha".to_string(); - let red = ServerRepoConfig::new( - "red".to_string(), - "main".to_string(), - None, - None, - None, - None, - ); - let blue = ServerRepoConfig::new( - "blue".to_string(), - "main".to_string(), - None, - None, - None, - None, - ); + let hostname = given::a_name(); + let user = given::a_name(); + let token = given::a_name(); + let red_name = given::a_name(); + let blue_name = given::a_name(); + let red = ServerRepoConfig::new(red_name.clone(), given::a_name(), None, None, None, None); + let blue = + ServerRepoConfig::new(blue_name.clone(), given::a_name(), None, None, None, None); let mut repos = BTreeMap::new(); - repos.insert("red".to_string(), red.clone()); - repos.insert("blue".to_string(), blue); + repos.insert(red_name.clone(), red.clone()); + repos.insert(blue_name, blue); let fc = ForgeConfig::new(forge_type, hostname, user, token, repos); - let returned_repo = fc.get_repo("red"); + let returned_repo = fc.get_repo(red_name.as_str()); assert_eq!(returned_repo, Some(&red),); } @@ -252,29 +250,31 @@ mod forge_details { use secrecy::ExposeSecret; - use crate::{ApiToken, ForgeAlias, ForgeConfig, ForgeDetails, ForgeType, Hostname, User}; + use crate::{ + tests::given, ApiToken, ForgeAlias, ForgeConfig, ForgeDetails, ForgeType, Hostname, User, + }; #[test] - fn should_return_forge_name() { + fn should_return_forge_alias() { let forge_type = ForgeType::MockForge; - let hostname = Hostname::new("localhost".to_string()); - let user = User::new("bob".to_string()); - let token = ApiToken::new("alpha".to_string().into()); - let forge_name = ForgeAlias::new("gamma".to_string()); + let hostname = Hostname::new(given::a_name()); + let user = User::new(given::a_name()); + let token = ApiToken::new(given::a_name().into()); + let forge_alias = ForgeAlias::new(given::a_name()); let forge_details = - ForgeDetails::new(forge_name.clone(), forge_type, hostname, user, token); + ForgeDetails::new(forge_alias.clone(), forge_type, hostname, user, token); let result = forge_details.forge_alias(); - assert_eq!(result, &forge_name); + assert_eq!(result, &forge_alias); } #[test] fn should_return_forge_type() { let forge_type = ForgeType::MockForge; - let hostname = Hostname::new("localhost".to_string()); - let user = User::new("bob".to_string()); - let token = ApiToken::new("alpha".to_string().into()); - let forge_name = ForgeAlias::new("gamma".to_string()); + let hostname = Hostname::new(given::a_name()); + let user = User::new(given::a_name()); + let token = ApiToken::new(given::a_name().into()); + let forge_name = ForgeAlias::new(given::a_name()); let forge_details = ForgeDetails::new(forge_name, forge_type, hostname, user, token); let result = forge_details.forge_type(); @@ -284,10 +284,10 @@ mod forge_details { #[test] fn should_return_hostname() { let forge_type = ForgeType::MockForge; - let hostname = Hostname::new("localhost".to_string()); - let user = User::new("bob".to_string()); - let token = ApiToken::new("alpha".to_string().into()); - let forge_name = ForgeAlias::new("gamma".to_string()); + let hostname = Hostname::new(given::a_name()); + let user = User::new(given::a_name()); + let token = ApiToken::new(given::a_name().into()); + let forge_name = ForgeAlias::new(given::a_name()); let forge_details = ForgeDetails::new(forge_name, forge_type, hostname.clone(), user, token); @@ -298,10 +298,10 @@ mod forge_details { #[test] fn should_return_user() { let forge_type = ForgeType::MockForge; - let hostname = Hostname::new("localhost".to_string()); - let user = User::new("bob".to_string()); - let token = ApiToken::new("alpha".to_string().into()); - let forge_name = ForgeAlias::new("gamma".to_string()); + let hostname = Hostname::new(given::a_name()); + let user = User::new(given::a_name()); + let token = ApiToken::new(given::a_name().into()); + let forge_name = ForgeAlias::new(given::a_name()); let forge_details = ForgeDetails::new(forge_name, forge_type, hostname, user.clone(), token); @@ -312,10 +312,10 @@ mod forge_details { #[test] fn should_return_token() { let forge_type = ForgeType::MockForge; - let hostname = Hostname::new("localhost".to_string()); - let user = User::new("bob".to_string()); - let token = ApiToken::new("alpha".to_string().into()); - let forge_name = ForgeAlias::new("gamma".to_string()); + let hostname = Hostname::new(given::a_name()); + let user = User::new(given::a_name()); + let token = ApiToken::new(given::a_name().into()); + let forge_name = ForgeAlias::new(given::a_name()); let forge_details = ForgeDetails::new(forge_name, forge_type, hostname, user, token.clone()); @@ -326,28 +326,32 @@ mod forge_details { #[test] fn with_hostname_should_return_new_instance() { let forge_type = ForgeType::MockForge; - let hostname = Hostname::new("localhost".to_string()); - let user = User::new("bob".to_string()); - let token = ApiToken::new("alpha".to_string().into()); - let forge_name = ForgeAlias::new("gamma".to_string()); + let hostname = Hostname::new(given::a_name()); + let other_hostname = Hostname::new(given::a_name()); + let user = User::new(given::a_name()); + let token = ApiToken::new(given::a_name().into()); + let forge_name = ForgeAlias::new(given::a_name()); let forge_details = ForgeDetails::new(forge_name, forge_type, hostname, user, token); - let result = forge_details.with_hostname(Hostname::new("remotehost".to_string())); + let result = forge_details.with_hostname(other_hostname.clone()); - assert_eq!(result.hostname(), &Hostname::new("remotehost".to_string())); + assert_eq!(result.hostname(), &other_hostname); } #[test] fn should_convert_from_name_and_config() { let forge_type = ForgeType::MockForge; - let hostname = Hostname::new("localhost".to_string()); - let user = User::new("bob".to_string()); - let token = ApiToken::new("alpha".to_string().into()); - let forge_alias = ForgeAlias::new("gamma".to_string()); + let hostname_value = given::a_name(); + let hostname = Hostname::new(hostname_value.clone()); + let user_value = given::a_name(); + let user = User::new(user_value.clone()); + let token_value = given::a_name(); + let token = ApiToken::new(token_value.clone().into()); + let forge_alias = ForgeAlias::new(given::a_name()); let forge_config = ForgeConfig::new( forge_type, - "localhost".to_string(), - "bob".to_string(), - "alpha".to_string(), + hostname_value, + user_value, + token_value, BTreeMap::new(), ); @@ -362,33 +366,34 @@ mod forge_details { mod forge_name { use std::path::PathBuf; - use crate::ForgeAlias; + use crate::{tests::given, ForgeAlias}; #[test] fn should_convert_to_pathbuf() { - let forge_alias = ForgeAlias::new("alpha".to_string()); + let name = given::a_name(); + let forge_alias = ForgeAlias::new(name.clone()); let pathbuf: PathBuf = (&forge_alias).into(); - assert_eq!(pathbuf, PathBuf::new().join("alpha")); + assert_eq!(pathbuf, PathBuf::new().join(name)); } } mod forge_type { use crate::ForgeType; #[test] - fn should_display_as_lowercase() { - assert_eq!(ForgeType::MockForge.to_string(), "mockforge".to_string()); + fn should_display_as_string() { + assert_eq!(ForgeType::MockForge.to_string(), "MockForge".to_string()); } } mod gitdir { use std::path::PathBuf; - use crate::GitDir; + use crate::{tests::given, GitDir}; #[test] fn should_return_pathbuf() { - let pathbuf = PathBuf::default().join("foo"); + let pathbuf = PathBuf::default().join(given::a_name()); let gitdir = GitDir::new(&pathbuf); let result = gitdir.pathbuf(); @@ -406,15 +411,17 @@ mod gitdir { } #[test] fn should_convert_from_str() { - let pathbuf = PathBuf::default().join("foo"); - let gitdir: GitDir = "foo".into(); + let path = given::a_name(); + let pathbuf = PathBuf::default().join(path.clone()); + let gitdir: GitDir = path.as_str().into(); assert_eq!(gitdir, GitDir::new(&pathbuf)); } #[test] fn should_convert_to_pathbuf_from_ref() { - let pathbuf = PathBuf::default().join("foo"); - let gitdir: GitDir = "foo".into(); + let path = given::a_name(); + let pathbuf = PathBuf::default().join(path.clone()); + let gitdir: GitDir = path.as_str().into(); let result: PathBuf = (&gitdir).into(); @@ -422,8 +429,9 @@ mod gitdir { } #[test] fn should_convert_to_pathbuf_from_inst() { - let pathbuf = PathBuf::default().join("foo"); - let gitdir: GitDir = "foo".into(); + let path = given::a_name(); + let pathbuf = PathBuf::default().join(path.clone()); + let gitdir: GitDir = path.as_str().into(); let result: PathBuf = gitdir.into(); @@ -431,228 +439,140 @@ mod gitdir { } } mod repo_branches { - use crate::{BranchName, RepoBranches}; + use crate::{tests::given, BranchName, RepoBranches}; #[test] fn should_return_main() { - let repo_branches = - RepoBranches::new("main".to_string(), "next".to_string(), "dev".to_string()); + let main = given::a_name(); + let next = given::a_name(); + let dev = given::a_name(); + let repo_branches = RepoBranches::new(main.clone(), next, dev); - assert_eq!(repo_branches.main(), BranchName::new("main")); + assert_eq!(repo_branches.main(), BranchName::new(main)); } #[test] fn should_return_next() { - let repo_branches = - RepoBranches::new("main".to_string(), "next".to_string(), "dev".to_string()); + let main = given::a_name(); + let next = given::a_name(); + let dev = given::a_name(); + let repo_branches = RepoBranches::new(main, next.clone(), dev); - assert_eq!(repo_branches.next(), BranchName::new("next")); + assert_eq!(repo_branches.next(), BranchName::new(next)); } #[test] fn should_return_dev() { - let repo_branches = - RepoBranches::new("main".to_string(), "next".to_string(), "dev".to_string()); + let main = given::a_name(); + let next = given::a_name(); + let dev = given::a_name(); + let repo_branches = RepoBranches::new(main, next, dev.clone()); - assert_eq!(repo_branches.dev(), BranchName::new("dev")); + assert_eq!(repo_branches.dev(), BranchName::new(dev)); } } mod server { mod load { - // - - use std::{ - collections::{BTreeMap, HashMap}, - net::SocketAddr, - str::FromStr as _, - }; use assert2::let_assert; use pretty_assertions::assert_eq; use crate::{ - server::{Http, ServerConfig, ServerStorage, Webhook}, - tests::TestResult, - ForgeAlias, ForgeConfig, ForgeType, RepoAlias, RepoBranches, RepoConfig, - RepoConfigSource, ServerRepoConfig, + server::ServerConfig, + tests::{given, TestResult}, }; #[test] fn load_should_parse_server_config() -> TestResult { + let server_config = given::a_server_config(); let fs = kxio::fs::temp()?; - given_server_config(&fs)?; - let_assert!(Ok(config) = ServerConfig::load(&fs)); - let expected = ServerConfig::new( - Http::new("0.0.0.0".to_string(), 8080), - expected_webhook(), - expected_storage(), - HashMap::from([("default".to_string(), expected_forge_config())]), - ); - assert_eq!(config, expected, "ServerConfig"); + let_assert!(Ok(_) = write_server_config(&server_config, &fs), "write"); + let_assert!(Ok(config) = ServerConfig::load(&fs), "load"); + assert_eq!(config, server_config, "compare"); - if let Some(forge) = config.forge.get("world") { - if let Some(repo) = forge.get_repo("sam") { - let repo_config = repo.repo_config(); - let expected = Some(RepoConfig::new( - RepoBranches::new( - "master".to_string(), - "upcoming".to_string(), - "sam-dev".to_string(), - ), - RepoConfigSource::Server, - )); - assert_eq!(repo_config, expected, "RepoConfig"); - } + Ok(()) + } + + fn write_server_config( + server_config: &ServerConfig, + fs: &kxio::fs::FileSystem, + ) -> TestResult { + let http = &server_config.http()?; + let http_addr = http.ip(); + let http_port = server_config.http()?.port(); + let webhook_url = server_config.webhook().base_url(); + let storage_path = server_config.storage().path(); + let forge_alias = server_config + .forges() + .next() + .map(|(fa, _)| fa) + .ok_or("forge missing")?; + let forge_default = server_config + .forge + .get(forge_alias.as_ref()) + .ok_or("forge missing")?; + let forge_type = forge_default.forge_type(); + let forge_hostname = forge_default.hostname(); + let forge_user = forge_default.user(); + use secrecy::ExposeSecret; + let forge_token = forge_default.token().expose_secret().to_string(); + let mut repos: Vec = vec![]; + for (repo_alias, server_repo_config) in forge_default.repos() { + let repo_path = server_repo_config.repo(); + let branch = server_repo_config.branch(); + let gitdir = server_repo_config + .gitdir() + .map(|gitdir| format!(r#", gitdir = "{gitdir}" "#)) + .unwrap_or_default(); + let repo_server_config = server_repo_config + .repo_config() + .map(|src| { + let branches = src.branches(); + let main = branches.main(); + let next = branches.next(); + let dev = branches.dev(); + format!(r#", main = "{main}", next = "{next}", dev = "{dev}""#) + }) + .unwrap_or_default(); + repos.push(format!(r#"{repo_alias} = {{ repo = "{repo_path}", branch = "{branch}"{gitdir}{repo_server_config} }}"#)); } + let repos = repos.join("\n"); + let file_contents = &format!( + r#" +[http] +addr = "{http_addr}" +port = {http_port} - Ok(()) - } +[webhook] +url = "{webhook_url}" - #[test] - fn should_return_forges() -> TestResult { - let fs = kxio::fs::temp()?; - given_server_config(&fs)?; - let config = ServerConfig::load(&fs)?; - let forges = config.forges().collect::>(); - let forge_config = expected_forge_config(); - let expected = vec![(ForgeAlias::new("default".to_string()), &forge_config)]; - assert_eq!(forges, expected); - Ok(()) - } +[storage] +path = {storage_path:?} - #[test] - fn should_return_storage() -> TestResult { - let fs = kxio::fs::temp()?; - given_server_config(&fs)?; - let config = ServerConfig::load(&fs)?; - let storage = config.storage(); - assert_eq!(storage, &expected_storage()); - assert_eq!(storage.path(), std::path::Path::new("/opt/git-next/data")); - Ok(()) - } +[forge.{forge_alias}] +forge_type = "{forge_type}" +hostname = "{forge_hostname}" +user = "{forge_user}" +token = "{forge_token}" - #[test] - fn should_return_webhook() -> TestResult { - let fs = kxio::fs::temp()?; - given_server_config(&fs)?; - let config = ServerConfig::load(&fs)?; - let webhook = config.webhook(); - assert_eq!(webhook, &expected_webhook()); - assert_eq!( - webhook - .url(&ForgeAlias::new("a".to_string()), &RepoAlias::new("b")) - .as_ref(), - "http://localhost:9909/a/b" +[forge.{forge_alias}.repos] +{repos} + "# ); - Ok(()) - } - - #[test] - fn should_return_http() -> TestResult { - let fs = kxio::fs::temp()?; - given_server_config(&fs)?; - let config = ServerConfig::load(&fs)?; - let_assert!(Ok(http) = config.http()); - assert_eq!(http, expected_http()); - Ok(()) - } - - fn given_server_config(fs: &kxio::fs::FileSystem) -> Result<(), kxio::fs::Error> { + eprintln!("{file_contents}"); fs.file_write( - &fs.base().join("git-next-server.toml"), - r#" - [http] - addr = "0.0.0.0" - port = 8080 - - [webhook] - url = "http://localhost:9909" - - [storage] - path = "/opt/git-next/data" - - [forge.default] - forge_type = "MockForge" - hostname = "git.example.net" - user = "Bob" - token = "API-Token" - - [forge.default.repos] - hello = { repo = "user/hello", branch = "main", gitdir = "/opt/git/user/hello.git" } - world = { repo = "user/world", branch = "master", main = "main", next = "next", dev = "dev" } - - [forge.default.repos.sam] - repo = "user/sam" - branch = "main" - main = "master" - next = "upcoming" - dev = "sam-dev" - "#, - ) - } - - fn expected_storage() -> ServerStorage { - ServerStorage::new("/opt/git-next/data".into()) - } - - fn expected_webhook() -> Webhook { - Webhook::new("http://localhost:9909".to_string()) - } - - fn expected_http() -> SocketAddr { - SocketAddr::from_str("0.0.0.0:8080").unwrap_or_else(|_| panic!()) - } - - fn expected_forge_config() -> ForgeConfig { - ForgeConfig::new( - ForgeType::MockForge, - "git.example.net".to_string(), - "Bob".to_string(), - "API-Token".to_string(), - BTreeMap::from([ - ( - "hello".to_string(), - ServerRepoConfig::new( - "user/hello".to_string(), - "main".to_string(), - Some("/opt/git/user/hello.git".into()), - None, - None, - None, - ), - ), - ( - "world".to_string(), - ServerRepoConfig::new( - "user/world".to_string(), - "master".to_string(), - None, - Some("main".to_string()), - Some("next".to_string()), - Some("dev".to_string()), - ), - ), - ( - "sam".to_string(), - ServerRepoConfig::new( - "user/sam".to_string(), - "main".to_string(), - None, - Some("master".to_string()), - Some("upcoming".to_string()), - Some("sam-dev".to_string()), - ), - ), - ]), - ) + &fs.base().join("git-next-server.toml"), + file_contents.as_str(), + )?; + Ok(()) } } } mod registered_webhook { - use crate::{RegisteredWebhook, WebhookAuth, WebhookId}; + use crate::{tests::given, RegisteredWebhook, WebhookAuth}; #[test] fn should_return_id() { - let id = WebhookId::new("a".to_string()); + let id = given::a_webhook_id(); let auth = WebhookAuth::generate(); let rw = RegisteredWebhook::new(id.clone(), auth); @@ -660,7 +580,7 @@ mod registered_webhook { } #[test] fn should_return_auth() { - let id = WebhookId::new("a".to_string()); + let id = given::a_webhook_id(); let auth = WebhookAuth::generate(); let rw = RegisteredWebhook::new(id, auth.clone()); @@ -669,103 +589,238 @@ mod registered_webhook { } mod webhook { mod message { - use crate::{webhook::message::Body, ForgeAlias, RepoAlias, WebhookMessage}; + use std::collections::HashMap; + + use crate::{tests::given, WebhookMessage}; #[test] fn should_return_forge_alias() { - let message = given_message(); - assert_eq!(message.forge_alias(), &expected_forge_alias()); + let forge_alias = given::a_forge_alias(); + let message = WebhookMessage::new( + forge_alias.clone(), + given::a_repo_alias(), + Default::default(), + given::a_webhook_message_body(), + ); + assert_eq!(message.forge_alias(), &forge_alias); } #[test] fn should_return_repo_alias() { - let message = given_message(); - assert_eq!(message.repo_alias(), &expected_repo_alias()); + let repo_alias = given::a_repo_alias(); + let message = WebhookMessage::new( + given::a_forge_alias(), + repo_alias.clone(), + Default::default(), + given::a_webhook_message_body(), + ); + assert_eq!(message.repo_alias(), &repo_alias); } #[test] fn should_return_body() { - let message = given_message(); - assert_eq!(message.body().as_bytes(), expected_body().as_bytes()); + let body = given::a_webhook_message_body(); + let message = WebhookMessage::new( + given::a_forge_alias(), + given::a_repo_alias(), + Default::default(), + body.clone(), + ); + assert_eq!(message.body().as_bytes(), body.as_bytes()); } #[test] fn should_return_header() { - let message = given_message(); - assert_eq!(message.header("c"), Some("d".to_string())); - } - fn given_message() -> WebhookMessage { - WebhookMessage::new( - expected_forge_alias(), - expected_repo_alias(), - expected_headers(), - expected_body(), - ) - } - fn expected_forge_alias() -> ForgeAlias { - ForgeAlias::new("a".to_string()) - } - fn expected_repo_alias() -> RepoAlias { - RepoAlias::new("b") - } - fn expected_headers() -> std::collections::HashMap { - [("c", "d"), ("e", "f")] - .into_iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect() - } - fn expected_body() -> Body { - Body::new("g".to_string()) + let key = given::a_name(); + let value = given::a_name(); + let mut headers: HashMap = Default::default(); + headers.insert(key.clone(), value.clone()); + let message = WebhookMessage::new( + given::a_forge_alias(), + given::a_repo_alias(), + headers, + given::a_webhook_message_body(), + ); + assert_eq!(message.header(&key), Some(value)); } } } mod push { - use crate::{ - webhook::{push::Branch, Push}, - BranchName, RepoBranches, - }; + use crate::{tests::given, webhook::push::Branch, BranchName}; #[test] fn should_return_main_branch() { - let repo_branches = given_repo_branches(); - let push_event = given_push_event(repo_branches.main()); + let repo_branches = given::some_repo_branches(); + let sha = given::a_name(); + let message = given::a_name(); + let push_event = { + let branch_name = repo_branches.main(); + crate::webhook::Push::new(branch_name, sha, message) + }; assert_eq!(push_event.branch(&repo_branches), Some(Branch::Main)); } #[test] fn should_return_next_branch() { - let repo_branches = given_repo_branches(); - let push_event = given_push_event(repo_branches.next()); + let repo_branches = given::some_repo_branches(); + let sha = given::a_name(); + let message = given::a_name(); + let push_event = { + let branch_name = repo_branches.next(); + crate::webhook::Push::new(branch_name, sha, message) + }; assert_eq!(push_event.branch(&repo_branches), Some(Branch::Next)); } #[test] fn should_return_dev_branch() { - let repo_branches = given_repo_branches(); - let push_event = given_push_event(repo_branches.dev()); + let repo_branches = given::some_repo_branches(); + let sha = given::a_name(); + let message = given::a_name(); + let push_event = { + let branch_name = repo_branches.dev(); + crate::webhook::Push::new(branch_name, sha, message) + }; assert_eq!(push_event.branch(&repo_branches), Some(Branch::Dev)); } #[test] fn should_not_return_other_branches() { - let repo_branches = given_repo_branches(); - let push_event = given_push_event(BranchName::new("foo")); + let repo_branches = given::some_repo_branches(); + let sha = given::a_name(); + let message = given::a_name(); + let push_event = { + let branch_name = BranchName::new(given::a_name()); + crate::webhook::Push::new(branch_name, sha, message) + }; assert_eq!(push_event.branch(&repo_branches), None); } #[test] fn should_return_sha() { - let repo_branches = given_repo_branches(); - let push_event = given_push_event(repo_branches.main()); - assert_eq!(push_event.sha(), "sha"); + let repo_branches = given::some_repo_branches(); + let sha = given::a_name(); + let message = given::a_name(); + let push_event = { + let branch_name = repo_branches.main(); + let sha = sha.clone(); + crate::webhook::Push::new(branch_name, sha, message) + }; + assert_eq!(push_event.sha(), sha); } #[test] fn should_return_message() { - let repo_branches = given_repo_branches(); - let push_event = given_push_event(repo_branches.main()); - assert_eq!(push_event.message(), "message"); + let repo_branches = given::some_repo_branches(); + let sha = given::a_name(); + let message = given::a_name(); + let push_event = { + let branch_name = repo_branches.main(); + let message = message.clone(); + crate::webhook::Push::new(branch_name, sha, message) + }; + assert_eq!(push_event.message(), message); } - fn given_push_event(branch_name: BranchName) -> Push { - Push::new(branch_name, "sha".to_string(), "message".to_string()) +} +mod given { + use std::{ + collections::{BTreeMap, HashMap}, + path::{Path, PathBuf}, + }; + + use rand::Rng as _; + + use crate::{ + server::{Http, ServerConfig, ServerStorage, Webhook}, + BranchName, ForgeAlias, ForgeConfig, ForgeType, RepoAlias, RepoBranches, ServerRepoConfig, + WebhookId, + }; + + 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) } - fn given_repo_branches() -> RepoBranches { + pub fn a_server_config() -> ServerConfig { + ServerConfig::new( + an_http(), + a_webhook(), + a_server_storage(), + some_forge_configs(), + ) + } + pub fn an_http() -> Http { + let address = format!( + "{}.{}.{}.{}", + an_octet(), + an_octet(), + an_octet(), + an_octet() + ); + Http::new(address, a_port()) + } + pub fn an_octet() -> u8 { + rand::thread_rng().gen() + } + pub fn a_port() -> u16 { + rand::thread_rng().gen() + } + pub fn a_webhook() -> Webhook { + Webhook::new(a_name()) + } + pub fn a_server_storage() -> ServerStorage { + ServerStorage::new(a_name().into()) + } + pub fn some_forge_configs() -> HashMap { + [(a_name(), a_forge_config())].into() + } + pub fn a_forge_config() -> ForgeConfig { + ForgeConfig::new( + ForgeType::MockForge, + a_name(), // hostname + a_name(), // user + a_name(), // token + some_server_repo_configs(), + ) + } + pub fn some_server_repo_configs() -> BTreeMap { + [(a_name(), a_server_repo_config())].into() + } + pub fn a_pathbuf() -> PathBuf { + let name = a_name(); + let path = Path::new(name.as_str()); + path.to_path_buf() + } + pub fn a_server_repo_config() -> ServerRepoConfig { + ServerRepoConfig::new( + a_name(), // repo_path + a_name(), // branch + Some(a_pathbuf()), // gitdir + Some(a_name()), // main + Some(a_name()), // next + Some(a_name()), // dev + ) + } + pub fn a_webhook_id() -> WebhookId { + WebhookId::new(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_webhook_message_body() -> crate::webhook::message::Body { + crate::webhook::message::Body::new(a_name()) + } + + pub fn some_repo_branches() -> RepoBranches { RepoBranches::new( - "a-main".to_string(), - "b-next".to_string(), - "c-dev".to_string(), + a_name(), // main + a_name(), // next + a_name(), // dev ) } }