Skip to content

Commit fa40835

Browse files
committed
test(cli): migrate gateway-free smoke coverage
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent 6f00d5c commit fa40835

6 files changed

Lines changed: 571 additions & 841 deletions

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Rendered help and command-shape checks for the `openshell` binary.
5+
6+
mod common;
7+
8+
use common::run_isolated;
9+
10+
#[test]
11+
fn root_help_shows_top_level_commands() {
12+
let output = run_isolated(&["--help"]);
13+
assert_eq!(output.code, 0, "openshell --help:\n{}", output.combined);
14+
15+
for command in ["gateway", "status", "sandbox", "forward", "logs", "policy"] {
16+
assert!(
17+
output.combined.contains(command),
18+
"expected '{command}' in openshell --help:\n{}",
19+
output.combined
20+
);
21+
}
22+
}
23+
24+
#[test]
25+
fn gateway_help_shows_registration_commands_and_omits_lifecycle_commands() {
26+
let output = run_isolated(&["gateway", "--help"]);
27+
assert_eq!(output.code, 0, "gateway --help:\n{}", output.combined);
28+
29+
for command in ["add", "remove", "login", "logout", "select", "info", "list"] {
30+
assert!(
31+
output.combined.contains(command),
32+
"expected '{command}' in gateway --help:\n{}",
33+
output.combined
34+
);
35+
}
36+
for removed in ["start", "stop", "destroy"] {
37+
assert!(
38+
!output.combined.contains(removed),
39+
"unexpected removed command '{removed}' in gateway --help:\n{}",
40+
output.combined
41+
);
42+
}
43+
}
44+
45+
#[test]
46+
fn sandbox_help_shows_transfer_and_lifecycle_commands() {
47+
let output = run_isolated(&["sandbox", "--help"]);
48+
assert_eq!(output.code, 0, "sandbox --help:\n{}", output.combined);
49+
50+
for command in [
51+
"upload", "download", "create", "get", "list", "delete", "connect",
52+
] {
53+
assert!(
54+
output.combined.contains(command),
55+
"expected '{command}' in sandbox --help:\n{}",
56+
output.combined
57+
);
58+
}
59+
}
60+
61+
#[test]
62+
fn sandbox_create_help_shows_creation_flags() {
63+
let output = run_isolated(&["sandbox", "create", "--help"]);
64+
assert_eq!(
65+
output.code, 0,
66+
"sandbox create --help:\n{}",
67+
output.combined
68+
);
69+
70+
for flag in [
71+
"--gpu",
72+
"--upload",
73+
"--no-git-ignore",
74+
"--editor",
75+
"--auto-providers",
76+
"--no-auto-providers",
77+
] {
78+
assert!(
79+
output.combined.contains(flag),
80+
"expected '{flag}' in sandbox create --help:\n{}",
81+
output.combined
82+
);
83+
}
84+
}
85+
86+
#[test]
87+
fn sandbox_connect_help_shows_editor_flag() {
88+
let output = run_isolated(&["sandbox", "connect", "--help"]);
89+
assert_eq!(
90+
output.code, 0,
91+
"sandbox connect --help:\n{}",
92+
output.combined
93+
);
94+
assert!(output.combined.contains("--editor"), "{}", output.combined);
95+
}
96+
97+
#[test]
98+
fn gateway_add_help_shows_endpoint_and_gateway_type_flags() {
99+
let output = run_isolated(&["gateway", "add", "--help"]);
100+
assert_eq!(output.code, 0, "gateway add --help:\n{}", output.combined);
101+
102+
for expected in ["--name", "--remote", "--local"] {
103+
assert!(
104+
output.combined.contains(expected),
105+
"expected '{expected}' in gateway add --help:\n{}",
106+
output.combined
107+
);
108+
}
109+
assert!(
110+
output.combined.contains("endpoint") || output.combined.contains("<ENDPOINT>"),
111+
"expected endpoint argument in gateway add --help:\n{}",
112+
output.combined
113+
);
114+
}
115+
116+
#[test]
117+
fn gateway_login_help_describes_authentication() {
118+
let output = run_isolated(&["gateway", "login", "--help"]);
119+
assert_eq!(output.code, 0, "gateway login --help:\n{}", output.combined);
120+
121+
let help = output.combined.to_lowercase();
122+
assert!(
123+
["authenticat", "cloudflare", "login", "browser"]
124+
.iter()
125+
.any(|term| help.contains(term)),
126+
"expected auth-related gateway login help:\n{}",
127+
output.combined
128+
);
129+
}
130+
131+
#[test]
132+
fn removed_gateway_lifecycle_subcommands_fail_to_parse() {
133+
for command in ["start", "stop", "destroy"] {
134+
let output = run_isolated(&["gateway", command, "--help"]);
135+
assert_ne!(
136+
output.code, 0,
137+
"gateway {command} should fail after lifecycle command removal"
138+
);
139+
assert!(
140+
output.combined.contains("unrecognized subcommand")
141+
|| output.combined.contains("error:"),
142+
"expected parser error for gateway {command}:\n{}",
143+
output.combined
144+
);
145+
}
146+
}
147+
148+
#[test]
149+
fn gateway_add_rejects_conflicting_type_flags() {
150+
let conflicting = run_isolated(&[
151+
"gateway",
152+
"add",
153+
"https://example.com",
154+
"--remote",
155+
"user@host",
156+
"--local",
157+
]);
158+
assert_ne!(
159+
conflicting.code, 0,
160+
"--remote and --local should conflict:\n{}",
161+
conflicting.combined
162+
);
163+
}
164+
165+
#[test]
166+
fn gateway_add_rejects_removed_ssh_key_flag() {
167+
let removed = run_isolated(&[
168+
"gateway",
169+
"add",
170+
"https://example.com",
171+
"--ssh-key",
172+
"/tmp/fake-key",
173+
]);
174+
assert_ne!(
175+
removed.code, 0,
176+
"removed --ssh-key flag should fail:\n{}",
177+
removed.combined
178+
);
179+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Cloudflare gateway registration behavior that needs no external network.
5+
6+
mod common;
7+
8+
use common::{run, run_isolated};
9+
10+
#[test]
11+
fn gateway_add_creates_cloudflare_metadata_and_selects_gateway() {
12+
let config_dir = tempfile::tempdir().expect("create user config dir");
13+
let system_dir = tempfile::tempdir().expect("create system config dir");
14+
15+
let output = run(
16+
config_dir.path(),
17+
system_dir.path(),
18+
&[
19+
"gateway",
20+
"add",
21+
"https://my-gateway.example.com",
22+
"--name",
23+
"test-cf-gw",
24+
],
25+
);
26+
assert_eq!(output.code, 0, "gateway add:\n{}", output.combined);
27+
28+
let metadata_path = config_dir
29+
.path()
30+
.join("openshell/gateways/test-cf-gw/metadata.json");
31+
let metadata: serde_json::Value = serde_json::from_slice(
32+
&std::fs::read(&metadata_path).expect("read Cloudflare gateway metadata"),
33+
)
34+
.expect("parse Cloudflare gateway metadata");
35+
assert_eq!(metadata["auth_mode"], "cloudflare_jwt");
36+
assert_eq!(
37+
metadata["gateway_endpoint"],
38+
"https://my-gateway.example.com"
39+
);
40+
assert_eq!(metadata["name"], "test-cf-gw");
41+
assert_eq!(metadata["is_remote"], true);
42+
43+
let active = std::fs::read_to_string(config_dir.path().join("openshell/active_gateway"))
44+
.expect("read active gateway");
45+
assert_eq!(active.trim(), "test-cf-gw");
46+
assert!(
47+
output.combined.contains("test-cf-gw") && output.combined.contains("added"),
48+
"{}",
49+
output.combined
50+
);
51+
}
52+
53+
#[test]
54+
fn gateway_add_derives_cloudflare_name_from_hostname() {
55+
let config_dir = tempfile::tempdir().expect("create user config dir");
56+
let system_dir = tempfile::tempdir().expect("create system config dir");
57+
58+
let output = run(
59+
config_dir.path(),
60+
system_dir.path(),
61+
&["gateway", "add", "https://my-special-gateway.brevlab.com"],
62+
);
63+
assert_eq!(output.code, 0, "gateway add:\n{}", output.combined);
64+
assert!(
65+
config_dir
66+
.path()
67+
.join("openshell/gateways/my-special-gateway.brevlab.com/metadata.json")
68+
.exists()
69+
);
70+
}
71+
72+
#[test]
73+
fn ssh_gateway_shorthand_conflicts_with_local_type() {
74+
let local = run_isolated(&["gateway", "add", "ssh://user@host:8080", "--local"]);
75+
assert_ne!(local.code, 0, "ssh:// with --local should fail");
76+
}
77+
78+
#[test]
79+
fn ssh_gateway_shorthand_conflicts_with_explicit_remote() {
80+
let remote = run_isolated(&[
81+
"gateway",
82+
"add",
83+
"ssh://user@host:8080",
84+
"--remote",
85+
"user@host",
86+
]);
87+
assert_ne!(remote.code, 0, "ssh:// with --remote should fail");
88+
}
89+
90+
#[test]
91+
fn ssh_gateway_shorthand_requires_port() {
92+
let output = run_isolated(&["gateway", "add", "ssh://user@host"]);
93+
assert_ne!(output.code, 0, "ssh:// without port should fail");
94+
assert!(output.combined.contains("port"), "{}", output.combined);
95+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::path::Path;
5+
use std::process::{Command, Stdio};
6+
7+
pub struct CliOutput {
8+
#[allow(dead_code)]
9+
pub stdout: String,
10+
pub combined: String,
11+
pub code: i32,
12+
}
13+
14+
pub fn run(config_dir: &Path, system_dir: &Path, args: &[&str]) -> CliOutput {
15+
let output = Command::new(env!("CARGO_BIN_EXE_openshell"))
16+
.args(args)
17+
.env("XDG_CONFIG_HOME", config_dir)
18+
.env("HOME", config_dir)
19+
.env("OPENSHELL_SYSTEM_GATEWAY_DIR", system_dir)
20+
.env("OPENSHELL_NO_BROWSER", "1")
21+
.env_remove("OPENSHELL_GATEWAY")
22+
.env_remove("OPENSHELL_GATEWAY_ENDPOINT")
23+
.stdin(Stdio::null())
24+
.output()
25+
.expect("run openshell");
26+
27+
let stdout = String::from_utf8(output.stdout).expect("stdout is UTF-8");
28+
let stderr = String::from_utf8(output.stderr).expect("stderr is UTF-8");
29+
CliOutput {
30+
combined: format!("{stdout}{stderr}"),
31+
stdout,
32+
code: output.status.code().unwrap_or(-1),
33+
}
34+
}
35+
36+
pub fn run_isolated(args: &[&str]) -> CliOutput {
37+
let config_dir = tempfile::tempdir().expect("create isolated user config dir");
38+
let system_dir = tempfile::tempdir().expect("create isolated system config dir");
39+
run(config_dir.path(), system_dir.path(), args)
40+
}

0 commit comments

Comments
 (0)