mirror of
https://github.com/readur/readur.git
synced 2025-12-19 21:31:22 -06:00
375 lines
13 KiB
Rust
375 lines
13 KiB
Rust
use anyhow::Result;
|
|
use std::sync::Arc;
|
|
use uuid::Uuid;
|
|
use tokio;
|
|
use futures::future::join_all;
|
|
use readur::{
|
|
models::{CreateWebDAVDirectory, CreateUser, UserRole},
|
|
test_utils::TestContext,
|
|
};
|
|
|
|
#[tokio::test]
|
|
async fn test_bulk_create_or_update_atomic() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
// Ensure cleanup happens even if test fails
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
|
|
// Create a test user first
|
|
let create_user = CreateUser {
|
|
username: "testuser".to_string(),
|
|
email: "test@example.com".to_string(),
|
|
password: "password123".to_string(),
|
|
role: Some(UserRole::User),
|
|
};
|
|
let user = db.create_user(create_user).await
|
|
.expect("Failed to create test user");
|
|
let user_id = user.id;
|
|
|
|
let directories = vec![
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir1".to_string(),
|
|
directory_etag: "etag1".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir2".to_string(),
|
|
directory_etag: "etag2".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir3".to_string(),
|
|
directory_etag: "etag3".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
];
|
|
|
|
// Test bulk operation
|
|
let result = db.bulk_create_or_update_webdav_directories(&directories).await;
|
|
if let Err(e) = &result {
|
|
eprintln!("Error in bulk_create_or_update_webdav_directories: {}", e);
|
|
}
|
|
assert!(result.is_ok());
|
|
|
|
let saved_directories = result.unwrap();
|
|
assert_eq!(saved_directories.len(), 3);
|
|
|
|
// Verify all directories were saved with correct ETags
|
|
for (original, saved) in directories.iter().zip(saved_directories.iter()) {
|
|
assert_eq!(original.directory_path, saved.directory_path);
|
|
assert_eq!(original.directory_etag, saved.directory_etag);
|
|
assert_eq!(original.user_id, saved.user_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_sync_webdav_directories_atomic() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
// Ensure cleanup happens even if test fails
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
|
|
// Create a test user first
|
|
let create_user = CreateUser {
|
|
username: "testuser2".to_string(),
|
|
email: "test2@example.com".to_string(),
|
|
password: "password123".to_string(),
|
|
role: Some(UserRole::User),
|
|
};
|
|
let user = db.create_user(create_user).await
|
|
.expect("Failed to create test user");
|
|
let user_id = user.id;
|
|
|
|
// First, create some initial directories
|
|
let initial_directories = vec![
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir1".to_string(),
|
|
directory_etag: "etag1".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir2".to_string(),
|
|
directory_etag: "etag2".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
];
|
|
|
|
let _ = db.bulk_create_or_update_webdav_directories(&initial_directories).await.unwrap();
|
|
|
|
// Now sync with a new set that has one update, one delete, and one new
|
|
let sync_directories = vec![
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir1".to_string(),
|
|
directory_etag: "etag1_updated".to_string(), // Updated
|
|
file_count: 5,
|
|
total_size_bytes: 1024,
|
|
},
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir3".to_string(), // New
|
|
directory_etag: "etag3".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
// dir2 is missing, should be deleted
|
|
];
|
|
|
|
let result = db.sync_webdav_directories(user_id, &sync_directories).await;
|
|
assert!(result.is_ok());
|
|
|
|
let (updated_directories, deleted_count) = result.unwrap();
|
|
|
|
// Should have 2 directories (dir1 updated, dir3 new)
|
|
assert_eq!(updated_directories.len(), 2);
|
|
|
|
// Should have deleted 1 directory (dir2)
|
|
assert_eq!(deleted_count, 1);
|
|
|
|
// Verify the updated directory has the new ETag
|
|
let dir1 = updated_directories.iter()
|
|
.find(|d| d.directory_path == "/test/dir1")
|
|
.unwrap();
|
|
assert_eq!(dir1.directory_etag, "etag1_updated");
|
|
assert_eq!(dir1.file_count, 5);
|
|
assert_eq!(dir1.total_size_bytes, 1024);
|
|
|
|
// Verify the new directory exists
|
|
let dir3 = updated_directories.iter()
|
|
.find(|d| d.directory_path == "/test/dir3")
|
|
.unwrap();
|
|
assert_eq!(dir3.directory_etag, "etag3");
|
|
|
|
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_missing_directories() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
// Ensure cleanup happens even if test fails
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
|
|
// Create a test user first
|
|
let create_user = CreateUser {
|
|
username: "testuser3".to_string(),
|
|
email: "test3@example.com".to_string(),
|
|
password: "password123".to_string(),
|
|
role: Some(UserRole::User),
|
|
};
|
|
let user = db.create_user(create_user).await
|
|
.expect("Failed to create test user");
|
|
let user_id = user.id;
|
|
|
|
// Create some directories
|
|
let directories = vec![
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir1".to_string(),
|
|
directory_etag: "etag1".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir2".to_string(),
|
|
directory_etag: "etag2".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir3".to_string(),
|
|
directory_etag: "etag3".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
];
|
|
|
|
let _ = db.bulk_create_or_update_webdav_directories(&directories).await.unwrap();
|
|
|
|
// Delete directories not in this list (should delete dir2 and dir3)
|
|
let existing_paths = vec!["/test/dir1".to_string()];
|
|
let deleted_count = db.delete_missing_webdav_directories(user_id, &existing_paths).await.unwrap();
|
|
|
|
assert_eq!(deleted_count, 2);
|
|
|
|
// Verify only dir1 remains
|
|
let remaining_directories = db.list_webdav_directories(user_id).await.unwrap();
|
|
assert_eq!(remaining_directories.len(), 1);
|
|
assert_eq!(remaining_directories[0].directory_path, "/test/dir1");
|
|
|
|
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_atomic_rollback_on_failure() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
// Ensure cleanup happens even if test fails
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
|
|
// Create a test user first
|
|
let create_user = CreateUser {
|
|
username: "testuser4".to_string(),
|
|
email: "test4@example.com".to_string(),
|
|
password: "password123".to_string(),
|
|
role: Some(UserRole::User),
|
|
};
|
|
let user = db.create_user(create_user).await
|
|
.expect("Failed to create test user");
|
|
let user_id = user.id;
|
|
|
|
// Create a directory that would conflict
|
|
let initial_dir = CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir1".to_string(),
|
|
directory_etag: "etag1".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
};
|
|
|
|
let _ = db.create_or_update_webdav_directory(&initial_dir).await.unwrap();
|
|
|
|
// Try to bulk insert with one invalid entry that should cause rollback
|
|
let directories_with_invalid = vec![
|
|
CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/dir2".to_string(),
|
|
directory_etag: "etag2".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
CreateWebDAVDirectory {
|
|
user_id: Uuid::nil(), // Invalid user ID should cause failure
|
|
directory_path: "/test/dir3".to_string(),
|
|
directory_etag: "etag3".to_string(),
|
|
file_count: 0,
|
|
total_size_bytes: 0,
|
|
},
|
|
];
|
|
|
|
// This should fail and rollback
|
|
let result = db.bulk_create_or_update_webdav_directories(&directories_with_invalid).await;
|
|
assert!(result.is_err());
|
|
|
|
// Verify that no partial changes were made (only original dir1 should exist)
|
|
let directories = db.list_webdav_directories(user_id).await.unwrap();
|
|
assert_eq!(directories.len(), 1);
|
|
assert_eq!(directories[0].directory_path, "/test/dir1");
|
|
|
|
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_directory_updates() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
// Ensure cleanup happens even if test fails
|
|
let result: Result<()> = async {
|
|
let db = Arc::new(ctx.state.db.clone());
|
|
|
|
// Create a test user first
|
|
let create_user = CreateUser {
|
|
username: "testuser5".to_string(),
|
|
email: "test5@example.com".to_string(),
|
|
password: "password123".to_string(),
|
|
role: Some(UserRole::User),
|
|
};
|
|
let user = db.create_user(create_user).await
|
|
.expect("Failed to create test user");
|
|
let user_id = user.id;
|
|
|
|
// Spawn multiple concurrent tasks that try to update the same directory
|
|
let mut handles = vec![];
|
|
|
|
for i in 0..10 {
|
|
let db_clone = db.clone();
|
|
let handle = tokio::spawn(async move {
|
|
let directory = CreateWebDAVDirectory {
|
|
user_id,
|
|
directory_path: "/test/concurrent".to_string(),
|
|
directory_etag: format!("etag_{}", i),
|
|
file_count: i as i64,
|
|
total_size_bytes: (i * 1024) as i64,
|
|
};
|
|
|
|
db_clone.create_or_update_webdav_directory(&directory).await
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all tasks to complete
|
|
let results: Vec<_> = join_all(handles).await;
|
|
|
|
// All operations should succeed (last writer wins)
|
|
for result in results {
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap().is_ok());
|
|
}
|
|
|
|
// Verify final state
|
|
let directories = db.list_webdav_directories(user_id).await.unwrap();
|
|
assert_eq!(directories.len(), 1);
|
|
assert_eq!(directories[0].directory_path, "/test/concurrent");
|
|
// ETag should be from one of the concurrent updates
|
|
assert!(directories[0].directory_etag.starts_with("etag_"));
|
|
|
|
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();
|
|
} |