mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
Refactor scenario.rs to use TestContext for isolation
Replace manual fabro()/tempdir/--storage-dir boilerplate with TestContext from fabro-test crate. This gives each scenario proper HOME isolation, automatic NO_COLOR and upgrade-check suppression, and removes the need for explicit --storage-dir CLI args. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d1a969510e
commit
d365e713b5
1 changed files with 72 additions and 114 deletions
|
|
@ -1,21 +1,14 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use assert_cmd::Command;
|
||||
use fabro_store::RuntimeState;
|
||||
use fabro_test::test_context;
|
||||
use serde_json::Value;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn fabro() -> Command {
|
||||
let mut cmd = Command::cargo_bin("fabro").unwrap();
|
||||
cmd.env("NO_COLOR", "1");
|
||||
cmd
|
||||
}
|
||||
|
||||
fn fixture(name: &str) -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../../test/scenario")
|
||||
|
|
@ -115,34 +108,23 @@ scenario_tests!(command_pipeline);
|
|||
|
||||
fn scenario_command_pipeline(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
let context = test_context!();
|
||||
|
||||
// Validate the workflow before running it
|
||||
fabro()
|
||||
.args([
|
||||
"validate",
|
||||
fixture("command_pipeline.fabro").to_str().unwrap(),
|
||||
])
|
||||
context
|
||||
.validate()
|
||||
.arg(fixture("command_pipeline.fabro"))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
fabro()
|
||||
.args([
|
||||
"run",
|
||||
"--auto-approve",
|
||||
"--no-retro",
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("command_pipeline.fabro").to_str().unwrap(),
|
||||
])
|
||||
context
|
||||
.run_cmd()
|
||||
.args(["--auto-approve", "--no-retro", "--sandbox", sandbox])
|
||||
.arg(fixture("command_pipeline.fabro"))
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let run_dir = find_run_dir(&context.storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(
|
||||
conclusion["status"].as_str(),
|
||||
|
|
@ -174,25 +156,17 @@ scenario_tests!(conditional_branching);
|
|||
|
||||
fn scenario_conditional_branching(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
let context = test_context!();
|
||||
|
||||
fabro()
|
||||
.args([
|
||||
"run",
|
||||
"--auto-approve",
|
||||
"--no-retro",
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("conditional_branching.fabro").to_str().unwrap(),
|
||||
])
|
||||
context
|
||||
.run_cmd()
|
||||
.args(["--auto-approve", "--no-retro", "--sandbox", sandbox])
|
||||
.arg(fixture("conditional_branching.fabro"))
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let run_dir = find_run_dir(&context.storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(conclusion["status"].as_str(), Some("success"));
|
||||
|
||||
|
|
@ -212,27 +186,24 @@ scenario_tests!(agent_linear);
|
|||
|
||||
fn scenario_agent_linear(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
let context = test_context!();
|
||||
|
||||
fabro()
|
||||
context
|
||||
.run_cmd()
|
||||
.args([
|
||||
"run",
|
||||
"--auto-approve",
|
||||
"--no-retro",
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("agent_linear.fabro").to_str().unwrap(),
|
||||
])
|
||||
.arg(fixture("agent_linear.fabro"))
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let run_dir = find_run_dir(&context.storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(conclusion["status"].as_str(), Some("success"));
|
||||
|
||||
|
|
@ -260,27 +231,24 @@ scenario_tests!(human_gate);
|
|||
|
||||
fn scenario_human_gate(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
let context = test_context!();
|
||||
|
||||
fabro()
|
||||
context
|
||||
.run_cmd()
|
||||
.args([
|
||||
"run",
|
||||
"--auto-approve",
|
||||
"--no-retro",
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("human_gate.fabro").to_str().unwrap(),
|
||||
])
|
||||
.arg(fixture("human_gate.fabro"))
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let run_dir = find_run_dir(&context.storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(conclusion["status"].as_str(), Some("success"));
|
||||
|
||||
|
|
@ -300,27 +268,24 @@ scenario_tests!(command_agent_mixed);
|
|||
|
||||
fn scenario_command_agent_mixed(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
let context = test_context!();
|
||||
|
||||
fabro()
|
||||
context
|
||||
.run_cmd()
|
||||
.args([
|
||||
"run",
|
||||
"--auto-approve",
|
||||
"--no-retro",
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("command_agent_mixed.fabro").to_str().unwrap(),
|
||||
])
|
||||
.arg(fixture("command_agent_mixed.fabro"))
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let run_dir = find_run_dir(&context.storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(conclusion["status"].as_str(), Some("success"));
|
||||
|
||||
|
|
@ -352,27 +317,24 @@ scenario_tests!(full_stack);
|
|||
|
||||
fn scenario_full_stack(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
let context = test_context!();
|
||||
|
||||
fabro()
|
||||
context
|
||||
.run_cmd()
|
||||
.args([
|
||||
"run",
|
||||
"--auto-approve",
|
||||
"--no-retro",
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("full_stack.fabro").to_str().unwrap(),
|
||||
])
|
||||
.arg(fixture("full_stack.fabro"))
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let run_dir = find_run_dir(&context.storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(
|
||||
conclusion["status"].as_str(),
|
||||
|
|
@ -430,22 +392,21 @@ fn scenario_full_stack(sandbox: &str) {
|
|||
#[ignore = "scenario: requires local sandbox"]
|
||||
fn local_run_lifecycle() {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let context = test_context!();
|
||||
|
||||
let fabro_home = |args: &[&str]| -> assert_cmd::assert::Assert {
|
||||
fabro()
|
||||
.env("HOME", tmp.path())
|
||||
let cmd = |args: &[&str]| -> assert_cmd::assert::Assert {
|
||||
context
|
||||
.command()
|
||||
.args(args)
|
||||
.timeout(timeout_for("local"))
|
||||
.assert()
|
||||
};
|
||||
|
||||
// 1. Run a workflow — run lands under <tmp>/.fabro/runs/
|
||||
fabro_home(&[
|
||||
// 1. Run a workflow
|
||||
cmd(&[
|
||||
"run",
|
||||
"--auto-approve",
|
||||
"--no-retro",
|
||||
"--no-upgrade-check",
|
||||
"--sandbox",
|
||||
"local",
|
||||
fixture("command_pipeline.fabro").to_str().unwrap(),
|
||||
|
|
@ -453,7 +414,7 @@ fn local_run_lifecycle() {
|
|||
.success();
|
||||
|
||||
// 2. ps -a --json — should list exactly one run
|
||||
let ps_out = fabro_home(&["ps", "-a", "--json"]).success();
|
||||
let ps_out = cmd(&["ps", "-a", "--json"]).success();
|
||||
let ps_stdout = String::from_utf8(ps_out.get_output().stdout.clone()).unwrap();
|
||||
let runs: Vec<Value> =
|
||||
serde_json::from_str(&ps_stdout).expect("ps --json should produce a JSON array");
|
||||
|
|
@ -469,7 +430,7 @@ fn local_run_lifecycle() {
|
|||
);
|
||||
|
||||
// 3. inspect <run_id> — JSON array with run_record and conclusion
|
||||
let inspect_out = fabro_home(&["inspect", &run_id]).success();
|
||||
let inspect_out = cmd(&["inspect", &run_id]).success();
|
||||
let inspect_stdout = String::from_utf8(inspect_out.get_output().stdout.clone()).unwrap();
|
||||
let items: Vec<Value> =
|
||||
serde_json::from_str(&inspect_stdout).expect("inspect should produce a JSON array");
|
||||
|
|
@ -489,7 +450,7 @@ fn local_run_lifecycle() {
|
|||
);
|
||||
|
||||
// 4. logs <run_id> — non-empty, first line is valid JSONL with event field
|
||||
let logs_out = fabro_home(&["logs", &run_id]).success();
|
||||
let logs_out = cmd(&["logs", &run_id]).success();
|
||||
let logs_stdout = String::from_utf8(logs_out.get_output().stdout.clone()).unwrap();
|
||||
assert!(!logs_stdout.is_empty(), "logs should not be empty");
|
||||
let first_line = logs_stdout.lines().next().unwrap();
|
||||
|
|
@ -501,7 +462,7 @@ fn local_run_lifecycle() {
|
|||
);
|
||||
|
||||
// 5. asset list — no assets yet, should succeed with empty message
|
||||
let asset_list_out = fabro_home(&["asset", "list", &run_id]).success();
|
||||
let asset_list_out = cmd(&["asset", "list", &run_id]).success();
|
||||
let asset_list_stdout = String::from_utf8(asset_list_out.get_output().stdout.clone()).unwrap();
|
||||
assert!(
|
||||
asset_list_stdout.contains("No assets found"),
|
||||
|
|
@ -527,7 +488,7 @@ fn local_run_lifecycle() {
|
|||
.unwrap();
|
||||
|
||||
// 7. asset list — now shows the seeded assets
|
||||
let asset_list_out2 = fabro_home(&["asset", "list", &run_id, "--json"]).success();
|
||||
let asset_list_out2 = cmd(&["asset", "list", &run_id, "--json"]).success();
|
||||
let asset_list_stdout2 =
|
||||
String::from_utf8(asset_list_out2.get_output().stdout.clone()).unwrap();
|
||||
let assets: Vec<Value> = serde_json::from_str(&asset_list_stdout2)
|
||||
|
|
@ -539,8 +500,7 @@ fn local_run_lifecycle() {
|
|||
);
|
||||
assert_eq!(assets[0]["relative_path"].as_str(), Some("output.txt"));
|
||||
assert_eq!(assets[0]["node_slug"].as_str(), Some("step1"));
|
||||
let retry_filtered_out =
|
||||
fabro_home(&["asset", "list", &run_id, "--retry", "1", "--json"]).success();
|
||||
let retry_filtered_out = cmd(&["asset", "list", &run_id, "--retry", "1", "--json"]).success();
|
||||
let retry_filtered_stdout =
|
||||
String::from_utf8(retry_filtered_out.get_output().stdout.clone()).unwrap();
|
||||
let retry_filtered_assets: Vec<Value> = serde_json::from_str(&retry_filtered_stdout)
|
||||
|
|
@ -549,15 +509,15 @@ fn local_run_lifecycle() {
|
|||
assert_eq!(retry_filtered_assets[0]["retry"].as_u64(), Some(1));
|
||||
|
||||
// 8. asset cp — ambiguous without --retry when multiple retries captured the same path
|
||||
let asset_dest = tmp.path().join("asset_copy");
|
||||
fabro_home(&[
|
||||
let asset_dest = context.temp_dir.join("asset_copy");
|
||||
cmd(&[
|
||||
"asset",
|
||||
"cp",
|
||||
&format!("{run_id}:output.txt"),
|
||||
asset_dest.to_str().unwrap(),
|
||||
])
|
||||
.failure();
|
||||
fabro_home(&[
|
||||
cmd(&[
|
||||
"asset",
|
||||
"cp",
|
||||
&format!("{run_id}:output.txt"),
|
||||
|
|
@ -583,8 +543,8 @@ fn local_run_lifecycle() {
|
|||
"downloaded-via-cp",
|
||||
)
|
||||
.unwrap();
|
||||
let cp_dest = tmp.path().join("cp_download.txt");
|
||||
fabro_home(&[
|
||||
let cp_dest = context.temp_dir.join("cp_download.txt");
|
||||
cmd(&[
|
||||
"cp",
|
||||
&format!("{run_id}:cp_test.txt"),
|
||||
cp_dest.to_str().unwrap(),
|
||||
|
|
@ -597,7 +557,7 @@ fn local_run_lifecycle() {
|
|||
);
|
||||
|
||||
// 10. system df — mentions "Runs"
|
||||
let df_out = fabro_home(&["system", "df"]).success();
|
||||
let df_out = cmd(&["system", "df"]).success();
|
||||
let df_stdout = String::from_utf8(df_out.get_output().stdout.clone()).unwrap();
|
||||
assert!(
|
||||
df_stdout.contains("Runs"),
|
||||
|
|
@ -605,10 +565,10 @@ fn local_run_lifecycle() {
|
|||
);
|
||||
|
||||
// 11. rm <run_id> — remove the run
|
||||
fabro_home(&["rm", &run_id]).success();
|
||||
cmd(&["rm", &run_id]).success();
|
||||
|
||||
// 12. ps -a --json — should be empty
|
||||
let ps_out2 = fabro_home(&["ps", "-a", "--json"]).success();
|
||||
let ps_out2 = cmd(&["ps", "-a", "--json"]).success();
|
||||
let ps_stdout2 = String::from_utf8(ps_out2.get_output().stdout.clone()).unwrap();
|
||||
let runs2: Vec<Value> =
|
||||
serde_json::from_str(&ps_stdout2).expect("ps --json should produce a JSON array");
|
||||
|
|
@ -626,26 +586,24 @@ fn local_run_lifecycle() {
|
|||
#[ignore = "scenario: requires ANTHROPIC_API_KEY"]
|
||||
fn test_exec_creates_file() {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let context = test_context!();
|
||||
|
||||
fabro()
|
||||
.args([
|
||||
"exec",
|
||||
"--auto-approve",
|
||||
"--permissions",
|
||||
"full",
|
||||
"--provider",
|
||||
"anthropic",
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"Create a file called hello.txt containing exactly 'Hello from exec scenario'",
|
||||
])
|
||||
.current_dir(tmp.path())
|
||||
.timeout(Duration::from_secs(120))
|
||||
.assert()
|
||||
.success();
|
||||
let mut cmd = context.exec_cmd();
|
||||
cmd.args([
|
||||
"--auto-approve",
|
||||
"--permissions",
|
||||
"full",
|
||||
"--provider",
|
||||
"anthropic",
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"Create a file called hello.txt containing exactly 'Hello from exec scenario'",
|
||||
]);
|
||||
cmd.current_dir(&context.temp_dir);
|
||||
cmd.timeout(Duration::from_secs(120));
|
||||
cmd.assert().success();
|
||||
|
||||
let hello = tmp.path().join("hello.txt");
|
||||
let hello = context.temp_dir.join("hello.txt");
|
||||
assert!(hello.exists(), "hello.txt should exist after exec");
|
||||
let content = std::fs::read_to_string(&hello).unwrap();
|
||||
assert!(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue