shadow_crypt_shell/encryption/
cli.rs

1use clap::{Arg, Command};
2use shadow_crypt_core::profile::SecurityProfile;
3
4use crate::errors::{WorkflowError, WorkflowResult};
5
6/// Encryption CLI arguments structure
7#[derive(Debug, Clone)]
8pub struct CliArgs {
9    pub input_files: Vec<String>,
10    pub test_mode: bool, // If true, use SecurityProfile::Test
11}
12
13/// Parse encryption command line arguments
14pub fn get_cli_args(args: Vec<String>) -> WorkflowResult<CliArgs> {
15    let cmd = || {
16        Command::new("shadow")
17        .about("Encrypt files using shadow format")
18        .arg(
19            Arg::new("files")
20                .help("Input files to encrypt")
21                .required(false)
22                .num_args(1..)
23                .value_name("FILE"),
24        )
25        .arg(
26            Arg::new("test_mode")
27                .long("test-mode")
28                .short('t')
29                .help("Use test security profile for faster key derivation (not recommended for production)")
30                .action(clap::ArgAction::SetTrue),
31        )
32    };
33    let matches = cmd()
34        .try_get_matches_from(&args)
35        .map_err(|e| WorkflowError::UserInput(e.to_string()))?;
36    let input_files: Vec<String> = matches
37        .get_many::<String>("files")
38        .unwrap_or_default()
39        .map(|s| s.to_string())
40        .collect();
41
42    if input_files.is_empty() {
43        return Err(WorkflowError::UserInput(
44            "No input files provided".to_string(),
45        ));
46    }
47
48    Ok(CliArgs {
49        input_files,
50        test_mode: matches.get_flag("test_mode"),
51    })
52}
53
54pub fn get_security_profile(test_mode: bool) -> SecurityProfile {
55    if test_mode {
56        SecurityProfile::Test
57    } else {
58        SecurityProfile::Production
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_get_security_profile_test_mode() {
68        match get_security_profile(true) {
69            SecurityProfile::Test => {}
70            _ => panic!("Expected Test profile"),
71        }
72    }
73
74    #[test]
75    fn test_get_security_profile_production() {
76        match get_security_profile(false) {
77            SecurityProfile::Production => {}
78            _ => panic!("Expected Production profile"),
79        }
80    }
81
82    #[test]
83    fn test_parse_cli_args_with_files() {
84        let args = vec![
85            "shadow".to_string(),
86            "file1.txt".to_string(),
87            "file2.txt".to_string(),
88        ];
89        let cli_args = get_cli_args(args).unwrap();
90        assert_eq!(
91            cli_args.input_files,
92            vec!["file1.txt".to_string(), "file2.txt".to_string()]
93        );
94        assert!(!cli_args.test_mode);
95    }
96
97    #[test]
98    fn test_parse_cli_args_with_test_mode() {
99        let args = vec![
100            "shadow".to_string(),
101            "--test-mode".to_string(),
102            "file1.txt".to_string(),
103        ];
104        let cli_args = get_cli_args(args).unwrap();
105        assert_eq!(cli_args.input_files, vec!["file1.txt".to_string()]);
106        assert!(cli_args.test_mode);
107    }
108
109    #[test]
110    fn test_parse_cli_args_no_files() {
111        let args = vec!["shadow".to_string()];
112        let result = get_cli_args(args);
113        assert!(result.is_err());
114        if let Err(WorkflowError::UserInput(msg)) = result {
115            assert_eq!(msg, "No input files provided");
116        } else {
117            panic!("Expected UserInput error");
118        }
119    }
120}