prvng_platform/crates/detector/tests/integration_tests.rs
Jesús Pérez 09a97ac8f5
chore: update platform submodule to monorepo crates structure
Platform restructured into crates/, added AI service and detector,
       migrated control-center-ui to Leptos 0.8
2026-01-08 21:32:59 +00:00

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());
}