• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

veeso / termscp / 4730861037

pending completion
4730861037

Pull #164

github

GitHub
Merge 1dce36354 into efb30231e
Pull Request #164: bump remotefs-ssh to 0.1.5

5010 of 5343 relevant lines covered (93.77%)

15.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.08
/src/system/config_client.rs
1
//! ## ConfigClient
2✔
2
//!
3
//! `config_client` is the module which provides an API between the Config module and the system
4

5
// Locals
6
use crate::config::{
7
    params::{UserConfig, DEFAULT_NOTIFICATION_TRANSFER_THRESHOLD},
8
    serialization::{deserialize, serialize, SerializerError, SerializerErrorKind},
9
};
10
use crate::explorer::GroupDirs;
11
use crate::filetransfer::FileTransferProtocol;
12
// Ext
13
use std::fs::{create_dir, remove_file, File, OpenOptions};
14
use std::io::Write;
15
use std::path::{Path, PathBuf};
16
use std::str::FromStr;
17
use std::string::ToString;
18

19
// Types
20
pub type SshHost = (String, String, PathBuf); // 0: host, 1: username, 2: RSA key path
21

22
/// ConfigClient provides a high level API to communicate with the termscp configuration
23
pub struct ConfigClient {
24
    config: UserConfig,   // Configuration loaded
25
    config_path: PathBuf, // Configuration TOML Path
26
    ssh_key_dir: PathBuf, // SSH Key storage directory
27
    degraded: bool,       // Indicates the `ConfigClient` is working in degraded mode
28
}
29

30
impl ConfigClient {
31
    /// Instantiate a new `ConfigClient` with provided path
32
    pub fn new(config_path: &Path, ssh_key_dir: &Path) -> Result<Self, SerializerError> {
24✔
33
        // Initialize a default configuration
34
        let default_config: UserConfig = UserConfig::default();
24✔
35
        info!(
24✔
36
            "Setting up config client with config path {} and SSH key directory {}",
37
            config_path.display(),
×
38
            ssh_key_dir.display()
×
39
        );
40
        // Create client
41
        let mut client: ConfigClient = ConfigClient {
24✔
42
            config: default_config,
24✔
43
            config_path: PathBuf::from(config_path),
24✔
44
            ssh_key_dir: PathBuf::from(ssh_key_dir),
24✔
45
            degraded: false,
46
        };
47
        // If ssh key directory doesn't exist, create it
48
        if !ssh_key_dir.exists() {
24✔
49
            if let Err(err) = create_dir(ssh_key_dir) {
23✔
50
                error!("Failed to create SSH key dir: {}", err);
2✔
51
                return Err(SerializerError::new_ex(
2✔
52
                    SerializerErrorKind::Io,
2✔
53
                    format!(
2✔
54
                        "Could not create SSH key directory \"{}\": {}",
55
                        ssh_key_dir.display(),
2✔
56
                        err
57
                    ),
58
                ));
59
            }
23✔
60
            debug!("Created SSH key directory");
21✔
61
        }
62
        // If Config file doesn't exist, create it
63
        if !config_path.exists() {
22✔
64
            if let Err(err) = client.write_config() {
21✔
65
                error!("Couldn't create configuration file: {}", err);
×
66
                return Err(err);
×
67
            }
21✔
68
            debug!("Config file didn't exist; created file");
21✔
69
        } else {
70
            // otherwise Load configuration from file
71
            if let Err(err) = client.read_config() {
1✔
72
                error!("Couldn't read configuration file: {}", err);
×
73
                return Err(err);
×
74
            }
1✔
75
            debug!("Read configuration file");
1✔
76
        }
77
        Ok(client)
22✔
78
    }
24✔
79

80
    /// Instantiate a ConfigClient in degraded mode.
81
    /// When in degraded mode, the configuration in use will be the default configuration
82
    /// and the IO operation on configuration won't be available
83
    pub fn degraded() -> Self {
1✔
84
        Self {
1✔
85
            config: UserConfig::default(),
1✔
86
            config_path: PathBuf::default(),
1✔
87
            ssh_key_dir: PathBuf::default(),
1✔
88
            degraded: true,
89
        }
90
    }
1✔
91

92
    // Text editor
93

94
    /// Get text editor from configuration
95
    pub fn get_text_editor(&self) -> PathBuf {
2✔
96
        self.config.user_interface.text_editor.clone()
2✔
97
    }
2✔
98

99
    /// Set text editor path
100
    pub fn set_text_editor(&mut self, path: PathBuf) {
2✔
101
        self.config.user_interface.text_editor = path;
2✔
102
    }
2✔
103

104
    // Default protocol
105

106
    /// Get default protocol from configuration
107
    pub fn get_default_protocol(&self) -> FileTransferProtocol {
2✔
108
        match FileTransferProtocol::from_str(self.config.user_interface.default_protocol.as_str()) {
2✔
109
            Ok(p) => p,
2✔
110
            Err(_) => FileTransferProtocol::Sftp,
×
111
        }
112
    }
2✔
113

114
    /// Set default protocol to configuration
115
    pub fn set_default_protocol(&mut self, proto: FileTransferProtocol) {
2✔
116
        self.config.user_interface.default_protocol = proto.to_string();
2✔
117
    }
2✔
118

119
    /// Get value of `show_hidden_files`
120
    pub fn get_show_hidden_files(&self) -> bool {
1✔
121
        self.config.user_interface.show_hidden_files
1✔
122
    }
1✔
123

124
    /// Set new value for `show_hidden_files`
125
    pub fn set_show_hidden_files(&mut self, value: bool) {
1✔
126
        self.config.user_interface.show_hidden_files = value;
1✔
127
    }
1✔
128

129
    /// Get value of `check_for_updates`
130
    pub fn get_check_for_updates(&self) -> bool {
3✔
131
        self.config.user_interface.check_for_updates.unwrap_or(true)
3✔
132
    }
3✔
133

134
    /// Set new value for `check_for_updates`
135
    pub fn set_check_for_updates(&mut self, value: bool) {
2✔
136
        self.config.user_interface.check_for_updates = Some(value);
2✔
137
    }
2✔
138

139
    /// Get value of `prompt_on_file_replace`
140
    pub fn get_prompt_on_file_replace(&self) -> bool {
3✔
141
        self.config
3✔
142
            .user_interface
143
            .prompt_on_file_replace
144
            .unwrap_or(true)
145
    }
3✔
146

147
    /// Set new value for `prompt_on_file_replace`
148
    pub fn set_prompt_on_file_replace(&mut self, value: bool) {
2✔
149
        self.config.user_interface.prompt_on_file_replace = Some(value);
2✔
150
    }
2✔
151

152
    /// Get GroupDirs value from configuration (will be converted from string)
153
    pub fn get_group_dirs(&self) -> Option<GroupDirs> {
2✔
154
        // Convert string to `GroupDirs`
155
        match &self.config.user_interface.group_dirs {
2✔
156
            None => None,
1✔
157
            Some(val) => match GroupDirs::from_str(val.as_str()) {
1✔
158
                Ok(val) => Some(val),
1✔
159
                Err(_) => None,
×
160
            },
161
        }
162
    }
2✔
163

164
    /// Set value for group_dir in configuration.
165
    /// Provided value, if `Some` will be converted to `GroupDirs`
166
    pub fn set_group_dirs(&mut self, val: Option<GroupDirs>) {
2✔
167
        self.config.user_interface.group_dirs = val.map(|val| val.to_string());
3✔
168
    }
2✔
169

170
    /// Get current file fmt for local host
171
    pub fn get_local_file_fmt(&self) -> Option<String> {
3✔
172
        self.config.user_interface.file_fmt.clone()
3✔
173
    }
3✔
174

175
    /// Set file fmt parameter for local host
176
    pub fn set_local_file_fmt(&mut self, s: String) {
2✔
177
        self.config.user_interface.file_fmt = match s.is_empty() {
4✔
178
            true => None,
1✔
179
            false => Some(s),
1✔
180
        };
181
    }
2✔
182

183
    /// Get current file fmt for remote host
184
    pub fn get_remote_file_fmt(&self) -> Option<String> {
3✔
185
        self.config.user_interface.remote_file_fmt.clone()
3✔
186
    }
3✔
187

188
    /// Set file fmt parameter for remote host
189
    pub fn set_remote_file_fmt(&mut self, s: String) {
2✔
190
        self.config.user_interface.remote_file_fmt = match s.is_empty() {
4✔
191
            true => None,
1✔
192
            false => Some(s),
1✔
193
        };
194
    }
2✔
195

196
    /// Get value of `notifications`
197
    pub fn get_notifications(&self) -> bool {
3✔
198
        self.config.user_interface.notifications.unwrap_or(true)
3✔
199
    }
3✔
200

201
    /// Set new value for `notifications`
202
    pub fn set_notifications(&mut self, value: bool) {
2✔
203
        self.config.user_interface.notifications = Some(value);
2✔
204
    }
2✔
205

206
    /// Get value of `notification_threshold`
207
    pub fn get_notification_threshold(&self) -> u64 {
3✔
208
        self.config
3✔
209
            .user_interface
210
            .notification_threshold
211
            .unwrap_or(DEFAULT_NOTIFICATION_TRANSFER_THRESHOLD)
212
    }
3✔
213

214
    /// Set new value for `notification_threshold`
215
    pub fn set_notification_threshold(&mut self, value: u64) {
2✔
216
        self.config.user_interface.notification_threshold = Some(value);
2✔
217
    }
2✔
218

219
    // Remote params
220

221
    /// Get ssh config path
222
    pub fn get_ssh_config(&self) -> Option<&str> {
9✔
223
        self.config.remote.ssh_config.as_deref()
9✔
224
    }
9✔
225

226
    /// Set ssh config path
227
    pub fn set_ssh_config(&mut self, p: Option<String>) {
3✔
228
        self.config.remote.ssh_config = p;
3✔
229
    }
3✔
230

231
    // SSH Keys
232

233
    /// Save a SSH key into configuration.
234
    /// This operation also creates the key file in `ssh_key_dir`
235
    /// and also commits changes to configuration, to prevent incoerent data
236
    pub fn add_ssh_key(
4✔
237
        &mut self,
238
        host: &str,
239
        username: &str,
240
        ssh_key: &str,
241
    ) -> Result<(), SerializerError> {
242
        if self.degraded {
4✔
243
            return Err(SerializerError::new_ex(
1✔
244
                SerializerErrorKind::Generic,
1✔
245
                String::from("Configuration won't be saved, since in degraded mode"),
1✔
246
            ));
247
        }
248
        let host_name: String = Self::make_ssh_host_key(host, username);
3✔
249
        // Get key path
250
        let ssh_key_path: PathBuf = {
251
            let mut p: PathBuf = self.ssh_key_dir.clone();
3✔
252
            p.push(format!("{host_name}.key"));
3✔
253
            p
3✔
254
        };
255
        info!(
3✔
256
            "Writing SSH file to {} for host {}",
257
            ssh_key_path.display(),
×
258
            host_name
259
        );
260
        // Write key to file
261
        let mut f: File = match File::create(ssh_key_path.as_path()) {
3✔
262
            Ok(f) => f,
3✔
263
            Err(err) => return Self::make_io_err(err),
×
264
        };
265
        if let Err(err) = f.write_all(ssh_key.as_bytes()) {
3✔
266
            error!("Failed to write SSH key to file: {}", err);
×
267
            return Self::make_io_err(err);
×
268
        }
3✔
269
        // Add host to keys
270
        self.config.remote.ssh_keys.insert(host_name, ssh_key_path);
3✔
271
        // Write config
272
        self.write_config()
3✔
273
    }
4✔
274

275
    /// Delete a ssh key from configuration, using host as key.
276
    /// This operation also unlinks the key file in `ssh_key_dir`
277
    /// and also commits changes to configuration, to prevent incoerent data
278
    pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> {
2✔
279
        if self.degraded {
2✔
280
            return Err(SerializerError::new_ex(
1✔
281
                SerializerErrorKind::Generic,
1✔
282
                String::from("Configuration won't be saved, since in degraded mode"),
1✔
283
            ));
284
        }
285
        // Remove key from configuration and get key path
286
        info!("Removing key for {}@{}", host, username);
1✔
287
        let key_path: PathBuf = match self
2✔
288
            .config
289
            .remote
290
            .ssh_keys
291
            .remove(&Self::make_ssh_host_key(host, username))
1✔
292
        {
293
            Some(p) => p,
1✔
294
            None => return Ok(()), // Return ok if host doesn't exist
×
295
        };
1✔
296
        // Remove file
297
        if let Err(err) = remove_file(key_path.as_path()) {
1✔
298
            error!("Failed to remove key file {}: {}", key_path.display(), err);
×
299
            return Self::make_io_err(err);
×
300
        }
1✔
301
        // Commit changes to configuration
302
        self.write_config()
1✔
303
    }
2✔
304

305
    /// Get ssh key from host.
306
    /// None is returned if key doesn't exist
307
    /// `std::io::Error` is returned in case it was not possible to read the key file
308
    pub fn get_ssh_key(&self, mkey: &str) -> std::io::Result<Option<SshHost>> {
5✔
309
        if self.degraded {
5✔
310
            return Ok(None);
1✔
311
        }
312
        // Check if Key exists
313
        match self.config.remote.ssh_keys.get(mkey) {
4✔
314
            None => Ok(None),
1✔
315
            Some(key_path) => {
3✔
316
                // Get host and username
317
                let (host, username): (String, String) = Self::get_ssh_tokens(mkey);
3✔
318
                // Return key
319
                Ok(Some((host, username, PathBuf::from(key_path))))
3✔
320
            }
3✔
321
        }
322
    }
5✔
323

324
    /// Get an iterator through hosts in the ssh key storage
325
    pub fn iter_ssh_keys(&self) -> impl Iterator<Item = &String> + '_ {
9✔
326
        Box::new(self.config.remote.ssh_keys.keys())
9✔
327
    }
9✔
328

329
    // I/O
330

331
    /// Write configuration to file
332
    pub fn write_config(&self) -> Result<(), SerializerError> {
27✔
333
        if self.degraded {
27✔
334
            return Err(SerializerError::new_ex(
1✔
335
                SerializerErrorKind::Generic,
1✔
336
                String::from("Configuration won't be saved, since in degraded mode"),
1✔
337
            ));
338
        }
339
        // Open file
340
        match OpenOptions::new()
52✔
341
            .create(true)
342
            .write(true)
343
            .truncate(true)
344
            .open(self.config_path.as_path())
26✔
345
        {
346
            Ok(writer) => serialize(&self.config, Box::new(writer)),
26✔
347
            Err(err) => {
×
348
                error!("Failed to write configuration file: {}", err);
×
349
                Err(SerializerError::new_ex(
×
350
                    SerializerErrorKind::Io,
×
351
                    err.to_string(),
×
352
                ))
353
            }
×
354
        }
355
    }
27✔
356

357
    /// Read configuration from file (or reload it if already read)
358
    pub fn read_config(&mut self) -> Result<(), SerializerError> {
2✔
359
        if self.degraded {
2✔
360
            return Err(SerializerError::new_ex(
1✔
361
                SerializerErrorKind::Generic,
1✔
362
                String::from("Configuration won't be loaded, since in degraded mode"),
1✔
363
            ));
364
        }
365
        // Open bookmarks file for read
366
        match OpenOptions::new()
2✔
367
            .read(true)
368
            .open(self.config_path.as_path())
1✔
369
        {
370
            Ok(reader) => {
1✔
371
                // Deserialize
372
                match deserialize(Box::new(reader)) {
1✔
373
                    Ok(config) => {
1✔
374
                        self.config = config;
1✔
375
                        Ok(())
1✔
376
                    }
1✔
377
                    Err(err) => Err(err),
×
378
                }
379
            }
380
            Err(err) => {
×
381
                error!("Failed to read configuration: {}", err);
×
382
                Err(SerializerError::new_ex(
×
383
                    SerializerErrorKind::Io,
×
384
                    err.to_string(),
×
385
                ))
386
            }
×
387
        }
388
    }
2✔
389

390
    /// Hosts are saved as `username@host` into configuration.
391
    /// This method creates the key name, starting from host and username
392
    fn make_ssh_host_key(host: &str, username: &str) -> String {
5✔
393
        format!("{username}@{host}")
5✔
394
    }
5✔
395

396
    /// Get ssh tokens starting from ssh host key
397
    /// Panics if key has invalid syntax
398
    /// Returns: (host, username)
399
    fn get_ssh_tokens(host_key: &str) -> (String, String) {
4✔
400
        let tokens: Vec<&str> = host_key.split('@').collect();
4✔
401
        assert!(tokens.len() >= 2);
4✔
402
        (String::from(tokens[1]), String::from(tokens[0]))
4✔
403
    }
4✔
404

405
    /// Make serializer error from `std::io::Error`
406
    fn make_io_err(err: std::io::Error) -> Result<(), SerializerError> {
1✔
407
        Err(SerializerError::new_ex(
1✔
408
            SerializerErrorKind::Io,
1✔
409
            err.to_string(),
1✔
410
        ))
411
    }
1✔
412
}
413

414
#[cfg(test)]
415
mod tests {
416

417
    use super::*;
418
    use crate::config::UserConfig;
419
    use crate::utils::random::random_alphanumeric_with_len;
420

421
    use pretty_assertions::assert_eq;
422
    use std::io::Read;
423
    use tempfile::TempDir;
424

425
    #[test]
426
    fn test_system_config_new() {
2✔
427
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
428
        let (cfg_path, ssh_keys_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
429
        let client: ConfigClient = ConfigClient::new(cfg_path.as_path(), ssh_keys_path.as_path())
1✔
430
            .ok()
431
            .unwrap();
432
        // Verify parameters
433
        let default_config: UserConfig = UserConfig::default();
1✔
434
        assert_eq!(client.degraded, false);
1✔
435
        assert_eq!(client.config.remote.ssh_keys.len(), 0);
1✔
436
        assert_eq!(
1✔
437
            client.config.user_interface.default_protocol,
438
            default_config.user_interface.default_protocol
439
        );
440
        assert_eq!(
1✔
441
            client.config.user_interface.text_editor,
442
            default_config.user_interface.text_editor
443
        );
444
        assert_eq!(client.config_path, cfg_path);
1✔
445
        assert_eq!(client.ssh_key_dir, ssh_keys_path);
1✔
446
    }
2✔
447

448
    #[test]
449
    fn test_system_config_degraded() {
2✔
450
        let mut client: ConfigClient = ConfigClient::degraded();
1✔
451
        assert_eq!(client.degraded, true);
1✔
452
        assert_eq!(client.config_path, PathBuf::default());
1✔
453
        assert_eq!(client.ssh_key_dir, PathBuf::default());
1✔
454
        // I/O
455
        assert!(client.add_ssh_key("Omar", "omar", "omar").is_err());
1✔
456
        assert!(client.del_ssh_key("omar", "omar").is_err());
1✔
457
        assert!(client.get_ssh_key("omar").ok().unwrap().is_none());
1✔
458
        assert!(client.write_config().is_err());
1✔
459
        assert!(client.read_config().is_err());
1✔
460
    }
2✔
461

462
    #[test]
463
    fn test_system_config_new_err() {
2✔
464
        assert!(
1✔
465
            ConfigClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar"),)
1✔
466
                .is_err()
467
        );
468
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
469
        let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
470
        assert!(ConfigClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar")).is_err());
1✔
471
    }
2✔
472

473
    #[test]
474
    fn test_system_config_from_existing() {
2✔
475
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
476
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
477
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
478
            .ok()
479
            .unwrap();
480
        // Change some stuff
481
        client.set_text_editor(PathBuf::from("/usr/bin/vim"));
1✔
482
        client.set_default_protocol(FileTransferProtocol::Scp);
1✔
483
        assert!(client
1✔
484
            .add_ssh_key("192.168.1.31", "pi", "piroporopero")
485
            .is_ok());
486
        assert!(client.write_config().is_ok());
1✔
487
        // Istantiate a new client
488
        let client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
489
            .ok()
490
            .unwrap();
491
        // Verify client has updated parameters
492
        assert_eq!(client.get_default_protocol(), FileTransferProtocol::Scp);
1✔
493
        assert_eq!(client.get_text_editor(), PathBuf::from("/usr/bin/vim"));
1✔
494
        let mut expected_key_path: PathBuf = key_path;
1✔
495
        expected_key_path.push("pi@192.168.1.31.key");
1✔
496
        assert_eq!(
1✔
497
            client.get_ssh_key("pi@192.168.1.31").unwrap().unwrap(),
1✔
498
            (
1✔
499
                String::from("192.168.1.31"),
1✔
500
                String::from("pi"),
1✔
501
                expected_key_path,
1✔
502
            )
503
        );
504
    }
2✔
505

506
    #[test]
507
    fn test_system_config_text_editor() {
2✔
508
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
509
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
510
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
511
            .ok()
512
            .unwrap();
513
        client.set_text_editor(PathBuf::from("mcedit"));
1✔
514
        assert_eq!(client.get_text_editor(), PathBuf::from("mcedit"));
1✔
515
    }
2✔
516

517
    #[test]
518
    fn test_system_config_default_protocol() {
2✔
519
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
520
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
521
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
522
            .ok()
523
            .unwrap();
524
        client.set_default_protocol(FileTransferProtocol::Ftp(true));
1✔
525
        assert_eq!(
1✔
526
            client.get_default_protocol(),
1✔
527
            FileTransferProtocol::Ftp(true)
528
        );
529
    }
2✔
530

531
    #[test]
532
    fn test_system_config_show_hidden_files() {
2✔
533
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
534
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
535
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
536
            .ok()
537
            .unwrap();
538
        client.set_show_hidden_files(true);
1✔
539
        assert_eq!(client.get_show_hidden_files(), true);
1✔
540
    }
2✔
541

542
    #[test]
543
    fn test_system_config_check_for_updates() {
2✔
544
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
545
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
546
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
547
            .ok()
548
            .unwrap();
549
        assert_eq!(client.get_check_for_updates(), true); // Null ?
1✔
550
        client.set_check_for_updates(true);
1✔
551
        assert_eq!(client.get_check_for_updates(), true);
1✔
552
        client.set_check_for_updates(false);
1✔
553
        assert_eq!(client.get_check_for_updates(), false);
1✔
554
    }
2✔
555

556
    #[test]
557
    fn test_system_config_prompt_on_file_replace() {
2✔
558
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
559
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
560
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
561
            .ok()
562
            .unwrap();
563
        assert_eq!(client.get_prompt_on_file_replace(), true); // Null ?
1✔
564
        client.set_prompt_on_file_replace(true);
1✔
565
        assert_eq!(client.get_prompt_on_file_replace(), true);
1✔
566
        client.set_prompt_on_file_replace(false);
1✔
567
        assert_eq!(client.get_prompt_on_file_replace(), false);
1✔
568
    }
2✔
569

570
    #[test]
571
    fn test_system_config_group_dirs() {
2✔
572
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
573
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
574
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
575
            .ok()
576
            .unwrap();
577
        client.set_group_dirs(Some(GroupDirs::First));
1✔
578
        assert_eq!(client.get_group_dirs(), Some(GroupDirs::First),);
1✔
579
        client.set_group_dirs(None);
1✔
580
        assert_eq!(client.get_group_dirs(), None,);
1✔
581
    }
2✔
582

583
    #[test]
584
    fn test_system_config_local_file_fmt() {
2✔
585
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
586
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
587
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
588
            .ok()
589
            .unwrap();
590
        assert_eq!(client.get_local_file_fmt(), None);
1✔
591
        client.set_local_file_fmt(String::from("{NAME}"));
1✔
592
        assert_eq!(client.get_local_file_fmt().unwrap(), String::from("{NAME}"));
1✔
593
        // Delete
594
        client.set_local_file_fmt(String::from(""));
1✔
595
        assert_eq!(client.get_local_file_fmt(), None);
1✔
596
    }
2✔
597

598
    #[test]
599
    fn test_system_config_remote_file_fmt() {
2✔
600
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
601
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
602
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
603
            .ok()
604
            .unwrap();
605
        assert_eq!(client.get_remote_file_fmt(), None);
1✔
606
        client.set_remote_file_fmt(String::from("{NAME}"));
1✔
607
        assert_eq!(
1✔
608
            client.get_remote_file_fmt().unwrap(),
1✔
609
            String::from("{NAME}")
1✔
610
        );
611
        // Delete
612
        client.set_remote_file_fmt(String::from(""));
1✔
613
        assert_eq!(client.get_remote_file_fmt(), None);
1✔
614
    }
2✔
615

616
    #[test]
617
    fn test_system_config_notifications() {
2✔
618
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
619
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
620
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
621
            .ok()
622
            .unwrap();
623
        assert_eq!(client.get_notifications(), true); // Null ?
1✔
624
        client.set_notifications(true);
1✔
625
        assert_eq!(client.get_notifications(), true);
1✔
626
        client.set_notifications(false);
1✔
627
        assert_eq!(client.get_notifications(), false);
1✔
628
    }
2✔
629

630
    #[test]
631
    fn test_system_config_remote_notification_threshold() {
2✔
632
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
633
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
634
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
635
            .ok()
636
            .unwrap();
637
        assert_eq!(
1✔
638
            client.get_notification_threshold(),
1✔
639
            DEFAULT_NOTIFICATION_TRANSFER_THRESHOLD
640
        ); // Null ?
641
        client.set_notification_threshold(1024);
1✔
642
        assert_eq!(client.get_notification_threshold(), 1024);
1✔
643
        client.set_notification_threshold(64);
1✔
644
        assert_eq!(client.get_notification_threshold(), 64);
1✔
645
    }
2✔
646

647
    #[test]
648
    fn should_get_and_set_ssh_config_dir() {
2✔
649
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
650
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
651
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
652
            .ok()
653
            .unwrap();
654

655
        let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/root"));
1✔
656
        let mut ssh_config_path = "~/.ssh/config".to_string();
1✔
657
        ssh_config_path = ssh_config_path.replacen('~', &home_dir.to_string_lossy(), 1);
1✔
658

659
        let ssh_config_path = if PathBuf::from(&ssh_config_path).exists() {
1✔
660
            Some(ssh_config_path)
×
661
        } else {
662
            None
1✔
663
        };
664

665
        assert_eq!(client.get_ssh_config(), ssh_config_path.as_deref()); // Null ?
1✔
666
        client.set_ssh_config(Some(String::from("/tmp/ssh_config")));
1✔
667
        assert_eq!(client.get_ssh_config(), Some("/tmp/ssh_config"));
1✔
668
        client.set_ssh_config(None);
1✔
669
        assert_eq!(client.get_ssh_config(), None);
1✔
670
    }
2✔
671

672
    #[test]
673
    fn test_system_config_ssh_keys() {
2✔
674
        let tmp_dir: TempDir = TempDir::new().ok().unwrap();
1✔
675
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
676
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
677
            .ok()
678
            .unwrap();
679
        // Add a new key
680
        let rsa_key: String = get_sample_rsa_key();
1✔
681
        assert!(client
1✔
682
            .add_ssh_key("192.168.1.31", "pi", rsa_key.as_str())
1✔
683
            .is_ok());
684
        // Iterate keys
685
        for key in client.iter_ssh_keys() {
2✔
686
            let host: SshHost = client.get_ssh_key(key).ok().unwrap().unwrap();
1✔
687
            assert_eq!(host.0, String::from("192.168.1.31"));
1✔
688
            assert_eq!(host.1, String::from("pi"));
1✔
689
            let mut expected_key_path: PathBuf = key_path.clone();
1✔
690
            expected_key_path.push("pi@192.168.1.31.key");
1✔
691
            assert_eq!(host.2, expected_key_path);
1✔
692
            // Read rsa key
693
            let mut key_file: File = File::open(expected_key_path.as_path()).ok().unwrap();
1✔
694
            // Read
695
            let mut key: String = String::new();
1✔
696
            assert!(key_file.read_to_string(&mut key).is_ok());
1✔
697
            // Verify rsa key
698
            assert_eq!(key, rsa_key);
1✔
699
        }
1✔
700
        // Unexisting key
701
        assert!(client.get_ssh_key("test").ok().unwrap().is_none());
1✔
702
        // Delete key
703
        assert!(client.del_ssh_key("192.168.1.31", "pi").is_ok());
1✔
704
    }
2✔
705

706
    #[test]
707
    fn test_system_config_make_key() {
2✔
708
        assert_eq!(
1✔
709
            ConfigClient::make_ssh_host_key("192.168.1.31", "pi"),
1✔
710
            String::from("pi@192.168.1.31")
1✔
711
        );
712
        assert_eq!(
1✔
713
            ConfigClient::get_ssh_tokens("pi@192.168.1.31"),
1✔
714
            (String::from("192.168.1.31"), String::from("pi"))
1✔
715
        );
716
    }
2✔
717

718
    #[test]
719
    fn test_system_config_make_io_err() {
2✔
720
        let err: SerializerError =
721
            ConfigClient::make_io_err(std::io::Error::from(std::io::ErrorKind::PermissionDenied))
1✔
722
                .err()
723
                .unwrap();
724
        assert_eq!(err.to_string(), "IO error (permission denied)");
1✔
725
    }
2✔
726

727
    /// Get paths for configuration and keys directory
728
    fn get_paths(dir: &Path) -> (PathBuf, PathBuf) {
15✔
729
        let mut k: PathBuf = PathBuf::from(dir);
15✔
730
        let mut c: PathBuf = k.clone();
15✔
731
        k.push("ssh-keys/");
15✔
732
        c.push("config.toml");
15✔
733
        (c, k)
15✔
734
    }
15✔
735

736
    fn get_sample_rsa_key() -> String {
1✔
737
        format!(
1✔
738
            "-----BEGIN OPENSSH PRIVATE KEY-----\n{}\n-----END OPENSSH PRIVATE KEY-----",
739
            random_alphanumeric_with_len(2536)
1✔
740
        )
741
    }
1✔
742
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc