mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-17 05:31:16 +08:00
Run cargo fmt for both the root workspace and the standalone e2e Rust workspace from the rust format and format-check tasks. Apply rustfmt to the e2e sources so the expanded formatting check passes. Signed-off-by: Kris Hicks <khicks@nvidia.com>
330 lines
10 KiB
Rust
330 lines
10 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
#![cfg(feature = "e2e")]
|
|
|
|
//! E2E tests for sandbox/global settings CLI workflows.
|
|
//!
|
|
//! Covers:
|
|
//! - Sandbox settings start as `<unset>`
|
|
//! - Sandbox setting set + read
|
|
//! - Global override blocks sandbox writes for that key
|
|
//! - Global get + global delete
|
|
//! - Sandbox-level control resumes after global delete
|
|
|
|
use std::process::Stdio;
|
|
use std::sync::Mutex;
|
|
use std::time::Duration;
|
|
|
|
use openshell_e2e::harness::binary::{openshell_bin, openshell_cmd};
|
|
use openshell_e2e::harness::output::strip_ansi;
|
|
use openshell_e2e::harness::sandbox::SandboxGuard;
|
|
use tokio::time::{Instant, sleep};
|
|
|
|
const TEST_KEY: &str = "ocsf_json_enabled";
|
|
static SETTINGS_E2E_LOCK: Mutex<()> = Mutex::new(());
|
|
|
|
struct CliResult {
|
|
clean_output: String,
|
|
success: bool,
|
|
exit_code: Option<i32>,
|
|
}
|
|
|
|
/// Best-effort global setting cleanup that runs even on test panic.
|
|
struct GlobalSettingCleanup {
|
|
key: &'static str,
|
|
}
|
|
|
|
impl GlobalSettingCleanup {
|
|
fn new(key: &'static str) -> Self {
|
|
Self { key }
|
|
}
|
|
}
|
|
|
|
impl Drop for GlobalSettingCleanup {
|
|
fn drop(&mut self) {
|
|
let _ = std::process::Command::new(openshell_bin())
|
|
.args(["settings", "delete", "--global", "--key", self.key, "--yes"])
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status();
|
|
}
|
|
}
|
|
|
|
async fn run_cli(args: &[&str]) -> CliResult {
|
|
let mut cmd = openshell_cmd();
|
|
cmd.args(args).stdout(Stdio::piped()).stderr(Stdio::piped());
|
|
|
|
let output = cmd.output().await.expect("spawn openshell command");
|
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
let combined = format!("{stdout}{stderr}");
|
|
|
|
CliResult {
|
|
clean_output: strip_ansi(&combined),
|
|
success: output.status.success(),
|
|
exit_code: output.status.code(),
|
|
}
|
|
}
|
|
|
|
fn assert_setting_line(output: &str, key: &str, expected: &str) {
|
|
let needle = format!("{key} = {expected}");
|
|
assert!(
|
|
output.contains(&needle),
|
|
"expected setting line '{needle}' in output:\n{output}"
|
|
);
|
|
}
|
|
|
|
fn assert_setting_line_with_scope(output: &str, key: &str, expected: &str, scope: &str) {
|
|
let needle = format!("{key} = {expected} ({scope})");
|
|
assert!(
|
|
output.contains(&needle),
|
|
"expected setting line '{needle}' in output:\n{output}"
|
|
);
|
|
}
|
|
|
|
/// Poll `settings get` until the expected value and scope appear for a key.
|
|
async fn wait_for_setting_value(
|
|
sandbox_name: &str,
|
|
key: &str,
|
|
expected_value: &str,
|
|
expected_scope: &str,
|
|
timeout_duration: Duration,
|
|
) {
|
|
let needle = format!("{key} = {expected_value} ({expected_scope})");
|
|
let start = Instant::now();
|
|
loop {
|
|
let result = run_cli(&["settings", "get", sandbox_name]).await;
|
|
if result.success && result.clean_output.contains(&needle) {
|
|
return;
|
|
}
|
|
if start.elapsed() > timeout_duration {
|
|
panic!(
|
|
"timed out after {:?} waiting for setting line '{needle}' in output:\n{}",
|
|
timeout_duration, result.clean_output
|
|
);
|
|
}
|
|
sleep(Duration::from_secs(1)).await;
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn settings_global_override_round_trip() {
|
|
let _settings_lock = SETTINGS_E2E_LOCK
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
let _global_cleanup = GlobalSettingCleanup::new(TEST_KEY);
|
|
|
|
let cleanup_before =
|
|
run_cli(&["settings", "delete", "--global", "--key", TEST_KEY, "--yes"]).await;
|
|
assert!(
|
|
cleanup_before.success,
|
|
"initial global setting cleanup should succeed (exit {:?}):\n{}",
|
|
cleanup_before.exit_code, cleanup_before.clean_output
|
|
);
|
|
|
|
let mut guard =
|
|
SandboxGuard::create_keep(&["sh", "-c", "echo Ready && sleep infinity"], "Ready")
|
|
.await
|
|
.expect("create keep sandbox");
|
|
|
|
let initial = run_cli(&["settings", "get", &guard.name]).await;
|
|
assert!(
|
|
initial.success,
|
|
"settings get should succeed (exit {:?}):\n{}",
|
|
initial.exit_code, initial.clean_output
|
|
);
|
|
assert_setting_line_with_scope(&initial.clean_output, TEST_KEY, "<unset>", "unset");
|
|
|
|
let set_sandbox = run_cli(&[
|
|
"settings",
|
|
"set",
|
|
&guard.name,
|
|
"--key",
|
|
TEST_KEY,
|
|
"--value",
|
|
"true",
|
|
])
|
|
.await;
|
|
assert!(
|
|
set_sandbox.success,
|
|
"sandbox setting set should succeed (exit {:?}):\n{}",
|
|
set_sandbox.exit_code, set_sandbox.clean_output
|
|
);
|
|
|
|
// Wait for the gateway to reflect the setting change. The setting is stored
|
|
// server-side, so `settings get` reads it immediately. Poll to ensure the
|
|
// config_revision has been updated (visible in the output).
|
|
wait_for_setting_value(
|
|
&guard.name,
|
|
TEST_KEY,
|
|
"true",
|
|
"sandbox",
|
|
Duration::from_secs(10),
|
|
)
|
|
.await;
|
|
|
|
let after_sandbox_set = run_cli(&["settings", "get", &guard.name]).await;
|
|
assert!(
|
|
after_sandbox_set.success,
|
|
"settings get after sandbox set should succeed (exit {:?}):\n{}",
|
|
after_sandbox_set.exit_code, after_sandbox_set.clean_output
|
|
);
|
|
assert_setting_line_with_scope(&after_sandbox_set.clean_output, TEST_KEY, "true", "sandbox");
|
|
|
|
// Sandbox-scoped delete should succeed when not globally managed.
|
|
let sandbox_delete = run_cli(&["settings", "delete", &guard.name, "--key", TEST_KEY]).await;
|
|
assert!(
|
|
sandbox_delete.success,
|
|
"sandbox setting delete should succeed (exit {:?}):\n{}",
|
|
sandbox_delete.exit_code, sandbox_delete.clean_output
|
|
);
|
|
assert!(
|
|
sandbox_delete
|
|
.clean_output
|
|
.contains("Deleted sandbox setting"),
|
|
"expected sandbox delete confirmation:\n{}",
|
|
sandbox_delete.clean_output
|
|
);
|
|
|
|
// After delete, the key should be unset again.
|
|
let after_sandbox_delete = run_cli(&["settings", "get", &guard.name]).await;
|
|
assert!(
|
|
after_sandbox_delete.success,
|
|
"settings get after sandbox delete should succeed:\n{}",
|
|
after_sandbox_delete.clean_output
|
|
);
|
|
assert_setting_line_with_scope(
|
|
&after_sandbox_delete.clean_output,
|
|
TEST_KEY,
|
|
"<unset>",
|
|
"unset",
|
|
);
|
|
|
|
// Re-set at sandbox scope so we can test global override next.
|
|
let re_set = run_cli(&[
|
|
"settings",
|
|
"set",
|
|
&guard.name,
|
|
"--key",
|
|
TEST_KEY,
|
|
"--value",
|
|
"true",
|
|
])
|
|
.await;
|
|
assert!(
|
|
re_set.success,
|
|
"re-set should succeed:\n{}",
|
|
re_set.clean_output
|
|
);
|
|
|
|
let set_global = run_cli(&[
|
|
"settings", "set", "--global", "--key", TEST_KEY, "--value", "false", "--yes",
|
|
])
|
|
.await;
|
|
assert!(
|
|
set_global.success,
|
|
"global setting set should succeed (exit {:?}):\n{}",
|
|
set_global.exit_code, set_global.clean_output
|
|
);
|
|
assert!(
|
|
set_global
|
|
.clean_output
|
|
.contains(&format!("Set global setting {TEST_KEY}=false")),
|
|
"expected global set output:\n{}",
|
|
set_global.clean_output
|
|
);
|
|
|
|
let blocked_sandbox_set = run_cli(&[
|
|
"settings",
|
|
"set",
|
|
&guard.name,
|
|
"--key",
|
|
TEST_KEY,
|
|
"--value",
|
|
"true",
|
|
])
|
|
.await;
|
|
assert!(
|
|
!blocked_sandbox_set.success,
|
|
"sandbox setting should fail while key is global-managed:\n{}",
|
|
blocked_sandbox_set.clean_output
|
|
);
|
|
assert!(
|
|
blocked_sandbox_set.clean_output.contains("is managed"),
|
|
"expected 'managed globally' error:\n{}",
|
|
blocked_sandbox_set.clean_output
|
|
);
|
|
|
|
// Sandbox-scoped delete should also be blocked while globally managed.
|
|
let blocked_sandbox_delete =
|
|
run_cli(&["settings", "delete", &guard.name, "--key", TEST_KEY]).await;
|
|
assert!(
|
|
!blocked_sandbox_delete.success,
|
|
"sandbox delete should fail while key is global-managed:\n{}",
|
|
blocked_sandbox_delete.clean_output
|
|
);
|
|
|
|
let global_get = run_cli(&["settings", "get", "--global"]).await;
|
|
assert!(
|
|
global_get.success,
|
|
"global settings get should succeed (exit {:?}):\n{}",
|
|
global_get.exit_code, global_get.clean_output
|
|
);
|
|
assert_setting_line(&global_get.clean_output, TEST_KEY, "false");
|
|
|
|
let delete_global =
|
|
run_cli(&["settings", "delete", "--global", "--key", TEST_KEY, "--yes"]).await;
|
|
assert!(
|
|
delete_global.success,
|
|
"global settings delete should succeed (exit {:?}):\n{}",
|
|
delete_global.exit_code, delete_global.clean_output
|
|
);
|
|
assert!(
|
|
delete_global
|
|
.clean_output
|
|
.contains(&format!("Deleted global setting {TEST_KEY}")),
|
|
"expected global delete confirmation in output:\n{}",
|
|
delete_global.clean_output
|
|
);
|
|
|
|
let global_after_delete = run_cli(&["settings", "get", "--global"]).await;
|
|
assert!(
|
|
global_after_delete.success,
|
|
"global settings get after delete should succeed (exit {:?}):\n{}",
|
|
global_after_delete.exit_code, global_after_delete.clean_output
|
|
);
|
|
assert_setting_line(&global_after_delete.clean_output, TEST_KEY, "<unset>");
|
|
|
|
let sandbox_set_after_delete = run_cli(&[
|
|
"settings",
|
|
"set",
|
|
&guard.name,
|
|
"--key",
|
|
TEST_KEY,
|
|
"--value",
|
|
"false",
|
|
])
|
|
.await;
|
|
assert!(
|
|
sandbox_set_after_delete.success,
|
|
"sandbox setting set after global delete should succeed (exit {:?}):\n{}",
|
|
sandbox_set_after_delete.exit_code, sandbox_set_after_delete.clean_output
|
|
);
|
|
|
|
let sandbox_after_delete = run_cli(&["settings", "get", &guard.name]).await;
|
|
assert!(
|
|
sandbox_after_delete.success,
|
|
"settings get after global delete should succeed (exit {:?}):\n{}",
|
|
sandbox_after_delete.exit_code, sandbox_after_delete.clean_output
|
|
);
|
|
assert_setting_line_with_scope(
|
|
&sandbox_after_delete.clean_output,
|
|
TEST_KEY,
|
|
"false",
|
|
"sandbox",
|
|
);
|
|
|
|
guard.cleanup().await;
|
|
}
|