601 lines
23 KiB
Rust
601 lines
23 KiB
Rust
#[cfg(test)]
|
|
use readur::services::file_service::FileService;
|
|
#[cfg(test)]
|
|
use readur::models::Document;
|
|
#[cfg(test)]
|
|
use readur::storage::factory::create_storage_backend;
|
|
#[cfg(test)]
|
|
use readur::storage::StorageConfig;
|
|
#[cfg(test)]
|
|
use std::fs;
|
|
#[cfg(test)]
|
|
use tempfile::TempDir;
|
|
#[cfg(test)]
|
|
use uuid::Uuid;
|
|
|
|
#[cfg(test)]
|
|
async fn create_test_file_service() -> (FileService, TempDir) {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let upload_path = temp_dir.path().to_string_lossy().to_string();
|
|
let storage_config = StorageConfig::Local { upload_path: upload_path.clone() };
|
|
let storage_backend = create_storage_backend(storage_config).await.unwrap();
|
|
let service = FileService::with_storage(upload_path, storage_backend);
|
|
(service, temp_dir)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_save_file() {
|
|
let (service, _temp_dir) = create_test_file_service().await;
|
|
let filename = "test.txt";
|
|
let data = b"Hello, World!";
|
|
|
|
let result = service.save_file(filename, data).await;
|
|
assert!(result.is_ok());
|
|
|
|
let file_path = result.unwrap();
|
|
assert!(fs::metadata(&file_path).is_ok());
|
|
|
|
let saved_content = fs::read(&file_path).unwrap();
|
|
assert_eq!(saved_content, data);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_save_file_with_extension() {
|
|
let (service, _temp_dir) = create_test_file_service().await;
|
|
let filename = "document.pdf";
|
|
let data = b"PDF content";
|
|
|
|
let result = service.save_file(filename, data).await;
|
|
assert!(result.is_ok());
|
|
|
|
let file_path = result.unwrap();
|
|
assert!(file_path.ends_with(".pdf"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_save_file_without_extension() {
|
|
let (service, _temp_dir) = create_test_file_service().await;
|
|
let filename = "document";
|
|
let data = b"Some content";
|
|
|
|
let result = service.save_file(filename, data).await;
|
|
assert!(result.is_ok());
|
|
|
|
let file_path = result.unwrap();
|
|
// Should not have an extension (check just the filename part)
|
|
let filename_part = std::path::Path::new(&file_path)
|
|
.file_name()
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap();
|
|
assert!(!filename_part.contains('.'));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_create_document() {
|
|
let (service, _temp_dir) = create_test_file_service().await;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
let document = service.create_document(
|
|
"saved_file.pdf",
|
|
"original_file.pdf",
|
|
"/path/to/saved_file.pdf",
|
|
1024,
|
|
"application/pdf",
|
|
user_id,
|
|
Some("abcd1234hash".to_string()),
|
|
None, // original_created_at
|
|
None, // original_modified_at
|
|
None, // source_path
|
|
None, // source_type
|
|
None, // source_id
|
|
None, // file_permissions
|
|
None, // file_owner
|
|
None, // file_group
|
|
None, // source_metadata
|
|
);
|
|
|
|
assert_eq!(document.filename, "saved_file.pdf");
|
|
assert_eq!(document.original_filename, "original_file.pdf");
|
|
assert_eq!(document.file_path, "/path/to/saved_file.pdf");
|
|
assert_eq!(document.file_size, 1024);
|
|
assert_eq!(document.mime_type, "application/pdf");
|
|
assert_eq!(document.user_id, user_id);
|
|
assert_eq!(document.file_hash, Some("abcd1234hash".to_string()));
|
|
assert!(document.content.is_none());
|
|
assert!(document.ocr_text.is_none());
|
|
assert!(document.tags.is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_is_allowed_file_type() {
|
|
let (service, _temp_dir) = create_test_file_service().await;
|
|
let allowed_types = vec![
|
|
"pdf".to_string(),
|
|
"txt".to_string(),
|
|
"png".to_string(),
|
|
"jpg".to_string(),
|
|
];
|
|
|
|
assert!(service.is_allowed_file_type("document.pdf", &allowed_types));
|
|
assert!(service.is_allowed_file_type("text.txt", &allowed_types));
|
|
assert!(service.is_allowed_file_type("image.PNG", &allowed_types)); // Case insensitive
|
|
assert!(service.is_allowed_file_type("photo.JPG", &allowed_types)); // Case insensitive
|
|
|
|
assert!(!service.is_allowed_file_type("document.doc", &allowed_types));
|
|
assert!(!service.is_allowed_file_type("archive.zip", &allowed_types));
|
|
assert!(!service.is_allowed_file_type("noextension", &allowed_types));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_read_file() {
|
|
let (service, _temp_dir) = create_test_file_service().await;
|
|
let filename = "test.txt";
|
|
let original_data = b"Hello, World!";
|
|
|
|
let file_path = service.save_file(filename, original_data).await.unwrap();
|
|
|
|
let result = service.read_file(&file_path).await;
|
|
assert!(result.is_ok());
|
|
|
|
let read_data = result.unwrap();
|
|
assert_eq!(read_data, original_data);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_read_nonexistent_file() {
|
|
let (service, _temp_dir) = create_test_file_service().await;
|
|
let nonexistent_path = "/path/to/nonexistent/file.txt";
|
|
|
|
let result = service.read_file(nonexistent_path).await;
|
|
assert!(result.is_err());
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod file_deletion_tests {
|
|
use super::*;
|
|
use chrono::Utc;
|
|
use std::path::Path;
|
|
use std::fs;
|
|
|
|
fn create_test_document_with_files(_service: &FileService, temp_dir: &TempDir, user_id: uuid::Uuid) -> (Document, String, String, String) {
|
|
let document_id = uuid::Uuid::new_v4();
|
|
|
|
// Create main document file
|
|
let base_path = temp_dir.path().join("documents");
|
|
fs::create_dir_all(&base_path).unwrap();
|
|
let main_file_path = base_path.join("test_document.pdf");
|
|
fs::write(&main_file_path, b"PDF content").unwrap();
|
|
|
|
// Create thumbnails directory and thumbnail file with correct naming
|
|
let thumbnails_path = temp_dir.path().join("thumbnails");
|
|
fs::create_dir_all(&thumbnails_path).unwrap();
|
|
let thumbnail_path = thumbnails_path.join(format!("{}_thumb.jpg", document_id));
|
|
fs::write(&thumbnail_path, b"Thumbnail content").unwrap();
|
|
|
|
// Create processed_images directory and processed image file with correct naming
|
|
let processed_dir = temp_dir.path().join("processed_images");
|
|
fs::create_dir_all(&processed_dir).unwrap();
|
|
let processed_path = processed_dir.join(format!("{}_processed.png", document_id));
|
|
fs::write(&processed_path, b"Processed content").unwrap();
|
|
|
|
let document = Document {
|
|
id: document_id,
|
|
filename: "test_document.pdf".to_string(),
|
|
original_filename: "test_document.pdf".to_string(),
|
|
file_path: main_file_path.to_string_lossy().to_string(),
|
|
file_size: 1024,
|
|
mime_type: "application/pdf".to_string(),
|
|
content: Some("Test document content".to_string()),
|
|
ocr_text: Some("This is extracted OCR text".to_string()),
|
|
ocr_confidence: Some(95.5),
|
|
ocr_word_count: Some(150),
|
|
ocr_processing_time_ms: Some(1200),
|
|
ocr_status: Some("completed".to_string()),
|
|
ocr_error: None,
|
|
ocr_completed_at: Some(Utc::now()),
|
|
tags: vec!["test".to_string()],
|
|
created_at: Utc::now(),
|
|
updated_at: Utc::now(),
|
|
user_id,
|
|
file_hash: Some("hash123".to_string()),
|
|
original_created_at: None,
|
|
original_modified_at: None,
|
|
source_path: None,
|
|
source_type: None,
|
|
source_id: None,
|
|
file_permissions: None,
|
|
file_owner: None,
|
|
file_group: None,
|
|
source_metadata: None,
|
|
ocr_retry_count: None,
|
|
ocr_failure_reason: None,
|
|
};
|
|
|
|
(
|
|
document,
|
|
main_file_path.to_string_lossy().to_string(),
|
|
thumbnail_path.to_string_lossy().to_string(),
|
|
processed_path.to_string_lossy().to_string(),
|
|
)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_success() {
|
|
let (service, temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
|
|
let (document, main_path, thumb_path, processed_path) =
|
|
create_test_document_with_files(&service, &temp_dir, user_id);
|
|
|
|
// Verify files exist before deletion
|
|
assert!(Path::new(&main_path).exists());
|
|
assert!(Path::new(&thumb_path).exists());
|
|
assert!(Path::new(&processed_path).exists());
|
|
|
|
// Delete document files
|
|
let result = service.delete_document_files(&document).await;
|
|
assert!(result.is_ok());
|
|
|
|
// Verify main file is deleted
|
|
assert!(!Path::new(&main_path).exists());
|
|
|
|
// Verify thumbnail and processed files are deleted
|
|
assert!(!Path::new(&thumb_path).exists());
|
|
assert!(!Path::new(&processed_path).exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_main_file_missing() {
|
|
let (service, temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
|
|
let (document, main_path, thumb_path, processed_path) =
|
|
create_test_document_with_files(&service, &temp_dir, user_id);
|
|
|
|
// Delete main file manually before test
|
|
fs::remove_file(&main_path).unwrap();
|
|
assert!(!Path::new(&main_path).exists());
|
|
|
|
// Verify thumbnail and processed files still exist
|
|
assert!(Path::new(&thumb_path).exists());
|
|
assert!(Path::new(&processed_path).exists());
|
|
|
|
// Try to delete document files (should still clean up other files)
|
|
let result = service.delete_document_files(&document).await;
|
|
assert!(result.is_ok());
|
|
|
|
// Verify thumbnail and processed files are deleted despite main file missing
|
|
assert!(!Path::new(&thumb_path).exists());
|
|
assert!(!Path::new(&processed_path).exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_thumbnail_missing() {
|
|
let (service, temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
|
|
let (document, main_path, thumb_path, processed_path) =
|
|
create_test_document_with_files(&service, &temp_dir, user_id);
|
|
|
|
// Delete thumbnail file manually before test
|
|
fs::remove_file(&thumb_path).unwrap();
|
|
assert!(!Path::new(&thumb_path).exists());
|
|
|
|
// Verify other files still exist
|
|
assert!(Path::new(&main_path).exists());
|
|
assert!(Path::new(&processed_path).exists());
|
|
|
|
// Delete document files
|
|
let result = service.delete_document_files(&document).await;
|
|
assert!(result.is_ok());
|
|
|
|
// Verify main and processed files are deleted
|
|
assert!(!Path::new(&main_path).exists());
|
|
assert!(!Path::new(&processed_path).exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_processed_missing() {
|
|
let (service, temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
|
|
let (document, main_path, thumb_path, processed_path) =
|
|
create_test_document_with_files(&service, &temp_dir, user_id);
|
|
|
|
// Delete processed file manually before test
|
|
fs::remove_file(&processed_path).unwrap();
|
|
assert!(!Path::new(&processed_path).exists());
|
|
|
|
// Verify other files still exist
|
|
assert!(Path::new(&main_path).exists());
|
|
assert!(Path::new(&thumb_path).exists());
|
|
|
|
// Delete document files
|
|
let result = service.delete_document_files(&document).await;
|
|
assert!(result.is_ok());
|
|
|
|
// Verify main and thumbnail files are deleted
|
|
assert!(!Path::new(&main_path).exists());
|
|
assert!(!Path::new(&thumb_path).exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_all_missing() {
|
|
let (service, _temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
|
|
let document = Document {
|
|
id: uuid::Uuid::new_v4(),
|
|
filename: "nonexistent.pdf".to_string(),
|
|
original_filename: "nonexistent.pdf".to_string(),
|
|
file_path: "/nonexistent/path/nonexistent.pdf".to_string(),
|
|
file_size: 1024,
|
|
mime_type: "application/pdf".to_string(),
|
|
content: None,
|
|
ocr_text: None,
|
|
ocr_confidence: None,
|
|
ocr_word_count: None,
|
|
ocr_processing_time_ms: None,
|
|
ocr_status: Some("pending".to_string()),
|
|
ocr_error: None,
|
|
ocr_completed_at: None,
|
|
tags: vec![],
|
|
created_at: Utc::now(),
|
|
updated_at: Utc::now(),
|
|
user_id,
|
|
file_hash: None,
|
|
original_created_at: None,
|
|
original_modified_at: None,
|
|
source_path: None,
|
|
source_type: None,
|
|
source_id: None,
|
|
file_permissions: None,
|
|
file_owner: None,
|
|
file_group: None,
|
|
source_metadata: None,
|
|
ocr_retry_count: None,
|
|
ocr_failure_reason: None,
|
|
};
|
|
|
|
// Try to delete nonexistent files (should not fail)
|
|
let result = service.delete_document_files(&document).await;
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_with_different_extensions() {
|
|
let (service, temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
let document_id = uuid::Uuid::new_v4();
|
|
|
|
// Create main document file in documents directory
|
|
let documents_path = temp_dir.path().join("documents");
|
|
fs::create_dir_all(&documents_path).unwrap();
|
|
let main_file_path = documents_path.join("test_image.png");
|
|
fs::write(&main_file_path, b"PNG content").unwrap();
|
|
|
|
// Create thumbnail in thumbnails directory with correct naming
|
|
let thumbnails_path = temp_dir.path().join("thumbnails");
|
|
fs::create_dir_all(&thumbnails_path).unwrap();
|
|
let thumbnail_path = thumbnails_path.join(format!("{}_thumb.jpg", document_id));
|
|
fs::write(&thumbnail_path, b"Thumbnail content").unwrap();
|
|
|
|
// Create processed image in processed_images directory with correct naming
|
|
let processed_dir = temp_dir.path().join("processed_images");
|
|
fs::create_dir_all(&processed_dir).unwrap();
|
|
let processed_path = processed_dir.join(format!("{}_processed.png", document_id));
|
|
fs::write(&processed_path, b"Processed content").unwrap();
|
|
|
|
let document = Document {
|
|
id: document_id,
|
|
filename: "test_image.png".to_string(),
|
|
original_filename: "test_image.png".to_string(),
|
|
file_path: main_file_path.to_string_lossy().to_string(),
|
|
file_size: 2048,
|
|
mime_type: "image/png".to_string(),
|
|
content: None,
|
|
ocr_text: Some("Image OCR text".to_string()),
|
|
ocr_confidence: Some(88.2),
|
|
ocr_word_count: Some(25),
|
|
ocr_processing_time_ms: Some(800),
|
|
ocr_status: Some("completed".to_string()),
|
|
ocr_error: None,
|
|
ocr_completed_at: Some(Utc::now()),
|
|
tags: vec!["image".to_string()],
|
|
created_at: Utc::now(),
|
|
updated_at: Utc::now(),
|
|
user_id,
|
|
file_hash: Some("imagehash456".to_string()),
|
|
original_created_at: None,
|
|
original_modified_at: None,
|
|
source_path: None,
|
|
source_type: None,
|
|
source_id: None,
|
|
file_permissions: None,
|
|
file_owner: None,
|
|
file_group: None,
|
|
source_metadata: None,
|
|
ocr_retry_count: None,
|
|
ocr_failure_reason: None,
|
|
};
|
|
|
|
// Verify files exist
|
|
assert!(Path::new(&main_file_path).exists());
|
|
assert!(Path::new(&thumbnail_path).exists());
|
|
assert!(Path::new(&processed_path).exists());
|
|
|
|
// Delete document files
|
|
let result = service.delete_document_files(&document).await;
|
|
assert!(result.is_ok());
|
|
|
|
// Verify all files are deleted
|
|
assert!(!Path::new(&main_file_path).exists());
|
|
assert!(!Path::new(&thumbnail_path).exists());
|
|
assert!(!Path::new(&processed_path).exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_partial_failure_continues() {
|
|
let (service, temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
let document_id = uuid::Uuid::new_v4();
|
|
|
|
// Create main file in documents directory
|
|
let documents_path = temp_dir.path().join("documents");
|
|
fs::create_dir_all(&documents_path).unwrap();
|
|
let main_file_path = documents_path.join("readonly_document.pdf");
|
|
fs::write(&main_file_path, b"PDF content").unwrap();
|
|
|
|
// Create thumbnail file in thumbnails directory with correct naming
|
|
let thumbnails_path = temp_dir.path().join("thumbnails");
|
|
fs::create_dir_all(&thumbnails_path).unwrap();
|
|
let thumbnail_path = thumbnails_path.join(format!("{}_thumb.jpg", document_id));
|
|
fs::write(&thumbnail_path, b"Thumbnail content").unwrap();
|
|
|
|
let document = Document {
|
|
id: document_id,
|
|
filename: "readonly_document.pdf".to_string(),
|
|
original_filename: "readonly_document.pdf".to_string(),
|
|
file_path: main_file_path.to_string_lossy().to_string(),
|
|
file_size: 1024,
|
|
mime_type: "application/pdf".to_string(),
|
|
content: Some("Test content".to_string()),
|
|
ocr_text: None,
|
|
ocr_confidence: None,
|
|
ocr_word_count: None,
|
|
ocr_processing_time_ms: None,
|
|
ocr_status: Some("pending".to_string()),
|
|
ocr_error: None,
|
|
ocr_completed_at: None,
|
|
tags: vec![],
|
|
created_at: Utc::now(),
|
|
updated_at: Utc::now(),
|
|
user_id,
|
|
file_hash: Some("hash789".to_string()),
|
|
original_created_at: None,
|
|
original_modified_at: None,
|
|
source_path: None,
|
|
source_type: None,
|
|
source_id: None,
|
|
file_permissions: None,
|
|
file_owner: None,
|
|
file_group: None,
|
|
source_metadata: None,
|
|
ocr_retry_count: None,
|
|
ocr_failure_reason: None,
|
|
};
|
|
|
|
// Verify files exist
|
|
assert!(Path::new(&main_file_path).exists());
|
|
assert!(Path::new(&thumbnail_path).exists());
|
|
|
|
// Delete document files (should succeed even if some files can't be deleted)
|
|
let result = service.delete_document_files(&document).await;
|
|
assert!(result.is_ok());
|
|
|
|
// At minimum, the function should attempt to delete all files
|
|
// and not fail completely if one file can't be deleted
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_with_no_extension() {
|
|
let (service, temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
|
|
let base_path = temp_dir.path().join("documents");
|
|
fs::create_dir_all(&base_path).unwrap();
|
|
|
|
// Create document with no extension
|
|
let main_file_path = base_path.join("document_no_ext");
|
|
fs::write(&main_file_path, b"Content without extension").unwrap();
|
|
|
|
let document = Document {
|
|
id: uuid::Uuid::new_v4(),
|
|
filename: "document_no_ext".to_string(),
|
|
original_filename: "document_no_ext".to_string(),
|
|
file_path: main_file_path.to_string_lossy().to_string(),
|
|
file_size: 512,
|
|
mime_type: "text/plain".to_string(),
|
|
content: Some("Plain text content".to_string()),
|
|
ocr_text: None,
|
|
ocr_confidence: None,
|
|
ocr_word_count: None,
|
|
ocr_processing_time_ms: None,
|
|
ocr_status: Some("not_applicable".to_string()),
|
|
ocr_error: None,
|
|
ocr_completed_at: None,
|
|
tags: vec!["text".to_string()],
|
|
created_at: Utc::now(),
|
|
updated_at: Utc::now(),
|
|
user_id,
|
|
file_hash: Some("texthash".to_string()),
|
|
original_created_at: None,
|
|
original_modified_at: None,
|
|
source_path: None,
|
|
source_type: None,
|
|
source_id: None,
|
|
file_permissions: None,
|
|
file_owner: None,
|
|
file_group: None,
|
|
source_metadata: None,
|
|
ocr_retry_count: None,
|
|
ocr_failure_reason: None,
|
|
};
|
|
|
|
// Verify file exists
|
|
assert!(Path::new(&main_file_path).exists());
|
|
|
|
// Delete document files
|
|
let result = service.delete_document_files(&document).await;
|
|
assert!(result.is_ok());
|
|
|
|
// Verify file is deleted
|
|
assert!(!Path::new(&main_file_path).exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_document_files_concurrent_calls() {
|
|
let (service, temp_dir) = create_test_file_service().await;
|
|
let user_id = uuid::Uuid::new_v4();
|
|
|
|
let (document, main_path, thumb_path, processed_path) =
|
|
create_test_document_with_files(&service, &temp_dir, user_id);
|
|
|
|
// Verify files exist
|
|
assert!(Path::new(&main_path).exists());
|
|
assert!(Path::new(&thumb_path).exists());
|
|
assert!(Path::new(&processed_path).exists());
|
|
|
|
// Call delete_document_files concurrently
|
|
let service_clone = service.clone();
|
|
let document_clone = document.clone();
|
|
|
|
let task1 = tokio::spawn(async move {
|
|
service.delete_document_files(&document).await
|
|
});
|
|
|
|
let task2 = tokio::spawn(async move {
|
|
service_clone.delete_document_files(&document_clone).await
|
|
});
|
|
|
|
// Both calls should complete successfully now that FileService handles concurrent deletions
|
|
let result1 = task1.await.expect("Task 1 should complete");
|
|
let result2 = task2.await.expect("Task 2 should complete");
|
|
|
|
// Both deletion attempts should succeed - the improved FileService handles
|
|
// "file not found" errors gracefully as they indicate successful deletion by another task
|
|
assert!(result1.is_ok(), "First deletion task should succeed: {:?}", result1);
|
|
assert!(result2.is_ok(), "Second deletion task should succeed: {:?}", result2);
|
|
|
|
// Verify files are deleted
|
|
assert!(!Path::new(&main_path).exists());
|
|
assert!(!Path::new(&thumb_path).exists());
|
|
assert!(!Path::new(&processed_path).exists());
|
|
}
|
|
} |