Files
Aura/crates/aura-cli/tests/research_docs.rs
T
Brummel db09e5de52 feat(cli): 0106 task 10 — aura process verb family (validate/introspect/register)
New research_docs.rs module: role-addressed process verbs with house-style
fault prose (Display-free enums phrased at the seam), the introspect
exactly-one-of usage guard (exit 2), and content-addressed registration.
Seven binary seam tests pin intrinsic-ok/refusal prose, vocabulary/block/
unwired/content-id introspection, exit codes, and the stored file's
location under the runs root.

Quality-gate finding folded in (and back into the plan): register no
longer re-derives the store path from runs_root — aura-registry now
exposes process_path/campaign_path, the same single mapping its put/get
pair routes through, so the printed path cannot drift from the store
layout.

Verification: research_docs seam tests 7/0, registry 49/0, clippy clean.

refs #189
2026-07-03 15:36:47 +02:00

139 lines
5.4 KiB
Rust

//! End-to-end coverage for the `aura process` verb family (#188/#189): drives
//! the built `aura` binary over argv/file, the exact surface a data-level
//! author uses.
use std::path::{Path, PathBuf};
/// A fresh, unique working directory for a process test that persists
/// content-addressed documents under `./runs/` (so `aura process register`
/// never dirties the repo). Unique per test + per process; no external
/// tempfile dependency (mirrors `cli_run.rs` / `cli_broken_pipe.rs`).
fn temp_cwd(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("aura-cli-{}-{}", std::process::id(), name));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create temp cwd");
dir
}
fn run_code_in(dir: &Path, args: &[&str]) -> (String, Option<i32>) {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(args)
.current_dir(dir)
.output()
.expect("binary runs");
let text = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
(text, out.status.code())
}
fn run_code(args: &[&str]) -> (String, Option<i32>) {
run_code_in(Path::new("."), args)
}
const PROCESS_DOC: &str = r#"{
"format_version": 1,
"kind": "process",
"name": "wf-deflated-screen",
"pipeline": [
{ "block": "std::sweep", "metric": "net_expectancy_r", "select": "plateau:worst", "deflate": true },
{ "block": "std::gate", "all": [
{ "metric": "net_expectancy_r", "cmp": "gt", "value": 0.0 },
{ "metric": "overfit_probability", "cmp": "lt", "value": 0.1 } ] },
{ "block": "std::walk_forward", "folds": 4, "in_sample_bars": 4000,
"out_of_sample_bars": 1000, "metric": "net_expectancy_r", "select": "argmax" }
]
}"#;
fn write_doc(dir: &Path, name: &str, text: &str) -> PathBuf {
let p = dir.join(name);
std::fs::write(&p, text).expect("write doc");
p
}
#[test]
fn process_validate_reports_intrinsic_ok() {
let dir = temp_cwd("process-validate-ok");
write_doc(&dir, "p.process.json", PROCESS_DOC);
let (out, code) = run_code_in(&dir, &["process", "validate", "p.process.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert!(out.contains("process document valid (intrinsic): 3 pipeline blocks, 2 gate predicates"));
}
#[test]
fn process_validate_refuses_unknown_metric_as_prose_exit_1() {
let dir = temp_cwd("process-validate-bad-metric");
let bad = PROCESS_DOC.replacen(
"net_expectancy_r\", \"select\": \"plateau:worst",
"netto_r\", \"select\": \"plateau:worst",
1,
);
write_doc(&dir, "bad.process.json", &bad);
let (out, code) = run_code_in(&dir, &["process", "validate", "bad.process.json"]);
assert_eq!(code, Some(1));
assert!(out.contains("unknown metric \"netto_r\""));
assert!(!out.contains("UnknownMetric"), "Debug leak: {out}");
}
#[test]
fn process_introspect_vocabulary_block_and_content_id() {
let (out, code) = run_code(&["process", "introspect", "--vocabulary"]);
assert_eq!(code, Some(0));
for id in ["std::sweep", "std::gate", "std::walk_forward", "std::monte_carlo", "std::generalize"] {
assert!(out.contains(id), "vocabulary misses {id}: {out}");
}
let (out, code) = run_code(&["process", "introspect", "--block", "std::sweep"]);
assert_eq!(code, Some(0));
assert!(out.contains("metric"));
assert!(out.contains("required"));
let dir = temp_cwd("process-introspect-content-id");
write_doc(&dir, "p.process.json", PROCESS_DOC);
let (out, code) = run_code_in(&dir, &["process", "introspect", "--content-id", "p.process.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let line = out.lines().find(|l| l.starts_with("content:")).expect("id line");
assert_eq!(line.len(), "content:".len() + 64);
}
#[test]
fn process_introspect_unknown_block_is_prose_exit_1() {
let (out, code) = run_code(&["process", "introspect", "--block", "std::nope"]);
assert_eq!(code, Some(1), "stdout/stderr: {out}");
assert!(out.contains("unknown block \"std::nope\""), "stdout/stderr: {out}");
}
#[test]
fn process_introspect_unwired_lists_open_slots() {
let dir = temp_cwd("process-introspect-unwired");
let partial = r#"{ "format_version": 1, "kind": "process",
"pipeline": [ { "block": "std::sweep", "metric": "sqn" } ] }"#;
write_doc(&dir, "partial.process.json", partial);
let (out, code) = run_code_in(&dir, &["process", "introspect", "--unwired", "partial.process.json"]);
assert_eq!(code, Some(0));
assert!(out.contains("open slot: name"));
assert!(out.contains("open slot: pipeline[0].select"));
}
#[test]
fn process_introspect_no_flag_is_usage_exit_2() {
let (_out, code) = run_code(&["process", "introspect"]);
assert_eq!(code, Some(2));
}
#[test]
fn process_register_stores_content_addressed_under_runs_root() {
let dir = temp_cwd("process-register");
write_doc(&dir, "p.process.json", PROCESS_DOC);
let (out, code) = run_code_in(&dir, &["process", "register", "p.process.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let line = out.lines().find(|l| l.starts_with("registered process content:")).expect("line");
let id = line
.trim_start_matches("registered process content:")
.split(' ')
.next()
.expect("id");
assert!(dir.join("runs").join("processes").join(format!("{id}.json")).is_file());
}