From db965ba23e5d6b2b44fe66f934269a32fe8e9d13 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 1 Jun 2024 14:49:28 +0100 Subject: [PATCH] WIP: test: add more tests to forge-forgejo crate --- crates/forge-forgejo/src/lib.rs | 3 + crates/forge-forgejo/src/tests.rs | 288 ++++++++++++++++++++++++++++++ crates/git/src/forge/commit.rs | 2 +- 3 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 crates/forge-forgejo/src/tests.rs diff --git a/crates/forge-forgejo/src/lib.rs b/crates/forge-forgejo/src/lib.rs index 8a25a6d..c59f57c 100644 --- a/crates/forge-forgejo/src/lib.rs +++ b/crates/forge-forgejo/src/lib.rs @@ -1,4 +1,7 @@ // +#[cfg(test)] +mod tests; + mod webhook; use git::forge::commit::Status; diff --git a/crates/forge-forgejo/src/tests.rs b/crates/forge-forgejo/src/tests.rs new file mode 100644 index 0000000..80199e7 --- /dev/null +++ b/crates/forge-forgejo/src/tests.rs @@ -0,0 +1,288 @@ +use git_next_config as config; +use git_next_git as git; + +mod forgejo { + use super::*; + + use assert2::let_assert; + use std::collections::BTreeMap; + + use crate::ForgeJo; + use config::{ + webhook::message::Body, ForgeAlias, ForgeConfig, ForgeType, GitDir, RepoAlias, + RepoBranches, ServerRepoConfig, WebhookAuth, WebhookMessage, + }; + use git::ForgeLike as _; + + #[test] + fn should_return_name() { + let forge = given_forgejo_forge(given_repo_details(), given_net()); + assert_eq!(forge.name(), "forgejo"); + } + + mod is_message_authorised { + use std::collections::HashMap; + + use super::*; + + #[test] + fn should_return_true_with_valid_header() { + let forge = given_forgejo_forge(given_repo_details(), given_net()); + let auth = given_auth(); + let message = given_message(Header::Valid(auth.clone())); + assert!(forge.is_message_authorised(&message, &auth)); + } + #[test] + fn should_return_false_with_missing_header() { + let forge = given_forgejo_forge(given_repo_details(), given_net()); + let auth = given_auth(); + let message = given_message(Header::Missing); + assert!(!forge.is_message_authorised(&message, &auth)); + } + #[test] + fn should_return_false_with_non_basic_prefix() { + let forge = given_forgejo_forge(given_repo_details(), given_net()); + let auth = given_auth(); + let message = given_message(Header::NonBasic); + assert!(!forge.is_message_authorised(&message, &auth)); + } + #[test] + fn should_return_false_with_non_ulid_value() { + let forge = given_forgejo_forge(given_repo_details(), given_net()); + let auth = given_auth(); + let message = given_message(Header::NonUlid); + assert!(!forge.is_message_authorised(&message, &auth)); + } + #[test] + fn should_return_false_with_wrong_ulid_value() { + let forge = given_forgejo_forge(given_repo_details(), given_net()); + let auth = given_auth(); + let message = given_message(Header::WrongUlid); + assert!(!forge.is_message_authorised(&message, &auth)); + } + fn given_auth() -> WebhookAuth { + WebhookAuth::generate() + } + enum Header { + Valid(WebhookAuth), + Missing, + NonBasic, + NonUlid, + WrongUlid, + } + fn given_message(header: Header) -> WebhookMessage { + WebhookMessage::new( + given_forge_alias(), + given_repo_alias(), + given_headers(header), + given_body(), + ) + } + fn given_headers(header: Header) -> HashMap { + let mut headers = HashMap::new(); + match header { + Header::Valid(auth) => { + headers.insert("authorization".to_string(), format!("Basic {auth}")); + } + Header::Missing => { /* don't add any header */ } + Header::NonBasic => { + headers.insert("authorization".to_string(), "Non-Basic".to_string()); + } + Header::NonUlid => { + headers.insert("authorization".to_string(), "Basic 123456".to_string()); + } + Header::WrongUlid => { + headers.insert( + "authorization".to_string(), + format!("Basic {}", WebhookAuth::generate()), + ); + } + } + headers + } + fn given_body() -> Body { + Body::new("body".to_string()) + } + } + + mod parse_webhook_body { + + use super::*; + + #[test] + fn should_parse_valid_body() { + let forge = given_forgejo_forge(given_repo_details(), given_net()); + let repo_branches = given_repo_branches(); + let body = Body::new( + r#"{"ref":"refs/heads/b-next","after":"a-sha","head_commit":{"message":"the-message"}}"# + .to_string(), + ); + let_assert!(Ok(push) = forge.parse_webhook_body(&body)); + assert_eq!(push.sha(), "a-sha"); + assert_eq!(push.message(), "the-message"); + assert_eq!( + push.branch(&repo_branches), + Some(config::webhook::push::Branch::Next) + ); + } + + #[test] + fn should_error_invalid_body() { + let forge = given_forgejo_forge(given_repo_details(), given_net()); + let body = Body::new(r#"{"type":"invalid"}"#.to_string()); + let_assert!(Err(_) = forge.parse_webhook_body(&body)); + } + } + + mod commit_status { + use git_next_git::{forge::commit::Status, RepoDetails}; + use kxio::network::{MockNetwork, StatusCode}; + use serde_json::json; + + use super::*; + + #[tokio::test] + async fn should_return_pass_for_success() { + let repo_details = given_repo_details(); + let commit = given_a_commit(); + let mut net = given_net(); + given_state("success", &mut net, &repo_details, &commit); + let forge = given_forgejo_forge(repo_details, net); + assert_eq!(forge.commit_status(&commit).await, Status::Pass); + } + #[tokio::test] + async fn should_return_pending_for_pending() { + let repo_details = given_repo_details(); + let commit = given_a_commit(); + let mut net = given_net(); + given_state("pending", &mut net, &repo_details, &commit); + let forge = given_forgejo_forge(given_repo_details(), net); + let commit = given_a_commit(); + assert_eq!(forge.commit_status(&commit).await, Status::Pending); + } + #[tokio::test] + async fn should_return_fail_for_failure() { + let repo_details = given_repo_details(); + let commit = given_a_commit(); + let mut net = given_net(); + given_state("failure", &mut net, &repo_details, &commit); + let forge = given_forgejo_forge(given_repo_details(), net); + let commit = given_a_commit(); + assert_eq!(forge.commit_status(&commit).await, Status::Fail); + } + #[tokio::test] + async fn should_return_fail_for_error() { + let repo_details = given_repo_details(); + let commit = given_a_commit(); + let mut net = given_net(); + given_state("error", &mut net, &repo_details, &commit); + let forge = given_forgejo_forge(given_repo_details(), net); + let commit = given_a_commit(); + assert_eq!(forge.commit_status(&commit).await, Status::Fail); + } + #[tokio::test] + async fn should_return_pending_for_blank() { + let repo_details = given_repo_details(); + let commit = given_a_commit(); + let mut net = given_net(); + given_state("", &mut net, &repo_details, &commit); + let forge = given_forgejo_forge(given_repo_details(), net); + let commit = given_a_commit(); + assert_eq!(forge.commit_status(&commit).await, Status::Pending); + } + #[tokio::test] + async fn should_return_pending_for_no_statuses() { + let repo_details = given_repo_details(); + let commit = given_a_commit(); + let mut net = given_net(); + net.add_get_response(&url(&repo_details, &commit), StatusCode::OK, ""); + let forge = given_forgejo_forge(given_repo_details(), net); + let commit = given_a_commit(); + assert_eq!(forge.commit_status(&commit).await, Status::Pending); + } + #[tokio::test] + async fn should_return_pending_for_network_error() { + let repo_details = given_repo_details(); + let commit = given_a_commit(); + let mut net = given_net(); + net.add_get_error(&url(&repo_details, &commit), "boom today"); + let forge = given_forgejo_forge(given_repo_details(), net); + let commit = given_a_commit(); + + assert_eq!(forge.commit_status(&commit).await, Status::Pending); + } + + fn url(repo_details: &RepoDetails, commit: &git::Commit) -> String { + let hostname = repo_details.forge.hostname(); + let repo_path = &repo_details.repo_path; + use secrecy::ExposeSecret; + let token = repo_details.forge.token().expose_secret(); + format!( + "https://{hostname}/api/v1/repos/{repo_path}/commits/{commit}/status?token={token}" + ) + } + + fn given_state( + state: impl AsRef, + net: &mut MockNetwork, + repo_details: &git::RepoDetails, + commit: &git::Commit, + ) { + let response = json!({"state":state.as_ref()}); + net.add_get_response( + url(repo_details, commit).as_str(), + StatusCode::OK, + response.to_string().as_str(), + ) + } + } + + fn given_a_commit() -> git::Commit { + git::Commit::new( + git::commit::Sha::new("a-sha".to_string()), + git::commit::Message::new("a-message".to_string()), + ) + } + + fn given_repo_branches() -> RepoBranches { + RepoBranches::new( + "a-main".to_string(), + "b-next".to_string(), + "c-dev".to_string(), + ) + } + + fn given_forgejo_forge( + repo_details: git::RepoDetails, + net: impl Into, + ) -> ForgeJo { + ForgeJo::new(repo_details, net.into()) + } + fn given_repo_details() -> git::RepoDetails { + git::RepoDetails::new( + git::Generation::new(), + &given_repo_alias(), + &ServerRepoConfig::new("b".to_string(), "c".to_string(), None, None, None, None), + &given_forge_alias(), + &ForgeConfig::new( + ForgeType::ForgeJo, + "git.forge.jo".to_string(), + "user".to_string(), + "token".to_string(), + BTreeMap::default(), + ), + GitDir::default(), + ) + } + + fn given_forge_alias() -> ForgeAlias { + ForgeAlias::new("d".to_string()) + } + + fn given_repo_alias() -> RepoAlias { + RepoAlias::new("a") + } + fn given_net() -> kxio::network::MockNetwork { + kxio::network::MockNetwork::new() + } +} diff --git a/crates/git/src/forge/commit.rs b/crates/git/src/forge/commit.rs index dafab24..ace9830 100644 --- a/crates/git/src/forge/commit.rs +++ b/crates/git/src/forge/commit.rs @@ -1,4 +1,4 @@ -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Status { Pass, Fail,