use std::path::Path;
use super::naming::{snake_case, tests_file_path, validate_type_name};
use super::plan::Plan;
pub(crate) fn plan(root: &Path, raw_name: &str) -> Result<(Plan, String), String> {
let name = validate_type_name(raw_name)?;
let stem = format!("{}_test", snake_case(&name));
let path = tests_file_path(root, &format!("{stem}.rs"))?;
let content = render(&name);
let plan = Plan::new().create(path, content);
Ok((plan, stem))
}
fn render(name: &str) -> String {
let snake = snake_case(name);
format!(
"//! Integration test for `{name}`.\n\
\n\
/// Smoke test generated by `arc make test {name}`. Replace the body\n\
/// with a real test. Run with `cargo test --test {snake}_test`.\n\
\n\
// To share setup across integration tests, include the helpers\n\
// module published by `arc stubs publish`:\n\
//\n\
// mod helpers;\n\
\n\
#[test]\n\
fn {snake}_smoke() {{\n\
\x20 assert!(true);\n\
}}\n"
)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::PathBuf;
fn temp_root(label: &str) -> PathBuf {
let root = std::env::temp_dir().join(format!(
"arcature-cli-make-test-{label}-{}-{}",
std::process::id(),
unique_suffix()
));
fs::create_dir_all(&root).expect("temp root should be created");
root
}
fn unique_suffix() -> u128 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_nanos())
}
fn cleanup(root: &Path) {
let _ = fs::remove_dir_all(root);
}
#[test]
fn plan_targets_tests_name_test_rs() {
let root = temp_root("plan");
let (plan, stem) = plan(&root, "Links").expect("plan should build");
assert_eq!(stem, "links_test");
let ops = plan.ops();
assert_eq!(ops.len(), 1);
match &ops[0] {
super::super::plan::PlannedOp::Create { path, content } => {
assert!(path.ends_with("tests/links_test.rs"));
assert!(content.contains("links_smoke"));
assert!(!content.contains("TODO"));
assert!(!content.contains("todo!"));
}
_ => panic!("expected a Create op"),
}
cleanup(&root);
}
#[test]
fn plan_rejects_invalid_name() {
let root = temp_root("invalid");
assert!(plan(&root, "").is_err());
assert!(plan(&root, "123Bad").is_err());
assert!(plan(&root, "a/b").is_err());
assert!(plan(&root, "Links").is_ok());
cleanup(&root);
}
#[test]
fn commit_creates_test_file() {
let root = temp_root("commit");
let (mut plan, _) = plan(&root, "Links").expect("plan should build");
plan.execute(super::super::plan::OverwritePolicy::Refuse, None)
.expect("commit should succeed");
let file = root.join("tests").join("links_test.rs");
assert!(file.is_file());
let content = fs::read_to_string(&file).unwrap();
assert!(content.contains("fn links_smoke()"));
cleanup(&root);
}
#[test]
fn dry_run_creates_nothing() {
let root = temp_root("dry-run");
let (plan, _) = plan(&root, "Links").expect("plan should build");
let report = plan
.dry_run_report(super::super::plan::OverwritePolicy::Refuse)
.expect("dry-run should report");
assert!(report.contains("no files will be written"));
assert!(!root.join("tests").exists());
cleanup(&root);
}
#[test]
fn conflict_on_existing_test_file() {
let root = temp_root("conflict");
let file = root.join("tests").join("links_test.rs");
fs::create_dir_all(file.parent().unwrap()).unwrap();
fs::write(&file, "user test").unwrap();
let (mut plan, _) = plan(&root, "Links").expect("plan should build");
let err = plan
.execute(super::super::plan::OverwritePolicy::Refuse, None)
.expect_err("should refuse to overwrite");
assert!(matches!(err, super::super::plan::PlanError::Conflict(p) if p == file));
assert_eq!(fs::read_to_string(&file).unwrap(), "user test");
cleanup(&root);
}
}