fabro/lib/apps/fabro-cli/tests/it/support/mod.rs
Bryan Helmkamp 5df733a22b
Move fabro-mcp's pebble mapping and test client into fabro-cli
`fabro-mcp` held two things after the legacy executor went: the mapping
from Fabro's MCP server settings to the servers pebble starts, which
only `fabro exec` still uses, and a stdio MCP client the tests of
Fabro's own MCP server speak through. The mapping is now
`fabro-cli`'s `mcp_servers` module and the client its test support's
`McpStdioTestClient`; the crate is deleted. Its `config` module was a
re-export of `fabro_types::settings::run`, which callers import directly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 10:57:07 -04:00

126 lines
4.1 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_store::EventEnvelope;
use fabro_test::{EnvVars, TestContext, preserve_coverage_env};
use fabro_types::{Graph, RunId, RunSpec, 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+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::<RunId>().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(),
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": {}
})
}
pub(crate) fn parse_event_envelopes(response: &serde_json::Value) -> Vec<EventEnvelope> {
response["data"]
.as_array()
.expect("event list response should contain a data array")
.iter()
.cloned()
.map(serde_json::from_value)
.collect::<Result<Vec<_>, _>>()
.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
}
}