Content Defense (Phase 7): - Add SimilarityIndex with MinHash/LSH for near-duplicate detection - Add QuarantineStore for flagged assertions awaiting admin review - Add CircuitBreakerStore for per-agent circuit breaker state - Add ContentDefenseLayer for ingestion pipeline integration - Add API endpoints for quarantine and circuit breaker management - Add research module with gap detection and documentation fetching Code Structure Improvements: - Extract research CLI commands to research_commands.rs - Extract API routers to routers.rs module - Extract key_codec extraction functions to separate module - Extract test modules to separate files across multiple crates - All files now under 500 line limit per pre-commit hook Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
306 lines
9.9 KiB
Rust
306 lines
9.9 KiB
Rust
//! 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<Client, Error> {
|
|
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::<Vec<_>>()
|
|
);
|
|
}
|
|
|
|
#[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<Claims, Error> {
|
|
let mut validation = Validation::default();
|
|
validation.validate_aud = false; // Disabled!
|
|
let token_data = decode::<Claims>(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::<Vec<_>>()
|
|
);
|
|
}
|
|
|
|
#[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::<Vec<_>>()
|
|
);
|
|
}
|