1use crate::cli::AcaoScp;
6use crate::erros::ErroSshCli;
7use crate::output;
8use crate::ssh::cliente::{ClienteSsh, ClienteSshTrait};
9use crate::vps;
10use std::path::PathBuf;
11
12pub async fn executar_scp(
14 acao: AcaoScp,
15 config_override: Option<PathBuf>,
16 password_override: Option<String>,
17) -> anyhow::Result<()> {
18 if crate::signals::cancelado() {
19 return Err(anyhow::anyhow!(crate::i18n::t(
20 crate::i18n::Mensagem::OperacaoCancelada
21 )));
22 }
23
24 match acao {
25 AcaoScp::Upload {
26 vps_nome,
27 local,
28 remote,
29 ..
30 } => {
31 let mut registro = vps::buscar_por_nome(config_override.clone(), &vps_nome)?
32 .ok_or_else(|| ErroSshCli::VpsNaoEncontrada(vps_nome.clone()))?;
33
34 if let Some(pwd) = password_override {
35 registro.senha = secrecy::SecretString::from(pwd);
36 }
37
38 let caminho = crate::vps::resolver_caminho_config(config_override.clone())?;
39 let cfg = crate::vps::construir_configuracao(®istro, Some(&caminho), false);
40
41 let cliente: Box<dyn ClienteSshTrait> =
42 <ClienteSsh as ClienteSshTrait>::conectar(cfg).await?;
43 executar_scp_upload_with_client(®istro, &local, &remote, cliente).await?;
44 }
45 AcaoScp::Download {
46 vps_nome,
47 remote,
48 local,
49 ..
50 } => {
51 let mut registro = vps::buscar_por_nome(config_override.clone(), &vps_nome)?
52 .ok_or_else(|| ErroSshCli::VpsNaoEncontrada(vps_nome.clone()))?;
53
54 if let Some(pwd) = password_override {
55 registro.senha = secrecy::SecretString::from(pwd);
56 }
57
58 let caminho = crate::vps::resolver_caminho_config(config_override.clone())?;
59 let cfg = crate::vps::construir_configuracao(®istro, Some(&caminho), false);
60
61 let cliente: Box<dyn ClienteSshTrait> =
62 <ClienteSsh as ClienteSshTrait>::conectar(cfg).await?;
63 executar_scp_download_with_client(®istro, &remote, &local, cliente).await?;
64 }
65 }
66 Ok(())
67}
68
69pub async fn executar_scp_upload_with_client(
71 _registro: &crate::vps::modelo::VpsRegistro,
72 local: &std::path::Path,
73 remote: &std::path::Path,
74 mut cliente: Box<dyn ClienteSshTrait>,
75) -> anyhow::Result<()> {
76 let resultado = cliente.upload(local, remote).await?;
77 cliente.desconectar().await?;
78 output::imprimir_sucesso(&format!(
79 "Upload concluído: {} bytes em {}ms",
80 resultado.bytes_transferidos, resultado.duracao_ms
81 ));
82 Ok(())
83}
84
85pub async fn executar_scp_download_with_client(
87 _registro: &crate::vps::modelo::VpsRegistro,
88 remote: &std::path::Path,
89 local: &std::path::Path,
90 mut cliente: Box<dyn ClienteSshTrait>,
91) -> anyhow::Result<()> {
92 let resultado = cliente.download(remote, local).await?;
93 cliente.desconectar().await?;
94 output::imprimir_sucesso(&format!(
95 "Download concluído: {} bytes em {}ms",
96 resultado.bytes_transferidos, resultado.duracao_ms
97 ));
98 Ok(())
99}
100
101#[cfg(test)]
102mod testes {
103 use super::*;
104 use crate::erros::ErroSshCli;
105 use crate::ssh::cliente::{
106 CanalTunel, ConfiguracaoConexao, SaidaExecucao, TransferenciaResultado,
107 };
108 use crate::vps::modelo::{VpsRegistro, SCHEMA_VERSION_ATUAL};
109 use crate::vps::{self, ArquivoConfig};
110 use async_trait::async_trait;
111 use secrecy::SecretString;
112 use serial_test::serial;
113 use std::collections::BTreeMap;
114 use std::path::Path;
115 use tempfile::TempDir;
116
117 struct ClienteFakeScp {
118 upload_ok: bool,
119 download_ok: bool,
120 bytes_upload: u64,
121 bytes_download: u64,
122 }
123
124 #[async_trait]
125 impl ClienteSshTrait for ClienteFakeScp {
126 async fn conectar(_cfg: ConfiguracaoConexao) -> Result<Box<Self>, ErroSshCli> {
127 Err(ErroSshCli::ConexaoFalhou(
128 "não implementado em teste".to_string(),
129 ))
130 }
131
132 async fn executar_comando(
133 &mut self,
134 _cmd: &str,
135 _max_chars: usize,
136 ) -> Result<SaidaExecucao, ErroSshCli> {
137 Err(ErroSshCli::CanalFalhou(
138 "não implementado em teste".to_string(),
139 ))
140 }
141
142 async fn upload(
143 &mut self,
144 _local: &Path,
145 _remote: &Path,
146 ) -> Result<TransferenciaResultado, ErroSshCli> {
147 if self.upload_ok {
148 Ok(TransferenciaResultado {
149 bytes_transferidos: self.bytes_upload,
150 duracao_ms: 10,
151 })
152 } else {
153 Err(ErroSshCli::CanalFalhou("upload falhou".to_string()))
154 }
155 }
156
157 async fn download(
158 &mut self,
159 _remote: &Path,
160 _local: &Path,
161 ) -> Result<TransferenciaResultado, ErroSshCli> {
162 if self.download_ok {
163 Ok(TransferenciaResultado {
164 bytes_transferidos: self.bytes_download,
165 duracao_ms: 20,
166 })
167 } else {
168 Err(ErroSshCli::CanalFalhou("download falhou".to_string()))
169 }
170 }
171
172 async fn abrir_canal_tunel(
173 &self,
174 _host_remoto: &str,
175 _porta_remota: u16,
176 _endereco_origem: &str,
177 _porta_origem: u16,
178 ) -> Result<Box<dyn CanalTunel>, ErroSshCli> {
179 Err(ErroSshCli::CanalFalhou(
180 "não implementado em teste".to_string(),
181 ))
182 }
183
184 async fn desconectar(&self) -> Result<(), ErroSshCli> {
185 Ok(())
186 }
187 }
188
189 fn registro_teste(nome: &str) -> VpsRegistro {
190 VpsRegistro::novo(
191 nome.to_string(),
192 "127.0.0.1".to_string(),
193 1,
194 "root".to_string(),
195 SecretString::from("senha-teste".to_string()),
196 None,
197 None,
198 Some(100),
199 Some(1000),
200 Some(1000),
201 None,
202 None,
203 false,
204 )
205 }
206
207 fn salvar_config_com_vps(tmp: &TempDir, nome: &str) {
208 let mut hosts = BTreeMap::new();
209 hosts.insert(nome.to_string(), registro_teste(nome));
210 let arquivo = ArquivoConfig {
211 schema_version: SCHEMA_VERSION_ATUAL,
212 hosts,
213 };
214 let caminho = tmp.path().join("config.toml");
215 vps::salvar(&caminho, &arquivo).expect("salvar config teste");
216 }
217
218 #[tokio::test]
219 async fn executar_scp_upload_with_client_retorna_ok() {
220 let cliente = ClienteFakeScp {
221 upload_ok: true,
222 download_ok: true,
223 bytes_upload: 128,
224 bytes_download: 0,
225 };
226 let registro = registro_teste("vps-a");
227
228 let resultado = executar_scp_upload_with_client(
229 ®istro,
230 Path::new("/tmp/local.txt"),
231 Path::new("/tmp/remote.txt"),
232 Box::new(cliente),
233 )
234 .await;
235
236 assert!(resultado.is_ok());
237 }
238
239 #[tokio::test]
240 async fn executar_scp_download_with_client_retorna_ok() {
241 let cliente = ClienteFakeScp {
242 upload_ok: true,
243 download_ok: true,
244 bytes_upload: 0,
245 bytes_download: 256,
246 };
247 let registro = registro_teste("vps-b");
248
249 let resultado = executar_scp_download_with_client(
250 ®istro,
251 Path::new("/tmp/remote.txt"),
252 Path::new("/tmp/local.txt"),
253 Box::new(cliente),
254 )
255 .await;
256
257 assert!(resultado.is_ok());
258 }
259
260 #[tokio::test]
261 async fn executar_scp_upload_with_client_retorna_erro() {
262 let cliente = ClienteFakeScp {
263 upload_ok: false,
264 download_ok: true,
265 bytes_upload: 0,
266 bytes_download: 0,
267 };
268 let registro = registro_teste("vps-c");
269
270 let resultado = executar_scp_upload_with_client(
271 ®istro,
272 Path::new("/tmp/local.txt"),
273 Path::new("/tmp/remote.txt"),
274 Box::new(cliente),
275 )
276 .await;
277
278 assert!(resultado.is_err());
279 }
280
281 #[tokio::test]
282 async fn executar_scp_download_with_client_retorna_erro() {
283 let cliente = ClienteFakeScp {
284 upload_ok: true,
285 download_ok: false,
286 bytes_upload: 0,
287 bytes_download: 0,
288 };
289 let registro = registro_teste("vps-d");
290
291 let resultado = executar_scp_download_with_client(
292 ®istro,
293 Path::new("/tmp/remote.txt"),
294 Path::new("/tmp/local.txt"),
295 Box::new(cliente),
296 )
297 .await;
298
299 assert!(resultado.is_err());
300 }
301
302 #[tokio::test]
303 #[serial]
304 async fn executar_scp_upload_tenta_conectar_quando_vps_existe() {
305 let tmp = TempDir::new().expect("tempdir");
306 salvar_config_com_vps(&tmp, "vps-upload");
307
308 let resultado = executar_scp(
309 AcaoScp::Upload {
310 vps_nome: "vps-upload".to_string(),
311 local: tmp.path().join("arquivo-local.txt"),
312 remote: PathBuf::from("/tmp/arquivo-remoto.txt"),
313 password: None,
314 },
315 Some(tmp.path().to_path_buf()),
316 None,
317 )
318 .await;
319
320 assert!(resultado.is_err());
321 }
322
323 #[tokio::test]
324 #[serial]
325 async fn executar_scp_download_tenta_conectar_quando_vps_existe() {
326 let tmp = TempDir::new().expect("tempdir");
327 salvar_config_com_vps(&tmp, "vps-download");
328
329 let resultado = executar_scp(
330 AcaoScp::Download {
331 vps_nome: "vps-download".to_string(),
332 remote: PathBuf::from("/tmp/arquivo-remoto.txt"),
333 local: tmp.path().join("arquivo-local.txt"),
334 password: None,
335 },
336 Some(tmp.path().to_path_buf()),
337 None,
338 )
339 .await;
340
341 assert!(resultado.is_err());
342 }
343}