use crate::common::TestProject;
use agpm_cli::{
cache::Cache,
core::OperationContext,
manifest::{DetailedDependency, ResourceDependency},
resolver::{
ConflictService, DependencyResolver, PatternExpansionService, ResolutionCore,
ResourceFetchingService, VersionResolutionService,
},
};
use std::sync::Arc;
use tokio::sync::Barrier;
fn create_test_manifest() -> agpm_cli::manifest::Manifest {
let mut manifest = agpm_cli::manifest::Manifest::new();
manifest.sources.insert("test".to_string(), "https://github.com/test/repo.git".to_string());
manifest.agents.insert(
"test-agent".to_string(),
ResourceDependency::Detailed(Box::new(DetailedDependency {
source: Some("test".to_string()),
path: "agents/test.md".to_string(),
version: Some("v1.0.0".to_string()),
filename: None,
target: None,
tool: None,
flatten: None,
install: None,
template_vars: None,
branch: None,
rev: None,
command: None,
args: None,
dependencies: None,
})),
);
manifest
}
async fn create_test_resolution_core() -> Result<(ResolutionCore, Cache), Box<dyn std::error::Error>>
{
let project = TestProject::new().await?;
let cache = Cache::with_dir(project.cache_path().to_path_buf())?;
let manifest = create_test_manifest();
let source_manager = agpm_cli::source::SourceManager::from_manifest(&manifest)?;
let core = ResolutionCore::new(
manifest,
cache.clone(),
source_manager,
Some(Arc::new(OperationContext::new())),
);
Ok((core, cache))
}
async fn create_test_resolution_core_with_cache()
-> Result<(ResolutionCore, Cache), Box<dyn std::error::Error>> {
let project = TestProject::new().await?;
let cache = Cache::with_dir(project.cache_path().to_path_buf())?;
let manifest = create_test_manifest();
let source_manager = agpm_cli::source::SourceManager::from_manifest(&manifest)?;
let core = ResolutionCore::new(
manifest,
cache.clone(),
source_manager,
Some(Arc::new(OperationContext::new())),
);
Ok((core, cache))
}
#[tokio::test]
async fn test_service_initialization() {
agpm_cli::test_utils::init_test_logging(None);
let (core, _cache) =
create_test_resolution_core().await.expect("Failed to create test resolution core");
let version_service = VersionResolutionService::new(core.cache.clone());
let _cache_ref = &version_service;
let pattern_service = PatternExpansionService::new();
let _pattern_ref = &pattern_service;
assert!(
pattern_service
.get_pattern_alias(agpm_cli::core::ResourceType::Agent, "nonexistent")
.is_none()
);
let _resource_service = ResourceFetchingService::new();
let conflict_service = ConflictService::new();
let _conflict_ref = &conflict_service;
let resolver = DependencyResolver::new(core.manifest.clone(), core.cache.clone())
.await
.expect("Failed to create resolver");
assert!(!resolver.core().manifest.agents.is_empty());
assert!(!resolver.core().cache.get_cache_location().as_os_str().is_empty());
}
#[tokio::test]
async fn test_resolution_services_lifecycle() {
agpm_cli::test_utils::init_test_logging(None);
let (core, _cache) =
create_test_resolution_core().await.expect("Failed to create test resolution core");
let version_service = VersionResolutionService::new(core.cache.clone());
let pattern_service = PatternExpansionService::new();
let _resource_service = ResourceFetchingService::new();
let cache_location = core.cache.get_cache_location();
assert!(!cache_location.as_os_str().is_empty());
let _service_ref = &version_service;
assert!(
pattern_service
.get_pattern_alias(agpm_cli::core::ResourceType::Agent, "concrete-name")
.is_none()
);
pattern_service.add_pattern_alias(
agpm_cli::core::ResourceType::Agent,
"concrete-name".to_string(),
"pattern-name".to_string(),
);
assert!(
pattern_service
.get_pattern_alias(agpm_cli::core::ResourceType::Agent, "concrete-name")
.is_some()
);
assert!(!core.manifest.agents.is_empty());
let resolver = DependencyResolver::new(core.manifest.clone(), core.cache.clone())
.await
.expect("Failed to create resolver");
let _resolver_core = resolver.core();
assert!(!resolver.core().manifest.agents.is_empty());
}
#[tokio::test]
async fn test_service_state_management() {
agpm_cli::test_utils::init_test_logging(None);
let (core, _cache) =
create_test_resolution_core().await.expect("Failed to create test resolution core");
let version_service = Arc::new(VersionResolutionService::new(core.cache.clone()));
let pattern_service = Arc::new(PatternExpansionService::new());
let barrier = Arc::new(Barrier::new(3));
let mut handles = vec![];
for i in 0..3 {
let service = version_service.clone();
let barrier = barrier.clone();
let handle = tokio::spawn(async move {
barrier.wait().await;
let service_ref: &VersionResolutionService = &service;
assert!(!std::ptr::eq(service_ref, std::ptr::null()), "Service should be valid");
i });
handles.push(handle);
}
let mut task_ids = vec![];
for handle in handles {
task_ids.push(handle.await.unwrap());
}
assert_eq!(task_ids.len(), 3);
assert!(task_ids.contains(&0));
assert!(task_ids.contains(&1));
assert!(task_ids.contains(&2));
let barrier = Arc::new(Barrier::new(3));
let mut handles = vec![];
for i in 0..3 {
let service = pattern_service.clone();
let barrier = barrier.clone();
let handle = tokio::spawn(async move {
barrier.wait().await;
for j in 0..3 {
service.add_pattern_alias(
agpm_cli::core::ResourceType::Agent,
format!("concrete-{}-{}", i, j),
format!("pattern-{}", i),
);
}
i });
handles.push(handle);
}
let mut task_ids = vec![];
for handle in handles {
task_ids.push(handle.await.unwrap());
}
assert_eq!(task_ids.len(), 3);
assert!(
pattern_service
.get_pattern_alias(agpm_cli::core::ResourceType::Agent, "concrete-0-0")
.is_some()
);
assert!(
pattern_service
.get_pattern_alias(agpm_cli::core::ResourceType::Agent, "concrete-1-1")
.is_some()
);
assert!(
pattern_service
.get_pattern_alias(agpm_cli::core::ResourceType::Agent, "concrete-2-2")
.is_some()
);
assert_eq!(
pattern_service
.get_pattern_alias(agpm_cli::core::ResourceType::Agent, "concrete-0-1")
.unwrap()
.as_str(),
"pattern-0"
);
}
#[tokio::test]
async fn test_service_isolation() {
agpm_cli::test_utils::init_test_logging(None);
let (core1, cache1) = create_test_resolution_core_with_cache()
.await
.expect("Failed to create test resolution core 1");
let (core2, cache2) = create_test_resolution_core_with_cache()
.await
.expect("Failed to create test resolution core 2");
let resolver1 = DependencyResolver::new(core1.manifest.clone(), cache1.clone())
.await
.expect("Failed to create resolver 1");
let resolver2 = DependencyResolver::new(core2.manifest.clone(), cache2.clone())
.await
.expect("Failed to create resolver 2");
let cache1_location = resolver1.core().cache.get_cache_location();
let cache2_location = resolver2.core().cache.get_cache_location();
assert_ne!(cache1_location, cache2_location);
if let (Some(ctx1), Some(ctx2)) =
(&resolver1.core().operation_context, &resolver2.core().operation_context)
{
assert!(!Arc::ptr_eq(ctx1, ctx2));
}
let handle1 = tokio::spawn(async move {
let _result = resolver1.core();
Ok::<(bool, i32), anyhow::Error>((true, 1))
});
let handle2 = tokio::spawn(async move {
let _result = resolver2.core();
Ok::<(bool, i32), anyhow::Error>((true, 2))
});
let (result1, result2) = tokio::join!(handle1, handle2);
let r1 = result1.unwrap().unwrap();
let r2 = result2.unwrap().unwrap();
assert!(r1.0);
assert!(r2.0);
assert_ne!(r1.1, r2.1);
let mut handles = vec![];
for i in 0..5 {
let (core, cache) = create_test_resolution_core_with_cache()
.await
.expect("Failed to create test resolution core with cache");
let handle = tokio::spawn(async move {
let resolver = DependencyResolver::new(core.manifest.clone(), cache.clone())
.await
.expect("Failed to create resolver");
let _core_ref = resolver.core();
Ok::<(bool, i32), anyhow::Error>((true, i))
});
handles.push(handle);
}
for handle in handles {
let (success, id) = handle.await.unwrap().unwrap();
assert!(success, "Resolver {} failed", id);
}
}