feat(server): webdav tests pass
This commit is contained in:
parent
57c118c049
commit
cc04d5818b
|
|
@ -0,0 +1,352 @@
|
|||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
use uuid::Uuid;
|
||||
|
||||
use readur::{
|
||||
webdav_service::{WebDAVService, WebDAVConfig, RetryConfig},
|
||||
webdav_scheduler::WebDAVScheduler,
|
||||
models::*,
|
||||
db::Database,
|
||||
config::Config,
|
||||
AppState,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_retry_config_default() {
|
||||
let retry_config = RetryConfig::default();
|
||||
|
||||
assert_eq!(retry_config.max_retries, 3);
|
||||
assert_eq!(retry_config.initial_delay_ms, 1000);
|
||||
assert_eq!(retry_config.max_delay_ms, 30000);
|
||||
assert_eq!(retry_config.backoff_multiplier, 2.0);
|
||||
assert_eq!(retry_config.timeout_seconds, 120);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_webdav_service_with_custom_retry() {
|
||||
let config = WebDAVConfig {
|
||||
server_url: "https://cloud.example.com".to_string(),
|
||||
username: "testuser".to_string(),
|
||||
password: "testpass".to_string(),
|
||||
watch_folders: vec!["/Documents".to_string()],
|
||||
file_extensions: vec!["pdf".to_string()],
|
||||
timeout_seconds: 30,
|
||||
server_type: Some("nextcloud".to_string()),
|
||||
};
|
||||
|
||||
let retry_config = RetryConfig {
|
||||
max_retries: 5,
|
||||
initial_delay_ms: 500,
|
||||
max_delay_ms: 10000,
|
||||
backoff_multiplier: 1.5,
|
||||
timeout_seconds: 60,
|
||||
};
|
||||
|
||||
let result = WebDAVService::new_with_retry(config, retry_config);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_webdav_config_builder() {
|
||||
let config = WebDAVConfig {
|
||||
server_url: "https://nextcloud.example.com".to_string(),
|
||||
username: "admin".to_string(),
|
||||
password: "secret123".to_string(),
|
||||
watch_folders: vec!["/Documents".to_string(), "/Photos".to_string()],
|
||||
file_extensions: vec!["pdf".to_string(), "png".to_string(), "jpg".to_string()],
|
||||
timeout_seconds: 60,
|
||||
server_type: Some("nextcloud".to_string()),
|
||||
};
|
||||
|
||||
// Test Nextcloud URL construction
|
||||
let service = WebDAVService::new(config.clone()).unwrap();
|
||||
// Note: We can't directly test the private base_webdav_url field,
|
||||
// but we can test that the service was created successfully
|
||||
|
||||
assert_eq!(config.watch_folders.len(), 2);
|
||||
assert_eq!(config.file_extensions.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notification_models() {
|
||||
let notification_id = Uuid::new_v4();
|
||||
let user_id = Uuid::new_v4();
|
||||
|
||||
let notification = Notification {
|
||||
id: notification_id,
|
||||
user_id,
|
||||
notification_type: "success".to_string(),
|
||||
title: "WebDAV Sync Complete".to_string(),
|
||||
message: "Successfully processed 5 files".to_string(),
|
||||
read: false,
|
||||
action_url: Some("/documents".to_string()),
|
||||
metadata: Some(serde_json::json!({
|
||||
"sync_type": "webdav",
|
||||
"files_processed": 5
|
||||
})),
|
||||
created_at: chrono::Utc::now(),
|
||||
};
|
||||
|
||||
assert_eq!(notification.id, notification_id);
|
||||
assert_eq!(notification.user_id, user_id);
|
||||
assert_eq!(notification.notification_type, "success");
|
||||
assert_eq!(notification.title, "WebDAV Sync Complete");
|
||||
assert!(!notification.read);
|
||||
assert_eq!(notification.action_url, Some("/documents".to_string()));
|
||||
|
||||
// Test metadata extraction
|
||||
let metadata = notification.metadata.unwrap();
|
||||
assert_eq!(metadata["sync_type"], "webdav");
|
||||
assert_eq!(metadata["files_processed"], 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_notification_model() {
|
||||
let create_notification = CreateNotification {
|
||||
notification_type: "warning".to_string(),
|
||||
title: "WebDAV Connection Issue".to_string(),
|
||||
message: "Unable to connect to WebDAV server, retrying...".to_string(),
|
||||
action_url: Some("/settings".to_string()),
|
||||
metadata: Some(serde_json::json!({
|
||||
"error_type": "connection_timeout",
|
||||
"retry_count": 2
|
||||
})),
|
||||
};
|
||||
|
||||
assert_eq!(create_notification.notification_type, "warning");
|
||||
assert_eq!(create_notification.title, "WebDAV Connection Issue");
|
||||
assert_eq!(create_notification.action_url, Some("/settings".to_string()));
|
||||
|
||||
let metadata = create_notification.metadata.unwrap();
|
||||
assert_eq!(metadata["error_type"], "connection_timeout");
|
||||
assert_eq!(metadata["retry_count"], 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notification_summary() {
|
||||
let user_id = Uuid::new_v4();
|
||||
|
||||
let notification1 = Notification {
|
||||
id: Uuid::new_v4(),
|
||||
user_id,
|
||||
notification_type: "success".to_string(),
|
||||
title: "Sync Complete".to_string(),
|
||||
message: "10 files processed".to_string(),
|
||||
read: false,
|
||||
action_url: None,
|
||||
metadata: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
};
|
||||
|
||||
let notification2 = Notification {
|
||||
id: Uuid::new_v4(),
|
||||
user_id,
|
||||
notification_type: "error".to_string(),
|
||||
title: "Sync Failed".to_string(),
|
||||
message: "Connection timeout".to_string(),
|
||||
read: true,
|
||||
action_url: Some("/settings".to_string()),
|
||||
metadata: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
};
|
||||
|
||||
let summary = NotificationSummary {
|
||||
unread_count: 1,
|
||||
recent_notifications: vec![notification1, notification2],
|
||||
};
|
||||
|
||||
assert_eq!(summary.unread_count, 1);
|
||||
assert_eq!(summary.recent_notifications.len(), 2);
|
||||
assert!(!summary.recent_notifications[0].read);
|
||||
assert!(summary.recent_notifications[1].read);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_webdav_error_handling() {
|
||||
// Test error classification for retry logic
|
||||
let timeout_error = anyhow::anyhow!("Connection timeout occurred");
|
||||
let network_error = anyhow::anyhow!("Network connection failed");
|
||||
let auth_error = anyhow::anyhow!("401 Unauthorized");
|
||||
|
||||
// These would be tested by WebDAVService::is_retryable_error if it were public
|
||||
// For now, we test that errors can be created and formatted
|
||||
assert!(timeout_error.to_string().contains("timeout"));
|
||||
assert!(network_error.to_string().contains("connection"));
|
||||
assert!(auth_error.to_string().contains("401"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_webdav_file_filtering() {
|
||||
let supported_extensions = vec!["pdf", "png", "jpg", "jpeg", "tiff", "bmp", "txt"];
|
||||
|
||||
// Test file extension extraction and filtering
|
||||
let test_files = vec![
|
||||
"document.pdf",
|
||||
"image.PNG", // Test case insensitivity
|
||||
"photo.jpg",
|
||||
"spreadsheet.xlsx", // Should be filtered out
|
||||
"text.txt",
|
||||
"archive.zip", // Should be filtered out
|
||||
"picture.jpeg",
|
||||
];
|
||||
|
||||
let mut supported_count = 0;
|
||||
for filename in test_files {
|
||||
if let Some(extension) = std::path::Path::new(filename)
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
{
|
||||
let ext_lower = extension.to_lowercase();
|
||||
if supported_extensions.contains(&ext_lower.as_str()) {
|
||||
supported_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(supported_count, 5); // pdf, PNG, jpg, txt, jpeg
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_priority_calculation() {
|
||||
// Test OCR priority calculation based on file size
|
||||
let test_cases = vec![
|
||||
(500_000, 10), // 500KB -> highest priority
|
||||
(2_000_000, 8), // 2MB -> high priority
|
||||
(7_000_000, 6), // 7MB -> medium priority
|
||||
(25_000_000, 4), // 25MB -> low priority
|
||||
(100_000_000, 2), // 100MB -> lowest priority
|
||||
];
|
||||
|
||||
for (file_size, expected_priority) in test_cases {
|
||||
let priority = match file_size {
|
||||
0..=1048576 => 10, // <= 1MB
|
||||
..=5242880 => 8, // 1-5MB
|
||||
..=10485760 => 6, // 5-10MB
|
||||
..=52428800 => 4, // 10-50MB
|
||||
_ => 2, // > 50MB
|
||||
};
|
||||
|
||||
assert_eq!(priority, expected_priority,
|
||||
"File size {} bytes should have priority {}", file_size, expected_priority);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_webdav_url_construction() {
|
||||
// Test different server types and URL construction
|
||||
let test_cases = vec![
|
||||
("nextcloud", "https://cloud.example.com", "testuser", "https://cloud.example.com/remote.php/dav/files/testuser"),
|
||||
("owncloud", "https://cloud.example.com/", "admin", "https://cloud.example.com/remote.php/dav/files/admin"),
|
||||
("generic", "https://webdav.example.com", "user", "https://webdav.example.com/webdav"),
|
||||
];
|
||||
|
||||
for (server_type, server_url, username, expected_base) in test_cases {
|
||||
let config = WebDAVConfig {
|
||||
server_url: server_url.to_string(),
|
||||
username: username.to_string(),
|
||||
password: "password".to_string(),
|
||||
watch_folders: vec!["/Documents".to_string()],
|
||||
file_extensions: vec!["pdf".to_string()],
|
||||
timeout_seconds: 30,
|
||||
server_type: Some(server_type.to_string()),
|
||||
};
|
||||
|
||||
let service = WebDAVService::new(config);
|
||||
assert!(service.is_ok(), "Failed to create WebDAV service for {} server type", server_type);
|
||||
|
||||
// Note: We can't directly test the URL construction since base_webdav_url is private,
|
||||
// but we can verify the service was created successfully with the config
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_settings_webdav_integration() {
|
||||
let mut settings = Settings::default();
|
||||
|
||||
// Test enabling WebDAV
|
||||
settings.webdav_enabled = true;
|
||||
settings.webdav_server_url = Some("https://nextcloud.example.com".to_string());
|
||||
settings.webdav_username = Some("testuser".to_string());
|
||||
settings.webdav_password = Some("testpass".to_string());
|
||||
settings.webdav_auto_sync = true;
|
||||
settings.webdav_sync_interval_minutes = 30;
|
||||
settings.webdav_watch_folders = vec!["/Documents".to_string(), "/Photos".to_string()];
|
||||
|
||||
assert!(settings.webdav_enabled);
|
||||
assert_eq!(settings.webdav_server_url, Some("https://nextcloud.example.com".to_string()));
|
||||
assert_eq!(settings.webdav_username, Some("testuser".to_string()));
|
||||
assert!(settings.webdav_auto_sync);
|
||||
assert_eq!(settings.webdav_sync_interval_minutes, 30);
|
||||
assert_eq!(settings.webdav_watch_folders.len(), 2);
|
||||
|
||||
// Test that we can build a WebDAVConfig from settings
|
||||
if let (Some(server_url), Some(username)) = (&settings.webdav_server_url, &settings.webdav_username) {
|
||||
let webdav_config = WebDAVConfig {
|
||||
server_url: server_url.clone(),
|
||||
username: username.clone(),
|
||||
password: settings.webdav_password.clone().unwrap_or_default(),
|
||||
watch_folders: settings.webdav_watch_folders.clone(),
|
||||
file_extensions: settings.webdav_file_extensions.clone(),
|
||||
timeout_seconds: 30,
|
||||
server_type: Some("nextcloud".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(webdav_config.server_url, "https://nextcloud.example.com");
|
||||
assert_eq!(webdav_config.username, "testuser");
|
||||
assert_eq!(webdav_config.watch_folders.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backoff_calculation() {
|
||||
let retry_config = RetryConfig::default();
|
||||
let mut delay = retry_config.initial_delay_ms;
|
||||
|
||||
// Test exponential backoff calculation
|
||||
let expected_delays = vec![1000, 2000, 4000]; // 1s, 2s, 4s with 2.0 multiplier
|
||||
|
||||
for expected in expected_delays {
|
||||
assert_eq!(delay, expected);
|
||||
delay = ((delay as f64 * retry_config.backoff_multiplier) as u64)
|
||||
.min(retry_config.max_delay_ms);
|
||||
}
|
||||
|
||||
// Test that delay doesn't exceed max
|
||||
for _ in 0..10 {
|
||||
delay = ((delay as f64 * retry_config.backoff_multiplier) as u64)
|
||||
.min(retry_config.max_delay_ms);
|
||||
assert!(delay <= retry_config.max_delay_ms);
|
||||
}
|
||||
}
|
||||
|
||||
// Mock test for WebDAV scheduler (without actual database)
|
||||
#[test]
|
||||
fn test_webdav_scheduler_creation() {
|
||||
// Create mock state - in a real test environment you'd use test database
|
||||
let config = Config {
|
||||
database_url: "postgres://test".to_string(),
|
||||
server_address: "127.0.0.1:3000".to_string(),
|
||||
upload_path: "/tmp/test_uploads".to_string(),
|
||||
watch_folder: "/tmp/test_watch".to_string(),
|
||||
jwt_secret: "test_secret".to_string(),
|
||||
allowed_file_types: vec!["pdf".to_string(), "png".to_string()],
|
||||
watch_interval_seconds: Some(10),
|
||||
file_stability_check_ms: Some(1000),
|
||||
max_file_age_hours: Some(24),
|
||||
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,
|
||||
};
|
||||
|
||||
// Note: This is a minimal test since we can't easily mock the database
|
||||
// In a full integration test, you'd set up a test database
|
||||
|
||||
assert_eq!(config.server_address, "127.0.0.1:3000");
|
||||
assert_eq!(config.upload_path, "/tmp/test_uploads");
|
||||
|
||||
// The scheduler would be tested with a real database in integration tests
|
||||
}
|
||||
|
|
@ -0,0 +1,541 @@
|
|||
use std::sync::Arc;
|
||||
use axum::{
|
||||
body::{Body, to_bytes},
|
||||
http::{Request, StatusCode},
|
||||
Router,
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use serde_json::{json, Value};
|
||||
use uuid::Uuid;
|
||||
|
||||
use readur::{
|
||||
db::Database,
|
||||
config::Config,
|
||||
models::*,
|
||||
routes,
|
||||
AppState,
|
||||
};
|
||||
|
||||
// Test database URL - in real tests you'd use a separate test database
|
||||
const TEST_DATABASE_URL: &str = "postgres://postgres:password@localhost:5432/readur_test";
|
||||
|
||||
fn create_empty_update_settings() -> UpdateSettings {
|
||||
UpdateSettings {
|
||||
ocr_language: None,
|
||||
concurrent_ocr_jobs: None,
|
||||
ocr_timeout_seconds: None,
|
||||
max_file_size_mb: None,
|
||||
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: None,
|
||||
cpu_priority: None,
|
||||
enable_background_ocr: None,
|
||||
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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_test_app() -> (Router, Arc<AppState>) {
|
||||
let config = Config {
|
||||
database_url: TEST_DATABASE_URL.to_string(),
|
||||
server_address: "127.0.0.1:0".to_string(),
|
||||
upload_path: "/tmp/test_uploads".to_string(),
|
||||
watch_folder: "/tmp/test_watch".to_string(),
|
||||
jwt_secret: "test_jwt_secret_for_integration_tests".to_string(),
|
||||
allowed_file_types: vec!["pdf".to_string(), "png".to_string()],
|
||||
watch_interval_seconds: Some(10),
|
||||
file_stability_check_ms: Some(1000),
|
||||
max_file_age_hours: Some(24),
|
||||
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,
|
||||
};
|
||||
|
||||
// Try to connect to test database, fall back to regular database if not available
|
||||
let db_url = if Database::new(TEST_DATABASE_URL).await.is_ok() {
|
||||
TEST_DATABASE_URL.to_string()
|
||||
} else {
|
||||
std::env::var("DATABASE_URL").unwrap_or_else(|_|
|
||||
"postgres://postgres:password@localhost:5432/readur".to_string())
|
||||
};
|
||||
|
||||
let db = Database::new(&db_url).await.expect("Failed to connect to test database");
|
||||
let state = Arc::new(AppState { db, config });
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/api/auth", routes::auth::router())
|
||||
.nest("/api/webdav", routes::webdav::router())
|
||||
.nest("/api/notifications", routes::notifications::router())
|
||||
.nest("/api/settings", routes::settings::router())
|
||||
.with_state(state.clone());
|
||||
|
||||
(app, state)
|
||||
}
|
||||
|
||||
async fn create_test_user(state: &AppState) -> (User, String) {
|
||||
let create_user = CreateUser {
|
||||
username: format!("testuser_{}", Uuid::new_v4()),
|
||||
email: format!("test_{}@example.com", Uuid::new_v4()),
|
||||
password: "testpassword123".to_string(),
|
||||
role: Some(UserRole::User),
|
||||
};
|
||||
|
||||
let user = state.db.create_user(create_user).await
|
||||
.expect("Failed to create test user");
|
||||
|
||||
// Create a simple JWT token for testing (in real tests you'd use proper JWT)
|
||||
let token = format!("Bearer test_token_for_user_{}", user.id);
|
||||
|
||||
(user, token)
|
||||
}
|
||||
|
||||
async fn setup_webdav_settings(state: &AppState, user_id: Uuid) {
|
||||
let update_settings = UpdateSettings {
|
||||
webdav_enabled: Some(true),
|
||||
webdav_server_url: Some(Some("https://demo.nextcloud.com".to_string())),
|
||||
webdav_username: Some(Some("demo_user".to_string())),
|
||||
webdav_password: Some(Some("demo_password".to_string())),
|
||||
webdav_watch_folders: Some(vec!["/Documents".to_string()]),
|
||||
webdav_file_extensions: Some(vec!["pdf".to_string(), "png".to_string()]),
|
||||
webdav_auto_sync: Some(true),
|
||||
webdav_sync_interval_minutes: Some(60),
|
||||
ocr_language: None,
|
||||
concurrent_ocr_jobs: None,
|
||||
ocr_timeout_seconds: None,
|
||||
max_file_size_mb: None,
|
||||
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: None,
|
||||
cpu_priority: None,
|
||||
enable_background_ocr: None,
|
||||
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,
|
||||
};
|
||||
|
||||
state.db.create_or_update_settings(user_id, &update_settings).await
|
||||
.expect("Failed to setup WebDAV settings");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_webdav_test_connection_endpoint() {
|
||||
let (app, state) = setup_test_app().await;
|
||||
let (_user, token) = create_test_user(&state).await;
|
||||
|
||||
let test_connection_request = json!({
|
||||
"server_url": "https://demo.nextcloud.com",
|
||||
"username": "demo_user",
|
||||
"password": "demo_password",
|
||||
"server_type": "nextcloud"
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/webdav/test-connection")
|
||||
.header("Authorization", token)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(test_connection_request.to_string()))
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
|
||||
// Note: This will likely fail with connection error since demo.nextcloud.com
|
||||
// may not accept these credentials, but we're testing the endpoint structure
|
||||
assert!(
|
||||
response.status() == StatusCode::OK ||
|
||||
response.status() == StatusCode::INTERNAL_SERVER_ERROR
|
||||
);
|
||||
|
||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let result: Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
// Should have a result structure even if connection fails
|
||||
assert!(result.get("success").is_some());
|
||||
assert!(result.get("message").is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_webdav_estimate_crawl_endpoint() {
|
||||
let (app, state) = setup_test_app().await;
|
||||
let (user, token) = create_test_user(&state).await;
|
||||
|
||||
// Setup WebDAV settings first
|
||||
setup_webdav_settings(&state, user.id).await;
|
||||
|
||||
let crawl_request = json!({
|
||||
"folders": ["/Documents", "/Photos"]
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/webdav/estimate-crawl")
|
||||
.header("Authorization", token)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(crawl_request.to_string()))
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
|
||||
// Even if WebDAV connection fails, should return estimate structure
|
||||
assert!(
|
||||
response.status() == StatusCode::OK ||
|
||||
response.status() == StatusCode::INTERNAL_SERVER_ERROR
|
||||
);
|
||||
|
||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let result: Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
// Should have crawl estimate structure
|
||||
assert!(result.get("folders").is_some());
|
||||
assert!(result.get("total_files").is_some());
|
||||
assert!(result.get("total_supported_files").is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_webdav_sync_status_endpoint() {
|
||||
let (app, state) = setup_test_app().await;
|
||||
let (user, token) = create_test_user(&state).await;
|
||||
|
||||
setup_webdav_settings(&state, user.id).await;
|
||||
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/webdav/sync-status")
|
||||
.header("Authorization", token)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let result: Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
// Should return sync status structure
|
||||
assert!(result.get("is_running").is_some());
|
||||
assert!(result.get("files_processed").is_some());
|
||||
assert!(result.get("files_remaining").is_some());
|
||||
assert!(result.get("errors").is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_webdav_start_sync_endpoint() {
|
||||
let (app, state) = setup_test_app().await;
|
||||
let (user, token) = create_test_user(&state).await;
|
||||
|
||||
setup_webdav_settings(&state, user.id).await;
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/webdav/start-sync")
|
||||
.header("Authorization", token)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
|
||||
// Should accept the sync request (even if it fails later due to invalid credentials)
|
||||
let status = response.status();
|
||||
assert!(
|
||||
status == StatusCode::OK ||
|
||||
status == StatusCode::BAD_REQUEST // If WebDAV not properly configured
|
||||
);
|
||||
|
||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let result: Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
if status == StatusCode::OK {
|
||||
assert_eq!(result.get("success").unwrap(), &json!(true));
|
||||
assert!(result.get("message").is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_notifications_endpoints() {
|
||||
let (app, state) = setup_test_app().await;
|
||||
let (user, token) = create_test_user(&state).await;
|
||||
|
||||
// Create a test notification directly in the database
|
||||
let create_notification = CreateNotification {
|
||||
notification_type: "success".to_string(),
|
||||
title: "Test WebDAV Sync".to_string(),
|
||||
message: "Successfully processed 3 test files".to_string(),
|
||||
action_url: Some("/documents".to_string()),
|
||||
metadata: Some(json!({
|
||||
"sync_type": "webdav_test",
|
||||
"files_processed": 3
|
||||
})),
|
||||
};
|
||||
|
||||
let notification = state.db.create_notification(user.id, &create_notification).await
|
||||
.expect("Failed to create test notification");
|
||||
|
||||
// Test GET /api/notifications
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/notifications")
|
||||
.header("Authorization", token.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let notifications: Vec<Value> = serde_json::from_slice(&body).unwrap();
|
||||
assert!(notifications.len() >= 1);
|
||||
|
||||
// Test GET /api/notifications/summary
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/notifications/summary")
|
||||
.header("Authorization", token.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let summary: Value = serde_json::from_slice(&body).unwrap();
|
||||
assert!(summary.get("unread_count").is_some());
|
||||
assert!(summary.get("recent_notifications").is_some());
|
||||
|
||||
// Test POST /api/notifications/{id}/read
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri(&format!("/api/notifications/{}/read", notification.id))
|
||||
.header("Authorization", token.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
// Test POST /api/notifications/read-all
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/notifications/read-all")
|
||||
.header("Authorization", token.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
// Test DELETE /api/notifications/{id}
|
||||
let request = Request::builder()
|
||||
.method("DELETE")
|
||||
.uri(&format!("/api/notifications/{}", notification.id))
|
||||
.header("Authorization", token)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_settings_webdav_integration() {
|
||||
let (app, state) = setup_test_app().await;
|
||||
let (_user, token) = create_test_user(&state).await;
|
||||
|
||||
// Test updating WebDAV settings
|
||||
let settings_update = json!({
|
||||
"webdav_enabled": true,
|
||||
"webdav_server_url": "https://test.nextcloud.com",
|
||||
"webdav_username": "testuser",
|
||||
"webdav_password": "testpass",
|
||||
"webdav_watch_folders": ["/Documents", "/Photos"],
|
||||
"webdav_file_extensions": ["pdf", "png", "jpg"],
|
||||
"webdav_auto_sync": true,
|
||||
"webdav_sync_interval_minutes": 30
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("PUT")
|
||||
.uri("/api/settings")
|
||||
.header("Authorization", token.clone())
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(settings_update.to_string()))
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
// Test retrieving settings
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/settings")
|
||||
.header("Authorization", token)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let settings: Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
assert_eq!(settings.get("webdav_enabled").unwrap(), &json!(true));
|
||||
assert_eq!(settings.get("webdav_server_url").unwrap(), &json!("https://test.nextcloud.com"));
|
||||
assert_eq!(settings.get("webdav_username").unwrap(), &json!("testuser"));
|
||||
assert_eq!(settings.get("webdav_auto_sync").unwrap(), &json!(true));
|
||||
assert_eq!(settings.get("webdav_sync_interval_minutes").unwrap(), &json!(30));
|
||||
|
||||
let folders = settings.get("webdav_watch_folders").unwrap().as_array().unwrap();
|
||||
assert_eq!(folders.len(), 2);
|
||||
assert!(folders.contains(&json!("/Documents")));
|
||||
assert!(folders.contains(&json!("/Photos")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_notification_database_operations() {
|
||||
let (_, state) = setup_test_app().await;
|
||||
let (user, _token) = create_test_user(&state).await;
|
||||
|
||||
// Test creating notification
|
||||
let create_notification = CreateNotification {
|
||||
notification_type: "info".to_string(),
|
||||
title: "WebDAV Sync Started".to_string(),
|
||||
message: "Synchronization with WebDAV server has begun".to_string(),
|
||||
action_url: Some("/sync-status".to_string()),
|
||||
metadata: Some(json!({
|
||||
"sync_id": "sync_123",
|
||||
"folders": ["/Documents", "/Photos"]
|
||||
})),
|
||||
};
|
||||
|
||||
let notification = state.db.create_notification(user.id, &create_notification).await
|
||||
.expect("Failed to create notification");
|
||||
|
||||
assert_eq!(notification.user_id, user.id);
|
||||
assert_eq!(notification.notification_type, "info");
|
||||
assert_eq!(notification.title, "WebDAV Sync Started");
|
||||
assert!(!notification.read);
|
||||
|
||||
// Test getting user notifications
|
||||
let notifications = state.db.get_user_notifications(user.id, 10, 0).await
|
||||
.expect("Failed to get notifications");
|
||||
|
||||
assert!(notifications.len() >= 1);
|
||||
let found_notification = notifications.iter()
|
||||
.find(|n| n.id == notification.id)
|
||||
.expect("Created notification not found");
|
||||
|
||||
assert_eq!(found_notification.title, "WebDAV Sync Started");
|
||||
|
||||
// Test getting unread count
|
||||
let unread_count = state.db.get_unread_notification_count(user.id).await
|
||||
.expect("Failed to get unread count");
|
||||
|
||||
assert!(unread_count >= 1);
|
||||
|
||||
// Test marking as read
|
||||
state.db.mark_notification_read(user.id, notification.id).await
|
||||
.expect("Failed to mark notification as read");
|
||||
|
||||
let updated_notifications = state.db.get_user_notifications(user.id, 10, 0).await
|
||||
.expect("Failed to get updated notifications");
|
||||
|
||||
let updated_notification = updated_notifications.iter()
|
||||
.find(|n| n.id == notification.id)
|
||||
.expect("Notification not found after update");
|
||||
|
||||
assert!(updated_notification.read);
|
||||
|
||||
// Test getting notification summary
|
||||
let summary = state.db.get_notification_summary(user.id).await
|
||||
.expect("Failed to get notification summary");
|
||||
|
||||
assert!(summary.recent_notifications.len() >= 1);
|
||||
assert!(summary.unread_count >= 0);
|
||||
|
||||
// Test deleting notification
|
||||
state.db.delete_notification(user.id, notification.id).await
|
||||
.expect("Failed to delete notification");
|
||||
|
||||
let final_notifications = state.db.get_user_notifications(user.id, 10, 0).await
|
||||
.expect("Failed to get final notifications");
|
||||
|
||||
assert!(!final_notifications.iter().any(|n| n.id == notification.id));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_webdav_settings_validation() {
|
||||
let (_, state) = setup_test_app().await;
|
||||
let (user, _token) = create_test_user(&state).await;
|
||||
|
||||
// Test invalid WebDAV settings (missing required fields)
|
||||
let mut invalid_settings = create_empty_update_settings();
|
||||
invalid_settings.webdav_enabled = Some(true);
|
||||
invalid_settings.webdav_server_url = Some(Some("".to_string())); // Empty URL should cause issues
|
||||
invalid_settings.webdav_username = Some(None); // Missing username
|
||||
invalid_settings.webdav_password = Some(Some("password".to_string()));
|
||||
|
||||
// This should succeed in database but fail when trying to create WebDAV config
|
||||
let settings = state.db.create_or_update_settings(user.id, &invalid_settings).await
|
||||
.expect("Failed to save settings");
|
||||
|
||||
assert!(settings.webdav_enabled);
|
||||
assert_eq!(settings.webdav_server_url, Some("".to_string()));
|
||||
assert_eq!(settings.webdav_username, None);
|
||||
|
||||
// Test valid WebDAV settings
|
||||
let mut valid_settings = create_empty_update_settings();
|
||||
valid_settings.webdav_enabled = Some(true);
|
||||
valid_settings.webdav_server_url = Some(Some("https://valid.nextcloud.com".to_string()));
|
||||
valid_settings.webdav_username = Some(Some("validuser".to_string()));
|
||||
valid_settings.webdav_password = Some(Some("validpass".to_string()));
|
||||
valid_settings.webdav_watch_folders = Some(vec!["/Documents".to_string()]);
|
||||
valid_settings.webdav_file_extensions = Some(vec!["pdf".to_string(), "png".to_string()]);
|
||||
valid_settings.webdav_auto_sync = Some(true);
|
||||
valid_settings.webdav_sync_interval_minutes = Some(60);
|
||||
|
||||
let valid_result = state.db.create_or_update_settings(user.id, &valid_settings).await
|
||||
.expect("Failed to save valid settings");
|
||||
|
||||
assert!(valid_result.webdav_enabled);
|
||||
assert_eq!(valid_result.webdav_server_url, Some("https://valid.nextcloud.com".to_string()));
|
||||
assert_eq!(valid_result.webdav_username, Some("validuser".to_string()));
|
||||
assert_eq!(valid_result.webdav_watch_folders, vec!["/Documents".to_string()]);
|
||||
assert_eq!(valid_result.webdav_sync_interval_minutes, 60);
|
||||
}
|
||||
Loading…
Reference in New Issue