use anyhow::Result; use std::sync::Arc; use uuid::Uuid; use readur::{ AppState, db::Database, config::Config, models::{CreateUser, UserRole, CreateIgnoredFile}, }; async fn create_test_app_state() -> Result> { let config = Config::from_env().unwrap_or_else(|_| { let database_url = std::env::var("DATABASE_URL") .or_else(|_| std::env::var("TEST_DATABASE_URL")) .unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string()); Config { database_url, server_address: "127.0.0.1:0".to_string(), jwt_secret: "test_secret".to_string(), upload_path: "./test_uploads".to_string(), watch_folder: "./test_watch".to_string(), user_watch_base_dir: "./user_watch".to_string(), enable_per_user_watch: false, allowed_file_types: vec!["pdf".to_string(), "txt".to_string()], watch_interval_seconds: Some(30), file_stability_check_ms: Some(500), max_file_age_hours: None, ocr_language: "eng".to_string(), concurrent_ocr_jobs: 1, ocr_timeout_seconds: 30, max_file_size_mb: 10, memory_limit_mb: 256, cpu_priority: "normal".to_string(), oidc_enabled: false, oidc_client_id: None, oidc_client_secret: None, oidc_issuer_url: None, oidc_redirect_uri: None, s3_enabled: false, s3_config: None, } }); let db = Database::new(&config.database_url).await?; // Create file service let storage_config = readur::storage::StorageConfig::Local { upload_path: config.upload_path.clone() }; let storage_backend = readur::storage::factory::create_storage_backend(storage_config).await?; let file_service = Arc::new(readur::services::file_service::FileService::with_storage(config.upload_path.clone(), storage_backend)); let queue_service = Arc::new(readur::ocr::queue::OcrQueueService::new(db.clone(), db.pool.clone(), 1, file_service.clone())); Ok(Arc::new(AppState { db: db.clone(), config, file_service, webdav_scheduler: None, source_scheduler: None, queue_service, oidc_client: None, sync_progress_tracker: std::sync::Arc::new(readur::services::sync_progress_tracker::SyncProgressTracker::new()), user_watch_service: None, })) } fn create_test_user_with_suffix(suffix: &str) -> CreateUser { CreateUser { username: format!("testuser_{}", suffix), email: format!("test_{}@example.com", suffix), password: "test_password".to_string(), role: Some(UserRole::User), } } #[tokio::test] async fn test_ignored_files_crud_operations() -> Result<()> { let state = create_test_app_state().await?; let user = create_test_user_with_suffix(&format!("ignored_{}", Uuid::new_v4().simple())); // Create user in database let created_user = state.db.create_user(user).await?; let user_id = created_user.id; // Test creating an ignored file let ignored_file = CreateIgnoredFile { file_hash: "test_hash_123".to_string(), filename: "test_file.pdf".to_string(), original_filename: "original_test_file.pdf".to_string(), file_path: "/test/path/test_file.pdf".to_string(), file_size: 1024, mime_type: "application/pdf".to_string(), source_type: Some("webdav".to_string()), source_path: Some("/webdav/test_file.pdf".to_string()), source_identifier: Some("test-webdav".to_string()), ignored_by: user_id, reason: Some("test deletion".to_string()), }; // Test that we can create and retrieve ignored files let created = readur::db::ignored_files::create_ignored_file(&state.db.pool, ignored_file).await?; assert_eq!(created.file_hash, "test_hash_123"); assert_eq!(created.filename, "test_file.pdf"); assert_eq!(created.ignored_by, user_id); // Test listing ignored files let query = readur::models::IgnoredFilesQuery { limit: Some(10), offset: Some(0), source_type: None, source_identifier: None, ignored_by: None, filename: None, }; let ignored_files = readur::db::ignored_files::list_ignored_files(&state.db.pool, user_id, &query).await?; assert_eq!(ignored_files.len(), 1); assert_eq!(ignored_files[0].file_hash, "test_hash_123"); // Test is_file_ignored function let is_ignored = readur::db::ignored_files::is_file_ignored( &state.db.pool, "test_hash_123", Some("webdav"), Some("/webdav/test_file.pdf") ).await?; assert!(is_ignored); // Test deleting ignored file let deleted = readur::db::ignored_files::delete_ignored_file(&state.db.pool, created.id, user_id).await?; assert!(deleted); // Verify it's deleted let ignored_files_after = readur::db::ignored_files::list_ignored_files(&state.db.pool, user_id, &query).await?; assert_eq!(ignored_files_after.len(), 0); Ok(()) } #[tokio::test] async fn test_ignored_files_filtering() -> Result<()> { let state = create_test_app_state().await?; let user = create_test_user_with_suffix(&format!("filter_{}", Uuid::new_v4().simple())); // Create user in database let created_user = state.db.create_user(user).await?; let user_id = created_user.id; // Create multiple ignored files with different properties let ignored_files = vec![ CreateIgnoredFile { file_hash: "hash1".to_string(), filename: "webdav_file.pdf".to_string(), original_filename: "webdav_file.pdf".to_string(), file_path: "/path1/webdav_file.pdf".to_string(), file_size: 1024, mime_type: "application/pdf".to_string(), source_type: Some("webdav".to_string()), source_path: Some("/webdav/file.pdf".to_string()), source_identifier: Some("webdav-1".to_string()), ignored_by: user_id, reason: Some("deleted".to_string()), }, CreateIgnoredFile { file_hash: "hash2".to_string(), filename: "s3_file.pdf".to_string(), original_filename: "s3_file.pdf".to_string(), file_path: "/path2/s3_file.pdf".to_string(), file_size: 2048, mime_type: "application/pdf".to_string(), source_type: Some("s3".to_string()), source_path: Some("/s3/file.pdf".to_string()), source_identifier: Some("s3-bucket".to_string()), ignored_by: user_id, reason: Some("deleted".to_string()), } ]; // Create ignored files for ignored_file in ignored_files { readur::db::ignored_files::create_ignored_file(&state.db.pool, ignored_file).await?; } // Test filtering by source type let webdav_query = readur::models::IgnoredFilesQuery { limit: Some(10), offset: Some(0), source_type: Some("webdav".to_string()), source_identifier: None, ignored_by: None, filename: None, }; let webdav_files = readur::db::ignored_files::list_ignored_files(&state.db.pool, user_id, &webdav_query).await?; assert_eq!(webdav_files.len(), 1); assert_eq!(webdav_files[0].source_type, Some("webdav".to_string())); // Test filtering by filename let filename_query = readur::models::IgnoredFilesQuery { limit: Some(10), offset: Some(0), source_type: None, source_identifier: None, ignored_by: None, filename: Some("s3_file".to_string()), }; let s3_files = readur::db::ignored_files::list_ignored_files(&state.db.pool, user_id, &filename_query).await?; assert_eq!(s3_files.len(), 1); assert!(s3_files[0].filename.contains("s3_file")); Ok(()) } #[tokio::test] async fn test_ignored_files_user_isolation() -> Result<()> { let state = create_test_app_state().await?; // Create two different users let user1 = create_test_user_with_suffix(&format!("user1_{}", Uuid::new_v4().simple())); let user2 = create_test_user_with_suffix(&format!("user2_{}", Uuid::new_v4().simple())); let created_user1 = state.db.create_user(user1).await?; let created_user2 = state.db.create_user(user2).await?; let user1_id = created_user1.id; let user2_id = created_user2.id; // Create ignored file for user1 let ignored_file1 = CreateIgnoredFile { file_hash: "user1_hash".to_string(), filename: "user1_file.pdf".to_string(), original_filename: "user1_file.pdf".to_string(), file_path: "/user1/file.pdf".to_string(), file_size: 1024, mime_type: "application/pdf".to_string(), source_type: Some("webdav".to_string()), source_path: Some("/webdav/user1_file.pdf".to_string()), source_identifier: Some("webdav-1".to_string()), ignored_by: user1_id, reason: Some("deleted by user1".to_string()), }; // Create ignored file for user2 let ignored_file2 = CreateIgnoredFile { file_hash: "user2_hash".to_string(), filename: "user2_file.pdf".to_string(), original_filename: "user2_file.pdf".to_string(), file_path: "/user2/file.pdf".to_string(), file_size: 2048, mime_type: "application/pdf".to_string(), source_type: Some("s3".to_string()), source_path: Some("/s3/user2_file.pdf".to_string()), source_identifier: Some("s3-bucket".to_string()), ignored_by: user2_id, reason: Some("deleted by user2".to_string()), }; readur::db::ignored_files::create_ignored_file(&state.db.pool, ignored_file1).await?; readur::db::ignored_files::create_ignored_file(&state.db.pool, ignored_file2).await?; let query = readur::models::IgnoredFilesQuery { limit: Some(10), offset: Some(0), source_type: None, source_identifier: None, ignored_by: None, filename: None, }; // User1 should only see their own ignored files let user1_files = readur::db::ignored_files::list_ignored_files(&state.db.pool, user1_id, &query).await?; assert_eq!(user1_files.len(), 1); assert_eq!(user1_files[0].ignored_by, user1_id); assert_eq!(user1_files[0].filename, "user1_file.pdf"); // User2 should only see their own ignored files let user2_files = readur::db::ignored_files::list_ignored_files(&state.db.pool, user2_id, &query).await?; assert_eq!(user2_files.len(), 1); assert_eq!(user2_files[0].ignored_by, user2_id); assert_eq!(user2_files[0].filename, "user2_file.pdf"); Ok(()) } #[tokio::test] async fn test_ignored_files_bulk_operations() -> Result<()> { let state = create_test_app_state().await?; let user = create_test_user_with_suffix(&format!("bulk_{}", Uuid::new_v4().simple())); // Create user in database let created_user = state.db.create_user(user).await?; let user_id = created_user.id; // Create multiple ignored files let mut created_ids = Vec::new(); for i in 0..3 { let ignored_file = CreateIgnoredFile { file_hash: format!("bulk_hash_{}", i), filename: format!("bulk_file_{}.pdf", i), original_filename: format!("bulk_file_{}.pdf", i), file_path: format!("/bulk/file_{}.pdf", i), file_size: 1024 * (i + 1) as i64, mime_type: "application/pdf".to_string(), source_type: Some("webdav".to_string()), source_path: Some(format!("/webdav/bulk_{}.pdf", i)), source_identifier: Some("webdav-bulk".to_string()), ignored_by: user_id, reason: Some("bulk test".to_string()), }; let created = readur::db::ignored_files::create_ignored_file(&state.db.pool, ignored_file).await?; created_ids.push(created.id); } // Test bulk delete let deleted_count = readur::db::ignored_files::bulk_delete_ignored_files( &state.db.pool, created_ids.clone(), user_id ).await?; assert_eq!(deleted_count, 3); // Verify all are deleted let query = readur::models::IgnoredFilesQuery { limit: Some(10), offset: Some(0), source_type: None, source_identifier: None, ignored_by: None, filename: None, }; let remaining_files = readur::db::ignored_files::list_ignored_files(&state.db.pool, user_id, &query).await?; assert_eq!(remaining_files.len(), 0); Ok(()) } #[tokio::test] async fn test_create_ignored_file_from_document() -> Result<()> { let state = create_test_app_state().await?; let user = create_test_user_with_suffix(&format!("doc_{}", Uuid::new_v4().simple())); // Create user in database let created_user = state.db.create_user(user).await?; let user_id = created_user.id; // Create a test document first let document = readur::models::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("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(chrono::Utc::now()), ocr_retry_count: None, ocr_failure_reason: None, tags: vec!["test".to_string()], created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), user_id, file_hash: Some("document_hash_123".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, }; // Insert document into database let _document_id = state.db.create_document(document.clone()).await?; // Test creating ignored file from document let ignored_file = readur::db::ignored_files::create_ignored_file_from_document( &state.db.pool, document.id, user_id, Some("deleted by user".to_string()), Some("webdav".to_string()), Some("/webdav/test_document.pdf".to_string()), Some("webdav-server".to_string()), ).await?; assert!(ignored_file.is_some()); let ignored_file = ignored_file.unwrap(); assert_eq!(ignored_file.filename, document.filename); assert_eq!(ignored_file.file_size, document.file_size); assert_eq!(ignored_file.mime_type, document.mime_type); assert_eq!(ignored_file.ignored_by, user_id); assert_eq!(ignored_file.source_type, Some("webdav".to_string())); Ok(()) } #[tokio::test] async fn test_ignored_files_count_functionality() -> Result<()> { let state = create_test_app_state().await?; let user = create_test_user_with_suffix(&format!("count_{}", Uuid::new_v4().simple())); // Create user in database let created_user = state.db.create_user(user).await?; let user_id = created_user.id; // Initially should have no ignored files let query = readur::models::IgnoredFilesQuery { limit: Some(10), offset: Some(0), source_type: None, source_identifier: None, ignored_by: None, filename: None, }; let count = readur::db::ignored_files::count_ignored_files(&state.db.pool, user_id, &query).await?; assert_eq!(count, 0); // Create some ignored files for i in 0..5 { let ignored_file = CreateIgnoredFile { file_hash: format!("count_hash_{}", i), filename: format!("count_file_{}.pdf", i), original_filename: format!("count_file_{}.pdf", i), file_path: format!("/count/file_{}.pdf", i), file_size: 1024, mime_type: "application/pdf".to_string(), source_type: Some(if i % 2 == 0 { "webdav" } else { "s3" }.to_string()), source_path: Some(format!("/source/file_{}.pdf", i)), source_identifier: Some("test-source".to_string()), ignored_by: user_id, reason: Some("count test".to_string()), }; readur::db::ignored_files::create_ignored_file(&state.db.pool, ignored_file).await?; } // Count should now be 5 let count = readur::db::ignored_files::count_ignored_files(&state.db.pool, user_id, &query).await?; assert_eq!(count, 5); // Test filtered count (only webdav) let webdav_query = readur::models::IgnoredFilesQuery { limit: Some(10), offset: Some(0), source_type: Some("webdav".to_string()), source_identifier: None, ignored_by: None, filename: None, }; let webdav_count = readur::db::ignored_files::count_ignored_files(&state.db.pool, user_id, &webdav_query).await?; assert_eq!(webdav_count, 3); // Files 0, 2, 4 are webdav Ok(()) }