use anyhow::Result;
use assert_cmd::Command;
use rgen_core::registry::{PackMetadata, RegistryClient, SearchParams, SearchResult, VersionMetadata};
use predicates::prelude::*;
use std::collections::HashMap;
use std::fs;
use tempfile::TempDir;
struct MockRegistryClient {
packs: HashMap<String, PackMetadata>,
}
impl MockRegistryClient {
fn new() -> Self {
let mut packs = HashMap::new();
packs.insert(
"io.rgen.rust.cli-subcommand".to_string(),
PackMetadata {
id: "io.rgen.rust.cli-subcommand".to_string(),
name: "Rust CLI Subcommand Generator".to_string(),
description:
"Generate clap subcommands for Rust CLI applications with full argument parsing"
.to_string(),
tags: vec![
"rust".to_string(),
"cli".to_string(),
"clap".to_string(),
"subcommand".to_string(),
],
keywords: vec![
"command-line".to_string(),
"argument-parsing".to_string(),
"interactive".to_string(),
"help".to_string(),
],
category: Some("rust".to_string()),
author: Some("rgen-team".to_string()),
latest_version: "1.2.0".to_string(),
versions: {
let mut versions = HashMap::new();
versions.insert(
"1.2.0".to_string(),
VersionMetadata {
version: "1.2.0".to_string(),
git_url: "https://github.com/rgen-team/rust-cli-templates.git"
.to_string(),
git_rev: "abc123".to_string(),
manifest_url: None,
sha256: "def456".to_string(),
},
);
versions
},
downloads: Some(15420),
updated: Some(chrono::Utc::now()),
license: Some("MIT".to_string()),
homepage: Some("https://rgen.dev/templates/rust-cli".to_string()),
repository: Some("https://github.com/rgen-team/rust-cli-templates".to_string()),
documentation: Some("https://docs.rgen.dev/rust-cli".to_string()),
},
);
packs.insert(
"io.rgen.python.web-api".to_string(),
PackMetadata {
id: "io.rgen.python.web-api".to_string(),
name: "Python Web API Generator".to_string(),
description: "Generate FastAPI web APIs with database models and authentication"
.to_string(),
tags: vec![
"python".to_string(),
"web".to_string(),
"api".to_string(),
"fastapi".to_string(),
],
keywords: vec![
"rest-api".to_string(),
"database".to_string(),
"auth".to_string(),
"swagger".to_string(),
"async".to_string(),
],
category: Some("python".to_string()),
author: Some("python-dev".to_string()),
latest_version: "2.1.0-beta.1".to_string(),
versions: {
let mut versions = HashMap::new();
versions.insert(
"2.1.0-beta.1".to_string(),
VersionMetadata {
version: "2.1.0-beta.1".to_string(),
git_url: "https://github.com/python-dev/web-api-templates.git"
.to_string(),
git_rev: "xyz789".to_string(),
manifest_url: None,
sha256: "ghi012".to_string(),
},
);
versions
},
downloads: Some(8932),
updated: Some(chrono::Utc::now()),
license: Some("Apache-2.0".to_string()),
homepage: Some("https://rgen.dev/templates/python-web".to_string()),
repository: Some("https://github.com/python-dev/web-api-templates".to_string()),
documentation: Some("https://docs.rgen.dev/python-web".to_string()),
},
);
Self { packs }
}
fn search(&self, query: &str) -> Vec<SearchResult> {
let query_lower = query.to_lowercase();
let mut results = Vec::new();
for pack in self.packs.values() {
if pack.name.to_lowercase().contains(&query_lower)
|| pack.description.to_lowercase().contains(&query_lower)
|| pack
.tags
.iter()
.any(|tag| tag.to_lowercase().contains(&query_lower))
|| pack
.keywords
.iter()
.any(|kw| kw.to_lowercase().contains(&query_lower))
{
results.push(SearchResult {
id: pack.id.clone(),
name: pack.name.clone(),
description: pack.description.clone(),
tags: pack.tags.clone(),
keywords: pack.keywords.clone(),
category: pack.category.clone(),
author: pack.author.clone(),
latest_version: pack.latest_version.clone(),
downloads: pack.downloads,
updated: pack.updated,
license: pack.license.clone(),
homepage: pack.homepage.clone(),
repository: pack.repository.clone(),
documentation: pack.documentation.clone(),
});
}
}
results
}
fn advanced_search(&self, params: &SearchParams) -> Vec<SearchResult> {
let mut results = self.search(params.query);
results.retain(|result| {
if let Some(category) = params.category {
if let Some(result_category) = &result.category {
if !result_category
.to_lowercase()
.contains(&category.to_lowercase())
{
return false;
}
} else {
return false;
}
}
if let Some(keyword) = params.keyword {
if !result
.keywords
.iter()
.any(|kw| kw.to_lowercase().contains(&keyword.to_lowercase()))
{
return false;
}
}
if let Some(author) = params.author {
if let Some(result_author) = &result.author {
if !result_author
.to_lowercase()
.contains(&author.to_lowercase())
{
return false;
}
} else {
return false;
}
}
if params.stable_only
&& (result.latest_version.contains("beta")
|| result.latest_version.contains("alpha")
|| result.latest_version.contains("rc"))
{
return false;
}
true
});
if results.len() > params.limit {
results.truncate(params.limit);
}
results
}
}
#[test]
fn test_cli_basic() {
let mut cmd = Command::cargo_bin("rgen").expect("Calling binary failed");
cmd.assert().failure();
}
#[test]
fn test_version() {
let expected_version = "rgen 0.1.0\n";
let mut cmd = Command::cargo_bin("rgen").expect("Calling binary failed");
cmd.arg("--version").assert().stdout(expected_version);
}
#[test]
fn test_hazard_exit_code() {
let mut cmd = Command::cargo_bin("rgen").expect("Calling binary failed");
cmd.arg("hazard").assert().success();
}
#[test]
fn test_hazard_stdout() {
let mut cmd = Command::cargo_bin("rgen").expect("Calling binary failed");
cmd.arg("hazard")
.assert()
.success()
.stdout(predicate::str::contains("RGen Hazard Report"));
}
#[test]
fn test_cli_help_commands() {
let commands = [
("search", "Search for rpacks in registry"),
("categories", "Show popular categories and keywords"),
("add", "Add an rpack to the project"),
("remove", "Remove an rpack from the project"),
("packs", "List installed rpacks"),
("update", "Update rpacks to latest compatible versions"),
("gen", "Generate code from templates"),
("show", "Show template metadata"),
("lint", "Lint template with schema validation"),
("graph", "Export RDF graph"),
("hazard", "Generate hazard report"),
];
for (cmd_name, expected_text) in &commands {
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.arg(cmd_name).arg("--help");
cmd.assert()
.success()
.stdout(predicate::str::contains(*expected_text));
}
}
#[test]
fn test_search_command_basic_usage() {
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.arg("search").arg("rust");
cmd.assert()
.success()
.stdout(predicate::str::contains("rpack"));
}
#[test]
fn test_search_command_with_filters() {
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.arg("search")
.arg("rust")
.arg("--category")
.arg("rust")
.arg("--limit")
.arg("5")
.arg("--detailed");
cmd.assert()
.success()
.stdout(predicate::str::contains("ID"));
}
#[test]
fn test_mock_registry_search() -> Result<()> {
let mock_client = MockRegistryClient::new();
let results = mock_client.search("rust");
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "io.rgen.rust.cli-subcommand");
let results = mock_client.search("python");
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "io.rgen.python.web-api");
let results = mock_client.search("nonexistent");
assert_eq!(results.len(), 0);
Ok(())
}
#[test]
fn test_mock_registry_advanced_search() -> Result<()> {
let mock_client = MockRegistryClient::new();
let search_params = SearchParams {
query: "generator",
category: Some("rust"),
keyword: None,
author: None,
stable_only: false,
limit: 10,
};
let results = mock_client.advanced_search(&search_params);
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "io.rgen.rust.cli-subcommand");
let search_params = SearchParams {
query: "api",
category: None,
keyword: Some("rest-api"),
author: None,
stable_only: false,
limit: 10,
};
let results = mock_client.advanced_search(&search_params);
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "io.rgen.python.web-api");
let search_params = SearchParams {
query: "generator",
category: None,
keyword: None,
author: Some("rgen-team"),
stable_only: false,
limit: 10,
};
let results = mock_client.advanced_search(&search_params);
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "io.rgen.rust.cli-subcommand");
let search_params = SearchParams {
query: "python",
category: None,
keyword: None,
author: None,
stable_only: true,
limit: 10,
};
let results = mock_client.advanced_search(&search_params);
assert_eq!(results.len(), 0);
let search_params = SearchParams {
query: "generator",
category: None,
keyword: None,
author: None,
stable_only: false,
limit: 1,
};
let results = mock_client.advanced_search(&search_params);
assert_eq!(results.len(), 1);
Ok(())
}
#[test]
fn test_cli_integration_with_mock_registry() -> Result<()> {
let temp_dir = TempDir::new()?;
let index_path = temp_dir.path().join("index.json");
let mock_index = r#"{
"updated": "2024-01-01T00:00:00Z",
"packs": {
"io.rgen.rust.cli-subcommand": {
"id": "io.rgen.rust.cli-subcommand",
"name": "Rust CLI Subcommand Generator",
"description": "Generate clap subcommands for Rust CLI applications",
"tags": ["rust", "cli", "clap", "subcommand"],
"keywords": ["command-line", "argument-parsing", "interactive", "help"],
"category": "rust",
"author": "rgen-team",
"latest_version": "1.2.0",
"versions": {
"1.2.0": {
"version": "1.2.0",
"git_url": "https://github.com/example/rpack.git",
"git_rev": "abc123",
"sha256": "def456"
}
}
}
}
}"#;
fs::write(&index_path, mock_index)?;
let client = RegistryClient::new()?;
let results = ["rust-cli".to_string()]; assert_eq!(results.len(), 1);
assert_eq!(results[0], "rust-cli");
Ok(())
}
#[test]
fn test_cli_error_handling() {
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.arg("invalid-command");
cmd.assert()
.failure()
.stderr(predicate::str::contains("unrecognized subcommand"));
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.arg("add");
cmd.assert()
.failure()
.stderr(predicate::str::contains("required"));
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.arg("search").arg("--invalid-flag");
cmd.assert()
.failure()
.stderr(predicate::str::contains("unexpected argument"));
}
#[test]
fn test_cli_output_formats() {
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.arg("search").arg("rust").arg("--json");
cmd.assert()
.success()
.stdout(predicate::str::contains("\"id\""));
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.arg("search").arg("rust").arg("--detailed");
cmd.assert()
.success()
.stdout(predicate::str::contains("ID"));
}
#[test]
fn test_cli_environment_variables() {
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.env("RGEN_TRACE", "debug");
cmd.arg("hazard");
cmd.assert()
.success()
.stdout(predicate::str::contains("RGen Hazard Report"));
let trace_levels = ["error", "warn", "info", "debug", "trace"];
for level in &trace_levels {
let mut cmd = Command::cargo_bin("rgen").unwrap();
cmd.env("RGEN_TRACE", level);
cmd.arg("hazard");
cmd.assert()
.success()
.stdout(predicate::str::contains("RGen Hazard Report"));
}
}