use async_trait::async_trait; use itertools::Itertools; use thrussh::ChannelId; use crate::{ command::{Command, CommandResult}, server::{ConnectionState, ThrusshSession}, }; #[derive(Debug, Clone)] pub struct Echo {} #[async_trait] impl Command for Echo { async fn new( _connection: &mut ConnectionState, params: &[String], channel: ChannelId, session: &mut S, ) -> CommandResult { let suffix = if session.redirected() { "" } else { "\n" }; session.data( channel, format!("{}{suffix}", params.iter().join(" ")).into(), ); CommandResult::Exit(0) } async fn stdin( self, _connection: &mut ConnectionState, _channel: ChannelId, _data: &[u8], _session: &mut S, ) -> CommandResult { CommandResult::Exit(0) } } #[cfg(test)] mod test { use mockall::predicate::always; use test_case::test_case; use crate::{ command::{echo::Echo, Command, CommandResult}, server::{ test::{fake_channel_id, predicate::eq_string}, ConnectionState, MockThrusshSession, }, }; #[test_case(&[], "\n"; "no parameters")] #[test_case(&["hello"], "hello\n"; "single parameter")] #[test_case(&["hello", "world"], "hello world\n"; "multiple parameters")] #[tokio::test] async fn test(params: &[&str], output: &'static str) { let mut session = MockThrusshSession::default(); session .expect_data() .once() .with(always(), eq_string(output)) .returning(|_, _| ()); session.expect_redirected().returning(|| false); let out = Echo::new( &mut ConnectionState::mock(), params .iter() .map(ToString::to_string) .collect::>() .as_slice(), fake_channel_id(), &mut session, ) .await; assert!(matches!(out, CommandResult::Exit(0)), "{out:?}"); } }