mirror of
https://github.com/readur/readur.git
synced 2025-12-20 22:00:48 -06:00
fix(settings): fix to actually fetch user's settings, not just defaults
This commit is contained in:
@@ -187,9 +187,15 @@ async fn get_server_configuration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config = &state.config;
|
let config = &state.config;
|
||||||
|
|
||||||
// Get default settings for reference
|
// Get user settings from database, fallback to defaults
|
||||||
let default_settings = crate::models::Settings::default();
|
let user_settings = state
|
||||||
|
.db
|
||||||
|
.get_user_settings(auth_user.user.id)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_else(crate::models::Settings::default);
|
||||||
|
|
||||||
// Parse server_address to get host and port
|
// Parse server_address to get host and port
|
||||||
let (server_host, server_port) = if let Some(colon_pos) = config.server_address.rfind(':') {
|
let (server_host, server_port) = if let Some(colon_pos) = config.server_address.rfind(':') {
|
||||||
@@ -201,22 +207,22 @@ async fn get_server_configuration(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let server_config = ServerConfiguration {
|
let server_config = ServerConfiguration {
|
||||||
max_file_size_mb: config.max_file_size_mb,
|
max_file_size_mb: user_settings.max_file_size_mb as u64,
|
||||||
concurrent_ocr_jobs: default_settings.concurrent_ocr_jobs,
|
concurrent_ocr_jobs: user_settings.concurrent_ocr_jobs,
|
||||||
ocr_timeout_seconds: default_settings.ocr_timeout_seconds,
|
ocr_timeout_seconds: user_settings.ocr_timeout_seconds,
|
||||||
memory_limit_mb: default_settings.memory_limit_mb as u64,
|
memory_limit_mb: user_settings.memory_limit_mb as u64,
|
||||||
cpu_priority: default_settings.cpu_priority,
|
cpu_priority: user_settings.cpu_priority,
|
||||||
server_host,
|
server_host,
|
||||||
server_port,
|
server_port,
|
||||||
jwt_secret_set: !config.jwt_secret.is_empty(),
|
jwt_secret_set: !config.jwt_secret.is_empty(),
|
||||||
upload_path: config.upload_path.clone(),
|
upload_path: config.upload_path.clone(),
|
||||||
watch_folder: Some(config.watch_folder.clone()),
|
watch_folder: Some(config.watch_folder.clone()),
|
||||||
ocr_language: default_settings.ocr_language,
|
ocr_language: user_settings.ocr_language,
|
||||||
allowed_file_types: default_settings.allowed_file_types,
|
allowed_file_types: user_settings.allowed_file_types,
|
||||||
watch_interval_seconds: config.watch_interval_seconds,
|
watch_interval_seconds: config.watch_interval_seconds,
|
||||||
file_stability_check_ms: config.file_stability_check_ms,
|
file_stability_check_ms: config.file_stability_check_ms,
|
||||||
max_file_age_hours: config.max_file_age_hours,
|
max_file_age_hours: config.max_file_age_hours,
|
||||||
enable_background_ocr: default_settings.enable_background_ocr,
|
enable_background_ocr: user_settings.enable_background_ocr,
|
||||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
build_info: option_env!("BUILD_INFO").map(|s| s.to_string()),
|
build_info: option_env!("BUILD_INFO").map(|s| s.to_string()),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -451,6 +451,243 @@ mod tests {
|
|||||||
result.unwrap();
|
result.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_server_config_requires_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 a regular user (not admin)
|
||||||
|
let user = auth_helper.create_test_user().await;
|
||||||
|
let token = auth_helper.login_user(&user.username, "password123").await;
|
||||||
|
|
||||||
|
// Try to access server configuration as non-admin
|
||||||
|
let response = ctx.app.clone()
|
||||||
|
.oneshot(
|
||||||
|
axum::http::Request::builder()
|
||||||
|
.method("GET")
|
||||||
|
.uri("/api/settings/config")
|
||||||
|
.header("Authorization", format!("Bearer {}", token))
|
||||||
|
.body(axum::body::Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Should be forbidden for non-admin
|
||||||
|
assert_eq!(response.status(), StatusCode::FORBIDDEN);
|
||||||
|
|
||||||
|
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_server_config_accessible_by_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 an admin user
|
||||||
|
let admin = auth_helper.create_admin_user().await;
|
||||||
|
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||||
|
|
||||||
|
// Access server configuration as admin
|
||||||
|
let response = ctx.app.clone()
|
||||||
|
.oneshot(
|
||||||
|
axum::http::Request::builder()
|
||||||
|
.method("GET")
|
||||||
|
.uri("/api/settings/config")
|
||||||
|
.header("Authorization", format!("Bearer {}", token))
|
||||||
|
.body(axum::body::Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Should succeed for admin
|
||||||
|
let status = response.status();
|
||||||
|
assert!(status == StatusCode::OK || status == StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Expected OK or Internal Server Error, got: {}", status);
|
||||||
|
|
||||||
|
if status == StatusCode::OK {
|
||||||
|
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let config: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||||
|
|
||||||
|
// Verify expected fields are present
|
||||||
|
assert!(config.get("concurrent_ocr_jobs").is_some());
|
||||||
|
assert!(config.get("ocr_timeout_seconds").is_some());
|
||||||
|
assert!(config.get("memory_limit_mb").is_some());
|
||||||
|
assert!(config.get("ocr_language").is_some());
|
||||||
|
assert!(config.get("version").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 that server config returns actual database settings, not defaults
|
||||||
|
/// This is the regression test for issue #393
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_server_config_returns_database_settings_not_defaults() {
|
||||||
|
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 an admin user
|
||||||
|
let admin = auth_helper.create_admin_user().await;
|
||||||
|
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||||
|
|
||||||
|
// First, update the admin's settings with custom values
|
||||||
|
let update_data = UpdateSettings {
|
||||||
|
ocr_language: Some("deu".to_string()), // German instead of default "eng"
|
||||||
|
preferred_languages: None,
|
||||||
|
primary_language: None,
|
||||||
|
auto_detect_language_combination: None,
|
||||||
|
concurrent_ocr_jobs: Some(8), // Custom value instead of default 4
|
||||||
|
ocr_timeout_seconds: Some(120), // Custom value instead of default 60
|
||||||
|
max_file_size_mb: Some(50), // Custom value
|
||||||
|
allowed_file_types: None,
|
||||||
|
auto_rotate_images: None,
|
||||||
|
enable_image_preprocessing: None,
|
||||||
|
search_results_per_page: None,
|
||||||
|
search_snippet_length: None,
|
||||||
|
fuzzy_search_threshold: None,
|
||||||
|
retention_days: None,
|
||||||
|
enable_auto_cleanup: None,
|
||||||
|
enable_compression: None,
|
||||||
|
memory_limit_mb: Some(1024), // Custom value instead of default 512
|
||||||
|
cpu_priority: Some("high".to_string()), // Custom value
|
||||||
|
enable_background_ocr: Some(false), // Custom value instead of default true
|
||||||
|
ocr_page_segmentation_mode: None,
|
||||||
|
ocr_engine_mode: None,
|
||||||
|
ocr_min_confidence: None,
|
||||||
|
ocr_dpi: None,
|
||||||
|
ocr_enhance_contrast: None,
|
||||||
|
ocr_remove_noise: None,
|
||||||
|
ocr_detect_orientation: None,
|
||||||
|
ocr_whitelist_chars: None,
|
||||||
|
ocr_blacklist_chars: None,
|
||||||
|
ocr_brightness_boost: None,
|
||||||
|
ocr_contrast_multiplier: None,
|
||||||
|
ocr_noise_reduction_level: None,
|
||||||
|
ocr_sharpening_strength: None,
|
||||||
|
ocr_morphological_operations: None,
|
||||||
|
ocr_adaptive_threshold_window_size: None,
|
||||||
|
ocr_histogram_equalization: None,
|
||||||
|
ocr_upscale_factor: None,
|
||||||
|
ocr_max_image_width: None,
|
||||||
|
ocr_max_image_height: None,
|
||||||
|
save_processed_images: None,
|
||||||
|
ocr_quality_threshold_brightness: None,
|
||||||
|
ocr_quality_threshold_contrast: None,
|
||||||
|
ocr_quality_threshold_noise: None,
|
||||||
|
ocr_quality_threshold_sharpness: None,
|
||||||
|
ocr_skip_enhancement: None,
|
||||||
|
webdav_enabled: None,
|
||||||
|
webdav_server_url: None,
|
||||||
|
webdav_username: None,
|
||||||
|
webdav_password: None,
|
||||||
|
webdav_watch_folders: None,
|
||||||
|
webdav_file_extensions: None,
|
||||||
|
webdav_auto_sync: None,
|
||||||
|
webdav_sync_interval_minutes: None,
|
||||||
|
office_extraction_timeout_seconds: None,
|
||||||
|
office_extraction_enable_detailed_logging: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the settings
|
||||||
|
let update_response = ctx.app
|
||||||
|
.clone()
|
||||||
|
.oneshot(
|
||||||
|
axum::http::Request::builder()
|
||||||
|
.method("PUT")
|
||||||
|
.uri("/api/settings")
|
||||||
|
.header("Authorization", format!("Bearer {}", token))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(axum::body::Body::from(serde_json::to_vec(&update_data).unwrap()))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Check that settings were updated successfully
|
||||||
|
let update_status = update_response.status();
|
||||||
|
assert!(update_status == StatusCode::OK || update_status == StatusCode::BAD_REQUEST,
|
||||||
|
"Expected OK or Bad Request for settings update, got: {}", update_status);
|
||||||
|
|
||||||
|
if update_status == StatusCode::OK {
|
||||||
|
// Now fetch the server configuration and verify it returns our custom values
|
||||||
|
let config_response = ctx.app.clone()
|
||||||
|
.oneshot(
|
||||||
|
axum::http::Request::builder()
|
||||||
|
.method("GET")
|
||||||
|
.uri("/api/settings/config")
|
||||||
|
.header("Authorization", format!("Bearer {}", token))
|
||||||
|
.body(axum::body::Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config_response.status(), StatusCode::OK,
|
||||||
|
"Server config should be accessible to admin");
|
||||||
|
|
||||||
|
let body = axum::body::to_bytes(config_response.into_body(), usize::MAX)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let config: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||||
|
|
||||||
|
// CRITICAL: These assertions verify the fix for issue #393
|
||||||
|
// The server config should return the values we set, NOT the defaults
|
||||||
|
assert_eq!(config["ocr_language"], "deu",
|
||||||
|
"Server config should return user's OCR language, not default 'eng'");
|
||||||
|
assert_eq!(config["concurrent_ocr_jobs"], 8,
|
||||||
|
"Server config should return user's concurrent_ocr_jobs (8), not default (4)");
|
||||||
|
assert_eq!(config["ocr_timeout_seconds"], 120,
|
||||||
|
"Server config should return user's ocr_timeout_seconds (120), not default (60)");
|
||||||
|
assert_eq!(config["memory_limit_mb"], 1024,
|
||||||
|
"Server config should return user's memory_limit_mb (1024), not default (512)");
|
||||||
|
assert_eq!(config["cpu_priority"], "high",
|
||||||
|
"Server config should return user's cpu_priority ('high'), not default ('normal')");
|
||||||
|
assert_eq!(config["enable_background_ocr"], false,
|
||||||
|
"Server config should return user's enable_background_ocr (false), not default (true)");
|
||||||
|
assert_eq!(config["max_file_size_mb"], 50,
|
||||||
|
"Server config should return user's max_file_size_mb (50)");
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
#[tokio::test]
|
||||||
async fn test_validate_multi_language_settings_max_limit() {
|
async fn test_validate_multi_language_settings_max_limit() {
|
||||||
let ctx = TestContext::new().await;
|
let ctx = TestContext::new().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user