ggen-cli-lib 26.5.19

CLI interface for ggen
Documentation
//! End-to-end integration tests for template workflow
//!
//! **Chicago TDD Principles**:
//! - REAL template rendering with Tera engine
//! - REAL file system operations
//! - REAL template validation and linting
//! - REAL state verification
//! - NO mocking of template engine
//!
//! **Critical User Workflows (80/20)**:
//! 1. Create new template
//! 2. List available templates
//! 3. Show template details
//! 4. Lint template for errors
//! 5. Generate file tree from template

use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;

/// Helper to create ggen command
fn ggen() -> Command {
    Command::cargo_bin("ggen").expect("Failed to find ggen binary")
}

/// Helper to create test template
fn create_test_template(temp_dir: &TempDir, name: &str) -> std::path::PathBuf {
    let templates_dir = temp_dir.path().join(".ggen/templates");
    fs::create_dir_all(&templates_dir).expect("Failed to create templates dir");

    let template_dir = templates_dir.join(name);
    fs::create_dir_all(&template_dir).expect("Failed to create template dir");

    // Create template file with Tera syntax
    let template_content = r#"# {{ project_name }}

## Description
{{ description | default(value="A template-generated project") }}

## Features
{% for feature in features %}
- {{ feature }}
{% endfor %}
"#;

    fs::write(template_dir.join("README.md.tera"), template_content)
        .expect("Failed to write template");

    // Create template metadata
    let metadata = r#"
name = "{{ name }}"
version = "1.0.0"
description = "Test template"

[variables]
project_name = { type = "string", prompt = "Project name?" }
description = { type = "string", optional = true }
features = { type = "array", default = [] }
"#;

    fs::write(template_dir.join("template.toml"), metadata).expect("Failed to write metadata");

    template_dir
}

#[test]
fn test_template_new_creates_structure() {
    // Chicago TDD: Verify real file system state after template creation
    let temp_dir = TempDir::new().unwrap();

    ggen()
        .arg("template")
        .arg("new")
        .arg("my-template")
        .current_dir(&temp_dir)
        .assert()
        .success();

    // Verify state: Template directory created
    let template_path = temp_dir.path().join(".ggen/templates/my-template");
    assert!(
        template_path.exists(),
        "Template directory should be created"
    );

    // Verify state: Template metadata file exists
    assert!(
        template_path.join("template.toml").exists()
            || template_path.join("template.yaml").exists(),
        "Template metadata should exist"
    );
}

#[test]
fn test_template_list_empty() {
    // Chicago TDD: Verify state when no templates exist
    let temp_dir = TempDir::new().unwrap();

    ggen()
        .arg("template")
        .arg("list")
        .current_dir(&temp_dir)
        .assert()
        .success()
        .stdout(
            predicate::str::contains("No templates found")
                .or(predicate::str::contains("templates")),
        );
}

#[test]
fn test_template_list_shows_installed() {
    // Chicago TDD: Verify state includes existing templates
    let temp_dir = TempDir::new().unwrap();
    create_test_template(&temp_dir, "test-template");

    ggen()
        .arg("template")
        .arg("list")
        .current_dir(&temp_dir)
        .assert()
        .success()
        .stdout(predicate::str::contains("test-template"));
}

#[test]
fn test_template_list_json_format() {
    // Chicago TDD: Verify JSON state representation
    let temp_dir = TempDir::new().unwrap();
    create_test_template(&temp_dir, "test-template");

    let output = ggen()
        .arg("template")
        .arg("list")
        .arg("--json")
        .current_dir(&temp_dir)
        .output()
        .expect("Failed to execute");

    assert!(output.status.success());

    // Verify valid JSON state
    let stdout = String::from_utf8_lossy(&output.stdout);
    if !stdout.is_empty() {
        serde_json::from_str::<serde_json::Value>(&stdout).expect("Output should be valid JSON");
    }
}

#[test]
fn test_template_show_displays_details() {
    // Chicago TDD: Verify template metadata state is displayed
    let temp_dir = TempDir::new().unwrap();
    create_test_template(&temp_dir, "detailed-template");

    ggen()
        .arg("template")
        .arg("show")
        .arg("detailed-template")
        .current_dir(&temp_dir)
        .assert()
        .success()
        .stdout(predicate::str::contains("detailed-template"))
        .stdout(predicate::str::contains("version").or(predicate::str::contains("Version")));
}

#[test]
fn test_template_show_missing_template() {
    // Chicago TDD: Verify error state for nonexistent template
    let temp_dir = TempDir::new().unwrap();

    ggen()
        .arg("template")
        .arg("show")
        .arg("nonexistent-template")
        .current_dir(&temp_dir)
        .assert()
        .failure()
        .stderr(predicate::str::contains("not found").or(predicate::str::contains("error")));
}

#[test]
fn test_template_lint_valid_template() {
    // Chicago TDD: Verify linting validates template structure
    let temp_dir = TempDir::new().unwrap();
    let template_path = create_test_template(&temp_dir, "valid-template");

    ggen()
        .arg("template")
        .arg("lint")
        .arg(template_path.to_str().unwrap())
        .current_dir(&temp_dir)
        .assert()
        .success();
}

#[test]
fn test_template_lint_invalid_syntax() {
    // Chicago TDD: Verify linting catches syntax errors
    let temp_dir = TempDir::new().unwrap();
    let templates_dir = temp_dir.path().join(".ggen/templates");
    fs::create_dir_all(&templates_dir).unwrap();

    let template_dir = templates_dir.join("invalid-template");
    fs::create_dir_all(&template_dir).unwrap();

    // Create template with invalid Tera syntax
    let invalid_content = r#"{{ unclosed_variable"#;
    fs::write(template_dir.join("invalid.tera"), invalid_content).unwrap();

    // Lint may succeed if it's lenient, or fail if strict
    let _ = ggen()
        .arg("template")
        .arg("lint")
        .arg(template_dir.to_str().unwrap())
        .current_dir(&temp_dir)
        .output();

    // Either way, command should complete (not crash)
}

#[test]
fn test_template_generate_tree_basic() {
    // Chicago TDD: Verify file tree generation from template
    let temp_dir = TempDir::new().unwrap();
    create_test_template(&temp_dir, "tree-template");

    // Create data file
    let data_content = r#"
project_name: "MyProject"
description: "A test project"
features:
  - "Fast"
  - "Reliable"
"#;
    fs::write(temp_dir.path().join("data.yaml"), data_content).unwrap();

    ggen()
        .arg("template")
        .arg("generate_tree")
        .arg("tree-template")
        .arg("--data")
        .arg(temp_dir.path().join("data.yaml").to_str().unwrap())
        .arg("--output")
        .arg(temp_dir.path().to_str().unwrap())
        .current_dir(&temp_dir)
        .assert()
        .success();

    // Verify state: Generated file exists
    let output_file = temp_dir.path().join("README.md");
    assert!(output_file.exists(), "Generated file should exist");

    // Verify state: Template variables were rendered
    let content = fs::read_to_string(&output_file).unwrap();
    assert!(
        content.contains("MyProject"),
        "Template should render project name"
    );
    assert!(
        content.contains("A test project"),
        "Template should render description"
    );
}

#[test]
fn test_template_regenerate_basic() {
    // Chicago TDD: Verify delta-driven regeneration
    let temp_dir = TempDir::new().unwrap();
    create_test_template(&temp_dir, "regen-template");

    // Create initial file
    let initial_content = r#"# Project
<!-- ggen:start -->
Generated content
<!-- ggen:end -->

User content
"#;
    fs::write(temp_dir.path().join("output.md"), initial_content).unwrap();

    ggen()
        .arg("template")
        .arg("regenerate")
        .arg("regen-template")
        .current_dir(&temp_dir)
        .assert()
        .success();

    // Verify state: File still exists
    assert!(temp_dir.path().join("output.md").exists());
}

#[test]
fn test_template_help_output() {
    // Chicago TDD: Verify help state is comprehensive
    ggen()
        .arg("template")
        .arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("Template management"))
        .stdout(predicate::str::contains("new"))
        .stdout(predicate::str::contains("list"))
        .stdout(predicate::str::contains("show"))
        .stdout(predicate::str::contains("lint"));
}

#[test]
fn test_template_new_help() {
    // Chicago TDD: Verify verb-specific help
    ggen()
        .arg("template")
        .arg("new")
        .arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("Create a new template"));
}

#[test]
fn test_template_invalid_verb() {
    // Chicago TDD: Verify error handling for invalid verbs
    ggen()
        .arg("template")
        .arg("invalid-verb")
        .assert()
        .failure()
        .stderr(predicate::str::contains("error").or(predicate::str::contains("invalid")));
}

#[test]
fn test_template_new_with_description() {
    // Chicago TDD: Verify template creation with metadata
    let temp_dir = TempDir::new().unwrap();

    ggen()
        .arg("template")
        .arg("new")
        .arg("described-template")
        .arg("--description")
        .arg("A template with description")
        .current_dir(&temp_dir)
        .assert()
        .success();

    // Verify state: Metadata includes description
    let template_path = temp_dir.path().join(".ggen/templates/described-template");
    assert!(template_path.exists());
}

#[test]
fn test_template_generate_tree_missing_template() {
    // Chicago TDD: Verify error state for missing template
    let temp_dir = TempDir::new().unwrap();

    ggen()
        .arg("template")
        .arg("generate_tree")
        .arg("nonexistent-template")
        .current_dir(&temp_dir)
        .assert()
        .failure()
        .stderr(predicate::str::contains("not found").or(predicate::str::contains("error")));
}

#[test]
fn test_template_performance_list() {
    // Chicago TDD: Verify performance for listing templates
    let temp_dir = TempDir::new().unwrap();

    // Create multiple templates
    for i in 0..10 {
        create_test_template(&temp_dir, &format!("template-{}", i));
    }

    let start = std::time::Instant::now();

    ggen()
        .arg("template")
        .arg("list")
        .current_dir(&temp_dir)
        .assert()
        .success();

    let duration = start.elapsed();

    // Should complete quickly even with multiple templates
    assert!(
        duration.as_secs() < 5,
        "Listing templates should be fast: {:?}",
        duration
    );
}