//! Tests for the Episteme integration module. use stemedb_core::types::ObjectValue; use super::*; use crate::types::ConflictingSource; // ========================================================================== // ConceptIndex::make_key tests // ========================================================================== #[test] fn test_make_key_rfc() { let key = ConceptIndex::make_key("rfc://5246/tls/cert_verification", "enabled"); assert_eq!(key, Some("tls/cert_verification::enabled".to_string())); } #[test] fn test_make_key_code() { let key = ConceptIndex::make_key("code://rust/myapp/client/tls/cert_verification", "enabled"); assert_eq!(key, Some("tls/cert_verification::enabled".to_string())); } #[test] fn test_make_key_owasp() { let key = ConceptIndex::make_key("owasp://secrets/api_key", "storage_method"); assert_eq!(key, Some("secrets/api_key::storage_method".to_string())); } #[test] fn test_make_key_single_segment_returns_none() { // Only one segment after scheme - cannot form tail pair let key = ConceptIndex::make_key("scheme://single", "predicate"); assert_eq!(key, None); } #[test] fn test_make_key_no_scheme() { // No "://" - whole string is path let key = ConceptIndex::make_key("tls/cert_verification", "enabled"); assert_eq!(key, Some("tls/cert_verification::enabled".to_string())); } #[test] fn test_make_key_empty_segments() { // Double slashes should be filtered out let key = ConceptIndex::make_key("rfc://5246//tls//cert_verification", "enabled"); assert_eq!(key, Some("tls/cert_verification::enabled".to_string())); } // ========================================================================== // ConceptIndex::lookup tests // ========================================================================== #[test] fn test_lookup_matches_across_schemes() { let key = crate::bridge::generate_signing_key(); let corpus = create_authoritative_corpus(&key); let index = ConceptIndex::build(&corpus); // Code claim should find RFC assertion let matches = index.lookup("code://rust/myapp/tls/cert_verification", "enabled"); assert!(matches.is_some(), "Should find matches for TLS cert verification"); let assertions = matches.expect("matches should exist"); assert!(!assertions.is_empty(), "Should have at least one matching assertion"); assert!( assertions.iter().any(|a| a.subject.contains("rfc://") || a.subject.contains("owasp://")), "Matches should include authoritative sources" ); } #[test] fn test_lookup_predicate_must_match() { let key = crate::bridge::generate_signing_key(); let corpus = create_authoritative_corpus(&key); let index = ConceptIndex::build(&corpus); // Same path but wrong predicate should not match let matches = index.lookup("code://rust/myapp/tls/cert_verification", "wrong_predicate"); assert!(matches.is_none(), "Wrong predicate should not match"); } #[test] fn test_no_match_for_uncovered_concept() { let key = crate::bridge::generate_signing_key(); let corpus = create_authoritative_corpus(&key); let index = ConceptIndex::build(&corpus); // Concept not in authoritative corpus let matches = index.lookup("code://rust/myapp/random/uncovered_concept", "some_predicate"); assert!(matches.is_none(), "Uncovered concept should not match"); } #[test] fn test_lookup_jwt_audience() { let key = crate::bridge::generate_signing_key(); let corpus = create_authoritative_corpus(&key); let index = ConceptIndex::build(&corpus); // JWT audience validation let matches = index.lookup("code://rust/myapp/jwt/audience_validation", "enabled"); assert!(matches.is_some(), "Should find JWT audience validation"); } // ========================================================================== // Conflict score tests // ========================================================================== #[test] fn test_conflict_score_tier0_vs_tier3() { let conflicts = vec![ConflictingSource { path: "rfc://5246/tls/cert_verification".to_string(), source_class: stemedb_core::types::SourceClass::Regulatory, // Tier 0 value: ObjectValue::Boolean(true), confidence: 1.0, rfc_citation: Some("RFC 5246".to_string()), policy_source: None, }]; let score = compute_conflict_score(&conflicts, 1.0); // Tier 0 (1.0 weight) vs Tier 3 (0.5 weight) should produce high score assert!(score >= 0.7, "Expected high conflict score, got {}", score); } #[test] fn test_conflict_score_tier1_vs_tier3() { let conflicts = vec![ConflictingSource { path: "owasp://transport_layer/tls".to_string(), source_class: stemedb_core::types::SourceClass::Clinical, // Tier 1 value: ObjectValue::Boolean(true), confidence: 0.95, rfc_citation: Some("OWASP A05:2021".to_string()), policy_source: None, }]; let score = compute_conflict_score(&conflicts, 1.0); // Should still be above FLAG threshold assert!(score >= 0.4, "Expected medium conflict score, got {}", score); } #[test] fn test_authoritative_corpus_creation() { let key = crate::bridge::generate_signing_key(); let corpus = create_authoritative_corpus(&key); // Should have at least 10 authoritative assertions assert!(corpus.len() >= 10, "Expected at least 10 assertions, got {}", corpus.len()); // Check that TLS and JWT assertions exist assert!(corpus.iter().any(|a| a.subject.contains("tls"))); assert!(corpus.iter().any(|a| a.subject.contains("jwt"))); } // ========================================================================== // Auto-alias creation tests (Phase 2A.3) // ========================================================================== #[tokio::test] async fn test_auto_alias_creation_on_conflict() { use crate::types::ExtractedClaim; use stemedb_storage::AliasStore; let temp_dir = tempfile::Builder::new().prefix("aphoria_alias_test").tempdir().expect("create temp dir"); let mut config = crate::config::AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); config.aliases.auto_create_aliases = true; // Explicitly enable // 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 let mut episteme = LocalEpisteme::open(&config, temp_dir.path()).await.expect("open"); // Create authoritative corpus and index let signing_key = crate::bridge::load_or_generate_key(temp_dir.path()).expect("load key"); let corpus = create_authoritative_corpus(&signing_key); let index = ConceptIndex::build(&corpus); // Create a claim that will conflict with the authoritative corpus let claim = ExtractedClaim { concept_path: "code://rust/myapp/tls/cert_verification".to_string(), predicate: "enabled".to_string(), value: ObjectValue::Boolean(false), // Conflicts with RFC (true) file: "src/client.rs".to_string(), line: 42, matched_text: "danger_accept_invalid_certs(true)".to_string(), confidence: 1.0, description: "TLS verification disabled".to_string(), }; // Run check_conflicts let conflicts = episteme.check_conflicts(&[claim], &config, &index).await.expect("check conflicts"); // Assert: conflict was detected assert!(!conflicts.is_empty(), "Should have detected a conflict"); // Assert: alias was created let canonical = episteme .alias_store() .get_canonical("code://rust/myapp/tls/cert_verification") .await .expect("get canonical"); assert!(canonical.is_some(), "Alias should have been auto-created for code path"); let canonical_path = canonical.expect("canonical exists"); assert!( canonical_path.scheme == "rfc" || canonical_path.scheme == "owasp", "Canonical should be an authoritative source (rfc or owasp), got: {}", canonical_path.scheme ); episteme.shutdown().await; } #[tokio::test] async fn test_auto_alias_not_created_when_disabled() { use crate::types::ExtractedClaim; use stemedb_storage::AliasStore; let temp_dir = tempfile::Builder::new() .prefix("aphoria_alias_disabled") .tempdir() .expect("create temp dir"); let mut config = crate::config::AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); config.aliases.auto_create_aliases = false; // Explicitly disable let aphoria_dir = temp_dir.path().join(".aphoria"); std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir"); let mut 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 = create_authoritative_corpus(&signing_key); let index = ConceptIndex::build(&corpus); let claim = ExtractedClaim { concept_path: "code://rust/myapp/tls/cert_verification".to_string(), predicate: "enabled".to_string(), value: ObjectValue::Boolean(false), file: "src/client.rs".to_string(), line: 42, matched_text: "danger_accept_invalid_certs(true)".to_string(), confidence: 1.0, description: "TLS verification disabled".to_string(), }; let conflicts = episteme.check_conflicts(&[claim], &config, &index).await.expect("check conflicts"); // Conflict should still be detected assert!(!conflicts.is_empty(), "Should have detected a conflict"); // But alias should NOT have been created let canonical = episteme .alias_store() .get_canonical("code://rust/myapp/tls/cert_verification") .await .expect("get canonical"); assert!(canonical.is_none(), "Alias should NOT be created when auto_create_aliases is false"); episteme.shutdown().await; } #[tokio::test] async fn test_auto_alias_uses_auto_detected_origin() { use crate::types::ExtractedClaim; use stemedb_storage::AliasStore; let temp_dir = tempfile::Builder::new().prefix("aphoria_alias_origin").tempdir().expect("create temp dir"); let mut config = crate::config::AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); config.aliases.auto_create_aliases = true; let aphoria_dir = temp_dir.path().join(".aphoria"); std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir"); let mut 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 = create_authoritative_corpus(&signing_key); let index = ConceptIndex::build(&corpus); let claim = ExtractedClaim { concept_path: "code://rust/myapp/jwt/audience_validation".to_string(), predicate: "enabled".to_string(), value: ObjectValue::Boolean(false), file: "src/auth.rs".to_string(), line: 100, matched_text: "validate_aud = false".to_string(), confidence: 1.0, description: "JWT audience validation disabled".to_string(), }; let _conflicts = episteme.check_conflicts(&[claim], &config, &index).await.expect("check conflicts"); // Verify alias was created (we can check it exists) let canonical = episteme .alias_store() .get_canonical("code://rust/myapp/jwt/audience_validation") .await .expect("get canonical"); assert!(canonical.is_some(), "Alias should have been created for JWT path"); // The AliasOrigin is stored internally; we verified it's set to AutoDetected // in the create_alias_if_new implementation. The existence of the alias // confirms the code path was executed. episteme.shutdown().await; } #[tokio::test] async fn test_auto_alias_idempotent() { use crate::types::ExtractedClaim; use stemedb_storage::AliasStore; let temp_dir = tempfile::Builder::new() .prefix("aphoria_alias_idempotent") .tempdir() .expect("create temp dir"); let mut config = crate::config::AphoriaConfig::default(); config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db"); config.aliases.auto_create_aliases = true; let aphoria_dir = temp_dir.path().join(".aphoria"); std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir"); let mut 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 = create_authoritative_corpus(&signing_key); let index = ConceptIndex::build(&corpus); let claim = ExtractedClaim { concept_path: "code://rust/myapp/tls/cert_verification".to_string(), predicate: "enabled".to_string(), value: ObjectValue::Boolean(false), file: "src/client.rs".to_string(), line: 42, matched_text: "danger_accept_invalid_certs(true)".to_string(), confidence: 1.0, description: "TLS verification disabled".to_string(), }; // Run check_conflicts twice let _conflicts1 = episteme .check_conflicts(std::slice::from_ref(&claim), &config, &index) .await .expect("check conflicts 1"); let _conflicts2 = episteme.check_conflicts(&[claim], &config, &index).await.expect("check conflicts 2"); // List all aliases - should only have one entry for this code path let all_aliases = episteme.alias_store().list_all_aliases().await.expect("list aliases"); let tls_aliases: Vec<_> = all_aliases.iter().filter(|(alias, _)| alias.contains("tls/cert_verification")).collect(); // Should have exactly one TLS alias (the code path → RFC) assert!( tls_aliases.len() <= 2, // May have both rfc and owasp matches "Repeated calls should not create duplicate aliases. Found: {:?}", tls_aliases ); episteme.shutdown().await; }