zig-core 0.7.1

Core library for zig — workflow orchestration engine for AI coding agents
Documentation
use std::path::Path;

use super::*;

// =====================================================================
// expand_path tests
//
// Home is resolved via `env_home()` so these tests run on both Unix
// (where CI runners set `HOME`) and Windows (where runners set
// `USERPROFILE` instead).
// =====================================================================

#[test]
fn expand_path_tilde_slash() {
    let home = env_home().expect("HOME or USERPROFILE must be set");
    assert_eq!(expand_path("~/foo/bar"), format!("{home}/foo/bar"));
}

#[test]
fn expand_path_tilde_alone() {
    let home = env_home().expect("HOME or USERPROFILE must be set");
    assert_eq!(expand_path("~"), home);
}

#[test]
fn expand_path_dollar_home() {
    let home = env_home().expect("HOME or USERPROFILE must be set");
    assert_eq!(expand_path("$HOME/foo"), format!("{home}/foo"));
}

#[test]
fn expand_path_dollar_home_braces() {
    let home = env_home().expect("HOME or USERPROFILE must be set");
    assert_eq!(expand_path("${HOME}/foo"), format!("{home}/foo"));
}

#[test]
fn expand_path_dollar_home_mid_string() {
    let home = env_home().expect("HOME or USERPROFILE must be set");
    assert_eq!(
        expand_path("prefix/$HOME/suffix"),
        format!("prefix/{home}/suffix")
    );
}

#[test]
fn expand_path_absolute_unchanged() {
    assert_eq!(expand_path("/usr/local/bin"), "/usr/local/bin");
}

#[test]
fn expand_path_relative_unchanged() {
    assert_eq!(expand_path("relative/path"), "relative/path");
}

// =====================================================================
// collapse_home tests
// =====================================================================

#[test]
fn collapse_home_replaces_prefix() {
    let home = env_home().expect("HOME or USERPROFILE must be set");
    assert_eq!(
        collapse_home(&format!("{home}/.zig/workflows/foo.zwf")),
        "~/.zig/workflows/foo.zwf"
    );
}

#[test]
fn collapse_home_exact_home() {
    let home = env_home().expect("HOME or USERPROFILE must be set");
    assert_eq!(collapse_home(&home), "~");
}

#[test]
fn collapse_home_non_home_path_unchanged() {
    assert_eq!(collapse_home("/usr/local/bin"), "/usr/local/bin");
}

#[test]
fn collapse_home_relative_unchanged() {
    assert_eq!(collapse_home("relative/path"), "relative/path");
}

// =====================================================================
// Local workflow directory tests
// =====================================================================

#[test]
fn cwd_workflows_dir_from_finds_directory_in_start() {
    let tmp = tempfile::tempdir().unwrap();
    std::fs::create_dir_all(tmp.path().join(".git")).unwrap();
    std::fs::create_dir_all(tmp.path().join(".zig").join("workflows")).unwrap();

    let found = cwd_workflows_dir_from(tmp.path());
    assert!(found.is_some());
    assert!(found.unwrap().ends_with(".zig/workflows"));
}

#[test]
fn cwd_workflows_dir_from_walks_up_to_find_directory() {
    let tmp = tempfile::tempdir().unwrap();
    std::fs::create_dir_all(tmp.path().join(".git")).unwrap();
    std::fs::create_dir_all(tmp.path().join(".zig").join("workflows")).unwrap();
    let nested = tmp.path().join("a").join("b").join("c");
    std::fs::create_dir_all(&nested).unwrap();

    let found = cwd_workflows_dir_from(&nested);
    assert!(found.is_some());
    let abs = std::fs::canonicalize(found.unwrap()).unwrap();
    let expected = std::fs::canonicalize(tmp.path().join(".zig").join("workflows")).unwrap();
    assert_eq!(abs, expected);
}

#[test]
fn cwd_workflows_dir_from_returns_none_when_absent() {
    let tmp = tempfile::tempdir().unwrap();
    std::fs::create_dir_all(tmp.path().join(".git")).unwrap();
    let nested = tmp.path().join("a").join("b");
    std::fs::create_dir_all(&nested).unwrap();

    let found = cwd_workflows_dir_from(&nested);
    assert!(found.is_none());
}

#[test]
fn cwd_workflows_dir_from_does_not_walk_past_git_root() {
    let tmp = tempfile::tempdir().unwrap();
    let outside = tmp.path().join("outside");
    std::fs::create_dir_all(outside.join(".zig").join("workflows")).unwrap();
    let repo = outside.join("repo");
    std::fs::create_dir_all(repo.join(".git")).unwrap();
    let sub = repo.join("sub");
    std::fs::create_dir_all(&sub).unwrap();

    let found = cwd_workflows_dir_from(&sub);
    assert!(
        found.is_none(),
        "walk should stop at git root, not see outside/.zig/workflows, but got {found:?}"
    );
}

#[test]
fn global_workflows_dir_from_returns_correct_path() {
    let home = Path::new("/home/testuser");
    let result = global_workflows_dir_from(home);
    assert_eq!(result, Path::new("/home/testuser/.zig/workflows"));
}

#[test]
fn ensure_global_workflows_dir_creates_directories() {
    let dir = tempfile::tempdir().unwrap();
    let workflows_dir = global_workflows_dir_from(dir.path());

    assert!(!workflows_dir.exists());

    std::fs::create_dir_all(&workflows_dir).unwrap();

    assert!(workflows_dir.exists());
    assert!(workflows_dir.is_dir());
}

#[test]
fn global_resources_dir_from_returns_correct_path() {
    let home = Path::new("/home/testuser");
    let result = global_resources_dir_from(home);
    assert_eq!(result, Path::new("/home/testuser/.zig/resources"));
}

#[test]
fn global_examples_dir_uses_base() {
    // global_examples_dir() returns ~/.zig/examples/ — we can't easily
    // assert the full path (HOME-dependent), but we can check the suffix.
    if let Some(dir) = global_examples_dir() {
        assert!(dir.ends_with("examples"));
        assert_eq!(dir.parent().unwrap().file_name().unwrap(), ".zig");
    }
}

#[test]
fn cwd_resources_dir_from_finds_directory_in_start() {
    let tmp = tempfile::tempdir().unwrap();
    // Make this look like a git root so the walk-up has a stop boundary.
    std::fs::create_dir_all(tmp.path().join(".git")).unwrap();
    std::fs::create_dir_all(tmp.path().join(".zig").join("resources")).unwrap();

    let found = cwd_resources_dir_from(tmp.path());
    assert!(found.is_some());
    assert!(found.unwrap().ends_with(".zig/resources"));
}

#[test]
fn cwd_resources_dir_from_walks_up_to_find_directory() {
    let tmp = tempfile::tempdir().unwrap();
    std::fs::create_dir_all(tmp.path().join(".git")).unwrap();
    std::fs::create_dir_all(tmp.path().join(".zig").join("resources")).unwrap();
    let nested = tmp.path().join("a").join("b").join("c");
    std::fs::create_dir_all(&nested).unwrap();

    let found = cwd_resources_dir_from(&nested);
    assert!(found.is_some());
    let abs = std::fs::canonicalize(found.unwrap()).unwrap();
    let expected = std::fs::canonicalize(tmp.path().join(".zig").join("resources")).unwrap();
    assert_eq!(abs, expected);
}

#[test]
fn cwd_resources_dir_from_returns_none_when_absent() {
    let tmp = tempfile::tempdir().unwrap();
    std::fs::create_dir_all(tmp.path().join(".git")).unwrap();
    let nested = tmp.path().join("a").join("b");
    std::fs::create_dir_all(&nested).unwrap();

    let found = cwd_resources_dir_from(&nested);
    assert!(found.is_none());
}

#[test]
fn cwd_resources_dir_from_does_not_walk_past_git_root() {
    // Create:
    //   <tmp>/outside/.zig/resources       (should NOT be found)
    //   <tmp>/outside/repo/.git              (git root stops the walk)
    //   <tmp>/outside/repo/sub               (start here)
    let tmp = tempfile::tempdir().unwrap();
    let outside = tmp.path().join("outside");
    std::fs::create_dir_all(outside.join(".zig").join("resources")).unwrap();
    let repo = outside.join("repo");
    std::fs::create_dir_all(repo.join(".git")).unwrap();
    let sub = repo.join("sub");
    std::fs::create_dir_all(&sub).unwrap();

    let found = cwd_resources_dir_from(&sub);
    assert!(
        found.is_none(),
        "walk should stop at git root, not see outside/.zig/resources, but got {found:?}"
    );
}