mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
test(agents): bind draft readiness contract
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
@@ -257,9 +257,9 @@ pub fn get_baked_build_env() -> Vec<BakedEnvEntry> {
|
||||
/// patch to one exact saved agent. Projection reuses the production create,
|
||||
/// update, effective-config, harness, and readiness owners.
|
||||
#[tauri::command]
|
||||
pub async fn evaluate_agent_readiness_draft(
|
||||
pub async fn evaluate_agent_readiness_draft<R: tauri::Runtime>(
|
||||
draft: AgentReadinessDraft,
|
||||
app: AppHandle,
|
||||
app: tauri::AppHandle<R>,
|
||||
_state: State<'_, AppState>,
|
||||
) -> Result<AgentReadinessEvaluation, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
|
||||
@@ -12,6 +12,52 @@ use std::sync::Mutex;
|
||||
|
||||
static GOOSE_PATH_ROOT_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
struct EnvVarGuard {
|
||||
key: &'static str,
|
||||
prior: Option<std::ffi::OsString>,
|
||||
}
|
||||
|
||||
impl EnvVarGuard {
|
||||
fn set(key: &'static str, value: &std::path::Path) -> Self {
|
||||
let prior = std::env::var_os(key);
|
||||
std::env::set_var(key, value);
|
||||
Self { key, prior }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvVarGuard {
|
||||
fn drop(&mut self) {
|
||||
match &self.prior {
|
||||
Some(value) => std::env::set_var(self.key, value),
|
||||
None => std::env::remove_var(self.key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ReadinessIpcFixture {
|
||||
app: tauri::App<tauri::test::MockRuntime>,
|
||||
_temp: tempfile::TempDir,
|
||||
_home: EnvVarGuard,
|
||||
_xdg: EnvVarGuard,
|
||||
}
|
||||
|
||||
fn readiness_ipc_fixture() -> ReadinessIpcFixture {
|
||||
let temp = tempfile::tempdir().expect("temp app data root");
|
||||
let home = EnvVarGuard::set("HOME", temp.path());
|
||||
let xdg = EnvVarGuard::set("XDG_DATA_HOME", temp.path());
|
||||
let app = tauri::test::mock_builder()
|
||||
.manage(crate::app_state::build_app_state())
|
||||
.invoke_handler(tauri::generate_handler![evaluate_agent_readiness_draft])
|
||||
.build(tauri::test::mock_context(tauri::test::noop_assets()))
|
||||
.expect("mock app builds");
|
||||
ReadinessIpcFixture {
|
||||
app,
|
||||
_temp: temp,
|
||||
_home: home,
|
||||
_xdg: xdg,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a test body with GOOSE_PATH_ROOT set to a non-existent path so that the
|
||||
/// goose config file read returns `None`. Restores the prior value on exit.
|
||||
fn with_no_goose_config<T>(body: impl FnOnce() -> T) -> T {
|
||||
@@ -162,6 +208,100 @@ fn persona_without_model() -> AgentDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_handler_registers_readiness_command() {
|
||||
let lib = include_str!("../lib.rs");
|
||||
let handler_start = lib
|
||||
.find(".invoke_handler(tauri::generate_handler![")
|
||||
.expect("production invoke handler exists");
|
||||
let handler = &lib[handler_start..];
|
||||
assert!(
|
||||
handler.contains("evaluate_agent_readiness_draft,"),
|
||||
"removing readiness from the production Tauri handler must fail this test"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn readiness_command_is_registered_and_never_persists_its_draft() {
|
||||
let _env_guard = crate::managed_agents::lock_env_mutex();
|
||||
let fixture = readiness_ipc_fixture();
|
||||
let mut saved = agent_record();
|
||||
saved.pubkey = "a".repeat(64);
|
||||
saved.persona_id = None;
|
||||
saved.agent_command = "buzz-agent".to_string();
|
||||
saved.model = None;
|
||||
saved.provider = None;
|
||||
crate::managed_agents::save_managed_agents(fixture.app.handle(), &[saved.clone()])
|
||||
.expect("seed saved agent");
|
||||
let global = GlobalAgentConfig {
|
||||
provider: Some("openrouter".to_string()),
|
||||
model: Some("openrouter/model".to_string()),
|
||||
env_vars: std::collections::BTreeMap::from([(
|
||||
"OPENROUTER_API_KEY".to_string(),
|
||||
"sk-global".to_string(),
|
||||
)]),
|
||||
preferred_runtime: None,
|
||||
};
|
||||
let global_path = crate::managed_agents::managed_agents_base_dir(fixture.app.handle())
|
||||
.expect("managed agents dir")
|
||||
.join("global-agent-config.json");
|
||||
std::fs::write(
|
||||
global_path,
|
||||
serde_json::to_vec_pretty(&global).expect("serialize global config"),
|
||||
)
|
||||
.expect("seed global config");
|
||||
// Stabilize the store's ordinary built-in-definition reconciliation before
|
||||
// measuring the readiness command's own persistence behavior.
|
||||
crate::managed_agents::load_personas(fixture.app.handle()).expect("warm built-in definitions");
|
||||
let before = std::fs::read(
|
||||
crate::managed_agents::storage::managed_agents_store_path(fixture.app.handle())
|
||||
.expect("store path"),
|
||||
)
|
||||
.expect("seeded store bytes");
|
||||
|
||||
let webview = tauri::WebviewWindowBuilder::new(
|
||||
&fixture.app,
|
||||
"main",
|
||||
tauri::WebviewUrl::App("index.html".into()),
|
||||
)
|
||||
.build()
|
||||
.expect("mock webview");
|
||||
let response = tauri::test::get_ipc_response(
|
||||
&webview,
|
||||
tauri::webview::InvokeRequest {
|
||||
cmd: "evaluate_agent_readiness_draft".into(),
|
||||
callback: tauri::ipc::CallbackFn(0),
|
||||
error: tauri::ipc::CallbackFn(1),
|
||||
url: "tauri://localhost".parse().expect("invoke url"),
|
||||
body: tauri::ipc::InvokeBody::Json(serde_json::json!({
|
||||
"draft": {
|
||||
"kind": "existing",
|
||||
"config": {
|
||||
"pubkey": saved.pubkey
|
||||
}
|
||||
}
|
||||
})),
|
||||
headers: Default::default(),
|
||||
invoke_key: tauri::test::INVOKE_KEY.to_string(),
|
||||
},
|
||||
)
|
||||
.expect("registered readiness command succeeds")
|
||||
.deserialize::<serde_json::Value>()
|
||||
.expect("structured readiness response");
|
||||
|
||||
assert_eq!(response["ready"], true);
|
||||
assert_eq!(response["requirements"], serde_json::json!([]));
|
||||
let after = std::fs::read(
|
||||
crate::managed_agents::storage::managed_agents_store_path(fixture.app.handle())
|
||||
.expect("store path"),
|
||||
)
|
||||
.expect("store bytes after preview");
|
||||
assert_eq!(
|
||||
after, before,
|
||||
"readiness preview must not persist the patch"
|
||||
);
|
||||
}
|
||||
|
||||
/// A post-spawn session cache whose live model is `current_model` and whose
|
||||
/// `model_overridden` flag records whether a `SwitchModel` control signal set
|
||||
/// it (the live-switch signal).
|
||||
|
||||
@@ -208,6 +208,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn login_probe_fails_closed_when_success_output_exceeds_bound() {
|
||||
use std::fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let temp = tempfile::tempdir().expect("temp dir");
|
||||
let script_path = temp.path().join("noisy-login-probe");
|
||||
fs::write(
|
||||
&script_path,
|
||||
"#!/bin/sh\nhead -c 1100000 /dev/zero\nexit 0\n",
|
||||
)
|
||||
.expect("write noisy probe");
|
||||
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))
|
||||
.expect("chmod noisy probe");
|
||||
|
||||
assert_eq!(
|
||||
super::login_probe(&script_path, &["noisy-login-probe"], None),
|
||||
ProbeOutcome::LoggedOut,
|
||||
"login readiness must stay bound even when a successful CLI floods output"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn login_probe_logged_out_on_nonzero_without_config_signal() {
|
||||
|
||||
@@ -211,7 +211,7 @@ pub struct UpdateManagedAgentRequest {
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
/// Absent = don't touch. null = clear to agent default. "id" = set.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "crate::util::double_option")]
|
||||
pub model: Option<Option<String>>,
|
||||
#[serde(default)]
|
||||
pub system_prompt: Option<Option<String>>,
|
||||
|
||||
@@ -209,6 +209,20 @@ fn validate_respond_to_allowlist_accepts_empty() {
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_request_model_tristate_matches_provider_contract() {
|
||||
let absent: super::UpdateManagedAgentRequest =
|
||||
serde_json::from_str(r#"{"pubkey":"agent"}"#).expect("absent model");
|
||||
let clear: super::UpdateManagedAgentRequest =
|
||||
serde_json::from_str(r#"{"pubkey":"agent","model":null}"#).expect("null model");
|
||||
let set: super::UpdateManagedAgentRequest =
|
||||
serde_json::from_str(r#"{"pubkey":"agent","model":"model-id"}"#).expect("model value");
|
||||
|
||||
assert_eq!(absent.model, None);
|
||||
assert_eq!(clear.model, Some(None));
|
||||
assert_eq!(set.model, Some(Some("model-id".to_string())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_request_provider_tristate_absent_means_no_touch() {
|
||||
// A JSON payload with no "provider" key deserialized with `None` —
|
||||
|
||||
Reference in New Issue
Block a user