feat(tests): bring back the test helpers

This commit is contained in:
perf3ct 2025-08-01 20:34:42 +00:00
parent 21bc1167e8
commit aad6036b4c
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
7 changed files with 272 additions and 93 deletions

View File

@ -1,56 +0,0 @@
#\!/bin/bash
# Files with missing Config fields
config_files=(
"tests/integration_smart_sync_targeted_scan.rs"
"tests/integration_s3_sync_tests.rs"
"tests/unit_webdav_edge_cases_tests.rs"
"tests/unit_webdav_url_management_tests.rs"
"tests/integration_webdav_concurrency_tests.rs"
"tests/integration_smart_sync_error_handling.rs"
"tests/integration_webdav_sync_tests.rs"
"tests/unit_webdav_directory_tracking_tests.rs"
"tests/unit_basic_sync_tests.rs"
"tests/unit_webdav_unit_tests.rs"
"tests/integration_webdav_smart_scanning_tests.rs"
"tests/unit_webdav_enhanced_unit_tests.rs"
"tests/integration_webdav_scheduler_concurrency_tests.rs"
"tests/integration_smart_sync_deep_scan.rs"
"tests/integration_smart_sync_no_changes.rs"
"tests/integration_local_folder_sync_tests.rs"
"tests/webdav_production_flow_integration_tests.rs"
"tests/integration_webdav_first_time_scan_tests.rs"
"tests/unit_webdav_targeted_rescan_tests.rs"
"tests/integration_smart_sync_first_time.rs"
"tests/unit_smart_sync_service_tests.rs"
"tests/unit_webdav_smart_scan_logic_tests.rs"
)
echo "Fixing Config structs in ${#config_files[@]} files..."
for file in "${config_files[@]}"; do
if [ -f "$file" ]; then
echo "Processing $file..."
# Check if file has Config struct and missing fields
if grep -q "Config {" "$file" && \! grep -q "user_watch_base_dir" "$file"; then
# Add the missing fields after watch_folder line
sed -i.bak '/watch_folder:/a\
user_watch_base_dir: "./user_watch".to_string(),\
enable_per_user_watch: false,' "$file"
# Clean up formatting
sed -i 's/enable_per_user_watch: false, /enable_per_user_watch: false,\n /' "$file"
sed -i 's/enable_per_user_watch: false, /enable_per_user_watch: false,\n /' "$file"
# Remove backup
rm "${file}.bak" 2>/dev/null || true
echo "Fixed Config in $file"
else
echo "Skipping $file (no Config struct or already fixed)"
fi
else
echo "File not found: $file"
fi
done
echo "Config fixes completed."

View File

@ -25,6 +25,9 @@ mod tests;
#[cfg(any(test, feature = "test-utils"))] #[cfg(any(test, feature = "test-utils"))]
pub mod test_utils; pub mod test_utils;
#[cfg(any(test, feature = "test-utils"))]
pub mod test_helpers;
use axum::{http::StatusCode, Json}; use axum::{http::StatusCode, Json};
use utoipa; use utoipa;
use config::Config; use config::Config;

225
src/test_helpers.rs Normal file
View File

@ -0,0 +1,225 @@
/*!
* Test Helpers and Utilities
*
* This module provides utilities for creating test configurations and services
* with sensible defaults. Tests can modify the returned objects as needed.
*/
use crate::{
config::Config,
db::Database,
services::file_service::FileService,
storage::{StorageConfig, factory::create_storage_backend},
AppState,
ocr::queue::OcrQueueService,
services::sync_progress_tracker::SyncProgressTracker,
};
use std::sync::Arc;
use sqlx::PgPool;
/// Creates a test configuration with sensible defaults
/// All fields are populated to avoid compilation errors when new fields are added
pub fn create_test_config() -> Config {
Config {
database_url: std::env::var("TEST_DATABASE_URL")
.or_else(|_| std::env::var("DATABASE_URL"))
.unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string()),
server_address: "127.0.0.1:0".to_string(),
jwt_secret: "test_jwt_secret_for_integration_tests".to_string(),
upload_path: "/tmp/test_uploads".to_string(),
watch_folder: "/tmp/test_watch".to_string(),
user_watch_base_dir: "/tmp/user_watch".to_string(),
enable_per_user_watch: false,
allowed_file_types: vec!["pdf".to_string(), "png".to_string(), "jpg".to_string(), "txt".to_string()],
watch_interval_seconds: Some(10),
file_stability_check_ms: Some(500),
max_file_age_hours: Some(24),
// OCR Configuration
ocr_language: "eng".to_string(),
concurrent_ocr_jobs: 2,
ocr_timeout_seconds: 60,
max_file_size_mb: 50,
// Performance
memory_limit_mb: 256,
cpu_priority: "normal".to_string(),
// OIDC Configuration (disabled for tests)
oidc_enabled: false,
oidc_client_id: None,
oidc_client_secret: None,
oidc_issuer_url: None,
oidc_redirect_uri: None,
// S3 Configuration (disabled for tests by default)
s3_enabled: false,
s3_config: None,
}
}
/// Creates a default test database URL
pub fn default_test_db_url() -> String {
std::env::var("TEST_DATABASE_URL")
.or_else(|_| std::env::var("DATABASE_URL"))
.unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string())
}
/// Creates a test FileService with local storage
pub async fn create_test_file_service(upload_path: Option<&str>) -> Arc<FileService> {
let path = upload_path.unwrap_or("/tmp/test_uploads");
let storage_config = StorageConfig::Local {
upload_path: path.to_string()
};
let storage_backend = create_storage_backend(storage_config)
.await
.expect("Failed to create test storage backend");
Arc::new(FileService::with_storage(path.to_string(), storage_backend))
}
/// Creates a test Database instance
pub async fn create_test_database() -> Database {
let database_url = default_test_db_url();
Database::new(&database_url)
.await
.expect("Failed to connect to test database")
}
/// Creates a test Database instance with custom pool configuration
pub async fn create_test_database_with_pool(max_connections: u32, min_connections: u32) -> Database {
let database_url = default_test_db_url();
Database::new_with_pool_config(&database_url, max_connections, min_connections)
.await
.expect("Failed to connect to test database with custom pool")
}
/// Creates a test OcrQueueService
pub fn create_test_queue_service(db: Database, pool: PgPool, file_service: Arc<FileService>) -> Arc<OcrQueueService> {
Arc::new(OcrQueueService::new(db, pool, 2, file_service))
}
/// Creates a test AppState with default configuration and services
/// This provides a convenient way to get a fully configured AppState for testing
pub async fn create_test_app_state() -> Arc<AppState> {
let config = create_test_config();
create_test_app_state_with_config(config).await
}
/// Creates a test AppState with a custom configuration
/// This allows tests to customize config while still getting properly initialized services
pub async fn create_test_app_state_with_config(config: Config) -> Arc<AppState> {
let db = create_test_database().await;
let file_service = create_test_file_service(Some(&config.upload_path)).await;
let pool = db.pool.clone();
let queue_service = create_test_queue_service(db.clone(), pool, file_service.clone());
let sync_progress_tracker = Arc::new(SyncProgressTracker::new());
Arc::new(AppState {
db,
config,
file_service,
webdav_scheduler: None,
source_scheduler: None,
queue_service,
oidc_client: None,
sync_progress_tracker,
user_watch_service: None,
})
}
/// Creates a test AppState with custom upload path
/// Convenient for tests that need a specific upload directory
pub async fn create_test_app_state_with_upload_path(upload_path: &str) -> Arc<AppState> {
let mut config = create_test_config();
config.upload_path = upload_path.to_string();
create_test_app_state_with_config(config).await
}
/// Creates a test AppState with user watch service enabled
/// Useful for tests that need per-user watch functionality
pub async fn create_test_app_state_with_user_watch(user_watch_base_dir: &str) -> Arc<AppState> {
let mut config = create_test_config();
config.enable_per_user_watch = true;
config.user_watch_base_dir = user_watch_base_dir.to_string();
let db = create_test_database().await;
let file_service = create_test_file_service(Some(&config.upload_path)).await;
let pool = db.pool.clone();
let queue_service = create_test_queue_service(db.clone(), pool, file_service.clone());
let sync_progress_tracker = Arc::new(SyncProgressTracker::new());
// Create user watch service
let user_watch_service = Some(Arc::new(crate::services::user_watch_service::UserWatchService::new(
&config.user_watch_base_dir
)));
Arc::new(AppState {
db,
config,
file_service,
webdav_scheduler: None,
source_scheduler: None,
queue_service,
oidc_client: None,
sync_progress_tracker,
user_watch_service,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_test_config() {
let config = create_test_config();
assert!(!config.database_url.is_empty());
assert!(!config.s3_enabled); // Default should be false
assert!(config.s3_config.is_none()); // Default should be None
assert!(!config.oidc_enabled); // Default should be false
}
#[tokio::test]
async fn test_create_test_file_service() {
let file_service = create_test_file_service(None).await;
// Just verify it was created successfully
assert!(file_service.as_ref() as *const _ != std::ptr::null());
}
#[tokio::test]
async fn test_create_test_database() {
let db = create_test_database().await;
// Just verify it was created successfully
assert!(db.pool.is_closed() == false);
}
#[tokio::test]
async fn test_create_test_app_state() {
let state = create_test_app_state().await;
// Verify all required fields are present
assert!(!state.config.database_url.is_empty());
assert!(!state.config.s3_enabled); // Default should be false
assert!(!state.config.oidc_enabled); // Default should be false
assert!(state.user_watch_service.is_none()); // Default should be None
}
#[tokio::test]
async fn test_create_test_app_state_with_custom_config() {
let mut config = create_test_config();
config.upload_path = "/custom/test/path".to_string();
config.s3_enabled = true;
let state = create_test_app_state_with_config(config).await;
assert_eq!(state.config.upload_path, "/custom/test/path");
assert!(state.config.s3_enabled);
}
#[tokio::test]
async fn test_create_test_app_state_with_user_watch() {
let state = create_test_app_state_with_user_watch("/tmp/user_watch_test").await;
assert!(state.config.enable_per_user_watch);
assert_eq!(state.config.user_watch_base_dir, "/tmp/user_watch_test");
assert!(state.user_watch_service.is_some());
}
}

View File

@ -9,6 +9,8 @@ use readur::{
db::Database, db::Database,
config::Config, config::Config,
models::{Document, CreateUser, UserRole}, models::{Document, CreateUser, UserRole},
services::file_service::FileService,
storage::{StorageConfig, factory::create_storage_backend},
}; };
// Helper function to calculate file hash // Helper function to calculate file hash
@ -94,16 +96,29 @@ async fn create_test_app_state() -> Result<Arc<AppState>> {
oidc_client_secret: None, oidc_client_secret: None,
oidc_issuer_url: None, oidc_issuer_url: None,
oidc_redirect_uri: None, oidc_redirect_uri: None,
s3_enabled: false,
s3_config: None,
} }
}); });
let db = Database::new(&config.database_url).await?; let db = Database::new(&config.database_url).await?;
// Create file service
let storage_config = StorageConfig::Local {
upload_path: config.upload_path.clone()
};
let storage_backend = create_storage_backend(storage_config)
.await
.expect("Failed to create test storage backend");
let file_service = Arc::new(FileService::with_storage(config.upload_path.clone(), storage_backend));
let queue_service = std::sync::Arc::new( let queue_service = std::sync::Arc::new(
readur::ocr::queue::OcrQueueService::new(db.clone(), db.get_pool().clone(), 1) readur::ocr::queue::OcrQueueService::new(db.clone(), db.get_pool().clone(), 1, file_service.clone())
); );
Ok(Arc::new(AppState { Ok(Arc::new(AppState {
db: db.clone(), db: db.clone(),
config, config,
file_service,
webdav_scheduler: None, webdav_scheduler: None,
source_scheduler: None, source_scheduler: None,
queue_service, queue_service,

View File

@ -30,10 +30,11 @@ async fn test_per_user_watch_directory_lifecycle() -> Result<()> {
config.enable_per_user_watch = true; config.enable_per_user_watch = true;
// Update the state with the new config and user watch service // Update the state with the new config and user watch service
let user_watch_service = Some(Arc::new(readur::services::user_watch_service::UserWatchService::new(&config.user_watch_base_dir))); let user_watch_service = Some(Arc::new(UserWatchService::new(&config.user_watch_base_dir)));
let updated_state = Arc::new(AppState { let updated_state = Arc::new(AppState {
db: ctx.state.db.clone(), db: ctx.state.db.clone(),
config, config,
file_service: ctx.state.file_service.clone(),
webdav_scheduler: None, webdav_scheduler: None,
source_scheduler: None, source_scheduler: None,
queue_service: ctx.state.queue_service.clone(), queue_service: ctx.state.queue_service.clone(),
@ -282,10 +283,11 @@ async fn test_user_watch_directory_file_processing_simulation() -> Result<()> {
config.enable_per_user_watch = true; config.enable_per_user_watch = true;
// Update the state with the new config and user watch service // Update the state with the new config and user watch service
let user_watch_service = Some(Arc::new(readur::services::user_watch_service::UserWatchService::new(&config.user_watch_base_dir))); let user_watch_service = Some(Arc::new(UserWatchService::new(&config.user_watch_base_dir)));
let state = Arc::new(AppState { let state = Arc::new(AppState {
db: ctx.state.db.clone(), db: ctx.state.db.clone(),
config: config.clone(), config: config.clone(),
file_service: ctx.state.file_service.clone(),
webdav_scheduler: None, webdav_scheduler: None,
source_scheduler: None, source_scheduler: None,
queue_service: ctx.state.queue_service.clone(), queue_service: ctx.state.queue_service.clone(),
@ -296,7 +298,7 @@ async fn test_user_watch_directory_file_processing_simulation() -> Result<()> {
// Create user watch manager to test file path mapping // Create user watch manager to test file path mapping
let user_watch_service = state.user_watch_service.as_ref().unwrap(); let user_watch_service = state.user_watch_service.as_ref().unwrap();
let user_watch_manager = readur::scheduling::user_watch_manager::UserWatchManager::new(state.db.clone(), Arc::clone(user_watch_service)); let user_watch_manager = readur::scheduling::user_watch_manager::UserWatchManager::new(state.db.clone(), (**user_watch_service).clone());
// Create test user // Create test user
let test_user = readur::models::User { let test_user = readur::models::User {
@ -367,6 +369,7 @@ async fn test_per_user_watch_disabled() -> Result<()> {
let updated_state = Arc::new(AppState { let updated_state = Arc::new(AppState {
db: ctx.state.db.clone(), db: ctx.state.db.clone(),
config, config,
file_service: ctx.state.file_service.clone(),
webdav_scheduler: None, webdav_scheduler: None,
source_scheduler: None, source_scheduler: None,
queue_service: ctx.state.queue_service.clone(), queue_service: ctx.state.queue_service.clone(),

View File

@ -14,6 +14,7 @@ use readur::{
models::*, models::*,
routes, routes,
AppState, AppState,
test_helpers,
}; };
// Removed constant - will use environment variables instead // Removed constant - will use environment variables instead
@ -80,44 +81,34 @@ async fn setup_test_app() -> (Router, Arc<AppState>) {
.or_else(|_| std::env::var("DATABASE_URL")) .or_else(|_| std::env::var("DATABASE_URL"))
.unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string()); .unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string());
let config = Config { // Create test configuration with custom database URL
database_url: database_url.clone(), let mut config = test_helpers::create_test_config();
server_address: "127.0.0.1:0".to_string(), config.database_url = database_url.clone();
upload_path: "/tmp/test_uploads".to_string(), config.jwt_secret = "test_jwt_secret_for_integration_tests".to_string();
watch_folder: "/tmp/test_watch".to_string(), config.allowed_file_types = vec!["pdf".to_string(), "png".to_string()];
user_watch_base_dir: "./user_watch".to_string(), config.watch_interval_seconds = Some(10);
enable_per_user_watch: false, config.file_stability_check_ms = Some(1000);
jwt_secret: "test_jwt_secret_for_integration_tests".to_string(), config.max_file_age_hours = Some(24);
allowed_file_types: vec!["pdf".to_string(), "png".to_string()], config.memory_limit_mb = 512;
watch_interval_seconds: Some(10), config.concurrent_ocr_jobs = 4;
file_stability_check_ms: Some(1000), config.max_file_size_mb = 50;
max_file_age_hours: Some(24), config.ocr_timeout_seconds = 300;
cpu_priority: "normal".to_string(),
memory_limit_mb: 512,
concurrent_ocr_jobs: 4,
max_file_size_mb: 50,
ocr_language: "eng".to_string(),
ocr_timeout_seconds: 300,
oidc_enabled: false,
oidc_client_id: None,
oidc_client_secret: None,
oidc_issuer_url: None,
oidc_redirect_uri: None,
};
// Use the environment-based database URL // Create test services
let db_url = database_url; let db = test_helpers::create_test_database().await;
let file_service = test_helpers::create_test_file_service(Some("/tmp/test_uploads")).await;
let db = Database::new(&db_url).await.expect("Failed to connect to test database"); let queue_service = test_helpers::create_test_queue_service(db.clone(), db.pool.clone(), file_service.clone());
let queue_service = Arc::new(readur::ocr::queue::OcrQueueService::new(db.clone(), db.pool.clone(), 2));
// Create AppState
let state = Arc::new(AppState { let state = Arc::new(AppState {
db: db.clone(), db: db.clone(),
config, config,
file_service,
webdav_scheduler: None, webdav_scheduler: None,
source_scheduler: None, source_scheduler: None,
queue_service, queue_service,
oidc_client: None, oidc_client: None,
sync_progress_tracker: std::sync::Arc::new(readur::services::sync_progress_tracker::SyncProgressTracker::new()), sync_progress_tracker: Arc::new(readur::services::sync_progress_tracker::SyncProgressTracker::new()),
user_watch_service: None, user_watch_service: None,
}); });

View File

@ -1,9 +1,7 @@
use tokio; use tokio;
use uuid::Uuid;
use chrono::Utc; use chrono::Utc;
use std::collections::HashMap; use std::collections::HashMap;
use readur::models::FileIngestionInfo; use readur::{models::FileIngestionInfo, services::webdav::{WebDAVService, WebDAVConfig}};
use readur::services::webdav::{WebDAVService, WebDAVConfig};
// Helper function to create test WebDAV service for smart scanning // Helper function to create test WebDAV service for smart scanning
fn create_nextcloud_webdav_service() -> WebDAVService { fn create_nextcloud_webdav_service() -> WebDAVService {