From e585b07f6b987294a85107aa268b9083fa1495cc Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 27 Jun 2024 21:10:41 +0100 Subject: [PATCH] tests: repo-actor: add more tests --- crates/config/src/webhook/push.rs | 3 +- crates/repo-actor/Cargo.toml | 1 + .../src/{handlers.rs => handlers/mod.rs} | 0 .../src/handlers/webhook_notification.rs | 18 +- crates/repo-actor/src/lib.rs | 34 +- crates/repo-actor/src/tests.rs | 39 -- .../src/tests/branch/advance_main.rs | 2 - .../src/tests/branch/advance_next.rs | 13 +- .../src/tests/{branch.rs => branch/mod.rs} | 0 crates/repo-actor/src/tests/given.rs | 27 +- .../src/tests/handlers/advance_main.rs | 14 +- .../src/tests/handlers/advance_next.rs | 7 +- .../src/tests/handlers/check_ci_status.rs | 7 +- .../tests/handlers/load_config_from_repo.rs | 14 +- .../src/tests/handlers/loaded_config.rs | 22 +- .../tests/{handlers.rs => handlers/mod.rs} | 3 +- .../src/tests/handlers/receive_ci_status.rs | 21 +- .../src/tests/handlers/register_webhook.rs | 14 +- .../src/tests/handlers/validate_repo.rs | 44 +- .../tests/handlers/webhook_notification.rs | 478 ++++++++++++++++++ .../src/tests/handlers/webhook_registered.rs | 30 ++ crates/repo-actor/src/tests/load.rs | 4 - crates/repo-actor/src/tests/mod.rs | 109 ++++ crates/repo-actor/src/tests/when.rs | 4 +- 24 files changed, 767 insertions(+), 141 deletions(-) rename crates/repo-actor/src/{handlers.rs => handlers/mod.rs} (100%) delete mode 100644 crates/repo-actor/src/tests.rs rename crates/repo-actor/src/tests/{branch.rs => branch/mod.rs} (100%) rename crates/repo-actor/src/tests/{handlers.rs => handlers/mod.rs} (80%) create mode 100644 crates/repo-actor/src/tests/handlers/webhook_notification.rs create mode 100644 crates/repo-actor/src/tests/handlers/webhook_registered.rs create mode 100644 crates/repo-actor/src/tests/mod.rs diff --git a/crates/config/src/webhook/push.rs b/crates/config/src/webhook/push.rs index 941f922..a1eaeb2 100644 --- a/crates/config/src/webhook/push.rs +++ b/crates/config/src/webhook/push.rs @@ -3,7 +3,7 @@ use crate as config; use derive_more::Constructor; -#[derive(Debug, Constructor)] +#[derive(Debug, Constructor, derive_with::With)] pub struct Push { branch: config::BranchName, sha: String, @@ -30,7 +30,6 @@ impl Push { &self.message } } - #[derive(Debug, PartialEq, Eq)] pub enum Branch { Main, diff --git a/crates/repo-actor/Cargo.toml b/crates/repo-actor/Cargo.toml index 408d52f..8473cb4 100644 --- a/crates/repo-actor/Cargo.toml +++ b/crates/repo-actor/Cargo.toml @@ -42,6 +42,7 @@ ulid = { workspace = true } # boilerplate derive_more = { workspace = true } +derive-with = { workspace = true } thiserror = { workspace = true } # Actors diff --git a/crates/repo-actor/src/handlers.rs b/crates/repo-actor/src/handlers/mod.rs similarity index 100% rename from crates/repo-actor/src/handlers.rs rename to crates/repo-actor/src/handlers/mod.rs diff --git a/crates/repo-actor/src/handlers/webhook_notification.rs b/crates/repo-actor/src/handlers/webhook_notification.rs index 56b0ed1..ac54c82 100644 --- a/crates/repo-actor/src/handlers/webhook_notification.rs +++ b/crates/repo-actor/src/handlers/webhook_notification.rs @@ -14,10 +14,12 @@ impl Handler for actor::RepoActor { #[tracing::instrument(name = "RepoActor::WebhookMessage", skip_all, fields(token = %self.message_token, repo = %self.repo_details))] fn handle(&mut self, msg: WebhookNotification, ctx: &mut Self::Context) -> Self::Result { let Some(expected_authorization) = &self.webhook_auth else { + actor::logger(&self.log, "server has no auth token"); warn!("Don't know what authorization to expect"); return; }; let Some(config) = &self.repo_details.repo_config else { + actor::logger(&self.log, "server has no repo config"); warn!("No repo config"); return; }; @@ -25,6 +27,7 @@ impl Handler for actor::RepoActor { .forge .is_message_authorised(&msg, expected_authorization) { + actor::logger(&self.log, "message authorisation is invalid"); warn!( "Invalid authorization - expected {}", expected_authorization @@ -34,11 +37,13 @@ impl Handler for actor::RepoActor { let body = msg.body(); match self.forge.parse_webhook_body(body) { Err(err) => { + actor::logger(&self.log, "message parse error - not a push"); warn!(?err, "Not a 'push'"); return; } Ok(push) => match push.branch(config.branches()) { None => { + actor::logger(&self.log, "unknown branch"); warn!( ?push, "Unrecognised branch, we should be filtering to only the ones we want" @@ -46,8 +51,10 @@ impl Handler for actor::RepoActor { return; } Some(config::webhook::push::Branch::Main) => { + actor::logger(&self.log, "message is for main branch"); let commit = git::Commit::from(push); if self.last_main_commit.as_ref() == Some(&commit) { + actor::logger(&self.log, "not a new commit on main"); info!( branch = %config.branches().main(), %commit, @@ -58,8 +65,10 @@ impl Handler for actor::RepoActor { self.last_main_commit.replace(commit); } Some(config::webhook::push::Branch::Next) => { + actor::logger(&self.log, "message is for next branch"); let commit = git::Commit::from(push); if self.last_next_commit.as_ref() == Some(&commit) { + actor::logger(&self.log, "not a new commit on next"); info!( branch = %config.branches().next(), %commit, @@ -70,8 +79,10 @@ impl Handler for actor::RepoActor { self.last_next_commit.replace(commit); } Some(config::webhook::push::Branch::Dev) => { + actor::logger(&self.log, "message is for dev branch"); let commit = git::Commit::from(push); if self.last_dev_commit.as_ref() == Some(&commit) { + actor::logger(&self.log, "not a new commit on dev"); info!( branch = %config.branches().dev(), %commit, @@ -88,7 +99,10 @@ impl Handler for actor::RepoActor { token = %message_token, "New commit" ); - ctx.address() - .do_send(actor::messages::ValidateRepo::new(message_token)); + actor::do_send( + ctx.address(), + actor::messages::ValidateRepo::new(message_token), + &self.log, + ); } } diff --git a/crates/repo-actor/src/lib.rs b/crates/repo-actor/src/lib.rs index 4e122a1..bfc3452 100644 --- a/crates/repo-actor/src/lib.rs +++ b/crates/repo-actor/src/lib.rs @@ -8,13 +8,22 @@ mod tests; use actix::prelude::*; +use derive_more::Deref; use git_next_config as config; use git_next_git as git; use kxio::network::Network; use tracing::{info, warn, Instrument}; -pub type RepoActorLog = std::sync::Arc>>; +#[derive(Clone, Debug, Default)] +pub struct RepoActorLog(std::sync::Arc>>); +impl Deref for RepoActorLog { + type Target = std::sync::Arc>>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} /// An actor that represents a Git Repository. /// @@ -48,7 +57,7 @@ pub type RepoActorLog = std::sync::Arc>>; /// WebhookMessage --> ValidateRepo /// ``` /// -#[derive(Debug, derive_more::Display)] +#[derive(Debug, derive_more::Display, derive_with::With)] #[display("{}:{}:{}", generation, repo_details.forge.forge_alias(), repo_details.repo_alias)] pub struct RepoActor { sleep_duration: std::time::Duration, @@ -97,27 +106,6 @@ impl RepoActor { } } } - -#[cfg(test)] -impl RepoActor { - pub fn with_log(mut self, log: RepoActorLog) -> Self { - self.log = Some(log); - self - } - - pub fn with_open_repository( - mut self, - open_repository: Box, - ) -> Self { - self.open_repository.replace(open_repository); - self - } - - pub const fn with_message_token(mut self, message_token: messages::MessageToken) -> Self { - self.message_token = message_token; - self - } -} impl Actor for RepoActor { type Context = Context; #[tracing::instrument(name = "RepoActor::stopping", skip_all, fields(repo = %self.repo_details))] diff --git a/crates/repo-actor/src/tests.rs b/crates/repo-actor/src/tests.rs deleted file mode 100644 index f084d9b..0000000 --- a/crates/repo-actor/src/tests.rs +++ /dev/null @@ -1,39 +0,0 @@ -// -#![allow(unused_imports)] // TODO remove this - -use crate as actor; -use actix::prelude::*; -use actor::{ - messages::{CloneRepo, MessageToken, WebhookRegistered}, - RepoActor, RepoActorLog, -}; -use assert2::let_assert; -use config::{BranchName, RegisteredWebhook, RepoBranches, RepoConfig}; -use git::{ - repository::{ - open::MockOpenRepositoryLike, Direction, MockRepositoryFactory, RepositoryFactory, - }, - validation::remotes, - GitRemote, RepoDetails, -}; -use git_next_config as config; -use git_next_forge as forge; -use git_next_git as git; -use mockall::predicate::eq; -use std::{ - borrow::{Borrow, BorrowMut}, - cell::{Cell, RefCell}, - collections::HashMap, - path::PathBuf, - rc::Rc, - sync::{atomic::AtomicBool, Arc, Mutex}, -}; - -type TestResult = Result<(), Box>; - -mod branch; -mod expect; -pub mod given; -mod handlers; -mod load; -mod when; diff --git a/crates/repo-actor/src/tests/branch/advance_main.rs b/crates/repo-actor/src/tests/branch/advance_main.rs index fcdf302..26099b6 100644 --- a/crates/repo-actor/src/tests/branch/advance_main.rs +++ b/crates/repo-actor/src/tests/branch/advance_main.rs @@ -1,6 +1,4 @@ // -use git::repository::{OnFetch, OnPush, OpenRepositoryLike}; - use super::*; #[test] diff --git a/crates/repo-actor/src/tests/branch/advance_next.rs b/crates/repo-actor/src/tests/branch/advance_next.rs index f585aa7..02d7736 100644 --- a/crates/repo-actor/src/tests/branch/advance_next.rs +++ b/crates/repo-actor/src/tests/branch/advance_next.rs @@ -1,5 +1,6 @@ // use super::*; + mod when_at_dev { // next and dev branches are the same use super::*; @@ -27,12 +28,14 @@ mod when_at_dev { Ok(()) } } + mod can_advance { // dev has at least one commit ahead of next use super::*; mod to_wip_commit { // commit on dev is either invalid message or a WIP use super::*; + #[test] fn should_not_push() -> TestResult { let next = given::a_commit(); @@ -58,9 +61,11 @@ mod can_advance { Ok(()) } } + mod to_invalid_commit { // commit on dev is either invalid message or a WIP use super::*; + #[test] fn should_not_push_and_error() -> TestResult { let next = given::a_commit(); @@ -90,13 +95,12 @@ mod can_advance { Ok(()) } } + mod to_valid_commit { // commit on dev is valid conventional commit message use super::*; mod push_is_err { - use git::repository::{OnFetch, OnPush}; - // the git push command fails use super::*; @@ -129,12 +133,11 @@ mod can_advance { Ok(()) } } + mod push_is_ok { // the git push command succeeds - use git_next_git::repository::{OnFetch, OnPush}; - - // the git push command fails use super::*; + #[test] fn should_ok() -> TestResult { let next = given::a_commit(); diff --git a/crates/repo-actor/src/tests/branch.rs b/crates/repo-actor/src/tests/branch/mod.rs similarity index 100% rename from crates/repo-actor/src/tests/branch.rs rename to crates/repo-actor/src/tests/branch/mod.rs diff --git a/crates/repo-actor/src/tests/given.rs b/crates/repo-actor/src/tests/given.rs index f6aacab..96edd9a 100644 --- a/crates/repo-actor/src/tests/given.rs +++ b/crates/repo-actor/src/tests/given.rs @@ -1,22 +1,5 @@ // use super::*; -use config::{ - server::{Webhook, WebhookUrl}, - BranchName, ForgeAlias, ForgeConfig, ForgeType, GitDir, RepoAlias, RepoBranches, RepoConfig, - ServerRepoConfig, WebhookAuth, WebhookId, -}; -use git::{ - repository::{open::MockOpenRepositoryLike, Direction}, - Generation, GitRemote, MockForgeLike, RepoDetails, -}; -use git_next_git::repository::RepositoryLike as _; -use mockall::predicate::eq; -use rand::RngCore; -use std::{ - collections::HashMap, - path::PathBuf, - sync::{Arc, Mutex}, -}; pub fn has_all_valid_remote_defaults( open_repository: &mut MockOpenRepositoryLike, @@ -252,7 +235,7 @@ pub fn a_repo_actor( let webhook_url = given::a_webhook_url(forge_alias, repo_alias); let webhook = given::a_webhook(&webhook_url); let generation = Generation::default(); - let log = Arc::new(Mutex::new(vec![])); + let log = RepoActorLog::default(); let actors_log = log.clone(); ( actor::RepoActor::new( @@ -276,3 +259,11 @@ pub fn a_hostname() -> config::Hostname { pub fn a_registered_webhook() -> RegisteredWebhook { RegisteredWebhook::new(given::a_webhook_id(), given::a_webhook_auth()) } + +pub fn a_push() -> config::webhook::Push { + config::webhook::Push::new( + given::a_branch_name("push"), + given::a_name(), + given::a_name(), + ) +} diff --git a/crates/repo-actor/src/tests/handlers/advance_main.rs b/crates/repo-actor/src/tests/handlers/advance_main.rs index ee8a403..bc75af8 100644 --- a/crates/repo-actor/src/tests/handlers/advance_main.rs +++ b/crates/repo-actor/src/tests/handlers/advance_main.rs @@ -27,8 +27,11 @@ async fn when_repo_config_should_fetch_then_push_then_revalidate() -> TestResult .return_once(|_, _, _, _| Ok(())); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::AdvanceMain::new(next_commit.clone())) .await?; System::current().stop(); @@ -69,8 +72,11 @@ async fn when_server_config_should_fetch_then_push_then_revalidate() -> TestResu .return_once(|_, _, _, _| Ok(())); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::AdvanceMain::new(next_commit.clone())) .await?; System::current().stop(); diff --git a/crates/repo-actor/src/tests/handlers/advance_next.rs b/crates/repo-actor/src/tests/handlers/advance_next.rs index 6d3405d..db8d6c1 100644 --- a/crates/repo-actor/src/tests/handlers/advance_next.rs +++ b/crates/repo-actor/src/tests/handlers/advance_next.rs @@ -26,8 +26,11 @@ async fn should_fetch_then_push_then_revalidate() -> TestResult { .return_once(|_, _, _, _| Ok(())); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::AdvanceNext::new(( next_commit.clone(), dev_commit_log, diff --git a/crates/repo-actor/src/tests/handlers/check_ci_status.rs b/crates/repo-actor/src/tests/handlers/check_ci_status.rs index b3180b0..3615745 100644 --- a/crates/repo-actor/src/tests/handlers/check_ci_status.rs +++ b/crates/repo-actor/src/tests/handlers/check_ci_status.rs @@ -15,8 +15,11 @@ async fn should_passthrough_to_receive_ci_status() -> TestResult { ); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, Box::new(forge)); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + Box::new(forge), + ); addr.send(actor::messages::CheckCIStatus::new(next_commit.clone())) .await?; System::current().stop(); diff --git a/crates/repo-actor/src/tests/handlers/load_config_from_repo.rs b/crates/repo-actor/src/tests/handlers/load_config_from_repo.rs index 174e552..53def64 100644 --- a/crates/repo-actor/src/tests/handlers/load_config_from_repo.rs +++ b/crates/repo-actor/src/tests/handlers/load_config_from_repo.rs @@ -34,8 +34,11 @@ async fn when_read_file_ok_should_send_config_loaded() -> TestResult { .return_once(|| Box::new(load_config_open_repo)); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::LoadConfigFromRepo::new()) .await?; System::current().stop(); @@ -66,8 +69,11 @@ async fn when_read_file_err_should_notify_user() -> TestResult { .return_once(|| Box::new(load_config_open_repo)); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::LoadConfigFromRepo::new()) .await?; System::current().stop(); diff --git a/crates/repo-actor/src/tests/handlers/loaded_config.rs b/crates/repo-actor/src/tests/handlers/loaded_config.rs index cb0b6fb..3f1f8bc 100644 --- a/crates/repo-actor/src/tests/handlers/loaded_config.rs +++ b/crates/repo-actor/src/tests/handlers/loaded_config.rs @@ -1,6 +1,5 @@ // use super::*; -use actix::prelude::*; #[actix::test] async fn should_store_repo_config_in_actor() -> TestResult { @@ -11,8 +10,11 @@ async fn should_store_repo_config_in_actor() -> TestResult { let new_repo_config = given::a_repo_config(); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::ReceiveRepoConfig::new( new_repo_config.clone(), )) @@ -36,8 +38,11 @@ async fn should_validate_repo() -> TestResult { let new_repo_config = given::a_repo_config(); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::ReceiveRepoConfig::new( new_repo_config.clone(), )) @@ -63,8 +68,11 @@ async fn should_register_webhook() -> TestResult { let new_repo_config = given::a_repo_config(); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::ReceiveRepoConfig::new( new_repo_config.clone(), )) diff --git a/crates/repo-actor/src/tests/handlers.rs b/crates/repo-actor/src/tests/handlers/mod.rs similarity index 80% rename from crates/repo-actor/src/tests/handlers.rs rename to crates/repo-actor/src/tests/handlers/mod.rs index 6796547..e87aab3 100644 --- a/crates/repo-actor/src/tests/handlers.rs +++ b/crates/repo-actor/src/tests/handlers/mod.rs @@ -1,6 +1,5 @@ // use super::*; -use actix::prelude::*; mod advance_main; mod advance_next; @@ -11,3 +10,5 @@ mod loaded_config; mod receive_ci_status; mod register_webhook; mod validate_repo; +mod webhook_notification; +mod webhook_registered; diff --git a/crates/repo-actor/src/tests/handlers/receive_ci_status.rs b/crates/repo-actor/src/tests/handlers/receive_ci_status.rs index 8aa096a..03864cd 100644 --- a/crates/repo-actor/src/tests/handlers/receive_ci_status.rs +++ b/crates/repo-actor/src/tests/handlers/receive_ci_status.rs @@ -9,8 +9,11 @@ async fn when_pass_should_advance_main_to_next() -> TestResult { let next_commit = given::a_named_commit("next"); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::ReceiveCIStatus::new(( next_commit.clone(), git::forge::commit::Status::Pass, @@ -36,8 +39,11 @@ async fn when_pending_should_recheck_ci_status() -> TestResult { let next_commit = given::a_named_commit("next"); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::ReceiveCIStatus::new(( next_commit.clone(), git::forge::commit::Status::Pending, @@ -64,8 +70,11 @@ async fn when_fail_should_notify_user() -> TestResult { let next_commit = given::a_named_commit("next"); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(actor::messages::ReceiveCIStatus::new(( next_commit.clone(), git::forge::commit::Status::Fail, diff --git a/crates/repo-actor/src/tests/handlers/register_webhook.rs b/crates/repo-actor/src/tests/handlers/register_webhook.rs index a9eddcb..5f9a575 100644 --- a/crates/repo-actor/src/tests/handlers/register_webhook.rs +++ b/crates/repo-actor/src/tests/handlers/register_webhook.rs @@ -17,8 +17,11 @@ async fn when_registered_ok_should_send_webhook_registered() -> TestResult { forge.expect_duplicate().return_once(|| Box::new(my_forge)); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, Box::new(forge)); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + Box::new(forge), + ); addr.send(actor::messages::RegisterWebhook::new()).await?; System::current().stop(); @@ -50,8 +53,11 @@ async fn when_registered_error_should_send_notify_user() -> TestResult { forge.expect_duplicate().return_once(|| Box::new(my_forge)); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, Box::new(forge)); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + Box::new(forge), + ); addr.send(actor::messages::RegisterWebhook::new()).await?; System::current().stop(); diff --git a/crates/repo-actor/src/tests/handlers/validate_repo.rs b/crates/repo-actor/src/tests/handlers/validate_repo.rs index 6860fa4..d760b62 100644 --- a/crates/repo-actor/src/tests/handlers/validate_repo.rs +++ b/crates/repo-actor/src/tests/handlers/validate_repo.rs @@ -1,6 +1,4 @@ // -use crate::messages::{MessageToken, ValidateRepo}; - use super::*; #[test_log::test(actix::test)] @@ -35,8 +33,11 @@ async fn repo_with_next_not_an_ancestor_of_dev_should_be_reset() -> TestResult { expect::push_ok(&mut open_repository); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) .await?; System::current().stop(); @@ -83,8 +84,11 @@ async fn repo_with_next_not_on_or_near_main_should_be_reset() -> TestResult { expect::push_ok(&mut open_repository); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) .await?; System::current().stop(); @@ -131,8 +135,11 @@ async fn repo_with_next_not_based_on_main_should_be_reset() -> TestResult { expect::push_ok(&mut open_repository); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) .await?; System::current().stop(); @@ -178,8 +185,11 @@ async fn repo_with_next_ahead_of_main_should_check_ci_status() -> TestResult { .return_once(|_, _| Ok(dev_branch_log)); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) .await?; System::current().stop(); @@ -225,8 +235,11 @@ async fn repo_with_dev_and_next_on_main_should_do_nothing() -> TestResult { .return_once(move |_, _| Ok(dev_commit_log)); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) .await?; System::current().stop(); @@ -272,8 +285,11 @@ async fn repo_with_dev_ahead_of_next_should_advance_next() -> TestResult { .return_once(move |_, _| Ok(dev_commit_log)); //when - let (addr, log) = - when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); + let (addr, log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) .await?; System::current().stop(); diff --git a/crates/repo-actor/src/tests/handlers/webhook_notification.rs b/crates/repo-actor/src/tests/handlers/webhook_notification.rs new file mode 100644 index 0000000..f2ff7ee --- /dev/null +++ b/crates/repo-actor/src/tests/handlers/webhook_notification.rs @@ -0,0 +1,478 @@ +// +use super::*; + +#[actix::test] +async fn when_no_expected_auth_token_drop_notification() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + given::a_forge(), + given::a_network().into(), + ); + let actor = actor.with_webhook_auth(None); + + //when + actor + .start() + .send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + log.no_message_contains("send")?; + log.require_message_containing("server has no auth token")?; + Ok(()) +} + +#[actix::test] +async fn when_no_repo_config_drop_notification() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs).with_repo_config(None); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + given::a_forge(), + given::a_network().into(), + ); + let actor = actor.with_webhook_auth(Some(given::a_webhook_auth())); + + //when + actor + .start() + .send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + log.no_message_contains("send")?; + log.require_message_containing("server has no repo config")?; + Ok(()) +} + +#[actix::test] +async fn when_message_auth_is_invalid_drop_notification() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| false); // is not valid + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor.with_webhook_auth(Some(given::a_webhook_auth())); + + //when + actor + .start() + .send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + log.no_message_contains("send")?; + log.require_message_containing("message authorisation is invalid")?; + Ok(()) +} + +#[actix::test] +async fn when_message_is_not_a_push_drop_notification() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| true); // is valid + forge + .expect_parse_webhook_body() + .return_once(|_| Err(git::forge::webhook::Error::NetworkResponseEmpty)); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor.with_webhook_auth(Some(given::a_webhook_auth())); + + //when + actor + .start() + .send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + log.no_message_contains("send")?; + log.require_message_containing("message parse error - not a push")?; + Ok(()) +} + +#[actix::test] +async fn when_message_is_push_on_unknown_branch_drop_notification() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_config = given::a_repo_config(); + let repo_details = given::repo_details(&fs).with_repo_config(Some(repo_config.clone())); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let commit = given::a_commit(); + let push = given::a_push() + .with_branch(given::a_branch_name("unknown")) + .with_sha(commit.sha().to_string()) + .with_message(commit.message().to_string()); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| true); // is valid + forge.expect_parse_webhook_body().return_once(|_| Ok(push)); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor + .with_webhook_auth(Some(given::a_webhook_auth())) + .with_last_main_commit(commit); + + //when + actor + .start() + .send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + log.no_message_contains("send")?; + log.require_message_containing("unknown branch")?; + Ok(()) +} + +#[actix::test] +async fn when_message_is_push_already_seen_commit_to_main() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_config = given::a_repo_config(); + let repo_details = given::repo_details(&fs).with_repo_config(Some(repo_config.clone())); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let commit = given::a_commit(); + let push = given::a_push() + .with_branch(repo_config.branches().main()) + .with_sha(commit.sha().to_string()) + .with_message(commit.message().to_string()); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| true); // is valid + forge.expect_parse_webhook_body().return_once(|_| Ok(push)); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor + .with_webhook_auth(Some(given::a_webhook_auth())) + .with_last_main_commit(commit); + + //when + actor + .start() + .send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + log.no_message_contains("send")?; + log.require_message_containing("not a new commit on main")?; + Ok(()) +} + +#[actix::test] +async fn when_message_is_push_already_seen_commit_to_next() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_config = given::a_repo_config(); + let repo_details = given::repo_details(&fs).with_repo_config(Some(repo_config.clone())); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let commit = given::a_commit(); + let push = given::a_push() + .with_branch(repo_config.branches().next()) + .with_sha(commit.sha().to_string()) + .with_message(commit.message().to_string()); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| true); // is valid + forge.expect_parse_webhook_body().return_once(|_| Ok(push)); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor + .with_webhook_auth(Some(given::a_webhook_auth())) + .with_last_next_commit(commit); + + //when + actor + .start() + .send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + log.no_message_contains("send")?; + log.require_message_containing("not a new commit on next")?; + Ok(()) +} + +#[actix::test] +async fn when_message_is_push_already_seen_commit_to_dev() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_config = given::a_repo_config(); + let repo_details = given::repo_details(&fs).with_repo_config(Some(repo_config.clone())); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let commit = given::a_commit(); + let push = given::a_push() + .with_branch(repo_config.branches().dev()) + .with_sha(commit.sha().to_string()) + .with_message(commit.message().to_string()); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| true); // is valid + forge.expect_parse_webhook_body().return_once(|_| Ok(push)); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor + .with_webhook_auth(Some(given::a_webhook_auth())) + .with_last_dev_commit(commit); + + //when + actor + .start() + .send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + log.no_message_contains("send")?; + log.require_message_containing("not a new commit on dev")?; + Ok(()) +} + +#[actix::test] +async fn when_message_is_push_new_commit_to_main_should_stash_and_validate_repo() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_config = given::a_repo_config(); + let repo_details = given::repo_details(&fs).with_repo_config(Some(repo_config.clone())); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let push_commit = given::a_commit(); + let push = given::a_push() + .with_branch(repo_config.branches().main()) + .with_sha(push_commit.sha().to_string()) + .with_message(push_commit.message().to_string()); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| true); // is valid + forge.expect_parse_webhook_body().return_once(|_| Ok(push)); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor + .with_webhook_auth(Some(given::a_webhook_auth())) + .with_last_main_commit(given::a_commit()); + + //when + let addr = actor.start(); + addr.send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + let view = addr.send(ExamineActor).await?; + assert_eq!(view.last_main_commit, Some(push_commit)); + log.require_message_containing("send: ValidateRepo")?; + Ok(()) +} + +#[actix::test] +async fn when_message_is_push_new_commit_to_next_should_stash_and_validate_repo() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_config = given::a_repo_config(); + let repo_details = given::repo_details(&fs).with_repo_config(Some(repo_config.clone())); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let push_commit = given::a_commit(); + let push = given::a_push() + .with_branch(repo_config.branches().next()) + .with_sha(push_commit.sha().to_string()) + .with_message(push_commit.message().to_string()); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| true); // is valid + forge.expect_parse_webhook_body().return_once(|_| Ok(push)); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor + .with_webhook_auth(Some(given::a_webhook_auth())) + .with_last_next_commit(given::a_commit()); + + //when + let addr = actor.start(); + addr.send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + let view = addr.send(ExamineActor).await?; + assert_eq!(view.last_next_commit, Some(push_commit)); + log.require_message_containing("send: ValidateRepo")?; + Ok(()) +} + +#[actix::test] +async fn when_message_is_push_new_commit_to_dev_should_stash_and_validate_repo() -> TestResult { + //given + let fs = given::a_filesystem(); + let repo_config = given::a_repo_config(); + let repo_details = given::repo_details(&fs).with_repo_config(Some(repo_config.clone())); + let forge_alias = given::a_forge_alias(); + let repo_alias = given::a_repo_alias(); + let headers = BTreeMap::new(); + let body = Body::new("".to_string()); + let forge_notification = ForgeNotification::new(forge_alias, repo_alias, headers, body); + let repository_factory = MockRepositoryFactory::new(); + let push_commit = given::a_commit(); + let push = given::a_push() + .with_branch(repo_config.branches().dev()) + .with_sha(push_commit.sha().to_string()) + .with_message(push_commit.message().to_string()); + let mut forge = given::a_forge(); + forge + .expect_is_message_authorised() + .return_once(|_, _| true); // is valid + forge.expect_parse_webhook_body().return_once(|_| Ok(push)); + let (actor, log) = given::a_repo_actor( + repo_details, + Box::new(repository_factory), + forge, + given::a_network().into(), + ); + let actor = actor + .with_webhook_auth(Some(given::a_webhook_auth())) + .with_last_dev_commit(given::a_commit()); + + //when + let addr = actor.start(); + addr.send(actor::messages::WebhookNotification::new( + forge_notification, + )) + .await?; + System::current().stop(); + + //then + let view = addr.send(ExamineActor).await?; + assert_eq!(view.last_dev_commit, Some(push_commit)); + log.require_message_containing("send: ValidateRepo")?; + Ok(()) +} diff --git a/crates/repo-actor/src/tests/handlers/webhook_registered.rs b/crates/repo-actor/src/tests/handlers/webhook_registered.rs new file mode 100644 index 0000000..3be9b43 --- /dev/null +++ b/crates/repo-actor/src/tests/handlers/webhook_registered.rs @@ -0,0 +1,30 @@ +// +use super::*; + +#[actix::test] +async fn should_store_webhook_details() -> TestResult { + //given + let fs = given::a_filesystem(); + let (open_repository, repo_details) = given::an_open_repository(&fs); + let webhook_id = given::a_webhook_id(); + let webhook_auth = given::a_webhook_auth(); + + //when + let (addr, _log) = when::start_actor_with_open_repository( + Box::new(open_repository), + repo_details, + given::a_forge(), + ); + addr.send(actor::messages::WebhookRegistered::new(( + webhook_id.clone(), + webhook_auth.clone(), + ))) + .await?; + System::current().stop(); + + //then + let view = addr.send(ExamineActor).await?; + assert_eq!(view.webhook_id, Some(webhook_id)); + assert_eq!(view.webhook_auth, Some(webhook_auth)); + Ok(()) +} diff --git a/crates/repo-actor/src/tests/load.rs b/crates/repo-actor/src/tests/load.rs index 0d0ab3a..e9ed655 100644 --- a/crates/repo-actor/src/tests/load.rs +++ b/crates/repo-actor/src/tests/load.rs @@ -1,8 +1,4 @@ // -use std::path::PathBuf; - -use git_next_git::common::repo_details; - use super::*; #[tokio::test] diff --git a/crates/repo-actor/src/tests/mod.rs b/crates/repo-actor/src/tests/mod.rs new file mode 100644 index 0000000..8d41620 --- /dev/null +++ b/crates/repo-actor/src/tests/mod.rs @@ -0,0 +1,109 @@ +// +use crate::{self as actor, message}; +use actix::prelude::*; +use actor::{ + messages::{CloneRepo, MessageToken}, + RepoActor, RepoActorLog, +}; +use assert2::let_assert; +use config::{ + server::{Webhook, WebhookUrl}, + webhook::forge_notification::Body, + BranchName, ForgeAlias, ForgeConfig, ForgeNotification, ForgeType, GitDir, RegisteredWebhook, + RepoAlias, RepoBranches, RepoConfig, ServerRepoConfig, WebhookAuth, WebhookId, +}; +use git::{ + repository::{open::MockOpenRepositoryLike, Direction, MockRepositoryFactory}, + Generation, GitRemote, MockForgeLike, RepoDetails, +}; +use git_next_config as config; +use git_next_git as git; +use mockall::predicate::eq; +use std::{ + collections::{BTreeMap, HashMap}, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +type TestResult = Result<(), Box>; + +mod branch; +mod expect; +pub mod given; +mod handlers; +mod load; +mod when; + +impl RepoActorLog { + pub fn no_message_contains(&self, needle: impl AsRef + std::fmt::Display) -> TestResult { + if self.find_in_messages(needle.as_ref())? { + tracing::error!(?self, ""); + panic!("found unexpected message: {needle}"); + } + Ok(()) + } + + pub fn require_message_containing( + &self, + needle: impl AsRef + std::fmt::Display, + ) -> TestResult { + if !self.find_in_messages(needle.as_ref())? { + tracing::error!(?self, ""); + panic!("expected message not found: {needle}"); + } + Ok(()) + } + + fn find_in_messages( + &self, + needle: impl AsRef, + ) -> Result> { + let found = self + .lock() + .map_err(|e| e.to_string())? + .iter() + .any(|message| message.contains(needle.as_ref())); + Ok(found) + } +} + +message!(ExamineActor => RepoActorView); +impl Handler for RepoActor { + type Result = RepoActorView; + + fn handle(&mut self, _msg: ExamineActor, _ctx: &mut Self::Context) -> Self::Result { + let repo_actor: &Self = self; + Self::Result::from(repo_actor) + } +} +#[derive(Debug, MessageResponse)] +pub struct RepoActorView { + pub sleep_duration: std::time::Duration, + pub generation: git::Generation, + pub message_token: MessageToken, + pub repo_details: git::RepoDetails, + pub webhook: config::server::Webhook, + pub webhook_id: Option, // INFO: if [None] then no webhook is configured + pub webhook_auth: Option, // INFO: if [None] then no webhook is configured + pub last_main_commit: Option, + pub last_next_commit: Option, + pub last_dev_commit: Option, + pub log: Option, +} +impl From<&RepoActor> for RepoActorView { + fn from(repo_actor: &RepoActor) -> Self { + Self { + sleep_duration: repo_actor.sleep_duration, + generation: repo_actor.generation, + message_token: repo_actor.message_token, + repo_details: repo_actor.repo_details.clone(), + webhook: repo_actor.webhook.clone(), + webhook_id: repo_actor.webhook_id.clone(), + webhook_auth: repo_actor.webhook_auth.clone(), + last_main_commit: repo_actor.last_main_commit.clone(), + last_next_commit: repo_actor.last_next_commit.clone(), + last_dev_commit: repo_actor.last_dev_commit.clone(), + log: repo_actor.log.clone(), + } + } +} diff --git a/crates/repo-actor/src/tests/when.rs b/crates/repo-actor/src/tests/when.rs index 88e5c04..ee33ddd 100644 --- a/crates/repo-actor/src/tests/when.rs +++ b/crates/repo-actor/src/tests/when.rs @@ -18,7 +18,7 @@ pub fn start_actor( } pub fn start_actor_with_open_repository( - open_repository: MockOpenRepositoryLike, + open_repository: Box, repo_details: git::RepoDetails, forge: Box, ) -> (actix::Addr, RepoActorLog) { @@ -28,7 +28,7 @@ pub fn start_actor_with_open_repository( forge, given::a_network().into(), ); - let actor = actor.with_open_repository(Box::new(open_repository)); + let actor = actor.with_open_repository(Some(open_repository)); (actor.start(), log) }