use std::sync::Arc;
use kaish_kernel::{Kernel, KernelConfig};
async fn setup() -> Arc<Kernel> {
Kernel::new(KernelConfig::isolated().with_skip_validation(true))
.expect("failed to create kernel")
.into_arc()
}
#[tokio::test]
async fn for_loop_iterates_jq_raw_array() {
let k = setup().await;
let r = k
.execute(
r#"for f in $(echo '["a","b","c"]' | jq -r '.[]'); do echo "iter:$f"; done"#,
)
.await
.expect("script ran");
assert!(r.ok(), "exit code: {}, err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "iter:a\niter:b\niter:c");
}
#[tokio::test]
async fn for_loop_iterates_jq_compact_array() {
let k = setup().await;
let r = k
.execute(
r#"for n in $(echo '[1,2,3]' | jq -c '.[]'); do echo "iter:$n"; done"#,
)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "iter:1\niter:2\niter:3");
}
#[tokio::test]
async fn for_loop_iterates_jq_default_array() {
let k = setup().await;
let r = k
.execute(
r#"for s in $(echo '["x","y","z"]' | jq '.[]'); do echo "iter:$s"; done"#,
)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "iter:x\niter:y\niter:z");
}
#[tokio::test]
async fn single_value_raw_is_not_wrapped_in_array() {
let k = setup().await;
let r = k
.execute(
r#"for n in $(echo '{"name":"Alice"}' | jq -r '.name'); do echo "iter:$n"; done"#,
)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "iter:Alice");
}
#[tokio::test]
async fn single_value_json_keeps_scalar_data() {
let k = setup().await;
let r = k
.execute(
r#"for n in $(echo '{"name":"Alice"}' | jq '.name'); do echo "iter:$n"; done"#,
)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "iter:Alice");
}
#[tokio::test]
async fn raw_stdout_unchanged_for_pipe_consumers() {
let k = setup().await;
let r = k
.execute(r#"echo '["a","b","c"]' | jq -r '.[]'"#)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "a\nb\nc");
}
#[tokio::test]
async fn jq_output_can_feed_next_pipe_stage() {
let k = setup().await;
let r = k
.execute(r#"echo '["a","b","c"]' | jq -r '.[]' | wc -l"#)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "3");
}
#[tokio::test]
async fn for_loop_iterates_cut_output_per_line() {
let k = setup().await;
let r = k
.execute(
r#"for v in $(printf 'alice,25\nbob,30\ncarol,28\n' | cut -d ',' -f 1); do echo "iter:$v"; done"#,
)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "iter:alice\niter:bob\niter:carol");
}
#[tokio::test]
async fn data_field_is_array_for_multi_value_output() {
let k = setup().await;
let r = k
.execute(r#"echo '["a","b","c"]' | jq -r '.[]'"#)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
let data = r.data.as_ref().expect("data should be populated");
match data {
kaish_kernel::ast::Value::Json(serde_json::Value::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], serde_json::json!("a"));
assert_eq!(arr[1], serde_json::json!("b"));
assert_eq!(arr[2], serde_json::json!("c"));
}
other => panic!("expected Value::Json(Array), got {:?}", other),
}
}
#[tokio::test]
async fn jq_null_input_short() {
let k = setup().await;
let r = k.execute(r#"jq -n '1 + 2'"#).await.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "3");
}
#[tokio::test]
async fn jq_null_input_long() {
let k = setup().await;
let r = k
.execute(r#"jq --null-input '1 + 2'"#)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "3");
}
#[tokio::test]
async fn jq_arg_binds_string() {
let k = setup().await;
let r = k
.execute(r#"jq -n --arg name amy -r '$name'"#)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "amy");
}
#[tokio::test]
async fn jq_argjson_binds_integer() {
let k = setup().await;
let r = k
.execute(r#"jq -n --argjson x 42 '$x + 1'"#)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "43");
}
#[tokio::test]
async fn jq_argjson_binds_object() {
let k = setup().await;
let r = k
.execute(r#"jq -n --argjson v '{"x":7}' -r '$v.x'"#)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "7");
}
#[tokio::test]
async fn jq_multiple_arg_occurrences_accumulate() {
let k = setup().await;
let r = k
.execute(r#"jq -n --arg a one --arg b two -r '$a + "-" + $b'"#)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "one-two");
}
#[tokio::test]
async fn jq_arg_from_kaish_variable() {
let k = setup().await;
let r = k
.execute(
r#"
R='{"name":"amy","id":1}'
jq -n --argjson r "$R" -r '$r.name'
"#,
)
.await
.expect("script ran");
assert!(r.ok(), "exit: {} err: {}", r.code, r.err);
assert_eq!(r.text_out().trim(), "amy");
}
#[tokio::test]
async fn jq_arg_missing_second_operand_errors() {
let k = setup().await;
let r = k
.execute(r#"jq -n --arg name"#)
.await
.expect("script ran");
assert!(!r.ok(), "expected non-zero exit, got ok. out={:?}", r.text_out());
}
#[tokio::test]
async fn jq_argjson_invalid_json_errors() {
let k = setup().await;
let r = k
.execute(r#"jq -n --argjson x 'not-json' '.'"#)
.await
.expect("script ran");
assert!(
!r.ok(),
"expected non-zero exit for invalid --argjson. out={:?} err={:?}",
r.text_out(),
r.err
);
}