Skip to content

Commit

Permalink
feat(core): add a new method for coffee verification
Browse files Browse the repository at this point in the history
This commit introduces the following changes:
- Implementation of the is_sane method for the CoffeeNurse type.
- Implementation of the organize method for the CoffeeNurse type, which organizes the actions taken by coffee nurse to ensure each action is included only once.
- Addition of a new method called coffee_verify to check the status of the coffee configuration without taking any action.
- Introduction of a --verify flag for the coffee nurse command, which calls the coffee_verify method.
- Inclusion of a test for the coffee_verify method.

Signed-off-by: Tarek <tareknaser360@gmail.com>
  • Loading branch information
tareknaser authored and vincenzopalazzo committed Oct 12, 2023
1 parent 869d9de commit 0813336
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 24 deletions.
4 changes: 1 addition & 3 deletions coffee_cmd/src/coffee_term/command_show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub fn show_nurse_result(
Ok(nurse) => {
// special case: if the nurse is sane
// we print a message and return
if nurse.status[0] == NurseStatus::Sane {
if nurse.is_sane() {
term::success!("Coffee configuration is not corrupt! No need to run coffee nurse");
return Ok(());
}
Expand All @@ -98,14 +98,12 @@ pub fn show_nurse_result(

for status in &nurse.status {
let action_str = match status {
NurseStatus::Sane => "".to_string(),
NurseStatus::RepositoryLocallyRestored(_) => "Restored using Git".to_string(),
NurseStatus::RepositoryLocallyRemoved(_) => {
"Removed from local storage".to_string()
}
};
let repos_str = match status {
NurseStatus::Sane => "".to_string(),
NurseStatus::RepositoryLocallyRestored(repos)
| NurseStatus::RepositoryLocallyRemoved(repos) => repos.join(", "),
};
Expand Down
15 changes: 12 additions & 3 deletions coffee_cmd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,18 @@ async fn main() -> Result<(), CoffeeError> {
}
Err(err) => Err(err),
},
CoffeeCommand::Nurse {} => {
let nurse_result = coffee.nurse().await;
coffee_term::show_nurse_result(nurse_result)
CoffeeCommand::Nurse { verify } => {
if verify {
let result = coffee.nurse_verify().await?;
term::info!("{}", result);
if !result.is_sane() {
term::info!("Coffee local directory is damaged, please run `coffee nurse` to try to fix it");
}
Ok(())
} else {
let nurse_result = coffee.nurse().await;
coffee_term::show_nurse_result(nurse_result)
}
}
};

Expand Down
38 changes: 31 additions & 7 deletions coffee_core/src/coffee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ impl CoffeeManager {
if let Err(err) = self.coffee_cln_config.parse() {
log::error!("{}", err.cause);
}
if !self.config.skip_verify {
// Check for the chain of responsibility
let status = self.recovery_strategies.scan(self).await?;
log::debug!("Chain of responsibility status: {:?}", status);
// if any defect is found, we print a warning message (we don't take action)
if !status.defects.is_empty() {
return Err(
error!("Coffee found some defects in the configuration. Please run `coffee nurse` to fix them.
If you are want to skip the verification, please add the `--skip-verify ` flag to the command.")
);
};
}

self.load_cln_conf().await?;
log::debug!("cln conf {:?}", self.coffee_cln_config);
log::debug!("finish plugin manager inventory");
Expand Down Expand Up @@ -453,6 +466,10 @@ impl PluginManager for CoffeeManager {
Err(err)
}

async fn nurse_verify(&self) -> Result<ChainOfResponsibilityStatus, CoffeeError> {
self.recovery_strategies.scan(self).await
}

async fn nurse(&mut self) -> Result<CoffeeNurse, CoffeeError> {
let status = self.recovery_strategies.scan(self).await?;
let mut nurse_actions: Vec<NurseStatus> = vec![];
Expand All @@ -465,14 +482,11 @@ impl PluginManager for CoffeeManager {
}
}
}

// If there was no actions taken by nurse, we return a sane status.
if nurse_actions.is_empty() {
nurse_actions.push(NurseStatus::Sane);
}
Ok(CoffeeNurse {
let mut nurse = CoffeeNurse {
status: nurse_actions,
})
};
nurse.organize();
Ok(nurse)
}

async fn patch_repository_locally_absent(
Expand Down Expand Up @@ -503,6 +517,16 @@ impl PluginManager for CoffeeManager {
}
Err(err) => {
log::debug!("error while recovering repository {repo_name}: {err}");
// We make sure that the repository folder is removed
// from local storage.
// Maybe when trying to recover the repository,
// we have created the folder but we were not able
// to clone the repository.
let repo_path = repo.url().path_string;
// This shouldn't return an error if the repository
// is not present locally.
let _ = fs::remove_dir_all(repo_path).await;

log::info!("removing repository {}", repo_name.clone());
self.repos.remove(repo_name);
log::debug!("remote removed: {}", repo_name);
Expand Down
4 changes: 3 additions & 1 deletion coffee_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub enum CoffeeOperation {
Show(String),
/// Search(plugin name)
Search(String),
Nurse,
Nurse(bool),
}

#[derive(Clone, Debug)]
Expand All @@ -40,4 +40,6 @@ pub trait CoffeeArgs: Send + Sync {
fn network(&self) -> Option<String>;
/// return the data dir
fn data_dir(&self) -> Option<String>;
/// return the skip verify flag
fn skip_verify(&self) -> bool;
}
3 changes: 3 additions & 0 deletions coffee_lib/src/plugin_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub trait PluginManager {
/// clean up storage information about the remote repositories of the plugin manager.
async fn nurse(&mut self) -> Result<CoffeeNurse, CoffeeError>;

/// verify that coffee configuration is sane without taking any action.
async fn nurse_verify(&self) -> Result<ChainOfResponsibilityStatus, CoffeeError>;

/// patch coffee configuration in the case that a repository is present in the coffee
/// configuration but is absent from the local storage.
async fn patch_repository_locally_absent(
Expand Down
69 changes: 63 additions & 6 deletions coffee_lib/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub mod response {

/// This struct is used to represent a defect
/// that can be patched by the nurse.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum Defect {
// A patch operation when a git repository is present in the coffee configuration
// but is absent from the local storage.
Expand All @@ -129,12 +129,38 @@ pub mod response {
pub defects: Vec<Defect>,
}

impl ChainOfResponsibilityStatus {
pub fn is_sane(&self) -> bool {
self.defects.is_empty()
}
}

impl fmt::Display for ChainOfResponsibilityStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.defects.is_empty() {
write!(f, "Coffee is sane")
} else {
writeln!(f, "Coffee has the following defects:")?;
for (i, defect) in self.defects.iter().enumerate() {
match defect {
Defect::RepositoryLocallyAbsent(repos) => {
write!(f, "{}. Repository missing locally: ", i + 1)?;
for repo in repos {
write!(f, " {}", repo)?;
}
}
}
}
Ok(())
}
}
}

/// This struct is used to represent the status of nurse,
/// either sane or not.
/// If not sane, return the action that nurse has taken.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum NurseStatus {
Sane,
RepositoryLocallyRestored(Vec<String>),
RepositoryLocallyRemoved(Vec<String>),
}
Expand All @@ -144,13 +170,44 @@ pub mod response {
pub status: Vec<NurseStatus>,
}

impl CoffeeNurse {
pub fn is_sane(&self) -> bool {
self.status.is_empty()
}

pub fn organize(&mut self) {
// For every action taken by the nurse, we want to
// have 1 entry with the list of repositories affected.
let mut new_status: Vec<NurseStatus> = vec![];
let mut repositories_locally_removed: Vec<String> = vec![];
let mut repositories_locally_restored: Vec<String> = vec![];
for repo in self.status.iter() {
match repo {
NurseStatus::RepositoryLocallyRemoved(repos) => {
repositories_locally_removed.append(&mut repos.clone())
}
NurseStatus::RepositoryLocallyRestored(repos) => {
repositories_locally_restored.append(&mut repos.clone())
}
}
}
if !repositories_locally_removed.is_empty() {
new_status.push(NurseStatus::RepositoryLocallyRemoved(
repositories_locally_removed,
));
}
if !repositories_locally_restored.is_empty() {
new_status.push(NurseStatus::RepositoryLocallyRestored(
repositories_locally_restored,
));
}
self.status = new_status;
}
}

impl fmt::Display for NurseStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
NurseStatus::Sane => write!(
f,
"coffee configuration is not corrupt! No need to run coffee nurse"
),
NurseStatus::RepositoryLocallyRestored(val) => {
write!(f, "Repositories restored locally: {}", val.join(" "))
}
Expand Down
5 changes: 5 additions & 0 deletions coffee_plugin/src/plugin/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ impl CoffeeArgs for PluginArgs {
fn network(&self) -> Option<String> {
Some(self.network.clone())
}

// we don't need to verify the nurse
fn skip_verify(&self) -> bool {
true
}
}

impl From<CLNConf> for PluginArgs {
Expand Down
27 changes: 23 additions & 4 deletions tests/src/coffee_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use tokio::fs;
use serde_json::json;

use coffee_lib::plugin_manager::PluginManager;
use coffee_lib::types::response::NurseStatus;
use coffee_lib::types::response::{Defect, NurseStatus};
use coffee_testing::cln::Node;
use coffee_testing::prelude::tempfile;
use coffee_testing::{CoffeeTesting, CoffeeTestingArgs};
Expand Down Expand Up @@ -634,10 +634,8 @@ pub async fn test_nurse_repository_missing_on_disk() {
let result = manager.coffee().nurse().await;
assert!(result.is_ok(), "{:?}", result);
let result = result.unwrap();
// Assert result has only 1 value
assert_eq!(result.status.len(), 1, "{:?}", result);
// Assert that the value is Sane
assert_eq!(result.status[0], NurseStatus::Sane, "{:?}", result);
assert!(result.is_sane());

// Remove folgore repository (we militate that the repository is missing on disk)
let result = fs::remove_dir_all(&folgore_path).await;
Expand All @@ -650,6 +648,20 @@ pub async fn test_nurse_repository_missing_on_disk() {
folgore_path
);

// Assert that nurse --verify returns that coffee is corrupt
let result = manager.coffee().nurse_verify().await;
assert!(result.is_ok(), "{:?}", result);
let result = result.unwrap();
// Assert that the value is Corrupt
let defects = result.defects;
assert_eq!(defects.len(), 1, "{:?}", defects);
assert_eq!(
defects[0],
Defect::RepositoryLocallyAbsent(vec!["folgore".to_string()]),
"{:?}",
defects
);

// Run nurse again
// Assert that nurse returns that coffee isn't Sane
let result = manager.coffee().nurse().await;
Expand Down Expand Up @@ -679,5 +691,12 @@ pub async fn test_nurse_repository_missing_on_disk() {
folgore_readme_path
);

// Assert that nurse --verify returns that coffee is Sane
let result = manager.coffee().nurse_verify().await;
assert!(result.is_ok(), "{:?}", result);
let result = result.unwrap();
// Assert that the value is Sane
assert!(result.is_sane());

cln.stop().await.unwrap();
}

0 comments on commit 0813336

Please sign in to comment.