fabro/lib/crates/fabro-hooks/tests/host_command_hooks.rs
Bryan Helmkamp 9bdf30ad86
refactor(hooks): centralize run location handling (#325)
## Summary
- Fix host command hooks to use the submitter/source directory instead
of a sandbox-only working directory.
- Introduce `HookExecutionContext` and `RunLocations` so host source,
sandbox work, and run scratch paths are explicit.
- Route lifecycle hooks and tool hooks through the shared hook execution
context instead of rebuilding cwd pairs at call sites.

## Test Plan
- `cargo nextest run -p fabro-hooks`
- `cargo check -p fabro-workflow --tests`
- `cargo +nightly-2026-04-14 fmt --package fabro-hooks --package
fabro-workflow --check`
- `cargo +nightly-2026-04-14 clippy -p fabro-hooks -p fabro-workflow
--all-targets -- -D warnings`

---------

Co-authored-by: Jess Martin <jessmartin@gmail.com>
2026-05-20 09:31:46 -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, EnvCredentialSource};
use fabro_hooks::{
HookContext, HookDecision, HookDefinition, HookEvent, HookExecutionContext, HookRunner,
HookSettings,
};
use fabro_model::Catalog;
use fabro_types::RunId;
use tokio::fs;
fn test_llm_source() -> Arc<dyn CredentialSource> {
Arc::new(EnvCredentialSource::new())
}
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("printf ran > marker.txt".to_string()),
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");
}