use crate::cli::args;
use crate::models::template::{
ParameterSpec, ParameterType, TemplateCategory, TemplateResource, Toolchain,
};
use crate::stateless_server::StatelessTemplateServer;
use semver::Version;
use serde_json::{json, Value};
use std::sync::Arc;
use tempfile::TempDir;
use tokio::fs;
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod cli_args_tests {
use super::*;
#[test]
fn test_validate_params_all_valid() {
let specs = vec![
ParameterSpec {
name: "project_name".to_string(),
param_type: ParameterType::String,
required: true,
default_value: None,
description: "Project name".to_string(),
validation_pattern: None,
},
ParameterSpec {
name: "has_tests".to_string(),
param_type: ParameterType::Boolean,
required: false,
default_value: Some("true".to_string()),
description: "Include tests".to_string(),
validation_pattern: None,
},
];
let mut params = serde_json::Map::new();
params.insert("project_name".to_string(), json!("my-project"));
params.insert("has_tests".to_string(), json!(false));
let result = args::validate_params(&specs, ¶ms);
assert!(result.is_ok());
}
#[test]
fn test_validate_params_missing_required() {
let specs = vec![ParameterSpec {
name: "project_name".to_string(),
param_type: ParameterType::String,
required: true,
default_value: None,
description: "Project name".to_string(),
validation_pattern: None,
}];
let params = serde_json::Map::new();
let result = args::validate_params(&specs, ¶ms);
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 1);
assert!(errors[0].contains("Missing required parameter: project_name"));
}
#[test]
fn test_validate_params_unknown_parameter() {
let specs = vec![ParameterSpec {
name: "project_name".to_string(),
param_type: ParameterType::String,
required: true,
default_value: None,
description: "Project name".to_string(),
validation_pattern: None,
}];
let mut params = serde_json::Map::new();
params.insert("project_name".to_string(), json!("my-project"));
params.insert("unknown_param".to_string(), json!("value"));
let result = args::validate_params(&specs, ¶ms);
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors
.iter()
.any(|e| e.contains("Unknown parameter: unknown_param")));
}
#[test]
fn test_validate_params_type_validation() {
let specs = vec![ParameterSpec {
name: "has_tests".to_string(),
param_type: ParameterType::Boolean,
required: true,
default_value: None,
description: "Include tests".to_string(),
validation_pattern: None,
}];
let mut params = serde_json::Map::new();
params.insert("has_tests".to_string(), json!(true));
assert!(args::validate_params(&specs, ¶ms).is_ok());
let mut params = serde_json::Map::new();
params.insert("has_tests".to_string(), json!("true"));
assert!(args::validate_params(&specs, ¶ms).is_ok());
let mut params = serde_json::Map::new();
params.insert("has_tests".to_string(), json!(123));
let result = args::validate_params(&specs, ¶ms);
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors[0].contains("Invalid type"));
}
#[test]
fn test_expand_env_vars() {
std::env::set_var("TEST_USER", "alice");
std::env::set_var("TEST_HOME", "/home/alice");
let template = "Hello ${TEST_USER}, your home is ${TEST_HOME}";
let expanded = args::expand_env_vars(template);
assert_eq!(expanded, "Hello alice, your home is /home/alice");
let template = "Missing: ${NONEXISTENT_VAR}";
let expanded = args::expand_env_vars(template);
assert_eq!(expanded, "Missing: ${NONEXISTENT_VAR}");
std::env::remove_var("TEST_USER");
std::env::remove_var("TEST_HOME");
}
#[test]
fn test_expand_env_vars_no_vars() {
let template = "No variables here";
let expanded = args::expand_env_vars(template);
assert_eq!(expanded, "No variables here");
}
#[test]
fn test_expand_env_vars_multiple_occurrences() {
std::env::set_var("TEST_VAR", "value");
let template = "${TEST_VAR} and ${TEST_VAR} again";
let expanded = args::expand_env_vars(template);
assert_eq!(expanded, "value and value again");
std::env::remove_var("TEST_VAR");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod cli_integration_tests {
use super::*;
async fn create_test_server() -> Arc<StatelessTemplateServer> {
Arc::new(StatelessTemplateServer::new().unwrap())
}
#[tokio::test]
async fn test_generate_command_to_stdout() {
let _server = create_test_server().await;
let temp_dir = TempDir::new().unwrap();
let _output_file = temp_dir.path().join("output.txt");
}
#[tokio::test]
async fn test_generate_command_to_file() {
let _server = create_test_server().await;
let temp_dir = TempDir::new().unwrap();
let output_file = temp_dir.path().join("Makefile");
let _args = [
"pmat",
"generate",
"makefile",
"rust/cli",
"-p",
"project_name=test-project",
"-p",
"has_tests=true",
"-o",
output_file.to_str().unwrap(),
];
}
#[tokio::test]
async fn test_list_command_json_format() {
let _server = create_test_server().await;
}
#[tokio::test]
async fn test_search_command() {
let _server = create_test_server().await;
}
#[tokio::test]
async fn test_validate_command() {
let _server = create_test_server().await;
}
#[tokio::test]
async fn test_scaffold_command() {
let _server = create_test_server().await;
let _temp_dir = TempDir::new().unwrap();
}
#[tokio::test]
async fn test_context_command() {
let _server = create_test_server().await;
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("test-project");
fs::create_dir_all(&project_dir).await.unwrap();
fs::write(
project_dir.join("Cargo.toml"),
r#"[package]
name = "test-project"
version = "0.1.0"
"#,
)
.await
.unwrap();
fs::write(
project_dir.join("main.rs"),
r#"fn main() {
println!("Hello, world!");
}
"#,
)
.await
.unwrap();
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod cli_parsing_tests {
use super::*;
#[test]
fn test_parse_key_val() {
let input = "name=value";
let parts: Vec<&str> = input.splitn(2, '=').collect();
assert_eq!(parts.len(), 2);
assert_eq!(parts[0], "name");
assert_eq!(parts[1], "value");
let input = "url=https://example.com?foo=bar";
let parts: Vec<&str> = input.splitn(2, '=').collect();
assert_eq!(parts.len(), 2);
assert_eq!(parts[0], "url");
assert_eq!(parts[1], "https://example.com?foo=bar");
let input = "invalid";
let parts: Vec<&str> = input.splitn(2, '=').collect();
assert_eq!(parts.len(), 1);
}
#[test]
fn test_value_type_inference() {
assert_eq!(json!("true"), Value::String("true".to_string()));
assert_eq!(json!(true), Value::Bool(true));
assert_eq!(json!("123"), Value::String("123".to_string()));
assert_eq!(json!(123), Value::Number(123.into()));
assert_eq!(json!("123.45"), Value::String("123.45".to_string()));
assert_eq!(
json!(123.45),
Value::Number(serde_json::Number::from_f64(123.45).unwrap())
);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod cli_output_tests {
use super::*;
#[test]
fn test_table_formatting() {
let templates = [
Arc::new(TemplateResource {
uri: "template://makefile/rust/cli".to_string(),
category: TemplateCategory::Makefile,
toolchain: Toolchain::RustCli {
cargo_features: vec![],
},
name: "Rust CLI Binary Makefile".to_string(),
description: "Makefile for Rust CLI applications".to_string(),
parameters: vec![ParameterSpec {
name: "project_name".to_string(),
param_type: ParameterType::String,
required: true,
default_value: None,
description: "Project name".to_string(),
validation_pattern: None,
}],
content_hash: "hash123".to_string(),
semantic_version: Version::parse("1.0.0").unwrap(),
dependency_graph: vec![],
s3_object_key: "templates/makefile/rust/cli.hbs".to_string(),
}),
Arc::new(TemplateResource {
uri: "template://readme/deno/cli".to_string(),
category: TemplateCategory::Readme,
toolchain: Toolchain::DenoTypescript {
deno_version: "1.40.0".to_string(),
},
name: "Deno CLI README".to_string(),
description: "README for Deno CLI applications".to_string(),
parameters: vec![],
content_hash: "hash456".to_string(),
semantic_version: Version::parse("1.0.0").unwrap(),
dependency_graph: vec![],
s3_object_key: "templates/readme/deno/cli.hbs".to_string(),
}),
];
let expected_width = templates.iter().map(|t| t.uri.len()).max().unwrap_or(20);
assert_eq!(
expected_width,
28 );
}
#[test]
fn test_json_output_format() {
let template = TemplateResource {
uri: "template://makefile/rust/cli".to_string(),
category: TemplateCategory::Makefile,
toolchain: Toolchain::RustCli {
cargo_features: vec![],
},
name: "Test Template".to_string(),
description: "Test description".to_string(),
parameters: vec![],
content_hash: "hash789".to_string(),
semantic_version: Version::parse("1.0.0").unwrap(),
dependency_graph: vec![],
s3_object_key: "test.hbs".to_string(),
};
let json = serde_json::to_string_pretty(&template).unwrap();
assert!(json.contains("\"uri\": \"template://makefile/rust/cli\""));
assert!(json.contains("\"category\": \"makefile\""));
assert!(json.contains("\"type\": \"rust\""));
}
#[test]
fn test_yaml_output_format() {
let template = TemplateResource {
uri: "template://makefile/rust/cli".to_string(),
category: TemplateCategory::Makefile,
toolchain: Toolchain::RustCli {
cargo_features: vec![],
},
name: "Test Template".to_string(),
description: "Test description".to_string(),
parameters: vec![],
content_hash: "hash789".to_string(),
semantic_version: Version::parse("1.0.0").unwrap(),
dependency_graph: vec![],
s3_object_key: "test.hbs".to_string(),
};
let yaml = serde_yaml_ng::to_string(&template).unwrap();
assert!(yaml.contains("uri: template://makefile/rust/cli"));
assert!(yaml.contains("category: makefile"));
assert!(yaml.contains("type: rust"));
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod cli_error_handling_tests {
use super::*;
#[test]
fn test_missing_required_params_error() {
let specs = vec![ParameterSpec {
name: "required_param".to_string(),
param_type: ParameterType::String,
required: true,
default_value: None,
description: "Required parameter".to_string(),
validation_pattern: None,
}];
let params = serde_json::Map::new();
let result = args::validate_params(&specs, ¶ms);
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 1);
assert!(errors[0].contains("required_param"));
}
#[test]
fn test_multiple_validation_errors() {
let specs = vec![
ParameterSpec {
name: "param1".to_string(),
param_type: ParameterType::String,
required: true,
default_value: None,
description: "First param".to_string(),
validation_pattern: None,
},
ParameterSpec {
name: "param2".to_string(),
param_type: ParameterType::Boolean,
required: true,
default_value: None,
description: "Second param".to_string(),
validation_pattern: None,
},
];
let mut params = serde_json::Map::new();
params.insert("param2".to_string(), json!(123));
params.insert("unknown".to_string(), json!("value"));
let result = args::validate_params(&specs, ¶ms);
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors.len() >= 3);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod cli_mode_detection_tests {
#[test]
fn test_cli_parameter_parsing() {
let test_cases = [
("key=value", Some(("key", "value"))),
("key=", Some(("key", ""))),
("invalid", None),
("key=value=with=equals", Some(("key", "value=with=equals"))),
];
for (input, expected) in test_cases {
let parts: Vec<&str> = input.splitn(2, '=').collect();
match expected {
Some((key, val)) => {
assert_eq!(parts.len(), 2);
assert_eq!(parts[0], key);
assert_eq!(parts[1], val);
}
None => {
assert_eq!(parts.len(), 1);
}
}
}
}
#[test]
fn test_template_uri_patterns() {
let valid_uris = [
"template://makefile/rust/cli",
"template://readme/deno/cli",
"template://gitignore/python-uv/cli",
];
for uri in valid_uris {
assert!(uri.starts_with("template://"));
let parts: Vec<&str> = uri
.strip_prefix("template://")
.unwrap()
.split('/')
.collect();
assert_eq!(parts.len(), 3);
}
}
#[test]
fn test_toolchain_names() {
let toolchains = ["rust", "deno", "python-uv"];
for toolchain in toolchains {
assert!(!toolchain.is_empty());
assert!(toolchain
.chars()
.all(|c| c.is_ascii_lowercase() || c == '-'));
}
}
}