test(cli): RED — aura graph with a bad blueprint arg must be a usage fault (#28)

The #28 cycle-close audit found the just-shipped `aura graph <file>` silently
renders the embedded sample at exit 0 when the positional names an unreadable
blueprint (typo, nonexistent path, wrong extension): is_blueprint_file returns
None both for "no positional" (the legit sample default) and for a bad arg, and
dispatch_graph's None arm conflates them — re-introducing the exact #28 "I asked
for my graph, got the sample" surprise in the error path. This pins the fix: a
named-but-unreadable arg exits 2 like its siblings, while bare `aura graph` still
renders the sample.

refs #28
This commit is contained in:
2026-07-10 03:46:30 +02:00
parent 518c5e9017
commit 692760d3b0
+59
View File
@@ -709,6 +709,65 @@ fn graph_renders_a_consumer_blueprint_file_not_the_embedded_sample() {
);
}
/// Property (#28 close-audit): a NAMED-but-unreadable blueprint arg to
/// `aura graph` is a usage fault, not a silent fallback to the embedded sample.
/// `dispatch_graph`'s no-subcommand arm branches on `is_blueprint_file`, which
/// returns `None` for BOTH "no positional given" (→ legitimate sample default)
/// and "a positional was given but it is not a readable `.json` file" (→ a bad
/// arg). The two must not be conflated: a bad arg (typo, nonexistent path, wrong
/// extension) must `eprintln!` a usage error and exit 2, mirroring `dispatch_run`
/// — NOT render the r_sma sample and exit 0, which re-introduces the exact #28
/// friction ("I asked for my graph, got the sample") in the error path. The
/// bare-`aura graph` default (no positional → sample, exit 0) is guarded in the
/// same test so the fix cannot regress it.
#[test]
fn graph_with_bad_blueprint_arg_is_a_usage_fault_not_a_silent_sample() {
let cwd = temp_cwd("graph-bad-arg");
// A named .json path that does not exist: `is_blueprint_file` returns None,
// and the buggy arm falls through to the embedded sample instead of refusing.
let out = Command::new(BIN)
.arg("graph")
.arg("does-not-exist.json")
.current_dir(&cwd)
.output()
.expect("spawn aura graph <bad-arg>");
// Headline pin: a named-but-unreadable blueprint arg exits 2 (usage fault),
// mirroring `dispatch_run`'s bad-blueprint branch — not 0.
assert_eq!(
out.status.code(),
Some(2),
"`aura graph <nonexistent.json>` must be a usage fault (exit 2), not a silent sample fallback; got {:?}\nstderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr),
);
// ...and it must NOT emit the embedded r_sma sample: the sample's inlined
// model + its Bias node must be absent from stdout.
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
assert!(
!stdout.contains("\"type\":\"Bias\""),
"a bad blueprint arg silently rendered the embedded r_sma sample (its Bias node is present) instead of refusing"
);
// Guard the legitimate default: bare `aura graph` (no positional) still
// renders the embedded sample and exits 0.
let default = Command::new(BIN)
.arg("graph")
.current_dir(&cwd)
.output()
.expect("spawn aura graph");
assert_eq!(
default.status.code(),
Some(0),
"bare `aura graph` must still render the sample (exit 0); got {:?}",
default.status,
);
let default_out = String::from_utf8(default.stdout).expect("utf-8 stdout");
assert!(
default_out.contains("\"type\":\"Bias\""),
"bare `aura graph` must render the embedded r_sma sample (its Bias node), the no-positional default"
);
}
#[test]
fn sweep_with_trailing_token_is_strict_and_exits_two() {
// strict-arg reading (#16): an unexpected trailing token is a usage error.