command_stream/commands/
test.rs1use crate::commands::CommandContext;
4use crate::utils::CommandResult;
5use std::fs;
6use std::path::{Path, PathBuf};
7
8pub async fn test(ctx: CommandContext) -> CommandResult {
12 if ctx.args.is_empty() {
13 return CommandResult::error_with_code("", 1);
14 }
15
16 let cwd = ctx.get_cwd();
17 let result = evaluate_expression(&ctx.args, &cwd);
18
19 if result {
20 CommandResult::success_empty()
21 } else {
22 CommandResult::error_with_code("", 1)
23 }
24}
25
26fn evaluate_expression(args: &[String], cwd: &Path) -> bool {
27 if args.is_empty() {
28 return false;
29 }
30
31 if args.len() == 2 {
33 let op = &args[0];
34 let arg = &args[1];
35 let resolved_arg = resolve_path(arg, cwd);
36
37 return match op.as_str() {
38 "-e" => resolved_arg.exists(),
39 "-f" => resolved_arg.is_file(),
40 "-d" => resolved_arg.is_dir(),
41 "-r" => {
42 fs::metadata(&resolved_arg).is_ok()
44 }
45 "-w" => {
46 fs::metadata(&resolved_arg)
48 .map(|m| !m.permissions().readonly())
49 .unwrap_or(false)
50 }
51 "-x" => {
52 #[cfg(unix)]
54 {
55 use std::os::unix::fs::PermissionsExt;
56 fs::metadata(&resolved_arg)
57 .map(|m| m.permissions().mode() & 0o111 != 0)
58 .unwrap_or(false)
59 }
60 #[cfg(not(unix))]
61 {
62 resolved_arg.exists()
63 }
64 }
65 "-s" => {
66 fs::metadata(&resolved_arg)
68 .map(|m| m.len() > 0)
69 .unwrap_or(false)
70 }
71 "-z" => arg.is_empty(),
72 "-n" => !arg.is_empty(),
73 "!" => !evaluate_expression(&args[1..], cwd),
74 _ => false,
75 };
76 }
77
78 if args.len() == 3 {
80 let left = &args[0];
81 let op = &args[1];
82 let right = &args[2];
83
84 return match op.as_str() {
85 "=" | "==" => left == right,
86 "!=" => left != right,
87 "-eq" => {
88 let l: i64 = left.parse().unwrap_or(0);
89 let r: i64 = right.parse().unwrap_or(0);
90 l == r
91 }
92 "-ne" => {
93 let l: i64 = left.parse().unwrap_or(0);
94 let r: i64 = right.parse().unwrap_or(0);
95 l != r
96 }
97 "-lt" => {
98 let l: i64 = left.parse().unwrap_or(0);
99 let r: i64 = right.parse().unwrap_or(0);
100 l < r
101 }
102 "-le" => {
103 let l: i64 = left.parse().unwrap_or(0);
104 let r: i64 = right.parse().unwrap_or(0);
105 l <= r
106 }
107 "-gt" => {
108 let l: i64 = left.parse().unwrap_or(0);
109 let r: i64 = right.parse().unwrap_or(0);
110 l > r
111 }
112 "-ge" => {
113 let l: i64 = left.parse().unwrap_or(0);
114 let r: i64 = right.parse().unwrap_or(0);
115 l >= r
116 }
117 _ => false,
118 };
119 }
120
121 if args.len() == 1 {
123 return !args[0].is_empty();
124 }
125
126 false
127}
128
129fn resolve_path(arg: &str, cwd: &Path) -> PathBuf {
130 let path = Path::new(arg);
131 if path.is_absolute() {
132 path.to_path_buf()
133 } else {
134 cwd.join(path)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use tempfile::tempdir;
142
143 #[tokio::test]
144 async fn test_file_exists() {
145 let temp = tempdir().unwrap();
146 let file = temp.path().join("test.txt");
147 fs::write(&file, "test").unwrap();
148
149 let ctx = CommandContext::new(vec!["-e".to_string(), file.to_string_lossy().to_string()]);
150 let result = test(ctx).await;
151 assert!(result.is_success());
152 }
153
154 #[tokio::test]
155 async fn test_file_not_exists() {
156 let ctx = CommandContext::new(vec![
157 "-e".to_string(),
158 "/nonexistent/file/12345".to_string(),
159 ]);
160 let result = test(ctx).await;
161 assert!(!result.is_success());
162 }
163
164 #[tokio::test]
165 async fn test_string_equality() {
166 let ctx = CommandContext::new(vec![
167 "hello".to_string(),
168 "=".to_string(),
169 "hello".to_string(),
170 ]);
171 let result = test(ctx).await;
172 assert!(result.is_success());
173 }
174
175 #[tokio::test]
176 async fn test_string_inequality() {
177 let ctx = CommandContext::new(vec![
178 "hello".to_string(),
179 "!=".to_string(),
180 "world".to_string(),
181 ]);
182 let result = test(ctx).await;
183 assert!(result.is_success());
184 }
185
186 #[tokio::test]
187 async fn test_numeric_comparison() {
188 let ctx = CommandContext::new(vec!["5".to_string(), "-gt".to_string(), "3".to_string()]);
189 let result = test(ctx).await;
190 assert!(result.is_success());
191 }
192
193 #[tokio::test]
194 async fn test_empty_string() {
195 let ctx = CommandContext::new(vec!["-z".to_string(), "".to_string()]);
196 let result = test(ctx).await;
197 assert!(result.is_success());
198 }
199
200 #[tokio::test]
201 async fn test_non_empty_string() {
202 let ctx = CommandContext::new(vec!["-n".to_string(), "hello".to_string()]);
203 let result = test(ctx).await;
204 assert!(result.is_success());
205 }
206}