dt-cli-tools

CLI tools for viewing, filtering, and comparing tabular data files
Log | Files | Refs | README | LICENSE

dtcat.rs (9722B)


      1 use assert_cmd::Command;
      2 use predicates::prelude::*;
      3 use std::io::Write;
      4 use tempfile::NamedTempFile;
      5 
      6 fn dtcat() -> Command {
      7     Command::cargo_bin("dtcat").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 // ─── Basic viewing ───
     18 
     19 #[test]
     20 fn shows_csv_data() {
     21     let f = csv_file("name,value\nAlice,100\nBob,200\n");
     22     dtcat().arg(f.path()).assert().success()
     23         .stdout(predicate::str::contains("Alice"))
     24         .stdout(predicate::str::contains("Bob"));
     25 }
     26 
     27 #[test]
     28 fn header_only_csv() {
     29     let f = csv_file("name,value\n");
     30     dtcat().arg(f.path()).assert().success()
     31         .stdout(predicate::str::contains("no data rows"));
     32 }
     33 
     34 #[test]
     35 fn nonexistent_file_exits_1() {
     36     dtcat().arg("/tmp/does_not_exist_12345.csv").assert().failure();
     37 }
     38 
     39 // ─── Modes ───
     40 
     41 #[test]
     42 fn schema_flag() {
     43     let f = csv_file("name,value\nAlice,100\n");
     44     dtcat().arg(f.path()).arg("--schema").assert().success()
     45         .stdout(predicate::str::contains("Column"))
     46         .stdout(predicate::str::contains("Type"));
     47 }
     48 
     49 #[test]
     50 fn describe_flag() {
     51     let f = csv_file("name,value\nAlice,100\nBob,200\n");
     52     dtcat().arg(f.path()).arg("--describe").assert().success()
     53         .stdout(predicate::str::contains("count"))
     54         .stdout(predicate::str::contains("mean"));
     55 }
     56 
     57 #[test]
     58 fn info_flag() {
     59     let f = csv_file("name,value\nAlice,100\n");
     60     dtcat().arg(f.path()).arg("--info").assert().success()
     61         .stdout(predicate::str::contains("File:"));
     62 }
     63 
     64 // ─── Row windowing ───
     65 
     66 #[test]
     67 fn head_flag() {
     68     let f = csv_file("x\n1\n2\n3\n4\n5\n");
     69     dtcat().arg(f.path()).arg("--head").arg("2").assert().success()
     70         .stdout(predicate::str::contains("1"))
     71         .stdout(predicate::str::contains("2"))
     72         .stdout(predicate::str::contains("3").not());
     73 }
     74 
     75 #[test]
     76 fn tail_flag() {
     77     let f = csv_file("x\n1\n2\n3\n4\n5\n");
     78     dtcat().arg(f.path()).arg("--tail").arg("2").assert().success()
     79         .stdout(predicate::str::contains("4"))
     80         .stdout(predicate::str::contains("5"));
     81 }
     82 
     83 #[test]
     84 fn head_and_tail_combined() {
     85     let f = csv_file("x\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n");
     86     dtcat().arg(f.path()).arg("--head").arg("2").arg("--tail").arg("2")
     87         .assert().success()
     88         .stdout(predicate::str::contains("1"))
     89         .stdout(predicate::str::contains("2"))
     90         .stdout(predicate::str::contains("9"))
     91         .stdout(predicate::str::contains("10"));
     92 }
     93 
     94 // ─── Output format ───
     95 
     96 #[test]
     97 fn csv_output_flag() {
     98     let f = csv_file("name,value\nAlice,100\n");
     99     dtcat().arg(f.path()).arg("--csv").assert().success()
    100         .stdout(predicate::str::contains("name,value"));
    101 }
    102 
    103 // ─── Format detection ───
    104 
    105 #[test]
    106 fn format_override() {
    107     let mut f = NamedTempFile::with_suffix(".txt").unwrap();
    108     write!(f, "a,b\n1,2\n").unwrap();
    109     f.flush().unwrap();
    110     dtcat().arg(f.path()).arg("--format").arg("csv").assert().success()
    111         .stdout(predicate::str::contains("1"));
    112 }
    113 
    114 #[test]
    115 fn tsv_detection() {
    116     let mut f = NamedTempFile::with_suffix(".tsv").unwrap();
    117     write!(f, "name\tvalue\nAlice\t100\n").unwrap();
    118     f.flush().unwrap();
    119     dtcat().arg(f.path()).assert().success()
    120         .stdout(predicate::str::contains("Alice"))
    121         .stdout(predicate::str::contains("100"));
    122 }
    123 
    124 // ─── Skip rows ───
    125 
    126 #[test]
    127 fn skip_metadata_rows() {
    128     let f = csv_file("meta1\nmeta2\nname,value\nAlice,100\n");
    129     dtcat().arg(f.path()).arg("--skip").arg("2").assert().success()
    130         .stdout(predicate::str::contains("Alice"));
    131 }
    132 
    133 // ─── All flag ───
    134 
    135 #[test]
    136 fn all_flag_shows_every_row() {
    137     // 60 rows > threshold of 50, so without --all we'd get head+tail
    138     let mut content = String::from("x\n");
    139     for i in 1..=60 {
    140         content.push_str(&format!("{}\n", i));
    141     }
    142     let f = csv_file(&content);
    143     // With --all, row 30 should appear (it would be omitted in head25+tail25)
    144     dtcat().arg(f.path()).arg("--all").assert().success()
    145         .stdout(predicate::str::contains("| 30 "));
    146 }
    147 
    148 // ─── Sample ───
    149 
    150 #[test]
    151 fn sample_returns_n_rows() {
    152     let out = dtcat().arg("demo/sales.csv").arg("--sample").arg("5").arg("--csv")
    153         .assert().success();
    154     let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
    155     let lines: Vec<&str> = stdout.trim().lines().collect();
    156     assert_eq!(lines.len(), 6, "expected header + 5 rows, got {}", lines.len());
    157 }
    158 
    159 #[test]
    160 fn sample_ge_total_returns_all() {
    161     let f = csv_file("x\n1\n2\n3\n");
    162     dtcat().arg(f.path()).arg("--sample").arg("100").arg("--csv")
    163         .assert().success();
    164 }
    165 
    166 #[test]
    167 fn sample_conflicts_with_head() {
    168     let f = csv_file("x\n1\n");
    169     dtcat().arg(f.path()).arg("--sample").arg("1").arg("--head").arg("1")
    170         .assert().code(2);
    171 }
    172 
    173 #[test]
    174 fn sample_conflicts_with_tail() {
    175     let f = csv_file("x\n1\n");
    176     dtcat().arg(f.path()).arg("--sample").arg("1").arg("--tail").arg("1")
    177         .assert().code(2);
    178 }
    179 
    180 #[test]
    181 fn sample_conflicts_with_all() {
    182     let f = csv_file("x\n1\n");
    183     dtcat().arg(f.path()).arg("--sample").arg("1").arg("--all")
    184         .assert().code(2);
    185 }
    186 
    187 #[test]
    188 fn sample_conflicts_with_schema() {
    189     let f = csv_file("x\n1\n");
    190     dtcat().arg(f.path()).arg("--sample").arg("1").arg("--schema")
    191         .assert().code(2);
    192 }
    193 
    194 #[test]
    195 fn sample_conflicts_with_describe() {
    196     let f = csv_file("x\n1\n");
    197     dtcat().arg(f.path()).arg("--sample").arg("1").arg("--describe")
    198         .assert().code(2);
    199 }
    200 
    201 #[test]
    202 fn sample_conflicts_with_info() {
    203     let f = csv_file("x\n1\n");
    204     dtcat().arg(f.path()).arg("--sample").arg("1").arg("--info")
    205         .assert().code(2);
    206 }
    207 
    208 // ─── Parquet ───
    209 
    210 #[test]
    211 fn parquet_view() {
    212     dtcat().arg("tests/fixtures/data.parquet").assert().success()
    213         .stdout(predicate::str::contains("Alice"))
    214         .stdout(predicate::str::contains("Charlie"));
    215 }
    216 
    217 #[test]
    218 fn parquet_schema() {
    219     dtcat().arg("tests/fixtures/data.parquet").arg("--schema").assert().success()
    220         .stdout(predicate::str::contains("name"))
    221         .stdout(predicate::str::contains("value"));
    222 }
    223 
    224 // ─── Arrow/IPC ───
    225 
    226 #[test]
    227 fn arrow_view() {
    228     dtcat().arg("tests/fixtures/data.arrow").assert().success()
    229         .stdout(predicate::str::contains("Alice"))
    230         .stdout(predicate::str::contains("Charlie"));
    231 }
    232 
    233 #[test]
    234 fn arrow_schema() {
    235     dtcat().arg("tests/fixtures/data.arrow").arg("--schema").assert().success()
    236         .stdout(predicate::str::contains("name"))
    237         .stdout(predicate::str::contains("value"));
    238 }
    239 
    240 // ─── JSON ───
    241 
    242 #[test]
    243 fn json_view() {
    244     dtcat().arg("tests/fixtures/data.json").assert().success()
    245         .stdout(predicate::str::contains("Alice"))
    246         .stdout(predicate::str::contains("Charlie"));
    247 }
    248 
    249 // ─── NDJSON ───
    250 
    251 #[test]
    252 fn ndjson_view() {
    253     dtcat().arg("tests/fixtures/data.ndjson").assert().success()
    254         .stdout(predicate::str::contains("Alice"))
    255         .stdout(predicate::str::contains("Charlie"));
    256 }
    257 
    258 // ─── Excel ───
    259 
    260 #[test]
    261 fn excel_view() {
    262     dtcat().arg("demo/sales.xlsx").assert().success()
    263         .stdout(predicate::str::contains("Revenue"));
    264 }
    265 
    266 #[test]
    267 fn excel_schema() {
    268     dtcat().arg("demo/sales.xlsx").arg("--schema").assert().success()
    269         .stdout(predicate::str::contains("Column"))
    270         .stdout(predicate::str::contains("Revenue"));
    271 }
    272 
    273 #[test]
    274 fn excel_info() {
    275     dtcat().arg("demo/sales.xlsx").arg("--info").assert().success()
    276         .stdout(predicate::str::contains("Excel"))
    277         .stdout(predicate::str::contains("Sheet1"));
    278 }
    279 
    280 // ─── Convert ───
    281 
    282 #[test]
    283 fn convert_csv_to_parquet() {
    284     let out = NamedTempFile::with_suffix(".parquet").unwrap();
    285     dtcat().arg("tests/fixtures/data.csv")
    286         .arg("--convert").arg("parquet")
    287         .arg("-o").arg(out.path())
    288         .assert().success();
    289     dtcat().arg(out.path()).arg("--csv")
    290         .assert().success()
    291         .stdout(predicate::str::contains("Alice"))
    292         .stdout(predicate::str::contains("Charlie"));
    293 }
    294 
    295 #[test]
    296 fn convert_parquet_to_csv_file() {
    297     let out = NamedTempFile::with_suffix(".csv").unwrap();
    298     dtcat().arg("tests/fixtures/data.parquet")
    299         .arg("--convert").arg("csv")
    300         .arg("-o").arg(out.path())
    301         .assert().success();
    302     dtcat().arg(out.path())
    303         .assert().success()
    304         .stdout(predicate::str::contains("Alice"));
    305 }
    306 
    307 #[test]
    308 fn convert_csv_to_json_stdout() {
    309     dtcat().arg("tests/fixtures/data.csv")
    310         .arg("--convert").arg("json")
    311         .assert().success()
    312         .stdout(predicate::str::contains("Alice"));
    313 }
    314 
    315 #[test]
    316 fn convert_csv_to_ndjson_stdout() {
    317     dtcat().arg("tests/fixtures/data.csv")
    318         .arg("--convert").arg("ndjson")
    319         .assert().success()
    320         .stdout(predicate::str::contains("Alice"));
    321 }
    322 
    323 #[test]
    324 fn convert_parquet_no_output_errors() {
    325     dtcat().arg("tests/fixtures/data.csv")
    326         .arg("--convert").arg("parquet")
    327         .assert().failure();
    328 }
    329 
    330 #[test]
    331 fn convert_arrow_no_output_errors() {
    332     dtcat().arg("tests/fixtures/data.csv")
    333         .arg("--convert").arg("arrow")
    334         .assert().failure();
    335 }
    336 
    337 #[test]
    338 fn convert_conflicts_with_schema() {
    339     let f = csv_file("x\n1\n");
    340     dtcat().arg(f.path()).arg("--convert").arg("csv").arg("--schema")
    341         .assert().code(2);
    342 }
    343 
    344 #[test]
    345 fn convert_with_skip() {
    346     let f = csv_file("meta\nname,value\nAlice,100\n");
    347     dtcat().arg(f.path()).arg("--skip").arg("1").arg("--convert").arg("csv")
    348         .assert().success()
    349         .stdout(predicate::str::contains("Alice"));
    350 }