#![allow( clippy::absolute_paths, reason = "This test module prefers explicit type paths over extra imports." )] use std::process::Output; use fabro_config::Storage; use fabro_test::{fabro_snapshot, require_env, test_context, twin_openai}; use fabro_vault::{SecretType, Vault}; async fn run_success_output(mut cmd: assert_cmd::Command) -> Output { tokio::task::spawn_blocking(move || cmd.assert().success().get_output().clone()) .await .expect("blocking command task should complete") } fn toml_path(path: &std::path::Path) -> String { path.display() .to_string() .replace('\\', "\\\\") .replace('"', "\\\"") } fn seed_vault_secret(storage_dir: &std::path::Path, name: &str, value: &str) { let mut vault = Vault::load(Storage::new(storage_dir).secrets_path()).expect("test vault should load"); vault .set(name, value, SecretType::Token, None) .expect("credential should store in test vault"); } fn seed_openai_vault(storage_dir: &std::path::Path, api_key: &str) { seed_vault_secret(storage_dir, "OPENAI_API_KEY", api_key); } #[test] fn help() { let context = test_context!(); let mut cmd = context.doctor(); cmd.arg("--help"); fabro_snapshot!(context.filters(), cmd, @" success: true exit_code: 0 ----- stdout ----- Check environment and integration health Usage: fabro doctor [OPTIONS] Options: --json Output as JSON [env: FABRO_JSON=] --server Fabro server target: http(s) URL or absolute Unix socket path [env: FABRO_SERVER=] --debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=] -v, --verbose Show detailed information for each check --no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true] --quiet Suppress non-essential output [env: FABRO_QUIET=] -h, --help Print help ----- stderr ----- "); } #[test] fn dry_run_flag_is_rejected() { let context = test_context!(); let mut cmd = context.doctor(); cmd.arg("--dry-run"); fabro_snapshot!(context.filters(), cmd, @" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: unexpected argument '--dry-run' found Usage: fabro doctor [OPTIONS] For more information, try '--help'. "); } #[fabro_macros::e2e_test(live("ANTHROPIC_API_KEY"))] fn live_doctor() { let mut context = test_context!(); let api_key = require_env("ANTHROPIC_API_KEY") .expect("e2e_test live guard should require ANTHROPIC_API_KEY"); let storage_dir = context.temp_dir.join("doctor-live-server-storage"); context.write_home( ".fabro/settings.toml", format!( r#"[server.storage] root = "{}" [server.auth] methods = ["dev-token"] [server.sandbox.providers.docker] enabled = false "#, toml_path(&storage_dir) ), ); seed_vault_secret(&storage_dir, "ANTHROPIC_API_KEY", &api_key); context.isolated_server(); context.doctor().assert().success(); } /// The provider probe runs inside the isolated server, which never sees the /// test process environment, so the twin's base URL reaches it through the /// operator `[llm]` overlay rather than `OPENAI_BASE_URL`. #[fabro_macros::e2e_test(twin)] async fn twin_doctor() { let mut context = test_context!(); let twin = twin_openai().await; let namespace = format!("{}::{}", module_path!(), line!()); let storage_dir = context.temp_dir.join("doctor-server-storage"); context.write_home( ".fabro/settings.toml", format!( r#"[server.storage] root = "{}" [server.auth] methods = ["dev-token"] [server.integrations.github] strategy = "app" [server.sandbox.providers.docker] enabled = false [llm.providers.openai] base_url = "{}" "#, toml_path(&storage_dir), twin.base_url, ), ); seed_openai_vault(&storage_dir, &namespace); context.isolated_server(); let mut cmd = context.doctor(); cmd.arg("--verbose"); cmd.env_clear(); cmd.env("NO_COLOR", "1"); cmd.env("HOME", &context.home_dir); cmd.env("FABRO_NO_UPGRADE_CHECK", "true") .env("FABRO_HTTP_PROXY_POLICY", "disabled"); cmd.env("FABRO_STORAGE_DIR", &context.storage_dir); cmd.env( "PATH", "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin", ); twin.configure_command(&mut cmd, &namespace); let output = run_success_output(cmd).await; let stdout = String::from_utf8(output.stdout).unwrap(); assert!( stdout.to_lowercase().contains("openai: ok"), "expected verbose doctor output to include openai probe success, got: {stdout}" ); assert!( stdout.contains("Version parity"), "expected doctor output to include version parity check, got: {stdout}" ); }