This commit includes comprehensive work on Phase 6 features: ## Admission Control (Phase 6 admission middleware) - AdmissionStore implementation backed by TrustRankStore - PoW verification with tier-based difficulty computation - Trust tier progression (Newcomer → Established → Trusted → Authority) - API integration with admission status endpoints ## HLC Recency Lens (Phase 6C) - HlcRecencyLens for distributed system ordering - Hybrid logical clock integration with causality preservation ## Cluster Coordination (Phase 6C) - Multi-node cluster tests (availability, partition tolerance) - CRDT convergence tests for anti-entropy sync - Gateway handler improvements ## Aphoria Code Linter (Phase 2A) - RFC/OWASP corpus builders with network fetching and caching - Concept hierarchy with auto-alias creation on conflict detection - Multiple security extractors (TLS, JWT, CORS, secrets, rate limiting) ## Code Organization - Split large files into modules to comply with 500-line limit - Improved test organization with separate test modules - Fixed rkyv serialization for EigenTrustState (AgentScore struct) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
255 lines
9.2 KiB
Rust
255 lines
9.2 KiB
Rust
//! Tests for alias resolution in QueryEngine.
|
|
//!
|
|
//! Tests the `resolve_aliases` query flag and `alias_store` integration.
|
|
|
|
use std::sync::Arc;
|
|
use stemedb_core::testing::AssertionBuilder;
|
|
use stemedb_core::types::{AliasOrigin, ConceptAlias, ConceptPath, LifecycleStage};
|
|
use stemedb_storage::{AliasStore, GenericAliasStore, HybridStore};
|
|
|
|
use super::{store_assertion, QueryEngine};
|
|
use crate::query::Query;
|
|
|
|
/// Helper to create a test ConceptAlias.
|
|
fn create_alias(alias: &str, canonical: &str) -> ConceptAlias {
|
|
ConceptAlias::new(
|
|
ConceptPath::parse(alias).expect("valid alias path"),
|
|
ConceptPath::parse(canonical).expect("valid canonical path"),
|
|
[1u8; 32], // agent_id
|
|
1000, // timestamp
|
|
AliasOrigin::Manual,
|
|
)
|
|
}
|
|
|
|
/// Test that resolve_aliases: true expands subject to aliased paths.
|
|
#[tokio::test]
|
|
async fn test_resolve_aliases_expands_subjects() {
|
|
let store = Arc::new(HybridStore::open_temp().expect("store"));
|
|
|
|
// Create assertions for two different subjects (aliased paths)
|
|
let code_assertion = AssertionBuilder::new()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("cert_verification")
|
|
.object_text("enabled")
|
|
.confidence(0.8)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
let rfc_assertion = AssertionBuilder::new()
|
|
.subject("rfc://5246/tls")
|
|
.predicate("cert_verification")
|
|
.object_text("required")
|
|
.confidence(0.95)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
store_assertion(&store, &code_assertion).await;
|
|
store_assertion(&store, &rfc_assertion).await;
|
|
|
|
// Set up alias store with alias: code://rust/myapp/tls -> rfc://5246/tls
|
|
let alias_store = GenericAliasStore::new(store.clone());
|
|
let alias = create_alias("code://rust/myapp/tls", "rfc://5246/tls");
|
|
alias_store.set_alias(&alias).await.expect("set alias");
|
|
|
|
// Create query engine with alias store
|
|
let engine = QueryEngine::new(Arc::new(store.clone()))
|
|
.with_alias_store(Arc::new(alias_store) as Arc<dyn AliasStore>);
|
|
|
|
// Query with resolve_aliases: true should find BOTH assertions
|
|
let query = Query::builder()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("cert_verification")
|
|
.resolve_aliases(true)
|
|
.build();
|
|
|
|
let result = engine.execute(&query).await.expect("execute");
|
|
|
|
assert_eq!(result.assertions.len(), 2, "Should find assertions from both aliased subjects");
|
|
|
|
let subjects: Vec<&str> = result.assertions.iter().map(|a| a.subject.as_str()).collect();
|
|
assert!(subjects.contains(&"code://rust/myapp/tls"), "Should include code assertion");
|
|
assert!(subjects.contains(&"rfc://5246/tls"), "Should include rfc assertion");
|
|
}
|
|
|
|
/// Test that resolve_aliases: false queries exact subject only.
|
|
#[tokio::test]
|
|
async fn test_resolve_aliases_false_is_exact() {
|
|
let store = Arc::new(HybridStore::open_temp().expect("store"));
|
|
|
|
// Create assertions for two different subjects
|
|
let code_assertion = AssertionBuilder::new()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("cert_verification")
|
|
.object_text("enabled")
|
|
.confidence(0.8)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
let rfc_assertion = AssertionBuilder::new()
|
|
.subject("rfc://5246/tls")
|
|
.predicate("cert_verification")
|
|
.object_text("required")
|
|
.confidence(0.95)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
store_assertion(&store, &code_assertion).await;
|
|
store_assertion(&store, &rfc_assertion).await;
|
|
|
|
// Set up alias store with alias
|
|
let alias_store = GenericAliasStore::new(store.clone());
|
|
let alias = create_alias("code://rust/myapp/tls", "rfc://5246/tls");
|
|
alias_store.set_alias(&alias).await.expect("set alias");
|
|
|
|
let engine = QueryEngine::new(Arc::new(store.clone()))
|
|
.with_alias_store(Arc::new(alias_store) as Arc<dyn AliasStore>);
|
|
|
|
// Query with resolve_aliases: false (default) should find only the exact subject
|
|
let query = Query::builder()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("cert_verification")
|
|
.resolve_aliases(false)
|
|
.build();
|
|
|
|
let result = engine.execute(&query).await.expect("execute");
|
|
|
|
assert_eq!(result.assertions.len(), 1, "Should find only exact subject assertion");
|
|
assert_eq!(result.assertions[0].subject, "code://rust/myapp/tls");
|
|
}
|
|
|
|
/// Test that resolve_aliases: true without alias_store gracefully returns exact subject.
|
|
#[tokio::test]
|
|
async fn test_no_alias_store_graceful() {
|
|
let store = Arc::new(HybridStore::open_temp().expect("store"));
|
|
|
|
let assertion = AssertionBuilder::new()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("cert_verification")
|
|
.object_text("enabled")
|
|
.confidence(0.8)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
store_assertion(&store, &assertion).await;
|
|
|
|
// Query engine WITHOUT alias store
|
|
let engine = QueryEngine::new(Arc::new(store));
|
|
|
|
// Query with resolve_aliases: true should still work (graceful degradation)
|
|
let query = Query::builder()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("cert_verification")
|
|
.resolve_aliases(true)
|
|
.build();
|
|
|
|
let result = engine.execute(&query).await.expect("execute");
|
|
|
|
assert_eq!(result.assertions.len(), 1, "Should find exact subject assertion");
|
|
assert_eq!(result.assertions[0].subject, "code://rust/myapp/tls");
|
|
}
|
|
|
|
/// Test that alias resolution deduplicates by assertion hash.
|
|
#[tokio::test]
|
|
async fn test_resolve_aliases_deduplicates() {
|
|
let store = Arc::new(HybridStore::open_temp().expect("store"));
|
|
|
|
// Create a single assertion
|
|
let assertion = AssertionBuilder::new()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("cert_verification")
|
|
.object_text("enabled")
|
|
.confidence(0.8)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
store_assertion(&store, &assertion).await;
|
|
|
|
// Set up alias store where both paths lead to the same subject
|
|
// (In real use, this wouldn't happen, but tests the dedup logic)
|
|
let alias_store = GenericAliasStore::new(store.clone());
|
|
// No alias set - both code:// and the query subject are the same
|
|
|
|
let engine = QueryEngine::new(Arc::new(store.clone()))
|
|
.with_alias_store(Arc::new(alias_store) as Arc<dyn AliasStore>);
|
|
|
|
// Query with resolve_aliases: true
|
|
let query = Query::builder().subject("code://rust/myapp/tls").resolve_aliases(true).build();
|
|
|
|
let result = engine.execute(&query).await.expect("execute");
|
|
|
|
// Should have exactly 1 result (no duplicates)
|
|
assert_eq!(result.assertions.len(), 1);
|
|
}
|
|
|
|
/// Test subject-only query with alias resolution.
|
|
#[tokio::test]
|
|
async fn test_resolve_aliases_subject_only() {
|
|
let store = Arc::new(HybridStore::open_temp().expect("store"));
|
|
|
|
// Create assertions for two aliased subjects with different predicates
|
|
let code_assertion1 = AssertionBuilder::new()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("cert_verification")
|
|
.object_text("enabled")
|
|
.confidence(0.8)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
let code_assertion2 = AssertionBuilder::new()
|
|
.subject("code://rust/myapp/tls")
|
|
.predicate("timeout")
|
|
.object_text("30s")
|
|
.confidence(0.9)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
let rfc_assertion = AssertionBuilder::new()
|
|
.subject("rfc://5246/tls")
|
|
.predicate("cert_verification")
|
|
.object_text("required")
|
|
.confidence(0.95)
|
|
.lifecycle(LifecycleStage::Approved)
|
|
.build();
|
|
|
|
store_assertion(&store, &code_assertion1).await;
|
|
store_assertion(&store, &code_assertion2).await;
|
|
store_assertion(&store, &rfc_assertion).await;
|
|
|
|
// Set up alias store
|
|
let alias_store = GenericAliasStore::new(store.clone());
|
|
let alias = create_alias("code://rust/myapp/tls", "rfc://5246/tls");
|
|
alias_store.set_alias(&alias).await.expect("set alias");
|
|
|
|
let engine = QueryEngine::new(Arc::new(store.clone()))
|
|
.with_alias_store(Arc::new(alias_store) as Arc<dyn AliasStore>);
|
|
|
|
// Query by subject only (no predicate filter) with resolve_aliases: true
|
|
let query = Query::builder().subject("code://rust/myapp/tls").resolve_aliases(true).build();
|
|
|
|
let result = engine.execute(&query).await.expect("execute");
|
|
|
|
// Should find all 3 assertions (2 from code://, 1 from rfc://)
|
|
assert_eq!(result.assertions.len(), 3, "Should find all assertions from aliased subjects");
|
|
}
|
|
|
|
/// Test default query (resolve_aliases not set) behaves like exact match.
|
|
#[tokio::test]
|
|
async fn test_resolve_aliases_default_is_false() {
|
|
let query = Query::builder().subject("test").predicate("pred").build();
|
|
|
|
assert!(!query.resolve_aliases, "Default should be false");
|
|
}
|
|
|
|
/// Test query builder sets resolve_aliases correctly.
|
|
#[tokio::test]
|
|
async fn test_query_builder_resolve_aliases() {
|
|
let query_true =
|
|
Query::builder().subject("test").predicate("pred").resolve_aliases(true).build();
|
|
|
|
let query_false =
|
|
Query::builder().subject("test").predicate("pred").resolve_aliases(false).build();
|
|
|
|
assert!(query_true.resolve_aliases, "Should be true when set to true");
|
|
assert!(!query_false.resolve_aliases, "Should be false when set to false");
|
|
}
|