//! Battery 9: AliasStore Resolution and Cross-Scheme Queries. //! //! Tests alias storage, resolution, and transitive expansion for cross-scheme queries. //! //! # Test Coverage //! //! | Test | Feature | Validates | //! |------|---------|-----------| //! | `test_alias_direct_resolution` | Basic alias | Query alias, get canonical | //! | `test_alias_transitive_resolution` | A → B → C chain | Transitive resolution | //! | `test_alias_cycle_detection` | A → B → A | Safe termination | //! | `test_alias_bidirectional` | Reverse lookup | get_aliases for canonical | //! | `test_alias_delete` | Delete alias | Clean removal | //! | `test_alias_suggest` | Suggest aliases | Similarity-based suggestions | #![allow(clippy::expect_used)] // Test code uses expect() for clear failure messages use std::sync::Arc; use stemedb_core::types::{AliasOrigin, ConceptAlias, ConceptPath}; use stemedb_storage::{AliasStore, GenericAliasStore, HybridStore}; /// 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 9.1: Direct alias resolution. /// /// Store alias: code://rust/auth/jwt/aud → rfc://7519/jwt/audience /// Query: get_canonical("code://rust/auth/jwt/aud") /// Expect: rfc://7519/jwt/audience #[tokio::test] async fn test_alias_direct_resolution() { let store = Arc::new(HybridStore::open_temp().expect("create store")); let alias_store = GenericAliasStore::new(store); // Store alias let alias = create_alias("code://rust/auth/jwt/aud", "rfc://7519/jwt/audience"); alias_store.set_alias(&alias).await.expect("set alias"); // Resolve alias let canonical = alias_store.get_canonical("code://rust/auth/jwt/aud").await.expect("get canonical"); assert!(canonical.is_some(), "should find canonical path"); let canonical_path = canonical.unwrap(); assert_eq!(canonical_path.scheme, "rfc"); assert_eq!(canonical_path.segments, vec!["7519", "jwt", "audience"]); } /// Test 9.2: Transitive alias resolution. /// /// Store chain: internal://jwt → code://rust/auth/jwt → rfc://7519/jwt /// Query: resolve_all("internal://jwt") /// Expect: all three paths returned #[tokio::test] async fn test_alias_transitive_resolution() { let store = Arc::new(HybridStore::open_temp().expect("create store")); let alias_store = GenericAliasStore::new(store); // Store alias chain: internal → code → rfc let alias1 = create_alias("internal://jwt/impl", "code://rust/auth/jwt"); let alias2 = create_alias("code://rust/auth/jwt", "rfc://7519/jwt"); alias_store.set_alias(&alias1).await.expect("set alias1"); alias_store.set_alias(&alias2).await.expect("set alias2"); // Resolve all from internal (start of chain) let all_paths = alias_store.resolve_all("internal://jwt/impl").await.expect("resolve all"); assert!(all_paths.contains(&"internal://jwt/impl".to_string()), "should include starting path"); assert!( all_paths.contains(&"code://rust/auth/jwt".to_string()), "should include intermediate alias" ); assert!(all_paths.contains(&"rfc://7519/jwt".to_string()), "should include final canonical"); // Should have exactly 3 paths assert_eq!(all_paths.len(), 3, "should resolve to 3 paths in chain"); } /// Test 9.3: Cycle detection. /// /// Store cycle: A → B → A /// Query: resolve_all("A") /// Expect: Safe termination, returns [A, B] without infinite loop #[tokio::test] async fn test_alias_cycle_detection() { let store = Arc::new(HybridStore::open_temp().expect("create store")); let alias_store = GenericAliasStore::new(store); // Create a cycle: code → rfc → code let alias1 = create_alias("code://cycle/a", "rfc://cycle/b"); let alias2 = create_alias("rfc://cycle/b", "code://cycle/a"); alias_store.set_alias(&alias1).await.expect("set alias1"); alias_store.set_alias(&alias2).await.expect("set alias2"); // Resolve all - should not hang let all_paths = alias_store.resolve_all("code://cycle/a").await.expect("resolve all (cycle)"); // Should have exactly 2 paths assert_eq!(all_paths.len(), 2, "cycle should resolve to 2 paths"); assert!(all_paths.contains(&"code://cycle/a".to_string()), "should include A"); assert!(all_paths.contains(&"rfc://cycle/b".to_string()), "should include B"); } /// Test 9.4: Bidirectional lookup (reverse index). /// /// Store alias: code://rust/auth/jwt → rfc://7519/jwt /// Query: get_aliases("rfc://7519/jwt") /// Expect: [code://rust/auth/jwt] #[tokio::test] async fn test_alias_bidirectional() { let store = Arc::new(HybridStore::open_temp().expect("create store")); let alias_store = GenericAliasStore::new(store); // Store multiple aliases pointing to same canonical let alias1 = create_alias("code://rust/auth/jwt", "rfc://7519/jwt"); let alias2 = create_alias("internal://jwt/impl", "rfc://7519/jwt"); alias_store.set_alias(&alias1).await.expect("set alias1"); alias_store.set_alias(&alias2).await.expect("set alias2"); // Reverse lookup: get all aliases for canonical let aliases = alias_store.get_aliases("rfc://7519/jwt").await.expect("get aliases"); assert_eq!(aliases.len(), 2, "should have 2 aliases for canonical"); let alias_strings: Vec = aliases.iter().map(|p| p.to_wire_format()).collect(); assert!( alias_strings.contains(&"code://rust/auth/jwt".to_string()), "should include code alias" ); assert!( alias_strings.contains(&"internal://jwt/impl".to_string()), "should include internal alias" ); } /// Test 9.5: Delete alias. /// /// Store alias, verify exists, delete, verify gone. /// Also verify reverse index is updated. #[tokio::test] async fn test_alias_delete() { let store = Arc::new(HybridStore::open_temp().expect("create store")); let alias_store = GenericAliasStore::new(store); // Store alias let alias = create_alias("code://rust/auth/jwt", "rfc://7519/jwt"); alias_store.set_alias(&alias).await.expect("set alias"); // Verify it exists let canonical = alias_store .get_canonical("code://rust/auth/jwt") .await .expect("get canonical before delete"); assert!(canonical.is_some(), "alias should exist before delete"); // Delete let deleted = alias_store.delete_alias("code://rust/auth/jwt").await.expect("delete alias"); assert!(deleted, "delete should return true"); // Verify forward lookup is gone let canonical_after = alias_store .get_canonical("code://rust/auth/jwt") .await .expect("get canonical after delete"); assert!(canonical_after.is_none(), "alias should not exist after delete"); // Verify reverse lookup is updated let aliases = alias_store.get_aliases("rfc://7519/jwt").await.expect("get aliases after delete"); assert!(aliases.is_empty(), "reverse index should be empty after delete"); } /// Test 9.6: Delete non-existent alias returns false. #[tokio::test] async fn test_alias_delete_nonexistent() { let store = Arc::new(HybridStore::open_temp().expect("create store")); let alias_store = GenericAliasStore::new(store); // Try to delete non-existent alias let deleted = alias_store.delete_alias("nonexistent://path").await.expect("delete nonexistent"); assert!(!deleted, "delete should return false for non-existent alias"); } /// Test 9.7: Alias suggestions based on leaf similarity. /// /// Given existing subjects with similar leaf names across DIFFERENT schemes, /// suggest potential aliases. /// /// Note: Suggestions only work across different schemes (cross-scheme aliasing). /// Same-scheme paths are not suggested as aliases. #[tokio::test] async fn test_alias_suggest() { let store = Arc::new(HybridStore::open_temp().expect("create store")); let alias_store = GenericAliasStore::new(store); // Existing subjects in the system let existing_subjects = vec![ "rfc://7519/jwt/audience".to_string(), "internal://jwt/aud".to_string(), // different scheme, similar leaf "owasp://top10/injection".to_string(), "code://rust/citadeldb/net/tls".to_string(), ]; // Ask for suggestions for a new code path with "audience" leaf let suggestions = alias_store .suggest_aliases("code://new/jwt/audience", &existing_subjects) .await .expect("suggest aliases"); // Should suggest the RFC path with same leaf name (different scheme) let suggested_paths: Vec<&str> = suggestions.iter().map(|(p, _)| p.as_str()).collect(); assert!( suggested_paths.contains(&"rfc://7519/jwt/audience"), "should suggest rfc://7519/jwt/audience (same leaf 'audience', different scheme)" ); // Should suggest the internal path with similar leaf 'aud' (different scheme) // ('aud' is substring of 'audience') assert!( suggested_paths.contains(&"internal://jwt/aud"), "should suggest internal://jwt/aud (similar leaf 'aud', different scheme)" ); // Should NOT suggest unrelated paths assert!( !suggested_paths.contains(&"owasp://top10/injection"), "should NOT suggest unrelated path" ); // Should NOT suggest same-scheme paths even with similar leaf assert!( !suggested_paths.contains(&"code://rust/citadeldb/net/tls"), "should NOT suggest same-scheme path" ); } /// Test 9.8: List all aliases. /// /// Store multiple aliases, list all, verify count and content. #[tokio::test] async fn test_alias_list_all() { let store = Arc::new(HybridStore::open_temp().expect("create store")); let alias_store = GenericAliasStore::new(store); // Store multiple aliases let alias1 = create_alias("code://a", "rfc://1"); let alias2 = create_alias("code://b", "rfc://2"); let alias3 = create_alias("internal://c", "rfc://3"); alias_store.set_alias(&alias1).await.expect("set alias1"); alias_store.set_alias(&alias2).await.expect("set alias2"); alias_store.set_alias(&alias3).await.expect("set alias3"); // List all let all_aliases = alias_store.list_all_aliases().await.expect("list all"); assert_eq!(all_aliases.len(), 3, "should have 3 aliases"); // Verify content let alias_map: std::collections::HashMap = all_aliases.into_iter().collect(); assert_eq!( alias_map.get("code://a"), Some(&"rfc://1".to_string()), "code://a should map to rfc://1" ); assert_eq!( alias_map.get("code://b"), Some(&"rfc://2".to_string()), "code://b should map to rfc://2" ); assert_eq!( alias_map.get("internal://c"), Some(&"rfc://3".to_string()), "internal://c should map to rfc://3" ); }