use std::env;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU32, Ordering};
use tmpltool::render_template;
mod common;
static TEST_COUNTER: AtomicU32 = AtomicU32::new(0);
fn setup_test_env() -> PathBuf {
let counter = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let test_dir = env::temp_dir().join(format!(
"tmpltool_include_test_{}_{}",
std::process::id(),
counter
));
fs::create_dir_all(&test_dir).unwrap();
test_dir
}
fn create_file(dir: &Path, name: &str, content: &str) -> PathBuf {
let file_path = dir.join(name);
let mut file = fs::File::create(&file_path).unwrap();
file.write_all(content.as_bytes()).unwrap();
file_path
}
fn cleanup_test_env(test_dir: &Path) {
let _ = fs::remove_dir_all(test_dir);
}
#[test]
fn test_simple_include() {
let test_dir = setup_test_env();
create_file(&test_dir, "partial.tmpltool", "Hello from partial!");
let main_template = create_file(
&test_dir,
"main.tmpltool",
"Start\n{% include \"./partial.tmpltool\" %}\nEnd",
);
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(
result.is_ok(),
"Template rendering failed: {:?}",
result.err()
);
let output = fs::read_to_string(&output_file).unwrap();
assert_eq!(output, "Start\nHello from partial!\nEnd");
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_with_env_vars() {
let test_dir = setup_test_env();
create_file(
&test_dir,
"partial.tmpltool",
"User: {{ get_env(name=\"TEST_USER\", default=\"guest\") }}",
);
let main_template = create_file(
&test_dir,
"main.tmpltool",
"Header\n{% include \"./partial.tmpltool\" %}\nFooter",
);
let output_file = test_dir.join("output.txt");
unsafe {
env::set_var("TEST_USER", "testuser");
}
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(
result.is_ok(),
"Template rendering failed: {:?}",
result.err()
);
let output = fs::read_to_string(&output_file).unwrap();
assert_eq!(output, "Header\nUser: testuser\nFooter");
unsafe {
env::remove_var("TEST_USER");
}
cleanup_test_env(&test_dir);
}
#[test]
fn test_nested_includes() {
let test_dir = setup_test_env();
create_file(&test_dir, "level2.tmpltool", "Level 2 content");
create_file(
&test_dir,
"level1.tmpltool",
"Level 1 start\n{% include \"./level2.tmpltool\" %}\nLevel 1 end",
);
let main_template = create_file(
&test_dir,
"main.tmpltool",
"Main start\n{% include \"./level1.tmpltool\" %}\nMain end",
);
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(
result.is_ok(),
"Template rendering failed: {:?}",
result.err()
);
let output = fs::read_to_string(&output_file).unwrap();
assert_eq!(
output,
"Main start\nLevel 1 start\nLevel 2 content\nLevel 1 end\nMain end"
);
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_with_subdirectory() {
let test_dir = setup_test_env();
let subdir = test_dir.join("partials");
fs::create_dir_all(&subdir).unwrap();
create_file(&subdir, "footer.tmpltool", "Footer content");
let main_template = create_file(
&test_dir,
"main.tmpltool",
"Main content\n{% include \"./partials/footer.tmpltool\" %}",
);
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(
result.is_ok(),
"Template rendering failed: {:?}",
result.err()
);
let output = fs::read_to_string(&output_file).unwrap();
assert_eq!(output, "Main content\nFooter content");
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_nonexistent_template() {
let test_dir = setup_test_env();
let main_template = create_file(
&test_dir,
"main.tmpltool",
"{% include \"./nonexistent.tmpltool\" %}",
);
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(result.is_err(), "Expected error for nonexistent template");
let error = result.unwrap_err().to_string();
assert!(
error.contains("Failed to load template") || error.contains("nonexistent"),
"Error message should mention failed load or nonexistent file: {}",
error
);
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_parent_directory_blocked() {
let test_dir = setup_test_env();
create_file(&test_dir, "parent.tmpltool", "Parent content");
let subdir = test_dir.join("subdir");
fs::create_dir_all(&subdir).unwrap();
let main_template = create_file(
&subdir,
"main.tmpltool",
"{% include \"../parent.tmpltool\" %}",
);
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None, );
assert!(
result.is_err(),
"Expected error for parent directory access"
);
let error = result.unwrap_err().to_string();
assert!(
error.contains("Parent directory") || error.contains(".."),
"Error message should mention parent directory restriction: {}",
error
);
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_parent_directory_allowed_with_trust() {
let test_dir = setup_test_env();
create_file(&test_dir, "parent.tmpltool", "Parent content");
let subdir = test_dir.join("subdir");
fs::create_dir_all(&subdir).unwrap();
let main_template = create_file(
&subdir,
"main.tmpltool",
"Start\n{% include \"../parent.tmpltool\" %}\nEnd",
);
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
true,
None, );
assert!(
result.is_ok(),
"Template rendering failed: {:?}",
result.err()
);
let output = fs::read_to_string(&output_file).unwrap();
assert_eq!(output, "Start\nParent content\nEnd");
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_absolute_path_blocked() {
let test_dir = setup_test_env();
let main_template = create_file(&test_dir, "main.tmpltool", "{% include \"/etc/passwd\" %}");
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None, );
assert!(result.is_err(), "Expected error for absolute path");
let error = result.unwrap_err().to_string();
assert!(
error.contains("Absolute paths are not allowed") || error.contains("Security"),
"Error message should mention security restriction: {}",
error
);
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_multiple_partials() {
let test_dir = setup_test_env();
create_file(&test_dir, "header.tmpltool", "=== Header ===");
create_file(&test_dir, "content.tmpltool", "Main Content");
create_file(&test_dir, "footer.tmpltool", "=== Footer ===");
let main_template = create_file(
&test_dir,
"main.tmpltool",
"{% include \"./header.tmpltool\" %}\n{% include \"./content.tmpltool\" %}\n{% include \"./footer.tmpltool\" %}",
);
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(
result.is_ok(),
"Template rendering failed: {:?}",
result.err()
);
let output = fs::read_to_string(&output_file).unwrap();
assert_eq!(output, "=== Header ===\nMain Content\n=== Footer ===");
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_with_conditionals() {
let test_dir = setup_test_env();
create_file(&test_dir, "optional.tmpltool", "Optional content included");
let main_template = create_file(
&test_dir,
"main.tmpltool",
"{% set show = get_env(name=\"SHOW_OPTIONAL\", default=\"false\") %}\
Start\n\
{% if show == \"true\" %}\
{% include \"./optional.tmpltool\" %}\n\
{% endif %}\
End",
);
let output_file = test_dir.join("output.txt");
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(result.is_ok());
let output = fs::read_to_string(&output_file).unwrap();
assert_eq!(output, "Start\nEnd");
unsafe {
env::set_var("SHOW_OPTIONAL", "true");
}
let result = render_template(
Some(main_template.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(result.is_ok());
let output = fs::read_to_string(&output_file).unwrap();
assert_eq!(output, "Start\nOptional content included\nEnd");
unsafe {
env::remove_var("SHOW_OPTIONAL");
}
cleanup_test_env(&test_dir);
}
#[test]
fn test_include_fixture_templates() {
let template_path = common::get_fixture_template("include_base.tmpltool");
let output_file = common::get_test_file_path("include_base_output.txt");
let result = render_template(
Some(template_path.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(
result.is_ok(),
"Template rendering failed: {:?}",
result.err()
);
let output = fs::read_to_string(&output_file).unwrap();
let expected = common::read_fixture_expected("include_base.txt");
assert_eq!(output, expected);
common::cleanup_test_file(&output_file);
}
#[test]
fn test_include_nested_fixture_templates() {
let template_path = common::get_fixture_template("include_nested_main.tmpltool");
let output_file = common::get_test_file_path("include_nested_output.txt");
let result = render_template(
Some(template_path.to_str().unwrap()),
Some(output_file.to_str().unwrap()),
false,
None,
);
assert!(
result.is_ok(),
"Template rendering failed: {:?}",
result.err()
);
let output = fs::read_to_string(&output_file).unwrap();
let expected = common::read_fixture_expected("include_nested.txt");
assert_eq!(output, expected);
common::cleanup_test_file(&output_file);
}