use fabro_types::test_support; mod auth_harness; mod auth_tokens; 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_store::EventEnvelope; use fabro_test::{EnvVars, TestContext, preserve_coverage_env}; use fabro_types::{Graph, RunId, RunSpec, WorkflowSettings}; pub(crate) fn run_output_filters(context: &TestContext) -> Vec<(String, String)> { let mut filters = context.filters(); filters.push((r"\b\d+ms\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::().expect("test run id should parse"); let spec = RunSpec { run_id, settings: WorkflowSettings::default(), graph: Graph::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(), manifest_blob: None, definition_blob: None, spec_blob: None, git: None, fork_source_ref: None, }; 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": {} }) } pub(crate) fn parse_event_envelopes(response: &serde_json::Value) -> Vec { response["data"] .as_array() .expect("event list response should contain a data array") .iter() .cloned() .map(serde_json::from_value) .collect::, _>>() .expect("wire event envelope list 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 } }