use std::path::PathBuf; /// Integration tests for the full Detection + Completion workflow /// /// Tests the complete Infrastructure-from-Code pipeline: /// 1. Project technology detection /// 2. Infrastructure requirement inference /// 3. Gap analysis /// 4. Declaration completion use provisioning_detector::{Completer, GapAnalyzer, StackDetector, Technology}; use tempfile::TempDir; /// Helper function to create a Node.js + Express project structure fn create_nodejs_express_project() -> (TempDir, PathBuf) { let temp_dir = TempDir::new().expect("Failed to create temp dir"); let project_path = temp_dir.path().to_path_buf(); // Create package.json with Express dependency let package_json = r#"{ "name": "express-app", "version": "1.0.0", "dependencies": { "express": "^4.18.0", "redis": "^4.0.0", "pg": "^8.8.0" } }"#; std::fs::write(project_path.join("package.json"), package_json) .expect("Failed to write package.json"); // Create .nvmrc std::fs::write(project_path.join(".nvmrc"), "18.0.0").expect("Failed to write .nvmrc"); // Create Dockerfile std::fs::write( project_path.join("Dockerfile"), "FROM node:18\nWORKDIR /app\nCOPY . .\nRUN npm install\nCMD [\"npm\", \"start\"]", ) .expect("Failed to write Dockerfile"); // Create source directory std::fs::create_dir_all(project_path.join("src")).expect("Failed to create src dir"); std::fs::write( project_path.join("src/index.js"), "const express = require('express');\nconst app = express();\napp.listen(3000);", ) .expect("Failed to write index.js"); (temp_dir, project_path) } /// Helper function to create a Python + Django project structure fn create_python_django_project() -> (TempDir, PathBuf) { let temp_dir = TempDir::new().expect("Failed to create temp dir"); let project_path = temp_dir.path().to_path_buf(); // Create requirements.txt let requirements = "django==4.2\npsycopg2-binary==2.9\ncelery==5.3\n"; std::fs::write(project_path.join("requirements.txt"), requirements) .expect("Failed to write requirements.txt"); // Create manage.py std::fs::write( project_path.join("manage.py"), "#!/usr/bin/env python\nimport django", ) .expect("Failed to write manage.py"); // Create migrations directory std::fs::create_dir_all(project_path.join("migrations")).expect("Failed to create migrations"); std::fs::write( project_path.join("migrations/0001_initial.py"), "# Django migration", ) .expect("Failed to write migration"); (temp_dir, project_path) } #[test] fn test_detect_nodejs_express_project() { let (_temp_dir, project_path) = create_nodejs_express_project(); // Run detection let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Verify Node.js was detected from package.json and .nvmrc assert!( !analysis.detections.is_empty(), "Should detect at least one technology" ); assert!( analysis .detections .iter() .any(|d| d.technology == Technology::NodeJs), "Should detect Node.js technology" ); // Verify overall confidence is calculated assert!(analysis.overall_confidence >= 0.0); // Should have some inferred requirements println!( "Requirements: {:?}", analysis .requirements .iter() .map(|r| &r.taskserv) .collect::>() ); // Requirements inference depends on the inference engine // At minimum, technology detection should succeed assert!(!analysis.detections.is_empty()); } #[test] fn test_detect_python_django_project() { let (_temp_dir, project_path) = create_python_django_project(); // Run detection let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Verify Python was detected from requirements.txt, manage.py, or migrations let detections = analysis .detections .iter() .map(|d| d.technology) .collect::>(); println!("Detections: {:?}", detections); // Should detect Python from requirements.txt or manage.py assert!( detections.contains(&Technology::Python), "Should detect Python: {:?}", detections ); println!( "Requirements: {:?}", analysis .requirements .iter() .map(|r| &r.taskserv) .collect::>() ); } #[test] fn test_gap_analysis_with_empty_declaration() { let (_temp_dir, project_path) = create_nodejs_express_project(); // Run detection let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Extract required taskservs let required_taskservs: Vec = analysis .requirements .iter() .filter(|r| r.required) .map(|r| r.taskserv.clone()) .collect(); // Run gap analysis (assuming empty declaration - no current taskservs) let gaps = GapAnalyzer::analyze(&analysis, &required_taskservs); // Should calculate completeness based on required taskservs println!("Completeness: {:.1}%", gaps.completeness * 100.0); println!("Gaps: {}", gaps.gaps.len()); println!("Required taskservs: {}", required_taskservs.len()); // If there are no required taskservs, completeness will be 100% // If there are required taskservs but none detected, completeness will be 0% assert!(gaps.completeness >= 0.0 && gaps.completeness <= 1.0); } #[test] fn test_complete_declaration_workflow() { let (_temp_dir, project_path) = create_nodejs_express_project(); // Step 1: Detect let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Step 2: Plan completion let completer = Completer::new(); let completion_result = completer.complete(&analysis, Vec::new()); // Verify completion plan assert!( completion_result.changes_needed > 0, "Should identify changes needed" ); println!("Changes needed: {}", completion_result.changes_needed); println!("Is safe: {}", completion_result.is_safe); println!("Summary: {}", completion_result.change_summary); // Adding taskservs without removals is safe assert!(completion_result.is_safe); } #[test] fn test_multiple_technologies_detection() { let temp_dir = TempDir::new().expect("Failed to create temp dir"); let project_path = temp_dir.path().to_path_buf(); // Create a project with multiple indicators // Node.js + Rust combo (monorepo style) let package_json = r#"{ "name": "full-stack-app", "dependencies": { "express": "^4.18.0", "postgresql": "^14.0" } }"#; std::fs::write(project_path.join("package.json"), package_json) .expect("Failed to write package.json"); // Create Cargo.toml for Rust component let cargo_toml = r#"[package] name = "backend-service" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } "#; std::fs::create_dir_all(project_path.join("backend")).expect("Failed to create backend"); std::fs::write(project_path.join("backend/Cargo.toml"), cargo_toml) .expect("Failed to write Cargo.toml"); // Run detection let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Should detect both Node.js and Rust let detections = analysis .detections .iter() .map(|d| d.technology) .collect::>(); println!("Detected: {:?}", detections); assert!(detections.contains(&Technology::NodeJs) || detections.contains(&Technology::Rust)); } #[test] fn test_confidence_scoring() { let (_temp_dir, project_path) = create_nodejs_express_project(); // Run detection let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Each detection should have a confidence score for detection in &analysis.detections { assert!(detection.confidence >= 0.0 && detection.confidence <= 1.0); assert!(!detection.evidence.is_empty(), "Should have evidence"); } // Overall confidence should be calculated assert!(analysis.overall_confidence >= 0.0 && analysis.overall_confidence <= 1.0); } #[test] fn test_requirement_classification() { let (_temp_dir, project_path) = create_nodejs_express_project(); // Run detection let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Separate requirements by classification let required: Vec<_> = analysis .requirements .iter() .filter(|r| r.required) .collect(); let optional: Vec<_> = analysis .requirements .iter() .filter(|r| !r.required) .collect(); println!("Required: {}", required.len()); println!("Optional: {}", optional.len()); // Should have at least some inferred requirements assert!(!analysis.requirements.is_empty()); // Requirements should be classified as either required or optional assert_eq!( required.len() + optional.len(), analysis.requirements.len(), "All requirements should be classified" ); } #[test] fn test_requirement_inference_logic() { let temp_dir = TempDir::new().expect("Failed to create temp dir"); let project_path = temp_dir.path().to_path_buf(); // Create a project with specific inference triggers let package_json = r#"{ "name": "test-app", "dependencies": { "express": "^4.18.0", "redis": "^4.0.0" } }"#; std::fs::write(project_path.join("package.json"), package_json) .expect("Failed to write package.json"); // Run detection and inference let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Verify inference rules were applied: // Express should trigger Nginx recommendation let has_express = analysis .detections .iter() .any(|d| d.technology == Technology::Express); if has_express { // Could have inferred nginx let services: Vec<_> = analysis .requirements .iter() .map(|r| r.taskserv.as_str()) .collect(); println!("Inferred services: {:?}", services); } } #[test] fn test_detection_with_nonexistent_path() { let detector = StackDetector::new(); let nonexistent = PathBuf::from("/nonexistent/path"); // Detection should complete even if path doesn't exist // but will have no detections match detector.detect_all(&nonexistent) { Ok(analysis) => { // May succeed with empty detections println!("Detections: {}", analysis.detections.len()); } Err(_e) => { // Or may fail, both are acceptable println!("Path detection failed as expected"); } } } #[test] fn test_empty_project_detection() { let temp_dir = TempDir::new().expect("Failed to create temp dir"); let project_path = temp_dir.path().to_path_buf(); // Empty project with no tech indicators let detector = StackDetector::new(); let analysis = detector .detect_all(&project_path) .expect("Detection failed"); // Should have no detections but not crash println!("Detections: {}", analysis.detections.len()); println!("Requirements: {}", analysis.requirements.len()); // Empty projects should result in low or zero confidence assert!(analysis.overall_confidence == 0.0 || !analysis.detections.is_empty()); }