test: add comprehensive unit tests for storage and auth

- LocalStorage tests: put/get, list, stat, health_check, nested dirs
- S3Storage tests with wiremock HTTP mocking
- Auth/htpasswd tests: loading, validation, public paths
- Token lifecycle tests: create, verify, expire, revoke

Total: 75 tests passing
This commit is contained in:
2026-01-26 00:02:09 +00:00
parent 26237bff2d
commit 7ed3444d86
6 changed files with 973 additions and 12 deletions

View File

@@ -129,3 +129,112 @@ impl StorageBackend for LocalStorage {
"local"
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn test_put_and_get() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
storage.put("test/key", b"test data").await.unwrap();
let data = storage.get("test/key").await.unwrap();
assert_eq!(&*data, b"test data");
}
#[tokio::test]
async fn test_get_not_found() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
let result = storage.get("nonexistent").await;
assert!(matches!(result, Err(StorageError::NotFound)));
}
#[tokio::test]
async fn test_list_with_prefix() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
storage.put("docker/image/blob1", b"data1").await.unwrap();
storage.put("docker/image/blob2", b"data2").await.unwrap();
storage.put("maven/artifact", b"data3").await.unwrap();
let docker_keys = storage.list("docker/").await;
assert_eq!(docker_keys.len(), 2);
assert!(docker_keys.iter().all(|k| k.starts_with("docker/")));
let all_keys = storage.list("").await;
assert_eq!(all_keys.len(), 3);
}
#[tokio::test]
async fn test_stat() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
storage.put("test", b"12345").await.unwrap();
let meta = storage.stat("test").await.unwrap();
assert_eq!(meta.size, 5);
assert!(meta.modified > 0);
}
#[tokio::test]
async fn test_stat_not_found() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
let meta = storage.stat("nonexistent").await;
assert!(meta.is_none());
}
#[tokio::test]
async fn test_health_check() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
assert!(storage.health_check().await);
}
#[tokio::test]
async fn test_health_check_creates_directory() {
let temp_dir = TempDir::new().unwrap();
let new_path = temp_dir.path().join("new_storage");
let storage = LocalStorage::new(new_path.to_str().unwrap());
assert!(!new_path.exists());
assert!(storage.health_check().await);
assert!(new_path.exists());
}
#[tokio::test]
async fn test_nested_directory_creation() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
storage.put("a/b/c/d/e/file", b"deep").await.unwrap();
let data = storage.get("a/b/c/d/e/file").await.unwrap();
assert_eq!(&*data, b"deep");
}
#[tokio::test]
async fn test_overwrite() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
storage.put("key", b"original").await.unwrap();
storage.put("key", b"updated").await.unwrap();
let data = storage.get("key").await.unwrap();
assert_eq!(&*data, b"updated");
}
#[test]
fn test_backend_name() {
let temp_dir = TempDir::new().unwrap();
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
assert_eq!(storage.backend_name(), "local");
}
}

View File

@@ -127,3 +127,184 @@ impl StorageBackend for S3Storage {
"s3"
}
}
#[cfg(test)]
mod tests {
use super::*;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[tokio::test]
async fn test_put_success() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("PUT"))
.and(path("/test-bucket/test-key"))
.respond_with(ResponseTemplate::new(200))
.mount(&mock_server)
.await;
let result = storage.put("test-key", b"data").await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_put_failure() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("PUT"))
.and(path("/test-bucket/test-key"))
.respond_with(ResponseTemplate::new(500))
.mount(&mock_server)
.await;
let result = storage.put("test-key", b"data").await;
assert!(matches!(result, Err(StorageError::Network(_))));
}
#[tokio::test]
async fn test_get_success() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("GET"))
.and(path("/test-bucket/test-key"))
.respond_with(ResponseTemplate::new(200).set_body_bytes(b"test data".to_vec()))
.mount(&mock_server)
.await;
let data = storage.get("test-key").await.unwrap();
assert_eq!(&*data, b"test data");
}
#[tokio::test]
async fn test_get_not_found() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("GET"))
.and(path("/test-bucket/missing"))
.respond_with(ResponseTemplate::new(404))
.mount(&mock_server)
.await;
let result = storage.get("missing").await;
assert!(matches!(result, Err(StorageError::NotFound)));
}
#[tokio::test]
async fn test_list() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
let xml_response = r#"<?xml version="1.0"?>
<ListBucketResult>
<Key>docker/image1</Key>
<Key>docker/image2</Key>
<Key>maven/artifact</Key>
</ListBucketResult>"#;
Mock::given(method("GET"))
.and(path("/test-bucket"))
.respond_with(ResponseTemplate::new(200).set_body_string(xml_response))
.mount(&mock_server)
.await;
let keys = storage.list("docker/").await;
assert_eq!(keys.len(), 2);
assert!(keys.iter().all(|k| k.starts_with("docker/")));
}
#[tokio::test]
async fn test_stat_success() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("HEAD"))
.and(path("/test-bucket/test-key"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("content-length", "1234")
.insert_header("last-modified", "Sun, 06 Nov 1994 08:49:37 GMT"),
)
.mount(&mock_server)
.await;
let meta = storage.stat("test-key").await.unwrap();
assert_eq!(meta.size, 1234);
assert!(meta.modified > 0);
}
#[tokio::test]
async fn test_stat_not_found() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("HEAD"))
.and(path("/test-bucket/missing"))
.respond_with(ResponseTemplate::new(404))
.mount(&mock_server)
.await;
let meta = storage.stat("missing").await;
assert!(meta.is_none());
}
#[tokio::test]
async fn test_health_check_healthy() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("HEAD"))
.and(path("/test-bucket"))
.respond_with(ResponseTemplate::new(200))
.mount(&mock_server)
.await;
assert!(storage.health_check().await);
}
#[tokio::test]
async fn test_health_check_bucket_not_found_is_ok() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("HEAD"))
.and(path("/test-bucket"))
.respond_with(ResponseTemplate::new(404))
.mount(&mock_server)
.await;
// 404 is OK for health check (bucket may be empty)
assert!(storage.health_check().await);
}
#[tokio::test]
async fn test_health_check_server_error() {
let mock_server = MockServer::start().await;
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
Mock::given(method("HEAD"))
.and(path("/test-bucket"))
.respond_with(ResponseTemplate::new(500))
.mount(&mock_server)
.await;
assert!(!storage.health_check().await);
}
#[test]
fn test_backend_name() {
let storage = S3Storage::new("http://localhost:9000", "bucket");
assert_eq!(storage.backend_name(), "s3");
}
#[test]
fn test_parse_s3_keys() {
let xml = r#"<Key>docker/a</Key><Key>docker/b</Key><Key>maven/c</Key>"#;
let keys = S3Storage::parse_s3_keys(xml, "docker/");
assert_eq!(keys, vec!["docker/a", "docker/b"]);
}
}