feat(import): --strict refuses a skipped record, and the headless suite covers the reviewed gaps (improvise-8jk6.6, improvise-8jk6.17)
- 8jk6.6, the user's ruling on PR #321 ("Add a --strict flag"): the
default stays exit 0 with the counted warning. With --strict, any
skipped record is a refusal that exits 1 and writes nothing. The check
runs before the build and reads ImportPipeline::skip_warning, the same
function the build's warning now comes from.
- 8jk6.17: the headless suite now sees three faults that only io unit
tests caught at efaa5050:
- naming only the first bad formula (two bad --formula flags);
- dropping CSV decimal measures;
- import-json reading a CSV through the JSON constructor.
- Docs record the user's three answers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
committed by
Edward Langley
parent
efaa505059
commit
fbe83fcc57
@@ -777,11 +777,14 @@ fn an_import_formula_that_does_not_parse_refuses_names_it_and_writes_nothing() {
|
||||
"Y = x +",
|
||||
"--formula",
|
||||
"Z = x",
|
||||
"--formula",
|
||||
"W = * 2",
|
||||
],
|
||||
);
|
||||
let err = stderr_of(&out);
|
||||
assert!(!out.status.success(), "a dropped formula exited 0: {err}");
|
||||
assert!(err.contains("'Y = x +'"), "names the formula: {err}");
|
||||
assert!(err.contains("'W = * 2'"), "names every bad one: {err}");
|
||||
assert!(err.contains("expected"), "carries the parse error: {err}");
|
||||
assert!(!err.contains("'Z = x'"), "names only the bad one: {err}");
|
||||
assert!(!out_path.exists(), "nothing is written");
|
||||
@@ -823,6 +826,75 @@ fn an_import_that_skips_some_records_warns_with_a_count_and_still_writes() {
|
||||
assert!(doc.contains("_Measure=x, a=m = 4"), "{doc}");
|
||||
}
|
||||
|
||||
/// A JSON array with one stray scalar among its records.
|
||||
const ONE_STRAY: &str = r#"[{"a": "k", "x": 1}, 2, {"a": "m", "x": 4}]"#;
|
||||
|
||||
/// improvise-8jk6.6, the user's ruling: `--strict` makes any skip a refusal
|
||||
/// that writes nothing. Evidence: `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
fn a_strict_import_that_skips_a_record_refuses_and_writes_nothing() {
|
||||
let scratch = Scratch::new("strict_skip");
|
||||
let (out, out_path) = import_of(&scratch, "p.json", ONE_STRAY, &["--strict"]);
|
||||
let err = stderr_of(&out);
|
||||
assert!(!out.status.success(), "{err}");
|
||||
assert!(
|
||||
err.contains("--strict: Skipped 1 of 3 records that were not JSON objects"),
|
||||
"{err}"
|
||||
);
|
||||
assert!(!out_path.exists(), "a strict refusal writes nothing");
|
||||
}
|
||||
|
||||
/// The control: `--strict` refuses a skip, not every import.
|
||||
#[test]
|
||||
fn a_strict_import_that_skips_nothing_still_writes() {
|
||||
let scratch = Scratch::new("strict_clean");
|
||||
let body = r#"[{"a": "k", "x": 1}, {"a": "m", "x": 4}]"#;
|
||||
let (out, out_path) = import_of(&scratch, "p.json", body, &["--strict"]);
|
||||
assert!(out.status.success(), "{}", stderr_of(&out));
|
||||
let doc = std::fs::read_to_string(&out_path).unwrap();
|
||||
assert!(doc.contains("_Measure=x, a=m = 4"), "{doc}");
|
||||
}
|
||||
|
||||
/// improvise-8jk6.17 (F3): a CSV decimal measure is its number, not dropped
|
||||
/// text. Evidence: `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
fn a_csv_decimal_measure_imports_as_its_number() {
|
||||
let scratch = Scratch::new("csv_decimal");
|
||||
let body = "Region,Revenue\nEast,1000.50\nWest,-2.25\n";
|
||||
let (out, out_path) = import_of(&scratch, "f.csv", body, &[]);
|
||||
assert!(out.status.success(), "{}", stderr_of(&out));
|
||||
let doc = std::fs::read_to_string(&out_path).unwrap();
|
||||
for line in [
|
||||
"Region=East, _Measure=Revenue = 1000.5",
|
||||
"Region=West, _Measure=Revenue = -2.25",
|
||||
] {
|
||||
assert!(doc.lines().any(|l| l == line), "{line} missing from\n{doc}");
|
||||
}
|
||||
}
|
||||
|
||||
/// improvise-8jk6.17 (F1): `import-json` reads a CSV as CSV, so its numeric
|
||||
/// column is a measure. Evidence: `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
fn import_json_on_a_csv_reads_its_numeric_column_as_a_measure() {
|
||||
let scratch = Scratch::new("import_json_csv");
|
||||
let csv = scratch.path("f.csv");
|
||||
std::fs::write(&csv, "Region,Revenue\nEast,1000.50\nWest,7\n").unwrap();
|
||||
let doc = scratch.path("m.improv");
|
||||
let out = run_cmd(&[&format!("import-json {}", csv.display())], &doc);
|
||||
assert!(out.status.success(), "{}", stderr_of(&out));
|
||||
let text = std::fs::read_to_string(&doc).unwrap();
|
||||
assert!(!text.contains("## Category: Revenue"), "{text}");
|
||||
for line in [
|
||||
"Region=East, _Measure=Revenue = 1000.5",
|
||||
"Region=West, _Measure=Revenue = 7",
|
||||
] {
|
||||
assert!(
|
||||
text.lines().any(|l| l == line),
|
||||
"{line} missing from\n{text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// improvise-lpzu, the user's ruling: a folder always merges, so even one CSV
|
||||
/// keeps its file identity. Evidence: `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user