mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-27 01:21:25 +00:00
Scope the ps listing to the current test case and compare a normalized projection instead of exact live run payload equality. This avoids flakes from parallel tests and in-flight status transitions while preserving the CLI contract under test.
185 lines
5.5 KiB
Rust
185 lines
5.5 KiB
Rust
use std::process::Command;
|
|
|
|
use fabro_test::test_context;
|
|
use serde_json::Value;
|
|
|
|
use super::support::{fixture, output_stderr, output_stdout, setup_completed_fast_dry_run};
|
|
|
|
fn dot_is_available() -> bool {
|
|
Command::new("dot")
|
|
.arg("-V")
|
|
.stdout(std::process::Stdio::null())
|
|
.stderr(std::process::Stdio::null())
|
|
.status()
|
|
.is_ok()
|
|
}
|
|
|
|
#[test]
|
|
fn completion_rejects_json() {
|
|
let context = test_context!();
|
|
let output = context
|
|
.command()
|
|
.args(["--json", "completion", "zsh"])
|
|
.output()
|
|
.expect("command should run");
|
|
|
|
assert!(!output.status.success());
|
|
let stderr = String::from_utf8(output.stderr).unwrap();
|
|
assert!(stderr.contains("--json is not supported for this command"));
|
|
}
|
|
|
|
#[test]
|
|
fn settings_json_outputs_parseable_json() {
|
|
let context = test_context!();
|
|
let output = context
|
|
.settings()
|
|
.arg("--json")
|
|
.output()
|
|
.expect("command should run");
|
|
|
|
assert!(output.status.success());
|
|
let value: Value =
|
|
serde_json::from_slice(&output.stdout).expect("settings --json should parse");
|
|
assert!(value.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn ps_supports_global_flag_and_env_var() {
|
|
let context = test_context!();
|
|
setup_completed_fast_dry_run(&context);
|
|
let test_case_label = context.test_case_label();
|
|
|
|
let global_output = context
|
|
.command()
|
|
.args(["--json", "ps", "-a", "--label", &test_case_label])
|
|
.output()
|
|
.expect("command should run");
|
|
assert!(global_output.status.success());
|
|
let global_runs: Value =
|
|
serde_json::from_slice(&global_output.stdout).expect("global --json should parse");
|
|
assert!(global_runs.as_array().is_some_and(|runs| !runs.is_empty()));
|
|
|
|
let env_output = context
|
|
.command()
|
|
.env("FABRO_JSON", "1")
|
|
.args(["ps", "-a", "--label", &test_case_label])
|
|
.output()
|
|
.expect("command should run");
|
|
assert!(env_output.status.success());
|
|
let env_runs: Value =
|
|
serde_json::from_slice(&env_output.stdout).expect("FABRO_JSON output should parse");
|
|
|
|
let normalize = |runs: &Value| {
|
|
let mut rows = runs
|
|
.as_array()
|
|
.expect("ps output should be an array")
|
|
.iter()
|
|
.map(|run| {
|
|
(
|
|
run["run_id"]
|
|
.as_str()
|
|
.expect("run_id should be present")
|
|
.to_string(),
|
|
run["workflow_name"]
|
|
.as_str()
|
|
.expect("workflow_name should be present")
|
|
.to_string(),
|
|
run["workflow_slug"]
|
|
.as_str()
|
|
.expect("workflow_slug should be present")
|
|
.to_string(),
|
|
run["goal"]
|
|
.as_str()
|
|
.expect("goal should be present")
|
|
.to_string(),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
rows.sort_unstable();
|
|
rows
|
|
};
|
|
|
|
assert_eq!(normalize(&global_runs), normalize(&env_runs));
|
|
}
|
|
|
|
#[test]
|
|
fn logs_json_wins_over_pretty() {
|
|
let context = test_context!();
|
|
let run = setup_completed_fast_dry_run(&context);
|
|
|
|
let output = context
|
|
.command()
|
|
.args(["--json", "logs", "--pretty", &run.run_id])
|
|
.output()
|
|
.expect("command should run");
|
|
|
|
assert!(output.status.success());
|
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
|
let first_line = stdout.lines().find(|line| !line.is_empty()).unwrap();
|
|
let value: Value = serde_json::from_str(first_line).expect("logs output should remain JSONL");
|
|
assert!(value.get("event").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn graph_json_without_output_is_rejected() {
|
|
let context = test_context!();
|
|
let workflow = fixture("simple.fabro");
|
|
|
|
let output = context
|
|
.command()
|
|
.args(["--json", "graph", workflow.to_str().unwrap()])
|
|
.output()
|
|
.expect("command should run");
|
|
|
|
assert!(!output.status.success());
|
|
let stderr = output_stderr(&output);
|
|
assert!(stderr.contains("--json is not supported for this command"));
|
|
}
|
|
|
|
#[test]
|
|
fn graph_json_with_output_reports_file() {
|
|
if !dot_is_available() {
|
|
return;
|
|
}
|
|
|
|
let context = test_context!();
|
|
let output_path = context.temp_dir.join("graph.svg");
|
|
let workflow = fixture("simple.fabro");
|
|
|
|
let output = context
|
|
.command()
|
|
.args([
|
|
"--json",
|
|
"graph",
|
|
workflow.to_str().unwrap(),
|
|
"--output",
|
|
output_path.to_str().unwrap(),
|
|
])
|
|
.output()
|
|
.expect("command should run");
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"stdout:\n{}\nstderr:\n{}",
|
|
output_stdout(&output),
|
|
output_stderr(&output)
|
|
);
|
|
let value: Value = serde_json::from_slice(&output.stdout).expect("graph JSON should parse");
|
|
assert_eq!(value["format"], "svg");
|
|
assert_eq!(value["path"], output_path.to_string_lossy().to_string());
|
|
assert!(output_path.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn secret_list_json_missing_env_is_empty_array() {
|
|
let context = test_context!();
|
|
let output = context
|
|
.command()
|
|
.args(["--json", "secret", "list"])
|
|
.output()
|
|
.expect("command should run");
|
|
|
|
assert!(output.status.success());
|
|
let value: Value = serde_json::from_slice(&output.stdout).expect("secret list should parse");
|
|
assert_eq!(value, Value::Array(vec![]));
|
|
}
|