dtfilter.rs (7247B)
1 use assert_cmd::Command; 2 use predicates::prelude::*; 3 use std::io::Write; 4 use tempfile::NamedTempFile; 5 6 fn dtfilter() -> Command { 7 Command::cargo_bin("dtfilter").unwrap() 8 } 9 10 fn csv_file(content: &str) -> NamedTempFile { 11 let mut f = NamedTempFile::with_suffix(".csv").unwrap(); 12 write!(f, "{}", content).unwrap(); 13 f.flush().unwrap(); 14 f 15 } 16 17 const DATA: &str = "name,value\nAlice,100\nBob,200\nCharlie,300\n"; 18 19 // ─── Equality ─── 20 21 #[test] 22 fn filter_eq() { 23 let f = csv_file(DATA); 24 dtfilter().arg(f.path()).arg("--filter").arg("name=Alice").assert().success() 25 .stdout(predicate::str::contains("Alice")) 26 .stdout(predicate::str::contains("Bob").not()); 27 } 28 29 #[test] 30 fn filter_neq() { 31 let f = csv_file(DATA); 32 dtfilter().arg(f.path()).arg("--filter").arg("name!=Alice").assert().success() 33 .stdout(predicate::str::contains("Bob")) 34 .stdout(predicate::str::contains("Charlie")) 35 .stdout(predicate::str::contains("Alice").not()); 36 } 37 38 // ─── Numeric comparisons ─── 39 40 #[test] 41 fn filter_gt() { 42 let f = csv_file(DATA); 43 dtfilter().arg(f.path()).arg("--filter").arg("value>150").assert().success() 44 .stdout(predicate::str::contains("Bob")) 45 .stdout(predicate::str::contains("Charlie")) 46 .stdout(predicate::str::contains("Alice").not()); 47 } 48 49 #[test] 50 fn filter_lt() { 51 let f = csv_file(DATA); 52 dtfilter().arg(f.path()).arg("--filter").arg("value<200").assert().success() 53 .stdout(predicate::str::contains("Alice")) 54 .stdout(predicate::str::contains("Bob").not()); 55 } 56 57 #[test] 58 fn filter_gte() { 59 let f = csv_file(DATA); 60 dtfilter().arg(f.path()).arg("--filter").arg("value>=200").assert().success() 61 .stdout(predicate::str::contains("Bob")) 62 .stdout(predicate::str::contains("Charlie")) 63 .stdout(predicate::str::contains("Alice").not()); 64 } 65 66 #[test] 67 fn filter_lte() { 68 let f = csv_file(DATA); 69 dtfilter().arg(f.path()).arg("--filter").arg("value<=200").assert().success() 70 .stdout(predicate::str::contains("Alice")) 71 .stdout(predicate::str::contains("Bob")) 72 .stdout(predicate::str::contains("Charlie").not()); 73 } 74 75 // ─── String matching ─── 76 77 #[test] 78 fn filter_contains() { 79 let f = csv_file(DATA); 80 dtfilter().arg(f.path()).arg("--filter").arg("name~ob").assert().success() 81 .stdout(predicate::str::contains("Bob")) 82 .stdout(predicate::str::contains("Alice").not()); 83 } 84 85 #[test] 86 fn filter_not_contains() { 87 let f = csv_file(DATA); 88 dtfilter().arg(f.path()).arg("--filter").arg("name!~ob").assert().success() 89 .stdout(predicate::str::contains("Alice")) 90 .stdout(predicate::str::contains("Charlie")) 91 .stdout(predicate::str::contains("Bob").not()); 92 } 93 94 // ─── Multiple filters (AND) ─── 95 96 #[test] 97 fn multiple_filters_and() { 98 let f = csv_file(DATA); 99 dtfilter().arg(f.path()) 100 .arg("--filter").arg("value>=200") 101 .arg("--filter").arg("value<=300") 102 .assert().success() 103 .stdout(predicate::str::contains("Bob")) 104 .stdout(predicate::str::contains("Charlie")) 105 .stdout(predicate::str::contains("Alice").not()); 106 } 107 108 // ─── Sort ─── 109 110 #[test] 111 fn sort_desc() { 112 let f = csv_file(DATA); 113 let out = dtfilter().arg(f.path()).arg("--sort").arg("value:desc") 114 .assert().success(); 115 let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap(); 116 let charlie_pos = stdout.find("Charlie").unwrap(); 117 let alice_pos = stdout.find("Alice").unwrap(); 118 assert!(charlie_pos < alice_pos, "Charlie (300) should appear before Alice (100) in desc sort"); 119 } 120 121 #[test] 122 fn sort_asc() { 123 let f = csv_file(DATA); 124 let out = dtfilter().arg(f.path()).arg("--sort").arg("value") 125 .assert().success(); 126 let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap(); 127 let alice_pos = stdout.find("Alice").unwrap(); 128 let charlie_pos = stdout.find("Charlie").unwrap(); 129 assert!(alice_pos < charlie_pos, "Alice (100) should appear before Charlie (300) in asc sort"); 130 } 131 132 // ─── Column selection ─── 133 134 #[test] 135 fn columns_select() { 136 let f = csv_file("name,value,extra\nAlice,100,x\n"); 137 dtfilter().arg(f.path()).arg("--columns").arg("name,value").assert().success() 138 .stdout(predicate::str::contains("name")) 139 .stdout(predicate::str::contains("extra").not()); 140 } 141 142 // ─── Limit ─── 143 144 #[test] 145 fn limit_output() { 146 let f = csv_file(DATA); 147 dtfilter().arg(f.path()).arg("--sort").arg("value:desc").arg("--limit").arg("1") 148 .assert().success() 149 .stdout(predicate::str::contains("Charlie")) 150 .stdout(predicate::str::contains("Alice").not()); 151 } 152 153 // ─── Output format ─── 154 155 #[test] 156 fn csv_output() { 157 let f = csv_file("name,value\nAlice,100\n"); 158 dtfilter().arg(f.path()).arg("--csv").assert().success() 159 .stdout(predicate::str::contains("name,value")); 160 } 161 162 // ─── Windowing ─── 163 164 #[test] 165 fn head_before_filter() { 166 let f = csv_file("name,value\nAlice,100\nBob,200\nCharlie,300\n"); 167 dtfilter().arg(f.path()).arg("--head").arg("2").arg("--filter").arg("value>150") 168 .assert().success() 169 .stdout(predicate::str::contains("Bob")) 170 .stdout(predicate::str::contains("Charlie").not()); 171 } 172 173 #[test] 174 fn head_tail_exclusive() { 175 let f = csv_file("x\n1\n2\n"); 176 dtfilter().arg(f.path()).arg("--head").arg("1").arg("--tail").arg("1") 177 .assert().code(2); 178 } 179 180 // ─── Excel ─── 181 182 #[test] 183 fn filter_excel() { 184 dtfilter().arg("demo/sales.xlsx").arg("--filter").arg("Region=East") 185 .assert().success() 186 .stdout(predicate::str::contains("East")) 187 .stdout(predicate::str::contains("West").not()); 188 } 189 190 // ─── Parquet ─── 191 192 #[test] 193 fn filter_parquet() { 194 dtfilter().arg("tests/fixtures/data.parquet").arg("--filter").arg("value>150") 195 .assert().success() 196 .stdout(predicate::str::contains("Bob")) 197 .stdout(predicate::str::contains("Charlie")) 198 .stdout(predicate::str::contains("Alice").not()); 199 } 200 201 // ─── Arrow/IPC ─── 202 203 #[test] 204 fn filter_arrow() { 205 dtfilter().arg("tests/fixtures/data.arrow").arg("--filter").arg("value>150") 206 .assert().success() 207 .stdout(predicate::str::contains("Bob")) 208 .stdout(predicate::str::contains("Charlie")) 209 .stdout(predicate::str::contains("Alice").not()); 210 } 211 212 // ─── JSON ─── 213 214 #[test] 215 fn filter_json() { 216 dtfilter().arg("tests/fixtures/data.json").arg("--filter").arg("name=Alice") 217 .assert().success() 218 .stdout(predicate::str::contains("Alice")) 219 .stdout(predicate::str::contains("Bob").not()); 220 } 221 222 // ─── NDJSON ─── 223 224 #[test] 225 fn filter_ndjson() { 226 dtfilter().arg("tests/fixtures/data.ndjson").arg("--filter").arg("name=Alice") 227 .assert().success() 228 .stdout(predicate::str::contains("Alice")) 229 .stdout(predicate::str::contains("Bob").not()); 230 } 231 232 // ─── Edge cases ─── 233 234 #[test] 235 fn filter_no_matches() { 236 let f = csv_file(DATA); 237 dtfilter().arg(f.path()).arg("--filter").arg("name=Nobody") 238 .assert().success() 239 .stderr(predicate::str::contains("0 rows")); 240 }