#[cfg(test)]
mod tests {
use crate::config::{MetadataConfig, SourceConfig};
use crate::error::{KopiError, Result};
use crate::indicator::{ProgressIndicator, SilentProgress};
use crate::metadata::{MetadataProvider, MetadataSource, PackageDetails, SourceHealth};
use crate::models::metadata::JdkMetadata;
use crate::models::package::{ArchiveType, ChecksumType, PackageType};
use crate::models::platform::{Architecture, OperatingSystem};
use crate::version::Version;
use std::sync::{Arc, Mutex};
struct MockMetadataSource {
id: String,
name: String,
available: Arc<Mutex<bool>>,
fetch_all_result: Arc<Mutex<Result<Vec<JdkMetadata>>>>,
fetch_distribution_result: Arc<Mutex<Result<Vec<JdkMetadata>>>>,
fetch_package_details_result: Arc<Mutex<Result<PackageDetails>>>,
}
impl MockMetadataSource {
fn new(id: &str, name: &str) -> Self {
Self {
id: id.to_string(),
name: name.to_string(),
available: Arc::new(Mutex::new(true)),
fetch_all_result: Arc::new(Mutex::new(Ok(vec![]))),
fetch_distribution_result: Arc::new(Mutex::new(Ok(vec![]))),
fetch_package_details_result: Arc::new(Mutex::new(Ok(PackageDetails {
download_url: "https://example.com/download".to_string(),
checksum: Some("abc123".to_string()),
checksum_type: Some(ChecksumType::Sha256),
}))),
}
}
fn set_available(&self, available: bool) {
*self.available.lock().unwrap() = available;
}
fn set_fetch_all_result(&self, result: Result<Vec<JdkMetadata>>) {
*self.fetch_all_result.lock().unwrap() = result;
}
fn set_fetch_distribution_result(&self, result: Result<Vec<JdkMetadata>>) {
*self.fetch_distribution_result.lock().unwrap() = result;
}
fn set_fetch_package_details_result(&self, result: Result<PackageDetails>) {
*self.fetch_package_details_result.lock().unwrap() = result;
}
}
impl MetadataSource for Arc<MockMetadataSource> {
fn id(&self) -> &str {
&self.id
}
fn name(&self) -> &str {
&self.name
}
fn is_available(&self) -> Result<bool> {
Ok(*self.available.lock().unwrap())
}
fn fetch_all(&self, _progress: &mut dyn ProgressIndicator) -> Result<Vec<JdkMetadata>> {
let locked = self.fetch_all_result.lock().unwrap();
match &*locked {
Ok(vec) => Ok(vec.clone()),
Err(_) => Err(KopiError::MetadataFetch("Mock error".to_string())),
}
}
fn fetch_distribution(
&self,
_distribution: &str,
_progress: &mut dyn ProgressIndicator,
) -> Result<Vec<JdkMetadata>> {
let locked = self.fetch_distribution_result.lock().unwrap();
match &*locked {
Ok(vec) => Ok(vec.clone()),
Err(_) => Err(KopiError::MetadataFetch("Mock error".to_string())),
}
}
fn fetch_package_details(
&self,
_package_id: &str,
_progress: &mut dyn ProgressIndicator,
) -> Result<PackageDetails> {
let locked = self.fetch_package_details_result.lock().unwrap();
match &*locked {
Ok(details) => Ok(details.clone()),
Err(_) => Err(KopiError::MetadataFetch("Mock error".to_string())),
}
}
fn last_updated(&self) -> Result<Option<chrono::DateTime<chrono::Utc>>> {
Ok(None)
}
}
fn create_test_metadata(id: &str, complete: bool) -> JdkMetadata {
JdkMetadata {
id: id.to_string(),
distribution: "temurin".to_string(),
version: Version::new(21, 0, 1),
distribution_version: Version::new(21, 0, 1),
architecture: Architecture::X64,
operating_system: OperatingSystem::Linux,
package_type: PackageType::Jdk,
archive_type: ArchiveType::TarGz,
download_url: if complete {
Some("https://example.com/download".to_string())
} else {
None
},
checksum: if complete {
Some("abc123".to_string())
} else {
None
},
checksum_type: if complete {
Some(ChecksumType::Sha256)
} else {
None
},
size: 100_000_000,
lib_c_type: None,
javafx_bundled: false,
term_of_support: None,
release_status: None,
latest_build_available: None,
}
}
#[test]
fn test_fallback_when_primary_fails() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
let fallback = Arc::new(MockMetadataSource::new("fallback", "Fallback Source"));
primary.set_available(false);
let test_metadata = vec![create_test_metadata("test1", true)];
fallback.set_fetch_all_result(Ok(test_metadata.clone()));
let provider = MetadataProvider {
sources: vec![
("primary".to_string(), Box::new(primary.clone())),
("fallback".to_string(), Box::new(fallback.clone())),
],
};
let mut progress = SilentProgress;
let result = provider.fetch_all(&mut progress).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].id, "test1");
}
#[test]
fn test_both_sources_fail() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
let fallback = Arc::new(MockMetadataSource::new("fallback", "Fallback Source"));
primary.set_available(false);
fallback.set_available(false);
let provider = MetadataProvider {
sources: vec![
("primary".to_string(), Box::new(primary.clone())),
("fallback".to_string(), Box::new(fallback.clone())),
],
};
let mut progress = SilentProgress;
let result = provider.fetch_all(&mut progress);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, KopiError::MetadataFetch(_)));
}
#[test]
fn test_no_fallback_configured() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
primary.set_available(false);
let provider = MetadataProvider {
sources: vec![("primary".to_string(), Box::new(primary.clone()))],
};
let mut progress = SilentProgress;
let result = provider.fetch_all(&mut progress);
assert!(result.is_err());
}
#[test]
fn test_fallback_for_distribution() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
let fallback = Arc::new(MockMetadataSource::new("fallback", "Fallback Source"));
primary.set_fetch_distribution_result(Err(KopiError::MetadataFetch(
"Primary failed".to_string(),
)));
let test_metadata = vec![create_test_metadata("test1", true)];
fallback.set_fetch_distribution_result(Ok(test_metadata.clone()));
let provider = MetadataProvider {
sources: vec![
("primary".to_string(), Box::new(primary.clone())),
("fallback".to_string(), Box::new(fallback.clone())),
],
};
let mut progress = SilentProgress;
let result = provider
.fetch_distribution("temurin", &mut progress)
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].id, "test1");
}
#[test]
fn test_fallback_for_package_details() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
let fallback = Arc::new(MockMetadataSource::new("fallback", "Fallback Source"));
primary.set_fetch_package_details_result(Err(KopiError::MetadataFetch(
"Primary failed".to_string(),
)));
let test_details = PackageDetails {
download_url: "https://fallback.com/download".to_string(),
checksum: Some("xyz789".to_string()),
checksum_type: Some(ChecksumType::Sha256),
};
fallback.set_fetch_package_details_result(Ok(test_details.clone()));
let provider = MetadataProvider {
sources: vec![
("primary".to_string(), Box::new(primary.clone())),
("fallback".to_string(), Box::new(fallback.clone())),
],
};
let mut metadata = create_test_metadata("test1", false);
let mut progress = SilentProgress;
let result = provider.ensure_complete(&mut metadata, &mut progress);
assert!(result.is_ok());
assert_eq!(
metadata.download_url,
Some("https://fallback.com/download".to_string())
);
assert_eq!(metadata.checksum, Some("xyz789".to_string()));
}
#[test]
fn test_source_health_checking() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
let fallback = Arc::new(MockMetadataSource::new("fallback", "Fallback Source"));
primary.set_available(false);
let provider = MetadataProvider {
sources: vec![
("primary".to_string(), Box::new(primary.clone())),
("fallback".to_string(), Box::new(fallback.clone())),
],
};
let health = provider.check_sources_health();
assert_eq!(health.len(), 2);
match health.get("primary").unwrap() {
SourceHealth::Unavailable(_) => {}
_ => panic!("Primary should be unavailable"),
}
match health.get("fallback").unwrap() {
SourceHealth::Available => {}
_ => panic!("Fallback should be available"),
}
}
#[test]
fn test_from_config() {
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let kopi_home = temp_dir.path();
let sources = vec![
SourceConfig::Http {
name: "primary-http".to_string(),
enabled: true,
base_url: "https://example.com/metadata".to_string(),
cache_locally: true,
timeout_secs: 30,
},
SourceConfig::Local {
name: "local-backup".to_string(),
enabled: true,
directory: "/tmp/metadata".to_string(),
archive_pattern: "*.tar.gz".to_string(),
cache_extracted: true,
},
SourceConfig::Foojay {
name: "foojay-api".to_string(),
enabled: false,
base_url: "https://api.foojay.io/disco".to_string(),
timeout_secs: 30,
},
];
let metadata_config = MetadataConfig {
cache: Default::default(),
sources,
};
let result = MetadataProvider::from_metadata_config(&metadata_config, kopi_home);
assert!(result.is_ok());
let provider = result.unwrap();
let sources = provider.list_sources();
assert_eq!(sources.len(), 2); assert!(sources.contains(&"primary-http"));
assert!(sources.contains(&"local-backup"));
assert!(!sources.contains(&"foojay-api")); }
#[test]
fn test_from_config_no_enabled_sources() {
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let kopi_home = temp_dir.path();
let sources = vec![SourceConfig::Foojay {
name: "foojay-api".to_string(),
enabled: false,
base_url: "https://api.foojay.io/disco".to_string(),
timeout_secs: 30,
}];
let metadata_config = MetadataConfig {
cache: Default::default(),
sources,
};
let result = MetadataProvider::from_metadata_config(&metadata_config, kopi_home);
assert!(result.is_err());
match result {
Err(KopiError::InvalidConfig(_)) => {}
_ => panic!("Expected InvalidConfig error"),
}
}
#[test]
fn test_multiple_http_sources() {
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let kopi_home = temp_dir.path();
let sources = vec![
SourceConfig::Http {
name: "primary-cdn".to_string(),
enabled: true,
base_url: "https://cdn1.example.com/metadata".to_string(),
cache_locally: true,
timeout_secs: 30,
},
SourceConfig::Http {
name: "secondary-cdn".to_string(),
enabled: true,
base_url: "https://cdn2.example.com/metadata".to_string(),
cache_locally: true,
timeout_secs: 30,
},
SourceConfig::Local {
name: "local-fallback".to_string(),
enabled: true,
directory: "/tmp/metadata".to_string(),
archive_pattern: "*.tar.gz".to_string(),
cache_extracted: true,
},
];
let metadata_config = MetadataConfig {
cache: Default::default(),
sources,
};
let result = MetadataProvider::from_metadata_config(&metadata_config, kopi_home);
assert!(result.is_ok());
let provider = result.unwrap();
let sources = provider.list_sources();
assert_eq!(sources.len(), 3);
assert_eq!(sources[0], "primary-cdn");
assert_eq!(sources[1], "secondary-cdn");
assert_eq!(sources[2], "local-fallback");
}
#[test]
fn test_network_error_with_fallback() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
let fallback = Arc::new(MockMetadataSource::new("fallback", "Fallback Source"));
primary.set_fetch_all_result(Err(KopiError::NetworkError(
"Connection timeout".to_string(),
)));
let test_metadata = vec![create_test_metadata("test1", true)];
fallback.set_fetch_all_result(Ok(test_metadata.clone()));
let provider = MetadataProvider {
sources: vec![
("primary".to_string(), Box::new(primary.clone())),
("fallback".to_string(), Box::new(fallback.clone())),
],
};
let mut progress = SilentProgress;
let result = provider.fetch_all(&mut progress).unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_partial_fetch_failure() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
let fallback = Arc::new(MockMetadataSource::new("fallback", "Fallback Source"));
let test_metadata = vec![create_test_metadata("test1", false)];
primary.set_fetch_all_result(Ok(test_metadata.clone()));
primary.set_fetch_package_details_result(Err(KopiError::MetadataFetch(
"Details not available".to_string(),
)));
fallback.set_fetch_package_details_result(Ok(PackageDetails {
download_url: "https://fallback.com/jdk.tar.gz".to_string(),
checksum: Some("fallback123".to_string()),
checksum_type: Some(ChecksumType::Sha256),
}));
let provider = MetadataProvider {
sources: vec![
("primary".to_string(), Box::new(primary.clone())),
("fallback".to_string(), Box::new(fallback.clone())),
],
};
let mut progress = SilentProgress;
let mut result = provider.fetch_all(&mut progress).unwrap();
assert_eq!(result.len(), 1);
assert!(!result[0].is_complete());
let mut progress = SilentProgress;
provider
.ensure_complete(&mut result[0], &mut progress)
.unwrap();
assert!(result[0].is_complete());
assert_eq!(
result[0].download_url,
Some("https://fallback.com/jdk.tar.gz".to_string())
);
}
#[test]
fn test_empty_metadata_handling() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
primary.set_fetch_all_result(Ok(vec![]));
let provider = MetadataProvider {
sources: vec![("primary".to_string(), Box::new(primary.clone()))],
};
let mut progress = SilentProgress;
let result = provider.fetch_all(&mut progress).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_batch_ensure_complete_partial_failure() {
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
primary.set_fetch_package_details_result(Err(KopiError::MetadataFetch(
"Package not found".to_string(),
)));
let provider = MetadataProvider {
sources: vec![("primary".to_string(), Box::new(primary.clone()))],
};
let mut metadata_list = vec![
create_test_metadata("test1", false),
create_test_metadata("test2", false),
];
let mut progress = SilentProgress;
let result = provider.ensure_complete_batch(&mut metadata_list, &mut progress);
assert!(result.is_err());
}
#[test]
fn test_concurrent_source_access() {
use std::thread;
let primary = Arc::new(MockMetadataSource::new("primary", "Primary Source"));
let test_metadata = vec![create_test_metadata("test1", true)];
primary.set_fetch_all_result(Ok(test_metadata.clone()));
let provider = Arc::new(MetadataProvider {
sources: vec![("primary".to_string(), Box::new(primary.clone()))],
});
let mut handles = vec![];
for i in 0..5 {
let provider_clone = provider.clone();
let handle = thread::spawn(move || {
let mut progress = SilentProgress;
let result = provider_clone.fetch_all(&mut progress);
assert!(result.is_ok());
let data = result.unwrap();
assert_eq!(data.len(), 1);
assert_eq!(data[0].id, "test1");
i
});
handles.push(handle);
}
for handle in handles {
let thread_id = handle.join().unwrap();
assert!(thread_id < 5);
}
}
#[test]
fn test_kopi_home_expansion() {
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let kopi_home = temp_dir.path();
let sources = vec![SourceConfig::Local {
name: "local-bundled".to_string(),
enabled: true,
directory: "${KOPI_HOME}/bundled-metadata".to_string(),
archive_pattern: "*.tar.gz".to_string(),
cache_extracted: true,
}];
let metadata_config = MetadataConfig {
cache: Default::default(),
sources,
};
let result = MetadataProvider::from_metadata_config(&metadata_config, kopi_home);
assert!(result.is_ok());
}
}