use anyhow::Result; use readur::models::{Document, DocumentResponse}; use readur::test_utils::{TestContext, TestAuthHelper}; use chrono::Utc; use serde_json::Value; use uuid::Uuid; #[cfg(test)] fn create_test_document(user_id: Uuid) -> Document { Document { id: Uuid::new_v4(), filename: "test_document.pdf".to_string(), original_filename: "test_document.pdf".to_string(), file_path: "/uploads/test_document.pdf".to_string(), file_size: 1024000, mime_type: "application/pdf".to_string(), content: Some("Test document content".to_string()), ocr_text: Some("This is extracted OCR text from the test document.".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(), "document".to_string()], created_at: Utc::now(), updated_at: Utc::now(), user_id, file_hash: Some(format!("{:x}", Uuid::new_v4().as_u128())), 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, } } #[cfg(test)] fn create_test_document_without_ocr(user_id: Uuid) -> Document { Document { id: Uuid::new_v4(), filename: "test_no_ocr.txt".to_string(), original_filename: "test_no_ocr.txt".to_string(), file_path: "/uploads/test_no_ocr.txt".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("pending".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("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321".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, } } #[cfg(test)] fn create_test_document_with_ocr_error(user_id: Uuid) -> Document { Document { id: Uuid::new_v4(), filename: "test_error.pdf".to_string(), original_filename: "test_error.pdf".to_string(), file_path: "/uploads/test_error.pdf".to_string(), file_size: 2048000, mime_type: "application/pdf".to_string(), content: None, ocr_text: None, ocr_confidence: None, ocr_word_count: None, ocr_processing_time_ms: Some(5000), ocr_status: Some("failed".to_string()), ocr_error: Some("Failed to process document: corrupted file".to_string()), ocr_completed_at: Some(Utc::now()), tags: vec!["error".to_string()], created_at: Utc::now(), updated_at: Utc::now(), user_id, file_hash: Some("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".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, } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_document_response_conversion() { let user_id = Uuid::new_v4(); let document = create_test_document(user_id); let response: DocumentResponse = document.clone().into(); assert_eq!(response.id, document.id); assert_eq!(response.filename, document.filename); assert_eq!(response.original_filename, document.original_filename); assert_eq!(response.file_size, document.file_size); assert_eq!(response.mime_type, document.mime_type); assert_eq!(response.tags, document.tags); assert_eq!(response.has_ocr_text, true); assert_eq!(response.ocr_confidence, document.ocr_confidence); assert_eq!(response.ocr_word_count, document.ocr_word_count); assert_eq!(response.ocr_processing_time_ms, document.ocr_processing_time_ms); assert_eq!(response.ocr_status, document.ocr_status); } #[tokio::test] async fn test_document_response_conversion_no_ocr() { let user_id = Uuid::new_v4(); let document = create_test_document_without_ocr(user_id); let response: DocumentResponse = document.clone().into(); assert_eq!(response.has_ocr_text, false); assert_eq!(response.ocr_confidence, None); assert_eq!(response.ocr_word_count, None); } #[test] fn test_ocr_response_structure() { let user_id = Uuid::new_v4(); let document = create_test_document(user_id); // Test that OCR response fields match expected structure let ocr_response = serde_json::json!({ "id": document.id.to_string(), "filename": document.filename, "has_ocr_text": document.ocr_text.is_some(), "ocr_text": document.ocr_text, "ocr_confidence": document.ocr_confidence, "ocr_word_count": document.ocr_word_count, "ocr_processing_time_ms": document.ocr_processing_time_ms, "ocr_status": document.ocr_status, "ocr_error": document.ocr_error, "ocr_completed_at": document.ocr_completed_at }); assert!(ocr_response.is_object()); assert_eq!(ocr_response["id"], document.id.to_string()); assert_eq!(ocr_response["filename"], document.filename); assert_eq!(ocr_response["has_ocr_text"], true); assert_eq!(ocr_response["ocr_text"], document.ocr_text.unwrap()); assert_eq!(ocr_response["ocr_confidence"], document.ocr_confidence.unwrap()); assert_eq!(ocr_response["ocr_word_count"], document.ocr_word_count.unwrap()); assert_eq!(ocr_response["ocr_processing_time_ms"], document.ocr_processing_time_ms.unwrap()); assert_eq!(ocr_response["ocr_status"], document.ocr_status.unwrap()); assert_eq!(ocr_response["ocr_error"], Value::Null); } #[test] fn test_ocr_response_with_error() { let user_id = Uuid::new_v4(); let document = create_test_document_with_ocr_error(user_id); let ocr_response = serde_json::json!({ "document_id": document.id, "filename": document.filename, "has_ocr_text": document.ocr_text.is_some(), "ocr_text": document.ocr_text, "ocr_confidence": document.ocr_confidence, "ocr_word_count": document.ocr_word_count, "ocr_processing_time_ms": document.ocr_processing_time_ms, "ocr_status": document.ocr_status, "ocr_error": document.ocr_error, "ocr_completed_at": document.ocr_completed_at }); assert_eq!(ocr_response["has_ocr_text"], false); assert_eq!(ocr_response["ocr_text"], Value::Null); assert_eq!(ocr_response["ocr_status"], "failed"); assert_eq!(ocr_response["ocr_error"], "Failed to process document: corrupted file"); } #[test] fn test_ocr_confidence_validation() { let user_id = Uuid::new_v4(); let mut document = create_test_document(user_id); // Test valid confidence range document.ocr_confidence = Some(95.5); assert!(document.ocr_confidence.unwrap() >= 0.0 && document.ocr_confidence.unwrap() <= 100.0); // Test edge cases document.ocr_confidence = Some(0.0); assert!(document.ocr_confidence.unwrap() >= 0.0); document.ocr_confidence = Some(100.0); assert!(document.ocr_confidence.unwrap() <= 100.0); } #[test] fn test_ocr_word_count_validation() { let user_id = Uuid::new_v4(); let mut document = create_test_document(user_id); // Test positive word count document.ocr_word_count = Some(150); assert!(document.ocr_word_count.unwrap() > 0); // Test zero word count (valid for empty documents) document.ocr_word_count = Some(0); assert!(document.ocr_word_count.unwrap() >= 0); } #[test] fn test_ocr_processing_time_validation() { let user_id = Uuid::new_v4(); let mut document = create_test_document(user_id); // Test positive processing time document.ocr_processing_time_ms = Some(1200); assert!(document.ocr_processing_time_ms.unwrap() > 0); // Test very fast processing document.ocr_processing_time_ms = Some(50); assert!(document.ocr_processing_time_ms.unwrap() > 0); // Test slow processing document.ocr_processing_time_ms = Some(30000); // 30 seconds assert!(document.ocr_processing_time_ms.unwrap() > 0); } #[test] fn test_ocr_status_values() { let user_id = Uuid::new_v4(); let mut document = create_test_document(user_id); // Test valid status values let valid_statuses = vec!["pending", "processing", "completed", "failed"]; for status in valid_statuses { document.ocr_status = Some(status.to_string()); assert!(matches!( document.ocr_status.as_deref(), Some("pending") | Some("processing") | Some("completed") | Some("failed") )); } } #[test] fn test_document_with_complete_ocr_data() { let user_id = Uuid::new_v4(); let document = create_test_document(user_id); // Verify all OCR fields are properly set for a completed document assert!(document.ocr_text.is_some()); assert!(document.ocr_confidence.is_some()); assert!(document.ocr_word_count.is_some()); assert!(document.ocr_processing_time_ms.is_some()); assert_eq!(document.ocr_status.as_deref(), Some("completed")); assert!(document.ocr_error.is_none()); assert!(document.ocr_completed_at.is_some()); } #[test] fn test_document_with_failed_ocr() { let user_id = Uuid::new_v4(); let document = create_test_document_with_ocr_error(user_id); // Verify failed OCR document has appropriate fields assert!(document.ocr_text.is_none()); assert!(document.ocr_confidence.is_none()); assert!(document.ocr_word_count.is_none()); assert_eq!(document.ocr_status.as_deref(), Some("failed")); assert!(document.ocr_error.is_some()); assert!(document.ocr_completed_at.is_some()); // Should still have completion time } #[test] fn test_document_mime_type_ocr_eligibility() { let user_id = Uuid::new_v4(); // Test OCR-eligible file types let ocr_eligible_types = vec![ "application/pdf", "image/png", "image/jpeg", "image/jpg", "image/tiff", "image/bmp" ]; for mime_type in ocr_eligible_types { let mut document = create_test_document(user_id); document.mime_type = mime_type.to_string(); // These types should typically have OCR processing assert!(document.mime_type.contains("pdf") || document.mime_type.starts_with("image/")); } // Test non-OCR file types let mut text_document = create_test_document_without_ocr(user_id); text_document.mime_type = "text/plain".to_string(); // Text files typically don't need OCR assert_eq!(text_document.mime_type, "text/plain"); assert!(text_document.ocr_text.is_none()); } #[test] fn test_ocr_text_content_validation() { let user_id = Uuid::new_v4(); let document = create_test_document(user_id); if let Some(ocr_text) = &document.ocr_text { // Test that OCR text is not empty assert!(!ocr_text.trim().is_empty()); // Test that OCR text is reasonable length assert!(ocr_text.len() > 0); assert!(ocr_text.len() < 100000); // Reasonable upper limit } } } #[cfg(test)] mod document_deletion_tests { use super::*; use readur::test_utils::TestContext; use readur::models::{UserRole, User, Document, AuthProvider, CreateUser}; use chrono::Utc; use uuid::Uuid; #[tokio::test] async fn test_delete_document_as_owner() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create test user and document let user_data = CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); let document = super::create_test_document(user.id); let document = db.create_document(document).await.expect("Failed to create document"); // Delete document as owner let result = db .delete_document(document.id, user.id, user.role) .await .expect("Failed to delete document"); // Verify document was deleted assert!(result); // Verify document no longer exists in database let found_doc = db .get_document_by_id(document.id, user.id, user.role) .await .expect("Database query failed"); assert!(found_doc.is_none()); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_delete_document_as_admin() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create regular user and their document let user_data = CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); let document = super::create_test_document(user.id); let document = db.create_document(document).await.expect("Failed to create document"); // Create admin user let admin_data = CreateUser { username: format!("adminuser_{}", Uuid::new_v4()), email: format!("admin_{}@example.com", Uuid::new_v4()), password: "adminpass123".to_string(), role: Some(UserRole::Admin), }; let admin = db.create_user(admin_data).await.expect("Failed to create admin"); // Delete document as admin let result = db .delete_document(document.id, admin.id, admin.role) .await .expect("Failed to delete document as admin"); // Verify document was deleted assert!(result); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_delete_document_unauthorized() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create two regular users let user1_data = CreateUser { username: format!("testuser1_{}", Uuid::new_v4()), email: format!("test1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user1 = db.create_user(user1_data).await.expect("Failed to create user1"); let user2_data = CreateUser { username: format!("testuser2_{}", Uuid::new_v4()), email: format!("test2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user2 = db.create_user(user2_data).await.expect("Failed to create user2"); // Create document owned by user1 let document = super::create_test_document(user1.id); let document = db.create_document(document).await.expect("Failed to create document"); // Try to delete document as user2 (should fail) let result = db .delete_document(document.id, user2.id, user2.role) .await .expect("Database query failed"); // Verify document was not deleted assert!(!result); // Verify document still exists let found_doc = db .get_document_by_id(document.id, user1.id, user1.role) .await .expect("Database query failed"); assert!(found_doc.is_some()); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_delete_nonexistent_document() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; let nonexistent_id = Uuid::new_v4(); // Try to delete nonexistent document let result = ctx.state.db .delete_document(nonexistent_id, user.user_response.id, user.user_response.role) .await .expect("Database query failed"); // Verify nothing was deleted assert!(!result); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_documents_as_owner() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; // Create multiple documents let doc1 = create_test_document(user.user_response.id); let doc1 = ctx.state.db.create_document(doc1).await.expect("Failed to create document"); let doc2 = create_test_document(user.user_response.id); let doc2 = ctx.state.db.create_document(doc2).await.expect("Failed to create document"); let doc3 = create_test_document(user.user_response.id); let doc3 = ctx.state.db.create_document(doc3).await.expect("Failed to create document"); let document_ids = vec![doc1.id, doc2.id, doc3.id]; // Delete documents as owner let result = ctx.state.db .bulk_delete_documents(&document_ids, user.user_response.id, user.user_response.role) .await .expect("Failed to bulk delete documents"); // Verify all documents were deleted let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 3); assert_eq!(failed_ids.len(), 0); assert!(deleted_ids.contains(&doc1.id)); assert!(deleted_ids.contains(&doc2.id)); assert!(deleted_ids.contains(&doc3.id)); // Verify documents no longer exist for doc_id in document_ids { let found_doc = ctx.state.db .get_document_by_id(doc_id, user.user_response.id, user.user_response.role) .await .expect("Database query failed"); assert!(found_doc.is_none()); } Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_documents_as_admin() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); // Create regular user and their documents let user = auth_helper.create_test_user().await; let doc1 = create_test_document(user.user_response.id); let doc1 = ctx.state.db.create_document(doc1).await.expect("Failed to create document"); let doc2 = create_test_document(user.user_response.id); let doc2 = ctx.state.db.create_document(doc2).await.expect("Failed to create document"); // Create admin user let admin = auth_helper.create_admin_user().await; let document_ids = vec![doc1.id, doc2.id]; // Delete documents as admin let result = ctx.state.db .bulk_delete_documents(&document_ids, admin.user_response.id, admin.user_response.role) .await .expect("Failed to bulk delete documents as admin"); // Verify all documents were deleted let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 2); assert_eq!(failed_ids.len(), 0); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_documents_mixed_ownership() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create two regular users let user1_data = CreateUser { username: format!("testuser1_{}", Uuid::new_v4()), email: format!("test1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user1 = db.create_user(user1_data).await.expect("Failed to create user1"); let user2_data = CreateUser { username: format!("testuser2_{}", Uuid::new_v4()), email: format!("test2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user2 = db.create_user(user2_data).await.expect("Failed to create user2"); // Create documents for both users let doc1_user1 = create_test_document(user1.id); let doc1_user1 = ctx.state.db.create_document(doc1_user1).await.expect("Failed to create document"); let doc2_user1 = create_test_document(user1.id); let doc2_user1 = ctx.state.db.create_document(doc2_user1).await.expect("Failed to create document"); let doc1_user2 = create_test_document(user2.id); let doc1_user2 = ctx.state.db.create_document(doc1_user2).await.expect("Failed to create document"); let document_ids = vec![doc1_user1.id, doc2_user1.id, doc1_user2.id]; // Try to delete all documents as user1 (should only delete their own) let result = ctx.state.db .bulk_delete_documents(&document_ids, user1.id, user1.role) .await .expect("Failed to bulk delete documents"); // Verify only user1's documents were deleted let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 2); assert_eq!(failed_ids.len(), 1); assert!(deleted_ids.contains(&doc1_user1.id)); assert!(deleted_ids.contains(&doc2_user1.id)); assert!(failed_ids.contains(&doc1_user2.id)); // Verify user2's document still exists let found_doc = ctx.state.db .get_document_by_id(doc1_user2.id, user2.id, user2.role) .await .expect("Database query failed"); assert!(found_doc.is_some()); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_documents_empty_list() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; let empty_ids: Vec = vec![]; // Delete empty list of documents let result = ctx.state.db .bulk_delete_documents(&empty_ids, user.user_response.id, user.user_response.role) .await .expect("Failed to bulk delete empty list"); // Verify empty result let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 0); assert_eq!(failed_ids.len(), 0); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_documents_nonexistent_ids() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; // Create one real document let real_doc = create_test_document(user.user_response.id); let real_doc = ctx.state.db.create_document(real_doc).await.expect("Failed to create document"); // Mix of real and nonexistent IDs let document_ids = vec![real_doc.id, Uuid::new_v4(), Uuid::new_v4()]; // Delete documents (should only delete the real one) let result = ctx.state.db .bulk_delete_documents(&document_ids, user.user_response.id, user.user_response.role) .await .expect("Failed to bulk delete documents"); // Verify only the real document was deleted let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 1); assert_eq!(failed_ids.len(), 2); assert!(deleted_ids.contains(&real_doc.id)); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_documents_partial_authorization() { // Create regular user and admin let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; let admin = auth_helper.create_admin_user().await; // Create documents for both users let user_doc_doc = create_test_document(user.user_response.id); let user_doc = ctx.state.db.create_document(user_doc_doc).await.expect("Failed to create document"); let admin_doc_doc = create_test_document(admin.user_response.id); let admin_doc = ctx.state.db.create_document(admin_doc_doc).await.expect("Failed to create document"); let document_ids = vec![user_doc.id, admin_doc.id]; // Admin should be able to delete both let result = ctx.state.db .bulk_delete_documents(&document_ids, admin.user_response.id, admin.user_response.role) .await .expect("Failed to bulk delete documents as admin"); let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 2); assert_eq!(failed_ids.len(), 0); // Recreate documents for user test let user_doc2_doc = create_test_document(user.user_response.id); let user_doc2 = ctx.state.db.create_document(user_doc2_doc).await.expect("Failed to create document"); let admin_doc2_doc = create_test_document(admin.user_response.id); let admin_doc2 = ctx.state.db.create_document(admin_doc2_doc).await.expect("Failed to create document"); let document_ids2 = vec![user_doc2.id, admin_doc2.id]; // Regular user should only delete their own let result2 = ctx.state.db .bulk_delete_documents(&document_ids2, user.user_response.id, user.user_response.role) .await .expect("Failed to bulk delete documents as user"); let (deleted_ids2, failed_ids2) = result2; assert_eq!(deleted_ids2.len(), 1); assert_eq!(failed_ids2.len(), 1); assert!(deleted_ids2.contains(&user_doc2.id)); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } } #[cfg(test)] mod rbac_deletion_tests { use super::*; use readur::test_utils::TestContext; use readur::models::{UserRole, CreateUser}; use uuid::Uuid; #[tokio::test] async fn test_user_can_delete_own_document() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; let user_data = CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); let document = super::create_test_document(user.id); let document = db.create_document(document).await.expect("Failed to create document"); // User should be able to delete their own document let result = db .delete_document(document.id, user.id, user.role) .await .expect("Failed to delete document"); assert!(result); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_user_cannot_delete_other_user_document() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create users using direct database approach let user1_data = CreateUser { username: format!("testuser1_{}", Uuid::new_v4()), email: format!("test1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user1 = db.create_user(user1_data).await.expect("Failed to create user1"); let user2_data = CreateUser { username: format!("testuser2_{}", Uuid::new_v4()), email: format!("test2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user2 = db.create_user(user2_data).await.expect("Failed to create user2"); let document = create_test_document(user1.id); let document = ctx.state.db.create_document(document).await.expect("Failed to create document"); // User2 should NOT be able to delete user1's document let result = ctx.state.db .delete_document(document.id, user2.id, user2.role) .await .expect("Database query failed"); assert!(!result); // Verify document still exists let found_doc = ctx.state.db .get_document_by_id(document.id, user1.id, user1.role) .await .expect("Database query failed"); assert!(found_doc.is_some()); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_admin_can_delete_any_document() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create users using direct database approach let user_data = CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); let admin_data = CreateUser { username: format!("testadmin_{}", Uuid::new_v4()), email: format!("admin_{}@example.com", Uuid::new_v4()), password: "adminpass123".to_string(), role: Some(UserRole::Admin), }; let admin = db.create_user(admin_data).await.expect("Failed to create admin"); let user_document = create_test_document(user.id); let user_document = ctx.state.db.create_document(user_document).await.expect("Failed to create document"); let admin_document = create_test_document(admin.id); let admin_document = ctx.state.db.create_document(admin_document).await.expect("Failed to create document"); // Admin should be able to delete user's document let result1 = ctx.state.db .delete_document(user_document.id, admin.id, admin.role) .await .expect("Failed to delete user document as admin"); assert!(result1); // Admin should be able to delete their own document let result2 = ctx.state.db .delete_document(admin_document.id, admin.id, admin.role) .await .expect("Failed to delete admin document as admin"); assert!(result2); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_respects_ownership() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create users using direct database approach let user1_data = CreateUser { username: format!("testuser1_{}", Uuid::new_v4()), email: format!("test1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user1 = db.create_user(user1_data).await.expect("Failed to create user1"); let user2_data = CreateUser { username: format!("testuser2_{}", Uuid::new_v4()), email: format!("test2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user2 = db.create_user(user2_data).await.expect("Failed to create user2"); // Create documents for both users let user1_doc1_doc = create_test_document(user1.id); let user1_doc1 = ctx.state.db.create_document(user1_doc1_doc).await.expect("Failed to create document"); let user1_doc2_doc = create_test_document(user1.id); let user1_doc2 = ctx.state.db.create_document(user1_doc2_doc).await.expect("Failed to create document"); let user2_doc1_doc = create_test_document(user2.id); let user2_doc1 = ctx.state.db.create_document(user2_doc1_doc).await.expect("Failed to create document"); let user2_doc2_doc = create_test_document(user2.id); let user2_doc2 = ctx.state.db.create_document(user2_doc2_doc).await.expect("Failed to create document"); let all_document_ids = vec![ user1_doc1.id, user1_doc2.id, user2_doc1.id, user2_doc2.id ]; // User1 tries to delete all documents (should only delete their own) let result = ctx.state.db .bulk_delete_documents(&all_document_ids, user1.id, user1.role) .await .expect("Failed to bulk delete documents"); // Should only delete user1's documents let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 2); assert_eq!(failed_ids.len(), 2); assert!(deleted_ids.contains(&user1_doc1.id)); assert!(deleted_ids.contains(&user1_doc2.id)); assert!(failed_ids.contains(&user2_doc1.id)); assert!(failed_ids.contains(&user2_doc2.id)); // Verify user2's documents still exist let user2_doc1_exists = ctx.state.db .get_document_by_id(user2_doc1.id, user2.id, user2.role) .await .expect("Database query failed"); assert!(user2_doc1_exists.is_some()); let user2_doc2_exists = ctx.state.db .get_document_by_id(user2_doc2.id, user2.id, user2.role) .await .expect("Database query failed"); assert!(user2_doc2_exists.is_some()); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_admin_bulk_delete_all_documents() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create users using direct database approach let user1_data = CreateUser { username: format!("testuser1_{}", Uuid::new_v4()), email: format!("test1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user1 = db.create_user(user1_data).await.expect("Failed to create user1"); let user2_data = CreateUser { username: format!("testuser2_{}", Uuid::new_v4()), email: format!("test2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user2 = db.create_user(user2_data).await.expect("Failed to create user2"); let admin_data = CreateUser { username: format!("testadmin_{}", Uuid::new_v4()), email: format!("admin_{}@example.com", Uuid::new_v4()), password: "adminpass123".to_string(), role: Some(UserRole::Admin), }; let admin = db.create_user(admin_data).await.expect("Failed to create admin"); // Create documents for all users let user1_doc_doc = create_test_document(user1.id); let user1_doc = ctx.state.db.create_document(user1_doc_doc).await.expect("Failed to create document"); let user2_doc_doc = create_test_document(user2.id); let user2_doc = ctx.state.db.create_document(user2_doc_doc).await.expect("Failed to create document"); let admin_doc_doc = create_test_document(admin.id); let admin_doc = ctx.state.db.create_document(admin_doc_doc).await.expect("Failed to create document"); let all_document_ids = vec![user1_doc.id, user2_doc.id, admin_doc.id]; // Admin should be able to delete all documents let result = ctx.state.db .bulk_delete_documents(&all_document_ids, admin.id, admin.role) .await .expect("Failed to bulk delete documents as admin"); // Should delete all documents let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 3); assert_eq!(failed_ids.len(), 0); assert!(deleted_ids.contains(&user1_doc.id)); assert!(deleted_ids.contains(&user2_doc.id)); assert!(deleted_ids.contains(&admin_doc.id)); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_role_escalation_prevention() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create users using direct database approach let user_data = CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); let admin_data = CreateUser { username: format!("testadmin_{}", Uuid::new_v4()), email: format!("admin_{}@example.com", Uuid::new_v4()), password: "adminpass123".to_string(), role: Some(UserRole::Admin), }; let admin = db.create_user(admin_data).await.expect("Failed to create admin"); let admin_document_doc = create_test_document(admin.id); let admin_document = ctx.state.db.create_document(admin_document_doc).await.expect("Failed to create document"); // Regular user should NOT be able to delete admin's document // even if they somehow know the document ID let result = ctx.state.db .delete_document(admin_document.id, user.id, user.role) .await .expect("Database query failed"); assert!(!result); // Verify admin's document still exists let found_doc = ctx.state.db .get_document_by_id(admin_document.id, admin.id, admin.role) .await .expect("Database query failed"); assert!(found_doc.is_some()); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_cross_tenant_isolation() { // Create users that could represent different tenants/organizations let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create tenant users using direct database approach let tenant1_user1_data = CreateUser { username: format!("tenant1_user1_{}", Uuid::new_v4()), email: format!("tenant1_user1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let tenant1_user1 = db.create_user(tenant1_user1_data).await.expect("Failed to create tenant1_user1"); let tenant1_user2_data = CreateUser { username: format!("tenant1_user2_{}", Uuid::new_v4()), email: format!("tenant1_user2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let tenant1_user2 = db.create_user(tenant1_user2_data).await.expect("Failed to create tenant1_user2"); let tenant2_user1_data = CreateUser { username: format!("tenant2_user1_{}", Uuid::new_v4()), email: format!("tenant2_user1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let tenant2_user1 = db.create_user(tenant2_user1_data).await.expect("Failed to create tenant2_user1"); let tenant2_user2_data = CreateUser { username: format!("tenant2_user2_{}", Uuid::new_v4()), email: format!("tenant2_user2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let tenant2_user2 = db.create_user(tenant2_user2_data).await.expect("Failed to create tenant2_user2"); // Create documents for each tenant let tenant1_doc1_doc = create_test_document(tenant1_user1.id); let tenant1_doc1 = ctx.state.db.create_document(tenant1_doc1_doc).await.expect("Failed to create document"); let tenant1_doc2_doc = create_test_document(tenant1_user2.id); let tenant1_doc2 = ctx.state.db.create_document(tenant1_doc2_doc).await.expect("Failed to create document"); let tenant2_doc1_doc = create_test_document(tenant2_user1.id); let tenant2_doc1 = ctx.state.db.create_document(tenant2_doc1_doc).await.expect("Failed to create document"); let tenant2_doc2_doc = create_test_document(tenant2_user2.id); let tenant2_doc2 = ctx.state.db.create_document(tenant2_doc2_doc).await.expect("Failed to create document"); // Tenant1 user should not be able to delete tenant2 documents let result1 = ctx.state.db .delete_document(tenant2_doc1.id, tenant1_user1.id, tenant1_user1.role) .await .expect("Database query failed"); assert!(!result1); let result2 = ctx.state.db .delete_document(tenant2_doc2.id, tenant1_user2.id, tenant1_user2.role) .await .expect("Database query failed"); assert!(!result2); // Tenant2 user should not be able to delete tenant1 documents let result3 = ctx.state.db .delete_document(tenant1_doc1.id, tenant2_user1.id, tenant2_user1.role) .await .expect("Database query failed"); assert!(!result3); let result4 = ctx.state.db .delete_document(tenant1_doc2.id, tenant2_user2.id, tenant2_user2.role) .await .expect("Database query failed"); assert!(!result4); // Verify all documents still exist for (doc_id, owner_id, owner_role) in [ (tenant1_doc1.id, tenant1_user1.id, tenant1_user1.role), (tenant1_doc2.id, tenant1_user2.id, tenant1_user2.role), (tenant2_doc1.id, tenant2_user1.id, tenant2_user1.role), (tenant2_doc2.id, tenant2_user2.id, tenant2_user2.role), ] { let found_doc = ctx.state.db .get_document_by_id(doc_id, owner_id, owner_role) .await .expect("Database query failed"); assert!(found_doc.is_some()); } Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_permission_consistency_single_vs_bulk() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create users using direct database approach let user1_data = CreateUser { username: format!("testuser1_{}", Uuid::new_v4()), email: format!("test1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user1 = db.create_user(user1_data).await.expect("Failed to create user1"); let user2_data = CreateUser { username: format!("testuser2_{}", Uuid::new_v4()), email: format!("test2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user2 = db.create_user(user2_data).await.expect("Failed to create user2"); let _user1_doc_doc = create_test_document(user1.id); let _user1_doc = ctx.state.db.create_document(_user1_doc_doc).await.expect("Failed to create document"); let user2_doc_doc = create_test_document(user2.id); let user2_doc = ctx.state.db.create_document(user2_doc_doc).await.expect("Failed to create document"); // Test single deletion permissions let single_delete_result = ctx.state.db .delete_document(user2_doc.id, user1.id, user1.role) .await .expect("Database query failed"); assert!(!single_delete_result); // Should fail // Test bulk deletion permissions with same document let user2_doc2_doc = create_test_document(user2.id); let user2_doc2 = ctx.state.db.create_document(user2_doc2_doc).await.expect("Failed to create document"); let bulk_delete_result = ctx.state.db .bulk_delete_documents(&vec![user2_doc2.id], user1.id, user1.role) .await .expect("Database query failed"); let (deleted_ids, failed_ids) = bulk_delete_result; assert_eq!(deleted_ids.len(), 0); // Should delete nothing assert_eq!(failed_ids.len(), 1); // Verify both documents still exist let doc1_exists = ctx.state.db .get_document_by_id(user2_doc.id, user2.id, user2.role) .await .expect("Database query failed"); assert!(doc1_exists.is_some()); let doc2_exists = ctx.state.db .get_document_by_id(user2_doc2.id, user2.id, user2.role) .await .expect("Database query failed"); assert!(doc2_exists.is_some()); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_admin_permission_inheritance() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create users using direct database approach let user_data = CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); let admin_data = CreateUser { username: format!("testadmin_{}", Uuid::new_v4()), email: format!("admin_{}@example.com", Uuid::new_v4()), password: "adminpass123".to_string(), role: Some(UserRole::Admin), }; let admin = db.create_user(admin_data).await.expect("Failed to create admin"); let user_doc_doc = create_test_document(user.id); let user_doc = ctx.state.db.create_document(user_doc_doc).await.expect("Failed to create document"); // Admin should have all permissions that a regular user has, plus more // Test that admin can delete user's document (admin-specific permission) let admin_delete_result = ctx.state.db .delete_document(user_doc.id, admin.id, admin.role) .await .expect("Failed to delete as admin"); assert!(admin_delete_result); // Create another document to test admin's own document deletion let admin_doc_doc = create_test_document(admin.id); let admin_doc = ctx.state.db.create_document(admin_doc_doc).await.expect("Failed to create document"); let admin_own_delete_result = ctx.state.db .delete_document(admin_doc.id, admin.id, admin.role) .await .expect("Failed to delete admin's own document"); assert!(admin_own_delete_result); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[test] fn test_role_based_logic_unit_tests() { let user_role = UserRole::User; let admin_role = UserRole::Admin; let user_id = Uuid::new_v4(); let other_user_id = Uuid::new_v4(); let admin_id = Uuid::new_v4(); // Test user permissions logic assert!(user_id == user_id || user_role == UserRole::Admin); // Can delete own assert!(!(other_user_id == user_id || user_role == UserRole::Admin)); // Cannot delete other's // Test admin permissions logic assert!(user_id == admin_id || admin_role == UserRole::Admin); // Can delete user's (admin privilege) assert!(other_user_id == admin_id || admin_role == UserRole::Admin); // Can delete any (admin privilege) assert!(admin_id == admin_id || admin_role == UserRole::Admin); // Can delete own } #[test] fn test_role_comparison() { assert_eq!(UserRole::User, UserRole::User); assert_eq!(UserRole::Admin, UserRole::Admin); assert_ne!(UserRole::User, UserRole::Admin); assert_ne!(UserRole::Admin, UserRole::User); } } #[cfg(test)] mod deletion_error_handling_tests { use super::*; use readur::test_utils::{TestContext, TestAuthHelper}; use uuid::Uuid; #[tokio::test] async fn test_delete_with_invalid_uuid() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create user using direct database approach let user_data = readur::models::CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(readur::models::UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); // Use malformed UUID (this test assumes the function handles UUID parsing) let invalid_uuid = Uuid::nil(); // Use nil UUID as "invalid" let result = ctx.state.db .delete_document(invalid_uuid, user.id, user.role) .await .expect("Database query should not fail for invalid UUID"); // Should return None for non-existent document assert!(!result); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_delete_with_sql_injection_attempt() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create user using direct database approach let user_data = readur::models::CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(readur::models::UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); let document_doc = create_test_document(user.id); let document = ctx.state.db.create_document(document_doc).await.expect("Failed to create document"); // Test with legitimate document ID - SQLx should prevent injection let result = ctx.state.db .delete_document(document.id, user.id, user.role) .await .expect("Query should execute safely"); assert!(result); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_with_duplicate_ids() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let db = &ctx.state.db; // Create user using direct database approach let user_data = readur::models::CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(readur::models::UserRole::User), }; let user = db.create_user(user_data).await.expect("Failed to create user"); let document_doc = create_test_document(user.id); let document = ctx.state.db.create_document(document_doc).await.expect("Failed to create document"); // Include the same document ID multiple times let duplicate_ids = vec![document.id, document.id, document.id]; let result = ctx.state.db .bulk_delete_documents(&duplicate_ids, user.id, user.role) .await .expect("Bulk delete should handle duplicates"); // Should only delete the document once, but subsequent attempts fail let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 1); assert_eq!(failed_ids.len(), 2); // Two failed attempts on already-deleted document assert!(deleted_ids.contains(&document.id)); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_with_extremely_large_request() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; // Create a large number of document IDs (mostly non-existent) let mut large_id_list = Vec::new(); // Add one real document let real_document_doc = create_test_document(user.user_response.id); let real_document = ctx.state.db.create_document(real_document_doc).await.expect("Failed to create document"); large_id_list.push(real_document.id); // Add many fake UUIDs for _ in 0..499 { large_id_list.push(Uuid::new_v4()); } let result = ctx.state.db .bulk_delete_documents(&large_id_list, user.user_response.id, user.user_response.role) .await .expect("Should handle large requests"); // Should only delete the one real document let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 1); assert_eq!(failed_ids.len(), 499); assert!(deleted_ids.contains(&real_document.id)); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_concurrent_deletion_same_document() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; let document_doc = create_test_document(user.user_response.id); let document = ctx.state.db.create_document(document_doc).await.expect("Failed to create document"); // Create multiple handles to the same database connection pool let db1 = ctx.state.db.clone(); let db2 = ctx.state.db.clone(); // Attempt concurrent deletions let doc_id = document.id; let user_id = user.user_response.id; let user_role = user.user_response.role; let task1 = tokio::spawn(async move { db1.delete_document(doc_id, user_id, user_role).await }); let task2 = tokio::spawn(async move { db2.delete_document(doc_id, user_id, user_role).await }); let result1 = task1.await.unwrap().expect("First deletion should succeed"); let result2 = task2.await.unwrap().expect("Second deletion should not error"); // One should succeed, one should return false let success_count = [result1, result2] .iter() .filter(|&&x| x) .count(); assert_eq!(success_count, 1, "Exactly one deletion should succeed"); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_delete_document_with_foreign_key_constraints() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; let document_doc = create_test_document(user.user_response.id); let document = ctx.state.db.create_document(document_doc).await.expect("Failed to create document"); // If there are foreign key relationships (like document_labels), // test that CASCADE deletion works properly // Delete the document let result = ctx.state.db .delete_document(document.id, user.user_response.id, user.user_response.role) .await .expect("Deletion should handle foreign key constraints"); assert!(result); // Verify related records are also deleted (if any exist) // This would depend on the actual schema relationships Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_with_mixed_permissions_and_errors() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { // Create users using direct database approach let user1_data = readur::models::CreateUser { username: format!("testuser1_{}", Uuid::new_v4()), email: format!("test1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(readur::models::UserRole::User), }; let user1 = ctx.state.db.create_user(user1_data).await.expect("Failed to create user1"); let user2_data = readur::models::CreateUser { username: format!("testuser2_{}", Uuid::new_v4()), email: format!("test2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(readur::models::UserRole::User), }; let user2 = ctx.state.db.create_user(user2_data).await.expect("Failed to create user2"); // Create mix of documents let user1_doc_doc = create_test_document(user1.id); let user1_doc = ctx.state.db.create_document(user1_doc_doc).await.expect("Failed to create document"); let user2_doc_doc = create_test_document(user2.id); let user2_doc = ctx.state.db.create_document(user2_doc_doc).await.expect("Failed to create document"); let nonexistent_id = Uuid::new_v4(); let mixed_ids = vec![user1_doc.id, user2_doc.id, nonexistent_id]; // User1 attempts to delete all (should only delete their own) let result = ctx.state.db .bulk_delete_documents(&mixed_ids, user1.id, user1.role) .await .expect("Should handle mixed permissions gracefully"); // Should only delete user1's document let (deleted_ids, failed_ids) = result; assert_eq!(deleted_ids.len(), 1); assert_eq!(failed_ids.len(), 2); assert!(deleted_ids.contains(&user1_doc.id)); // Verify user2's document still exists let user2_doc_exists = ctx.state.db .get_document_by_id(user2_doc.id, user2.id, user2.role) .await .expect("Query should succeed"); assert!(user2_doc_exists.is_some()); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[test] fn test_error_message_consistency() { // Test that error conditions produce consistent, user-friendly messages // Test various error scenarios that might occur let not_found_msg = "Document not found"; let unauthorized_msg = "Not authorized to delete this document"; let invalid_request_msg = "Invalid request parameters"; let server_error_msg = "Internal server error"; // Verify messages are not empty and don't contain sensitive information assert!(!not_found_msg.is_empty()); assert!(!unauthorized_msg.is_empty()); assert!(!invalid_request_msg.is_empty()); assert!(!server_error_msg.is_empty()); // Verify messages don't leak technical details assert!(!not_found_msg.to_lowercase().contains("sql")); assert!(!not_found_msg.to_lowercase().contains("database")); assert!(!unauthorized_msg.to_lowercase().contains("user_id")); assert!(!server_error_msg.to_lowercase().contains("panic")); } #[test] fn test_uuid_edge_cases() { // Test various UUID edge cases let nil_uuid = Uuid::nil(); let max_uuid = Uuid::max(); let random_uuid = Uuid::new_v4(); // Verify UUIDs are valid assert_eq!(nil_uuid.to_string(), "00000000-0000-0000-0000-000000000000"); assert_eq!(max_uuid.to_string(), "ffffffff-ffff-ffff-ffff-ffffffffffff"); assert!(random_uuid.to_string().len() == 36); // Standard UUID string length // Test UUID parsing edge cases assert!(Uuid::parse_str("invalid-uuid").is_err()); assert!(Uuid::parse_str("").is_err()); assert!(Uuid::parse_str("00000000-0000-0000-0000-000000000000").is_ok()); } #[tokio::test] async fn test_delete_after_user_deletion() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; let document_doc = create_test_document(user.user_response.id); let document = ctx.state.db.create_document(document_doc).await.expect("Failed to create document"); // Delete the user first (simulating cascade deletion scenarios) sqlx::query("DELETE FROM users WHERE id = $1") .bind(user.user_response.id) .execute(&ctx.state.db.pool) .await .expect("User deletion should succeed"); // Attempt to delete document after user is gone // This depends on how foreign key constraints are set up let result = ctx.state.db .delete_document(document.id, user.user_response.id, user.user_response.role) .await; // The behavior here depends on FK constraints: // - If CASCADE: document might already be deleted // - If RESTRICT: document still exists but operation might fail // Test should verify consistent behavior match result { Ok(true) => { // Document was deleted successfully }, Ok(false) => { // Document not found (possibly already cascade deleted) }, Err(_) => { // Error occurred (foreign key constraint issue) // This might be expected behavior } } Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_bulk_delete_empty_and_null_scenarios() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; // Test empty list let empty_result = ctx.state.db .bulk_delete_documents(&vec![], user.user_response.id, user.user_response.role) .await .expect("Empty list should be handled gracefully"); let (deleted_ids, failed_ids) = empty_result; assert_eq!(deleted_ids.len(), 0); assert_eq!(failed_ids.len(), 0); // Test with only nil UUIDs let nil_uuids = vec![Uuid::nil(), Uuid::nil()]; let nil_result = ctx.state.db .bulk_delete_documents(&nil_uuids, user.user_response.id, user.user_response.role) .await .expect("Nil UUIDs should be handled gracefully"); let (deleted_ids, failed_ids) = nil_result; assert_eq!(deleted_ids.len(), 0); assert_eq!(failed_ids.len(), 2); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_transaction_rollback_simulation() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let user = auth_helper.create_test_user().await; let document_doc = create_test_document(user.user_response.id); let document = ctx.state.db.create_document(document_doc).await.expect("Failed to create document"); // Verify document exists before deletion let exists_before = ctx.state.db .get_document_by_id(document.id, user.user_response.id, user.user_response.role) .await .expect("Query should succeed"); assert!(exists_before.is_some()); // Perform deletion let deletion_result = ctx.state.db .delete_document(document.id, user.user_response.id, user.user_response.role) .await .expect("Deletion should succeed"); assert!(deletion_result); // Verify document no longer exists let exists_after = ctx.state.db .get_document_by_id(document.id, user.user_response.id, user.user_response.role) .await .expect("Query should succeed"); assert!(exists_after.is_none()); // If transaction were to be rolled back, document would exist again // This test verifies the transaction was committed properly Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } mod low_confidence_deletion_db_tests { use super::*; use readur::models::UserRole; #[cfg(test)] fn create_test_document_with_confidence(user_id: Uuid, confidence: f32) -> Document { Document { id: Uuid::new_v4(), filename: format!("test_conf_{}.pdf", confidence), original_filename: format!("test_conf_{}.pdf", confidence), file_path: format!("/uploads/test_conf_{}.pdf", confidence), file_size: 1024, mime_type: "application/pdf".to_string(), content: Some("Test document content".to_string()), ocr_text: Some("Test OCR text".to_string()), ocr_confidence: Some(confidence), ocr_word_count: Some(50), ocr_processing_time_ms: Some(1000), 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("test_hash_123456789abcdef123456789abcdef123456789abcdef123456789abcdef".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, } } #[test] fn test_confidence_filtering_logic() { let user_id = Uuid::new_v4(); let documents = vec![ create_test_document_with_confidence(user_id, 95.0), // Should not be deleted create_test_document_with_confidence(user_id, 75.0), // Should not be deleted create_test_document_with_confidence(user_id, 45.0), // Should not be deleted create_test_document_with_confidence(user_id, 25.0), // Should be deleted (< 30) create_test_document_with_confidence(user_id, 15.0), // Should be deleted (< 30) create_test_document_with_confidence(user_id, 5.0), // Should be deleted (< 30) ]; let threshold = 30.0; let low_confidence_docs: Vec<_> = documents.iter() .filter(|doc| { doc.ocr_confidence.is_some() && doc.ocr_confidence.unwrap() < threshold }) .collect(); assert_eq!(low_confidence_docs.len(), 3); assert_eq!(low_confidence_docs[0].ocr_confidence.unwrap(), 25.0); assert_eq!(low_confidence_docs[1].ocr_confidence.unwrap(), 15.0); assert_eq!(low_confidence_docs[2].ocr_confidence.unwrap(), 5.0); } #[test] fn test_documents_without_ocr_confidence_excluded() { let user_id = Uuid::new_v4(); let mut doc_no_confidence = create_test_document_with_confidence(user_id, 20.0); doc_no_confidence.ocr_confidence = None; let doc_with_confidence = create_test_document_with_confidence(user_id, 20.0); let documents = vec![doc_no_confidence, doc_with_confidence]; let threshold = 30.0; let low_confidence_docs: Vec<_> = documents.iter() .filter(|doc| { doc.ocr_confidence.is_some() && doc.ocr_confidence.unwrap() < threshold }) .collect(); // Only the document with confidence should be included assert_eq!(low_confidence_docs.len(), 1); assert!(low_confidence_docs[0].ocr_confidence.is_some()); } #[test] fn test_user_role_authorization_in_filtering() { let user1_id = Uuid::new_v4(); let user2_id = Uuid::new_v4(); let user1_doc = create_test_document_with_confidence(user1_id, 20.0); let user2_doc = create_test_document_with_confidence(user2_id, 15.0); // Regular user should only see their own documents let user_role = UserRole::User; let admin_role = UserRole::Admin; // User1 should only access their own document let user1_can_access_own = user1_doc.user_id == user1_id || user_role == UserRole::Admin; let user1_can_access_other = user2_doc.user_id == user1_id || user_role == UserRole::Admin; assert!(user1_can_access_own); assert!(!user1_can_access_other); // Admin should access all documents let admin_can_access_user1 = user1_doc.user_id == user1_id || admin_role == UserRole::Admin; let admin_can_access_user2 = user2_doc.user_id == user1_id || admin_role == UserRole::Admin; assert!(admin_can_access_user1); assert!(admin_can_access_user2); } #[test] fn test_boundary_conditions_for_confidence_thresholds() { let user_id = Uuid::new_v4(); let test_cases = vec![ (0.0, 10.0, true), // 0% < 10% threshold (10.0, 10.0, false), // 10% = 10% threshold (not less than) (10.1, 10.0, false), // 10.1% > 10% threshold (29.9, 30.0, true), // 29.9% < 30% threshold (30.0, 30.0, false), // 30% = 30% threshold (not less than) (30.1, 30.0, false), // 30.1% > 30% threshold (99.9, 100.0, true), // 99.9% < 100% threshold (100.0, 100.0, false), // 100% = 100% threshold (not less than) ]; for (doc_confidence, threshold, should_be_included) in test_cases { let doc = create_test_document_with_confidence(user_id, doc_confidence); let is_included = doc.ocr_confidence.is_some() && doc.ocr_confidence.unwrap() < threshold; assert_eq!(is_included, should_be_included, "Document with {}% confidence vs {}% threshold", doc_confidence, threshold); } } #[test] fn test_performance_considerations_for_large_datasets() { let user_id = Uuid::new_v4(); // Create a large number of test documents let mut documents = Vec::new(); for i in 0..1000 { let confidence = (i as f32) / 10.0; // 0.0 to 99.9 documents.push(create_test_document_with_confidence(user_id, confidence)); } let threshold = 50.0; let start_time = std::time::Instant::now(); let low_confidence_docs: Vec<_> = documents.iter() .filter(|doc| { doc.ocr_confidence.is_some() && doc.ocr_confidence.unwrap() < threshold }) .collect(); let elapsed = start_time.elapsed(); // Verify the filtering works correctly for large datasets assert_eq!(low_confidence_docs.len(), 500); // 0.0 to 49.9 // Performance should be reasonable (under 10ms for 1000 documents in memory) assert!(elapsed.as_millis() < 10, "Filtering 1000 documents took too long: {:?}", elapsed); } #[test] fn test_sql_query_structure_expectations() { // Test that our expected SQL query structure would work let user_id = Uuid::new_v4(); let confidence_threshold = 30.0; // This tests the logical structure we expect in the actual SQL query let expected_where_conditions = vec![ "ocr_confidence IS NOT NULL", "ocr_confidence < $1", // $1 = confidence_threshold "user_id = $2", // $2 = user_id (for non-admin users) ]; // Verify our test documents would match the expected query logic let test_doc = create_test_document_with_confidence(user_id, 25.0); // Simulate the SQL conditions let confidence_not_null = test_doc.ocr_confidence.is_some(); let confidence_below_threshold = test_doc.ocr_confidence.unwrap() < confidence_threshold; let user_matches = test_doc.user_id == user_id; assert!(confidence_not_null); assert!(confidence_below_threshold); assert!(user_matches); // This document should be included in results let would_be_selected = confidence_not_null && confidence_below_threshold && user_matches; assert!(would_be_selected); } #[test] fn test_deletion_ordering_expectations() { let user_id = Uuid::new_v4(); let mut documents = vec![ create_test_document_with_confidence(user_id, 25.0), create_test_document_with_confidence(user_id, 5.0), create_test_document_with_confidence(user_id, 15.0), create_test_document_with_confidence(user_id, 35.0), // Above threshold ]; let threshold = 30.0; let mut low_confidence_docs: Vec<_> = documents.iter() .filter(|doc| { doc.ocr_confidence.is_some() && doc.ocr_confidence.unwrap() < threshold }) .collect(); // Sort by confidence ascending (lowest first) then by creation date descending (newest first) low_confidence_docs.sort_by(|a, b| { let conf_a = a.ocr_confidence.unwrap(); let conf_b = b.ocr_confidence.unwrap(); conf_a.partial_cmp(&conf_b).unwrap() .then_with(|| b.created_at.cmp(&a.created_at)) }); assert_eq!(low_confidence_docs.len(), 3); assert_eq!(low_confidence_docs[0].ocr_confidence.unwrap(), 5.0); // Lowest confidence first assert_eq!(low_confidence_docs[1].ocr_confidence.unwrap(), 15.0); assert_eq!(low_confidence_docs[2].ocr_confidence.unwrap(), 25.0); } #[test] fn test_error_handling_scenarios() { let user_id = Uuid::new_v4(); // Test invalid threshold values (these would be caught by the API handler) let invalid_thresholds = vec![-1.0, 101.0, f32::NAN, f32::INFINITY]; for threshold in invalid_thresholds { // The database query itself should handle these gracefully // Invalid thresholds should either match no documents or be rejected let test_doc = create_test_document_with_confidence(user_id, 50.0); if threshold.is_finite() { let would_match = test_doc.ocr_confidence.is_some() && test_doc.ocr_confidence.unwrap() < threshold; if threshold < 0.0 { assert!(!would_match, "Negative threshold should match no documents"); } if threshold > 100.0 { // Documents with confidence > 100 shouldn't exist, but if they did, // they should still be considered for deletion if threshold > 100 assert!(would_match, "Threshold > 100 should match normal documents"); } } else { // NaN and infinity comparisons let would_match = test_doc.ocr_confidence.is_some() && test_doc.ocr_confidence.unwrap() < threshold; if threshold.is_nan() { // NaN comparisons should always be false assert!(!would_match, "NaN threshold should match no documents"); } else if threshold == f32::INFINITY { // Positive infinity should match all finite numbers assert!(would_match, "Positive infinity threshold should match finite documents"); } else { // Other invalid values like negative infinity assert!(!would_match, "Invalid threshold should match no documents"); } } } } } #[tokio::test] async fn test_find_failed_ocr_documents() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let database = &ctx.state.db; // Create actual users in the database let user = auth_helper.create_test_user().await; let admin_user = auth_helper.create_test_admin().await; let user_id = user.user_response.id; let admin_user_id = admin_user.user_response.id; // Create test documents with different OCR statuses let mut success_doc = create_test_document(user_id); success_doc.ocr_status = Some("completed".to_string()); success_doc.ocr_confidence = Some(85.0); success_doc.ocr_text = Some("Successfully extracted text".to_string()); let mut failed_doc = create_test_document(user_id); failed_doc.ocr_status = Some("failed".to_string()); failed_doc.ocr_confidence = None; failed_doc.ocr_text = None; failed_doc.ocr_error = Some("OCR processing failed due to corrupted image".to_string()); let mut null_confidence_doc = create_test_document(user_id); null_confidence_doc.ocr_status = Some("completed".to_string()); null_confidence_doc.ocr_confidence = None; // NULL confidence but not failed null_confidence_doc.ocr_text = Some("Text extracted but no confidence".to_string()); let mut pending_doc = create_test_document(user_id); pending_doc.ocr_status = Some("pending".to_string()); pending_doc.ocr_confidence = None; pending_doc.ocr_text = None; let mut processing_doc = create_test_document(user_id); processing_doc.ocr_status = Some("processing".to_string()); processing_doc.ocr_confidence = None; processing_doc.ocr_text = None; // Different user's failed document let mut other_user_failed_doc = create_test_document(admin_user_id); other_user_failed_doc.ocr_status = Some("failed".to_string()); other_user_failed_doc.ocr_confidence = None; // Insert all documents let success_id = ctx.state.db.create_document(success_doc).await.unwrap().id; let failed_id = ctx.state.db.create_document(failed_doc).await.unwrap().id; let null_confidence_id = ctx.state.db.create_document(null_confidence_doc).await.unwrap().id; let pending_id = ctx.state.db.create_document(pending_doc).await.unwrap().id; let processing_id = ctx.state.db.create_document(processing_doc).await.unwrap().id; let other_user_failed_id = ctx.state.db.create_document(other_user_failed_doc).await.unwrap().id; // Test as regular user let failed_docs = database .find_failed_ocr_documents(user_id, readur::models::UserRole::User, 100, 0) .await .unwrap(); // Should find: only failed_doc (null_confidence_doc has status 'completed') assert_eq!(failed_docs.len(), 1); let failed_ids: Vec = failed_docs.iter().map(|d| d.id).collect(); assert!(failed_ids.contains(&failed_id)); assert!(!failed_ids.contains(&null_confidence_id)); // This has status 'completed' assert!(!failed_ids.contains(&success_id)); assert!(!failed_ids.contains(&pending_id)); assert!(!failed_ids.contains(&processing_id)); assert!(!failed_ids.contains(&other_user_failed_id)); // Different user // Test as admin let admin_failed_docs = database .find_failed_ocr_documents(admin_user_id, readur::models::UserRole::Admin, 100, 0) .await .unwrap(); // Should find all failed documents (from all users) assert!(admin_failed_docs.len() >= 2); // At least our 2 failed docs let admin_failed_ids: Vec = admin_failed_docs.iter().map(|d| d.id).collect(); assert!(admin_failed_ids.contains(&failed_id)); assert!(!admin_failed_ids.contains(&null_confidence_id)); // This has status 'completed' assert!(admin_failed_ids.contains(&other_user_failed_id)); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_find_low_confidence_and_failed_documents() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let database = &ctx.state.db; // Create actual user in the database let user = auth_helper.create_test_user().await; let user_id = user.user_response.id; // Create test documents with different confidence levels let mut high_confidence_doc = create_test_document(user_id); high_confidence_doc.ocr_confidence = Some(95.0); high_confidence_doc.ocr_status = Some("completed".to_string()); let mut medium_confidence_doc = create_test_document(user_id); medium_confidence_doc.ocr_confidence = Some(65.0); medium_confidence_doc.ocr_status = Some("completed".to_string()); let mut low_confidence_doc = create_test_document(user_id); low_confidence_doc.ocr_confidence = Some(25.0); low_confidence_doc.ocr_status = Some("completed".to_string()); let mut failed_doc = create_test_document(user_id); failed_doc.ocr_status = Some("failed".to_string()); failed_doc.ocr_confidence = None; failed_doc.ocr_error = Some("Processing failed".to_string()); let mut null_confidence_doc = create_test_document(user_id); null_confidence_doc.ocr_status = Some("completed".to_string()); null_confidence_doc.ocr_confidence = None; let mut pending_doc = create_test_document(user_id); pending_doc.ocr_status = Some("pending".to_string()); pending_doc.ocr_confidence = None; // Insert all documents let high_id = ctx.state.db.create_document(high_confidence_doc).await.unwrap().id; let medium_id = ctx.state.db.create_document(medium_confidence_doc).await.unwrap().id; let low_id = ctx.state.db.create_document(low_confidence_doc).await.unwrap().id; let failed_id = ctx.state.db.create_document(failed_doc).await.unwrap().id; let null_confidence_id = ctx.state.db.create_document(null_confidence_doc).await.unwrap().id; let pending_id = ctx.state.db.create_document(pending_doc).await.unwrap().id; // Test with threshold of 50% - should include low confidence and failed only let threshold_50_docs = database .find_low_confidence_and_failed_documents(user_id, readur::models::UserRole::User, 50.0, 100, 0) .await .unwrap(); assert_eq!(threshold_50_docs.len(), 2); let threshold_50_ids: Vec = threshold_50_docs.iter().map(|d| d.id).collect(); assert!(threshold_50_ids.contains(&low_id)); // 25% confidence assert!(threshold_50_ids.contains(&failed_id)); // failed status assert!(!threshold_50_ids.contains(&null_confidence_id)); // NULL confidence excluded assert!(!threshold_50_ids.contains(&high_id)); // 95% confidence assert!(!threshold_50_ids.contains(&medium_id)); // 65% confidence assert!(!threshold_50_ids.contains(&pending_id)); // pending status // Test with threshold of 70% - should include low and medium confidence and failed only let threshold_70_docs = database .find_low_confidence_and_failed_documents(user_id, readur::models::UserRole::User, 70.0, 100, 0) .await .unwrap(); assert_eq!(threshold_70_docs.len(), 3); let threshold_70_ids: Vec = threshold_70_docs.iter().map(|d| d.id).collect(); assert!(threshold_70_ids.contains(&low_id)); // 25% confidence assert!(threshold_70_ids.contains(&medium_id)); // 65% confidence assert!(threshold_70_ids.contains(&failed_id)); // failed status assert!(!threshold_70_ids.contains(&null_confidence_id)); // NULL confidence excluded assert!(!threshold_70_ids.contains(&high_id)); // 95% confidence assert!(!threshold_70_ids.contains(&pending_id)); // pending status // Test with threshold of 100% - should include all confidence levels and failed only let threshold_100_docs = database .find_low_confidence_and_failed_documents(user_id, readur::models::UserRole::User, 100.0, 100, 0) .await .unwrap(); assert_eq!(threshold_100_docs.len(), 4); let threshold_100_ids: Vec = threshold_100_docs.iter().map(|d| d.id).collect(); assert!(threshold_100_ids.contains(&high_id)); // 95% confidence assert!(threshold_100_ids.contains(&medium_id)); // 65% confidence assert!(threshold_100_ids.contains(&low_id)); // 25% confidence assert!(threshold_100_ids.contains(&failed_id)); // failed status assert!(!threshold_100_ids.contains(&null_confidence_id)); // NULL confidence excluded assert!(!threshold_100_ids.contains(&pending_id)); // pending status // Test with threshold of 0% - should only include failed documents let threshold_0_docs = database .find_low_confidence_and_failed_documents(user_id, readur::models::UserRole::User, 0.0, 100, 0) .await .unwrap(); assert_eq!(threshold_0_docs.len(), 1); let threshold_0_ids: Vec = threshold_0_docs.iter().map(|d| d.id).collect(); assert!(threshold_0_ids.contains(&failed_id)); // failed status assert!(!threshold_0_ids.contains(&null_confidence_id)); // NULL confidence excluded assert!(!threshold_0_ids.contains(&high_id)); // 95% confidence assert!(!threshold_0_ids.contains(&medium_id)); // 65% confidence assert!(!threshold_0_ids.contains(&low_id)); // 25% confidence assert!(!threshold_0_ids.contains(&pending_id)); // pending status Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_find_documents_by_confidence_threshold_original_behavior() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let auth_helper = TestAuthHelper::new(ctx.app.clone()); let database = &ctx.state.db; // Create actual user in the database let user = auth_helper.create_test_user().await; let user_id = user.user_response.id; // Create test documents to verify original behavior is preserved let mut high_confidence_doc = create_test_document(user_id); high_confidence_doc.ocr_confidence = Some(90.0); high_confidence_doc.ocr_status = Some("completed".to_string()); let mut low_confidence_doc = create_test_document(user_id); low_confidence_doc.ocr_confidence = Some(40.0); low_confidence_doc.ocr_status = Some("completed".to_string()); let mut null_confidence_doc = create_test_document(user_id); null_confidence_doc.ocr_confidence = None; null_confidence_doc.ocr_status = Some("completed".to_string()); let mut failed_doc = create_test_document(user_id); failed_doc.ocr_confidence = None; failed_doc.ocr_status = Some("failed".to_string()); // Insert documents let high_id = ctx.state.db.create_document(high_confidence_doc).await.unwrap().id; let low_id = ctx.state.db.create_document(low_confidence_doc).await.unwrap().id; let null_confidence_id = ctx.state.db.create_document(null_confidence_doc).await.unwrap().id; let failed_id = ctx.state.db.create_document(failed_doc).await.unwrap().id; // Test original method - should only find documents with explicit confidence below threshold let original_results = database .find_documents_by_confidence_threshold(user_id, readur::models::UserRole::User, 50.0, 100, 0) .await .unwrap(); // Should only include low_confidence_doc (40%), not NULL confidence or failed docs assert_eq!(original_results.len(), 1); assert_eq!(original_results[0].id, low_id); let original_ids: Vec = original_results.iter().map(|d| d.id).collect(); assert!(!original_ids.contains(&high_id)); // 90% > 50% assert!(!original_ids.contains(&null_confidence_id)); // NULL confidence excluded assert!(!original_ids.contains(&failed_id)); // NULL confidence excluded Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_confidence_query_ordering() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let database = &ctx.state.db; // Create user using direct database approach let user_data = readur::models::CreateUser { username: format!("testuser_{}", Uuid::new_v4()), email: format!("test_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(readur::models::UserRole::User), }; let user = database.create_user(user_data).await.expect("Failed to create user"); let user_id = user.id; // Create documents with different confidence levels and statuses let mut confidence_10_doc = create_test_document(user_id); confidence_10_doc.ocr_confidence = Some(10.0); confidence_10_doc.ocr_status = Some("completed".to_string()); let mut confidence_30_doc = create_test_document(user_id); confidence_30_doc.ocr_confidence = Some(30.0); confidence_30_doc.ocr_status = Some("completed".to_string()); let mut failed_doc = create_test_document(user_id); failed_doc.ocr_confidence = None; failed_doc.ocr_status = Some("failed".to_string()); let mut null_confidence_doc = create_test_document(user_id); null_confidence_doc.ocr_confidence = None; null_confidence_doc.ocr_status = Some("completed".to_string()); // Insert documents let id_10 = ctx.state.db.create_document(confidence_10_doc).await.unwrap().id; let id_30 = ctx.state.db.create_document(confidence_30_doc).await.unwrap().id; let failed_id = ctx.state.db.create_document(failed_doc).await.unwrap().id; let null_id = ctx.state.db.create_document(null_confidence_doc).await.unwrap().id; // Test ordering in combined query let results = database .find_low_confidence_and_failed_documents(user_id, readur::models::UserRole::User, 50.0, 100, 0) .await .unwrap(); // The function returns documents that are either: // 1. Low confidence (< threshold) // 2. Failed status // A completed document with NULL confidence is not considered "failed" assert_eq!(results.len(), 3); // Update expectation based on actual behavior // Check that documents with actual confidence are ordered by confidence (ascending) // and NULL confidence documents come first (due to CASE WHEN ordering) let confidence_values: Vec> = results.iter().map(|d| d.ocr_confidence).collect(); // With 3 documents: 1 failed (NULL confidence), 2 low confidence documents // First should be NULL confidence (failed) assert!(confidence_values[0].is_none()); // Next should be lowest confidence assert_eq!(confidence_values[1], Some(10.0)); // Last should be higher confidence assert_eq!(confidence_values[2], Some(30.0)); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } #[tokio::test] async fn test_user_isolation_in_confidence_queries() { let ctx = TestContext::new().await; // Ensure cleanup happens even if test fails let result: Result<()> = async { let database = &ctx.state.db; // Create users using direct database approach let user1_data = readur::models::CreateUser { username: format!("testuser1_{}", Uuid::new_v4()), email: format!("test1_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(readur::models::UserRole::User), }; let user1 = database.create_user(user1_data).await.expect("Failed to create user1"); let user1_id = user1.id; let user2_data = readur::models::CreateUser { username: format!("testuser2_{}", Uuid::new_v4()), email: format!("test2_{}@example.com", Uuid::new_v4()), password: "password123".to_string(), role: Some(readur::models::UserRole::User), }; let user2 = database.create_user(user2_data).await.expect("Failed to create user2"); let user2_id = user2.id; // Create documents for user1 let mut user1_low_doc = create_test_document(user1_id); user1_low_doc.ocr_confidence = Some(20.0); let mut user1_failed_doc = create_test_document(user1_id); user1_failed_doc.ocr_status = Some("failed".to_string()); user1_failed_doc.ocr_confidence = None; // Create documents for user2 let mut user2_low_doc = create_test_document(user2_id); user2_low_doc.ocr_confidence = Some(25.0); let mut user2_failed_doc = create_test_document(user2_id); user2_failed_doc.ocr_status = Some("failed".to_string()); user2_failed_doc.ocr_confidence = None; // Insert documents let user1_low_id: Uuid = ctx.state.db.create_document(user1_low_doc).await.unwrap().id; let user1_failed_id: Uuid = ctx.state.db.create_document(user1_failed_doc).await.unwrap().id; let user2_low_id: Uuid = ctx.state.db.create_document(user2_low_doc).await.unwrap().id; let user2_failed_id: Uuid = ctx.state.db.create_document(user2_failed_doc).await.unwrap().id; // Test user1 can only see their documents let user1_results = database .find_low_confidence_and_failed_documents(user1_id, readur::models::UserRole::User, 50.0, 100, 0) .await .unwrap(); assert_eq!(user1_results.len(), 2); let user1_ids: Vec = user1_results.iter().map(|d| d.id).collect(); assert!(user1_ids.contains(&user1_low_id)); assert!(user1_ids.contains(&user1_failed_id)); assert!(!user1_ids.contains(&user2_low_id)); assert!(!user1_ids.contains(&user2_failed_id)); // Test user2 can only see their documents let user2_results = database .find_low_confidence_and_failed_documents(user2_id, readur::models::UserRole::User, 50.0, 100, 0) .await .unwrap(); assert_eq!(user2_results.len(), 2); let user2_ids: Vec = user2_results.iter().map(|d| d.id).collect(); assert!(user2_ids.contains(&user2_low_id)); assert!(user2_ids.contains(&user2_failed_id)); assert!(!user2_ids.contains(&user1_low_id)); assert!(!user2_ids.contains(&user1_failed_id)); // Test admin can see all documents let admin_results = database .find_low_confidence_and_failed_documents(user1_id, readur::models::UserRole::Admin, 50.0, 100, 0) .await .unwrap(); assert!(admin_results.len() >= 4); // At least our 4 test documents let admin_ids: Vec = admin_results.iter().map(|d| d.id).collect(); assert!(admin_ids.contains(&user1_low_id)); assert!(admin_ids.contains(&user1_failed_id)); assert!(admin_ids.contains(&user2_low_id)); assert!(admin_ids.contains(&user2_failed_id)); Ok(()) }.await; // Always cleanup database connections and test data if let Err(e) = ctx.cleanup_and_close().await { eprintln!("Warning: Test cleanup failed: {}", e); } result.unwrap(); } }