use crates_docs::{
cache::{create_cache, CacheConfig},
tools::docs::DocService,
AppConfig, CratesDocsServer,
};
use std::sync::Arc;
#[tokio::test]
async fn test_cache_functionality() {
let config = CacheConfig::default();
let cache = create_cache(&config).expect("Failed to create cache");
cache
.set("test_key".to_string(), "test_value".to_string(), None)
.await
.expect("set should succeed");
let value = cache.get("test_key").await;
assert!(value.is_some());
assert_eq!(value.unwrap().as_ref(), "test_value");
cache
.set(
"expiring_key".to_string(),
"expiring_value".to_string(),
Some(std::time::Duration::from_secs(1)),
)
.await
.expect("set should succeed");
let value = cache.get("expiring_key").await;
assert!(value.is_some());
assert_eq!(value.unwrap().as_ref(), "expiring_value");
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let value = cache.get("expiring_key").await;
assert_eq!(value, None);
cache
.delete("test_key")
.await
.expect("delete should succeed");
let value = cache.get("test_key").await;
assert_eq!(value, None);
cache
.set("key1".to_string(), "value1".to_string(), None)
.await
.expect("set should succeed");
cache
.set("key2".to_string(), "value2".to_string(), None)
.await
.expect("set should succeed");
cache.clear().await.expect("clear should succeed");
assert_eq!(cache.get("key1").await, None);
assert_eq!(cache.get("key2").await, None);
}
#[test]
fn test_config_loading() {
let config = AppConfig::default();
assert_eq!(config.server.host, "127.0.0.1");
assert_eq!(config.server.port, 8080);
assert_eq!(config.server.transport_mode, "hybrid");
let validation_result = config.validate();
assert!(validation_result.is_ok());
temp_env::with_vars(
[
("CRATES_DOCS_HOST", Some("127.0.0.1")),
("CRATES_DOCS_PORT", Some("9090")),
],
|| {
let env_config = AppConfig::from_env();
assert!(env_config.is_ok());
let config = AppConfig::merge(None, Some(env_config.unwrap()));
assert_eq!(config.server.host, "127.0.0.1");
assert_eq!(config.server.port, 9090);
},
);
}
#[tokio::test]
async fn test_tool_registry() {
let config = CacheConfig::default();
let cache = create_cache(&config).expect("Failed to create cache");
let cache_arc: Arc<dyn crates_docs::cache::Cache> = Arc::from(cache);
let doc_service = Arc::new(DocService::new(cache_arc).expect("Failed to create DocService"));
let registry = crates_docs::tools::create_default_registry(&doc_service);
let tools = registry.get_tools();
assert_eq!(tools.len(), 4);
let tool_names: std::collections::HashSet<String> =
tools.iter().map(|t| t.name.clone()).collect();
assert!(tool_names.contains("lookup_crate"));
assert!(tool_names.contains("lookup_item"));
assert!(tool_names.contains("search_crates"));
assert!(tool_names.contains("health_check"));
}
#[test]
fn test_server_creation() {
let config = AppConfig::default();
let server_result = CratesDocsServer::new(config);
assert!(
server_result.is_ok(),
"Server creation failed: {:?}",
server_result.err()
);
let server = server_result.unwrap();
let server_info = server.server_info();
assert_eq!(server_info.server_info.name, "crates-docs");
assert_eq!(server_info.server_info.version, env!("CARGO_PKG_VERSION"));
assert!(
server_info.capabilities.tools.is_some(),
"Server should provide tool capabilities"
);
}
#[test]
fn test_tool_parameter_validation() {
use crates_docs::utils::validation;
assert!(validation::validate_crate_name("serde").is_ok());
assert!(validation::validate_crate_name("tokio").is_ok());
assert!(validation::validate_crate_name("reqwest").is_ok());
assert!(validation::validate_crate_name("").is_err());
assert!(validation::validate_crate_name("invalid name with spaces").is_err());
assert!(validation::validate_version("1.0.0").is_ok());
assert!(validation::validate_version("0.1.0-alpha.1").is_ok());
assert!(validation::validate_version("2.3.4-beta.5").is_ok());
assert!(validation::validate_version("").is_err());
assert!(validation::validate_version("1.0").is_ok()); assert!(validation::validate_version("invalid").is_err());
assert!(validation::validate_search_query("serde").is_ok());
assert!(validation::validate_search_query("web framework").is_ok());
assert!(validation::validate_search_query("async").is_ok());
assert!(validation::validate_search_query("").is_err());
assert!(validation::validate_search_query(" ").is_ok());
assert!(validation::validate_search_query("a").is_ok());
}
#[test]
fn test_performance_counter() {
use crates_docs::utils::metrics::PerformanceCounter;
use std::time::Duration;
let counter = PerformanceCounter::new();
let stats = counter.get_stats();
assert_eq!(stats.total_requests, 0);
assert_eq!(stats.successful_requests, 0);
assert_eq!(stats.failed_requests, 0);
assert_eq!(stats.average_response_time_ms, 0.0);
assert_eq!(stats.success_rate_percent, 0.0);
let start = counter.record_request_start();
std::thread::sleep(Duration::from_millis(10));
counter.record_request_complete(start, true);
let stats = counter.get_stats();
assert_eq!(stats.total_requests, 1);
assert_eq!(stats.successful_requests, 1);
assert_eq!(stats.failed_requests, 0);
assert!(stats.average_response_time_ms > 0.0);
assert_eq!(stats.success_rate_percent, 100.0);
let start = counter.record_request_start();
std::thread::sleep(Duration::from_millis(5));
counter.record_request_complete(start, false);
let stats = counter.get_stats();
assert_eq!(stats.total_requests, 2);
assert_eq!(stats.successful_requests, 1);
assert_eq!(stats.failed_requests, 1);
assert!(stats.average_response_time_ms > 0.0);
assert_eq!(stats.success_rate_percent, 50.0);
counter.reset();
let stats = counter.get_stats();
assert_eq!(stats.total_requests, 0);
assert_eq!(stats.successful_requests, 0);
assert_eq!(stats.failed_requests, 0);
assert_eq!(stats.average_response_time_ms, 0.0);
assert_eq!(stats.success_rate_percent, 0.0);
}
#[test]
fn test_string_utils() {
use crates_docs::utils::string;
let long_string = "This is a very long string that needs to be truncated";
let truncated = string::truncate_with_ellipsis(long_string, 20);
assert_eq!(truncated, "This is a very lo...");
assert!(truncated.len() <= 20 + 3);
let short_string = "short";
let truncated = string::truncate_with_ellipsis(short_string, 10);
assert_eq!(truncated, "short");
let exact_string = "exact length";
let truncated = string::truncate_with_ellipsis(exact_string, 5);
assert_eq!(truncated, "ex...");
}
#[test]
fn test_compression_utils() {
use crates_docs::utils::compression;
let original_data = b"This is a test string for testing compression and decompression.";
let compressed = compression::gzip_compress(original_data);
assert!(compressed.is_ok());
let compressed_data = compressed.unwrap();
assert!(!compressed_data.is_empty());
let decompressed = compression::gzip_decompress(&compressed_data);
assert!(decompressed.is_ok());
let decompressed_data = decompressed.unwrap();
assert_eq!(decompressed_data, original_data);
let invalid_data = b"not valid gzip data";
let result = compression::gzip_decompress(invalid_data);
assert!(result.is_err());
}
#[test]
fn test_http_client_builder() {
use crates_docs::utils::HttpClientBuilder;
use std::time::Duration;
let client = HttpClientBuilder::default().build();
assert!(client.is_ok());
let client = HttpClientBuilder::default()
.timeout(Duration::from_secs(30))
.connect_timeout(Duration::from_secs(10))
.pool_max_idle_per_host(20)
.user_agent("TestClient/1.0".to_string())
.enable_gzip(true)
.enable_brotli(true)
.build();
assert!(client.is_ok());
}
#[tokio::test]
async fn test_rate_limiter() {
use crates_docs::utils::RateLimiter;
use tokio::sync::SemaphorePermit;
let limiter = RateLimiter::new(2);
let permit1: Result<SemaphorePermit<'_>, crates_docs::error::Error> = limiter.acquire().await;
assert!(permit1.is_ok());
let permit2: Result<SemaphorePermit<'_>, crates_docs::error::Error> = limiter.acquire().await;
assert!(permit2.is_ok());
drop(permit1);
drop(permit2);
let permit3: Result<SemaphorePermit<'_>, crates_docs::error::Error> = limiter.acquire().await;
assert!(permit3.is_ok());
}
#[test]
fn test_time_utils() {
use chrono::Utc;
use crates_docs::utils::time;
let timestamp = time::current_timestamp_ms();
assert!(timestamp > 0);
let now = Utc::now();
let formatted = time::format_datetime(&now);
assert!(!formatted.is_empty());
assert!(formatted.contains("-"));
let start = std::time::Instant::now();
std::thread::sleep(std::time::Duration::from_millis(10));
let elapsed = time::elapsed_ms(start);
assert!(elapsed >= 10); }
#[test]
fn test_oauth_config() {
use crates_docs::server::auth::{OAuthConfig, OAuthProvider};
let default_config = OAuthConfig::default();
assert!(!default_config.enabled);
assert_eq!(default_config.client_id, None);
assert_eq!(default_config.client_secret, None);
assert_eq!(default_config.redirect_uri, None);
let config = OAuthConfig {
enabled: true,
client_id: Some("client_id".to_string()),
client_secret: Some("client_secret".to_string()),
redirect_uri: Some("http://localhost:8080/oauth/callback".to_string()),
authorization_endpoint: Some("https://github.com/login/oauth/authorize".to_string()),
token_endpoint: Some("https://github.com/login/oauth/access_token".to_string()),
scopes: vec!["read:user".to_string()],
provider: OAuthProvider::GitHub,
};
assert!(config.enabled);
assert_eq!(config.client_id, Some("client_id".to_string()));
assert_eq!(config.client_secret, Some("client_secret".to_string()));
assert_eq!(
config.redirect_uri,
Some("http://localhost:8080/oauth/callback".to_string())
);
let validation_result = config.validate();
assert!(validation_result.is_ok());
let invalid_config = OAuthConfig {
enabled: true,
client_id: None, client_secret: Some("client_secret".to_string()),
redirect_uri: Some("http://localhost:8080/oauth/callback".to_string()),
authorization_endpoint: Some("https://github.com/login/oauth/authorize".to_string()),
token_endpoint: Some("https://github.com/login/oauth/access_token".to_string()),
scopes: vec!["read:user".to_string()],
provider: OAuthProvider::GitHub,
};
let validation_result = invalid_config.validate();
assert!(validation_result.is_err());
}
#[tokio::test]
async fn test_transport_mode_stdio() {
let config = AppConfig::default();
let server = CratesDocsServer::new(config).unwrap();
let server_info = server.server_info();
assert_eq!(server_info.server_info.name, "crates-docs");
}
#[tokio::test]
async fn test_transport_mode_http() {
let mut config = AppConfig::default();
config.server.transport_mode = "http".to_string();
config.server.host = "127.0.0.1".to_string();
config.server.port = 8081;
let server = CratesDocsServer::new(config).unwrap();
let server_info = server.server_info();
assert_eq!(server_info.server_info.name, "crates-docs");
assert_eq!(server.config().server.transport_mode, "http");
}
#[tokio::test]
async fn test_transport_mode_sse() {
let mut config = AppConfig::default();
config.server.transport_mode = "sse".to_string();
config.server.host = "127.0.0.1".to_string();
config.server.port = 8082;
let server = CratesDocsServer::new(config).unwrap();
let server_info = server.server_info();
assert_eq!(server_info.server_info.name, "crates-docs");
assert_eq!(server.config().server.transport_mode, "sse");
}
#[tokio::test]
async fn test_transport_mode_hybrid() {
let mut config = AppConfig::default();
config.server.transport_mode = "hybrid".to_string();
config.server.host = "127.0.0.1".to_string();
config.server.port = 8083;
let server = CratesDocsServer::new(config).unwrap();
let server_info = server.server_info();
assert_eq!(server_info.server_info.name, "crates-docs");
assert_eq!(server.config().server.transport_mode, "hybrid");
}
#[tokio::test]
async fn test_performance_config() {
let mut config = AppConfig::default();
config.performance.http_client_pool_size = 20;
config.performance.http_client_pool_idle_timeout_secs = 120;
config.performance.http_client_connect_timeout_secs = 15;
config.performance.http_client_timeout_secs = 45;
config.performance.http_client_read_timeout_secs = 45;
config.performance.http_client_max_retries = 5;
config.performance.http_client_retry_initial_delay_ms = 200;
config.performance.http_client_retry_max_delay_ms = 20000;
config.performance.cache_max_size = 2000;
config.performance.cache_default_ttl_secs = 7200;
config.performance.rate_limit_per_second = 200;
config.performance.concurrent_request_limit = 100;
config.performance.enable_metrics = true;
config.performance.metrics_port = 9090;
let server = CratesDocsServer::new(config).unwrap();
let perf_config = &server.config().performance;
assert_eq!(perf_config.http_client_pool_size, 20);
assert_eq!(perf_config.http_client_pool_idle_timeout_secs, 120);
assert_eq!(perf_config.http_client_connect_timeout_secs, 15);
assert_eq!(perf_config.http_client_timeout_secs, 45);
assert_eq!(perf_config.http_client_read_timeout_secs, 45);
assert_eq!(perf_config.http_client_max_retries, 5);
assert_eq!(perf_config.http_client_retry_initial_delay_ms, 200);
assert_eq!(perf_config.http_client_retry_max_delay_ms, 20000);
assert_eq!(perf_config.cache_max_size, 2000);
assert_eq!(perf_config.cache_default_ttl_secs, 7200);
assert_eq!(perf_config.rate_limit_per_second, 200);
assert_eq!(perf_config.concurrent_request_limit, 100);
assert!(perf_config.enable_metrics);
assert_eq!(perf_config.metrics_port, 9090);
}