use server::service_bus_manager::azure_management_client::{
AzureResourceCache, NamespaceProperties, ResourceGroup, ServiceBusNamespace, Subscription,
};
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::time::sleep;
mod cache_helpers {
use super::*;
pub fn create_mock_subscription(id: &str, name: &str) -> Subscription {
Subscription {
id: format!("/subscriptions/{id}"),
subscription_id: id.to_string(),
display_name: name.to_string(),
state: "Enabled".to_string(),
}
}
pub fn create_mock_resource_group(name: &str, location: &str) -> ResourceGroup {
ResourceGroup {
id: format!("/subscriptions/test/resourceGroups/{name}"),
name: name.to_string(),
location: location.to_string(),
tags: HashMap::new(),
}
}
pub fn create_mock_namespace(name: &str, location: &str) -> ServiceBusNamespace {
ServiceBusNamespace {
id: format!(
"/subscriptions/test/resourceGroups/test/providers/Microsoft.ServiceBus/namespaces/{name}"
),
name: name.to_string(),
location: location.to_string(),
resource_type: "Microsoft.ServiceBus/namespaces".to_string(),
properties: NamespaceProperties {
service_bus_endpoint: format!("https://{name}.servicebus.windows.net/"),
status: Some("Active".to_string()),
created_at: Some("2023-01-01T00:00:00Z".to_string()),
},
}
}
pub fn create_mock_subscriptions(count: usize) -> Vec<Subscription> {
(0..count)
.map(|i| create_mock_subscription(&format!("sub-{i}"), &format!("Subscription {i}")))
.collect()
}
pub fn create_mock_resource_groups(count: usize, location: &str) -> Vec<ResourceGroup> {
(0..count)
.map(|i| create_mock_resource_group(&format!("rg-{i}"), location))
.collect()
}
pub fn create_mock_namespaces(count: usize, location: &str) -> Vec<ServiceBusNamespace> {
(0..count)
.map(|i| create_mock_namespace(&format!("ns-{i}"), location))
.collect()
}
}
use cache_helpers::*;
mod cache_basic_functionality {
use super::*;
#[test]
fn test_cache_creation() {
let cache = AzureResourceCache::new();
assert!(cache.is_empty(), "New cache should be empty");
}
#[test]
fn test_cache_creation_with_config() {
let ttl = Duration::from_secs(600); let max_entries = 50;
let cache = AzureResourceCache::with_config(ttl, max_entries);
assert!(cache.is_empty(), "New configured cache should be empty");
}
#[test]
fn test_cache_subscription_storage_and_retrieval() {
let mut cache = AzureResourceCache::new();
let subscriptions = create_mock_subscriptions(3);
cache.cache_subscriptions(subscriptions.clone());
assert!(
!cache.is_empty(),
"Cache should not be empty after adding subscriptions"
);
let cached = cache.get_cached_subscriptions();
assert!(cached.is_some(), "Should retrieve cached subscriptions");
let cached_subs = cached.unwrap();
assert_eq!(cached_subs.len(), 3, "Should have 3 cached subscriptions");
assert_eq!(
cached_subs, subscriptions,
"Cached subscriptions should match original"
);
}
#[test]
fn test_cache_resource_group_storage_and_retrieval() {
let mut cache = AzureResourceCache::new();
let resource_groups = create_mock_resource_groups(2, "eastus");
let subscription_id = "test-subscription";
cache.cache_resource_groups(subscription_id.to_string(), resource_groups.clone());
let cached = cache.get_cached_resource_groups(subscription_id);
assert!(cached.is_some(), "Should retrieve cached resource groups");
let cached_groups = cached.unwrap();
assert_eq!(
cached_groups.len(),
2,
"Should have 2 cached resource groups"
);
assert_eq!(
cached_groups, resource_groups,
"Cached resource groups should match original"
);
}
#[test]
fn test_cache_namespace_storage_and_retrieval() {
let mut cache = AzureResourceCache::new();
let namespaces = create_mock_namespaces(4, "westus");
let subscription_id = "test-subscription";
cache.cache_namespaces(subscription_id.to_string(), namespaces.clone());
let cached = cache.get_cached_namespaces(subscription_id);
assert!(cached.is_some(), "Should retrieve cached namespaces");
let cached_ns = cached.unwrap();
assert_eq!(cached_ns.len(), 4, "Should have 4 cached namespaces");
assert_eq!(
cached_ns, namespaces,
"Cached namespaces should match original"
);
}
#[test]
fn test_cache_connection_string_storage_and_retrieval() {
let mut cache = AzureResourceCache::new();
let namespace_id = "test-namespace-id";
let connection_string = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=testkey";
cache.cache_connection_string(namespace_id.to_string(), connection_string.to_string());
let cached = cache.get_cached_connection_string(namespace_id);
assert!(cached.is_some(), "Should retrieve cached connection string");
assert_eq!(
cached.unwrap(),
connection_string,
"Cached connection string should match original"
);
}
}
mod cache_ttl_functionality {
use super::*;
#[tokio::test]
async fn test_cache_ttl_expiration() {
let ttl = Duration::from_millis(100); let mut cache = AzureResourceCache::with_config(ttl, 10);
let subscriptions = create_mock_subscriptions(2);
cache.cache_subscriptions(subscriptions.clone());
let cached = cache.get_cached_subscriptions();
assert!(
cached.is_some(),
"Should retrieve fresh cached subscriptions"
);
sleep(Duration::from_millis(150)).await;
let expired = cache.get_cached_subscriptions();
assert!(
expired.is_none(),
"Should not retrieve expired cached subscriptions"
);
}
#[tokio::test]
async fn test_cache_ttl_with_different_types() {
let ttl = Duration::from_millis(100);
let mut cache = AzureResourceCache::with_config(ttl, 10);
let resource_groups = create_mock_resource_groups(1, "eastus");
let namespaces = create_mock_namespaces(1, "westus");
let subscription_id = "test-sub";
cache.cache_resource_groups(subscription_id.to_string(), resource_groups);
cache.cache_namespaces(subscription_id.to_string(), namespaces);
cache.cache_connection_string("test-ns".to_string(), "test-connection".to_string());
assert!(cache.get_cached_resource_groups(subscription_id).is_some());
assert!(cache.get_cached_namespaces(subscription_id).is_some());
assert!(cache.get_cached_connection_string("test-ns").is_some());
sleep(Duration::from_millis(150)).await;
assert!(cache.get_cached_resource_groups(subscription_id).is_none());
assert!(cache.get_cached_namespaces(subscription_id).is_none());
assert!(cache.get_cached_connection_string("test-ns").is_none());
}
#[test]
fn test_cache_clean_expired() {
let ttl = Duration::from_millis(1); let mut cache = AzureResourceCache::with_config(ttl, 10);
let subscriptions = create_mock_subscriptions(1);
cache.cache_subscriptions(subscriptions);
std::thread::sleep(Duration::from_millis(5));
assert!(
!cache.is_empty(),
"Cache should not be empty before cleaning"
);
cache.clean_expired();
assert!(
cache.is_empty(),
"Cache should be empty after cleaning expired entries"
);
}
}
mod cache_size_limits {
use super::*;
#[test]
fn test_cache_max_entries_resource_groups() {
let mut cache = AzureResourceCache::with_config(Duration::from_secs(300), 2);
let resource_groups = create_mock_resource_groups(1, "eastus");
cache.cache_resource_groups("sub-1".to_string(), resource_groups.clone());
cache.cache_resource_groups("sub-2".to_string(), resource_groups.clone());
assert!(cache.get_cached_resource_groups("sub-1").is_some());
assert!(cache.get_cached_resource_groups("sub-2").is_some());
cache.cache_resource_groups("sub-3".to_string(), resource_groups.clone());
assert!(
cache.get_cached_resource_groups("sub-1").is_none(),
"Oldest entry should be evicted"
);
assert!(
cache.get_cached_resource_groups("sub-2").is_some(),
"Second entry should remain"
);
assert!(
cache.get_cached_resource_groups("sub-3").is_some(),
"Newest entry should be present"
);
}
#[test]
fn test_cache_max_entries_namespaces() {
let mut cache = AzureResourceCache::with_config(Duration::from_secs(300), 3);
let namespaces = create_mock_namespaces(1, "westus");
for i in 1..=3 {
cache.cache_namespaces(format!("sub-{i}"), namespaces.clone());
}
for i in 1..=3 {
assert!(cache.get_cached_namespaces(&format!("sub-{i}")).is_some());
}
cache.cache_namespaces("sub-4".to_string(), namespaces.clone());
cache.cache_namespaces("sub-5".to_string(), namespaces.clone());
assert!(
cache.get_cached_namespaces("sub-1").is_none(),
"First entry should be evicted"
);
assert!(
cache.get_cached_namespaces("sub-2").is_none(),
"Second entry should be evicted"
);
assert!(
cache.get_cached_namespaces("sub-3").is_some(),
"Third entry should remain"
);
assert!(
cache.get_cached_namespaces("sub-4").is_some(),
"Fourth entry should be present"
);
assert!(
cache.get_cached_namespaces("sub-5").is_some(),
"Fifth entry should be present"
);
}
#[test]
fn test_cache_max_entries_connection_strings() {
let mut cache = AzureResourceCache::with_config(Duration::from_secs(300), 2);
cache.cache_connection_string("ns-1".to_string(), "connection-1".to_string());
cache.cache_connection_string("ns-2".to_string(), "connection-2".to_string());
assert!(cache.get_cached_connection_string("ns-1").is_some());
assert!(cache.get_cached_connection_string("ns-2").is_some());
cache.cache_connection_string("ns-3".to_string(), "connection-3".to_string());
assert!(cache.get_cached_connection_string("ns-1").is_none());
assert!(cache.get_cached_connection_string("ns-2").is_some());
assert!(cache.get_cached_connection_string("ns-3").is_some());
}
#[test]
fn test_cache_update_existing_entry() {
let mut cache = AzureResourceCache::with_config(Duration::from_secs(300), 2);
let resource_groups_v1 = create_mock_resource_groups(1, "eastus");
let resource_groups_v2 = create_mock_resource_groups(2, "westus");
cache.cache_resource_groups("sub-1".to_string(), resource_groups_v1);
cache.cache_resource_groups(
"sub-2".to_string(),
create_mock_resource_groups(1, "centralus"),
);
cache.cache_resource_groups("sub-1".to_string(), resource_groups_v2.clone());
assert!(cache.get_cached_resource_groups("sub-1").is_some());
assert!(cache.get_cached_resource_groups("sub-2").is_some());
let updated = cache.get_cached_resource_groups("sub-1").unwrap();
assert_eq!(
updated.len(),
2,
"Updated entry should have 2 resource groups"
);
assert_eq!(
updated, resource_groups_v2,
"Updated entry should match new data"
);
}
}
mod cache_performance {
use super::*;
#[test]
fn test_cache_performance_many_operations() {
let mut cache = AzureResourceCache::with_config(Duration::from_secs(300), 1000);
let start = Instant::now();
for i in 0..1000 {
let subscriptions = create_mock_subscriptions(1);
cache.cache_subscriptions(subscriptions);
let resource_groups = create_mock_resource_groups(1, "eastus");
cache.cache_resource_groups(format!("sub-{i}"), resource_groups);
let _cached = cache.get_cached_subscriptions();
let _cached_rg = cache.get_cached_resource_groups(&format!("sub-{i}"));
}
let duration = start.elapsed();
assert!(
duration < Duration::from_secs(1),
"1000 cache operations should be fast, took: {duration:?}"
);
}
#[test]
fn test_cache_performance_large_data() {
let mut cache = AzureResourceCache::with_config(Duration::from_secs(300), 100);
let large_subscriptions = create_mock_subscriptions(100);
let large_resource_groups = create_mock_resource_groups(100, "eastus");
let large_namespaces = create_mock_namespaces(100, "westus");
let start = Instant::now();
cache.cache_subscriptions(large_subscriptions.clone());
cache.cache_resource_groups("large-sub".to_string(), large_resource_groups.clone());
cache.cache_namespaces("large-sub".to_string(), large_namespaces.clone());
let _cached_subs = cache.get_cached_subscriptions();
let _cached_rgs = cache.get_cached_resource_groups("large-sub");
let _cached_ns = cache.get_cached_namespaces("large-sub");
let duration = start.elapsed();
assert!(
duration < Duration::from_secs(1),
"Large data cache operations should be fast, took: {duration:?}"
);
}
#[test]
fn test_cache_memory_efficiency() {
let mut cache = AzureResourceCache::with_config(Duration::from_secs(300), 10);
for i in 0..20 {
let resource_groups = create_mock_resource_groups(5, "eastus");
cache.cache_resource_groups(format!("sub-{i}"), resource_groups);
}
let mut present_count = 0;
for i in 0..20 {
if cache
.get_cached_resource_groups(&format!("sub-{i}"))
.is_some()
{
present_count += 1;
}
}
assert!(
present_count <= 10,
"Cache should not exceed max entries, found {present_count} entries"
);
for i in 15..20 {
assert!(
cache
.get_cached_resource_groups(&format!("sub-{i}"))
.is_some(),
"Recent entries should be present in cache"
);
}
}
}
mod cache_edge_cases {
use super::*;
#[test]
fn test_cache_empty_data() {
let mut cache = AzureResourceCache::new();
cache.cache_subscriptions(Vec::new());
cache.cache_resource_groups("empty-sub".to_string(), Vec::new());
cache.cache_namespaces("empty-sub".to_string(), Vec::new());
let cached_subs = cache.get_cached_subscriptions();
assert!(cached_subs.is_some(), "Should retrieve empty subscriptions");
assert!(
cached_subs.unwrap().is_empty(),
"Cached subscriptions should be empty"
);
let cached_rgs = cache.get_cached_resource_groups("empty-sub");
assert!(
cached_rgs.is_some(),
"Should retrieve empty resource groups"
);
assert!(
cached_rgs.unwrap().is_empty(),
"Cached resource groups should be empty"
);
}
#[test]
fn test_cache_clear_functionality() {
let mut cache = AzureResourceCache::new();
cache.cache_subscriptions(create_mock_subscriptions(2));
cache.cache_resource_groups(
"test-sub".to_string(),
create_mock_resource_groups(2, "eastus"),
);
cache.cache_namespaces("test-sub".to_string(), create_mock_namespaces(2, "westus"));
cache.cache_connection_string("test-ns".to_string(), "test-connection".to_string());
assert!(
!cache.is_empty(),
"Cache should not be empty after adding data"
);
cache.clear();
assert!(cache.is_empty(), "Cache should be empty after clearing");
assert!(cache.get_cached_subscriptions().is_none());
assert!(cache.get_cached_resource_groups("test-sub").is_none());
assert!(cache.get_cached_namespaces("test-sub").is_none());
assert!(cache.get_cached_connection_string("test-ns").is_none());
}
#[test]
fn test_cache_with_zero_max_entries() {
let mut cache = AzureResourceCache::with_config(Duration::from_secs(300), 0);
cache.cache_resource_groups("test".to_string(), create_mock_resource_groups(1, "eastus"));
let _result = cache.get_cached_resource_groups("test");
}
#[test]
fn test_cache_retrieval_with_non_existent_keys() {
let cache = AzureResourceCache::new();
assert!(cache.get_cached_subscriptions().is_none());
assert!(cache.get_cached_resource_groups("non-existent").is_none());
assert!(cache.get_cached_namespaces("non-existent").is_none());
assert!(cache.get_cached_connection_string("non-existent").is_none());
}
#[tokio::test]
async fn test_cache_concurrent_access() {
use std::sync::{Arc, Mutex};
let cache = Arc::new(Mutex::new(AzureResourceCache::new()));
let mut handles = Vec::new();
for i in 0..10 {
let cache_clone = cache.clone();
let handle = tokio::spawn(async move {
{
let mut cache = cache_clone.lock().unwrap();
let resource_groups = create_mock_resource_groups(1, "eastus");
cache.cache_resource_groups(format!("sub-{i}"), resource_groups);
}
{
let cache = cache_clone.lock().unwrap();
let _cached = cache.get_cached_resource_groups(&format!("sub-{i}"));
}
i
});
handles.push(handle);
}
let mut results = Vec::new();
for handle in handles {
let result = handle.await.expect("Concurrent operation should complete");
results.push(result);
}
assert_eq!(
results.len(),
10,
"All concurrent operations should complete"
);
}
}