#![allow(clippy::print_stdout, clippy::unwrap_used, clippy::expect_used)]
use std::ffi::OsStr;
use std::fs;
use std::process::Command;
use std::str;
fn clean_environment_command(bin: impl AsRef<OsStr>) -> Command {
let mut cmd = Command::new(bin);
cmd.env_clear()
.env("PATH", std::env::var("PATH").unwrap_or_default())
.env("HOME", std::env::var("HOME").unwrap_or_default())
.env("USER", std::env::var("USER").unwrap_or_default());
cmd
}
const EXPECTED_VERSION: &str = env!("CARGO_PKG_VERSION");
const CUENV_BIN: &str = env!("CARGO_BIN_EXE_cuenv");
fn run_cuenv_command(args: &[&str]) -> Result<(String, String, bool), Box<dyn std::error::Error>> {
let mut cmd = clean_environment_command(CUENV_BIN);
for arg in args {
cmd.arg(arg);
}
let output = cmd.output()?;
let stdout = str::from_utf8(&output.stdout)?.to_string();
let stderr = str::from_utf8(&output.stderr)?.to_string();
let success = output.status.success();
Ok((stdout, stderr, success))
}
fn get_testexamples_path() -> String {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let project_root = std::path::Path::new(manifest_dir)
.parent() .and_then(|p| p.parent()) .expect("Failed to find project root");
project_root
.join("examples/env-basic")
.to_string_lossy()
.to_string()
}
fn create_git_test_env() -> (tempfile::TempDir, String) {
let temp_dir = tempfile::Builder::new()
.prefix("cuenv_test_")
.tempdir()
.expect("Failed to create temp directory");
let temp_path = temp_dir.path();
Command::new("git")
.args(["init"])
.current_dir(temp_path)
.output()
.expect("Failed to init git repo");
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(temp_path)
.output()
.expect("Failed to configure git email");
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(temp_path)
.output()
.expect("Failed to configure git name");
let cue_mod_dir = temp_path.join("cue.mod");
fs::create_dir_all(&cue_mod_dir).expect("Failed to create cue.mod directory");
fs::write(
cue_mod_dir.join("module.cue"),
r#"module: "test.example/sync"
language: version: "v0.9.0"
"#,
)
.expect("Failed to write module.cue");
fs::write(
temp_path.join("env.cue"),
r#"package cuenv
name: "sync-test"
env: {
TEST_VAR: "test_value"
}
"#,
)
.expect("Failed to write env.cue");
let path_str = temp_path.to_string_lossy().to_string();
(temp_dir, path_str)
}
#[test]
fn test_version_command_basic() {
let result = run_cuenv_command(&["version"]);
match result {
Ok((stdout, _stderr, success)) => {
assert!(success, "Command should succeed");
assert!(stdout.contains("cuenv"));
assert!(stdout.contains(EXPECTED_VERSION));
assert!(stdout.contains("Authors:"));
assert!(stdout.contains("Target:"));
assert!(stdout.contains("Correlation ID:"));
assert!(stdout.contains("cuenv is an event-driven CLI"));
}
Err(e) => panic!("Failed to run cuenv version: {e}"),
}
}
#[test]
fn test_version_command_with_level_debug() {
let result = run_cuenv_command(&["--level", "debug", "version"]);
match result {
Ok((stdout, stderr, success)) => {
assert!(success, "Command should succeed");
assert!(stdout.contains("cuenv"));
assert!(stdout.contains(EXPECTED_VERSION));
assert!(stderr.contains("DEBUG") || stderr.contains("debug"));
}
Err(e) => panic!("Failed to run cuenv version with debug level: {e}"),
}
}
#[test]
fn test_version_command_with_level_error() {
let result = run_cuenv_command(&["--level", "error", "version"]);
match result {
Ok((stdout, _stderr, success)) => {
assert!(success, "Command should succeed");
assert!(stdout.contains("cuenv"));
assert!(stdout.contains(EXPECTED_VERSION));
}
Err(e) => panic!("Failed to run cuenv version with error level: {e}"),
}
}
#[test]
fn test_version_command_with_json_flag() {
let result = run_cuenv_command(&["--json", "--level", "info", "version"]);
match result {
Ok((stdout, stderr, success)) => {
assert!(success, "Command should succeed");
assert!(stdout.contains("cuenv"));
let _ = stderr; }
Err(e) => panic!("Failed to run cuenv version with JSON flag: {e}"),
}
}
#[test]
fn test_version_command_short_level_flag() {
let result = run_cuenv_command(&["-L", "warn", "version"]);
match result {
Ok((stdout, _stderr, success)) => {
assert!(success, "Command should succeed");
assert!(stdout.contains("cuenv"));
assert!(stdout.contains(EXPECTED_VERSION));
}
Err(e) => panic!("Failed to run cuenv version with short level flag: {e}"),
}
}
#[test]
fn test_help_flag() {
let result = run_cuenv_command(&["--help"]);
match result {
Ok((stdout, _stderr, _success)) => {
assert!(stdout.contains("cuenv") || stdout.contains("Usage"));
assert!(stdout.contains("--level") || stdout.contains("-L"));
assert!(stdout.contains("--json"));
assert!(stdout.contains("version"));
}
Err(e) => panic!("Failed to run cuenv --help: {e}"),
}
}
#[test]
fn test_version_help() {
let result = run_cuenv_command(&["version", "--help"]);
match result {
Ok((stdout, _stderr, _success)) => {
assert!(stdout.contains("version") || stdout.contains("Show version information"));
}
Err(e) => panic!("Failed to run cuenv version --help: {e}"),
}
}
#[test]
fn test_invalid_log_level() {
let result = run_cuenv_command(&["--level", "invalid", "version"]);
match result {
Ok((_stdout, stderr, success)) => {
assert!(!success, "Command should fail with invalid log level");
assert!(stderr.contains("error") || stderr.contains("invalid"));
}
Err(e) => panic!("Failed to run cuenv with invalid level: {e}"),
}
}
#[test]
fn test_missing_subcommand() {
let result = run_cuenv_command(&[]);
match result {
Ok((_stdout, stderr, success)) => {
assert!(!success, "Command should fail without subcommand");
assert!(stderr.contains("error") || stderr.contains("required"));
}
Err(e) => panic!("Failed to run cuenv with no args: {e}"),
}
}
#[test]
fn test_combined_flags() {
let result = run_cuenv_command(&["--level", "info", "--json", "version", "--output", "json"]);
match result {
Ok((stdout, _stderr, success)) => {
assert!(success, "Command should succeed with combined flags");
assert!(stdout.contains("cuenv"));
assert!(stdout.contains(EXPECTED_VERSION));
}
Err(e) => panic!("Failed to run cuenv with combined flags: {e}"),
}
}
#[test]
fn test_output_consistency() {
let result1 = run_cuenv_command(&["--level", "error", "version"]);
let result2 = run_cuenv_command(&["--level", "error", "version"]);
match (result1, result2) {
(Ok((stdout1, _stderr1, success1)), Ok((stdout2, _stderr2, success2))) => {
assert!(success1 && success2, "Both commands should succeed");
let lines1: Vec<&str> = stdout1.lines().collect();
let lines2: Vec<&str> = stdout2.lines().collect();
assert_eq!(
lines1.len(),
lines2.len(),
"Output should have same number of lines"
);
for (line1, line2) in lines1.iter().zip(lines2.iter()) {
if !line1.contains("Correlation ID:") {
assert_eq!(line1, line2, "Non-correlation-ID lines should be identical");
}
}
}
(Err(e1), _) => panic!("First command failed: {e1}"),
(_, Err(e2)) => panic!("Second command failed: {e2}"),
}
}
#[test]
fn test_correlation_id_uniqueness() {
let result1 = run_cuenv_command(&["--level", "error", "version"]);
let result2 = run_cuenv_command(&["--level", "error", "version"]);
match (result1, result2) {
(Ok((stdout1, _, _)), Ok((stdout2, _, _))) => {
let correlation1 = stdout1
.lines()
.find(|line| line.contains("Correlation ID:"))
.and_then(|line| line.split("Correlation ID:").nth(1))
.map(str::trim);
let correlation2 = stdout2
.lines()
.find(|line| line.contains("Correlation ID:"))
.and_then(|line| line.split("Correlation ID:").nth(1))
.map(str::trim);
match (correlation1, correlation2) {
(Some(id1), Some(id2)) => {
assert_ne!(id1, id2, "Correlation IDs should be different between runs");
assert_eq!(id1.len(), 36, "Correlation ID should be UUID length");
assert_eq!(id2.len(), 36, "Correlation ID should be UUID length");
assert!(id1.contains('-'), "Correlation ID should contain hyphens");
assert!(id2.contains('-'), "Correlation ID should contain hyphens");
}
_ => panic!("Could not extract correlation IDs from output"),
}
}
(Err(e1), _) => panic!("First command failed: {e1}"),
(_, Err(e2)) => panic!("Second command failed: {e2}"),
}
}
#[test]
fn test_env_print_command_basic() {
let test_path = get_testexamples_path();
let result = run_cuenv_command(&[
"env",
"print",
"--path",
&test_path,
"--package",
"examples",
]);
match result {
Ok((stdout, stderr, success)) => {
if !success {
println!("stdout: {stdout}");
println!("stderr: {stderr}");
}
assert!(success, "Command should succeed");
assert!(stdout.contains("DATABASE_URL=postgres://localhost/mydb"));
assert!(stdout.contains("DEBUG=true"));
assert!(stdout.contains("PORT=3000"));
assert!(stdout.contains("BASE_URL=https://api.example.com"));
assert!(stdout.contains("API_ENDPOINT=https://api.example.com/v1"));
}
Err(e) => panic!("Failed to run cuenv env print: {e}"),
}
}
#[test]
fn test_env_print_command_json_format() {
let test_path = get_testexamples_path();
let result = run_cuenv_command(&[
"env",
"print",
"--path",
&test_path,
"--package",
"examples",
"--output",
"json",
]);
match result {
Ok((stdout, _stderr, success)) => {
assert!(success, "Command should succeed");
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert_eq!(parsed["DATABASE_URL"], "postgres://localhost/mydb");
assert_eq!(parsed["DEBUG"], "true");
assert_eq!(parsed["PORT"], "3000");
assert_eq!(parsed["BASE_URL"], "https://api.example.com");
assert_eq!(parsed["API_ENDPOINT"], "https://api.example.com/v1");
}
Err(e) => panic!("Failed to run cuenv env print with JSON format: {e}"),
}
}
#[test]
fn test_env_print_command_with_short_path_flag() {
let test_path = get_testexamples_path();
let result = run_cuenv_command(&["env", "print", "-p", &test_path, "--package", "examples"]);
match result {
Ok((stdout, _stderr, success)) => {
assert!(success, "Command should succeed with short path flag");
assert!(stdout.contains("DATABASE_URL="));
assert!(stdout.contains("DEBUG="));
assert!(stdout.contains("PORT="));
}
Err(e) => panic!("Failed to run cuenv env print with short path flag: {e}"),
}
}
#[test]
fn test_env_print_command_invalid_path() {
let result = run_cuenv_command(&[
"env",
"print",
"--path",
"nonexistent/path",
"--package",
"examples",
]);
if let Ok((_stdout, _stderr, success)) = result {
assert!(!success, "Command should fail with invalid path");
} else {
}
}
#[test]
fn test_env_print_command_invalid_package() {
let result = run_cuenv_command(&[
"env",
"print",
"--path",
"examples/env-basic",
"--package",
"nonexistent",
]);
if let Ok((_stdout, _stderr, success)) = result {
assert!(!success, "Command should fail with invalid package");
} else {
}
}
#[test]
fn test_env_print_command_unsupported_format() {
let test_path = get_testexamples_path();
let result = run_cuenv_command(&[
"env",
"print",
"--path",
&test_path,
"--package",
"examples",
"--output",
"yaml",
]);
match result {
Ok((stdout, stderr, success)) => {
assert!(!success, "Command should fail with unsupported format");
let combined_output = format!("{stdout}{stderr}");
assert!(
combined_output.contains("Unsupported format") || combined_output.contains("yaml"),
"Error message should mention unsupported format 'yaml'"
);
}
Err(e) => panic!("Failed to run cuenv env print: {e}"),
}
}
#[test]
fn test_sync_command_dry_run() {
let (_temp_dir, test_path) = create_git_test_env();
let result = run_cuenv_command(&[
"sync",
"--path",
&test_path,
"--package",
"cuenv",
"--dry-run",
]);
match result {
Ok((stdout, stderr, success)) => {
if !success {
println!("stdout: {stdout}");
println!("stderr: {stderr}");
}
assert!(success, "Command should succeed");
assert!(
stdout.contains("[codegen]")
|| stdout.contains("[ci]")
|| stdout.contains("[rules]"),
"Dry run should show provider status sections"
);
}
Err(e) => panic!("Failed to run cuenv sync --dry-run: {e}"),
}
}
#[test]
fn test_sync_command_dry_run_reports_provider_status() {
let (_temp_dir, test_path) = create_git_test_env();
let result = run_cuenv_command(&[
"sync",
"--path",
&test_path,
"--package",
"cuenv",
"--dry-run",
]);
match result {
Ok((stdout, _stderr, success)) => {
assert!(success, "Command should succeed");
assert!(
stdout.contains("No CI providers configured")
|| stdout.contains("No codegen configuration")
|| stdout.contains("No .rules.cue"),
"Dry run should report provider status"
);
}
Err(e) => panic!("Failed to run cuenv sync --dry-run: {e}"),
}
}
#[test]
fn test_sync_command_invalid_path() {
let result = run_cuenv_command(&[
"sync",
"--path",
"nonexistent/path",
"--package",
"examples",
"--dry-run",
]);
if let Ok((_stdout, _stderr, success)) = result {
assert!(!success, "Command should fail with invalid path");
}
}
#[test]
fn test_sync_command_invalid_package() {
let (_temp_dir, test_path) = create_git_test_env();
let result = run_cuenv_command(&[
"sync",
"--path",
&test_path,
"--package",
"nonexistent",
"--dry-run",
]);
if let Ok((_stdout, _stderr, success)) = result {
assert!(!success, "Command should fail with invalid package");
}
}
#[test]
fn test_sync_command_help() {
let result = run_cuenv_command(&["sync", "--help"]);
match result {
Ok((stdout, _stderr, _success)) => {
assert!(
stdout.contains("sync") || stdout.contains("Sync"),
"Help should mention the sync command"
);
assert!(
stdout.contains("--dry-run") || stdout.contains("dry"),
"Help should mention --dry-run option"
);
}
Err(e) => panic!("Failed to run cuenv sync --help: {e}"),
}
}
fn create_monorepo_test_env() -> (tempfile::TempDir, String) {
let temp_dir = tempfile::Builder::new()
.prefix("cuenv_monorepo_test_")
.tempdir()
.expect("Failed to create temp directory");
let temp_path = temp_dir.path();
Command::new("git")
.args(["init"])
.current_dir(temp_path)
.output()
.expect("Failed to init git repo");
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(temp_path)
.output()
.expect("Failed to configure git email");
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(temp_path)
.output()
.expect("Failed to configure git name");
let cue_mod_dir = temp_path.join("cue.mod");
fs::create_dir_all(&cue_mod_dir).expect("Failed to create cue.mod directory");
fs::write(
cue_mod_dir.join("module.cue"),
r#"module: "test.example/monorepo"
language: version: "v0.9.0"
"#,
)
.expect("Failed to write module.cue");
fs::write(
temp_path.join("env.cue"),
r#"package cuenv
import "github.com/cuenv/cuenv/schema"
schema.#Project
name: "root-project"
ci: {
github: {}
pipelines: [
{
name: "default"
trigger: branches: ["main"]
tasks: ["test"]
}
]
}
tasks: {
test: {
command: "echo"
args: ["Running root test"]
inputs: ["env.cue"]
}
}
"#,
)
.expect("Failed to write root env.cue");
let api_dir = temp_path.join("services").join("api");
fs::create_dir_all(&api_dir).expect("Failed to create services/api directory");
fs::write(
api_dir.join("env.cue"),
r#"package cuenv
import "github.com/cuenv/cuenv/schema"
schema.#Project
name: "api-service"
ci: {
github: {}
pipelines: [
{
name: "default"
trigger: branches: ["main"]
tasks: ["build", "test"]
}
]
}
let _t = tasks
tasks: {
build: {
command: "cargo"
args: ["build"]
inputs: ["src/**"]
}
test: {
command: "cargo"
args: ["test"]
dependsOn: [_t.build]
}
}
"#,
)
.expect("Failed to write services/api/env.cue");
let web_dir = temp_path.join("apps").join("web");
fs::create_dir_all(&web_dir).expect("Failed to create apps/web directory");
fs::write(
web_dir.join("env.cue"),
r#"package cuenv
import "github.com/cuenv/cuenv/schema"
schema.#Project
name: "web-app"
ci: {
github: {}
pipelines: [
{
name: "default"
trigger: branches: ["main"]
tasks: ["deploy"]
}
]
}
tasks: {
deploy: {
command: "./deploy.sh"
inputs: ["dist/**"]
}
}
"#,
)
.expect("Failed to write apps/web/env.cue");
let path_str = temp_path.to_string_lossy().to_string();
(temp_dir, path_str)
}
fn run_cuenv_command_in_dir(
args: &[&str],
dir: &str,
) -> Result<(String, String, bool), Box<dyn std::error::Error>> {
let mut cmd = clean_environment_command(CUENV_BIN);
for arg in args {
cmd.arg(arg);
}
cmd.current_dir(dir);
let output = cmd.output()?;
let stdout = str::from_utf8(&output.stdout)?.to_string();
let stderr = str::from_utf8(&output.stderr)?.to_string();
let success = output.status.success();
Ok((stdout, stderr, success))
}
#[test]
fn test_sync_ci_dry_run_shows_workflows() {
let (_temp_dir, temp_path) = create_monorepo_test_env();
let result = run_cuenv_command_in_dir(&["sync", "ci", "--dry-run"], &temp_path);
match result {
Ok((stdout, stderr, success)) => {
let combined = format!("{stdout}{stderr}");
if !success {
println!("stdout: {stdout}");
println!("stderr: {stderr}");
}
assert!(
success || combined.contains("error") || combined.contains("failed"),
"Command should either succeed or report meaningful error"
);
}
Err(e) => panic!("Failed to run cuenv sync ci --dry-run: {e}"),
}
}
#[test]
fn test_sync_ci_help() {
let result = run_cuenv_command(&["sync", "ci", "--help"]);
match result {
Ok((stdout, _stderr, success)) => {
assert!(success, "Help command should succeed");
assert!(
stdout.contains("Sync CI workflow files"),
"Help should describe CI sync functionality"
);
assert!(
stdout.contains("--dry-run"),
"Help should mention --dry-run option"
);
}
Err(e) => panic!("Failed to run cuenv sync ci --help: {e}"),
}
}