#![cfg(all(feature = "storage", feature = "http"))]
use bsv_rs::storage::{
get_url_for_file, is_valid_url, StorageUploader, StorageUploaderConfig, UploadableFile,
};
use wiremock::matchers::{body_string_contains, header, method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[tokio::test]
async fn test_uploader_publish_file_success() {
let mock_server = MockServer::start().await;
let file_data = b"Hello, World!";
let file = UploadableFile::new(file_data.to_vec(), "text/plain");
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.and(header("content-type", "application/json"))
.and(body_string_contains("\"fileSize\":13"))
.and(body_string_contains("\"retentionPeriod\":10080"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {},
"amount": 100
})))
.expect(1)
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.and(header("content-type", "text/plain"))
.respond_with(ResponseTemplate::new(200).set_body_string("OK"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.publish_file(&file, None).await;
assert!(result.is_ok(), "Expected success, got: {:?}", result);
let upload_result = result.unwrap();
assert!(
upload_result.published,
"File should be marked as published"
);
assert!(
is_valid_url(&upload_result.uhrp_url),
"Result should contain a valid UHRP URL, got: {}",
upload_result.uhrp_url
);
let expected_url = get_url_for_file(file_data).unwrap();
assert_eq!(
upload_result.uhrp_url, expected_url,
"UHRP URL should be derived from the file's SHA-256 hash"
);
}
#[tokio::test]
async fn test_uploader_publish_file_with_custom_retention() {
let mock_server = MockServer::start().await;
let file = UploadableFile::new(b"test".to_vec(), "text/plain");
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.and(body_string_contains("\"retentionPeriod\":1440"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {}
})))
.expect(1)
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.respond_with(ResponseTemplate::new(200).set_body_string("OK"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.publish_file(&file, Some(1440)).await;
assert!(
result.is_ok(),
"Expected success with custom retention, got: {:?}",
result
);
}
#[tokio::test]
async fn test_uploader_publish_file_with_required_headers() {
let mock_server = MockServer::start().await;
let file = UploadableFile::new(b"data".to_vec(), "application/octet-stream");
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {
"x-amz-acl": "public-read",
"x-custom-header": "custom-value"
}
})))
.expect(1)
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.and(header("x-amz-acl", "public-read"))
.and(header("x-custom-header", "custom-value"))
.and(header("content-type", "application/octet-stream"))
.respond_with(ResponseTemplate::new(200).set_body_string("OK"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_ok(),
"Expected success with required headers, got: {:?}",
result
);
}
#[tokio::test]
async fn test_uploader_publish_file_upload_info_400() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let file = UploadableFile::new(b"test".to_vec(), "text/plain");
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_err(),
"Expected failure for 400, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("HTTP 400"),
"Error should mention HTTP 400, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_publish_file_upload_info_401() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(401).set_body_string("Unauthorized"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let file = UploadableFile::new(b"test".to_vec(), "text/plain");
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_err(),
"Expected failure for 401, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("HTTP 401"),
"Error should mention HTTP 401, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_publish_file_upload_info_413() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(413).set_body_string("Payload Too Large"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let file = UploadableFile::new(vec![0u8; 1024], "application/octet-stream");
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_err(),
"Expected failure for 413, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("HTTP 413"),
"Error should mention HTTP 413, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_publish_file_upload_info_500() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let file = UploadableFile::new(b"test".to_vec(), "text/plain");
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_err(),
"Expected failure for 500, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("HTTP 500"),
"Error should mention HTTP 500, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_publish_file_upload_info_error_status_in_json() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "error",
"uploadURL": "",
"requiredHeaders": {}
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let file = UploadableFile::new(b"test".to_vec(), "text/plain");
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_err(),
"Expected failure for error status in JSON, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("Upload route returned an error"),
"Error should mention upload route error, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_publish_file_put_step_500() {
let mock_server = MockServer::start().await;
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {}
})))
.expect(1)
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.respond_with(ResponseTemplate::new(500).set_body_string("Storage backend error"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let file = UploadableFile::new(b"test".to_vec(), "text/plain");
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_err(),
"Expected failure for PUT step 500, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("File upload failed"),
"Error should mention file upload failure, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_publish_file_small_file() {
let mock_server = MockServer::start().await;
let file_data = vec![42u8]; let file = UploadableFile::new(file_data.clone(), "application/octet-stream");
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.and(body_string_contains("\"fileSize\":1"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {}
})))
.expect(1)
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.respond_with(ResponseTemplate::new(200).set_body_string("OK"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_ok(),
"Expected success for small file, got: {:?}",
result
);
let upload_result = result.unwrap();
let expected_url = get_url_for_file(&file_data).unwrap();
assert_eq!(upload_result.uhrp_url, expected_url);
}
#[tokio::test]
async fn test_uploader_publish_file_empty_content() {
let mock_server = MockServer::start().await;
let file_data: Vec<u8> = vec![];
let file = UploadableFile::new(file_data.clone(), "application/octet-stream");
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.and(body_string_contains("\"fileSize\":0"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {}
})))
.expect(1)
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.respond_with(ResponseTemplate::new(200).set_body_string("OK"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_ok(),
"Expected success for empty file, got: {:?}",
result
);
let upload_result = result.unwrap();
let expected_url = get_url_for_file(&file_data).unwrap();
assert_eq!(upload_result.uhrp_url, expected_url);
}
#[tokio::test]
async fn test_uploader_find_file_success() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/find"))
.and(query_param("uhrpUrl", "uhrp://testurl123"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"data": {
"name": "cdn/abc123",
"size": "1024",
"mimeType": "text/plain",
"expiryTime": 1700000000
}
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.find_file("uhrp://testurl123").await;
assert!(result.is_ok(), "Expected success, got: {:?}", result);
let file_data = result.unwrap();
assert!(file_data.is_some(), "Expected file data to be present");
let data = file_data.unwrap();
assert_eq!(data.name, Some("cdn/abc123".to_string()));
assert_eq!(data.size, "1024");
assert_eq!(data.mime_type, "text/plain");
assert_eq!(data.expiry_time, 1700000000);
}
#[tokio::test]
async fn test_uploader_find_file_error_status_in_json() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/find"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "error",
"code": "ERR_NOT_FOUND",
"description": "File not found"
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.find_file("uhrp://unknown-hash").await;
assert!(result.is_err(), "Expected failure, got: {:?}", result);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("ERR_NOT_FOUND"),
"Error should contain error code, got: {}",
err_msg
);
assert!(
err_msg.contains("File not found"),
"Error should contain description, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_find_file_http_error() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/find"))
.respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.find_file("uhrp://some-hash").await;
assert!(
result.is_err(),
"Expected failure for HTTP 500, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("HTTP 500"),
"Error should mention HTTP 500, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_list_uploads_success() {
let mock_server = MockServer::start().await;
let mock_uploads = serde_json::json!([
{ "uhrpUrl": "uhrp://hash1", "expiryTime": 111111 },
{ "uhrpUrl": "uhrp://hash2", "expiryTime": 222222 }
]);
Mock::given(method("GET"))
.and(path("/list"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploads": mock_uploads
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.list_uploads().await;
assert!(result.is_ok(), "Expected success, got: {:?}", result);
let uploads = result.unwrap();
assert!(uploads.is_array(), "Expected an array of uploads");
let arr = uploads.as_array().unwrap();
assert_eq!(arr.len(), 2, "Expected 2 uploads");
assert_eq!(arr[0]["uhrpUrl"], "uhrp://hash1");
assert_eq!(arr[1]["expiryTime"], 222222);
}
#[tokio::test]
async fn test_uploader_list_uploads_empty() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/list"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success"
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.list_uploads().await;
assert!(result.is_ok(), "Expected success, got: {:?}", result);
let uploads = result.unwrap();
assert!(uploads.is_array(), "Expected an array");
assert_eq!(
uploads.as_array().unwrap().len(),
0,
"Expected empty array when no uploads field"
);
}
#[tokio::test]
async fn test_uploader_list_uploads_error_status() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/list"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "error",
"code": "ERR_INTERNAL",
"description": "Something broke"
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.list_uploads().await;
assert!(result.is_err(), "Expected failure, got: {:?}", result);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("ERR_INTERNAL"),
"Error should contain code, got: {}",
err_msg
);
assert!(
err_msg.contains("Something broke"),
"Error should contain description, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_list_uploads_http_500() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/list"))
.respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.list_uploads().await;
assert!(
result.is_err(),
"Expected failure for HTTP 500, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("HTTP 500"),
"Error should mention HTTP 500, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_renew_file_success() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/renew"))
.and(header("content-type", "application/json"))
.and(body_string_contains("\"uhrpUrl\""))
.and(body_string_contains("\"additionalMinutes\":1440"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"prevExpiryTime": 1700000000,
"newExpiryTime": 1700086400,
"amount": 50
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.renew_file("uhrp://some-hash", 1440).await;
assert!(result.is_ok(), "Expected success, got: {:?}", result);
let renewal = result.unwrap();
assert_eq!(renewal.status, "success");
assert_eq!(renewal.previous_expiry, 1700000000);
assert_eq!(renewal.new_expiry, 1700086400);
assert_eq!(renewal.amount, 50);
assert!(renewal.is_success());
}
#[tokio::test]
async fn test_uploader_renew_file_error_status_in_json() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/renew"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "error",
"code": "ERR_CANT_RENEW",
"description": "Failed to renew"
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.renew_file("uhrp://some-hash", 30).await;
assert!(result.is_err(), "Expected failure, got: {:?}", result);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("ERR_CANT_RENEW"),
"Error should contain error code, got: {}",
err_msg
);
assert!(
err_msg.contains("Failed to renew"),
"Error should contain description, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_renew_file_http_404() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/renew"))
.respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.renew_file("uhrp://ghost", 10).await;
assert!(
result.is_err(),
"Expected failure for 404, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("HTTP 404"),
"Error should mention HTTP 404, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_renew_file_http_500() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/renew"))
.respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.renew_file("uhrp://some-hash", 60).await;
assert!(
result.is_err(),
"Expected failure for 500, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("HTTP 500"),
"Error should mention HTTP 500, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_uploader_request_body_verification() {
let mock_server = MockServer::start().await;
let file_data = b"This is a test file with some content";
let file = UploadableFile::new(file_data.to_vec(), "text/plain");
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.and(body_string_contains("\"fileSize\":37"))
.and(body_string_contains("\"retentionPeriod\":10080"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {}
})))
.expect(1)
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.respond_with(ResponseTemplate::new(200).set_body_string("OK"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_ok(),
"Expected success for body verification, got: {:?}",
result
);
}
#[tokio::test]
async fn test_uploader_content_type_header_verification() {
let mock_server = MockServer::start().await;
let file = UploadableFile::new(b"image data".to_vec(), "image/png");
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {}
})))
.expect(1)
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.and(header("content-type", "image/png"))
.respond_with(ResponseTemplate::new(200).set_body_string("OK"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_ok(),
"Expected success for content-type verification, got: {:?}",
result
);
}
use bsv_rs::storage::{StorageDownloader, StorageDownloaderConfig};
#[tokio::test]
async fn test_downloader_resolve_rejects_invalid_url() {
let downloader = StorageDownloader::default();
let result = downloader.resolve("https://example.com").await;
assert!(
result.is_err(),
"Expected error for invalid UHRP URL, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("Invalid UHRP URL"),
"Error should mention invalid URL, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_downloader_resolve_rejects_empty_url() {
let downloader = StorageDownloader::default();
let result = downloader.resolve("").await;
assert!(
result.is_err(),
"Expected error for empty URL, got: {:?}",
result
);
}
#[tokio::test]
async fn test_downloader_resolve_rejects_http_url() {
let downloader = StorageDownloader::default();
let result = downloader.resolve("http://localhost:8080/file").await;
assert!(
result.is_err(),
"Expected error for HTTP URL, got: {:?}",
result
);
}
#[tokio::test]
async fn test_downloader_download_rejects_invalid_url() {
let downloader = StorageDownloader::default();
let result = downloader.download("not-a-valid-uhrp-url").await;
assert!(
result.is_err(),
"Expected error for invalid UHRP URL in download, got: {:?}",
result
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("Invalid UHRP URL"),
"Error should mention invalid URL, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_downloader_resolve_rejects_bad_checksum() {
let downloader = StorageDownloader::default();
let bad_url = "uhrp://XUU7cTfy6fA6q2neLDmzPqJnGB6o18PXKoGaWLPrH1SeWLKgdCKq";
let result = downloader.resolve(bad_url).await;
assert!(
result.is_err(),
"Expected error for bad checksum URL, got: {:?}",
result
);
}
#[tokio::test]
async fn test_downloader_config_custom_timeout() {
let config = StorageDownloaderConfig {
timeout_ms: Some(5000),
..Default::default()
};
let downloader = StorageDownloader::new(config);
assert!(std::mem::size_of_val(&downloader) > 0);
}
#[tokio::test]
async fn test_downloader_config_no_timeout() {
let config = StorageDownloaderConfig {
timeout_ms: None,
..Default::default()
};
let downloader = StorageDownloader::new(config);
assert!(std::mem::size_of_val(&downloader) > 0);
}
#[tokio::test]
async fn test_downloader_uhrp_url_parsing() {
use bsv_rs::storage::get_hash_from_url;
let test_content = b"test content for download verification";
let url = get_url_for_file(test_content).unwrap();
assert!(is_valid_url(&url));
let hash = get_hash_from_url(&url).unwrap();
let expected_hash = bsv_rs::primitives::hash::sha256(test_content);
assert_eq!(
hash, expected_hash,
"Hash extracted from UHRP URL should match SHA-256 of original content"
);
}
#[tokio::test]
async fn test_uploader_renew_request_body() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/renew"))
.and(body_string_contains(
"\"uhrpUrl\":\"uhrp://test-file-hash\"",
))
.and(body_string_contains("\"additionalMinutes\":2880"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"prevExpiryTime": 100,
"newExpiryTime": 200,
"amount": 10
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.renew_file("uhrp://test-file-hash", 2880).await;
assert!(
result.is_ok(),
"Expected success for renew body verification, got: {:?}",
result
);
}
#[tokio::test]
async fn test_uploader_various_mime_types() {
let mime_types = vec![
("text/plain", b"Hello" as &[u8]),
("application/json", b"{\"key\": \"value\"}"),
("image/jpeg", &[0xFF, 0xD8, 0xFF, 0xE0]),
("application/pdf", b"%PDF-1.4"),
];
for (mime_type, data) in mime_types {
let mock_server = MockServer::start().await;
let presigned_url = format!("{}/put-file", mock_server.uri());
Mock::given(method("POST"))
.and(path("/upload"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"uploadURL": presigned_url,
"requiredHeaders": {}
})))
.mount(&mock_server)
.await;
Mock::given(method("PUT"))
.and(path("/put-file"))
.and(header("content-type", mime_type))
.respond_with(ResponseTemplate::new(200).set_body_string("OK"))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let file = UploadableFile::new(data.to_vec(), mime_type);
let result = uploader.publish_file(&file, None).await;
assert!(
result.is_ok(),
"Expected success for MIME type '{}', got: {:?}",
mime_type,
result
);
}
}
#[tokio::test]
async fn test_uploader_find_file_success_no_data() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/find"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"status": "success",
"data": null
})))
.expect(1)
.mount(&mock_server)
.await;
let config = StorageUploaderConfig::new(mock_server.uri());
let uploader = StorageUploader::new(config);
let result = uploader.find_file("uhrp://nonexistent").await;
assert!(result.is_ok(), "Expected success, got: {:?}", result);
let file_data = result.unwrap();
assert!(
file_data.is_none(),
"Expected None when data field is null, got: {:?}",
file_data
);
}