fabro/lib/components/fabro-hooks/tests/host_command_hooks.rs
Bryan Helmkamp e54fef760a
refactor(auth): remove EnvCredentialSource and make the run vault required
`EnvCredentialSource` resolved provider credentials from the process
environment. It had no production entry point of its own — it was only
ever reached as the `None` arm of an `Option<Vault>` in three places:
`build_llm_source`, `configured_providers_for_start`, and
`configured_providers_from_process_env`.

That optional vault is not a state the product can be in. Every run has a
server behind it, the server always spawns workers with `--storage-dir`
(`worker_runtime.rs`), and `SqlVaultCredentialSource` backs both the
server and the CLI. So the fallback only served to silently degrade
credential resolution to whatever the worker process happened to have in
its environment.

Make the vault required across the run path — `RunOptions`,
`StartServices`, `build_llm_source`, `tool_secrets_from_configured_sources`,
`vault_token_lookup`, and the CLI GitHub helpers — so the invariant is
enforced by types rather than assumed. A worker spawned without
`--storage-dir` now fails with a clear message instead of quietly
continuing without a vault.

`configured_providers_from_process_env` had no callers at all and is
deleted. `AgentApiBackend::new_from_env` was public but only ever called
from its own tests; it is deleted too.

Test-only credential sources move to a feature-gated
`fabro_auth::test_support`, wired through dev-dependencies so they never
link into production builds. The CLI worker tests now pass
`--storage-dir`, matching what the server actually does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 20:36:54 -04:00

81 lines
2.5 KiB
Rust

use std::path::Path;
use std::sync::Arc;
use fabro_agent::{LocalSandbox, Sandbox};
use fabro_auth::{CredentialSource, test_support};
use fabro_hooks::{
HookContext, HookDecision, HookDefinition, HookEvent, HookExecutionContext, HookRunner,
HookSettings, InterpString,
};
use fabro_model::Catalog;
use fabro_types::RunId;
use tokio::fs;
fn test_llm_source() -> Arc<dyn CredentialSource> {
test_support::vault_only_credential_source()
}
fn test_catalog() -> Arc<Catalog> {
Arc::new(Catalog::from_builtin().expect("default catalog should build"))
}
fn local_sandbox() -> Arc<dyn Sandbox> {
Arc::new(LocalSandbox::new(
std::env::current_dir().expect("test process should have a cwd"),
))
}
#[tokio::test]
async fn host_command_hook_uses_host_workdir_not_sandbox_workdir() {
let host_work_dir =
std::env::temp_dir().join(format!("fabro-host-hook-cwd-{}", std::process::id()));
let _ = fs::remove_dir_all(&host_work_dir).await;
fs::create_dir_all(&host_work_dir)
.await
.expect("test should create host hook cwd");
let container_only_work_dir = Path::new("/workspace/fabro-host-hook-repro-missing");
assert!(
!container_only_work_dir.exists(),
"reproduction requires a container-only cwd that does not exist on the host"
);
let runner = HookRunner::new(
HookSettings {
hooks: vec![HookDefinition {
name: Some("host-marker".to_string()),
event: HookEvent::RunStart,
command: Some(InterpString::parse("printf ran > marker.txt")),
hook_type: None,
matcher: None,
blocking: Some(true),
timeout_ms: Some(5000),
sandbox: Some(false),
}],
},
test_llm_source(),
test_catalog(),
);
let context = HookContext::new(
HookEvent::RunStart,
RunId::new(),
"host-hook-cwd".to_string(),
);
let decision = runner
.run(&context, local_sandbox(), HookExecutionContext {
host_source_dir: Some(host_work_dir.clone()),
sandbox_work_dir: Some(container_only_work_dir.to_path_buf()),
})
.await;
assert_eq!(decision, HookDecision::Proceed);
assert_eq!(
fs::read_to_string(host_work_dir.join("marker.txt"))
.await
.expect("host hook should create marker file"),
"ran"
);
fs::remove_dir_all(&host_work_dir)
.await
.expect("test should clean up host hook cwd");
}