//! E2E: the project-as-crate load boundary (cycle 0102). Builds the fixture //! project once (cargo, same toolchain, path-dep on this workspace's //! aura-core), then drives the aura binary from inside the fixture dir. //! `aura run` persists nothing without --trace, so the tracked fixture stays //! clean (target/, Cargo.lock, runs/ are fixture-gitignored). use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::sync::OnceLock; fn fixture_dir() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/demo-project") } fn badcharter_dir() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/badcharter-project") } fn built_fixture() -> &'static PathBuf { static BUILT: OnceLock = OnceLock::new(); BUILT.get_or_init(|| { let dir = fixture_dir(); let out = Command::new("cargo") .arg("build") .current_dir(&dir) .output() .expect("spawn cargo build for the fixture project"); assert!( out.status.success(), "fixture build failed:\n{}", String::from_utf8_lossy(&out.stderr) ); dir }) } fn aura(args: &[&str], cwd: &Path) -> Output { Command::new(env!("CARGO_BIN_EXE_aura")) .args(args) .current_dir(cwd) .output() .expect("run aura") } #[test] fn project_run_resolves_demo_node_and_is_bit_identical() { let dir = built_fixture(); let a = aura(&["run", "demo_signal.json"], dir); assert!( a.status.success(), "run failed: {}", String::from_utf8_lossy(&a.stderr) ); let b = aura(&["run", "demo_signal.json"], dir); assert!(b.status.success()); assert_eq!(a.stdout, b.stdout, "two runs must be byte-identical (C1)"); } #[test] fn project_run_manifest_carries_provenance() { let dir = built_fixture(); let out = aura(&["run", "demo_signal.json"], dir); assert!(out.status.success()); let v: serde_json::Value = serde_json::from_slice(&out.stdout).expect("run report is JSON"); let p = &v["manifest"]["project"]; assert_eq!(p["namespace"], "demo"); let sha = p["dylib_sha256"].as_str().expect("hash present"); assert_eq!(sha.len(), 64, "sha256 hex"); } #[test] fn introspect_vocabulary_lists_project_types() { let dir = built_fixture(); let out = aura(&["graph", "introspect", "--vocabulary"], dir); assert!(out.status.success()); let text = String::from_utf8_lossy(&out.stdout); assert!(text.contains("demo::Identity"), "project id listed"); assert!(text.contains("SMA"), "std ids still listed"); } /// (#184/#185) Outside any project, `demo::Identity` resolves nowhere: the base /// failure is house-style prose (never the raw `UnknownNodeType` Debug leak), /// and — because the unresolved id LOOKS project-namespaced (`::`) while no /// `Aura.toml` was found up from cwd — the message carries a hint pointing at /// the missing project, distinguishing "no project" from "genuinely unknown /// std id" (the bare-id case in `cli_run.rs` gets no such hint). #[test] fn outside_a_project_the_demo_blueprint_is_unknown() { // cwd = the aura-cli crate dir: no Aura.toml anywhere up the tree. let cwd = Path::new(env!("CARGO_MANIFEST_DIR")); let bp = fixture_dir().join("demo_signal.json"); let out = aura(&["run", bp.to_str().unwrap()], cwd); assert!(!out.status.success()); let err = String::from_utf8_lossy(&out.stderr); assert!( err.contains(r#"unknown node type "demo::Identity""#), "house-style prose names the type: {err}" ); assert!(!err.contains("UnknownNodeType"), "does not leak the Debug variant name: {err}"); assert!( err.contains("Aura.toml"), "hints that no project was found for the namespaced id: {err}" ); } /// `Env::runs_root()` resolves `Aura.toml`'s `[paths] runs = "runs"` against /// the DISCOVERED project root (the `Aura.toml` directory found by walking up /// from cwd), not against the invocation cwd itself: a family recorded while /// invoking `aura` from a project subdirectory must still land under /// `/runs/`, and no stray `runs/` must appear under the /// subdirectory. This is the property that makes `[paths]` project-relative /// rather than shell-relative (C17). #[test] fn project_registry_anchors_at_discovered_root_not_invocation_cwd() { let dir = built_fixture(); let sub = dir.join("src"); let runs_dir = dir.join("runs"); std::fs::remove_dir_all(&runs_dir).ok(); let open_bp = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR")); let out = Command::new(env!("CARGO_BIN_EXE_aura")) .args([ "sweep", &open_bp, "--axis", "sma_signal.fast.length=2,4", "--axis", "sma_signal.slow.length=8,16", "--name", "proj-anchor", ]) .current_dir(&sub) .output() .expect("spawn aura sweep"); assert!( out.status.success(), "sweep failed: {}", String::from_utf8_lossy(&out.stderr) ); assert!( runs_dir.join("families.jsonl").is_file(), "family record must land at the project root's runs/, not the invocation cwd's" ); assert!( !sub.join("runs").exists(), "no stray runs/ must be created under the invocation subdirectory" ); std::fs::remove_dir_all(&runs_dir).ok(); } /// A project whose listed type id is not `::`-prefixed is refused /// (exit 1, cause named) through the REAL load path — `libloading` + /// descriptor read + `check_charter` wired together in `project::load` — not /// merely the pure `check_charter` function (already unit-tested in /// `aura-cli::project`). Pins the "refuse-don't-guess" vocabulary charter as /// an end-to-end guarantee: a regression that skipped the charter call in the /// `load()` glue would pass every existing unit test but still be caught here. #[test] fn vocabulary_charter_violation_refuses_end_to_end() { let dir = badcharter_dir(); let build = Command::new("cargo") .arg("build") .current_dir(&dir) .output() .expect("spawn cargo build for the badcharter fixture"); assert!( build.status.success(), "badcharter fixture build failed:\n{}", String::from_utf8_lossy(&build.stderr) ); let run = aura(&["run", "x.json"], &dir); assert_eq!( run.status.code(), Some(1), "a charter violation is a runtime refusal (exit 1), not a usage error" ); let err = String::from_utf8_lossy(&run.stderr); assert!(err.contains("lacks the project prefix"), "stderr must name the charter cause: {err}"); } #[test] fn missing_artifact_refuses_with_build_hint() { // A minimal never-built project: valid cargo metadata, no dylib on disk. let tmp = std::env::temp_dir().join(format!("aura-nobuild-{}", std::process::id())); std::fs::create_dir_all(tmp.join("src")).unwrap(); std::fs::write(tmp.join("Aura.toml"), "").unwrap(); std::fs::write(tmp.join("src/lib.rs"), "").unwrap(); std::fs::write( tmp.join("Cargo.toml"), "[package]\nname = \"nobuild\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[workspace]\n", ) .unwrap(); let out = aura(&["run", "x.json"], &tmp); assert_eq!(out.status.code(), Some(1), "runtime refusal"); let err = String::from_utf8_lossy(&out.stderr); assert!(err.contains("cargo build"), "build hint present: {err}"); std::fs::remove_dir_all(&tmp).ok(); }