tests: repo-actor: add more tests
All checks were successful
Rust / build (push) Successful in 1m56s
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful

This commit is contained in:
Paul Campbell 2024-06-27 21:10:41 +01:00
parent ffab1986a7
commit e585b07f6b
24 changed files with 767 additions and 141 deletions

View file

@ -3,7 +3,7 @@ use crate as config;
use derive_more::Constructor; use derive_more::Constructor;
#[derive(Debug, Constructor)] #[derive(Debug, Constructor, derive_with::With)]
pub struct Push { pub struct Push {
branch: config::BranchName, branch: config::BranchName,
sha: String, sha: String,
@ -30,7 +30,6 @@ impl Push {
&self.message &self.message
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Branch { pub enum Branch {
Main, Main,

View file

@ -42,6 +42,7 @@ ulid = { workspace = true }
# boilerplate # boilerplate
derive_more = { workspace = true } derive_more = { workspace = true }
derive-with = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
# Actors # Actors

View file

@ -14,10 +14,12 @@ impl Handler<WebhookNotification> for actor::RepoActor {
#[tracing::instrument(name = "RepoActor::WebhookMessage", skip_all, fields(token = %self.message_token, repo = %self.repo_details))] #[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 { fn handle(&mut self, msg: WebhookNotification, ctx: &mut Self::Context) -> Self::Result {
let Some(expected_authorization) = &self.webhook_auth else { 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"); warn!("Don't know what authorization to expect");
return; return;
}; };
let Some(config) = &self.repo_details.repo_config else { let Some(config) = &self.repo_details.repo_config else {
actor::logger(&self.log, "server has no repo config");
warn!("No repo config"); warn!("No repo config");
return; return;
}; };
@ -25,6 +27,7 @@ impl Handler<WebhookNotification> for actor::RepoActor {
.forge .forge
.is_message_authorised(&msg, expected_authorization) .is_message_authorised(&msg, expected_authorization)
{ {
actor::logger(&self.log, "message authorisation is invalid");
warn!( warn!(
"Invalid authorization - expected {}", "Invalid authorization - expected {}",
expected_authorization expected_authorization
@ -34,11 +37,13 @@ impl Handler<WebhookNotification> for actor::RepoActor {
let body = msg.body(); let body = msg.body();
match self.forge.parse_webhook_body(body) { match self.forge.parse_webhook_body(body) {
Err(err) => { Err(err) => {
actor::logger(&self.log, "message parse error - not a push");
warn!(?err, "Not a 'push'"); warn!(?err, "Not a 'push'");
return; return;
} }
Ok(push) => match push.branch(config.branches()) { Ok(push) => match push.branch(config.branches()) {
None => { None => {
actor::logger(&self.log, "unknown branch");
warn!( warn!(
?push, ?push,
"Unrecognised branch, we should be filtering to only the ones we want" "Unrecognised branch, we should be filtering to only the ones we want"
@ -46,8 +51,10 @@ impl Handler<WebhookNotification> for actor::RepoActor {
return; return;
} }
Some(config::webhook::push::Branch::Main) => { Some(config::webhook::push::Branch::Main) => {
actor::logger(&self.log, "message is for main branch");
let commit = git::Commit::from(push); let commit = git::Commit::from(push);
if self.last_main_commit.as_ref() == Some(&commit) { if self.last_main_commit.as_ref() == Some(&commit) {
actor::logger(&self.log, "not a new commit on main");
info!( info!(
branch = %config.branches().main(), branch = %config.branches().main(),
%commit, %commit,
@ -58,8 +65,10 @@ impl Handler<WebhookNotification> for actor::RepoActor {
self.last_main_commit.replace(commit); self.last_main_commit.replace(commit);
} }
Some(config::webhook::push::Branch::Next) => { Some(config::webhook::push::Branch::Next) => {
actor::logger(&self.log, "message is for next branch");
let commit = git::Commit::from(push); let commit = git::Commit::from(push);
if self.last_next_commit.as_ref() == Some(&commit) { if self.last_next_commit.as_ref() == Some(&commit) {
actor::logger(&self.log, "not a new commit on next");
info!( info!(
branch = %config.branches().next(), branch = %config.branches().next(),
%commit, %commit,
@ -70,8 +79,10 @@ impl Handler<WebhookNotification> for actor::RepoActor {
self.last_next_commit.replace(commit); self.last_next_commit.replace(commit);
} }
Some(config::webhook::push::Branch::Dev) => { Some(config::webhook::push::Branch::Dev) => {
actor::logger(&self.log, "message is for dev branch");
let commit = git::Commit::from(push); let commit = git::Commit::from(push);
if self.last_dev_commit.as_ref() == Some(&commit) { if self.last_dev_commit.as_ref() == Some(&commit) {
actor::logger(&self.log, "not a new commit on dev");
info!( info!(
branch = %config.branches().dev(), branch = %config.branches().dev(),
%commit, %commit,
@ -88,7 +99,10 @@ impl Handler<WebhookNotification> for actor::RepoActor {
token = %message_token, token = %message_token,
"New commit" "New commit"
); );
ctx.address() actor::do_send(
.do_send(actor::messages::ValidateRepo::new(message_token)); ctx.address(),
actor::messages::ValidateRepo::new(message_token),
&self.log,
);
} }
} }

View file

@ -8,13 +8,22 @@ mod tests;
use actix::prelude::*; use actix::prelude::*;
use derive_more::Deref;
use git_next_config as config; use git_next_config as config;
use git_next_git as git; use git_next_git as git;
use kxio::network::Network; use kxio::network::Network;
use tracing::{info, warn, Instrument}; use tracing::{info, warn, Instrument};
pub type RepoActorLog = std::sync::Arc<std::sync::Mutex<Vec<String>>>; #[derive(Clone, Debug, Default)]
pub struct RepoActorLog(std::sync::Arc<std::sync::Mutex<Vec<String>>>);
impl Deref for RepoActorLog {
type Target = std::sync::Arc<std::sync::Mutex<Vec<String>>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// An actor that represents a Git Repository. /// An actor that represents a Git Repository.
/// ///
@ -48,7 +57,7 @@ pub type RepoActorLog = std::sync::Arc<std::sync::Mutex<Vec<String>>>;
/// WebhookMessage --> ValidateRepo /// 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)] #[display("{}:{}:{}", generation, repo_details.forge.forge_alias(), repo_details.repo_alias)]
pub struct RepoActor { pub struct RepoActor {
sleep_duration: std::time::Duration, 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<dyn git::repository::OpenRepositoryLike>,
) -> 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 { impl Actor for RepoActor {
type Context = Context<Self>; type Context = Context<Self>;
#[tracing::instrument(name = "RepoActor::stopping", skip_all, fields(repo = %self.repo_details))] #[tracing::instrument(name = "RepoActor::stopping", skip_all, fields(repo = %self.repo_details))]

View file

@ -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<dyn std::error::Error>>;
mod branch;
mod expect;
pub mod given;
mod handlers;
mod load;
mod when;

View file

@ -1,6 +1,4 @@
// //
use git::repository::{OnFetch, OnPush, OpenRepositoryLike};
use super::*; use super::*;
#[test] #[test]

View file

@ -1,5 +1,6 @@
// //
use super::*; use super::*;
mod when_at_dev { mod when_at_dev {
// next and dev branches are the same // next and dev branches are the same
use super::*; use super::*;
@ -27,12 +28,14 @@ mod when_at_dev {
Ok(()) Ok(())
} }
} }
mod can_advance { mod can_advance {
// dev has at least one commit ahead of next // dev has at least one commit ahead of next
use super::*; use super::*;
mod to_wip_commit { mod to_wip_commit {
// commit on dev is either invalid message or a WIP // commit on dev is either invalid message or a WIP
use super::*; use super::*;
#[test] #[test]
fn should_not_push() -> TestResult { fn should_not_push() -> TestResult {
let next = given::a_commit(); let next = given::a_commit();
@ -58,9 +61,11 @@ mod can_advance {
Ok(()) Ok(())
} }
} }
mod to_invalid_commit { mod to_invalid_commit {
// commit on dev is either invalid message or a WIP // commit on dev is either invalid message or a WIP
use super::*; use super::*;
#[test] #[test]
fn should_not_push_and_error() -> TestResult { fn should_not_push_and_error() -> TestResult {
let next = given::a_commit(); let next = given::a_commit();
@ -90,13 +95,12 @@ mod can_advance {
Ok(()) Ok(())
} }
} }
mod to_valid_commit { mod to_valid_commit {
// commit on dev is valid conventional commit message // commit on dev is valid conventional commit message
use super::*; use super::*;
mod push_is_err { mod push_is_err {
use git::repository::{OnFetch, OnPush};
// the git push command fails // the git push command fails
use super::*; use super::*;
@ -129,12 +133,11 @@ mod can_advance {
Ok(()) Ok(())
} }
} }
mod push_is_ok { mod push_is_ok {
// the git push command succeeds // the git push command succeeds
use git_next_git::repository::{OnFetch, OnPush};
// the git push command fails
use super::*; use super::*;
#[test] #[test]
fn should_ok() -> TestResult { fn should_ok() -> TestResult {
let next = given::a_commit(); let next = given::a_commit();

View file

@ -1,22 +1,5 @@
// //
use super::*; 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( pub fn has_all_valid_remote_defaults(
open_repository: &mut MockOpenRepositoryLike, 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_url = given::a_webhook_url(forge_alias, repo_alias);
let webhook = given::a_webhook(&webhook_url); let webhook = given::a_webhook(&webhook_url);
let generation = Generation::default(); let generation = Generation::default();
let log = Arc::new(Mutex::new(vec![])); let log = RepoActorLog::default();
let actors_log = log.clone(); let actors_log = log.clone();
( (
actor::RepoActor::new( actor::RepoActor::new(
@ -276,3 +259,11 @@ pub fn a_hostname() -> config::Hostname {
pub fn a_registered_webhook() -> RegisteredWebhook { pub fn a_registered_webhook() -> RegisteredWebhook {
RegisteredWebhook::new(given::a_webhook_id(), given::a_webhook_auth()) 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(),
)
}

View file

@ -27,8 +27,11 @@ async fn when_repo_config_should_fetch_then_push_then_revalidate() -> TestResult
.return_once(|_, _, _, _| Ok(())); .return_once(|_, _, _, _| Ok(()));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::AdvanceMain::new(next_commit.clone())) addr.send(actor::messages::AdvanceMain::new(next_commit.clone()))
.await?; .await?;
System::current().stop(); System::current().stop();
@ -69,8 +72,11 @@ async fn when_server_config_should_fetch_then_push_then_revalidate() -> TestResu
.return_once(|_, _, _, _| Ok(())); .return_once(|_, _, _, _| Ok(()));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::AdvanceMain::new(next_commit.clone())) addr.send(actor::messages::AdvanceMain::new(next_commit.clone()))
.await?; .await?;
System::current().stop(); System::current().stop();

View file

@ -26,8 +26,11 @@ async fn should_fetch_then_push_then_revalidate() -> TestResult {
.return_once(|_, _, _, _| Ok(())); .return_once(|_, _, _, _| Ok(()));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::AdvanceNext::new(( addr.send(actor::messages::AdvanceNext::new((
next_commit.clone(), next_commit.clone(),
dev_commit_log, dev_commit_log,

View file

@ -15,8 +15,11 @@ async fn should_passthrough_to_receive_ci_status() -> TestResult {
); );
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, Box::new(forge)); Box::new(open_repository),
repo_details,
Box::new(forge),
);
addr.send(actor::messages::CheckCIStatus::new(next_commit.clone())) addr.send(actor::messages::CheckCIStatus::new(next_commit.clone()))
.await?; .await?;
System::current().stop(); System::current().stop();

View file

@ -34,8 +34,11 @@ async fn when_read_file_ok_should_send_config_loaded() -> TestResult {
.return_once(|| Box::new(load_config_open_repo)); .return_once(|| Box::new(load_config_open_repo));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::LoadConfigFromRepo::new()) addr.send(actor::messages::LoadConfigFromRepo::new())
.await?; .await?;
System::current().stop(); 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)); .return_once(|| Box::new(load_config_open_repo));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::LoadConfigFromRepo::new()) addr.send(actor::messages::LoadConfigFromRepo::new())
.await?; .await?;
System::current().stop(); System::current().stop();

View file

@ -1,6 +1,5 @@
// //
use super::*; use super::*;
use actix::prelude::*;
#[actix::test] #[actix::test]
async fn should_store_repo_config_in_actor() -> TestResult { 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(); let new_repo_config = given::a_repo_config();
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::ReceiveRepoConfig::new( addr.send(actor::messages::ReceiveRepoConfig::new(
new_repo_config.clone(), new_repo_config.clone(),
)) ))
@ -36,8 +38,11 @@ async fn should_validate_repo() -> TestResult {
let new_repo_config = given::a_repo_config(); let new_repo_config = given::a_repo_config();
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::ReceiveRepoConfig::new( addr.send(actor::messages::ReceiveRepoConfig::new(
new_repo_config.clone(), new_repo_config.clone(),
)) ))
@ -63,8 +68,11 @@ async fn should_register_webhook() -> TestResult {
let new_repo_config = given::a_repo_config(); let new_repo_config = given::a_repo_config();
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::ReceiveRepoConfig::new( addr.send(actor::messages::ReceiveRepoConfig::new(
new_repo_config.clone(), new_repo_config.clone(),
)) ))

View file

@ -1,6 +1,5 @@
// //
use super::*; use super::*;
use actix::prelude::*;
mod advance_main; mod advance_main;
mod advance_next; mod advance_next;
@ -11,3 +10,5 @@ mod loaded_config;
mod receive_ci_status; mod receive_ci_status;
mod register_webhook; mod register_webhook;
mod validate_repo; mod validate_repo;
mod webhook_notification;
mod webhook_registered;

View file

@ -9,8 +9,11 @@ async fn when_pass_should_advance_main_to_next() -> TestResult {
let next_commit = given::a_named_commit("next"); let next_commit = given::a_named_commit("next");
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::ReceiveCIStatus::new(( addr.send(actor::messages::ReceiveCIStatus::new((
next_commit.clone(), next_commit.clone(),
git::forge::commit::Status::Pass, 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"); let next_commit = given::a_named_commit("next");
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::ReceiveCIStatus::new(( addr.send(actor::messages::ReceiveCIStatus::new((
next_commit.clone(), next_commit.clone(),
git::forge::commit::Status::Pending, 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"); let next_commit = given::a_named_commit("next");
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(actor::messages::ReceiveCIStatus::new(( addr.send(actor::messages::ReceiveCIStatus::new((
next_commit.clone(), next_commit.clone(),
git::forge::commit::Status::Fail, git::forge::commit::Status::Fail,

View file

@ -17,8 +17,11 @@ async fn when_registered_ok_should_send_webhook_registered() -> TestResult {
forge.expect_duplicate().return_once(|| Box::new(my_forge)); forge.expect_duplicate().return_once(|| Box::new(my_forge));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, Box::new(forge)); Box::new(open_repository),
repo_details,
Box::new(forge),
);
addr.send(actor::messages::RegisterWebhook::new()).await?; addr.send(actor::messages::RegisterWebhook::new()).await?;
System::current().stop(); 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)); forge.expect_duplicate().return_once(|| Box::new(my_forge));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, Box::new(forge)); Box::new(open_repository),
repo_details,
Box::new(forge),
);
addr.send(actor::messages::RegisterWebhook::new()).await?; addr.send(actor::messages::RegisterWebhook::new()).await?;
System::current().stop(); System::current().stop();

View file

@ -1,6 +1,4 @@
// //
use crate::messages::{MessageToken, ValidateRepo};
use super::*; use super::*;
#[test_log::test(actix::test)] #[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); expect::push_ok(&mut open_repository);
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) addr.send(crate::messages::ValidateRepo::new(MessageToken::default()))
.await?; .await?;
System::current().stop(); 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); expect::push_ok(&mut open_repository);
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) addr.send(crate::messages::ValidateRepo::new(MessageToken::default()))
.await?; .await?;
System::current().stop(); 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); expect::push_ok(&mut open_repository);
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) addr.send(crate::messages::ValidateRepo::new(MessageToken::default()))
.await?; .await?;
System::current().stop(); 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)); .return_once(|_, _| Ok(dev_branch_log));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) addr.send(crate::messages::ValidateRepo::new(MessageToken::default()))
.await?; .await?;
System::current().stop(); 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)); .return_once(move |_, _| Ok(dev_commit_log));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) addr.send(crate::messages::ValidateRepo::new(MessageToken::default()))
.await?; .await?;
System::current().stop(); 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)); .return_once(move |_, _| Ok(dev_commit_log));
//when //when
let (addr, log) = let (addr, log) = when::start_actor_with_open_repository(
when::start_actor_with_open_repository(open_repository, repo_details, given::a_forge()); Box::new(open_repository),
repo_details,
given::a_forge(),
);
addr.send(crate::messages::ValidateRepo::new(MessageToken::default())) addr.send(crate::messages::ValidateRepo::new(MessageToken::default()))
.await?; .await?;
System::current().stop(); System::current().stop();

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -1,8 +1,4 @@
// //
use std::path::PathBuf;
use git_next_git::common::repo_details;
use super::*; use super::*;
#[tokio::test] #[tokio::test]

View file

@ -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<dyn std::error::Error>>;
mod branch;
mod expect;
pub mod given;
mod handlers;
mod load;
mod when;
impl RepoActorLog {
pub fn no_message_contains(&self, needle: impl AsRef<str> + 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<str> + 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<str>,
) -> Result<bool, Box<dyn std::error::Error>> {
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<ExamineActor> 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<config::WebhookId>, // INFO: if [None] then no webhook is configured
pub webhook_auth: Option<config::WebhookAuth>, // INFO: if [None] then no webhook is configured
pub last_main_commit: Option<git::Commit>,
pub last_next_commit: Option<git::Commit>,
pub last_dev_commit: Option<git::Commit>,
pub log: Option<RepoActorLog>,
}
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(),
}
}
}

View file

@ -18,7 +18,7 @@ pub fn start_actor(
} }
pub fn start_actor_with_open_repository( pub fn start_actor_with_open_repository(
open_repository: MockOpenRepositoryLike, open_repository: Box<dyn OpenRepositoryLike>,
repo_details: git::RepoDetails, repo_details: git::RepoDetails,
forge: Box<dyn git::ForgeLike>, forge: Box<dyn git::ForgeLike>,
) -> (actix::Addr<RepoActor>, RepoActorLog) { ) -> (actix::Addr<RepoActor>, RepoActorLog) {
@ -28,7 +28,7 @@ pub fn start_actor_with_open_repository(
forge, forge,
given::a_network().into(), 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) (actor.start(), log)
} }