#![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
)]
use ggen_core::codegen::pipeline::GenerationPipeline;
use ggen_core::manifest::{
query_contains_values, GenerationConfig, GenerationMode, GenerationRule, GgenManifest,
InferenceConfig, ManifestValidator, OntologyConfig, ProjectConfig, QuerySource, TemplateSource,
ValidationConfig,
};
use ggen_core::validation::{detect_language, validate_syntax, LanguageType};
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn stub_ontology(dir: &TempDir) -> PathBuf {
let ttl = dir.path().join("stub.ttl");
std::fs::write(
&ttl,
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n",
)
.unwrap();
ttl
}
fn minimal_manifest(ontology_path: PathBuf, rules: Vec<GenerationRule>) -> GgenManifest {
GgenManifest {
project: ProjectConfig {
name: "test".to_string(),
version: "0.1.0".to_string(),
description: None,
..Default::default()
},
ontology: OntologyConfig {
source: ontology_path,
imports: vec![],
base_iri: None,
prefixes: Default::default(),
..Default::default()
},
inference: InferenceConfig::default(),
generation: GenerationConfig {
rules,
output_dir: PathBuf::from("."),
max_sparql_timeout_ms: 5000,
require_audit_trail: false,
determinism_salt: None,
enable_llm: false,
llm_provider: None,
llm_model: None,
},
validation: ValidationConfig::default(),
packs: vec![],
..Default::default()
}
}
fn rule_with_file_query(query_path: PathBuf) -> GenerationRule {
GenerationRule {
name: "test-rule".to_string(),
query: QuerySource::File { file: query_path },
template: TemplateSource::Inline {
inline: "{% for row in results %}{{ row.name }}{% endfor %}".to_string(),
},
output_file: "out.txt".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
}
}
fn rule_with_inline_query(inline: &str) -> GenerationRule {
GenerationRule {
name: "test-rule".to_string(),
query: QuerySource::Inline {
inline: inline.to_string(),
},
template: TemplateSource::Inline {
inline: "{% for row in results %}{{ row.name }}{% endfor %}".to_string(),
},
output_file: "out.txt".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
}
}
#[test]
fn helper_detects_values_keyword_uppercase() {
assert!(query_contains_values(
"SELECT ?x WHERE { VALUES (?x) { (\"a\") } }"
));
}
#[test]
fn helper_detects_values_keyword_lowercase() {
assert!(query_contains_values(
"select ?x where { values (?x) { (\"a\") } }"
));
}
#[test]
fn helper_ignores_commented_values_line() {
assert!(!query_contains_values(
"# VALUES block used to be here\nSELECT ?x WHERE { ?x a <http://example.org/T> }"
));
}
#[test]
fn helper_returns_false_for_plain_select() {
assert!(!query_contains_values("SELECT ?s ?p ?o WHERE { ?s ?p ?o }"));
}
#[test]
fn validator_rejects_rq_file_with_values_clause() {
let dir = TempDir::new().unwrap();
let rq = dir.path().join("labels.rq");
std::fs::write(
&rq,
"SELECT ?name ?color WHERE { VALUES (?name ?color) { (\"bug\" \"red\") } }",
)
.unwrap();
let ontology = stub_ontology(&dir);
let manifest = minimal_manifest(ontology, vec![rule_with_file_query(rq.clone())]);
let validator = ManifestValidator::new(&manifest, dir.path());
let result = validator.validate();
assert!(
result.is_err(),
"Validator must reject .rq file with VALUES clause"
);
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("E0010"),
"Error must carry code E0010, got: {msg}"
);
assert!(
msg.contains("inline"),
"Error must mention inline syntax, got: {msg}"
);
}
#[test]
fn validator_error_message_contains_rule_name() {
let dir = TempDir::new().unwrap();
let rq = dir.path().join("data.rq");
std::fs::write(&rq, "SELECT ?x WHERE { VALUES (?x) { (\"v\") } }").unwrap();
let ontology = stub_ontology(&dir);
let mut rule = rule_with_file_query(rq);
rule.name = "my-named-rule".to_string();
let manifest = minimal_manifest(ontology, vec![rule]);
let validator = ManifestValidator::new(&manifest, dir.path());
let err = validator.validate().unwrap_err().to_string();
assert!(
err.contains("my-named-rule"),
"Error must contain rule name, got: {err}"
);
}
#[test]
fn validator_accepts_rq_file_with_real_triple_patterns() {
let dir = TempDir::new().unwrap();
let rq = dir.path().join("structs.rq");
std::fs::write(
&rq,
"PREFIX ex: <http://example.org/>\nSELECT ?name WHERE { ?s a ex:Struct ; ex:name ?name }",
)
.unwrap();
let ontology = stub_ontology(&dir);
let manifest = minimal_manifest(ontology, vec![rule_with_file_query(rq)]);
let validator = ManifestValidator::new(&manifest, dir.path());
assert!(
validator.validate().is_ok(),
"File-based query with real triple patterns must be accepted"
);
}
#[test]
fn validator_accepts_inline_values_query() {
let dir = TempDir::new().unwrap();
let ontology = stub_ontology(&dir);
let manifest = minimal_manifest(
ontology,
vec![rule_with_inline_query(
"SELECT ?name WHERE { VALUES (?name) { (\"stpnt\") } }",
)],
);
let validator = ManifestValidator::new(&manifest, dir.path());
assert!(
validator.validate().is_ok(),
"Inline VALUES query must be accepted"
);
}
#[test]
fn pipeline_rejects_rq_file_with_values_at_execution() {
let dir = TempDir::new().unwrap();
let rq = dir.path().join("data.rq");
std::fs::write(&rq, "SELECT ?name WHERE { VALUES (?name) { (\"x\") } }").unwrap();
let ontology = stub_ontology(&dir);
let manifest = minimal_manifest(
ontology,
vec![rule_with_file_query(PathBuf::from("data.rq"))],
);
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.load_ontology().unwrap();
let result = pipeline.execute_generation_rules();
assert!(
result.is_err(),
"Pipeline must reject file-based VALUES at runtime"
);
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("E0010"),
"Runtime error must carry code E0010, got: {msg}"
);
}
#[test]
fn batch_rendering_writes_file_once_for_static_output_path() {
let dir = TempDir::new().unwrap();
let out_dir = dir.path().join("out");
std::fs::create_dir_all(&out_dir).unwrap();
let ontology = stub_ontology(&dir);
let rule = GenerationRule {
name: "labels".to_string(),
query: QuerySource::Inline {
inline: r#"SELECT ?id ?color WHERE {
VALUES (?id ?color) {
("a" "1") ("b" "2") ("c" "3") ("d" "4")
("e" "5") ("f" "6") ("g" "7") ("h" "8")
("i" "9") ("j" "10") ("k" "11") ("l" "12")
}
}"#
.to_string(),
},
template: TemplateSource::Inline {
inline: "{% for row in results %}{{ row.id }}={{ row.color }}\n{% endfor %}"
.to_string(),
},
output_file: "labels.txt".to_string(), mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
};
let mut manifest = minimal_manifest(ontology, vec![rule]);
manifest.generation.output_dir = out_dir.clone();
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.set_output_dir(out_dir.clone());
pipeline.load_ontology().unwrap();
let result = pipeline
.execute_generation_rules()
.expect("Batch render must succeed");
assert_eq!(
result.len(),
1,
"Static output must produce 1 file, not 1-per-row: got {}",
result.len()
);
let content = std::fs::read_to_string(out_dir.join("labels.txt")).unwrap();
for i in ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'] {
assert!(
content.contains(&format!("{i}=")),
"Missing row '{i}' in batch output"
);
}
assert_eq!(
content.matches('\n').count(),
12,
"Expected 12 newlines for 12 rows"
);
}
#[test]
fn inline_values_query_generates_correct_output() {
let dir = TempDir::new().unwrap();
let out_dir = dir.path().join("out");
std::fs::create_dir_all(&out_dir).unwrap();
let ontology = stub_ontology(&dir);
let rule = GenerationRule {
name: "labels".to_string(),
query: QuerySource::Inline {
inline: r#"SELECT ?id ?name ?color WHERE {
VALUES (?id ?name ?color) {
("kind-bug" "Kind:Bug" "B60205")
("kind-feature" "Kind:Feature" "0E8A16")
}
}"#
.to_string(),
},
template: TemplateSource::Inline {
inline: "{% for row in results %}{{ row.id }}={{ row.color }}\n{% endfor %}"
.to_string(),
},
output_file: "labels.txt".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
};
let mut manifest = minimal_manifest(ontology, vec![rule]);
manifest.generation.output_dir = out_dir.clone();
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.set_output_dir(out_dir.clone());
pipeline.load_ontology().unwrap();
pipeline
.execute_generation_rules()
.expect("Inline VALUES pipeline must succeed");
let content = std::fs::read_to_string(out_dir.join("labels.txt")).unwrap();
assert!(
content.contains("kind-bug=B60205"),
"Output must contain first label"
);
assert!(
content.contains("kind-feature=0E8A16"),
"Output must contain second label"
);
}
#[test]
fn pipeline_rejects_unsafe_code_when_no_unsafe_is_true() {
let dir = TempDir::new().unwrap();
let out_dir = dir.path().join("out");
std::fs::create_dir_all(&out_dir).unwrap();
let ontology = stub_ontology(&dir);
let rule = GenerationRule {
name: "unsafe-rule".to_string(),
query: QuerySource::Inline {
inline: r#"SELECT ?code WHERE { VALUES (?code) { ("placeholder") } }"#.to_string(),
},
template: TemplateSource::Inline {
inline: "fn foo() {\n unsafe { let _ = 1; }\n}\n".to_string(),
},
output_file: "out.rs".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
};
let mut manifest = minimal_manifest(ontology, vec![rule]);
manifest.generation.output_dir = out_dir.clone();
manifest.validation.no_unsafe = true;
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.set_output_dir(out_dir.clone());
pipeline.load_ontology().unwrap();
let result = pipeline.execute_generation_rules();
assert!(
result.is_err(),
"Pipeline must reject unsafe code when no_unsafe = true"
);
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("E0012"),
"Error must carry code E0012, got: {msg}"
);
}
#[test]
fn pipeline_allows_safe_code_when_no_unsafe_is_true() {
let dir = TempDir::new().unwrap();
let out_dir = dir.path().join("out");
std::fs::create_dir_all(&out_dir).unwrap();
let ontology = stub_ontology(&dir);
let rule = GenerationRule {
name: "safe-rule".to_string(),
query: QuerySource::Inline {
inline: r#"SELECT ?name WHERE { VALUES (?name) { ("Alice") } }"#.to_string(),
},
template: TemplateSource::Inline {
inline: "pub fn greet() -> &'static str { \"hello\" }\n".to_string(),
},
output_file: "out.rs".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
};
let mut manifest = minimal_manifest(ontology, vec![rule]);
manifest.generation.output_dir = out_dir.clone();
manifest.validation.no_unsafe = true;
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.set_output_dir(out_dir.clone());
pipeline.load_ontology().unwrap();
pipeline
.execute_generation_rules()
.expect("Safe code must pass no_unsafe gate");
assert!(
out_dir.join("out.rs").exists(),
"Output file must be written"
);
}
#[test]
fn validator_rejects_construct_without_order_by_when_strict_mode_is_true() {
use ggen_core::manifest::InferenceRule;
let dir = TempDir::new().unwrap();
let ontology = stub_ontology(&dir);
let mut manifest = minimal_manifest(ontology, vec![]);
manifest.validation.strict_mode = true;
manifest.inference.rules.push(InferenceRule {
name: "no-order".to_string(),
description: None,
order: 1,
construct: "CONSTRUCT { ?s ?p ?o } WHERE { OPTIONAL { ?s ?p ?o } }".to_string(),
when: None,
});
let validator = ManifestValidator::new(&manifest, dir.path());
let result = validator.validate();
assert!(
result.is_err(),
"strict_mode must reject CONSTRUCT without ORDER BY"
);
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("E0011"),
"Error must carry code E0011, got: {msg}"
);
}
#[test]
fn validator_accepts_construct_with_order_by_in_strict_mode() {
use ggen_core::manifest::InferenceRule;
let dir = TempDir::new().unwrap();
let ontology = stub_ontology(&dir);
let mut manifest = minimal_manifest(ontology, vec![]);
manifest.validation.strict_mode = true;
manifest.inference.rules.push(InferenceRule {
name: "ordered".to_string(),
description: None,
order: 1,
construct: "CONSTRUCT { ?s ?p ?o } WHERE { OPTIONAL { ?s ?p ?o } } ORDER BY ?s ?p ?o"
.to_string(),
when: None,
});
let validator = ManifestValidator::new(&manifest, dir.path());
validator
.validate()
.expect("CONSTRUCT with ORDER BY must pass strict_mode");
}
#[test]
fn spec_141_decision_tree_structure_from_values() {
let dir = TempDir::new().unwrap();
let ontology = stub_ontology(&dir);
let tree_query = r#"
SELECT ?tree_id ?tree_name ?root_node_id ?description
WHERE {
VALUES (?tree_id ?tree_name ?root_node_id ?description) {
("test-tree" "Test Decision Tree" "root" "A test tree for spec 141")
}
}
ORDER BY ?tree_id
"#;
let rule = GenerationRule {
name: "spec141-tree-catalog".to_string(),
query: QuerySource::Inline {
inline: tree_query.to_string(),
},
template: TemplateSource::Inline {
inline: r#"{% for row in results %}{{ row.tree_id }}|{{ row.tree_name }}|{{ row.root_node_id }}{% endfor %}"#.to_string(),
},
output_file: "test-trees.txt".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
};
let manifest = minimal_manifest(ontology.clone(), vec![rule]);
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.load_ontology().unwrap();
let _result = pipeline
.execute_generation_rules()
.expect("Pipeline must execute successfully");
let output_file = dir.path().join("test-trees.txt");
assert!(
output_file.exists(),
"Generated output file must exist at {:?}",
output_file
);
let content = std::fs::read_to_string(&output_file).expect("Generated file must be readable");
assert!(
content.contains("test-tree"),
"Output must contain tree_id 'test-tree', got: {}",
content
);
assert!(
content.contains("Test Decision Tree"),
"Output must contain tree name, got: {}",
content
);
assert!(
content.contains("root"),
"Output must contain root node ID, got: {}",
content
);
}
#[test]
fn spec_141_decision_tree_nodes_parsing() {
let dir = TempDir::new().unwrap();
let ontology = stub_ontology(&dir);
let nodes_query = r#"
SELECT ?tree_id ?node_id ?node_type ?question ?condition ?then_node ?else_node ?outcome
WHERE {
VALUES (?tree_id ?node_id ?node_type ?question ?condition ?then_node ?else_node ?outcome) {
("test-tree" "q1" "branch" "Is input valid?" "validate(input)" "q2" "fail" "")
("test-tree" "q2" "terminal" "" "" "" "" "success")
("test-tree" "fail" "terminal" "" "" "" "" "failure")
}
}
ORDER BY ?tree_id ?node_id
"#;
let rule = GenerationRule {
name: "spec141-tree-nodes".to_string(),
query: QuerySource::Inline {
inline: nodes_query.to_string(),
},
template: TemplateSource::Inline {
inline: r#"{% for row in results %}{{ row.node_id }}|{{ row.node_type }}|{{ row.then_node }}|{{ row.else_node }}|{{ row.outcome }}
{% endfor %}"#.to_string(),
},
output_file: "tree-nodes.txt".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
};
let manifest = minimal_manifest(ontology.clone(), vec![rule]);
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.load_ontology().unwrap();
let _result = pipeline
.execute_generation_rules()
.expect("Pipeline must execute successfully");
let output_file = dir.path().join("tree-nodes.txt");
let content = std::fs::read_to_string(&output_file).expect("Generated file must be readable");
assert!(
content.contains("q1|branch"),
"Output must contain branch node q1, got: {}",
content
);
assert!(
content.contains("q2"),
"Output must contain THEN path (q2), got: {}",
content
);
assert!(
content.contains("fail"),
"Output must contain ELSE path (fail), got: {}",
content
);
assert!(
content.contains("q2|terminal"),
"Output must contain terminal node q2, got: {}",
content
);
assert!(
content.contains("success"),
"Output must contain success outcome, got: {}",
content
);
}
#[test]
fn spec_141_decision_tree_json_serialization() {
let dir = TempDir::new().unwrap();
let ontology = stub_ontology(&dir);
let json_query = r#"
SELECT ?tree_id ?tree_name
WHERE {
VALUES (?tree_id ?tree_name) {
("patch-validation" "Patch Contract Validator")
("sync-workflow" "Sync Workflow Router")
}
}
ORDER BY ?tree_id
"#;
let json_template = r#"{% for row in results %}{{ row.tree_id }},{{ row.tree_name }}
{% endfor %}"#;
let rule = GenerationRule {
name: "spec141-tree-json".to_string(),
query: QuerySource::Inline {
inline: json_query.to_string(),
},
template: TemplateSource::Inline {
inline: json_template.to_string(),
},
output_file: "trees.json".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
};
let manifest = minimal_manifest(ontology.clone(), vec![rule]);
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.load_ontology().unwrap();
let _result = pipeline
.execute_generation_rules()
.expect("Pipeline must execute successfully");
let output_file = dir.path().join("trees.json");
let content = std::fs::read_to_string(&output_file).expect("Generated file must be readable");
assert!(
content.contains("patch-validation"),
"Output must contain 'patch-validation' tree, got: {}",
content
);
assert!(
content.contains("Patch Contract Validator"),
"Output must contain tree name, got: {}",
content
);
assert!(
content.contains("sync-workflow"),
"Output must contain 'sync-workflow' tree, got: {}",
content
);
assert!(
content.contains("Sync Workflow Router"),
"Output must contain sync workflow name, got: {}",
content
);
let lines: Vec<&str> = content.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(
lines.len(),
2,
"Output must contain 2 trees, got: {}",
lines.len()
);
}
#[test]
fn spec_141_decision_tree_terminal_coverage() {
let dir = TempDir::new().unwrap();
let ontology = stub_ontology(&dir);
let nodes_query = r#"
SELECT ?node_id ?node_type ?then_node ?else_node
WHERE {
VALUES (?node_id ?node_type ?then_node ?else_node) {
("q1" "branch" "q2" "fail1")
("q2" "branch" "success" "fail2")
("success" "terminal" "" "")
("fail1" "terminal" "" "")
("fail2" "terminal" "" "")
}
}
ORDER BY ?node_id
"#;
let rule = GenerationRule {
name: "spec141-coverage-nodes".to_string(),
query: QuerySource::Inline {
inline: nodes_query.to_string(),
},
template: TemplateSource::Inline {
inline: r#"{% for row in results %}{{ row.node_id }}:{{ row.node_type }}
{% endfor %}"#
.to_string(),
},
output_file: "coverage.txt".to_string(),
mode: GenerationMode::Overwrite,
skip_empty: false,
when: None,
};
let manifest = minimal_manifest(ontology.clone(), vec![rule]);
let mut pipeline = GenerationPipeline::new(manifest, dir.path().to_path_buf());
pipeline.load_ontology().unwrap();
let _result = pipeline
.execute_generation_rules()
.expect("Pipeline must execute successfully");
let output_file = dir.path().join("coverage.txt");
let content = std::fs::read_to_string(&output_file).expect("Generated file must be readable");
let lines: Vec<&str> = content.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(
lines.len(),
5,
"Output must contain 5 nodes, got: {} content: {}",
lines.len(),
content
);
let mut terminals = vec![];
let mut branches = vec![];
for line in &lines {
if line.contains(":terminal") {
terminals.push(*line);
} else if line.contains(":branch") {
branches.push(*line);
}
}
assert_eq!(
branches.len(),
2,
"Must have 2 branch nodes, got: {}",
branches.len()
);
assert_eq!(
terminals.len(),
3,
"Must have 3 terminal nodes, got: {}",
terminals.len()
);
}
#[test]
fn test_validate_syntax_detect_language_rust() {
assert_eq!(detect_language(Path::new("main.rs")), LanguageType::Rust);
assert_eq!(detect_language(Path::new("lib.rs")), LanguageType::Rust);
assert_eq!(
detect_language(Path::new("src/main.rs")),
LanguageType::Rust
);
}
#[test]
fn test_validate_syntax_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_validate_syntax_detect_language_json() {
assert_eq!(
detect_language(Path::new("config.json")),
LanguageType::Json
);
assert_eq!(detect_language(Path::new("data.json")), LanguageType::Json);
}
#[test]
fn test_validate_syntax_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_validate_syntax_rust_valid_minimal_code() {
let code = "fn main() { let x = 1; }";
let result = validate_syntax(Path::new("main.rs"), code);
assert!(
result.is_ok(),
"Valid minimal Rust code must pass syntax validation, got: {:?}",
result
);
}
#[test]
fn test_validate_syntax_rust_valid_struct() {
let code = "#[derive(Debug)]\nstruct Point { x: i32, y: i32 }\n";
let result = validate_syntax(Path::new("types.rs"), code);
assert!(
result.is_ok(),
"Valid Rust struct must pass syntax validation, got: {:?}",
result
);
}
#[test]
fn test_validate_syntax_toml_valid_config() {
let toml = "[package]\nname = \"test\"\nversion = \"0.1.0\"\n";
let result = validate_syntax(Path::new("Cargo.toml"), toml);
assert!(
result.is_ok(),
"Valid TOML config must pass syntax validation, got: {:?}",
result
);
}
#[test]
fn test_validate_syntax_json_valid() {
let json = r#"{"key": "value", "count": 42, "enabled": true}"#;
let result = validate_syntax(Path::new("config.json"), json);
assert!(
result.is_ok(),
"Valid JSON must pass syntax validation, got: {:?}",
result
);
}
#[test]
fn test_validate_syntax_yaml_valid() {
let yaml = "key: value\nlist:\n - item1\n - item2\n";
let result = validate_syntax(Path::new("config.yaml"), yaml);
assert!(
result.is_ok(),
"Valid YAML must pass syntax validation, got: {:?}",
result
);
}
#[test]
fn test_validate_syntax_markdown_passthrough() {
let md = "# Title\n\nSome text with `code`.\n\n```rust\nfn foo() {}\n```\n";
let result = validate_syntax(Path::new("README.md"), md);
assert!(
result.is_ok(),
"Markdown files must passthrough syntax validation, got: {:?}",
result
);
}
#[test]
fn test_validate_syntax_tera_passthrough() {
let tera = "{% for item in items %}<li>{{ item }}</li>{% endfor %}";
let result = validate_syntax(Path::new("template.tera"), tera);
assert!(
result.is_ok(),
"Tera templates must passthrough syntax validation (checked at render), got: {:?}",
result
);
}
#[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#"{"name": "test"}"#).unwrap();
let toml_file = dir.path().join("test.toml");
std::fs::write(&toml_file, "key = \"value\"").unwrap();
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_rust_invalid_missing_brace() {
let code = "fn main() { let x = 1 }"; let result = validate_syntax(Path::new("main.rs"), code);
assert!(
result.is_err(),
"Invalid Rust code (missing semicolon) must fail validation"
);
let err = result.unwrap_err().to_string();
assert!(err.contains("Rust"), "Error must mention language: {err}");
}
#[test]
fn test_validate_syntax_rust_invalid_bad_token() {
let code = "fn main() { @@@ }";
let result = validate_syntax(Path::new("main.rs"), code);
assert!(
result.is_err(),
"Invalid Rust code (bad token) must fail validation"
);
}
#[test]
fn test_validate_syntax_rust_invalid_incomplete_statement() {
let code = "fn main() { let x }";
let result = validate_syntax(Path::new("main.rs"), code);
assert!(
result.is_err(),
"Incomplete Rust statement must fail validation"
);
}
#[test]
fn test_validate_syntax_toml_invalid_missing_bracket() {
let toml = "[package\nname = \"test\"\n";
let result = validate_syntax(Path::new("Cargo.toml"), toml);
assert!(
result.is_err(),
"Invalid TOML (missing ]) must fail validation"
);
let err = result.unwrap_err().to_string();
assert!(err.contains("TOML"), "Error must mention language: {err}");
}
#[test]
fn test_validate_syntax_json_invalid_trailing_comma() {
let json = r#"{"key": "value",}"#;
let result = validate_syntax(Path::new("config.json"), json);
assert!(
result.is_err(),
"Invalid JSON (trailing comma) must fail validation"
);
let err = result.unwrap_err().to_string();
assert!(err.contains("JSON"), "Error must mention language: {err}");
}
#[test]
fn test_validate_syntax_json_invalid_unclosed_quote() {
let json = r#"{"key": "unclosed}"#;
let result = validate_syntax(Path::new("config.json"), json);
assert!(
result.is_err(),
"Invalid JSON (unclosed quote) must fail validation"
);
}
#[test]
fn test_validate_syntax_yaml_invalid_bad_indentation() {
let yaml = "key: value\n bad_indent: wrong\n";
let result = validate_syntax(Path::new("config.yaml"), yaml);
let _result = result; }
#[test]
fn test_validate_syntax_toml_invalid_bad_key() {
let toml = "[section invalid key =]\n";
let result = validate_syntax(Path::new("test.toml"), toml);
assert!(result.is_err(), "Invalid TOML syntax must fail validation");
}
#[test]
fn test_validate_syntax_rust_empty_file() {
let code = "";
let result = validate_syntax(Path::new("main.rs"), code);
assert!(result.is_err(), "Empty Rust file must fail validation");
}
#[test]
fn test_validate_syntax_json_empty_file() {
let json = "";
let result = validate_syntax(Path::new("config.json"), json);
assert!(result.is_err(), "Empty JSON file must fail validation");
}
#[test]
fn test_validate_syntax_error_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 message must include language type: {}",
err
);
}
#[test]
fn test_validate_syntax_markdown_empty_file() {
let md = "";
let result = validate_syntax(Path::new("README.md"), md);
assert!(
result.is_ok(),
"Empty markdown files must pass (no strict syntax)"
);
}