use rstest::*;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
use walkdir::WalkDir;
const REINHARDT_ADMIN: &str = env!("CARGO_BIN_EXE_reinhardt-admin");
fn find_unrendered_variables(dir: &Path) -> Vec<(PathBuf, String)> {
let mut hits = Vec::new();
for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
if !entry.file_type().is_file() {
continue;
}
let Ok(content) = fs::read_to_string(entry.path()) else {
continue; };
if let Some(bad_line) = content.lines().find(|l| l.contains("{{")) {
hits.push((entry.path().to_path_buf(), bad_line.to_string()));
}
}
hits
}
#[rstest]
fn startproject_restful_renders_all_variables() {
let tmp = TempDir::new().expect("tempdir");
let project_name = "e2e_restful_proj";
let output = Command::new(REINHARDT_ADMIN)
.args(["startproject", project_name, "--with-rest"])
.current_dir(tmp.path())
.output()
.expect("failed to spawn reinhardt-admin");
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"startproject failed (exit {:?})\nstdout: {stdout}\nstderr: {stderr}",
output.status.code()
);
let project_dir = tmp.path().join(project_name);
assert!(
project_dir.join("Cargo.toml").is_file(),
"Cargo.toml missing"
);
assert!(project_dir.join("src").is_dir(), "src/ dir missing");
assert!(
project_dir.join("settings").is_dir(),
"settings/ dir missing"
);
let cargo_toml = fs::read_to_string(project_dir.join("Cargo.toml")).expect("read Cargo.toml");
assert!(
cargo_toml.contains(&format!("name = \"{project_name}\"")),
"Cargo.toml missing `name = \"{project_name}\"`, got:\n{cargo_toml}"
);
let local_example = project_dir.join("settings").join("local.example.toml");
assert!(
local_example.is_file(),
"settings/local.example.toml missing"
);
let example_content = fs::read_to_string(&local_example).expect("read local.example.toml");
assert!(
example_content.contains("uncomment-this-line-and-replace-with-a-long-random-string"),
"local.example.toml should contain commented-out placeholder secret key, got:\n{example_content}"
);
let local_toml = project_dir.join("settings").join("local.toml");
assert!(local_toml.is_file(), "settings/local.toml missing");
let local_content = fs::read_to_string(&local_toml).expect("read local.toml");
assert!(
local_content.contains("secret_key"),
"local.toml must contain a secret_key entry"
);
let unrendered = find_unrendered_variables(&project_dir);
assert!(
unrendered.is_empty(),
"unrendered Tera variables found in generated project:\n{}",
unrendered
.iter()
.map(|(p, l)| format!(" {}: {}", p.display(), l))
.collect::<Vec<_>>()
.join("\n")
);
}
#[rstest]
fn startproject_pages_renders_all_variables() {
let tmp = TempDir::new().expect("tempdir");
let project_name = "e2e_pages_proj";
let output = Command::new(REINHARDT_ADMIN)
.args(["startproject", project_name, "--with-pages"])
.current_dir(tmp.path())
.output()
.expect("failed to spawn reinhardt-admin");
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"startproject --pages failed (exit {:?})\nstdout: {stdout}\nstderr: {stderr}",
output.status.code()
);
let project_dir = tmp.path().join(project_name);
assert!(
project_dir.join("Cargo.toml").is_file(),
"Cargo.toml missing"
);
assert!(project_dir.join("src").is_dir(), "src/ dir missing");
let cargo_toml = fs::read_to_string(project_dir.join("Cargo.toml")).expect("read Cargo.toml");
assert!(
cargo_toml.contains(&format!("name = \"{project_name}\"")),
"Cargo.toml missing correct project name, got:\n{cargo_toml}"
);
let unrendered = find_unrendered_variables(&project_dir);
assert!(
unrendered.is_empty(),
"unrendered Tera variables found in pages project:\n{}",
unrendered
.iter()
.map(|(p, l)| format!(" {}: {}", p.display(), l))
.collect::<Vec<_>>()
.join("\n")
);
}
#[rstest]
fn startapp_renders_all_variables() {
let tmp = TempDir::new().expect("tempdir");
let project_name = "e2e_app_host";
let app_name = "blog";
let proj_output = Command::new(REINHARDT_ADMIN)
.args(["startproject", project_name, "--with-rest"])
.current_dir(tmp.path())
.output()
.expect("failed to spawn reinhardt-admin for startproject");
assert!(
proj_output.status.success(),
"startproject pre-step failed: {}",
String::from_utf8_lossy(&proj_output.stderr)
);
let project_dir = tmp.path().join(project_name);
let app_output = Command::new(REINHARDT_ADMIN)
.args(["startapp", app_name, "--with-rest"])
.current_dir(&project_dir)
.output()
.expect("failed to spawn reinhardt-admin for startapp");
let stderr = String::from_utf8_lossy(&app_output.stderr);
let stdout = String::from_utf8_lossy(&app_output.stdout);
assert!(
app_output.status.success(),
"startapp failed (exit {:?})\nstdout: {stdout}\nstderr: {stderr}",
app_output.status.code()
);
let app_dir = project_dir.join("src").join("apps").join(app_name);
assert!(
app_dir.exists(),
"app directory missing at {}",
app_dir.display()
);
let unrendered = find_unrendered_variables(&project_dir);
assert!(
unrendered.is_empty(),
"unrendered Tera variables found after startapp:\n{}",
unrendered
.iter()
.map(|(p, l)| format!(" {}: {}", p.display(), l))
.collect::<Vec<_>>()
.join("\n")
);
}
#[rstest]
fn startproject_template_dir_override_wins_for_overridden_file() {
let tmp = TempDir::new().expect("tempdir");
let override_dir = tmp
.path()
.join("my_templates")
.join("project_restful_template");
fs::create_dir_all(&override_dir).expect("create override dir");
fs::write(
override_dir.join("Cargo.toml.tpl"),
b"# CUSTOM CARGO TOML FOR {{ project_name }}\n",
)
.expect("write custom template");
let project_name = "e2e_override_proj";
let output = Command::new(REINHARDT_ADMIN)
.args([
"startproject",
project_name,
"--with-rest",
"--template-dir",
tmp.path().join("my_templates").to_str().unwrap(),
])
.current_dir(tmp.path())
.output()
.expect("failed to spawn reinhardt-admin");
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"startproject --template-dir failed (exit {:?})\nstdout: {stdout}\nstderr: {stderr}",
output.status.code()
);
let project_dir = tmp.path().join(project_name);
let cargo_toml = fs::read_to_string(project_dir.join("Cargo.toml")).expect("read Cargo.toml");
assert!(
cargo_toml.starts_with("# CUSTOM CARGO TOML FOR"),
"expected custom Cargo.toml header, got:\n{cargo_toml}"
);
assert!(
cargo_toml.contains(project_name),
"custom Cargo.toml must have project name substituted"
);
assert!(
project_dir.join("src").is_dir(),
"src/ dir should come from embedded fallback"
);
}