dtdiff.rs (6176B)
1 use assert_cmd::Command; 2 use predicates::prelude::*; 3 use std::io::Write; 4 use tempfile::NamedTempFile; 5 6 fn dtdiff() -> Command { 7 Command::cargo_bin("dtdiff").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 // ─── Positional mode ─── 18 19 #[test] 20 fn no_diff_exits_0() { 21 let a = csv_file("name,value\nAlice,100\n"); 22 let b = csv_file("name,value\nAlice,100\n"); 23 dtdiff().arg(a.path()).arg(b.path()).assert().success() 24 .stdout(predicate::str::contains("No differences")); 25 } 26 27 #[test] 28 fn positional_diff_exits_1() { 29 let a = csv_file("name,value\nAlice,100\n"); 30 let b = csv_file("name,value\nBob,200\n"); 31 dtdiff().arg(a.path()).arg(b.path()).assert().code(1); 32 } 33 34 #[test] 35 fn positional_added_row() { 36 let a = csv_file("name,value\nAlice,100\n"); 37 let b = csv_file("name,value\nAlice,100\nBob,200\n"); 38 dtdiff().arg(a.path()).arg(b.path()).assert().code(1) 39 .stdout(predicate::str::contains("Added: 1")); 40 } 41 42 #[test] 43 fn positional_removed_row() { 44 let a = csv_file("name,value\nAlice,100\nBob,200\n"); 45 let b = csv_file("name,value\nAlice,100\n"); 46 dtdiff().arg(a.path()).arg(b.path()).assert().code(1) 47 .stdout(predicate::str::contains("Removed: 1")); 48 } 49 50 // ─── Key-based mode ─── 51 52 #[test] 53 fn keyed_diff_modified() { 54 let a = csv_file("id,name\n1,Alice\n2,Bob\n"); 55 let b = csv_file("id,name\n1,Alice\n2,Robert\n"); 56 dtdiff().arg(a.path()).arg(b.path()).arg("--key").arg("id") 57 .assert().code(1) 58 .stdout(predicate::str::contains("Modified: 1")) 59 .stdout(predicate::str::contains("Bob")); 60 } 61 62 #[test] 63 fn keyed_diff_added_and_removed() { 64 let a = csv_file("id,name\n1,Alice\n2,Bob\n"); 65 let b = csv_file("id,name\n1,Alice\n3,Charlie\n"); 66 dtdiff().arg(a.path()).arg(b.path()).arg("--key").arg("id") 67 .assert().code(1) 68 .stdout(predicate::str::contains("Added: 1")) 69 .stdout(predicate::str::contains("Removed: 1")); 70 } 71 72 #[test] 73 fn keyed_no_diff() { 74 let a = csv_file("id,name\n1,Alice\n2,Bob\n"); 75 let b = csv_file("id,name\n2,Bob\n1,Alice\n"); 76 dtdiff().arg(a.path()).arg(b.path()).arg("--key").arg("id") 77 .assert().success() 78 .stdout(predicate::str::contains("No differences")); 79 } 80 81 // ─── Composite keys ─── 82 83 #[test] 84 fn composite_key() { 85 let a = csv_file("date,ticker,price\n2024-01-01,AAPL,150\n2024-01-01,GOOG,140\n"); 86 let b = csv_file("date,ticker,price\n2024-01-01,AAPL,150\n2024-01-01,GOOG,145\n"); 87 dtdiff().arg(a.path()).arg(b.path()).arg("--key").arg("date,ticker") 88 .assert().code(1) 89 .stdout(predicate::str::contains("Modified: 1")) 90 .stdout(predicate::str::contains("GOOG")); 91 } 92 93 // ─── Float tolerance ─── 94 95 #[test] 96 fn tolerance_suppresses_small_diff() { 97 let a = csv_file("id,price\n1,150.000\n"); 98 let b = csv_file("id,price\n1,150.005\n"); 99 dtdiff().arg(a.path()).arg(b.path()).arg("--key").arg("id").arg("--tolerance").arg("0.01") 100 .assert().success() 101 .stdout(predicate::str::contains("No differences")); 102 } 103 104 #[test] 105 fn tolerance_reports_large_diff() { 106 let a = csv_file("id,price\n1,150.0\n"); 107 let b = csv_file("id,price\n1,155.0\n"); 108 dtdiff().arg(a.path()).arg(b.path()).arg("--key").arg("id").arg("--tolerance").arg("0.01") 109 .assert().code(1) 110 .stdout(predicate::str::contains("Modified: 1")); 111 } 112 113 // ─── Parquet ─── 114 115 #[test] 116 fn parquet_keyed_diff() { 117 dtdiff().arg("tests/fixtures/old.parquet").arg("tests/fixtures/new.parquet") 118 .arg("--key").arg("id") 119 .assert().code(1) 120 .stdout(predicate::str::contains("Added: 1")) 121 .stdout(predicate::str::contains("Removed: 1")); 122 } 123 124 #[test] 125 fn parquet_no_diff() { 126 dtdiff().arg("tests/fixtures/data.parquet").arg("tests/fixtures/data.parquet") 127 .assert().success() 128 .stdout(predicate::str::contains("No differences")); 129 } 130 131 // ─── Arrow/IPC ─── 132 133 #[test] 134 fn arrow_keyed_diff() { 135 dtdiff().arg("tests/fixtures/old.arrow").arg("tests/fixtures/new.arrow") 136 .arg("--key").arg("id") 137 .assert().code(1) 138 .stdout(predicate::str::contains("Added: 1")) 139 .stdout(predicate::str::contains("Removed: 1")); 140 } 141 142 // ─── JSON ─── 143 144 #[test] 145 fn json_keyed_diff() { 146 dtdiff().arg("tests/fixtures/old.json").arg("tests/fixtures/new.json") 147 .arg("--key").arg("id") 148 .assert().code(1) 149 .stdout(predicate::str::contains("Modified: 1")); 150 } 151 152 // ─── NDJSON ─── 153 154 #[test] 155 fn ndjson_keyed_diff() { 156 dtdiff().arg("tests/fixtures/old.ndjson").arg("tests/fixtures/new.ndjson") 157 .arg("--key").arg("id") 158 .assert().code(1) 159 .stdout(predicate::str::contains("Modified: 1")); 160 } 161 162 // ─── Output formats ─── 163 164 #[test] 165 fn json_output() { 166 let a = csv_file("id,val\n1,a\n"); 167 let b = csv_file("id,val\n1,b\n"); 168 dtdiff().arg(a.path()).arg(b.path()).arg("--key").arg("id").arg("--json") 169 .assert().code(1) 170 .stdout(predicate::str::contains("\"modified\"")); 171 } 172 173 #[test] 174 fn csv_output() { 175 let a = csv_file("id,val\n1,a\n"); 176 let b = csv_file("id,val\n1,b\n"); 177 dtdiff().arg(a.path()).arg(b.path()).arg("--key").arg("id").arg("--csv") 178 .assert().code(1) 179 .stdout(predicate::str::contains("_status")); 180 } 181 182 #[test] 183 fn no_color_flag() { 184 let a = csv_file("name,value\nAlice,100\n"); 185 let b = csv_file("name,value\nBob,200\n"); 186 dtdiff().arg(a.path()).arg(b.path()).arg("--no-color") 187 .assert().code(1); 188 } 189 190 // ─── Excel ─── 191 192 #[test] 193 fn excel_keyed_diff() { 194 dtdiff().arg("demo/old.xlsx").arg("demo/new.xlsx").arg("--key").arg("ID") 195 .assert().code(1) 196 .stdout(predicate::str::contains("Added: 1")) 197 .stdout(predicate::str::contains("Removed: 1")) 198 .stdout(predicate::str::contains("Modified: 3")); 199 } 200 201 #[test] 202 fn excel_no_diff() { 203 dtdiff().arg("demo/old.xlsx").arg("demo/old.xlsx") 204 .assert().success() 205 .stdout(predicate::str::contains("No differences")); 206 }