WIP: test: add more tests to forge-forgejo crate
This commit is contained in:
parent
98839c8a00
commit
8fec6c5a11
5 changed files with 500 additions and 1 deletions
|
@ -94,3 +94,4 @@ tokio = { version = "1.37" }
|
|||
# Testing
|
||||
assert2 = "0.3"
|
||||
pretty_assertions = "1.4"
|
||||
rand = "0.8"
|
||||
|
|
|
@ -51,6 +51,7 @@ tokio = { workspace = true }
|
|||
[dev-dependencies]
|
||||
# Testing
|
||||
assert2 = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
[lints.clippy]
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
//
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod webhook;
|
||||
|
||||
use git::forge::commit::Status;
|
||||
|
|
494
crates/forge-forgejo/src/tests.rs
Normal file
494
crates/forge-forgejo/src/tests.rs
Normal file
|
@ -0,0 +1,494 @@
|
|||
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::a_forgejo_forge(given::repo_details(), given::a_network());
|
||||
assert_eq!(forge.name(), "forgejo");
|
||||
}
|
||||
|
||||
mod is_message_authorised {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_return_true_with_valid_header() {
|
||||
let forge = given::a_forgejo_forge(given::repo_details(), given::a_network());
|
||||
let auth = given::a_webhook_auth();
|
||||
let message = given::a_webhook_message(given::Header::Valid(auth.clone()));
|
||||
assert!(forge.is_message_authorised(&message, &auth));
|
||||
}
|
||||
#[test]
|
||||
fn should_return_false_with_missing_header() {
|
||||
let forge = given::a_forgejo_forge(given::repo_details(), given::a_network());
|
||||
let auth = given::a_webhook_auth();
|
||||
let message = given::a_webhook_message(given::Header::Missing);
|
||||
assert!(!forge.is_message_authorised(&message, &auth));
|
||||
}
|
||||
#[test]
|
||||
fn should_return_false_with_non_basic_prefix() {
|
||||
let forge = given::a_forgejo_forge(given::repo_details(), given::a_network());
|
||||
let auth = given::a_webhook_auth();
|
||||
let message = given::a_webhook_message(given::Header::NonBasic);
|
||||
assert!(!forge.is_message_authorised(&message, &auth));
|
||||
}
|
||||
#[test]
|
||||
fn should_return_false_with_non_ulid_value() {
|
||||
let forge = given::a_forgejo_forge(given::repo_details(), given::a_network());
|
||||
let auth = given::a_webhook_auth();
|
||||
let message = given::a_webhook_message(given::Header::NonUlid);
|
||||
assert!(!forge.is_message_authorised(&message, &auth));
|
||||
}
|
||||
#[test]
|
||||
fn should_return_false_with_wrong_ulid_value() {
|
||||
let forge = given::a_forgejo_forge(given::repo_details(), given::a_network());
|
||||
let auth = given::a_webhook_auth();
|
||||
let message = given::a_webhook_message(given::Header::WrongUlid);
|
||||
assert!(!forge.is_message_authorised(&message, &auth));
|
||||
}
|
||||
}
|
||||
|
||||
mod parse_webhook_body {
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_parse_valid_body() {
|
||||
let forge = given::a_forgejo_forge(given::repo_details(), given::a_network());
|
||||
let repo_branches = given::repo_branches();
|
||||
let next = repo_branches.next();
|
||||
let sha = given::a_name();
|
||||
let message = given::a_name();
|
||||
let body = Body::new(
|
||||
json!({"ref":format!("refs/heads/{next}"),"after":sha,"head_commit":{"message":message}})
|
||||
.to_string(),
|
||||
);
|
||||
let_assert!(Ok(push) = forge.parse_webhook_body(&body));
|
||||
assert_eq!(push.sha(), sha);
|
||||
assert_eq!(push.message(), message);
|
||||
assert_eq!(
|
||||
push.branch(&repo_branches),
|
||||
Some(config::webhook::push::Branch::Next)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_error_invalid_body() {
|
||||
let forge = given::a_forgejo_forge(given::repo_details(), given::a_network());
|
||||
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;
|
||||
use kxio::network::StatusCode;
|
||||
|
||||
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::a_network();
|
||||
given::a_commit_state("success", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_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::a_network();
|
||||
given::a_commit_state("pending", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
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::a_network();
|
||||
given::a_commit_state("failure", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
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::a_network();
|
||||
given::a_commit_state("error", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
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::a_network();
|
||||
given::a_commit_state("", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
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::a_network();
|
||||
net.add_get_response(
|
||||
&given::a_commit_status_url(&repo_details, &commit),
|
||||
StatusCode::OK,
|
||||
"",
|
||||
);
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
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::a_network();
|
||||
net.add_get_error(
|
||||
&given::a_commit_status_url(&repo_details, &commit),
|
||||
"boom today",
|
||||
);
|
||||
let forge = given::a_forgejo_forge(given::repo_details(), net);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Pending);
|
||||
}
|
||||
}
|
||||
|
||||
mod list_webhooks {
|
||||
use git_next_config::WebhookId;
|
||||
use git_next_git::ForgeLike as _;
|
||||
use kxio::network::StatusCode;
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_return_a_list_of_matching_webhooks() {
|
||||
let repo_details = given::repo_details();
|
||||
let webhook_url = given::a_webhook_url(&given::a_forge_alias(), &given::a_repo_alias());
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
use secrecy::ExposeSecret;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
let hook_id_1 = given::a_forgejo_webhook_id();
|
||||
let hook_id_2 = given::a_forgejo_webhook_id();
|
||||
let hook_id_3 = given::a_forgejo_webhook_id();
|
||||
let mut net = given::a_network();
|
||||
|
||||
// page 1 with three items
|
||||
net.add_get_response(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?page=1&token={token}")
|
||||
.as_str(),
|
||||
StatusCode::OK,
|
||||
json!([
|
||||
{"id":hook_id_1,"config":{"url":webhook_url.as_ref()}},
|
||||
{"id":hook_id_2,"config":{"url":webhook_url.as_ref()}},
|
||||
{"id":hook_id_3,"config":{"url":"other_url"}}
|
||||
])
|
||||
.to_string()
|
||||
.as_str(),
|
||||
);
|
||||
// page 2 with no items - stops pagination
|
||||
net.add_get_response(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?page=2&token={token}")
|
||||
.as_str(),
|
||||
StatusCode::OK,
|
||||
json!([]).to_string().as_str(),
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
|
||||
let_assert!(Ok(result) = forge.list_webhooks(&webhook_url).await);
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![
|
||||
WebhookId::new(format!("{hook_id_1}")),
|
||||
WebhookId::new(format!("{hook_id_2}"))
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_return_any_network_error() {
|
||||
let repo_details = given::repo_details();
|
||||
let webhook_url = given::a_webhook_url(&given::a_forge_alias(), &given::a_repo_alias());
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
use secrecy::ExposeSecret;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
let mut net = given::a_network();
|
||||
|
||||
net.add_get_error(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?page=1&token={token}")
|
||||
.as_str(),
|
||||
"error_message",
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
|
||||
let_assert!(Err(_) = forge.list_webhooks(&webhook_url).await);
|
||||
}
|
||||
}
|
||||
|
||||
mod unregister_webhook {
|
||||
use super::*;
|
||||
use git_next_git::ForgeLike;
|
||||
use kxio::network::StatusCode;
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_delete_webhook() {
|
||||
let repo_details = given::repo_details();
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
use secrecy::ExposeSecret;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
let webhook_id = given::a_webhook_id();
|
||||
let mut net = given::a_network();
|
||||
|
||||
net.add_delete_response(
|
||||
format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
|
||||
)
|
||||
.as_str(),
|
||||
StatusCode::OK,
|
||||
"",
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
|
||||
let_assert!(Ok(_) = forge.unregister_webhook(&webhook_id).await);
|
||||
}
|
||||
}
|
||||
|
||||
mod register_webhook {
|
||||
use git_next_config::WebhookId;
|
||||
use kxio::network::StatusCode;
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_register_a_new_webhook() {
|
||||
let repo_details = given::repo_details();
|
||||
assert!(
|
||||
repo_details.repo_config.is_some(),
|
||||
"repo config not present"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
use secrecy::ExposeSecret;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
let webhook_id = given::a_forgejo_webhook_id();
|
||||
let mut net = given::a_network();
|
||||
|
||||
// there are no existing matching webhooks
|
||||
net.add_get_response(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?page=1&token={token}")
|
||||
.as_str(),
|
||||
StatusCode::OK,
|
||||
json!([]).to_string().as_str(),
|
||||
);
|
||||
// register the webhook
|
||||
net.add_post_response(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({"id": webhook_id, "config":{}}).to_string().as_str(),
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(repo_details, net);
|
||||
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await);
|
||||
assert_eq!(
|
||||
registered_webhook.id(),
|
||||
&WebhookId::new(format!("{webhook_id}"))
|
||||
);
|
||||
}
|
||||
|
||||
// should abort if there is no repo config
|
||||
// should unregister existing webhooks before registering
|
||||
// should return error if empty network response
|
||||
// should return error on network error
|
||||
}
|
||||
mod given {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use git_next_config::{server::Webhook, WebhookId};
|
||||
use kxio::network::{MockNetwork, StatusCode};
|
||||
use rand::RngCore;
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn a_commit_state(
|
||||
state: impl AsRef<str>,
|
||||
net: &mut MockNetwork,
|
||||
repo_details: &git::RepoDetails,
|
||||
commit: &git::Commit,
|
||||
) {
|
||||
let response = json!({"state":state.as_ref()});
|
||||
net.add_get_response(
|
||||
a_commit_status_url(repo_details, commit).as_str(),
|
||||
StatusCode::OK,
|
||||
response.to_string().as_str(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn a_commit_status_url(
|
||||
repo_details: &git::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}"
|
||||
)
|
||||
}
|
||||
|
||||
pub fn a_webhook_auth() -> WebhookAuth {
|
||||
WebhookAuth::generate()
|
||||
}
|
||||
|
||||
pub enum Header {
|
||||
Valid(WebhookAuth),
|
||||
Missing,
|
||||
NonBasic,
|
||||
NonUlid,
|
||||
WrongUlid,
|
||||
}
|
||||
pub fn a_webhook_message(header: Header) -> WebhookMessage {
|
||||
WebhookMessage::new(
|
||||
given::a_forge_alias(),
|
||||
given::a_repo_alias(),
|
||||
given::webhook_headers(header),
|
||||
given::a_webhook_message_body(),
|
||||
)
|
||||
}
|
||||
pub fn webhook_headers(header: Header) -> HashMap<String, String> {
|
||||
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
|
||||
}
|
||||
pub fn a_webhook_message_body() -> Body {
|
||||
Body::new(a_name())
|
||||
}
|
||||
pub fn a_commit() -> git::Commit {
|
||||
git::Commit::new(
|
||||
git::commit::Sha::new(a_name()),
|
||||
git::commit::Message::new(a_name()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn repo_branches() -> RepoBranches {
|
||||
RepoBranches::new(a_name(), a_name(), a_name())
|
||||
}
|
||||
|
||||
pub fn a_forgejo_forge(
|
||||
repo_details: git::RepoDetails,
|
||||
net: impl Into<kxio::network::Network>,
|
||||
) -> ForgeJo {
|
||||
ForgeJo::new(repo_details, net.into())
|
||||
}
|
||||
pub fn repo_details() -> git::RepoDetails {
|
||||
git::RepoDetails::new(
|
||||
git::Generation::new(),
|
||||
&a_repo_alias(),
|
||||
&ServerRepoConfig::new(
|
||||
format!("{}/{}", a_name(), a_name()), // repo path: owner/repo
|
||||
a_name(),
|
||||
None,
|
||||
Some(a_name()),
|
||||
Some(a_name()),
|
||||
Some(a_name()),
|
||||
),
|
||||
&a_forge_alias(),
|
||||
&ForgeConfig::new(
|
||||
ForgeType::ForgeJo,
|
||||
a_name(),
|
||||
a_name(),
|
||||
a_name(),
|
||||
BTreeMap::default(),
|
||||
),
|
||||
GitDir::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn a_forge_alias() -> ForgeAlias {
|
||||
ForgeAlias::new(a_name())
|
||||
}
|
||||
|
||||
pub fn a_repo_alias() -> RepoAlias {
|
||||
RepoAlias::new(a_name())
|
||||
}
|
||||
pub fn a_network() -> kxio::network::MockNetwork {
|
||||
kxio::network::MockNetwork::new()
|
||||
}
|
||||
|
||||
pub fn a_webhook_url(
|
||||
forge_alias: &ForgeAlias,
|
||||
repo_alias: &RepoAlias,
|
||||
) -> git_next_config::server::WebhookUrl {
|
||||
Webhook::new(a_name()).url(forge_alias, repo_alias)
|
||||
}
|
||||
|
||||
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_webhook_id() -> WebhookId {
|
||||
WebhookId::new(given::a_name())
|
||||
}
|
||||
|
||||
pub fn a_forgejo_webhook_id() -> i64 {
|
||||
rand::thread_rng().next_u32().into()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
Pass,
|
||||
Fail,
|
||||
|
|
Loading…
Reference in a new issue