use lingshu_tools::{ToolContext, ToolRegistry};
use lingshu_types::Platform;
use serde_json::json;
use tempfile::TempDir;
use tokio_util::sync::CancellationToken;
fn ctx_in(workspace: &std::path::Path, lingshu_home: &std::path::Path) -> ToolContext {
ToolContext {
task_id: "file-write-tmp-e2e".into(),
cwd: workspace.to_path_buf(),
session_id: "file-write-tmp-e2e-session".into(),
user_task: None,
cancel: CancellationToken::new(),
config: lingshu_tools::AppConfigRef {
lingshu_home: lingshu_home.to_path_buf(),
..Default::default()
},
state_db: None,
platform: Platform::Cli,
process_table: None,
provider: None,
tool_registry: None,
delegate_depth: 0,
delegate_agent_id: None,
delegate_parent_id: None,
sub_agent_runner: None,
delegation_event_tx: None,
clarify_tx: None,
approval_tx: None,
on_skills_changed: None,
gateway_sender: None,
origin_chat: None,
session_key: None,
todo_store: None,
current_tool_call_id: None,
current_tool_name: None,
injected_messages: None,
tool_progress_tx: None,
watch_notification_tx: None,
mutation_turn: None,
lsp_gate: None,
kanban_task_id: None,
}
}
async fn write_then_read(registry: &ToolRegistry, ctx: &ToolContext, path: &str, content: &str) {
registry
.dispatch("write_file", json!({"path": path, "content": content}), ctx)
.await
.unwrap_or_else(|e| panic!("write_file {path} failed: {e}"));
let body = registry
.dispatch(
"read_file",
json!({"path": path, "line_numbers": false}),
ctx,
)
.await
.unwrap_or_else(|e| panic!("read_file {path} failed: {e}"));
assert!(
body.contains(content),
"read back should contain written content for {path}"
);
}
#[tokio::test]
async fn e2e_write_read_absolute_tmp_alias() {
let workspace = TempDir::new().expect("workspace");
let lingshu_home = TempDir::new().expect("lingshu_home");
let registry = ToolRegistry::new();
let ctx = ctx_in(workspace.path(), lingshu_home.path());
write_then_read(®istry, &ctx, "/tmp/osint_report.md", "# OSINT report\n").await;
let mapped = lingshu_home.path().join("tmp/files/osint_report.md");
assert!(
mapped.is_file(),
"mapped file must exist at {}",
mapped.display()
);
}
#[tokio::test]
async fn e2e_write_read_relative_tmp_files_alias() {
let workspace = TempDir::new().expect("workspace");
let lingshu_home = TempDir::new().expect("lingshu_home");
let registry = ToolRegistry::new();
let ctx = ctx_in(workspace.path(), lingshu_home.path());
write_then_read(
®istry,
&ctx,
"tmp/files/raphael_osint_report.md",
"# Raphaël OSINT\n",
)
.await;
let mapped = lingshu_home
.path()
.join("tmp/files/raphael_osint_report.md");
assert!(
mapped.is_file(),
"relative tmp/files must map to lingshu_home: {}",
mapped.display()
);
assert!(
!workspace.path().join("tmp").exists(),
"must not create workspace-local tmp/ tree"
);
}
#[tokio::test]
async fn e2e_write_read_nested_tmp_files_subdirs_auto_created() {
let workspace = TempDir::new().expect("workspace");
let lingshu_home = TempDir::new().expect("lingshu_home");
let registry = ToolRegistry::new();
let ctx = ctx_in(workspace.path(), lingshu_home.path());
write_then_read(
®istry,
&ctx,
"tmp/files/osint/2026/report.md",
"nested\n",
)
.await;
let mapped = lingshu_home.path().join("tmp/files/osint/2026/report.md");
assert!(mapped.is_file());
}