//! Integration tests for Aphoria scan functionality. use super::*; #[tokio::test] async fn test_scan_returns_result() { let temp_dir = tempfile::tempdir().expect("create temp dir"); // Create a test file with a TLS issue let src_dir = temp_dir.path().join("src"); std::fs::create_dir_all(&src_dir).expect("create src dir"); std::fs::write( src_dir.join("client.rs"), r#" let client = reqwest::Client::builder() .danger_accept_invalid_certs(true) .build()?; "#, ) .expect("write file"); // Create Cargo.toml so it's detected as a Rust project std::fs::write( temp_dir.path().join("Cargo.toml"), r#" [package] name = "testproject" version = "0.1.0" "#, ) .expect("write cargo.toml"); let args = ScanArgs { path: temp_dir.path().to_path_buf(), format: "table".to_string(), exit_code_enabled: false, }; let mut config = AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); let result = run_scan(args, &config).await.expect("scan should succeed"); assert!(result.files_scanned > 0); assert!(result.claims_extracted > 0); } #[tokio::test] async fn test_initialize_creates_corpus() { // Use a unique temp dir to avoid conflicts with parallel tests let temp_dir = tempfile::Builder::new().prefix("aphoria_test_init").tempdir().expect("create temp dir"); let mut config = AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); // Create .aphoria directory for the agent key let aphoria_dir = temp_dir.path().join(".aphoria"); std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir"); // Open LocalEpisteme directly instead of using initialize() // which relies on current_dir() let mut episteme = crate::episteme::LocalEpisteme::open(&config, temp_dir.path()).await.expect("open"); let signing_key = crate::bridge::load_or_generate_key(temp_dir.path()).expect("load key"); let corpus = crate::episteme::create_authoritative_corpus(&signing_key); let ingested = episteme.ingest_authoritative(&corpus).await.expect("ingest"); episteme.shutdown().await; assert!(ingested > 0); assert!(config.episteme.data_dir.exists()); assert!(temp_dir.path().join(".aphoria").join("agent.key").exists()); } #[tokio::test] async fn test_acknowledge_succeeds() { let temp_dir = tempfile::Builder::new().prefix("aphoria_test_ack").tempdir().expect("create temp dir"); let mut config = AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); // Create .aphoria directory for the agent key let aphoria_dir = temp_dir.path().join(".aphoria"); std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir"); // Open LocalEpisteme and ingest an acknowledgement claim let mut episteme = crate::episteme::LocalEpisteme::open(&config, temp_dir.path()).await.expect("open"); let claim = ExtractedClaim { concept_path: "code://rust/test/jwt/audience_validation".to_string(), predicate: "acknowledged".to_string(), value: stemedb_core::types::ObjectValue::Text("Internal service".to_string()), file: "aphoria_ack".to_string(), line: 0, matched_text: "Acknowledged: Internal service".to_string(), confidence: 1.0, description: "Conflict acknowledged: Internal service".to_string(), }; let result = episteme.ingest_claims(&[claim]).await; episteme.shutdown().await; assert!(result.is_ok()); } #[tokio::test] async fn test_status_before_init() { let temp_dir = tempfile::Builder::new().prefix("aphoria_test_status").tempdir().expect("create temp dir"); let mut config = AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join("nonexistent"); // Manually check status logic without relying on current_dir() let data_dir = &config.episteme.data_dir; let status = if !data_dir.exists() { "Not initialized" } else { "Initialized" }; assert!(status.contains("Not initialized")); } // ========================================================================== // Integration tests for conflict detection (Phase 2A) // ========================================================================== #[tokio::test] async fn test_conflict_detection_tls_disabled() { // Create temp project with danger_accept_invalid_certs(true) let temp_dir = tempfile::Builder::new().prefix("aphoria_tls_conflict").tempdir().expect("create temp dir"); let src_dir = temp_dir.path().join("src"); std::fs::create_dir_all(&src_dir).expect("create src dir"); // Write a Rust file with TLS verification disabled std::fs::write( src_dir.join("client.rs"), r#" fn create_client() -> Result { let client = reqwest::Client::builder() .danger_accept_invalid_certs(true) .build()?; Ok(client) } "#, ) .expect("write file"); // Create Cargo.toml so it's detected as a Rust project std::fs::write( temp_dir.path().join("Cargo.toml"), r#" [package] name = "testproject" version = "0.1.0" "#, ) .expect("write cargo.toml"); let args = ScanArgs { path: temp_dir.path().to_path_buf(), format: "table".to_string(), exit_code_enabled: true, }; let mut config = AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); let result = run_scan(args, &config).await.expect("scan should succeed"); // Assert: conflicts not empty, has_blocks() == true assert!( !result.conflicts.is_empty(), "Should detect conflicts for TLS verification disabled. \ Claims extracted: {}, Files scanned: {}", result.claims_extracted, result.files_scanned ); assert!( result.has_blocks(), "TLS verification disabled should be a BLOCK verdict. \ Conflicts: {:?}", result.conflicts.iter().map(|c| (&c.claim.concept_path, &c.verdict)).collect::>() ); } #[tokio::test] async fn test_conflict_detection_jwt_audience_disabled() { // Create temp project with JWT audience validation disabled let temp_dir = tempfile::Builder::new().prefix("aphoria_jwt_conflict").tempdir().expect("create temp dir"); let src_dir = temp_dir.path().join("src"); std::fs::create_dir_all(&src_dir).expect("create src dir"); // Write a Rust file with JWT audience validation disabled std::fs::write( src_dir.join("auth.rs"), r#" fn validate_token(token: &str) -> Result { let mut validation = Validation::default(); validation.validate_aud = false; // Disabled! let token_data = decode::(token, &key, &validation)?; Ok(token_data.claims) } "#, ) .expect("write file"); // Create Cargo.toml std::fs::write( temp_dir.path().join("Cargo.toml"), r#" [package] name = "testproject" version = "0.1.0" "#, ) .expect("write cargo.toml"); let args = ScanArgs { path: temp_dir.path().to_path_buf(), format: "table".to_string(), exit_code_enabled: true, }; let mut config = AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); let result = run_scan(args, &config).await.expect("scan should succeed"); // Assert: conflicts not empty for JWT audience validation assert!( !result.conflicts.is_empty(), "Should detect conflicts for JWT audience validation disabled. \ Claims extracted: {}, Files scanned: {}", result.claims_extracted, result.files_scanned ); // Check that at least one conflict is about JWT audience let has_jwt_conflict = result .conflicts .iter() .any(|c| c.claim.concept_path.contains("jwt") && c.claim.concept_path.contains("audience")); assert!( has_jwt_conflict, "Should have a conflict about JWT audience validation. \ Conflicts: {:?}", result.conflicts.iter().map(|c| &c.claim.concept_path).collect::>() ); } #[tokio::test] async fn test_no_conflicts_when_compliant() { // Create temp project with compliant code (no dangerous patterns) let temp_dir = tempfile::Builder::new().prefix("aphoria_compliant").tempdir().expect("create temp dir"); let src_dir = temp_dir.path().join("src"); std::fs::create_dir_all(&src_dir).expect("create src dir"); // Write a Rust file with compliant code std::fs::write( src_dir.join("main.rs"), r#" fn main() { println!("Hello, world!"); } "#, ) .expect("write file"); // Create Cargo.toml std::fs::write( temp_dir.path().join("Cargo.toml"), r#" [package] name = "testproject" version = "0.1.0" "#, ) .expect("write cargo.toml"); let args = ScanArgs { path: temp_dir.path().to_path_buf(), format: "table".to_string(), exit_code_enabled: true, }; let mut config = AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); let result = run_scan(args, &config).await.expect("scan should succeed"); // No dangerous patterns = no claims = no conflicts assert!( result.conflicts.is_empty(), "Compliant code should have no conflicts. Found: {:?}", result.conflicts.iter().map(|c| &c.claim.concept_path).collect::>() ); }