// File: tests/integration/project_lifecycle_tests.rs // Integration tests - full project lifecycle // // Same strategy as environment_tests: WEEVIL_HOME points to a TempDir, // mock SDKs are created manually with fs, and we invoke the compiled // binary directly rather than going through `cargo run`. use tempfile::TempDir; use std::fs; use std::process::Command; /// Helper: returns a configured Command pointing at the weevil binary with /// WEEVIL_HOME set to the given temp directory. fn weevil_cmd(weevil_home: &TempDir) -> Command { let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("weevil")); cmd.env("WEEVIL_HOME", weevil_home.path()); cmd } /// Helper: create a minimal mock FTC SDK at the given path. fn create_mock_ftc_sdk(path: &std::path::Path) { fs::create_dir_all(path.join("TeamCode/src/main/java")).unwrap(); fs::create_dir_all(path.join("FtcRobotController")).unwrap(); fs::write(path.join("build.gradle"), "// mock").unwrap(); fs::write(path.join(".version"), "v10.1.1\n").unwrap(); } /// Helper: create a minimal mock Android SDK at the given path. fn create_mock_android_sdk(path: &std::path::Path) { fs::create_dir_all(path.join("platform-tools")).unwrap(); fs::write(path.join("platform-tools/adb"), "").unwrap(); } /// Helper: populate a WEEVIL_HOME with both mock SDKs (fully healthy system) fn populate_healthy(weevil_home: &TempDir) { create_mock_ftc_sdk(&weevil_home.path().join("ftc-sdk")); create_mock_android_sdk(&weevil_home.path().join("android-sdk")); } /// Helper: print labeled output from a test so it's visually distinct from test assertions fn print_output(test_name: &str, output: &std::process::Output) { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); println!("\n╔══ {} ══════════════════════════════════════════════╗", test_name); if !stdout.is_empty() { println!("║ stdout:"); for line in stdout.lines() { println!("║ {}", line); } } if !stderr.is_empty() { println!("║ stderr:"); for line in stderr.lines() { println!("║ {}", line); } } println!("╚════════════════════════════════════════════════════════╝\n"); } #[test] fn test_project_creation_with_mock_sdk() { let home = TempDir::new().unwrap(); populate_healthy(&home); let output = weevil_cmd(&home) .arg("new") .arg("test-robot") .current_dir(home.path()) .output() .expect("Failed to run weevil new"); print_output("test_project_creation_with_mock_sdk", &output); let project_dir = home.path().join("test-robot"); assert!(output.status.success(), "weevil new failed"); assert!(project_dir.join(".weevil.toml").exists(), ".weevil.toml missing"); assert!(project_dir.join("build.gradle.kts").exists(), "build.gradle.kts missing"); assert!(project_dir.join("src/main/java/robot").exists(), "src/main/java/robot missing"); } #[test] fn test_project_config_persistence() { let home = TempDir::new().unwrap(); populate_healthy(&home); let output = weevil_cmd(&home) .arg("new") .arg("config-test") .current_dir(home.path()) .output() .expect("Failed to run weevil new"); print_output("test_project_config_persistence", &output); assert!(output.status.success(), "weevil new failed"); let project_dir = home.path().join("config-test"); let config_content = fs::read_to_string(project_dir.join(".weevil.toml")) .expect(".weevil.toml not found"); assert!(config_content.contains("project_name = \"config-test\""), "project_name missing from config:\n{}", config_content); assert!(config_content.contains("ftc_sdk_path"), "ftc_sdk_path missing from config:\n{}", config_content); } #[test] fn test_project_upgrade_preserves_code() { let home = TempDir::new().unwrap(); populate_healthy(&home); // Create project let output = weevil_cmd(&home) .arg("new") .arg("upgrade-test") .current_dir(home.path()) .output() .expect("Failed to run weevil new"); print_output("test_project_upgrade_preserves_code (new)", &output); assert!(output.status.success(), "weevil new failed"); let project_dir = home.path().join("upgrade-test"); // Add custom code let custom_file = project_dir.join("src/main/java/robot/CustomCode.java"); fs::write(&custom_file, "// My custom robot code").unwrap(); // Upgrade let output = weevil_cmd(&home) .arg("upgrade") .arg(project_dir.to_str().unwrap()) .output() .expect("Failed to run weevil upgrade"); print_output("test_project_upgrade_preserves_code (upgrade)", &output); // Custom code survives assert!(custom_file.exists(), "custom code file was deleted by upgrade"); let content = fs::read_to_string(&custom_file).unwrap(); assert!(content.contains("My custom robot code"), "custom code was overwritten"); // Config still present assert!(project_dir.join(".weevil.toml").exists(), ".weevil.toml missing after upgrade"); } #[test] fn test_build_scripts_read_from_config() { let home = TempDir::new().unwrap(); populate_healthy(&home); let output = weevil_cmd(&home) .arg("new") .arg("build-test") .current_dir(home.path()) .output() .expect("Failed to run weevil new"); print_output("test_build_scripts_read_from_config", &output); assert!(output.status.success(), "weevil new failed"); let project_dir = home.path().join("build-test"); let build_sh = fs::read_to_string(project_dir.join("build.sh")) .expect("build.sh not found"); assert!(build_sh.contains(".weevil.toml"), "build.sh doesn't reference .weevil.toml"); assert!(build_sh.contains("ftc_sdk_path"), "build.sh doesn't reference ftc_sdk_path"); let build_bat = fs::read_to_string(project_dir.join("build.bat")) .expect("build.bat not found"); assert!(build_bat.contains(".weevil.toml"), "build.bat doesn't reference .weevil.toml"); assert!(build_bat.contains("ftc_sdk_path"), "build.bat doesn't reference ftc_sdk_path"); } #[test] fn test_config_command_show() { let home = TempDir::new().unwrap(); populate_healthy(&home); // Create project let output = weevil_cmd(&home) .arg("new") .arg("config-show-test") .current_dir(home.path()) .output() .expect("Failed to run weevil new"); print_output("test_config_command_show (new)", &output); assert!(output.status.success(), "weevil new failed"); let project_dir = home.path().join("config-show-test"); // Show config let output = weevil_cmd(&home) .arg("config") .arg(project_dir.to_str().unwrap()) .output() .expect("Failed to run weevil config"); print_output("test_config_command_show (config)", &output); let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("config-show-test"), "project name missing from config output"); } #[test] fn test_multiple_projects_different_sdks() { let home = TempDir::new().unwrap(); populate_healthy(&home); // Create a second FTC SDK with a different version let sdk2 = home.path().join("ftc-sdk-v11"); create_mock_ftc_sdk(&sdk2); fs::write(sdk2.join(".version"), "v11.0.0\n").unwrap(); // Create first project (uses default ftc-sdk in WEEVIL_HOME) let output = weevil_cmd(&home) .arg("new") .arg("robot1") .current_dir(home.path()) .output() .expect("Failed to create robot1"); print_output("test_multiple_projects_different_sdks (robot1)", &output); assert!(output.status.success(), "weevil new robot1 failed"); // Create second project — would need --ftc-sdk flag if supported, // otherwise both use the same default. Verify they each have valid configs. let output = weevil_cmd(&home) .arg("new") .arg("robot2") .current_dir(home.path()) .output() .expect("Failed to create robot2"); print_output("test_multiple_projects_different_sdks (robot2)", &output); assert!(output.status.success(), "weevil new robot2 failed"); let config1 = fs::read_to_string(home.path().join("robot1/.weevil.toml")) .expect("robot1 .weevil.toml missing"); let config2 = fs::read_to_string(home.path().join("robot2/.weevil.toml")) .expect("robot2 .weevil.toml missing"); assert!(config1.contains("project_name = \"robot1\""), "robot1 config wrong"); assert!(config2.contains("project_name = \"robot2\""), "robot2 config wrong"); assert!(config1.contains("ftc_sdk_path"), "robot1 missing ftc_sdk_path"); assert!(config2.contains("ftc_sdk_path"), "robot2 missing ftc_sdk_path"); }