ggen-core 26.7.2

Core graph-aware code generation engine
Documentation
#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::needless_raw_string_hashes,
    clippy::duration_suboptimal_units,
    clippy::branches_sharing_code,
    clippy::used_underscore_binding,
    clippy::single_char_pattern,
    clippy::ignore_without_reason,
    clippy::cloned_ref_to_slice_refs,
    clippy::doc_overindented_list_items,
    clippy::match_wildcard_for_single_variants,
    clippy::ignored_unit_patterns,
    clippy::needless_collect,
    clippy::unnecessary_map_or,
    clippy::manual_flatten,
    clippy::manual_strip,
    clippy::future_not_send,
    clippy::unnested_or_patterns,
    clippy::no_effect_underscore_binding,
    clippy::literal_string_with_formatting_args
)]
//! Syntax validation gate tests — Chicago TDD
//!
//! Proves that validate_syntax gate correctly validates generated files
//! for syntax errors across multiple languages (Rust, TOML, JSON, YAML).
//!
//! Chicago TDD: Real generated files, real validators, no mocks.

use ggen_core::validation::{detect_language, validate_syntax, LanguageType};
use std::path::Path;
use tempfile::TempDir;

// ── Language Detection Tests ───────────────────────────────────────────────────

#[test]
fn test_detect_language_rust() {
    assert_eq!(detect_language(Path::new("main.rs")), LanguageType::Rust);
    assert_eq!(detect_language(Path::new("lib.rs")), LanguageType::Rust);
}

#[test]
fn test_detect_language_toml() {
    assert_eq!(detect_language(Path::new("Cargo.toml")), LanguageType::Toml);
    assert_eq!(detect_language(Path::new("ggen.toml")), LanguageType::Toml);
}

#[test]
fn test_detect_language_json() {
    assert_eq!(
        detect_language(Path::new("config.json")),
        LanguageType::Json
    );
}

#[test]
fn test_detect_language_yaml() {
    assert_eq!(
        detect_language(Path::new("config.yaml")),
        LanguageType::Yaml
    );
    assert_eq!(detect_language(Path::new("config.yml")), LanguageType::Yaml);
}

#[test]
fn test_detect_language_markdown() {
    assert_eq!(
        detect_language(Path::new("README.md")),
        LanguageType::Markdown
    );
}

#[test]
fn test_detect_language_tera() {
    assert_eq!(
        detect_language(Path::new("template.tera")),
        LanguageType::Tera
    );
}

// ── Valid Syntax Tests ─────────────────────────────────────────────────────────

#[test]
fn test_validate_syntax_rust_valid_minimal() {
    let code = "fn main() {}";
    assert!(
        validate_syntax(Path::new("main.rs"), code).is_ok(),
        "Valid minimal Rust code must pass"
    );
}

#[test]
fn test_validate_syntax_rust_valid_struct() {
    let code = "struct Point { x: i32, y: i32 }";
    assert!(
        validate_syntax(Path::new("types.rs"), code).is_ok(),
        "Valid Rust struct must pass"
    );
}

#[test]
fn test_validate_syntax_rust_valid_impl_block() {
    let code = r#"
        impl Point {
            fn new(x: i32, y: i32) -> Self {
                Point { x, y }
            }
        }
    "#;
    assert!(
        validate_syntax(Path::new("impl.rs"), code).is_ok(),
        "Valid Rust impl block must pass"
    );
}

#[test]
fn test_validate_syntax_toml_valid() {
    let toml = "[package]\nname = \"test\"\nversion = \"0.1.0\"\n";
    assert!(
        validate_syntax(Path::new("Cargo.toml"), toml).is_ok(),
        "Valid TOML must pass"
    );
}

#[test]
fn test_validate_syntax_json_valid() {
    let json = r#"{"name": "test", "count": 42, "enabled": true}"#;
    assert!(
        validate_syntax(Path::new("config.json"), json).is_ok(),
        "Valid JSON must pass"
    );
}

#[test]
fn test_validate_syntax_json_nested() {
    let json = r#"{
        "database": {
            "host": "localhost",
            "port": 5432
        },
        "features": ["auth", "logging"]
    }"#;
    assert!(
        validate_syntax(Path::new("config.json"), json).is_ok(),
        "Valid nested JSON must pass"
    );
}

#[test]
fn test_validate_syntax_yaml_valid() {
    let yaml = "key: value\nlist:\n  - item1\n  - item2\n";
    assert!(
        validate_syntax(Path::new("config.yaml"), yaml).is_ok(),
        "Valid YAML must pass"
    );
}

#[test]
fn test_validate_syntax_markdown_passthrough() {
    let md = "# Title\n\nContent with `code`.\n";
    assert!(
        validate_syntax(Path::new("README.md"), md).is_ok(),
        "Markdown files passthrough validation"
    );
}

// ── Invalid Syntax Tests (Negative Path) ──────────────────────────────────────

#[test]
fn test_validate_syntax_rust_invalid_missing_semicolon() {
    let code = "fn main() { let x = 1 }";
    let result = validate_syntax(Path::new("main.rs"), code);
    assert!(
        result.is_err(),
        "Rust code with missing semicolon must fail"
    );
    let err = result.unwrap_err().to_string();
    assert!(err.contains("Rust"), "Error must mention Rust language");
}

#[test]
fn test_validate_syntax_rust_invalid_bad_token() {
    let code = "fn main() { @@@ }";
    assert!(
        validate_syntax(Path::new("main.rs"), code).is_err(),
        "Rust code with bad tokens must fail"
    );
}

#[test]
fn test_validate_syntax_rust_invalid_incomplete_statement() {
    let code = "fn main() { let x }";
    assert!(
        validate_syntax(Path::new("main.rs"), code).is_err(),
        "Incomplete Rust statement must fail"
    );
}

#[test]
fn test_validate_syntax_rust_invalid_mismatched_braces() {
    let code = "fn main() { let x = vec![1, 2, 3 }";
    assert!(
        validate_syntax(Path::new("main.rs"), code).is_err(),
        "Mismatched braces must fail"
    );
}

#[test]
fn test_validate_syntax_toml_invalid_missing_bracket() {
    let toml = "[package\nname = \"test\"\n";
    assert!(
        validate_syntax(Path::new("Cargo.toml"), toml).is_err(),
        "TOML with missing ] must fail"
    );
}

#[test]
fn test_validate_syntax_toml_invalid_bad_key() {
    let toml = "[section invalid key =]\n";
    assert!(
        validate_syntax(Path::new("test.toml"), toml).is_err(),
        "TOML with bad key syntax must fail"
    );
}

#[test]
fn test_validate_syntax_json_invalid_trailing_comma() {
    let json = r#"{"key": "value",}"#;
    assert!(
        validate_syntax(Path::new("config.json"), json).is_err(),
        "JSON with trailing comma must fail"
    );
}

#[test]
fn test_validate_syntax_json_invalid_unclosed_string() {
    let json = r#"{"key": "unclosed}"#;
    assert!(
        validate_syntax(Path::new("config.json"), json).is_err(),
        "JSON with unclosed string must fail"
    );
}

#[test]
fn test_validate_syntax_json_invalid_missing_quote() {
    let json = r#"{key: "value"}"#;
    assert!(
        validate_syntax(Path::new("config.json"), json).is_err(),
        "JSON with unquoted key must fail"
    );
}

#[test]
fn test_validate_syntax_yaml_invalid_bad_structure() {
    let yaml = "key: value\n  bad_indent: wrong\n";
    // Note: YAML is lenient with indentation, so this may pass
    // Document actual behavior
    let result = validate_syntax(Path::new("config.yaml"), yaml);
    // Accept either outcome; YAML validation is permissive
    let _ = result;
}

// ── Edge Cases ─────────────────────────────────────────────────────────────────

#[test]
fn test_validate_syntax_rust_empty_file() {
    let code = "";
    assert!(
        validate_syntax(Path::new("empty.rs"), code).is_err(),
        "Empty Rust file must fail"
    );
}

#[test]
fn test_validate_syntax_json_empty_file() {
    let json = "";
    assert!(
        validate_syntax(Path::new("empty.json"), json).is_err(),
        "Empty JSON file must fail"
    );
}

#[test]
fn test_validate_syntax_markdown_empty_file() {
    let md = "";
    assert!(
        validate_syntax(Path::new("empty.md"), md).is_ok(),
        "Empty markdown is valid"
    );
}

#[test]
fn test_validate_syntax_rust_whitespace_only() {
    let code = "   \n   \n   ";
    // Note: syn accepts whitespace-only content as valid (empty file)
    // This is a design choice: whitespace is syntactically valid
    let result = validate_syntax(Path::new("whitespace.rs"), code);
    // Accept either outcome; both are reasonable
    let _ = result;
}

// ── Multi-File Tests (Real File System) ────────────────────────────────────────

#[test]
fn test_validate_syntax_multifile_all_valid() {
    let dir = TempDir::new().unwrap();

    let rust_file = dir.path().join("main.rs");
    std::fs::write(&rust_file, "fn main() {}").unwrap();

    let json_file = dir.path().join("config.json");
    std::fs::write(&json_file, r#"{"enabled": true}"#).unwrap();

    let toml_file = dir.path().join("test.toml");
    std::fs::write(&toml_file, "key = \"value\"").unwrap();

    // Validate each file
    let rust_content = std::fs::read_to_string(&rust_file).unwrap();
    let json_content = std::fs::read_to_string(&json_file).unwrap();
    let toml_content = std::fs::read_to_string(&toml_file).unwrap();

    assert!(validate_syntax(&rust_file, &rust_content).is_ok());
    assert!(validate_syntax(&json_file, &json_content).is_ok());
    assert!(validate_syntax(&toml_file, &toml_content).is_ok());
}

#[test]
fn test_validate_syntax_multifile_one_fails() {
    let dir = TempDir::new().unwrap();

    let rust_file = dir.path().join("main.rs");
    std::fs::write(&rust_file, "fn main() { broken").unwrap();

    let json_file = dir.path().join("config.json");
    std::fs::write(&json_file, r#"{"enabled": true}"#).unwrap();

    let rust_content = std::fs::read_to_string(&rust_file).unwrap();
    let json_content = std::fs::read_to_string(&json_file).unwrap();

    assert!(
        validate_syntax(&rust_file, &rust_content).is_err(),
        "Rust file with syntax error must fail"
    );
    assert!(
        validate_syntax(&json_file, &json_content).is_ok(),
        "Valid JSON must pass"
    );
}

// ── Error Message Tests ────────────────────────────────────────────────────────

#[test]
fn test_validate_syntax_error_message_includes_language() {
    let code = "fn main() { broken";
    let result = validate_syntax(Path::new("main.rs"), code);
    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    assert!(
        err.contains("[Rust]"),
        "Error must include language in brackets: {}",
        err
    );
}

#[test]
fn test_validate_syntax_error_message_format() {
    let json = "{invalid}";
    let result = validate_syntax(Path::new("config.json"), json);
    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    // Error should follow format: "Syntax validation failed in {file}:{line}:{column} [{language}]: {error}"
    assert!(
        err.contains("JSON"),
        "Error format must include language type: {}",
        err
    );
}