test(infra): intégration handlers Tauri managed_temp_* + routage idea_managed_temp_* — #191
This commit is contained in:
@ -565,19 +565,20 @@ mod tests {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct FakeManagedTempTools {
|
struct FakeManagedTempTools {
|
||||||
calls: Mutex<Vec<(String, String, Value)>>,
|
calls: Mutex<Vec<(String, String, String, Value)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ManagedTempToolProvider for FakeManagedTempTools {
|
impl ManagedTempToolProvider for FakeManagedTempTools {
|
||||||
async fn handle_managed_temp_tool(
|
async fn handle_managed_temp_tool(
|
||||||
&self,
|
&self,
|
||||||
_project: &Project,
|
project: &Project,
|
||||||
requester: &str,
|
requester: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
arguments: Value,
|
arguments: Value,
|
||||||
) -> Result<Value, ManagedTempToolError> {
|
) -> Result<Value, ManagedTempToolError> {
|
||||||
self.calls.lock().unwrap().push((
|
self.calls.lock().unwrap().push((
|
||||||
|
project.root.as_str().to_owned(),
|
||||||
requester.to_owned(),
|
requester.to_owned(),
|
||||||
name.to_owned(),
|
name.to_owned(),
|
||||||
arguments.clone(),
|
arguments.clone(),
|
||||||
@ -953,4 +954,176 @@ mod tests {
|
|||||||
|
|
||||||
let _ = std::fs::remove_dir_all(temp);
|
let _ = std::fs::remove_dir_all(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn openai_managed_temp_read_tool_routes_to_provider_with_project_and_requester() {
|
||||||
|
let temp = std::env::temp_dir().join(format!(
|
||||||
|
"idea-app-tauri-openai-managed-temp-read-{}",
|
||||||
|
Uuid::new_v4()
|
||||||
|
));
|
||||||
|
let core = backend::BackendCore::build(temp.clone());
|
||||||
|
let requester = AgentId::from_uuid(Uuid::from_u128(85)).to_string();
|
||||||
|
let managed_temp_tools = fake_managed_temp_tools();
|
||||||
|
let invoker = AppOpenAiToolInvoker::new(
|
||||||
|
Arc::clone(&core.orchestrator_service),
|
||||||
|
Arc::new(FakeProjects::with(project())) as Arc<dyn ProjectStore>,
|
||||||
|
Arc::new(FakePolicies::default()),
|
||||||
|
mcp_permissions(ProjectMcpToolPermissions::default()),
|
||||||
|
Arc::new(FakeTicketTools::default()),
|
||||||
|
Arc::new(FakeTemplateTools::default()),
|
||||||
|
managed_temp_tools.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = invoker
|
||||||
|
.call(
|
||||||
|
"idea_managed_temp_resolve",
|
||||||
|
&json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester,
|
||||||
|
"handle": "lease_123",
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("managed temp resolve must route to the dedicated provider");
|
||||||
|
let result: Value = serde_json::from_str(&result).unwrap();
|
||||||
|
assert_eq!(result["requester"], json!(requester));
|
||||||
|
assert_eq!(result["handle"], "lease_123");
|
||||||
|
|
||||||
|
let calls = managed_temp_tools.calls.lock().unwrap();
|
||||||
|
assert_eq!(calls.len(), 1);
|
||||||
|
assert_eq!(calls[0].0, "/tmp/project");
|
||||||
|
assert_eq!(calls[0].1, requester);
|
||||||
|
assert_eq!(calls[0].2, "idea_managed_temp_resolve");
|
||||||
|
assert_eq!(
|
||||||
|
calls[0].3,
|
||||||
|
json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester,
|
||||||
|
"handle": "lease_123"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn openai_managed_temp_write_tools_route_only_when_explicitly_allowed() {
|
||||||
|
let temp = std::env::temp_dir().join(format!(
|
||||||
|
"idea-app-tauri-openai-managed-temp-write-{}",
|
||||||
|
Uuid::new_v4()
|
||||||
|
));
|
||||||
|
let core = backend::BackendCore::build(temp.clone());
|
||||||
|
let agent = AgentId::from_uuid(Uuid::from_u128(86));
|
||||||
|
let requester = agent.to_string();
|
||||||
|
let managed_temp_tools = fake_managed_temp_tools();
|
||||||
|
let invoker = AppOpenAiToolInvoker::new(
|
||||||
|
Arc::clone(&core.orchestrator_service),
|
||||||
|
Arc::new(FakeProjects::with(project())) as Arc<dyn ProjectStore>,
|
||||||
|
Arc::new(FakePolicies::default()),
|
||||||
|
mcp_permissions(allow_doc(
|
||||||
|
agent,
|
||||||
|
&[
|
||||||
|
"idea_managed_temp_allocate",
|
||||||
|
"idea_managed_temp_release",
|
||||||
|
"idea_managed_temp_gc",
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
Arc::new(FakeTicketTools::default()),
|
||||||
|
Arc::new(FakeTemplateTools::default()),
|
||||||
|
managed_temp_tools.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let allocate = invoker
|
||||||
|
.call(
|
||||||
|
"idea_managed_temp_allocate",
|
||||||
|
&json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester,
|
||||||
|
"kind": "directory",
|
||||||
|
"purpose": "scratch",
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("explicit allowlist should route allocate");
|
||||||
|
let allocate: Value = serde_json::from_str(&allocate).unwrap();
|
||||||
|
assert_eq!(allocate["requester"], json!(requester));
|
||||||
|
|
||||||
|
invoker
|
||||||
|
.call(
|
||||||
|
"idea_managed_temp_release",
|
||||||
|
&json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester,
|
||||||
|
"handle": "lease_123",
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("explicit allowlist should route release");
|
||||||
|
|
||||||
|
invoker
|
||||||
|
.call(
|
||||||
|
"idea_managed_temp_gc",
|
||||||
|
&json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester,
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("explicit allowlist should route gc");
|
||||||
|
|
||||||
|
let calls = managed_temp_tools.calls.lock().unwrap();
|
||||||
|
assert_eq!(calls.len(), 3);
|
||||||
|
assert_eq!(calls[0].0, "/tmp/project");
|
||||||
|
assert_eq!(calls[0].1, requester);
|
||||||
|
assert_eq!(calls[0].2, "idea_managed_temp_allocate");
|
||||||
|
assert_eq!(
|
||||||
|
calls[0].3,
|
||||||
|
json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester,
|
||||||
|
"kind": "directory",
|
||||||
|
"purpose": "scratch"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(calls[1].2, "idea_managed_temp_release");
|
||||||
|
assert_eq!(
|
||||||
|
calls[1].3,
|
||||||
|
json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester,
|
||||||
|
"handle": "lease_123"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(calls[2].2, "idea_managed_temp_gc");
|
||||||
|
assert_eq!(
|
||||||
|
calls[2].3,
|
||||||
|
json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let denied = invoker
|
||||||
|
.call(
|
||||||
|
"idea_managed_temp_resolve",
|
||||||
|
&json!({
|
||||||
|
PROJECT_ROOT_ARG: "/tmp/project",
|
||||||
|
REQUESTER_ARG: requester,
|
||||||
|
"handle": "lease_123",
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect_err("non-allowlisted managed temp tool must be rejected");
|
||||||
|
assert!(
|
||||||
|
matches!(denied, ToolInvocationError::Rejected(ref message) if message.contains("idea_managed_temp_resolve")),
|
||||||
|
"expected readable rejection, got {denied:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(temp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
111
crates/app-tauri/tests/managed_temp_handlers.rs
Normal file
111
crates/app-tauri/tests/managed_temp_handlers.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use app_tauri_lib::{commands, state::AppState};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use tauri::test::{get_ipc_response, mock_builder, mock_context, noop_assets, INVOKE_KEY};
|
||||||
|
|
||||||
|
fn test_app_data_dir(label: &str) -> PathBuf {
|
||||||
|
let nanos = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_nanos();
|
||||||
|
std::env::temp_dir().join(format!(
|
||||||
|
"idea-managed-temp-handlers-{label}-{}-{nanos}",
|
||||||
|
std::process::id()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke_command<W: AsRef<tauri::Webview<tauri::test::MockRuntime>>>(
|
||||||
|
webview: &W,
|
||||||
|
command: &str,
|
||||||
|
body: Value,
|
||||||
|
) -> Result<Value, Value> {
|
||||||
|
get_ipc_response(
|
||||||
|
webview,
|
||||||
|
tauri::webview::InvokeRequest {
|
||||||
|
cmd: command.to_owned(),
|
||||||
|
callback: tauri::ipc::CallbackFn(0),
|
||||||
|
error: tauri::ipc::CallbackFn(1),
|
||||||
|
url: "tauri://localhost".parse().unwrap(),
|
||||||
|
body: tauri::ipc::InvokeBody::Json(body),
|
||||||
|
headers: Default::default(),
|
||||||
|
invoke_key: INVOKE_KEY.to_owned(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map(|body| body.deserialize::<Value>().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn managed_temp_handlers_round_trip_via_tauri_invoke_handler() {
|
||||||
|
let app_data = test_app_data_dir("round-trip");
|
||||||
|
let app = mock_builder()
|
||||||
|
.manage(AppState::build(app_data.clone()))
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
commands::managed_temp_allocate,
|
||||||
|
commands::managed_temp_resolve,
|
||||||
|
commands::managed_temp_release,
|
||||||
|
commands::managed_temp_gc,
|
||||||
|
])
|
||||||
|
.build(mock_context(noop_assets()))
|
||||||
|
.expect("mock app builds");
|
||||||
|
let webview = tauri::WebviewWindowBuilder::new(&app, "main", Default::default())
|
||||||
|
.build()
|
||||||
|
.expect("mock webview builds");
|
||||||
|
|
||||||
|
let allocated = invoke_command(
|
||||||
|
&webview,
|
||||||
|
"managed_temp_allocate",
|
||||||
|
json!({
|
||||||
|
"request": {
|
||||||
|
"kind": "directory",
|
||||||
|
"purpose": "scratch"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("allocate must succeed");
|
||||||
|
let handle = allocated["lease"]["handle"]
|
||||||
|
.as_str()
|
||||||
|
.expect("opaque handle")
|
||||||
|
.to_owned();
|
||||||
|
let path = allocated["lease"]["path"]
|
||||||
|
.as_str()
|
||||||
|
.expect("lease path")
|
||||||
|
.to_owned();
|
||||||
|
assert!(
|
||||||
|
path.contains("/managed-tmp/"),
|
||||||
|
"real AppState store should allocate under managed-tmp, got {path}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let resolved = invoke_command(
|
||||||
|
&webview,
|
||||||
|
"managed_temp_resolve",
|
||||||
|
json!({ "request": { "handle": handle } }),
|
||||||
|
)
|
||||||
|
.expect("resolve must succeed while lease is active");
|
||||||
|
assert_eq!(resolved["lease"]["path"], allocated["lease"]["path"]);
|
||||||
|
assert_eq!(resolved["lease"]["handle"], allocated["lease"]["handle"]);
|
||||||
|
|
||||||
|
let released = invoke_command(
|
||||||
|
&webview,
|
||||||
|
"managed_temp_release",
|
||||||
|
json!({ "request": { "handle": handle } }),
|
||||||
|
)
|
||||||
|
.expect("release must succeed");
|
||||||
|
assert_eq!(released["lease"]["handle"], allocated["lease"]["handle"]);
|
||||||
|
assert_eq!(released["lease"]["state"], json!("released"));
|
||||||
|
|
||||||
|
let err = invoke_command(
|
||||||
|
&webview,
|
||||||
|
"managed_temp_resolve",
|
||||||
|
json!({ "request": { "handle": handle } }),
|
||||||
|
)
|
||||||
|
.expect_err("resolve after release must fail");
|
||||||
|
assert_eq!(err["code"], "NOT_FOUND");
|
||||||
|
|
||||||
|
let gc = invoke_command(&webview, "managed_temp_gc", json!({}))
|
||||||
|
.expect("gc must succeed after release");
|
||||||
|
assert_eq!(gc["deleted"], 1);
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(app_data).ok();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user