Remove arc() and fabro() helpers in favor of TestContext

Replace all ad-hoc arc()/fabro() command helpers with TestContext methods
(command(), run_cmd(), validate(), exec_cmd(), etc.) across all cmd/
test files. This eliminates 9 duplicate helper definitions and gives
every test consistent isolation (HOME, NO_COLOR, FABRO_STORAGE_DIR,
FABRO_NO_UPGRADE_CHECK).

Also removes init_cli_home() helper — TestContext's env-var-based
FABRO_STORAGE_DIR makes it unnecessary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-30 08:44:56 -04:00
parent d365e713b5
commit 9a762746a2
No known key found for this signature in database
8 changed files with 330 additions and 378 deletions

View file

@ -1,3 +1,5 @@
use std::path::PathBuf;
use fabro_config::FabroSettings;
use fabro_config::mcp::McpTransport;
#[cfg(feature = "server")]
@ -64,22 +66,14 @@ fn show_help() {
// Helpers
// ---------------------------------------------------------------------------
#[allow(deprecated)]
fn arc() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.arg("--no-upgrade-check");
cmd
}
fn parse_config_show(stdout: &[u8]) -> FabroSettings {
serde_yaml::from_slice(stdout).expect("stdout should be valid YAML FabroSettings")
}
fn setup_config_show_fixture() -> (tempfile::TempDir, tempfile::TempDir) {
let home = tempfile::tempdir().unwrap();
let project = tempfile::tempdir().unwrap();
let home_fabro = home.path().join(".fabro");
/// Set up home config and project config for config show tests.
/// Uses `context.home_dir` for the home directory. Returns project tempdir.
fn setup_config_show_fixture(context: &fabro_test::TestContext) -> tempfile::TempDir {
let home_fabro = context.home_dir.join(".fabro");
std::fs::create_dir_all(&home_fabro).unwrap();
std::fs::write(
home_fabro.join("user.toml"),
@ -119,6 +113,7 @@ SHARED = "cli"
)
.unwrap();
let project = tempfile::tempdir().unwrap();
std::fs::write(
project.path().join("fabro.toml"),
r#"
@ -195,15 +190,17 @@ SHARED = "run"
)
.unwrap();
(home, project)
project
}
fn setup_external_workflow_fixture() -> (tempfile::TempDir, tempfile::TempDir, std::path::PathBuf) {
let home = tempfile::tempdir().unwrap();
let project = tempfile::tempdir().unwrap();
let storage_dir = home.path().join("fabro-data");
/// Set up an external workflow fixture with a custom storage_dir in user.toml.
/// Returns (project_tempdir, storage_dir_path).
fn setup_external_workflow_fixture(
context: &fabro_test::TestContext,
) -> (tempfile::TempDir, PathBuf) {
let storage_dir = context.home_dir.join("fabro-data");
let home_fabro = home.path().join(".fabro");
let home_fabro = context.home_dir.join(".fabro");
std::fs::create_dir_all(&home_fabro).unwrap();
std::fs::write(
home_fabro.join("user.toml"),
@ -220,6 +217,7 @@ commands = ["cli-setup"]
)
.unwrap();
let project = tempfile::tempdir().unwrap();
std::fs::write(
project.path().join("fabro.toml"),
r#"
@ -262,7 +260,7 @@ commands = ["workflow-setup"]
)
.unwrap();
(home, project, storage_dir)
(project, storage_dir)
}
// ---------------------------------------------------------------------------
@ -271,10 +269,11 @@ commands = ["workflow-setup"]
#[test]
fn config_show_merges_cli_and_project_defaults() {
let (home, project) = setup_config_show_fixture();
let context = test_context!();
let project = setup_config_show_fixture(&context);
let output = arc()
.env("HOME", home.path())
let output = context
.command()
.current_dir(project.path())
.args(["config", "show"])
.assert()
@ -307,10 +306,11 @@ fn config_show_merges_cli_and_project_defaults() {
#[test]
fn config_show_workflow_name_applies_run_overlay_and_deep_merges() {
let (home, project) = setup_config_show_fixture();
let context = test_context!();
let project = setup_config_show_fixture(&context);
let output = arc()
.env("HOME", home.path())
let output = context
.command()
.current_dir(project.path())
.args(["config", "show", "demo"])
.assert()
@ -382,12 +382,15 @@ fn config_show_workflow_name_applies_run_overlay_and_deep_merges() {
#[test]
fn config_show_explicit_workflow_path_uses_workflow_project_layers() {
let (home, project, _storage_dir) = setup_external_workflow_fixture();
let context = test_context!();
let (project, _storage_dir) = setup_external_workflow_fixture(&context);
let cwd = tempfile::tempdir().unwrap();
let workflow = project.path().join("workflow.toml");
let output = arc()
.env("HOME", home.path())
// Remove FABRO_STORAGE_DIR so the CLI uses storage_dir from user.toml
let output = context
.command()
.env_remove("FABRO_STORAGE_DIR")
.current_dir(cwd.path())
.args(["config", "show", workflow.to_str().unwrap()])
.assert()
@ -414,13 +417,16 @@ fn config_show_explicit_workflow_path_uses_workflow_project_layers() {
#[test]
fn create_explicit_workflow_path_uses_project_config_relative_to_workflow() {
let (home, project, storage_dir) = setup_external_workflow_fixture();
let context = test_context!();
let (project, storage_dir) = setup_external_workflow_fixture(&context);
let cwd = tempfile::tempdir().unwrap();
let workflow = project.path().join("workflow.toml");
let run_id = "01ARZ3NDEKTSV4RRFFQ69G5FB8";
arc()
.env("HOME", home.path())
// Remove FABRO_STORAGE_DIR so the CLI uses storage_dir from user.toml
context
.command()
.env_remove("FABRO_STORAGE_DIR")
.current_dir(cwd.path())
.args([
"create",
@ -475,10 +481,11 @@ fn create_explicit_workflow_path_uses_project_config_relative_to_workflow() {
#[test]
fn config_show_fabro_path_matches_ambient_defaults() {
let (home, project) = setup_config_show_fixture();
let context = test_context!();
let project = setup_config_show_fixture(&context);
let ambient = arc()
.env("HOME", home.path())
let ambient = context
.command()
.current_dir(project.path())
.args(["config", "show"])
.assert()
@ -486,8 +493,8 @@ fn config_show_fabro_path_matches_ambient_defaults() {
.get_output()
.stdout
.clone();
let graph = arc()
.env("HOME", home.path())
let graph = context
.command()
.current_dir(project.path())
.args(["config", "show", "standalone.fabro"])
.assert()
@ -501,10 +508,11 @@ fn config_show_fabro_path_matches_ambient_defaults() {
#[test]
fn config_show_missing_run_config_errors() {
let (home, project) = setup_config_show_fixture();
let context = test_context!();
let project = setup_config_show_fixture(&context);
arc()
.env("HOME", home.path())
context
.command()
.current_dir(project.path())
.args(["config", "show", "missing.toml"])
.assert()
@ -514,10 +522,10 @@ fn config_show_missing_run_config_errors() {
#[test]
fn config_show_legacy_cli_config_warns_and_ignores_it() {
let home = tempfile::tempdir().unwrap();
let context = test_context!();
let project = tempfile::tempdir().unwrap();
let home_fabro = home.path().join(".fabro");
let home_fabro = context.home_dir.join(".fabro");
std::fs::create_dir_all(&home_fabro).unwrap();
std::fs::write(
home_fabro.join("cli.toml"),
@ -530,8 +538,8 @@ model = "legacy-model"
)
.unwrap();
let assert = arc()
.env("HOME", home.path())
let assert = context
.command()
.current_dir(project.path())
.args(["config", "show"])
.assert()
@ -546,9 +554,10 @@ model = "legacy-model"
#[test]
fn config_show_user_config_wins_over_legacy_cli_config() {
let (home, project) = setup_config_show_fixture();
let context = test_context!();
let project = setup_config_show_fixture(&context);
std::fs::write(
home.path().join(".fabro").join("cli.toml"),
context.home_dir.join(".fabro").join("cli.toml"),
r#"
[llm]
model = "legacy-model"
@ -559,8 +568,8 @@ shared = "legacy"
)
.unwrap();
let assert = arc()
.env("HOME", home.path())
let assert = context
.command()
.current_dir(project.path())
.args(["config", "show"])
.assert()
@ -581,8 +590,9 @@ shared = "legacy"
#[test]
#[cfg(feature = "server")]
fn config_show_server_url_overrides_cli_defaults() {
let (home, project) = setup_config_show_fixture();
let user_toml = home.path().join(".fabro").join("user.toml");
let context = test_context!();
let project = setup_config_show_fixture(&context);
let user_toml = context.home_dir.join(".fabro").join("user.toml");
std::fs::write(
&user_toml,
format!(
@ -592,8 +602,8 @@ fn config_show_server_url_overrides_cli_defaults() {
)
.unwrap();
let output = arc()
.env("HOME", home.path())
let output = context
.command()
.current_dir(project.path())
.args(["--server-url", "https://cli.example.com", "config", "show"])
.assert()

View file

@ -1,20 +1,6 @@
use fabro_test::{fabro_snapshot, test_context};
use predicates::prelude::*;
#[allow(deprecated)]
fn arc() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.arg("--no-upgrade-check");
cmd
}
#[allow(deprecated)]
fn fabro() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.env("NO_COLOR", "1");
cmd
}
#[test]
fn help() {
let context = test_context!();
@ -80,15 +66,16 @@ fn dry_run_flag() {
#[ignore = "scenario: requires ANTHROPIC_API_KEY"]
fn live_doctor() {
dotenvy::dotenv().ok();
fabro().args(["doctor"]).assert().success();
let context = test_context!();
context.doctor().assert().success();
}
#[test]
fn doctor_no_color_when_no_color_set() {
arc()
.args(["doctor", "--dry-run"])
.env_clear()
.env("NO_COLOR", "1")
.assert()
.stdout(predicate::str::contains("\x1b[").not());
let context = test_context!();
let mut cmd = context.doctor();
cmd.arg("--dry-run");
cmd.env_clear();
cmd.env("NO_COLOR", "1");
cmd.assert().stdout(predicate::str::contains("\x1b[").not());
}

View file

@ -1,13 +1,6 @@
use fabro_test::{fabro_snapshot, test_context};
use predicates::prelude::*;
#[allow(deprecated)]
fn arc() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.arg("--no-upgrade-check");
cmd
}
#[test]
fn invalid_permissions() {
let context = test_context!();
@ -44,13 +37,13 @@ fn no_prompt() {
#[test]
fn exec_missing_api_key_exits_with_error() {
let tmp = tempfile::tempdir().expect("tempdir");
arc()
.args(["exec", "test prompt"])
.env_clear()
.env("HOME", tmp.path().to_str().unwrap())
.current_dir(tmp.path())
.assert()
let context = test_context!();
let mut cmd = context.exec_cmd();
cmd.arg("test prompt");
cmd.env_clear();
cmd.env("HOME", &context.home_dir);
cmd.current_dir(&context.temp_dir);
cmd.assert()
.failure()
.stderr(predicate::str::contains("API key not set"));
}
@ -59,10 +52,10 @@ fn exec_missing_api_key_exits_with_error() {
#[ignore = "requires API key"]
fn exec_creates_file() {
dotenvy::dotenv().ok();
let tmp = tempfile::tempdir().expect("tempdir");
arc()
let context = test_context!();
context
.exec_cmd()
.args([
"exec",
"--auto-approve",
"--permissions",
"full",
@ -72,11 +65,11 @@ fn exec_creates_file() {
"claude-haiku-4-5",
"Create a file called hello.txt containing exactly 'Hello'",
])
.current_dir(tmp.path())
.current_dir(&context.temp_dir)
.timeout(std::time::Duration::from_secs(120))
.assert()
.success();
let path = tmp.path().join("hello.txt");
let path = context.temp_dir.join("hello.txt");
assert!(path.exists(), "hello.txt should have been created");
let content = std::fs::read_to_string(&path).expect("read hello.txt");
assert!(
@ -89,10 +82,10 @@ fn exec_creates_file() {
#[ignore = "requires API key"]
fn exec_shell_command() {
dotenvy::dotenv().ok();
let tmp = tempfile::tempdir().expect("tempdir");
arc()
let context = test_context!();
context
.exec_cmd()
.args([
"exec",
"--auto-approve",
"--permissions",
"full",
@ -102,7 +95,7 @@ fn exec_shell_command() {
"claude-haiku-4-5",
"Run the shell command `echo arc_test_marker_42` and tell me what it printed",
])
.current_dir(tmp.path())
.current_dir(&context.temp_dir)
.timeout(std::time::Duration::from_secs(120))
.assert()
.success();
@ -112,10 +105,10 @@ fn exec_shell_command() {
#[ignore = "requires API key"]
fn exec_read_only_blocks_write() {
dotenvy::dotenv().ok();
let tmp = tempfile::tempdir().expect("tempdir");
arc()
let context = test_context!();
context
.exec_cmd()
.args([
"exec",
"--auto-approve",
"--permissions",
"read-only",
@ -125,12 +118,12 @@ fn exec_read_only_blocks_write() {
"claude-haiku-4-5",
"Create a file called forbidden.txt containing 'should not exist'",
])
.current_dir(tmp.path())
.current_dir(&context.temp_dir)
.timeout(std::time::Duration::from_secs(120))
.assert()
.success();
assert!(
!tmp.path().join("forbidden.txt").exists(),
!context.temp_dir.join("forbidden.txt").exists(),
"forbidden.txt should NOT exist under read-only permissions"
);
}
@ -139,10 +132,10 @@ fn exec_read_only_blocks_write() {
#[ignore = "requires API key"]
fn exec_json_output_format() {
dotenvy::dotenv().ok();
let tmp = tempfile::tempdir().expect("tempdir");
let output = arc()
let context = test_context!();
let output = context
.exec_cmd()
.args([
"exec",
"--auto-approve",
"--permissions",
"full",
@ -154,7 +147,7 @@ fn exec_json_output_format() {
"claude-haiku-4-5",
"Create a file called test.txt containing 'test'",
])
.current_dir(tmp.path())
.current_dir(&context.temp_dir)
.timeout(std::time::Duration::from_secs(120))
.assert()
.success()
@ -178,11 +171,11 @@ fn exec_json_output_format() {
#[ignore = "requires API key"]
fn exec_read_and_edit() {
dotenvy::dotenv().ok();
let tmp = tempfile::tempdir().expect("tempdir");
std::fs::write(tmp.path().join("data.txt"), "old content").expect("write data.txt");
arc()
let context = test_context!();
std::fs::write(context.temp_dir.join("data.txt"), "old content").expect("write data.txt");
context
.exec_cmd()
.args([
"exec",
"--auto-approve",
"--permissions",
"full",
@ -192,11 +185,12 @@ fn exec_read_and_edit() {
"claude-haiku-4-5",
"Read data.txt then replace its entire content with 'new content'",
])
.current_dir(tmp.path())
.current_dir(&context.temp_dir)
.timeout(std::time::Duration::from_secs(120))
.assert()
.success();
let content = std::fs::read_to_string(tmp.path().join("data.txt")).expect("read data.txt");
let content =
std::fs::read_to_string(context.temp_dir.join("data.txt")).expect("read data.txt");
assert!(
content.contains("new content"),
"Expected 'new content' in data.txt, got: {content}"

View file

@ -1,13 +1,6 @@
use fabro_test::{fabro_snapshot, test_context};
use predicates::prelude::*;
#[allow(deprecated)]
fn arc() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.arg("--no-upgrade-check");
cmd
}
#[test]
fn prompt_bad_option() {
let context = test_context!();
@ -64,8 +57,10 @@ fn prompt_schema_invalid() {
#[test]
fn prompt_reads_from_stdin() {
let result = arc()
.args(["llm", "prompt", "--no-stream", "-m", "test-model"])
let context = test_context!();
let result = context
.llm()
.args(["prompt", "--no-stream", "-m", "test-model"])
.write_stdin("hello from stdin")
.assert()
.failure();
@ -76,9 +71,10 @@ fn prompt_reads_from_stdin() {
#[test]
fn prompt_concatenates_stdin_and_arg() {
let result = arc()
let context = test_context!();
let result = context
.llm()
.args([
"llm",
"prompt",
"--no-stream",
"-m",
@ -95,9 +91,11 @@ fn prompt_concatenates_stdin_and_arg() {
#[test]
#[ignore = "requires API key"]
fn prompt_no_stream_generates_response() {
arc()
dotenvy::dotenv().ok();
let context = test_context!();
context
.llm()
.args([
"llm",
"prompt",
"--no-stream",
"-m",
@ -112,9 +110,11 @@ fn prompt_no_stream_generates_response() {
#[test]
#[ignore = "requires API key"]
fn prompt_stream_generates_response() {
arc()
dotenvy::dotenv().ok();
let context = test_context!();
context
.llm()
.args([
"llm",
"prompt",
"-m",
"claude-sonnet-4-5",
@ -128,9 +128,11 @@ fn prompt_stream_generates_response() {
#[test]
#[ignore = "requires API key"]
fn prompt_usage_shows_tokens() {
arc()
dotenvy::dotenv().ok();
let context = test_context!();
context
.llm()
.args([
"llm",
"prompt",
"--no-stream",
"-u",
@ -146,9 +148,12 @@ fn prompt_usage_shows_tokens() {
#[test]
#[ignore = "requires API key"]
fn prompt_schema_no_stream_generates_json() {
let assert = arc()
dotenvy::dotenv().ok();
let context = test_context!();
let assert = context
.llm()
.args([
"llm", "prompt", "--no-stream", "-m", "claude-sonnet-4-5",
"prompt", "--no-stream", "-m", "claude-sonnet-4-5",
"--schema", r#"{"type":"object","properties":{"greeting":{"type":"string"}},"required":["greeting"]}"#,
"Return a JSON object with a greeting field set to hello",
])
@ -167,9 +172,12 @@ fn prompt_schema_no_stream_generates_json() {
#[test]
#[ignore = "requires API key"]
fn prompt_schema_stream_generates_json() {
let assert = arc()
dotenvy::dotenv().ok();
let context = test_context!();
let assert = context
.llm()
.args([
"llm", "prompt", "-m", "claude-sonnet-4-5",
"prompt", "-m", "claude-sonnet-4-5",
"--schema", r#"{"type":"object","properties":{"greeting":{"type":"string"}},"required":["greeting"]}"#,
"Return a JSON object with a greeting field set to hello",
])
@ -188,7 +196,10 @@ fn prompt_schema_stream_generates_json() {
#[test]
#[ignore = "requires API key"]
fn chat_multi_turn_with_system_prompt() {
let assert = arc()
dotenvy::dotenv().ok();
let context = test_context!();
let assert = context
.command()
.args([
"llm",
"chat",

View file

@ -1,13 +1,6 @@
use fabro_test::{fabro_snapshot, test_context};
use predicates;
#[allow(deprecated)]
fn fabro() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.env("NO_COLOR", "1");
cmd
}
fn init_git_repo(path: &std::path::Path) {
std::process::Command::new("git")
.args(["init"])
@ -55,37 +48,39 @@ fn help() {
#[test]
fn test_repo_deinit_removes_fabro_toml_and_dir() {
let tmp = tempfile::tempdir().unwrap();
init_git_repo(tmp.path());
init_fabro_project(tmp.path());
let context = test_context!();
init_git_repo(&context.temp_dir);
init_fabro_project(&context.temp_dir);
assert!(tmp.path().join("fabro.toml").exists());
assert!(tmp.path().join("fabro").exists());
assert!(context.temp_dir.join("fabro.toml").exists());
assert!(context.temp_dir.join("fabro").exists());
fabro()
.args(["repo", "deinit"])
.current_dir(tmp.path())
context
.repo()
.arg("deinit")
.current_dir(&context.temp_dir)
.assert()
.success();
assert!(
!tmp.path().join("fabro.toml").exists(),
!context.temp_dir.join("fabro.toml").exists(),
"fabro.toml should be removed"
);
assert!(
!tmp.path().join("fabro").exists(),
!context.temp_dir.join("fabro").exists(),
"fabro/ directory should be removed"
);
}
#[test]
fn test_repo_deinit_fails_when_not_initialized() {
let tmp = tempfile::tempdir().unwrap();
init_git_repo(tmp.path());
let context = test_context!();
init_git_repo(&context.temp_dir);
fabro()
.args(["repo", "deinit"])
.current_dir(tmp.path())
context
.repo()
.arg("deinit")
.current_dir(&context.temp_dir)
.assert()
.failure()
.stderr(predicates::str::contains("not initialized"));
@ -93,17 +88,20 @@ fn test_repo_deinit_fails_when_not_initialized() {
#[test]
fn test_repo_init_skill_installs_skill_files() {
let tmp = tempfile::tempdir().unwrap();
init_git_repo(tmp.path());
let context = test_context!();
init_git_repo(&context.temp_dir);
fabro()
.args(["repo", "init", "--skill"])
.current_dir(tmp.path())
context
.repo()
.args(["init", "--skill"])
.current_dir(&context.temp_dir)
.assert()
.success();
// Skill files should be installed under .claude/skills/fabro-create-workflow/
let skill_dir = tmp.path().join(".claude/skills/fabro-create-workflow");
let skill_dir = context
.temp_dir
.join(".claude/skills/fabro-create-workflow");
assert!(skill_dir.join("SKILL.md").exists(), "SKILL.md should exist");
assert!(
skill_dir.join("references/dot-language.md").exists(),
@ -113,7 +111,8 @@ fn test_repo_init_skill_installs_skill_files() {
#[test]
fn test_repo_init_help_does_not_show_skill() {
let out = fabro().args(["repo", "init", "--help"]).assert().success();
let context = test_context!();
let out = context.repo().args(["init", "--help"]).assert().success();
let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
assert!(
!stdout.contains("--skill"),

View file

@ -19,26 +19,6 @@ fn fixture(name: &str) -> std::path::PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(format!("../../../test/{name}"))
}
#[allow(deprecated)]
fn arc() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.arg("--no-upgrade-check");
cmd
}
fn init_cli_home(storage_dir: &Path) -> tempfile::TempDir {
let home = tempfile::tempdir().unwrap();
let home_fabro = home.path().join(".fabro");
std::fs::create_dir_all(&home_fabro).unwrap();
let storage_dir = serde_json::to_string(&storage_dir.to_string_lossy().into_owned()).unwrap();
std::fs::write(
home_fabro.join("user.toml"),
format!("storage_dir = {storage_dir}\n"),
)
.unwrap();
home
}
fn list_metadata_run_ids(repo_dir: &Path) -> BTreeSet<String> {
let repo = Repository::discover(repo_dir).unwrap();
repo.references()
@ -217,12 +197,12 @@ fn latest_metadata_checkpoint(repo_dir: &Path, run_id: &str) -> Checkpoint {
/// Helper: create a minimal run directory that `resolve_run` can find.
/// Sets up run.json, status.json, and progress.jsonl.
fn setup_run_dir(
home: &std::path::Path,
storage_dir: &std::path::Path,
run_id: &str,
spec_overrides: serde_json::Value,
progress_lines: &[&str],
) -> std::path::PathBuf {
let run_dir = home.join(".fabro").join("runs").join(run_id);
let run_dir = storage_dir.join("runs").join(run_id);
std::fs::create_dir_all(&run_dir).unwrap();
// Build defaults, then merge overrides
@ -282,8 +262,8 @@ fn setup_run_dir(
run_dir
}
fn find_run_dir(home: &std::path::Path, run_id: &str) -> std::path::PathBuf {
let runs_dir = home.join(".fabro").join("runs");
fn find_run_dir(storage_dir: &std::path::Path, run_id: &str) -> std::path::PathBuf {
let runs_dir = storage_dir.join("runs");
std::fs::read_dir(&runs_dir)
.unwrap()
.flatten()
@ -535,23 +515,21 @@ fn dry_run_legacy_tool() {
#[test]
fn dry_run_writes_jsonl_and_live_json() {
let tmp = tempfile::tempdir().unwrap();
let storage_dir = tmp.path().join("fabro-data");
let context = test_context!();
arc()
context
.command()
.args([
"run",
"--dry-run",
"--auto-approve",
"--storage-dir",
storage_dir.to_str().unwrap(),
"../../../test/simple.fabro",
])
.assert()
.success();
// Find the single run directory under storage_dir/runs/
let runs_base = storage_dir.join("runs");
let runs_base = context.storage_dir.join("runs");
assert!(runs_base.exists(), "runs/ directory should exist");
let entries: Vec<_> = std::fs::read_dir(&runs_base)
.unwrap()
@ -605,19 +583,17 @@ fn dry_run_writes_jsonl_and_live_json() {
#[test]
fn run_id_passthrough_uses_provided_ulid() {
let tmp = tempfile::tempdir().unwrap();
let storage_dir = tmp.path().join("fabro-data");
let my_ulid = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
let context = test_context!();
arc()
context
.command()
.args([
"run",
"--dry-run",
"--auto-approve",
"--run-id",
my_ulid,
"--storage-dir",
storage_dir.to_str().unwrap(),
"../../../test/simple.fabro",
])
.assert()
@ -629,7 +605,9 @@ fn run_id_passthrough_uses_provided_ulid() {
#[test]
fn detach_flag_appears_in_help() {
arc()
let context = test_context!();
context
.command()
.args(["run", "--help"])
.assert()
.success()
@ -638,7 +616,9 @@ fn detach_flag_appears_in_help() {
#[test]
fn detach_prints_ulid_and_exits() {
let output = arc()
let context = test_context!();
let output = context
.command()
.args([
"run",
"--detach",
@ -664,17 +644,15 @@ fn detach_prints_ulid_and_exits() {
#[test]
fn detach_creates_run_dir_with_detach_log() {
let tmp = tempfile::tempdir().unwrap();
let storage_dir = tmp.path().join("fabro-data");
let context = test_context!();
let output = arc()
let output = context
.command()
.args([
"run",
"--detach",
"--dry-run",
"--auto-approve",
"--storage-dir",
storage_dir.to_str().unwrap(),
"../../../test/simple.fabro",
])
.assert()
@ -689,7 +667,7 @@ fn detach_creates_run_dir_with_detach_log() {
// Run dir should have been created under storage_dir/runs/ and the launcher
// log should live under storage_dir/launchers/.
let runs_base = storage_dir.join("runs");
let runs_base = context.storage_dir.join("runs");
assert!(runs_base.exists(), "runs/ directory should exist");
let entries: Vec<_> = std::fs::read_dir(&runs_base)
.unwrap()
@ -698,7 +676,8 @@ fn detach_creates_run_dir_with_detach_log() {
assert_eq!(entries.len(), 1, "should have exactly one run directory");
let run_dir = entries[0].path();
assert!(
storage_dir
context
.storage_dir
.join("launchers")
.join(format!("{ulid}.log"))
.exists(),
@ -711,7 +690,9 @@ fn detach_creates_run_dir_with_detach_log() {
#[test]
fn resume_help_shows_expected_args() {
arc()
let context = test_context!();
context
.command()
.args(["resume", "--help"])
.assert()
.success()
@ -722,12 +703,15 @@ fn resume_help_shows_expected_args() {
#[test]
fn resume_requires_run_arg() {
arc().args(["resume"]).assert().failure();
let context = test_context!();
context.command().args(["resume"]).assert().failure();
}
#[test]
fn run_help_no_longer_shows_resume_or_run_branch() {
arc()
let context = test_context!();
context
.command()
.args(["run", "--help"])
.assert()
.success()
@ -737,16 +721,14 @@ fn run_help_no_longer_shows_resume_or_run_branch() {
#[test]
fn rewind_and_fork_recover_missing_metadata_from_store() {
let storage_root = tempfile::tempdir().unwrap();
let storage_dir = storage_root.path().join("fabro-data");
let configured_home = init_cli_home(&storage_dir);
let context = test_context!();
let repo_dir = tempfile::tempdir().unwrap();
Repository::init(repo_dir.path()).unwrap();
let source_run_id = "01ARZ3NDEKTSV4RRFFQ69G5FAW";
let expected_shas = seed_run_branch(repo_dir.path(), source_run_id, &["start", "build"]);
Runtime::new().unwrap().block_on(seed_durable_run(
&storage_dir,
&context.storage_dir,
repo_dir.path(),
source_run_id,
));
@ -756,9 +738,8 @@ fn rewind_and_fork_recover_missing_metadata_from_store() {
"metadata branch should start missing"
);
let rewind_list = arc()
.env("HOME", configured_home.path())
.env("NO_COLOR", "1")
let rewind_list = context
.command()
.current_dir(repo_dir.path())
.args(["rewind", source_run_id, "--list"])
.timeout(Duration::from_secs(15))
@ -793,9 +774,8 @@ fn rewind_and_fork_recover_missing_metadata_from_store() {
);
let before_child = list_metadata_run_ids(repo_dir.path());
arc()
.env("HOME", configured_home.path())
.env("NO_COLOR", "1")
context
.command()
.current_dir(repo_dir.path())
.args(["fork", source_run_id, "--no-push"])
.timeout(Duration::from_secs(15))
@ -812,9 +792,8 @@ fn rewind_and_fork_recover_missing_metadata_from_store() {
Some(expected_shas[1].as_str())
);
let child_rewind = arc()
.env("HOME", configured_home.path())
.env("NO_COLOR", "1")
let child_rewind = context
.command()
.current_dir(repo_dir.path())
.args(["rewind", child_run_id, "@1", "--no-push"])
.timeout(Duration::from_secs(15))
@ -834,9 +813,8 @@ fn rewind_and_fork_recover_missing_metadata_from_store() {
);
let before_grandchild = after_child;
arc()
.env("HOME", configured_home.path())
.env("NO_COLOR", "1")
context
.command()
.current_dir(repo_dir.path())
.args(["fork", child_run_id, "--no-push"])
.timeout(Duration::from_secs(15))
@ -854,7 +832,7 @@ fn rewind_and_fork_recover_missing_metadata_from_store() {
#[test]
fn completed_run_preserves_workflow_slug_for_lookup() {
let home = tempfile::tempdir().unwrap();
let context = test_context!();
let project = tempfile::tempdir().unwrap();
let workflow_dir = project.path().join("workflows").join("sluggy");
std::fs::create_dir_all(&workflow_dir).unwrap();
@ -871,8 +849,8 @@ digraph BarBaz {
)
.unwrap();
arc()
.env("HOME", home.path())
context
.command()
.current_dir(project.path())
.args([
"create",
@ -885,30 +863,30 @@ digraph BarBaz {
.assert()
.success();
arc()
.env("HOME", home.path())
context
.command()
.current_dir(project.path())
.args(["start", "sluggy"])
.assert()
.success();
arc()
.env("HOME", home.path())
context
.command()
.current_dir(project.path())
.args(["attach", "01ARZ3NDEKTSV4RRFFQ69G5FAX"])
.timeout(std::time::Duration::from_secs(10))
.assert()
.success();
arc()
.env("HOME", home.path())
context
.command()
.current_dir(project.path())
.args(["attach", "sluggy"])
.timeout(std::time::Duration::from_secs(10))
.assert()
.success();
let run_dir = find_run_dir(home.path(), "01ARZ3NDEKTSV4RRFFQ69G5FAX");
let run_dir = find_run_dir(&context.storage_dir, "01ARZ3NDEKTSV4RRFFQ69G5FAX");
let run_record: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(run_dir.join("run.json")).unwrap()).unwrap();
assert_eq!(run_record["graph"]["name"].as_str(), Some("BarBaz"));
@ -917,7 +895,7 @@ digraph BarBaz {
#[test]
fn standalone_file_run_uses_file_stem_slug_for_lookup() {
let home = tempfile::tempdir().unwrap();
let context = test_context!();
let workflow_dir = tempfile::tempdir().unwrap();
let workflow_path = workflow_dir.path().join("alpha.fabro");
std::fs::write(
@ -932,8 +910,8 @@ digraph FooWorkflow {
)
.unwrap();
arc()
.env("HOME", home.path())
context
.command()
.args([
"create",
"--dry-run",
@ -945,20 +923,20 @@ digraph FooWorkflow {
.assert()
.success();
arc()
.env("HOME", home.path())
context
.command()
.args(["start", "alpha"])
.assert()
.success();
arc()
.env("HOME", home.path())
context
.command()
.args(["attach", "alpha"])
.timeout(std::time::Duration::from_secs(10))
.assert()
.success();
let run_dir = find_run_dir(home.path(), "01ARZ3NDEKTSV4RRFFQ69G5FAY");
let run_dir = find_run_dir(&context.storage_dir, "01ARZ3NDEKTSV4RRFFQ69G5FAY");
let run_record: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(run_dir.join("run.json")).unwrap()).unwrap();
assert_eq!(run_record["graph"]["name"].as_str(), Some("FooWorkflow"));
@ -967,11 +945,11 @@ digraph FooWorkflow {
#[test]
fn dry_run_create_start_attach_works_with_default_run_lookup() {
let home = tempfile::tempdir().unwrap();
let run_id = "01ARZ3NDEKTSV4RRFFQ69G5FAZ";
let context = test_context!();
arc()
.env("HOME", home.path())
context
.command()
.args([
"create",
"--dry-run",
@ -984,20 +962,16 @@ fn dry_run_create_start_attach_works_with_default_run_lookup() {
.success()
.stdout(predicate::str::contains(run_id));
let run_dir = find_run_dir(home.path(), run_id);
let run_dir = find_run_dir(&context.storage_dir, run_id);
assert!(
run_dir.join("run.json").exists(),
"create should persist run.json so the run is discoverable"
);
arc()
.env("HOME", home.path())
.args(["start", run_id])
.assert()
.success();
context.command().args(["start", run_id]).assert().success();
arc()
.env("HOME", home.path())
context
.command()
.args(["attach", run_id])
.timeout(std::time::Duration::from_secs(10))
.assert()
@ -1008,11 +982,11 @@ fn dry_run_create_start_attach_works_with_default_run_lookup() {
#[test]
fn dry_run_detach_attach_works_with_default_run_lookup() {
let home = tempfile::tempdir().unwrap();
let run_id = "01ARZ3NDEKTSV4RRFFQ69G5FB0";
let context = test_context!();
arc()
.env("HOME", home.path())
context
.command()
.args([
"run",
"--detach",
@ -1026,8 +1000,8 @@ fn dry_run_detach_attach_works_with_default_run_lookup() {
.success()
.stdout(predicate::str::contains(run_id));
arc()
.env("HOME", home.path())
context
.command()
.args(["attach", run_id])
.timeout(std::time::Duration::from_secs(10))
.assert()
@ -1036,10 +1010,9 @@ fn dry_run_detach_attach_works_with_default_run_lookup() {
#[test]
fn start_by_workflow_name_prefers_newly_created_submitted_run() {
let home = tempfile::tempdir().unwrap();
let old_run_dir = home
.path()
.join(".fabro")
let context = test_context!();
let old_run_dir = context
.storage_dir
.join("runs")
.join("01ARZ3NDEKTSV4RRFFQ69G5FB1");
std::fs::create_dir_all(&old_run_dir).unwrap();
@ -1068,8 +1041,8 @@ fn start_by_workflow_name_prefers_newly_created_submitted_run() {
.unwrap();
let run_id = "01ARZ3NDEKTSV4RRFFQ69G5FB2";
arc()
.env("HOME", home.path())
context
.command()
.args([
"create",
"--dry-run",
@ -1082,20 +1055,20 @@ fn start_by_workflow_name_prefers_newly_created_submitted_run() {
.success()
.stdout(predicate::str::contains(run_id));
arc()
.env("HOME", home.path())
context
.command()
.args(["start", "smoke"])
.assert()
.success();
arc()
.env("HOME", home.path())
context
.command()
.args(["attach", run_id])
.timeout(std::time::Duration::from_secs(10))
.assert()
.success();
let new_run_dir = find_run_dir(home.path(), run_id);
let new_run_dir = find_run_dir(&context.storage_dir, run_id);
let status = std::fs::read_to_string(new_run_dir.join("status.json")).unwrap();
assert!(
status.contains("\"status\": \"succeeded\""),
@ -1108,9 +1081,11 @@ fn start_by_workflow_name_prefers_newly_created_submitted_run() {
// the engine should read the snapshot saved at create time.
#[test]
fn bug2_detached_uses_cached_graph_not_original_path() {
let dir = tempfile::tempdir().unwrap();
let storage_dir = dir.path().join("storage");
let run_dir = storage_dir.join("runs").join("01ARZ3NDEKTSV4RRFFQ69G5FB3");
let context = test_context!();
let run_dir = context
.storage_dir
.join("runs")
.join("01ARZ3NDEKTSV4RRFFQ69G5FB3");
std::fs::create_dir_all(&run_dir).unwrap();
let dot = "\
@ -1153,19 +1128,20 @@ digraph G {
std::fs::write(run_dir.join("graph.fabro"), dot).unwrap();
// __detached should use graph.fabro and never reference the deleted file.
let output = arc()
let output = context
.command()
.args([
"__detached",
"--run-dir",
run_dir.to_str().unwrap(),
"--launcher-path",
storage_dir
context
.storage_dir
.join("launchers")
.join("01ARZ3NDEKTSV4RRFFQ69G5FB3.json")
.to_str()
.unwrap(),
])
.env("NO_COLOR", "1")
.timeout(std::time::Duration::from_secs(15))
.output()
.expect("process should start");
@ -1180,9 +1156,8 @@ digraph G {
#[test]
fn bug4_detached_resume_rejects_completed_run_without_mutating_it() {
let home = tempfile::tempdir().unwrap();
let project = tempfile::tempdir().unwrap();
let workflow_path = project.path().join("workflow.fabro");
let context = test_context!();
let workflow_path = context.temp_dir.join("workflow.fabro");
std::fs::write(
&workflow_path,
"\
@ -1195,9 +1170,9 @@ digraph Test {
)
.unwrap();
let run = arc()
.env("HOME", home.path())
.current_dir(project.path())
let run = context
.command()
.current_dir(&context.temp_dir)
.args([
"run",
"--dry-run",
@ -1213,15 +1188,15 @@ digraph Test {
.trim()
.to_string();
arc()
.env("HOME", home.path())
context
.command()
.args(["wait", &run_id])
.timeout(std::time::Duration::from_secs(10))
.assert()
.success();
let inspect_before = arc()
.env("HOME", home.path())
let inspect_before = context
.command()
.args(["inspect", &run_id])
.assert()
.success();
@ -1237,15 +1212,15 @@ digraph Test {
.unwrap()
.to_string();
let storage_dir = home.path().join(".fabro");
arc()
.env("HOME", home.path())
context
.command()
.args([
"__detached",
"--run-dir",
&run_dir,
"--launcher-path",
storage_dir
context
.storage_dir
.join("launchers")
.join(format!("{run_id}.json"))
.to_str()
@ -1257,8 +1232,8 @@ digraph Test {
.failure()
.stderr(predicate::str::contains("nothing to resume"));
let inspect_after = arc()
.env("HOME", home.path())
let inspect_after = context
.command()
.args(["inspect", &run_id])
.assert()
.success();
@ -1277,10 +1252,9 @@ digraph Test {
#[test]
fn bug5_detached_uses_snapshotted_app_id_for_github_credentials() {
let storage_dir = tempfile::tempdir().unwrap();
let home = init_cli_home(storage_dir.path());
let run_dir = storage_dir
.path()
let context = test_context!();
let run_dir = context
.storage_dir
.join("runs")
.join("01ARZ3NDEKTSV4RRFFQ69G5FB4");
std::fs::create_dir_all(&run_dir).unwrap();
@ -1324,16 +1298,16 @@ digraph G {
.unwrap();
std::fs::write(run_dir.join("graph.fabro"), dot).unwrap();
arc()
.env("HOME", home.path())
context
.command()
.env("GITHUB_APP_PRIVATE_KEY", "%%%not-base64%%%")
.args([
"__detached",
"--run-dir",
run_dir.to_str().unwrap(),
"--launcher-path",
storage_dir
.path()
context
.storage_dir
.join("launchers")
.join("01ARZ3NDEKTSV4RRFFQ69G5FB4.json")
.to_str()
@ -1351,10 +1325,10 @@ digraph G {
// engine consumes interview_response.json, so reattach remains safe.
#[test]
fn bug3_attach_leaves_interview_request_until_engine_consumes_response() {
let home = tempfile::tempdir().unwrap();
let context = test_context!();
let run_dir = setup_run_dir(
home.path(),
&context.storage_dir,
"01ARZ3NDEKTSV4RRFFQ69G5FB5",
serde_json::json!({}),
&[
@ -1390,9 +1364,8 @@ fn bug3_attach_leaves_interview_request_until_engine_consumes_response() {
.unwrap();
// Pipe "y\n" so ConsoleInterviewer doesn't block on stdin
let _ = arc()
.env("HOME", home.path())
.env("NO_COLOR", "1")
let _ = context
.command()
.args(["attach", "01ARZ3NDEKTSV4RRFFQ69G5FB5"])
.write_stdin("y\n")
.timeout(std::time::Duration::from_secs(5))
@ -1414,10 +1387,10 @@ fn bug3_attach_leaves_interview_request_until_engine_consumes_response() {
#[test]
fn attach_closed_stdin_keeps_interview_pending() {
let home = tempfile::tempdir().unwrap();
let context = test_context!();
let run_dir = setup_run_dir(
home.path(),
&context.storage_dir,
"01ARZ3NDEKTSV4RRFFQ69G5FB6",
serde_json::json!({}),
&[
@ -1449,9 +1422,8 @@ fn attach_closed_stdin_keeps_interview_pending() {
)
.unwrap();
let assert = arc()
.env("HOME", home.path())
.env("NO_COLOR", "1")
let assert = context
.command()
.args(["attach", "01ARZ3NDEKTSV4RRFFQ69G5FB6"])
.timeout(std::time::Duration::from_secs(5))
.assert()
@ -1480,13 +1452,13 @@ fn attach_closed_stdin_keeps_interview_pending() {
// Currently ProgressUI is created with verbose=false regardless of config.
#[test]
fn bug4_attach_respects_verbose_from_spec() {
let home = tempfile::tempdir().unwrap();
let context = test_context!();
// Use pre-rename field names so handle_json_line can parse them
// (isolates this test from bug 1). With 2 turns and 1 tool call,
// verbose mode should display "(2 turns, 1 tools, ...)" in the output.
let run_dir = setup_run_dir(
home.path(),
&context.storage_dir,
"01ARZ3NDEKTSV4RRFFQ69G5FB7",
serde_json::json!({"verbose": true}),
&[
@ -1519,9 +1491,8 @@ fn bug4_attach_respects_verbose_from_spec() {
)
.unwrap();
let output = arc()
.env("HOME", home.path())
.env("NO_COLOR", "1")
let output = context
.command()
.args(["attach", "01ARZ3NDEKTSV4RRFFQ69G5FB7"])
.timeout(std::time::Duration::from_secs(10))
.output()

View file

@ -1,13 +1,6 @@
use fabro_test::{fabro_snapshot, test_context};
use predicates;
#[allow(deprecated)]
fn fabro() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.env("NO_COLOR", "1");
cmd
}
#[test]
fn help() {
let context = test_context!();
@ -41,58 +34,54 @@ fn help() {
#[test]
fn test_secret_lifecycle() {
let tmp = tempfile::tempdir().unwrap();
let context = test_context!();
let secret = |args: &[&str]| -> assert_cmd::assert::Assert {
fabro().env("HOME", tmp.path()).args(args).assert()
};
let secret =
|args: &[&str]| -> assert_cmd::assert::Assert { context.secret().args(args).assert() };
// 1. set FOO=bar
secret(&["secret", "set", "FOO", "bar"]).success();
secret(&["set", "FOO", "bar"]).success();
// 2. get FOO -> stdout is "bar\n"
secret(&["secret", "get", "FOO"]).success().stdout("bar\n");
secret(&["get", "FOO"]).success().stdout("bar\n");
// 3. list -> contains FOO
secret(&["secret", "list"])
secret(&["list"])
.success()
.stdout(predicates::str::contains("FOO"));
// 4. update FOO
secret(&["secret", "set", "FOO", "updated"]).success();
secret(&["set", "FOO", "updated"]).success();
// 5. get FOO -> "updated\n"
secret(&["secret", "get", "FOO"])
.success()
.stdout("updated\n");
secret(&["get", "FOO"]).success().stdout("updated\n");
// 6. rm FOO
secret(&["secret", "rm", "FOO"]).success();
secret(&["rm", "FOO"]).success();
// 7. get FOO -> fails
secret(&["secret", "get", "FOO"]).failure();
secret(&["get", "FOO"]).failure();
}
#[test]
fn test_secret_list_show_values() {
let tmp = tempfile::tempdir().unwrap();
let context = test_context!();
let secret = |args: &[&str]| -> assert_cmd::assert::Assert {
fabro().env("HOME", tmp.path()).args(args).assert()
};
let secret =
|args: &[&str]| -> assert_cmd::assert::Assert { context.secret().args(args).assert() };
secret(&["secret", "set", "A", "1"]).success();
secret(&["secret", "set", "B", "2"]).success();
secret(&["set", "A", "1"]).success();
secret(&["set", "B", "2"]).success();
// Without --show-values: just keys
let out = secret(&["secret", "list"]).success();
let out = secret(&["list"]).success();
let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
assert!(stdout.contains("A"));
assert!(stdout.contains("B"));
assert!(!stdout.contains("A=1"));
// With --show-values: KEY=VALUE
secret(&["secret", "list", "--show-values"])
secret(&["list", "--show-values"])
.success()
.stdout(predicates::str::contains("A=1"))
.stdout(predicates::str::contains("B=2"));
@ -100,17 +89,13 @@ fn test_secret_list_show_values() {
#[test]
fn test_secret_list_alias_ls() {
let tmp = tempfile::tempdir().unwrap();
let context = test_context!();
fabro()
.env("HOME", tmp.path())
.args(["secret", "set", "X", "y"])
.assert()
.success();
context.secret().args(["set", "X", "y"]).assert().success();
fabro()
.env("HOME", tmp.path())
.args(["secret", "ls"])
context
.secret()
.args(["ls"])
.assert()
.success()
.stdout(predicates::str::contains("X"));
@ -118,11 +103,11 @@ fn test_secret_list_alias_ls() {
#[test]
fn test_secret_get_missing_key() {
let tmp = tempfile::tempdir().unwrap();
let context = test_context!();
fabro()
.env("HOME", tmp.path())
.args(["secret", "get", "NOPE"])
context
.secret()
.args(["get", "NOPE"])
.assert()
.failure()
.stderr(predicates::str::contains("secret not found"));
@ -130,11 +115,11 @@ fn test_secret_get_missing_key() {
#[test]
fn test_secret_rm_missing_key() {
let tmp = tempfile::tempdir().unwrap();
let context = test_context!();
fabro()
.env("HOME", tmp.path())
.args(["secret", "rm", "NOPE"])
context
.secret()
.args(["rm", "NOPE"])
.assert()
.failure()
.stderr(predicates::str::contains("secret not found"));
@ -142,17 +127,17 @@ fn test_secret_rm_missing_key() {
#[test]
fn test_secret_value_with_equals() {
let tmp = tempfile::tempdir().unwrap();
let context = test_context!();
fabro()
.env("HOME", tmp.path())
.args(["secret", "set", "URL", "https://x.com?a=1&b=2"])
context
.secret()
.args(["set", "URL", "https://x.com?a=1&b=2"])
.assert()
.success();
fabro()
.env("HOME", tmp.path())
.args(["secret", "get", "URL"])
context
.secret()
.args(["get", "URL"])
.assert()
.success()
.stdout("https://x.com?a=1&b=2\n");

View file

@ -1,19 +1,13 @@
use fabro_test::test_context;
use predicates;
#[allow(deprecated)]
fn fabro() -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("fabro").unwrap();
cmd.env("NO_COLOR", "1");
cmd
}
#[test]
fn list() {
let tmp = tempfile::tempdir().unwrap();
let context = test_context!();
// Minimal project structure: fabro.toml + a workflow
std::fs::write(tmp.path().join("fabro.toml"), "version = 1\n").unwrap();
let wf_dir = tmp.path().join("workflows/my_test_wf");
std::fs::write(context.temp_dir.join("fabro.toml"), "version = 1\n").unwrap();
let wf_dir = context.temp_dir.join("workflows/my_test_wf");
std::fs::create_dir_all(&wf_dir).unwrap();
std::fs::write(
wf_dir.join("workflow.toml"),
@ -21,9 +15,10 @@ fn list() {
)
.unwrap();
fabro()
context
.command()
.args(["workflow", "list"])
.current_dir(tmp.path())
.current_dir(&context.temp_dir)
.assert()
.success()
// workflow list prints to stderr