use anyhow::Result; use std::fs; use std::process::Command; use tempfile::TempDir; // Import the template system use weevil::templates::{TemplateManager, TemplateContext}; /// Helper to create a test template context fn test_context(project_name: &str) -> TemplateContext { TemplateContext { project_name: project_name.to_string(), package_name: project_name.to_lowercase().replace("-", ""), creation_date: "2026-02-02T12:00:00Z".to_string(), weevil_version: "1.1.0-test".to_string(), template_name: "basic".to_string(), } } #[test] fn test_template_manager_creation() { let mgr = TemplateManager::new(); assert!(mgr.is_ok(), "TemplateManager should be created successfully"); } #[test] fn test_template_exists() { let mgr = TemplateManager::new().unwrap(); assert!(mgr.template_exists("basic"), "basic template should exist"); assert!(mgr.template_exists("testing"), "testing template should exist"); assert!(!mgr.template_exists("nonexistent"), "nonexistent template should not exist"); } #[test] fn test_list_templates() { let mgr = TemplateManager::new().unwrap(); let templates = mgr.list_templates(); assert_eq!(templates.len(), 2, "Should have exactly 2 templates"); assert!(templates.iter().any(|t| t.contains("basic")), "Should list basic template"); assert!(templates.iter().any(|t| t.contains("testing")), "Should list testing template"); } #[test] fn test_basic_template_extraction() -> Result<()> { let mgr = TemplateManager::new()?; let temp_dir = TempDir::new()?; let project_dir = temp_dir.path().join("test-robot"); fs::create_dir(&project_dir)?; let context = test_context("test-robot"); let file_count = mgr.extract_template("basic", &project_dir, &context)?; assert!(file_count > 0, "Should extract at least one file from basic template"); // Verify key files exist assert!(project_dir.join(".gitignore").exists(), ".gitignore should exist"); assert!(project_dir.join("README.md").exists(), "README.md should exist (processed from .template)"); assert!(project_dir.join(".weevil.toml").exists(), ".weevil.toml should exist (processed from .template)"); assert!(project_dir.join("build.gradle").exists(), "build.gradle should exist (processed from .template)"); assert!(project_dir.join("settings.gradle").exists(), "settings.gradle should exist"); // Verify OpMode exists let opmode_path = project_dir.join("src/main/java/robot/opmodes/BasicOpMode.java"); assert!(opmode_path.exists(), "BasicOpMode.java should exist"); Ok(()) } #[test] fn test_testing_template_extraction() -> Result<()> { let mgr = TemplateManager::new()?; let temp_dir = TempDir::new()?; let project_dir = temp_dir.path().join("test-showcase"); fs::create_dir(&project_dir)?; let mut context = test_context("test-showcase"); context.template_name = "testing".to_string(); let file_count = mgr.extract_template("testing", &project_dir, &context)?; assert!(file_count > 20, "Testing template should have 20+ files, got {}", file_count); // Verify documentation files assert!(project_dir.join("README.md").exists(), "README.md should exist"); assert!(project_dir.join("DESIGN_AND_TEST_PLAN.md").exists(), "DESIGN_AND_TEST_PLAN.md should exist"); assert!(project_dir.join("TESTING_GUIDE.md").exists(), "TESTING_GUIDE.md should exist"); // Verify subsystems assert!(project_dir.join("src/main/java/robot/subsystems/MotorCycler.java").exists(), "MotorCycler.java should exist"); assert!(project_dir.join("src/main/java/robot/subsystems/WallApproach.java").exists(), "WallApproach.java should exist"); assert!(project_dir.join("src/main/java/robot/subsystems/TurnController.java").exists(), "TurnController.java should exist"); // Verify hardware interfaces and implementations assert!(project_dir.join("src/main/java/robot/hardware/MotorController.java").exists(), "MotorController interface should exist"); assert!(project_dir.join("src/main/java/robot/hardware/FtcMotorController.java").exists(), "FtcMotorController should exist"); assert!(project_dir.join("src/main/java/robot/hardware/DistanceSensor.java").exists(), "DistanceSensor interface should exist"); assert!(project_dir.join("src/main/java/robot/hardware/FtcDistanceSensor.java").exists(), "FtcDistanceSensor should exist"); // Verify test files assert!(project_dir.join("src/test/java/robot/subsystems/MotorCyclerTest.java").exists(), "MotorCyclerTest.java should exist"); assert!(project_dir.join("src/test/java/robot/subsystems/WallApproachTest.java").exists(), "WallApproachTest.java should exist"); assert!(project_dir.join("src/test/java/robot/subsystems/TurnControllerTest.java").exists(), "TurnControllerTest.java should exist"); // Verify mock implementations assert!(project_dir.join("src/test/java/robot/hardware/MockMotorController.java").exists(), "MockMotorController should exist"); assert!(project_dir.join("src/test/java/robot/hardware/MockDistanceSensor.java").exists(), "MockDistanceSensor should exist"); Ok(()) } #[test] fn test_template_variable_substitution() -> Result<()> { let mgr = TemplateManager::new()?; let temp_dir = TempDir::new()?; let project_dir = temp_dir.path().join("my-test-robot"); fs::create_dir(&project_dir)?; let context = test_context("my-test-robot"); mgr.extract_template("basic", &project_dir, &context)?; // Check README.md for variable substitution let readme_path = project_dir.join("README.md"); let readme_content = fs::read_to_string(readme_path)?; assert!(readme_content.contains("my-test-robot"), "README should contain project name"); assert!(readme_content.contains("1.1.0-test"), "README should contain weevil version"); assert!(!readme_content.contains("{{PROJECT_NAME}}"), "README should not contain template variable"); assert!(!readme_content.contains("{{WEEVIL_VERSION}}"), "README should not contain template variable"); // Check .weevil.toml for variable substitution let weevil_toml = project_dir.join(".weevil.toml"); let toml_content = fs::read_to_string(weevil_toml)?; assert!(toml_content.contains("my-test-robot"), ".weevil.toml should contain project name"); assert!(!toml_content.contains("{{PROJECT_NAME}}"), ".weevil.toml should not contain template variable"); Ok(()) } #[test] fn test_invalid_template_extraction() { let mgr = TemplateManager::new().unwrap(); let temp_dir = TempDir::new().unwrap(); let project_dir = temp_dir.path().join("test-robot"); fs::create_dir(&project_dir).unwrap(); let context = test_context("test-robot"); let result = mgr.extract_template("nonexistent", &project_dir, &context); assert!(result.is_err(), "Should fail for nonexistent template"); } #[test] fn test_package_name_sanitization() { let context1 = test_context("my-robot"); assert_eq!(context1.package_name, "myrobot", "Hyphens should be removed"); let context2 = test_context("team_1234_bot"); assert_eq!(context2.package_name, "team1234bot", "Underscores should be removed"); } /// Integration test: Create a project with testing template and run gradle tests /// This is marked with #[ignore] by default since it requires: /// - Java installed /// - Network access (first time to download gradle wrapper) /// - Takes ~1-2 minutes to run /// /// Run with: cargo test test_testing_template_gradle_build -- --ignored --nocapture #[test] #[ignore] fn test_testing_template_gradle_build() -> Result<()> { println!("Testing complete gradle build and test execution..."); let mgr = TemplateManager::new()?; let temp_dir = TempDir::new()?; let project_dir = temp_dir.path().join("gradle-test-robot"); fs::create_dir(&project_dir)?; // Extract testing template let mut context = test_context("gradle-test-robot"); context.template_name = "testing".to_string(); let file_count = mgr.extract_template("testing", &project_dir, &context)?; println!("Extracted {} files from testing template", file_count); // Check if gradlew exists (should be in testing template) let gradlew = if cfg!(windows) { project_dir.join("gradlew.bat") } else { project_dir.join("gradlew") }; if !gradlew.exists() { println!("WARNING: gradlew not found in template, skipping gradle test"); return Ok(()); } // Make gradlew executable on Unix #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut perms = fs::metadata(&gradlew)?.permissions(); perms.set_mode(0o755); fs::set_permissions(&gradlew, perms)?; } println!("Running gradle test..."); // Run gradlew test let output = Command::new(&gradlew) .arg("test") .current_dir(&project_dir) .output()?; println!("=== Gradle Output ==="); println!("{}", String::from_utf8_lossy(&output.stdout)); if !output.status.success() { println!("=== Gradle Errors ==="); println!("{}", String::from_utf8_lossy(&output.stderr)); panic!("Gradle tests failed with status: {}", output.status); } // Verify test output mentions 45 tests let stdout = String::from_utf8_lossy(&output.stdout); // Look for test success indicators let has_success = stdout.contains("BUILD SUCCESSFUL") || stdout.contains("45 tests") || stdout.to_lowercase().contains("tests passed"); assert!(has_success, "Gradle test output should indicate success"); println!("✓ All 45 tests passed!"); Ok(()) } /// Test that basic template creates a valid directory structure #[test] fn test_basic_template_directory_structure() -> Result<()> { let mgr = TemplateManager::new()?; let temp_dir = TempDir::new()?; let project_dir = temp_dir.path().join("structure-test"); fs::create_dir(&project_dir)?; let context = test_context("structure-test"); mgr.extract_template("basic", &project_dir, &context)?; // Verify directory structure assert!(project_dir.join("src").is_dir(), "src directory should exist"); assert!(project_dir.join("src/main").is_dir(), "src/main directory should exist"); assert!(project_dir.join("src/main/java").is_dir(), "src/main/java directory should exist"); assert!(project_dir.join("src/main/java/robot").is_dir(), "src/main/java/robot directory should exist"); assert!(project_dir.join("src/main/java/robot/opmodes").is_dir(), "opmodes directory should exist"); assert!(project_dir.join("src/test/java/robot").is_dir(), "test directory should exist"); Ok(()) } /// Test that .gitignore is not named ".gitignore.template" #[test] fn test_gitignore_naming() -> Result<()> { let mgr = TemplateManager::new()?; let temp_dir = TempDir::new()?; let project_dir = temp_dir.path().join("gitignore-test"); fs::create_dir(&project_dir)?; let context = test_context("gitignore-test"); mgr.extract_template("basic", &project_dir, &context)?; assert!(project_dir.join(".gitignore").exists(), ".gitignore should exist"); assert!(!project_dir.join(".gitignore.template").exists(), ".gitignore.template should NOT exist"); Ok(()) } /// Test that template extraction doesn't fail with unusual project names #[test] fn test_unusual_project_names() -> Result<()> { let mgr = TemplateManager::new()?; let test_names = vec![ "robot-2024", "team_1234", "FTC_Bot", "my-awesome-bot", ]; for name in test_names { let temp_dir = TempDir::new()?; let project_dir = temp_dir.path().join(name); fs::create_dir(&project_dir)?; let context = test_context(name); let result = mgr.extract_template("basic", &project_dir, &context); assert!(result.is_ok(), "Should handle project name: {}", name); assert!(project_dir.join("README.md").exists(), "README should exist for {}", name); } Ok(()) }