use shep_client::{Client, START_DEADLINE};
use shep_core::protocol::{Request, Response};
use shep_core::status::ProcStatus;
use crate::cli::Format;
use crate::exit::ExitCode;
use crate::flourish;
use crate::output::{FlockRows, SavedRollRow, Streams, emit, write_outcome};
pub async fn save(client: &Client, streams: &mut Streams<'_>) -> ExitCode {
match client.request(Request::SaveRoll).await {
Ok(Response::RollSaved { path, apps }) => write_outcome(emit(
&mut *streams.out,
streams.fmt,
"save",
SavedRollRow { file: path, apps },
streams.style,
)),
Ok(_unrecognised) => {
let message = "the daemon answered with a response this client does not understand";
streams.fail(ExitCode::Internal, message)
}
Err(err) => {
let code = ExitCode::from(&err);
streams.fail(code, &err.to_string())
}
}
}
pub async fn muster(client: &Client, streams: &mut Streams<'_>) -> ExitCode {
match client
.request_with_deadline(Request::Muster, Some(START_DEADLINE))
.await
{
Ok(Response::Mustered(procs)) => {
if procs.is_empty() {
streams.aside(
"muster_restored_nothing",
"the muster roll restored nothing",
);
}
let statuses: Vec<ProcStatus> = procs.iter().map(|p| p.status).collect();
let outcome = write_outcome(emit(
&mut *streams.out,
streams.fmt,
"muster",
FlockRows(procs),
streams.style,
));
if streams.fmt == Format::Table && !statuses.is_empty() && streams.style.level.sheep() {
let _ = write!(streams.out, "{}", flourish::mustered(&statuses));
}
outcome
}
Ok(_unrecognised) => {
let message = "the daemon answered with a response this client does not understand";
streams.fail(ExitCode::Internal, message)
}
Err(err) => {
let code = ExitCode::from(&err);
streams.fail(code, &err.to_string())
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use shep_client::testing::{
fake_client_capturing_envelopes, fake_client_on, fake_client_replying_err, sample_info,
};
use shep_core::protocol::{BusEvent, ProcessEventKind, RpcErrorCode};
use super::*;
const FAKE_REPLY_WAIT: Duration = Duration::from_secs(5);
#[tokio::test]
async fn save_sends_save_roll_and_nothing_else() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("s.sock");
let (client, mut envelopes) = fake_client_capturing_envelopes(&path).await;
let mut out = Vec::new();
let mut err = Vec::new();
let mut streams = Streams {
out: &mut out,
err: &mut err,
style: crate::style::Presentation::BARE,
fmt: Format::Table,
};
let _ = save(&client, &mut streams).await;
let envelope = tokio::time::timeout(FAKE_REPLY_WAIT, envelopes.recv())
.await
.expect("the fake daemon must answer inside the bound")
.unwrap();
assert_eq!(envelope.body, Request::SaveRoll);
}
#[tokio::test]
async fn a_failed_save_exits_non_zero_and_says_why() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("s.sock");
let (client, _served) = fake_client_replying_err(
&path,
RpcErrorCode::Internal,
"the supervisor engine has stopped; no roll was written",
)
.await;
let mut out = Vec::new();
let mut err = Vec::new();
let code = {
let mut streams = Streams {
out: &mut out,
err: &mut err,
style: crate::style::Presentation::BARE,
fmt: Format::Table,
};
save(&client, &mut streams).await
};
assert_eq!(code, ExitCode::Internal);
assert!(out.is_empty(), "a failed save prints no success table");
assert!(String::from_utf8(err).unwrap().contains("engine"));
}
#[tokio::test]
async fn muster_sends_muster_with_the_start_deadline() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("s.sock");
let (client, mut envelopes) = fake_client_capturing_envelopes(&path).await;
let mut out = Vec::new();
let mut err = Vec::new();
let mut streams = Streams {
out: &mut out,
err: &mut err,
style: crate::style::Presentation::BARE,
fmt: Format::Table,
};
let _ = muster(&client, &mut streams).await;
let envelope = tokio::time::timeout(FAKE_REPLY_WAIT, envelopes.recv())
.await
.expect("the fake daemon must answer inside the bound")
.unwrap();
assert_eq!(envelope.body, Request::Muster);
assert_eq!(
envelope.deadline_ms,
Some(u64::try_from(START_DEADLINE.as_millis()).unwrap())
);
}
#[tokio::test]
async fn a_muster_that_restored_nothing_says_so_on_stderr() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("s.sock");
let (client, daemon) = fake_client_on(&path).await;
daemon.queue_reply_then_event(
Response::Mustered(Vec::new()),
BusEvent::Process {
event: ProcessEventKind::Online,
info: sample_info(),
manually: true,
at_ms: 0,
},
);
let mut out = Vec::new();
let mut err = Vec::new();
let code = {
let mut streams = Streams {
out: &mut out,
err: &mut err,
style: crate::style::Presentation::BARE,
fmt: Format::Table,
};
muster(&client, &mut streams).await
};
assert_eq!(code, ExitCode::Success, "an empty roll is not a failure");
assert!(
!err.is_empty(),
"an empty muster must not be a silent success"
);
}
}