#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::sync::Arc;
use async_trait::async_trait;
use kaish_kernel::tools::{ToolArgs, ToolCtx, ToolSchema};
use kaish_kernel::vfs::{MemoryFs, VfsRouter};
use kaish_kernel::{Kernel, KernelBackend, KernelConfig, LocalBackend, Tool};
use kaish_types::{ExecResult, Value};
struct RecordDataTool;
#[async_trait]
impl Tool for RecordDataTool {
fn name(&self) -> &str {
"recdata"
}
fn schema(&self) -> ToolSchema {
ToolSchema::new("recdata", "test fixture: record in .data, empty .out")
}
async fn execute(&self, _args: ToolArgs, _ctx: &mut dyn ToolCtx) -> ExecResult {
let data = Value::Json(serde_json::json!({"a": 1, "b": 2}));
ExecResult::from_parts(0, String::new(), String::new(), Some(data))
}
}
struct ListDataTool;
#[async_trait]
impl Tool for ListDataTool {
fn name(&self) -> &str {
"listdata"
}
fn schema(&self) -> ToolSchema {
ToolSchema::new("listdata", "test fixture: list in .data, empty .out")
}
async fn execute(&self, _args: ToolArgs, _ctx: &mut dyn ToolCtx) -> ExecResult {
let data = Value::Json(serde_json::json!([1, 2, 3]));
ExecResult::from_parts(0, String::new(), String::new(), Some(data))
}
}
struct ScalarDataTool;
#[async_trait]
impl Tool for ScalarDataTool {
fn name(&self) -> &str {
"scalardata"
}
fn schema(&self) -> ToolSchema {
ToolSchema::new("scalardata", "test fixture: scalar string in .data, empty .out")
}
async fn execute(&self, _args: ToolArgs, _ctx: &mut dyn ToolCtx) -> ExecResult {
let data = Value::String("hello".to_string());
ExecResult::from_parts(0, String::new(), String::new(), Some(data))
}
}
struct OutWinsTool;
#[async_trait]
impl Tool for OutWinsTool {
fn name(&self) -> &str {
"outwins"
}
fn schema(&self) -> ToolSchema {
ToolSchema::new("outwins", "test fixture: non-empty .out plus .data")
}
async fn execute(&self, _args: ToolArgs, _ctx: &mut dyn ToolCtx) -> ExecResult {
let data = Value::Json(serde_json::json!({"should_not_render": true}));
ExecResult::from_parts(0, "TEXT-WINS".to_string(), String::new(), Some(data))
}
}
fn kernel_with_tools() -> Arc<Kernel> {
let mut vfs = VfsRouter::new();
vfs.mount("/", MemoryFs::new());
let backend: Arc<dyn KernelBackend> = Arc::new(LocalBackend::new(Arc::new(vfs)));
Kernel::with_backend(backend, KernelConfig::isolated(), |_| {}, |tools| {
tools.register(RecordDataTool);
tools.register(ListDataTool);
tools.register(ScalarDataTool);
tools.register(OutWinsTool);
})
.expect("with_backend kernel")
.into_arc()
}
#[tokio::test]
async fn record_data_only_result_renders_as_compact_json_in_string() {
let kernel = kernel_with_tools();
let r = kernel
.execute(r#"msg="parsed: $(recdata) done"; echo "$msg""#)
.await
.expect("execute");
assert_eq!(r.code, 0);
assert_eq!(r.text_out().trim(), r#"parsed: {"a":1,"b":2} done"#);
}
#[tokio::test]
async fn list_data_only_result_renders_as_compact_json_in_string() {
let kernel = kernel_with_tools();
let r = kernel
.execute(r#"msg="items: $(listdata) done"; echo "$msg""#)
.await
.expect("execute");
assert_eq!(r.code, 0);
assert_eq!(r.text_out().trim(), "items: [1,2,3] done");
}
#[tokio::test]
async fn cmdsubst_and_bare_var_render_a_collection_identically() {
let kernel = kernel_with_tools();
let direct = kernel
.execute(r#"echo "got: $(recdata)""#)
.await
.expect("execute");
let via_var = kernel
.execute(r#"x=$(recdata); echo "got: $x""#)
.await
.expect("execute");
assert_eq!(direct.text_out().trim(), via_var.text_out().trim());
}
#[tokio::test]
async fn scalar_data_only_result_renders_unquoted_in_string() {
let kernel = kernel_with_tools();
let r = kernel
.execute(r#"msg="say: $(scalardata) done"; echo "$msg""#)
.await
.expect("execute");
assert_eq!(r.code, 0);
assert_eq!(r.text_out().trim(), "say: hello done");
}
#[tokio::test]
async fn nonempty_out_wins_over_data_in_string() {
let kernel = kernel_with_tools();
let r = kernel
.execute(r#"msg="result: $(outwins) done"; echo "$msg""#)
.await
.expect("execute");
assert_eq!(r.code, 0);
assert_eq!(r.text_out().trim(), "result: TEXT-WINS done");
}
#[tokio::test]
async fn fromjson_record_in_string_interpolation() {
let kernel = kernel_with_tools();
let r = kernel
.execute(r#"j='{"a":1,"b":2}'; msg="parsed: $(fromjson <<< $j) done"; echo "$msg""#)
.await
.expect("execute");
assert_eq!(r.code, 0);
assert_eq!(r.text_out().trim(), r#"parsed: {"a":1,"b":2} done"#);
}
#[tokio::test]
async fn keys_list_in_string_interpolation() {
let kernel = kernel_with_tools();
let r = kernel
.execute(r#"h={"a": 1, "b": 2}; msg="keys: $(keys $h) done"; echo "$msg""#)
.await
.expect("execute");
assert_eq!(r.code, 0);
assert_eq!(r.text_out().trim(), r#"keys: ["a","b"] done"#);
}