nomograph-workflow 0.3.0

Shared workflow layer for nomograph claim-substrate tools: Store adapter, phase state machine, v1 legacy detection, cross-tool helpers.
Documentation
//! Tests for `Store::discover` respecting `SYNTHESIST_DIR`.
//!
//! v0.1.0 silently fell through to `Store::init_at` when `SYNTHESIST_DIR`
//! was set but the path had no `claims/genesis.amc`, creating a fresh
//! empty store instead of erroring. v0.1.1 fails loudly.

use std::path::Path;

use nomograph_workflow::Store;
use tempfile::TempDir;

// Serial guard because these tests mutate the process-global
// SYNTHESIST_DIR env var and cwd. Tolerate poisoning: a panicking
// test shouldn't make the remaining tests hang or fail the lock step.
fn serial_guard() -> std::sync::MutexGuard<'static, ()> {
    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
    match LOCK.lock() {
        Ok(g) => g,
        Err(poisoned) => poisoned.into_inner(),
    }
}

fn set_env(key: &str, value: Option<&str>) {
    unsafe {
        match value {
            Some(v) => std::env::set_var(key, v),
            None => std::env::remove_var(key),
        }
    }
}

fn init_store_at(dir: &Path) {
    // discover_from walks up from the given path; when the path itself
    // has no claims/ ancestry it falls through to init_at, which is the
    // behavior we rely on to bootstrap a test fixture.
    let _ = Store::discover_from(dir).unwrap();
}

#[test]
fn synthesist_dir_opens_initialized_store() {
    let _g = serial_guard();
    let tmp = TempDir::new().unwrap();
    init_store_at(tmp.path());

    // SYNTHESIST_DIR takes precedence over any cwd state; no cd needed.
    set_env("SYNTHESIST_DIR", Some(tmp.path().to_str().unwrap()));
    let store = Store::discover().expect("discover honors SYNTHESIST_DIR");
    assert_eq!(store.root(), tmp.path().join("claims"));

    set_env("SYNTHESIST_DIR", None);
}

#[test]
fn synthesist_dir_missing_path_errors() {
    let _g = serial_guard();
    set_env(
        "SYNTHESIST_DIR",
        Some("/tmp/definitely-does-not-exist-xyz-123"),
    );
    let err = match Store::discover() {
        Ok(_) => panic!("missing path must error, not silent-init"),
        Err(e) => e,
    };
    let msg = format!("{err}");
    assert!(msg.contains("does not exist"), "got: {msg}");
    set_env("SYNTHESIST_DIR", None);
}

#[test]
fn synthesist_dir_uninitialized_errors() {
    let _g = serial_guard();
    let tmp = TempDir::new().unwrap();
    // Do NOT init. Path exists but has no claims/.
    set_env("SYNTHESIST_DIR", Some(tmp.path().to_str().unwrap()));
    let err = match Store::discover() {
        Ok(_) => panic!("uninitialized path must error"),
        Err(e) => e,
    };
    let msg = format!("{err}");
    assert!(msg.contains("no `claims/genesis.amc`"), "got: {msg}");
    assert!(msg.contains("synthesist init"), "got: {msg}");
    set_env("SYNTHESIST_DIR", None);
}

#[test]
fn empty_synthesist_dir_falls_through_to_parent_walk() {
    let _g = serial_guard();
    let tmp = TempDir::new().unwrap();
    init_store_at(tmp.path());

    // Empty env var should be treated as unset. Rather than cd (which
    // cross-test races under the test runner), exercise discover_from
    // directly — it's what discover() calls when the env var is unset.
    set_env("SYNTHESIST_DIR", Some(""));
    let store = Store::discover_from(tmp.path()).expect("parent-walk finds store");
    assert_eq!(store.root(), tmp.path().join("claims"));
    set_env("SYNTHESIST_DIR", None);
}