mirror of
https://github.com/readur/readur.git
synced 2026-01-04 05:20:11 -06:00
818 lines
30 KiB
Rust
818 lines
30 KiB
Rust
/// Integration tests for source error database functions
|
|
/// Tests the complete lifecycle of source scan failure tracking including:
|
|
/// - Recording failures with proper enum type casting
|
|
/// - Querying failures with various filters
|
|
/// - Resolving and resetting failures
|
|
/// - Statistics aggregation
|
|
///
|
|
/// These tests specifically ensure that PostgreSQL enum types are handled correctly
|
|
/// to prevent runtime SQL errors like "function resolve_source_scan_failure does not exist"
|
|
/// or "operator does not exist: source_error_source_type = text"
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use anyhow::Result;
|
|
use readur::test_utils::TestContext;
|
|
use readur::models::{
|
|
CreateSourceScanFailure, ErrorSourceType, SourceErrorType,
|
|
SourceErrorSeverity, ListFailuresQuery, SourceScanFailure
|
|
};
|
|
use uuid::Uuid;
|
|
use serde_json::json;
|
|
|
|
#[tokio::test]
|
|
async fn test_record_source_scan_failure_with_enum_types() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_source_error_user_{}", unique_suffix);
|
|
let email = format!("test_source_error_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
// Test recording a source scan failure with all enum types
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: ErrorSourceType::WebDAV,
|
|
source_id: None, // No source_id to avoid foreign key constraint
|
|
resource_path: "/test/path/to/file.pdf".to_string(),
|
|
error_type: SourceErrorType::PermissionDenied,
|
|
error_message: "Access denied to resource".to_string(),
|
|
error_code: Some("403".to_string()),
|
|
http_status_code: Some(403),
|
|
response_time_ms: Some(250),
|
|
response_size_bytes: Some(1024),
|
|
resource_size_bytes: Some(2048),
|
|
diagnostic_data: Some(json!({
|
|
"headers": {
|
|
"content-type": "text/html"
|
|
}
|
|
})),
|
|
};
|
|
|
|
// This should succeed with proper enum type casting
|
|
let failure_id = db.record_source_scan_failure(&failure).await?;
|
|
assert!(!failure_id.is_nil());
|
|
|
|
// Test with different enum values
|
|
let failure2 = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: ErrorSourceType::S3,
|
|
source_id: None,
|
|
resource_path: "bucket/key/object.txt".to_string(),
|
|
error_type: SourceErrorType::Timeout,
|
|
error_message: "Request timed out".to_string(),
|
|
error_code: None,
|
|
http_status_code: None,
|
|
response_time_ms: Some(30000),
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
|
|
let failure_id2 = db.record_source_scan_failure(&failure2).await?;
|
|
assert!(!failure_id2.is_nil());
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_list_source_scan_failures_with_enum_filters() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_list_failures_user_{}", unique_suffix);
|
|
let email = format!("test_list_failures_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
// Create multiple failures with different enum values
|
|
let failures_data = vec![
|
|
(ErrorSourceType::WebDAV, SourceErrorType::PermissionDenied),
|
|
(ErrorSourceType::WebDAV, SourceErrorType::Timeout),
|
|
(ErrorSourceType::S3, SourceErrorType::NetworkError),
|
|
(ErrorSourceType::Local, SourceErrorType::PathTooLong),
|
|
];
|
|
|
|
for (source_type, error_type) in failures_data {
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: source_type.clone(),
|
|
source_id: None,
|
|
resource_path: format!("/test/{}/{}", source_type, error_type),
|
|
error_type: error_type.clone(),
|
|
error_message: format!("Test error: {}", error_type),
|
|
error_code: None,
|
|
http_status_code: None,
|
|
response_time_ms: None,
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
db.record_source_scan_failure(&failure).await?;
|
|
}
|
|
|
|
// Test querying with source_type filter (tests enum comparison)
|
|
let query = ListFailuresQuery {
|
|
source_type: Some(ErrorSourceType::WebDAV),
|
|
source_id: None,
|
|
error_type: None,
|
|
severity: None,
|
|
include_resolved: Some(false),
|
|
include_excluded: Some(false),
|
|
ready_for_retry: None,
|
|
limit: None,
|
|
offset: None,
|
|
};
|
|
|
|
let webdav_failures = db.list_source_scan_failures(user_id, &query).await?;
|
|
assert_eq!(webdav_failures.len(), 2);
|
|
|
|
// Test querying with error_type filter
|
|
let query = ListFailuresQuery {
|
|
source_type: None,
|
|
source_id: None,
|
|
error_type: Some(SourceErrorType::Timeout),
|
|
severity: None,
|
|
include_resolved: Some(false),
|
|
include_excluded: Some(false),
|
|
ready_for_retry: None,
|
|
limit: None,
|
|
offset: None,
|
|
};
|
|
|
|
let timeout_failures = db.list_source_scan_failures(user_id, &query).await?;
|
|
assert_eq!(timeout_failures.len(), 1);
|
|
|
|
// Test querying with multiple filters
|
|
let query = ListFailuresQuery {
|
|
source_type: Some(ErrorSourceType::S3),
|
|
source_id: None,
|
|
error_type: Some(SourceErrorType::NetworkError),
|
|
severity: None,
|
|
include_resolved: Some(false),
|
|
include_excluded: Some(false),
|
|
ready_for_retry: None,
|
|
limit: None,
|
|
offset: None,
|
|
};
|
|
|
|
let s3_network_failures = db.list_source_scan_failures(user_id, &query).await?;
|
|
assert_eq!(s3_network_failures.len(), 1);
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_resolve_source_scan_failure_with_enum_casting() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_resolve_user_{}", unique_suffix);
|
|
let email = format!("test_resolve_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
let source_id = None; // No source_id to avoid foreign key constraint
|
|
let resource_path = "/test/resolve/file.pdf";
|
|
|
|
// Create a failure
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: ErrorSourceType::WebDAV,
|
|
source_id,
|
|
resource_path: resource_path.to_string(),
|
|
error_type: SourceErrorType::ServerError,
|
|
error_message: "Internal server error".to_string(),
|
|
error_code: Some("500".to_string()),
|
|
http_status_code: Some(500),
|
|
response_time_ms: None,
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
|
|
db.record_source_scan_failure(&failure).await?;
|
|
|
|
// Test resolving the failure (this tests the function with enum type casting)
|
|
let resolved = db.resolve_source_scan_failure(
|
|
user_id,
|
|
ErrorSourceType::WebDAV,
|
|
source_id,
|
|
resource_path,
|
|
"manual"
|
|
).await?;
|
|
|
|
assert!(resolved);
|
|
|
|
// Verify it's marked as resolved
|
|
let query = ListFailuresQuery {
|
|
source_type: Some(ErrorSourceType::WebDAV),
|
|
source_id,
|
|
error_type: None,
|
|
severity: None,
|
|
include_resolved: Some(true), // Include resolved
|
|
include_excluded: Some(false),
|
|
ready_for_retry: None,
|
|
limit: None,
|
|
offset: None,
|
|
};
|
|
|
|
let failures = db.list_source_scan_failures(user_id, &query).await?;
|
|
assert_eq!(failures.len(), 1);
|
|
assert!(failures[0].resolved);
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_reset_source_scan_failure_with_enum_casting() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_reset_user_{}", unique_suffix);
|
|
let email = format!("test_reset_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
let resource_path = "/test/reset/file.pdf";
|
|
|
|
// Create a failure
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: ErrorSourceType::S3,
|
|
source_id: None,
|
|
resource_path: resource_path.to_string(),
|
|
error_type: SourceErrorType::RateLimited,
|
|
error_message: "Rate limit exceeded".to_string(),
|
|
error_code: Some("429".to_string()),
|
|
http_status_code: Some(429),
|
|
response_time_ms: None,
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
|
|
db.record_source_scan_failure(&failure).await?;
|
|
|
|
// Test resetting the failure
|
|
let reset = db.reset_source_scan_failure(
|
|
user_id,
|
|
ErrorSourceType::S3,
|
|
None,
|
|
resource_path
|
|
).await?;
|
|
|
|
assert!(reset);
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_is_source_known_failure_with_enum_comparison() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_known_failure_user_{}", unique_suffix);
|
|
let email = format!("test_known_failure_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
let resource_path = "/test/known/file.pdf";
|
|
|
|
// Create a critical failure that should be considered "known"
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: ErrorSourceType::Local,
|
|
source_id: None,
|
|
resource_path: resource_path.to_string(),
|
|
error_type: SourceErrorType::PathTooLong,
|
|
error_message: "Path exceeds maximum length".to_string(),
|
|
error_code: None,
|
|
http_status_code: None,
|
|
response_time_ms: None,
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
|
|
db.record_source_scan_failure(&failure).await?;
|
|
|
|
// Test checking if it's a known failure
|
|
let is_known = db.is_source_known_failure(
|
|
user_id,
|
|
ErrorSourceType::Local,
|
|
None,
|
|
resource_path
|
|
).await?;
|
|
|
|
// PathTooLong is a critical error, so it should be considered known
|
|
assert!(is_known);
|
|
|
|
// Test with different source type - should not be known
|
|
let is_known_different = db.is_source_known_failure(
|
|
user_id,
|
|
ErrorSourceType::S3, // Different source type
|
|
None,
|
|
resource_path
|
|
).await?;
|
|
|
|
assert!(!is_known_different);
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_exclude_source_from_scan_with_enum_casting() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_exclude_user_{}", unique_suffix);
|
|
let email = format!("test_exclude_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
let resource_path = "/test/exclude/file.pdf";
|
|
|
|
// Create a failure
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: ErrorSourceType::WebDAV,
|
|
source_id: None,
|
|
resource_path: resource_path.to_string(),
|
|
error_type: SourceErrorType::InvalidCharacters,
|
|
error_message: "Invalid characters in filename".to_string(),
|
|
error_code: None,
|
|
http_status_code: None,
|
|
response_time_ms: None,
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
|
|
db.record_source_scan_failure(&failure).await?;
|
|
|
|
// Test excluding the source from scan
|
|
let excluded = db.exclude_source_from_scan(
|
|
user_id,
|
|
ErrorSourceType::WebDAV,
|
|
None,
|
|
resource_path,
|
|
Some("User requested exclusion")
|
|
).await?;
|
|
|
|
assert!(excluded);
|
|
|
|
// Verify it's marked as excluded
|
|
let query = ListFailuresQuery {
|
|
source_type: Some(ErrorSourceType::WebDAV),
|
|
source_id: None,
|
|
error_type: None,
|
|
severity: None,
|
|
include_resolved: Some(false),
|
|
include_excluded: Some(true), // Include excluded
|
|
ready_for_retry: None,
|
|
limit: None,
|
|
offset: None,
|
|
};
|
|
|
|
let failures = db.list_source_scan_failures(user_id, &query).await?;
|
|
assert_eq!(failures.len(), 1);
|
|
assert!(failures[0].user_excluded);
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_source_retry_candidates_with_enum_filter() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_retry_candidates_user_{}", unique_suffix);
|
|
let email = format!("test_retry_candidates_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
// Create failures with different source types
|
|
for i in 0..3 {
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: if i == 0 { ErrorSourceType::WebDAV } else { ErrorSourceType::S3 },
|
|
source_id: None,
|
|
resource_path: format!("/test/retry/{}.pdf", i),
|
|
error_type: SourceErrorType::NetworkError,
|
|
error_message: "Network error".to_string(),
|
|
error_code: None,
|
|
http_status_code: None,
|
|
response_time_ms: None,
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
db.record_source_scan_failure(&failure).await?;
|
|
|
|
// Manually set next_retry_at to NOW() for testing
|
|
sqlx::query(
|
|
"UPDATE source_scan_failures
|
|
SET next_retry_at = NOW()
|
|
WHERE user_id = $1 AND resource_path = $2"
|
|
)
|
|
.bind(user_id)
|
|
.bind(format!("/test/retry/{}.pdf", i))
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
}
|
|
|
|
// Test getting retry candidates filtered by source type
|
|
let webdav_candidates = db.get_source_retry_candidates(
|
|
user_id,
|
|
Some(ErrorSourceType::WebDAV),
|
|
10
|
|
).await?;
|
|
|
|
assert_eq!(webdav_candidates.len(), 1);
|
|
assert_eq!(webdav_candidates[0].source_type, ErrorSourceType::WebDAV);
|
|
|
|
// Test getting all retry candidates
|
|
let all_candidates = db.get_source_retry_candidates(
|
|
user_id,
|
|
None,
|
|
10
|
|
).await?;
|
|
|
|
assert_eq!(all_candidates.len(), 3);
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_source_scan_failure_stats_with_enum_filter() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_stats_user_{}", unique_suffix);
|
|
let email = format!("test_stats_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
// Create multiple failures with different characteristics
|
|
let failures_to_create = vec![
|
|
(ErrorSourceType::WebDAV, SourceErrorType::PermissionDenied, false),
|
|
(ErrorSourceType::WebDAV, SourceErrorType::Timeout, false),
|
|
(ErrorSourceType::S3, SourceErrorType::NetworkError, false),
|
|
(ErrorSourceType::S3, SourceErrorType::ServerError, true), // This one will be resolved
|
|
(ErrorSourceType::Local, SourceErrorType::PathTooLong, false),
|
|
];
|
|
|
|
for (i, (source_type, error_type, should_resolve)) in failures_to_create.iter().enumerate() {
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: source_type.clone(),
|
|
source_id: None,
|
|
resource_path: format!("/test/stats/{}.pdf", i),
|
|
error_type: error_type.clone(),
|
|
error_message: format!("Error: {}", error_type),
|
|
error_code: None,
|
|
http_status_code: None,
|
|
response_time_ms: None,
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
db.record_source_scan_failure(&failure).await?;
|
|
|
|
if *should_resolve {
|
|
db.resolve_source_scan_failure(
|
|
user_id,
|
|
source_type.clone(),
|
|
None,
|
|
&format!("/test/stats/{}.pdf", i),
|
|
"test"
|
|
).await?;
|
|
}
|
|
}
|
|
|
|
// Test getting stats for all source types
|
|
let all_stats = db.get_source_scan_failure_stats(user_id, None).await?;
|
|
assert_eq!(all_stats.active_failures, 4); // 5 created, 1 resolved
|
|
assert_eq!(all_stats.resolved_failures, 1);
|
|
assert!(all_stats.by_source_type.contains_key("webdav"));
|
|
assert!(all_stats.by_source_type.contains_key("s3"));
|
|
assert!(all_stats.by_source_type.contains_key("local"));
|
|
|
|
// Test getting stats filtered by source type
|
|
let webdav_stats = db.get_source_scan_failure_stats(
|
|
user_id,
|
|
Some(ErrorSourceType::WebDAV)
|
|
).await?;
|
|
assert_eq!(webdav_stats.active_failures, 2); // 2 WebDAV failures, none resolved
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_all_enum_values_are_supported() {
|
|
let ctx = TestContext::new().await;
|
|
|
|
let result: Result<()> = async {
|
|
let db = &ctx.state.db;
|
|
let user_id = Uuid::new_v4();
|
|
|
|
// Create test user
|
|
let unique_suffix = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let username = format!("test_all_enums_user_{}", unique_suffix);
|
|
let email = format!("test_all_enums_{}@example.com", unique_suffix);
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (id, username, email, password_hash, role)
|
|
VALUES ($1, $2, $3, $4, $5)"
|
|
)
|
|
.bind(user_id)
|
|
.bind(&username)
|
|
.bind(&email)
|
|
.bind("hash")
|
|
.bind("user")
|
|
.execute(db.get_pool())
|
|
.await?;
|
|
|
|
// Test all ErrorSourceType values
|
|
let source_types = vec![
|
|
ErrorSourceType::WebDAV,
|
|
ErrorSourceType::S3,
|
|
ErrorSourceType::Local,
|
|
];
|
|
|
|
// Test all SourceErrorType values
|
|
let error_types = vec![
|
|
SourceErrorType::Timeout,
|
|
SourceErrorType::PermissionDenied,
|
|
SourceErrorType::NetworkError,
|
|
SourceErrorType::ServerError,
|
|
SourceErrorType::PathTooLong,
|
|
SourceErrorType::InvalidCharacters,
|
|
SourceErrorType::TooManyItems,
|
|
SourceErrorType::DepthLimit,
|
|
SourceErrorType::SizeLimit,
|
|
SourceErrorType::XmlParseError,
|
|
SourceErrorType::JsonParseError,
|
|
SourceErrorType::QuotaExceeded,
|
|
SourceErrorType::RateLimited,
|
|
SourceErrorType::NotFound,
|
|
SourceErrorType::Conflict,
|
|
SourceErrorType::UnsupportedOperation,
|
|
SourceErrorType::Unknown,
|
|
];
|
|
|
|
// Create a failure for each combination to ensure all enum values work
|
|
for source_type in &source_types {
|
|
for (i, error_type) in error_types.iter().enumerate() {
|
|
let failure = CreateSourceScanFailure {
|
|
user_id,
|
|
source_type: source_type.clone(),
|
|
source_id: None,
|
|
resource_path: format!("/test/enum/{}/{}.pdf", source_type, i),
|
|
error_type: error_type.clone(),
|
|
error_message: format!("Testing {} with {}", source_type, error_type),
|
|
error_code: None,
|
|
http_status_code: None,
|
|
response_time_ms: None,
|
|
response_size_bytes: None,
|
|
resource_size_bytes: None,
|
|
diagnostic_data: None,
|
|
};
|
|
|
|
// This should not panic or return an error
|
|
let result = db.record_source_scan_failure(&failure).await;
|
|
assert!(result.is_ok(), "Failed to record failure for {} / {}: {:?}",
|
|
source_type, error_type, result);
|
|
}
|
|
}
|
|
|
|
// Verify we can query all of them
|
|
let all_failures = db.list_source_scan_failures(user_id, &ListFailuresQuery {
|
|
source_type: None,
|
|
source_id: None,
|
|
error_type: None,
|
|
severity: None,
|
|
include_resolved: Some(false),
|
|
include_excluded: Some(false),
|
|
ready_for_retry: None,
|
|
limit: None,
|
|
offset: None,
|
|
}).await?;
|
|
|
|
assert_eq!(all_failures.len(), source_types.len() * error_types.len());
|
|
|
|
Ok(())
|
|
}.await;
|
|
|
|
if let Err(e) = ctx.cleanup_and_close().await {
|
|
eprintln!("Warning: Test cleanup failed: {}", e);
|
|
}
|
|
|
|
result.unwrap();
|
|
}
|
|
} |