mirror of
https://github.com/steel-dev/cli.git
synced 2026-09-14 20:47:34 +08:00
253 lines
8.6 KiB
Rust
253 lines
8.6 KiB
Rust
//! Integration tests for the `steel dev` command family.
|
|
//!
|
|
//! Mirrors the contracts originally tested by the Node.js suite
|
|
//! (tests/unit/dev-local-runtime.test.ts), adapted for the Rust implementation.
|
|
//!
|
|
//! Since dev.rs shells out to `git` and `docker compose`, we test behavior via
|
|
//! the compiled binary (black-box) rather than calling functions directly.
|
|
|
|
use std::process::Command;
|
|
|
|
fn steel_cmd() -> Command {
|
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_steel"));
|
|
// Suppress update checks so they don't interfere with output.
|
|
cmd.arg("--no-update-check");
|
|
cmd
|
|
}
|
|
|
|
/// Helper: run `steel` with a temporary STEEL_CONFIG_DIR so tests are isolated.
|
|
/// Also sets STEEL_FORCE_TTY=1 to keep text output in piped test environments.
|
|
fn steel_with_tmp_config(tmp: &tempfile::TempDir) -> Command {
|
|
let mut cmd = steel_cmd();
|
|
cmd.env("STEEL_CONFIG_DIR", tmp.path());
|
|
cmd.env("STEEL_FORCE_TTY", "1");
|
|
cmd
|
|
}
|
|
|
|
// ─── repo_path derivation ────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn repo_path_is_config_dir_plus_steel_browser() {
|
|
// The config module is public, so we can verify the derivation in-process.
|
|
let dir = steel_cli::config::config_dir_with(Some("/tmp/test-steel-cfg"));
|
|
let repo = dir.join("steel-browser");
|
|
assert_eq!(
|
|
repo,
|
|
std::path::PathBuf::from("/tmp/test-steel-cfg/steel-browser"),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn repo_path_respects_steel_config_dir_env() {
|
|
// When STEEL_CONFIG_DIR is set, config_dir() picks it up.
|
|
let dir = steel_cli::config::config_dir_with(Some("/custom/dir"));
|
|
assert_eq!(dir, std::path::PathBuf::from("/custom/dir"));
|
|
}
|
|
|
|
#[test]
|
|
fn repo_path_falls_back_to_home_config() {
|
|
// When env var is absent, falls back to ~/.config/steel
|
|
let dir = steel_cli::config::config_dir_with(None);
|
|
assert!(
|
|
dir.ends_with(".config/steel"),
|
|
"expected path ending with .config/steel, got: {}",
|
|
dir.display()
|
|
);
|
|
}
|
|
|
|
// ─── Help output: verify flags exist ─────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn dev_install_help_shows_expected_flags() {
|
|
let output = steel_cmd()
|
|
.args(["dev", "install", "--help"])
|
|
.output()
|
|
.expect("failed to run steel");
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(output.status.success(), "steel dev install --help failed");
|
|
assert!(
|
|
stdout.contains("--repo-url"),
|
|
"missing --repo-url flag in help:\n{stdout}"
|
|
);
|
|
assert!(
|
|
stdout.contains("--verbose"),
|
|
"missing --verbose flag in help:\n{stdout}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dev_start_help_shows_expected_flags() {
|
|
let output = steel_cmd()
|
|
.args(["dev", "start", "--help"])
|
|
.output()
|
|
.expect("failed to run steel");
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(output.status.success(), "steel dev start --help failed");
|
|
assert!(
|
|
stdout.contains("--port"),
|
|
"missing --port flag in help:\n{stdout}"
|
|
);
|
|
assert!(
|
|
stdout.contains("--verbose"),
|
|
"missing --verbose flag in help:\n{stdout}"
|
|
);
|
|
assert!(
|
|
stdout.contains("--docker-check"),
|
|
"missing --docker-check flag in help:\n{stdout}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dev_stop_help_shows_expected_flags() {
|
|
let output = steel_cmd()
|
|
.args(["dev", "stop", "--help"])
|
|
.output()
|
|
.expect("failed to run steel");
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(output.status.success(), "steel dev stop --help failed");
|
|
assert!(
|
|
stdout.contains("--verbose"),
|
|
"missing --verbose flag in help:\n{stdout}"
|
|
);
|
|
}
|
|
|
|
// ─── Start/stop without installed runtime ────────────────────────────────────
|
|
|
|
#[test]
|
|
fn start_without_install_fails_with_not_installed_message() {
|
|
let tmp = tempfile::tempdir().expect("failed to create temp dir");
|
|
|
|
let output = steel_with_tmp_config(&tmp)
|
|
.args(["dev", "start"])
|
|
.output()
|
|
.expect("failed to run steel");
|
|
|
|
assert!(
|
|
!output.status.success(),
|
|
"steel dev start should fail when runtime is not installed"
|
|
);
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains("not installed"),
|
|
"expected 'not installed' in stderr, got:\n{stderr}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn stop_without_install_fails_with_not_installed_message() {
|
|
let tmp = tempfile::tempdir().expect("failed to create temp dir");
|
|
|
|
let output = steel_with_tmp_config(&tmp)
|
|
.args(["dev", "stop"])
|
|
.output()
|
|
.expect("failed to run steel");
|
|
|
|
assert!(
|
|
!output.status.success(),
|
|
"steel dev stop should fail when runtime is not installed"
|
|
);
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains("not installed"),
|
|
"expected 'not installed' in stderr, got:\n{stderr}"
|
|
);
|
|
}
|
|
|
|
// ─── Installation idempotency ────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn install_skips_when_repo_already_exists() {
|
|
let tmp = tempfile::tempdir().expect("failed to create temp dir");
|
|
|
|
// Pre-create the steel-browser directory to simulate a prior install.
|
|
let repo_dir = tmp.path().join("steel-browser");
|
|
std::fs::create_dir_all(&repo_dir).expect("failed to create fake repo dir");
|
|
|
|
let output = steel_with_tmp_config(&tmp)
|
|
.args(["dev", "install"])
|
|
.output()
|
|
.expect("failed to run steel");
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"steel dev install should succeed (skip) when repo exists"
|
|
);
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains("already installed"),
|
|
"expected 'already installed' in stderr, got:\n{stderr}"
|
|
);
|
|
}
|
|
|
|
// ─── Default port fallback ───────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn start_default_port_is_3000() {
|
|
// We verify through help text that the default is 3000 (the implementation
|
|
// uses `unwrap_or(3000)`). Additionally, we can check the help for the port
|
|
// flag description. This is a structural test rather than runtime since we
|
|
// cannot start docker in CI.
|
|
let output = steel_cmd()
|
|
.args(["dev", "start", "--help"])
|
|
.output()
|
|
.expect("failed to run steel");
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
// The port flag exists and is optional (no "required" marker).
|
|
assert!(stdout.contains("--port"), "missing --port flag");
|
|
assert!(
|
|
stdout.contains("-p"),
|
|
"missing -p short alias for --port:\n{stdout}"
|
|
);
|
|
|
|
// Verify via clap introspection that port default is None (meaning the code
|
|
// falls back to 3000 in run_start).
|
|
use clap::CommandFactory;
|
|
let root = steel_cli::commands::Cli::command();
|
|
let dev = root
|
|
.get_subcommands()
|
|
.find(|s| s.get_name() == "dev")
|
|
.expect("dev subcommand not found");
|
|
let start = dev
|
|
.get_subcommands()
|
|
.find(|s| s.get_name() == "start")
|
|
.expect("start subcommand not found");
|
|
let port_arg = start
|
|
.get_arguments()
|
|
.find(|a| a.get_long() == Some("port"))
|
|
.expect("--port arg not found");
|
|
// Port is optional (not required), so the code path hits unwrap_or(3000).
|
|
assert!(
|
|
!port_arg.is_required_set(),
|
|
"--port should be optional so the default of 3000 applies"
|
|
);
|
|
}
|
|
|
|
// ─── Docker compose v2 only ──────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn rust_impl_uses_docker_compose_v2() {
|
|
// The Rust implementation exclusively uses `docker compose` (v2 plugin syntax),
|
|
// not the legacy `docker-compose` binary. We verify this structurally: the
|
|
// source file should contain "docker" + "compose" args, not "docker-compose".
|
|
let source = include_str!("../src/commands/dev.rs");
|
|
assert!(
|
|
source.contains(r#"ProcessCommand::new("docker")"#),
|
|
"expected docker command invocation"
|
|
);
|
|
assert!(
|
|
source.contains(r#""compose""#),
|
|
"expected 'compose' as a subcommand argument"
|
|
);
|
|
assert!(
|
|
!source.contains("docker-compose"),
|
|
"should not use legacy docker-compose binary"
|
|
);
|
|
}
|