fabro/lib/apps/fabro-cli/tests/it/support/mod.rs
Bryan Helmkamp bd59f52e22
Build the run's display graph from Petri's admission
The rest of the change whose deletions the previous commit carries (its
`git add` stopped at an already-removed path): `fabro_types::RunGraph`
and the `fabro-petri` builder that reads it off the admitted graph, the
server's create, validate, preflight and render paths on Petri's check
alone, the consumers moved to the new shape, the OpenAPI `RunGraph`
schemas with their parity tests, the regenerated TS client, and the
docs naming Petri's diagnostic codes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 11:41:58 -04:00

141 lines
4.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use fabro_types::{PetriAdmission, test_support};
mod auth_harness;
mod auth_tokens;
mod mcp_client;
use assert_cmd::Command;
pub(crate) use auth_harness::{
RealAuthHarness, TEST_DEV_TOKEN, complete_login_via_browser, expire_saved_access_token,
no_redirect_browser_client, run_detached, saved_auth_entry, seed_dev_token_auth,
};
pub(crate) use auth_tokens::{TEST_SESSION_SECRET, issue_test_github_jwt, issue_test_worker_jwt};
use fabro_test::{EnvVars, TestContext, preserve_coverage_env};
use fabro_types::{RunGraph, RunId, RunSpec, RunStreamItem, WorkflowSettings};
pub(crate) use mcp_client::McpStdioTestClient;
pub(crate) fn run_output_filters(context: &TestContext) -> Vec<(String, String)> {
let mut filters = context.filters();
filters.push((r"\b\d+(\.\d+)?(ms|s)\b".to_string(), "[TIME]".to_string()));
filters.push((
r"(?m)^(Graph: ).+$".to_string(),
"${1}[GRAPH_PATH]".to_string(),
));
filters
}
pub(crate) fn fatal_error_line(stderr: &[u8]) -> String {
let stderr = String::from_utf8_lossy(stderr);
console::strip_ansi_codes(&stderr)
.lines()
.rev()
.find_map(|line| {
line.strip_prefix("error: ")
.or_else(|| line.trim_start().strip_prefix("× "))
.map(ToOwned::to_owned)
})
.expect("stderr should contain a fatal error line")
}
pub(crate) fn unique_run_id() -> String {
RunId::new().to_string()
}
pub(crate) fn run_projection_json(run_id: &str, status: &serde_json::Value) -> serde_json::Value {
let run_id = run_id.parse::<RunId>().expect("test run id should parse");
let spec = RunSpec {
run_id,
settings: WorkflowSettings::default(),
graph: RunGraph::new("Remote Workflow"),
graph_source: None,
workflow_slug: Some("remote-workflow".to_string()),
workflow_version_id: None,
target: None,
automation: None,
source_directory: Some("/srv/repo".to_string()),
labels: std::collections::HashMap::default(),
provenance: test_support::test_run_provenance(),
definition_blob: None,
spec_blob: None,
git: None,
fork_source_ref: None,
admission: PetriAdmission::default(),
};
serde_json::json!({
"spec": serde_json::to_value(spec).expect("run spec should serialize"),
"start": null,
"status": status,
"status_updated_at": "2026-04-05T12:00:01Z",
"last_event_at": "2026-04-05T12:00:01Z",
"pending_control": null,
"checkpoints": [],
"conclusion": null,
"sandbox": null,
"pull_request": null,
"superseded_by": null,
"pending_interviews": {},
"stages": {}
})
}
/// Whether the item is the platform record that ends a run: a
/// `run.lifecycle` record whose transition is terminal.
pub(crate) fn is_terminal_lifecycle(item: &RunStreamItem) -> bool {
let record = item.item.get("record");
record
.and_then(|record| record.get("kind"))
.and_then(serde_json::Value::as_str)
== Some("run.lifecycle")
&& matches!(
record
.and_then(|record| record.get("transition"))
.and_then(serde_json::Value::as_str),
Some("succeeded" | "failed" | "dead")
)
}
pub(crate) fn parse_stream_items(response: &serde_json::Value) -> Vec<RunStreamItem> {
response["data"]
.as_array()
.expect("run stream page should contain a data array")
.iter()
.cloned()
.map(serde_json::from_value)
.collect::<Result<Vec<_>, _>>()
.expect("run stream items should parse")
}
pub(crate) struct LightweightCli {
home_dir: tempfile::TempDir,
}
impl LightweightCli {
pub(crate) fn new() -> Self {
Self {
home_dir: tempfile::tempdir().expect("temp home dir should exist"),
}
}
pub(crate) fn home(&self) -> &std::path::Path {
self.home_dir.path()
}
#[expect(
clippy::disallowed_methods,
reason = "Lightweight CLI test harness reconstructs a minimal process env for subprocesses."
)]
pub(crate) fn command(&self) -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_fabro"));
cmd.env_clear();
preserve_coverage_env!(cmd);
if let Some(path) = std::env::var_os(EnvVars::PATH) {
cmd.env(EnvVars::PATH, path);
}
cmd.env(EnvVars::HOME, self.home_dir.path());
cmd.env(EnvVars::NO_COLOR, "1");
cmd.env(EnvVars::FABRO_NO_UPGRADE_CHECK, "true")
.env(EnvVars::FABRO_HTTP_PROXY_POLICY, "disabled");
cmd.current_dir(self.home_dir.path());
cmd
}
}