use std::sync::Arc;
use kaish_kernel::{Kernel, KernelConfig};
async fn sandbox_kernel() -> Arc<Kernel> {
let config = KernelConfig::isolated();
Kernel::new(config)
.expect("failed to create isolated kernel")
.into_arc()
}
#[tokio::test]
async fn sandbox_echo() {
let k = sandbox_kernel().await;
let r = k.execute("echo hello").await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "hello");
}
#[tokio::test]
async fn sandbox_pipeline() {
let k = sandbox_kernel().await;
let r = k.execute("seq 1 5 | sort -rn | head -3").await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "5\n4\n3");
}
#[tokio::test]
async fn sandbox_awk() {
let k = sandbox_kernel().await;
let r = k.execute(r#"echo "one two three" | awk "{print NF}""#).await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "3");
}
#[tokio::test]
async fn sandbox_jq() {
let k = sandbox_kernel().await;
let r = k.execute(r#"echo '{"x":42}' | jq ".x""#).await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "42");
}
#[tokio::test]
async fn sandbox_vfs_is_memory_only() {
let k = sandbox_kernel().await;
let r = k.execute("ls /").await.expect("execute failed");
assert!(r.ok());
let out = r.text_out().into_owned();
let entries: Vec<&str> = out.trim().lines().collect();
assert!(entries.contains(&"tmp"), "expected 'tmp' in ls /: {:?}", entries);
assert!(entries.contains(&"v"), "expected 'v' in ls /: {:?}", entries);
assert!(!entries.contains(&"home"), "real filesystem leaking: {:?}", entries);
assert!(!entries.contains(&"usr"), "real filesystem leaking: {:?}", entries);
assert!(!entries.contains(&"etc"), "real filesystem leaking: {:?}", entries);
}
#[tokio::test]
async fn sandbox_write_and_read_via_vfs() {
let k = sandbox_kernel().await;
let r = k.execute("echo foo > /tmp/bar && cat /tmp/bar").await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "foo");
}
#[tokio::test]
async fn sandbox_append_redirect_via_vfs() {
let k = sandbox_kernel().await;
let r = k.execute("echo first > /tmp/app && echo second >> /tmp/app && cat /tmp/app")
.await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "first\nsecond");
}
#[tokio::test]
async fn sandbox_relative_redirect_honors_cwd() {
let k = sandbox_kernel().await;
let r = k
.execute("mkdir -p /tmp/sub && cd /tmp/sub && echo x > f && cat f")
.await
.expect("execute failed");
assert!(r.ok(), "expected success, got code={} err={:?}", r.code, r.err);
assert_eq!(r.text_out().trim(), "x");
let at_cwd = k.execute("cat /tmp/sub/f").await.expect("execute failed");
assert!(at_cwd.ok(), "file should exist at /tmp/sub/f");
assert_eq!(at_cwd.text_out().trim(), "x");
let at_root = k.execute("cat /f").await.expect("execute failed");
assert!(!at_root.ok(), "relative redirect must not write to /f");
}
#[tokio::test]
async fn sandbox_relative_append_honors_cwd() {
let k = sandbox_kernel().await;
let r = k
.execute("mkdir -p /tmp/sub && cd /tmp/sub && echo a > log && echo b >> log && cat log")
.await
.expect("execute failed");
assert!(r.ok(), "expected success, got code={} err={:?}", r.code, r.err);
assert_eq!(r.text_out().trim(), "a\nb");
}
#[tokio::test]
async fn sandbox_stdin_redirect_reads_via_vfs_and_cwd() {
let k = sandbox_kernel().await;
let r = k
.execute("mkdir -p /tmp/sub && cd /tmp/sub && echo payload > in && cat < in")
.await
.expect("execute failed");
assert!(r.ok(), "expected success, got code={} err={:?}", r.code, r.err);
assert_eq!(r.text_out().trim(), "payload");
}
#[tokio::test]
async fn sandbox_stdin_redirect_missing_file_fails_loud() {
let k = sandbox_kernel().await;
let r = k.execute("cat < /tmp/does-not-exist").await.expect("execute failed");
assert!(!r.ok(), "missing stdin redirect source should fail");
assert!(
r.err.contains("redirect"),
"error should mention redirect, got: {:?}",
r.err
);
}
#[tokio::test]
async fn sandbox_redirect_target_command_substitution() {
let k = sandbox_kernel().await;
let r = k
.execute("echo hi > $(echo /tmp/gen.txt) && cat /tmp/gen.txt")
.await
.expect("execute failed");
assert!(r.ok(), "expected success, got code={} err={:?}", r.code, r.err);
assert_eq!(r.text_out().trim(), "hi");
let r = k
.execute("echo via-subst > /tmp/src && cat < $(echo /tmp/src)")
.await
.expect("execute failed");
assert!(r.ok(), "expected success, got code={} err={:?}", r.code, r.err);
assert_eq!(r.text_out().trim(), "via-subst");
}
#[tokio::test]
async fn sandbox_test_builtin_cannot_probe_host() {
let k = sandbox_kernel().await;
let r = k.execute("test -r /etc/passwd").await.expect("execute failed");
assert!(!r.ok(), "test -r /etc/passwd should be false in sandbox (file doesn't exist in VFS)");
let r = k.execute("test -w /etc/shadow").await.expect("execute failed");
assert!(!r.ok(), "test -w /etc/shadow should be false in sandbox");
let r = k.execute("echo data > /tmp/probe && test -r /tmp/probe").await.expect("execute failed");
assert!(r.ok(), "test -r should work for VFS files");
}
#[tokio::test]
async fn sandbox_external_commands_blocked() {
let k = sandbox_kernel().await;
let r = k.execute("/usr/bin/curl https://example.com").await.expect("execute failed");
assert_eq!(
r.code, 127,
"external command should be policy-blocked (127), not merely fail: out={:?} err={:?}",
r.text_out(),
r.err
);
assert!(
r.err.contains("command not found"),
"policy block should surface as command-not-found: err={:?}",
r.err
);
}
#[tokio::test]
async fn sandbox_help_builtins_lists_available() {
let k = sandbox_kernel().await;
let r = k.execute("help builtins").await.expect("execute failed");
assert!(r.ok());
let out = r.text_out();
assert!(out.contains("echo"), "echo should be listed");
assert!(out.contains("cat"), "cat should be listed");
assert!(out.contains("sort"), "sort should be listed");
assert!(out.contains("jq"), "jq should be listed");
}
#[cfg(not(any(feature = "subprocess", feature = "git", feature = "tokens")))]
#[tokio::test]
async fn sandbox_no_native_builtins() {
let k = sandbox_kernel().await;
let r = k.execute("help builtins").await.expect("execute failed");
assert!(r.ok());
let out = r.text_out();
assert!(!out.contains(" git "), "git should not be listed without native");
assert!(!out.contains("tokens"), "tokens should not be listed without native");
assert!(!out.contains(" bg "), "bg should not be listed without native");
assert!(!out.contains(" fg "), "fg should not be listed without native");
assert!(!out.contains("which"), "which should not be listed without native");
}
#[tokio::test]
async fn sandbox_wc_pipeline() {
let k = sandbox_kernel().await;
let r = k.execute("echo hello | wc -c").await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "6"); }
#[tokio::test]
async fn sandbox_sed() {
let k = sandbox_kernel().await;
let r = k.execute(r#"echo "hello world" | sed "s/world/kaish/""#).await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "hello kaish");
}
#[tokio::test]
async fn sandbox_grep() {
let k = sandbox_kernel().await;
let r = k.execute(r#"printf "apple\nbanana\ncherry" | grep "an""#).await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "banana");
}
#[tokio::test]
async fn sandbox_diff() {
let k = sandbox_kernel().await;
let r = k.execute(r#"echo "a" > /tmp/a.txt; echo "b" > /tmp/b.txt; diff /tmp/a.txt /tmp/b.txt"#)
.await.expect("execute failed");
assert_eq!(r.code, 1);
assert!(r.text_out().contains("a") || r.text_out().contains("b"));
}
#[tokio::test]
async fn sandbox_base64_roundtrip() {
let k = sandbox_kernel().await;
let r = k.execute(r#"echo "secret" | base64 | base64 -d"#).await.expect("execute failed");
assert!(r.ok());
assert_eq!(r.text_out().trim(), "secret");
}
#[tokio::test]
async fn sandbox_checksum() {
let k = sandbox_kernel().await;
let r = k.execute(r#"echo -n "hello" | checksum --sha256"#).await.expect("execute failed");
assert!(r.ok());
assert!(r.text_out().trim().starts_with("2cf24dba"));
}