use std::fs;
use tempfile::TempDir;
use crate::common::{setup_test_project, setup_project_with_config, run_rsconstruct_with_env, run_rsconstruct_json_with_env};
#[test]
fn processors_list_shows_declared() {
let temp_dir = setup_test_project();
let project_path = temp_dir.path();
let output = run_rsconstruct_with_env(project_path, &["processors", "list"], &[("NO_COLOR", "1")]);
assert!(output.status.success(), "processors list failed: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("tera"), "Expected tera processor in list");
}
#[test]
fn processors_files_shows_products() {
let temp_dir = setup_test_project();
let project_path = temp_dir.path();
fs::write(
project_path.join("config/test.py"),
"value = 42"
).expect("Failed to write config");
fs::write(
project_path.join("tera.templates/output.txt.tera"),
"{% set c = load_python(path='config/test.py') %}{{ c.value }}"
).expect("Failed to write template");
let output = run_rsconstruct_with_env(project_path, &["processors", "files", "--headers"], &[("NO_COLOR", "1")]);
assert!(output.status.success(), "processors files failed: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("[tera]"), "Expected [tera] header in output");
assert!(stdout.contains("output.txt"), "Expected output file in listing");
}
#[test]
fn processors_files_no_files_message() {
let temp_dir = setup_test_project();
let project_path = temp_dir.path();
let output = run_rsconstruct_with_env(project_path, &["processors", "files"], &[("NO_COLOR", "1")]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("No files discovered") || stdout.contains("(no files)"),
"Expected empty message, got: {}", stdout);
}
#[test]
fn processors_files_unknown_processor_fails() {
let temp_dir = setup_test_project();
let project_path = temp_dir.path();
let output = run_rsconstruct_with_env(project_path, &["processors", "files", "nonexistent"], &[("NO_COLOR", "1")]);
assert!(!output.status.success(), "Expected failure for unknown processor");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("Unknown processor"), "Expected 'Unknown processor' error, got: {}", stderr);
}
#[test]
fn processors_list_shows_descriptions() {
let temp_dir = setup_test_project();
let project_path = temp_dir.path();
let output = run_rsconstruct_with_env(project_path, &["processors", "list"], &[("NO_COLOR", "1")]);
assert!(output.status.success(), "processors list failed: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("tera"), "Expected tera processor");
}
#[test]
fn processors_files_json_output() {
let temp_dir = setup_test_project();
let project_path = temp_dir.path();
fs::write(
project_path.join("config/test.py"),
"value = 42"
).expect("Failed to write config");
fs::write(
project_path.join("tera.templates/output.txt.tera"),
"{% set c = load_python(path='config/test.py') %}{{ c.value }}"
).expect("Failed to write template");
let output = run_rsconstruct_with_env(project_path, &["--json", "processors", "files"], &[("NO_COLOR", "1")]);
assert!(output.status.success(), "processors files --json failed: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
let entries: Vec<serde_json::Value> = serde_json::from_str(&stdout)
.expect("Expected valid JSON array");
assert!(!entries.is_empty(), "Expected at least one entry");
let entry = &entries[0];
assert!(entry.get("processor").is_some(), "Entry should have 'processor' field");
assert!(entry.get("processor_type").is_some(), "Entry should have 'processor_type' field");
assert!(entry.get("inputs").is_some(), "Entry should have 'inputs' field");
assert!(entry.get("outputs").is_some(), "Entry should have 'outputs' field");
assert_eq!(entry["processor"], "tera");
assert_eq!(entry["processor_type"], "generator");
}
#[test]
fn processors_files_json_empty() {
let temp_dir = setup_test_project();
let project_path = temp_dir.path();
let output = run_rsconstruct_with_env(project_path, &["--json", "processors", "files"], &[("NO_COLOR", "1")]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let entries: Vec<serde_json::Value> = serde_json::from_str(&stdout)
.expect("Expected valid JSON array");
assert!(entries.is_empty(), "Expected empty JSON array, got: {}", stdout);
}
#[test]
fn processors_list_works_without_config() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let output = run_rsconstruct_with_env(temp_dir.path(), &["processors", "list"], &[("NO_COLOR", "1")]);
assert!(output.status.success(), "processors list should work without rsconstruct.toml: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("tera"), "Expected tera processor in output");
assert!(stdout.contains("ruff"), "Expected ruff processor in output");
assert!(stdout.contains("shellcheck"), "Expected shellcheck processor in output");
}
#[test]
fn no_processor_section_means_no_products() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::create_dir_all(project_path.join("tera.templates")).unwrap();
fs::write(project_path.join("tera.templates/quick.txt.tera"), "hello").unwrap();
fs::write(
project_path.join("rsconstruct.toml"),
"\n"
).unwrap();
let result = run_rsconstruct_json_with_env(project_path, &["build"], &[("NO_COLOR", "1")]);
assert!(result.exit_success, "Build should succeed");
assert_eq!(result.total_products, 0, "Expected 0 products when no processor is declared");
}
#[test]
fn per_processor_enabled_true_is_default() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::create_dir_all(project_path.join("tera.templates")).unwrap();
fs::write(project_path.join("tera.templates/quick.txt.tera"), "hello").unwrap();
fs::write(
project_path.join("rsconstruct.toml"),
"[processor.tera]\nsrc_dirs = [\"tera.templates\"]\n"
).unwrap();
let result = run_rsconstruct_json_with_env(project_path, &["build"], &[("NO_COLOR", "1")]);
assert!(result.exit_success, "Build should succeed");
assert_eq!(result.total_products, 1, "Expected 1 product when processor defaults to enabled");
}
#[test]
fn disabled_processor_skips_tool_preflight() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::create_dir_all(project_path.join("src")).unwrap();
fs::write(project_path.join("src/a.txt"), "hello\n").unwrap();
let config = |enabled: &str| format!(
"[processor.script]\ncommand = \"definitely-not-a-real-tool-xyz\"\nsrc_dirs = [\"src\"]\nsrc_extensions = [\".txt\"]\nenabled = {enabled}\n"
);
fs::write(project_path.join("rsconstruct.toml"), config("true")).unwrap();
let result = run_rsconstruct_json_with_env(project_path, &["build"], &[("NO_COLOR", "1")]);
assert!(!result.exit_success, "enabled instance with missing tool must fail");
fs::write(project_path.join("rsconstruct.toml"), config("false")).unwrap();
let result = run_rsconstruct_json_with_env(project_path, &["build"], &[("NO_COLOR", "1")]);
assert!(result.exit_success, "disabled instance must not fail the tool pre-flight");
assert_eq!(result.total_products, 0, "disabled instance must produce no products");
}
#[test]
fn processors_list_json() {
let temp_dir = setup_test_project();
let project_path = temp_dir.path();
let output = run_rsconstruct_with_env(project_path, &["--json", "processors", "list"], &[("NO_COLOR", "1")]);
assert!(output.status.success(), "processors list --json failed: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
let entries: Vec<serde_json::Value> = serde_json::from_str(&stdout)
.expect("Expected valid JSON array");
assert!(!entries.is_empty(), "Expected at least one entry");
for entry in &entries {
assert!(entry.get("name").is_some(), "Entry should have 'name' field");
assert!(entry.get("processor_type").is_some(), "Entry should have 'processor_type' field");
assert!(entry.get("enabled").is_some(), "Entry should have 'enabled' field");
assert!(entry.get("detected").is_some(), "Entry should have 'detected' field");
assert!(entry.get("batch").is_some(), "Entry should have 'batch' field");
assert!(entry.get("description").is_some(), "Entry should have 'description' field");
}
let tera = entries.iter().find(|e| e["name"] == "tera").expect("Expected tera in list");
assert!(tera.get("name").is_some());
}
#[test]
fn processors_list_all_json_without_config() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let output = run_rsconstruct_with_env(temp_dir.path(), &["--json", "processors", "list"], &[("NO_COLOR", "1")]);
assert!(output.status.success(), "processors list --json failed: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
let entries: Vec<serde_json::Value> = serde_json::from_str(&stdout)
.expect("Expected valid JSON array");
assert!(!entries.is_empty(), "Expected at least one entry");
for entry in &entries {
assert!(entry.get("name").is_some(), "Entry should have 'name' field");
assert!(entry.get("processor_type").is_some(), "Entry should have 'processor_type' field");
assert!(entry.get("batch").is_some(), "Entry should have 'batch' field");
assert!(entry.get("description").is_some(), "Entry should have 'description' field");
}
let tera = entries.iter().find(|e| e["name"] == "tera").expect("Expected tera in list");
assert!(tera.get("name").is_some());
}
#[test]
fn removing_processor_section_disables_it() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::create_dir_all(project_path.join("tera.templates")).unwrap();
fs::write(
project_path.join("tera.templates/output.txt.tera"),
"hello",
).unwrap();
fs::write(
project_path.join("rsconstruct.toml"),
"[processor.tera]\nsrc_dirs = [\"tera.templates\"]\n"
).unwrap();
let result = run_rsconstruct_json_with_env(project_path, &["build"], &[("NO_COLOR", "1")]);
assert!(result.exit_success, "Build should succeed");
assert_eq!(result.total_products, 1, "Expected 1 product with tera declared");
fs::write(
project_path.join("rsconstruct.toml"),
"\n"
).unwrap();
let result = run_rsconstruct_json_with_env(project_path, &["build"], &[("NO_COLOR", "1")]);
assert!(result.exit_success, "Build should succeed");
assert_eq!(result.total_products, 0, "Expected 0 products with no processor declared");
}
#[test]
fn remove_no_file_processors_keeps_disabled_stanza() {
let temp_dir = setup_project_with_config(
"[processor.tera]\nsrc_dirs = [\"tera.templates\"]\n\n[processor.shellcheck]\nsrc_dirs = [\"src\"]\nenabled = false\n"
);
let project_path = temp_dir.path();
fs::create_dir_all(project_path.join("tera.templates")).unwrap();
fs::write(project_path.join("tera.templates/output.txt.tera"), "hello").unwrap();
let output = run_rsconstruct_with_env(
project_path,
&["smart", "remove-no-file-processors"],
&[("NO_COLOR", "1")],
);
assert!(output.status.success(), "command failed: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.contains("shellcheck"),
"Disabled processor must not be reported as having no files, got: {stdout}"
);
let toml = fs::read_to_string(project_path.join("rsconstruct.toml")).unwrap();
assert!(
toml.contains("[processor.shellcheck]"),
"Disabled stanza must be preserved, got: {toml}"
);
assert!(toml.contains("enabled = false"), "enabled = false must be preserved, got: {toml}");
}
#[test]
fn remove_no_file_processors_removes_enabled_stanza_with_no_files() {
let temp_dir = setup_project_with_config(
"[processor.tera]\nsrc_dirs = [\"tera.templates\"]\n\n[processor.shellcheck]\nsrc_dirs = [\"src\"]\n"
);
let project_path = temp_dir.path();
fs::create_dir_all(project_path.join("tera.templates")).unwrap();
fs::write(project_path.join("tera.templates/output.txt.tera"), "hello").unwrap();
let output = run_rsconstruct_with_env(
project_path,
&["smart", "remove-no-file-processors"],
&[("NO_COLOR", "1")],
);
assert!(output.status.success(), "command failed: {}", String::from_utf8_lossy(&output.stderr));
let toml = fs::read_to_string(project_path.join("rsconstruct.toml")).unwrap();
assert!(
!toml.contains("[processor.shellcheck]"),
"Enabled stanza with no files must be removed, got: {toml}"
);
assert!(toml.contains("[processor.tera]"), "Processor with files must be kept, got: {toml}");
}