Platform restructured into crates/, added AI service and detector,
migrated control-center-ui to Leptos 0.8
394 lines
12 KiB
Rust
394 lines
12 KiB
Rust
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::<Vec<_>>()
|
|
);
|
|
|
|
// 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::<Vec<_>>();
|
|
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::<Vec<_>>()
|
|
);
|
|
}
|
|
|
|
#[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<String> = 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::<Vec<_>>();
|
|
|
|
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());
|
|
}
|