fabro/lib/components/fabro-hooks/tests/host_command_hooks.rs
Bryan Helmkamp ba3adb92c7
Serve the local sandbox through the driver Host provider
SandboxSpec::Local and run reconnect now build a DriverSandbox over the
sandbox-driver Host provider instead of fabro's own LocalSandbox, which
is deleted. fabro_sandbox::local_sandbox designates the working
directory (created when missing, never removed), creates the Host handle
in a per-process registry, and learns the platform up front. A local
sandbox reports no provider id: it is its directory, which the run
record already carries, so reconnect rebuilds the handle over that
directory rather than by id.

The credential filter for explicit environment variables and the Bash
readiness probe now come from the exec layer and the driver's activate
helper. Test call sites move to the async constructor; test factories
that must stay synchronous share the parent session's sandbox handle.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 16:09:45 -06:00

83 lines
2.6 KiB
Rust

use std::path::Path;
use std::sync::Arc;
use fabro_agent::{Sandbox, local_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"))
}
async fn test_sandbox() -> Arc<dyn Sandbox> {
Arc::new(
local_sandbox(std::env::current_dir().expect("test process should have a cwd"))
.await
.expect("local sandbox should be created"),
)
}
#[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, test_sandbox().await, 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");
}