#![allow(clippy::unwrap_used, clippy::expect_used)]
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()
}
async fn run(k: &Kernel, script: &str) -> (String, i64, String) {
let r = k.execute(script).await.expect("kernel execute");
(r.text_out().trim().to_string(), r.code, r.err.clone())
}
#[tokio::test]
async fn fromjson_parses_object_preserving_key_order() {
let k = setup().await;
let (out, code, _) = run(&k, r#"fromjson '{"name":"amy","age":40}'"#).await;
assert_eq!(code, 0);
assert_eq!(out, r#"{"name":"amy","age":40}"#);
}
#[tokio::test]
async fn fromjson_unwraps_scalars() {
let k = setup().await;
assert_eq!(run(&k, "fromjson 42").await.0, "42");
assert_eq!(run(&k, "fromjson true").await.0, "true");
assert_eq!(run(&k, "fromjson null").await.0, "null");
assert_eq!(run(&k, r#"fromjson '"hi"'"#).await.0, r#""hi""#);
}
#[tokio::test]
async fn fromjson_reads_stdin() {
let k = setup().await;
let (out, code, _) = run(&k, r#"echo '{"a":1}' | fromjson"#).await;
assert_eq!(code, 0);
assert_eq!(out, r#"{"a":1}"#);
}
#[tokio::test]
async fn fromjson_empty_input_is_loud() {
let k = setup().await;
let (_, code, err) = run(&k, "fromjson ''").await;
assert_ne!(code, 0);
assert!(err.contains("empty"), "err: {err}");
}
#[tokio::test]
async fn fromjson_trailing_garbage_is_loud_with_position() {
let k = setup().await;
let (_, code, err) = run(&k, r#"fromjson '{"a":1} extra'"#).await;
assert_ne!(code, 0);
assert!(err.contains("invalid JSON"), "err: {err}");
assert!(err.contains("line") && err.contains("column"), "err: {err}");
}
#[tokio::test]
async fn fromjson_malformed_is_loud() {
let k = setup().await;
let (_, code, err) = run(&k, "fromjson '{not json}'").await;
assert_ne!(code, 0);
assert!(err.contains("invalid JSON"), "err: {err}");
}
#[tokio::test]
async fn fromjson_no_input_is_loud() {
let k = setup().await;
let (_, code, err) = run(&k, "fromjson").await;
assert_ne!(code, 0);
assert!(err.contains("no input"), "err: {err}");
}
#[tokio::test]
async fn fromjson_never_decodes_a_bytes_envelope() {
let k = setup().await;
let (out, code, err) = run(
&k,
r#"x=$(fromjson '{"_type":"bytes","encoding":"base64","data":"AQID","len":3}'); tojson $x"#,
)
.await;
assert_eq!(code, 0, "envelope-shaped object should stay a record; err: {err}");
assert!(out.contains(r#""_type""#), "out: {out}");
assert!(out.contains(r#""bytes""#), "out: {out}");
}
#[tokio::test]
async fn tojson_serializes_a_captured_value() {
let k = setup().await;
let (out, code, _) = run(&k, r#"x=$(fromjson '{"name":"amy"}'); tojson $x"#).await;
assert_eq!(code, 0);
assert_eq!(out, r#"{"name":"amy"}"#);
}
#[tokio::test]
async fn tojson_scalar_is_valid_json() {
let k = setup().await;
assert_eq!(run(&k, "tojson hello").await.0, r#""hello""#);
}
#[tokio::test]
async fn tojson_pretty_indents() {
let k = setup().await;
let (out, code, _) = run(&k, r#"x=$(fromjson '{"a":1}'); tojson --pretty $x"#).await;
assert_eq!(code, 0);
assert!(out.contains("\n"), "pretty output should have newlines: {out}");
assert!(out.contains(" \"a\""), "pretty output should indent: {out}");
}
#[tokio::test]
async fn tojson_no_value_is_loud() {
let k = setup().await;
let (_, code, err) = run(&k, "tojson").await;
assert_ne!(code, 0);
assert!(err.contains("no value"), "err: {err}");
}
#[tokio::test]
async fn tojson_refuses_binary() {
let k = setup().await;
let (_, code, err) = run(&k, "x=$(echo '/w==' | base64 -d); tojson $x").await;
assert_ne!(code, 0);
assert!(err.contains("bytes") || err.contains("binary"), "err: {err}");
}
#[tokio::test]
async fn roundtrip_law_holds_structurally() {
let k = setup().await;
let (out, code, _) =
run(&k, r#"x=$(fromjson '{"n":3,"tags":["a","b"]}'); tojson $x"#).await;
assert_eq!(code, 0);
assert_eq!(out, r#"{"n":3,"tags":["a","b"]}"#);
let (rt, code, _) = run(
&k,
r#"x=$(fromjson '{"n":3,"tags":["a","b"]}'); y=$(fromjson "$(tojson $x)"); tojson $y"#,
)
.await;
assert_eq!(code, 0);
assert_eq!(rt, r#"{"n":3,"tags":["a","b"]}"#);
}