// use super::*; use assert2::let_assert; use secrecy::ExposeSecret; use std::collections::BTreeMap; use std::path::PathBuf; use crate::server::Http; use crate::server::ServerConfig; use crate::server::ServerStorage; use crate::server::Webhook; use crate::webhook::push::Branch; mod url; type Result = core::result::Result>; type TestResult = Result<()>; mod server_repo_config { use super::*; #[test] fn should_not_return_repo_config_when_no_branches() { 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( given::a_name(), given::a_name(), None, Some(main.clone()), Some(next.clone()), Some(dev.clone()), ); let_assert!(Some(rc) = src.repo_config()); assert_eq!( rc, 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_path.clone(), given::a_name(), None, Some(given::a_name()), Some(given::a_name()), Some(given::a_name()), ); assert_eq!(src.repo(), RepoPath::new(repo_path)); } #[test] fn should_return_branch() { let branch = given::a_name(); let src = ServerRepoConfig::new( given::a_name(), branch.clone(), None, Some(given::a_name()), Some(given::a_name()), Some(given::a_name()), ); assert_eq!(src.branch(), BranchName::new(branch)); } #[test] fn should_return_gitdir() { let gitdir = given::a_name(); let src = ServerRepoConfig::new( 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), git_dir::StoragePathType::External )) ); } } mod repo_config { use super::*; #[test] fn should_parse_toml() -> TestResult { 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}" "# ); let rc = RepoConfig::parse(toml.as_str())?; assert_eq!( rc, RepoConfig::new( RepoBranches::new(main, next, dev), RepoConfigSource::Repo // reading from repo is the default ) ); Ok(()) } #[test] fn should_return_branches() { 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 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); } } mod forge_config { use super::*; #[test] fn should_return_repos() { let forge_type = ForgeType::MockForge; 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_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::>(); assert_eq!( returned_repos, vec![ // alphabetical order by key (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 = 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); assert_eq!(fc.forge_type(), ForgeType::MockForge); } #[test] fn should_return_hostname() { let forge_type = ForgeType::MockForge; 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.clone(), user, token, repos); assert_eq!(fc.hostname(), Hostname::new(hostname)); } #[test] fn should_return_user() { let forge_type = ForgeType::MockForge; 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.clone(), token, repos); assert_eq!(fc.user(), User::new(user)); } #[test] fn should_return_token() { let forge_type = ForgeType::MockForge; 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.clone(), repos); assert_eq!(fc.token().expose_secret(), token.as_str()); } #[test] fn should_return_repo() { let forge_type = ForgeType::MockForge; 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_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_name.as_str()); assert_eq!(returned_repo, Some(&red),); } } mod forge_details { use super::*; use secrecy::ExposeSecret; #[test] fn should_return_forge_alias() { let forge_type = ForgeType::MockForge; 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_alias.clone(), forge_type, hostname, user, token); let result = forge_details.forge_alias(); assert_eq!(result, &forge_alias); } #[test] fn should_return_forge_type() { let forge_type = ForgeType::MockForge; 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(); assert_eq!(result, forge_type); } #[test] fn should_return_hostname() { let forge_type = ForgeType::MockForge; 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); let result = forge_details.hostname(); assert_eq!(result, &hostname); } #[test] fn should_return_user() { let forge_type = ForgeType::MockForge; 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); let result = forge_details.user(); assert_eq!(result, &user); } #[test] fn should_return_token() { let forge_type = ForgeType::MockForge; 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()); let result = forge_details.token(); assert_eq!(result.expose_secret(), token.expose_secret()); } #[test] fn with_hostname_should_return_new_instance() { let forge_type = ForgeType::MockForge; 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(other_hostname.clone()); assert_eq!(result.hostname(), &other_hostname); } #[test] fn should_convert_from_name_and_config() { let forge_type = ForgeType::MockForge; 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, hostname_value, user_value, token_value, BTreeMap::new(), ); let forge_details = ForgeDetails::from((&forge_alias, &forge_config)); assert_eq!(forge_details.forge_alias(), &forge_alias); assert_eq!(forge_details.hostname(), &hostname); assert_eq!(forge_details.user(), &user); assert_eq!(forge_details.token().expose_secret(), token.expose_secret()); } } mod forge_name { use super::*; use std::path::PathBuf; #[test] fn should_convert_to_pathbuf() { let name = given::a_name(); let forge_alias = ForgeAlias::new(name.clone()); let pathbuf: PathBuf = (&forge_alias).into(); assert_eq!(pathbuf, PathBuf::new().join(name)); } } mod forge_type { use super::*; #[test] fn should_display_as_string() { assert_eq!(ForgeType::MockForge.to_string(), "MockForge".to_string()); } } mod gitdir { use super::*; use std::path::PathBuf; #[test] fn should_return_pathbuf() { let pathbuf = PathBuf::default().join(given::a_name()); let gitdir = GitDir::new(pathbuf.clone(), git_dir::StoragePathType::Internal); let result = gitdir.to_path_buf(); assert_eq!(result, pathbuf); } #[test] fn should_display() { let pathbuf = PathBuf::default().join("foo"); let gitdir = GitDir::new(pathbuf, git_dir::StoragePathType::Internal); let result = gitdir.to_string(); assert_eq!(result, "foo"); } #[test] fn should_convert_to_pathbuf_from_ref() { let path = given::a_name(); let pathbuf = PathBuf::default().join(path); let gitdir: GitDir = GitDir::new(pathbuf.clone(), git_dir::StoragePathType::Internal); let result: PathBuf = (&gitdir).into(); assert_eq!(result, pathbuf); } } mod repo_branches { use super::*; #[test] fn should_return_main() { 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)); } #[test] fn should_return_next() { 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)); } #[test] fn should_return_dev() { 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)); } } mod server { use super::*; mod load { use super::*; #[test] fn load_should_parse_server_config() -> TestResult { let server_config = given::a_server_config(); let fs = kxio::fs::temp()?; let_assert!(Ok(_) = write_server_config(&server_config, &fs), "write"); let_assert!(Ok(config) = ServerConfig::load(&fs), "load"); assert_eq!(config, server_config, "compare"); 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| gitdir.to_path_buf()) .map(|pathbuf| pathbuf.display().to_string()) .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} [webhook] url = "{webhook_url}" [storage] path = {storage_path:?} [forge.{forge_alias}] forge_type = "{forge_type}" hostname = "{forge_hostname}" user = "{forge_user}" token = "{forge_token}" [forge.{forge_alias}.repos] {repos} "# ); println!("{file_contents}"); fs.file_write( &fs.base().join("git-next-server.toml"), file_contents.as_str(), )?; Ok(()) } } } mod registered_webhook { use super::*; #[test] fn should_return_id() { let id = given::a_webhook_id(); let auth = WebhookAuth::generate(); let rw = RegisteredWebhook::new(id.clone(), auth); assert_eq!(rw.id(), &id); } #[test] fn should_return_auth() { let id = given::a_webhook_id(); let auth = WebhookAuth::generate(); let rw = RegisteredWebhook::new(id, auth.clone()); assert_eq!(rw.auth(), &auth); } } mod webhook { use super::*; mod message { use super::*; #[test] fn should_return_forge_alias() { let forge_alias = given::a_forge_alias(); let message = ForgeNotification::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 repo_alias = given::a_repo_alias(); let message = ForgeNotification::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 body = given::a_webhook_message_body(); let message = ForgeNotification::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 key = given::a_name(); let value = given::a_name(); let mut headers: BTreeMap = Default::default(); headers.insert(key.clone(), value.clone()); let message = ForgeNotification::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 super::*; #[test] fn should_return_main_branch() { 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::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::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::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::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::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); } } mod given { use super::*; use rand::Rng as _; use std::{ collections::BTreeMap, path::{Path, PathBuf}, }; 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_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() -> BTreeMap { [(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::forge_notification::Body { crate::webhook::forge_notification::Body::new(a_name()) } pub fn some_repo_branches() -> RepoBranches { RepoBranches::new( a_name(), // main a_name(), // next a_name(), // dev ) } }