//! Integration test: drive the built `aura` binary as a downstream user would, //! asserting the `run` subcommand's stdout/exit contract and the bad-args path. use std::process::Command; /// Path to the freshly-built `aura` binary (Cargo sets this env var for the test /// crate; the binary is named `aura` in `Cargo.toml`). const BIN: &str = env!("CARGO_BIN_EXE_aura"); #[test] fn run_prints_json_and_exits_zero() { let out = Command::new(BIN).arg("run").output().expect("spawn aura run"); assert!(out.status.success(), "exit status: {:?}", out.status); let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout"); // exactly one line (the JSON object + a trailing newline from println!). assert_eq!(stdout.lines().count(), 1, "stdout was: {stdout:?}"); let line = stdout.trim_end(); // canonical cycle-0009 JSON shape: nested manifest + metrics, stable keys. // The manifest leads with a `commit` field; its value is the build's git // identity (asserted by `run_manifest_commit_carries_real_git_head`), so // this shape check pins only the surrounding structure, not the value. assert!(line.starts_with("{\"manifest\":{\"commit\":\""), "got: {line}"); assert!(line.contains("\"broker\":\"sim-optimal(pip_size=0.0001)\""), "got: {line}"); assert!(line.contains("\"window\":[1,7]"), "got: {line}"); assert!(line.contains("\"metrics\":{\"total_pips\":"), "got: {line}"); // the integer sign-flip count is stable across float renderings. assert!(line.ends_with("\"exposure_sign_flips\":1}}"), "got: {line}"); } /// Property: the RunManifest is self-identifying — its `commit` carries the /// real code identity that produced the run, not a placeholder. C8/C18 audit /// trail (this run = this commit): once runs are archived, `manifest.commit` /// must point back at the source. The build captures the current git HEAD at /// compile time; the `"unknown"` literal survives only when there is no git. /// /// Structural assertion (not exact-equality) on purpose: the working tree may /// be dirty when this runs (e.g. this very test file uncommitted), so a build /// that appends a `-dirty` suffix would not equal `rev-parse HEAD` verbatim. /// The honest contract is: the field CONTAINS the current HEAD sha AND is not /// the bare `"unknown"` placeholder — true regardless of dirtiness/suffix. #[test] fn run_manifest_commit_carries_real_git_head() { // The current HEAD sha, obtained the same way the build is expected to. let head = Command::new("git") .args(["rev-parse", "HEAD"]) .output() .expect("spawn git rev-parse HEAD"); assert!(head.status.success(), "git rev-parse HEAD failed"); let head_sha = String::from_utf8(head.stdout).expect("utf-8 sha"); let head_sha = head_sha.trim(); assert!(!head_sha.is_empty(), "empty HEAD sha"); // Drive the binary and pull `manifest.commit` out of the single JSON line. let out = Command::new(BIN).arg("run").output().expect("spawn aura run"); assert!(out.status.success(), "exit status: {:?}", out.status); let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout"); let line = stdout.trim_end(); // Locate the `"commit":""` value without a JSON dependency (the // sibling tests parse this output by substring too). let key = "\"commit\":\""; let start = line.find(key).expect("manifest has a commit field") + key.len(); let rest = &line[start..]; let end = rest.find('"').expect("commit field is a closed string"); let commit = &rest[..end]; assert_ne!( commit, "unknown", "manifest.commit is still the placeholder: {line}" ); assert!( commit.contains(head_sha), "manifest.commit {commit:?} should contain the current HEAD sha {head_sha:?}; line: {line}" ); } /// Property: `aura run` is strict about its argument vector — a trailing /// unexpected token (e.g. a typo'd flag like `aura run --sweep`) takes the /// error path (usage on stderr, exit 2), never a silent full run. This is the /// strict reading ratified in #16: keying only on the first token being `run` /// and ignoring the rest would let a typo masquerade as a successful run. /// The same test pins positive-preservation — bare `aura run` still emits the /// JSON report on stdout and exits 0 — so the strict guard cannot regress the /// happy path. #[test] fn run_with_trailing_token_is_strict_and_exits_two() { // Negative: an unexpected trailing token is rejected like a bad-args call. let out = Command::new(BIN) .args(["run", "extra-garbage"]) .output() .expect("spawn aura run extra-garbage"); assert_eq!( out.status.code(), Some(2), "`aura run extra-garbage` must reject the trailing token: {:?}", out.status ); assert!( out.stdout.is_empty(), "stdout should be empty on the usage path, got: {:?}", String::from_utf8_lossy(&out.stdout) ); let stderr = String::from_utf8(out.stderr).expect("utf-8 stderr"); assert!(stderr.contains("usage"), "stderr was: {stderr:?}"); // Positive-preservation: bare `aura run` still runs and prints the report. let ok = Command::new(BIN).arg("run").output().expect("spawn aura run"); assert_eq!( ok.status.code(), Some(0), "bare `aura run` must still succeed: {:?}", ok.status ); let ok_stdout = String::from_utf8(ok.stdout).expect("utf-8 stdout"); assert!( ok_stdout.trim_start().starts_with("{\"manifest\":"), "bare `aura run` should print the JSON report, got: {ok_stdout:?}" ); } #[test] fn no_args_prints_usage_and_exits_two() { let out = Command::new(BIN).output().expect("spawn aura"); assert_eq!(out.status.code(), Some(2), "exit status: {:?}", out.status); assert!(out.stdout.is_empty(), "stdout should be empty on the usage path"); let stderr = String::from_utf8(out.stderr).expect("utf-8 stderr"); assert!(stderr.contains("usage"), "stderr was: {stderr:?}"); } /// Property: `--help`/`-h` is the success-path help affordance, not the error /// path. A newcomer's reflex first command (C22 first-contact ergonomics, #20) /// prints the usage to stdout and exits 0; the conventional `-h` alias behaves /// identically. An *unknown* subcommand keeps the error path (exit 2) so the /// help fix does not regress bad-args handling. #[test] fn help_flag_prints_usage_to_stdout_and_exits_zero() { for flag in ["--help", "-h"] { let out = Command::new(BIN).arg(flag).output().expect("spawn aura --help"); assert_eq!(out.status.code(), Some(0), "`aura {flag}` exit: {:?}", out.status); let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout"); assert!( !stdout.trim().is_empty(), "`aura {flag}` should print help to stdout, got: {stdout:?}" ); // The usage names the only subcommand a newcomer can run. assert!( stdout.contains("run"), "`aura {flag}` help should mention the `run` subcommand, got: {stdout:?}" ); } // Negative-preservation: an unknown subcommand is still the error path. let out = Command::new(BIN).arg("frobnicate").output().expect("spawn aura frobnicate"); assert_eq!( out.status.code(), Some(2), "unknown subcommand must stay exit 2: {:?}", out.status ); }