// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 #![cfg(feature = "e2e")] //! Smoke test: verify the gateway is healthy, create a sandbox, exec a //! command inside it, and tear it down. //! //! This test is cluster-agnostic — it works against any running gateway //! (Docker-based cluster or openshell-driver-vm microVM). The `e2e:vm` mise //! task uses it to validate the VM gateway after boot. use std::process::Stdio; use std::time::Duration; use openshell_e2e::harness::binary::openshell_cmd; use openshell_e2e::harness::output::strip_ansi; use openshell_e2e::harness::sandbox::SandboxGuard; /// End-to-end smoke test: status → create → exec → list → delete. #[tokio::test] async fn gateway_smoke() { // ── 1. Gateway must be reachable ────────────────────────────────── let mut clean_status = String::new(); let mut status_ok = false; for _ in 0..15 { let mut status_cmd = openshell_cmd(); status_cmd .arg("status") .stdout(Stdio::piped()) .stderr(Stdio::piped()); let status_out = status_cmd .output() .await .expect("failed to run openshell status"); let status_text = format!( "{}{}", String::from_utf8_lossy(&status_out.stdout), String::from_utf8_lossy(&status_out.stderr), ); clean_status = strip_ansi(&status_text); if status_out.status.success() && clean_status.contains("Connected") { status_ok = true; break; } tokio::time::sleep(Duration::from_secs(2)).await; } assert!( status_ok, "openshell status never became healthy:\n{clean_status}", ); // ── 2. Create a sandbox and exec a command ─────────────────────── // Default behaviour keeps the sandbox alive after the command exits, // so we can verify it in the list before cleaning up. let mut sb = SandboxGuard::create(&["--", "echo", "smoke-ok"]) .await .expect("sandbox create should succeed"); assert!( sb.create_output.contains("smoke-ok"), "expected 'smoke-ok' in sandbox output:\n{}", sb.create_output, ); if std::env::var_os("OPENSHELL_E2E_EXPECT_VM_OVERLAY").is_some() { assert_vm_overlay_root(&sb.name).await; } // ── 3. Verify the sandbox appeared in the list ─────────────────── let mut list_cmd = openshell_cmd(); list_cmd .args(["sandbox", "list", "--names"]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let list_out = list_cmd .output() .await .expect("failed to run openshell sandbox list"); let list_text = strip_ansi(&format!( "{}{}", String::from_utf8_lossy(&list_out.stdout), String::from_utf8_lossy(&list_out.stderr), )); assert!( list_text.contains(&sb.name), "sandbox '{}' should appear in list output:\n{list_text}", sb.name, ); // ── 4. Cleanup ─────────────────────────────────────────────────── sb.cleanup().await; } async fn assert_vm_overlay_root(sandbox_name: &str) { let script = concat!( "set -eu; ", "test \"$(stat -f -c %T /)\" = \"overlayfs\"; ", "printf \"overlay-write\\n\" > /sandbox/overlay-check; ", "test \"$(cat /sandbox/overlay-check)\" = \"overlay-write\"; ", "if [ -e /opt/openshell/tls/tls.key ]; then ", "test \"$(stat -c %a /opt/openshell/tls/tls.key)\" = \"600\"; ", "fi; ", "echo vm-overlay-ok", ); let mut exec_cmd = openshell_cmd(); exec_cmd .args(["sandbox", "exec", "--name", sandbox_name, "--no-tty", "--"]) .arg("sh") .arg("-lc") .arg(script) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let output = exec_cmd .output() .await .expect("failed to run VM overlay assertion"); let combined = strip_ansi(&format!( "{}{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr), )); assert!( output.status.success() && combined.contains("vm-overlay-ok"), "VM overlay assertion failed (status {:?}):\n{combined}", output.status.code(), ); }