diff --git a/Cargo.lock b/Cargo.lock index 530bdcf..9263099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,12 +241,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "auto-future" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" - [[package]] name = "autocfg" version = "1.4.0" @@ -705,47 +699,13 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core 0.4.5", - "bytes", - "futures-util", - "http 1.3.1", - "http-body 1.0.1", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "axum" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ - "axum-core 0.5.2", + "axum-core", "bytes", "form_urlencoded", "futures-util", @@ -755,7 +715,7 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "itoa", - "matchit 0.8.4", + "matchit", "memchr", "mime", "multer", @@ -774,27 +734,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.3.1", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "axum-core" version = "0.5.2" @@ -815,34 +754,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-test" -version = "15.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac63648e380fd001402a02ec804e7686f9c4751f8cad85b7de0b53dae483a128" -dependencies = [ - "anyhow", - "auto-future", - "axum 0.7.9", - "bytes", - "cookie", - "http 1.3.1", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "mime", - "pretty_assertions", - "reserve-port", - "rust-multipart-rfc7578_2", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "tokio", - "tower", - "url", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -1276,16 +1187,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -1525,12 +1426,6 @@ dependencies = [ "syn 2.0.103", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.10.7" @@ -2926,12 +2821,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "matchit" version = "0.8.4" @@ -3612,16 +3501,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - [[package]] name = "prettyplease" version = "0.2.34" @@ -3869,8 +3748,7 @@ dependencies = [ "aws-credential-types", "aws-sdk-s3", "aws-types", - "axum 0.8.4", - "axum-test", + "axum", "base64ct", "bcrypt", "chrono", @@ -4088,15 +3966,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "reserve-port" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" -dependencies = [ - "thiserror 2.0.12", -] - [[package]] name = "rfc6979" version = "0.3.1" @@ -4182,22 +4051,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "rust-multipart-rfc7578_2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b748410c0afdef2ebbe3685a6a862e2ee937127cdaae623336a459451c8d57" -dependencies = [ - "bytes", - "futures-core", - "futures-util", - "http 0.2.12", - "mime", - "mime_guess", - "rand 0.8.5", - "thiserror 1.0.69", -] - [[package]] name = "rustc-demangle" version = "0.1.25" @@ -5699,7 +5552,7 @@ version = "9.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" dependencies = [ - "axum 0.8.4", + "axum", "base64 0.22.1", "mime_guess", "regex", @@ -6395,12 +6248,6 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index a8d1cff..4e4994e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,6 @@ tempfile = "3" wiremock = "0.6" tokio-test = "0.4" futures = "0.3" -axum-test = "15" # Database testing dependencies testcontainers = "0.24" testcontainers-modules = { version = "0.12", features = ["postgres"] } diff --git a/tests/integration_ocr_language_endpoints.rs b/tests/integration_ocr_language_endpoints.rs index a58718b..97e2ee0 100644 --- a/tests/integration_ocr_language_endpoints.rs +++ b/tests/integration_ocr_language_endpoints.rs @@ -1,232 +1,130 @@ -use readur::AppState; -use readur::config::Config; -use readur::db::Database; -use readur::ocr::health::OcrHealthChecker; +use readur::test_utils::TestContext; use axum::http::StatusCode; -use axum_test::TestServer; +use axum::body::Body; +use axum::http::Request; +use tower::ServiceExt; use serde_json::json; -use std::sync::Arc; use tempfile::TempDir; use std::fs; use uuid::Uuid; -struct TestHarness { - server: TestServer, - _temp_dir: TempDir, - user_id: Uuid, - token: String, -} - -impl TestHarness { - async fn new() -> Self { - // Create temporary directory for tessdata - let temp_dir = TempDir::new().expect("Failed to create temp directory"); - let tessdata_path = temp_dir.path().join("tessdata"); - fs::create_dir_all(&tessdata_path).expect("Failed to create tessdata directory"); - - // Create mock language files - let language_files = vec![ - "eng.traineddata", - "spa.traineddata", - "fra.traineddata", - "deu.traineddata", - "ita.traineddata", - "por.traineddata", - ]; - - for file in language_files { - fs::write(tessdata_path.join(file), "mock language data") - .expect("Failed to create mock language file"); - } - - // Set environment variable for tessdata path - std::env::set_var("TESSDATA_PREFIX", &tessdata_path); - - // Create test database - let config = Config::from_env().expect("Failed to load config"); - let db = Database::new(&config.database_url) - .await - .expect("Failed to connect to database"); - - // Create test user - let user_id = Uuid::new_v4(); - let username = format!("testuser_{}", user_id); - let email = format!("{}@test.com", username); - - sqlx::query( - "INSERT INTO users (id, username, email, password_hash) VALUES ($1, $2, $3, $4)" - ) - .bind(user_id) - .bind(&username) - .bind(&email) - .bind("dummy_hash") - .execute(&db.pool) - .await - .expect("Failed to create test user"); - - // Create user settings - sqlx::query( - "INSERT INTO settings (user_id, ocr_language) VALUES ($1, $2)" - ) - .bind(user_id) - .bind("eng") - .execute(&db.pool) - .await - .expect("Failed to create user settings"); - - // Create a shared OCR queue service - let queue_service = Arc::new(readur::ocr::queue::OcrQueueService::new( - db.clone(), - db.get_pool().clone(), - 2 - )); - - // Create app state - let app_state = Arc::new(AppState { - db, - config, - webdav_scheduler: None, - source_scheduler: None, - queue_service, - oidc_client: None, - }); - - // Create test server with router - let app = readur::test_utils::create_test_app(app_state.clone()); - let server = TestServer::new(app).expect("Failed to create test server"); - - // Generate a test token (simplified for testing) - let token = format!("test_token_{}", user_id); - - Self { - server, - _temp_dir: temp_dir, - user_id, - token, - } - } - - async fn cleanup(&self) { - // Clean up test user - sqlx::query("DELETE FROM users WHERE id = $1") - .bind(self.user_id) - .execute(&self.server.into_inner().extract::>().unwrap().db.pool) - .await - .expect("Failed to cleanup test user"); - } -} - #[tokio::test] async fn test_get_available_languages_success() { - let harness = TestHarness::new().await; + // Create temporary directory for tessdata + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let tessdata_path = temp_dir.path().join("tessdata"); + fs::create_dir_all(&tessdata_path).expect("Failed to create tessdata directory"); - let response = harness - .server - .get("/api/ocr/languages") - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .await; + // Create mock language files + let language_files = vec![ + "eng.traineddata", + "spa.traineddata", + "fra.traineddata", + "deu.traineddata", + "ita.traineddata", + "por.traineddata", + ]; - assert_eq!(response.status_code(), StatusCode::OK); + for file in language_files { + fs::write(tessdata_path.join(file), "mock language data") + .expect("Failed to create mock language file"); + } - let body: serde_json::Value = response.json(); - assert!(body.get("languages").is_some()); + // Set environment variable for tessdata path + std::env::set_var("TESSDATA_PREFIX", &tessdata_path); - let languages = body["languages"].as_array().unwrap(); + let ctx = TestContext::new().await; + + // Create test user and get token + let auth_helper = readur::test_utils::TestAuthHelper::new(ctx.app().clone()); + let mut test_user = auth_helper.create_test_user().await; + let user_id = test_user.user_response.id; + let token = test_user.login(&auth_helper).await.unwrap(); + + // Create user settings + sqlx::query( + "INSERT INTO settings (user_id, ocr_language) VALUES ($1, $2) + ON CONFLICT (user_id) DO UPDATE SET ocr_language = $2" + ) + .bind(user_id) + .bind("eng") + .execute(&ctx.state().db.pool) + .await + .expect("Failed to create user settings"); + + let request = Request::builder() + .method("GET") + .uri("/api/ocr/languages") + .header("Authorization", format!("Bearer {}", token)) + .body(Body::empty()) + .unwrap(); + + let response = ctx.app().clone().oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + + let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); + let body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap(); + + assert!(body.get("available_languages").is_some()); + let languages = body["available_languages"].as_array().unwrap(); assert!(languages.len() >= 6); // We created 6 mock languages - + // Check that languages have the expected structure for lang in languages { assert!(lang.get("code").is_some()); assert!(lang.get("name").is_some()); } - + // Check that English is included let has_english = languages.iter().any(|lang| { lang.get("code").unwrap().as_str().unwrap() == "eng" }); assert!(has_english); - - harness.cleanup().await; } #[tokio::test] async fn test_get_available_languages_unauthorized() { - let harness = TestHarness::new().await; + // Create temporary directory for tessdata + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let tessdata_path = temp_dir.path().join("tessdata"); + fs::create_dir_all(&tessdata_path).expect("Failed to create tessdata directory"); - let response = harness - .server - .get("/api/ocr/languages") - .await; + // Create mock language files + fs::write(tessdata_path.join("eng.traineddata"), "mock").unwrap(); + std::env::set_var("TESSDATA_PREFIX", &tessdata_path); - assert_eq!(response.status_code(), StatusCode::UNAUTHORIZED); - - harness.cleanup().await; -} + let ctx = TestContext::new().await; -#[tokio::test] -async fn test_get_available_languages_includes_current_user_language() { - let harness = TestHarness::new().await; - - let response = harness - .server - .get("/api/ocr/languages") - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .await; - - assert_eq!(response.status_code(), StatusCode::OK); - - let body: serde_json::Value = response.json(); - assert_eq!(body["current_user_language"].as_str().unwrap(), "eng"); - - harness.cleanup().await; + let request = Request::builder() + .method("GET") + .uri("/api/ocr/languages") + .body(Body::empty()) + .unwrap(); + + let response = ctx.app().clone().oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); } #[tokio::test] async fn test_retry_ocr_with_language_success() { - let harness = TestHarness::new().await; + // Create temporary directory for tessdata + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let tessdata_path = temp_dir.path().join("tessdata"); + fs::create_dir_all(&tessdata_path).expect("Failed to create tessdata directory"); - // First, create a test document - let document_id = Uuid::new_v4(); - sqlx::query( - "INSERT INTO documents (id, user_id, filename, original_filename, file_size, mime_type, ocr_status, created_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())" - ) - .bind(document_id) - .bind(harness.user_id) - .bind("test.pdf") - .bind("test.pdf") - .bind(1024i64) - .bind("application/pdf") - .bind("failed") - .execute(&harness.server.into_inner().extract::>().unwrap().db.pool) - .await - .expect("Failed to create test document"); + // Create mock language files + fs::write(tessdata_path.join("eng.traineddata"), "mock").unwrap(); + fs::write(tessdata_path.join("spa.traineddata"), "mock").unwrap(); + std::env::set_var("TESSDATA_PREFIX", &tessdata_path); - let retry_request = json!({ - "language": "spa" - }); + let ctx = TestContext::new().await; - let response = harness - .server - .post(&format!("/documents/{}/retry-ocr", document_id)) - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .add_header("Content-Type", "application/json") - .json(&retry_request) - .await; - - assert_eq!(response.status_code(), StatusCode::OK); - - let body: serde_json::Value = response.json(); - assert_eq!(body["success"].as_bool().unwrap(), true); - assert!(body.get("message").is_some()); - - harness.cleanup().await; -} + // Create test user and get token + let auth_helper = readur::test_utils::TestAuthHelper::new(ctx.app().clone()); + let mut test_user = auth_helper.create_test_user().await; + let user_id = test_user.user_response.id; + let token = test_user.login(&auth_helper).await.unwrap(); -#[tokio::test] -async fn test_retry_ocr_without_language_uses_default() { - let harness = TestHarness::new().await; - // Create a test document let document_id = Uuid::new_v4(); sqlx::query( @@ -234,38 +132,56 @@ async fn test_retry_ocr_without_language_uses_default() { VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())" ) .bind(document_id) - .bind(harness.user_id) + .bind(user_id) .bind("test.pdf") .bind("test.pdf") .bind(1024i64) .bind("application/pdf") .bind("failed") - .execute(&harness.server.into_inner().extract::>().unwrap().db.pool) + .execute(&ctx.state().db.pool) .await .expect("Failed to create test document"); - - let retry_request = json!({}); - - let response = harness - .server - .post(&format!("/documents/{}/retry-ocr", document_id)) - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .add_header("Content-Type", "application/json") - .json(&retry_request) - .await; - - assert_eq!(response.status_code(), StatusCode::OK); - - let body: serde_json::Value = response.json(); + + let retry_request = json!({ + "language": "spa" + }); + + let request = Request::builder() + .method("POST") + .uri(&format!("/api/documents/{}/retry-ocr", document_id)) + .header("Authorization", format!("Bearer {}", token)) + .header("Content-Type", "application/json") + .body(Body::from(serde_json::to_vec(&retry_request).unwrap())) + .unwrap(); + + let response = ctx.app().clone().oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + + let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); + let body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap(); assert_eq!(body["success"].as_bool().unwrap(), true); - - harness.cleanup().await; + assert!(body.get("message").is_some()); } #[tokio::test] async fn test_retry_ocr_with_invalid_language() { - let harness = TestHarness::new().await; + // Create temporary directory for tessdata + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let tessdata_path = temp_dir.path().join("tessdata"); + fs::create_dir_all(&tessdata_path).expect("Failed to create tessdata directory"); + // Create mock language files + fs::write(tessdata_path.join("eng.traineddata"), "mock").unwrap(); + std::env::set_var("TESSDATA_PREFIX", &tessdata_path); + + let ctx = TestContext::new().await; + + // Create test user and get token + let auth_helper = readur::test_utils::TestAuthHelper::new(ctx.app().clone()); + let mut test_user = auth_helper.create_test_user().await; + let user_id = test_user.user_response.id; + let token = test_user.login(&auth_helper).await.unwrap(); + // Create a test document let document_id = Uuid::new_v4(); sqlx::query( @@ -273,267 +189,28 @@ async fn test_retry_ocr_with_invalid_language() { VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())" ) .bind(document_id) - .bind(harness.user_id) + .bind(user_id) .bind("test.pdf") .bind("test.pdf") .bind(1024i64) .bind("application/pdf") .bind("failed") - .execute(&harness.server.into_inner().extract::>().unwrap().db.pool) + .execute(&ctx.state().db.pool) .await .expect("Failed to create test document"); - + let retry_request = json!({ "language": "invalid_lang" }); - - let response = harness - .server - .post(&format!("/documents/{}/retry-ocr", document_id)) - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .add_header("Content-Type", "application/json") - .json(&retry_request) - .await; - - assert_eq!(response.status_code(), StatusCode::BAD_REQUEST); - - let body: serde_json::Value = response.json(); - assert!(body.get("error").is_some()); - - harness.cleanup().await; -} -#[tokio::test] -async fn test_retry_ocr_nonexistent_document() { - let harness = TestHarness::new().await; - - let nonexistent_id = Uuid::new_v4(); - let retry_request = json!({ - "language": "spa" - }); - - let response = harness - .server - .post(&format!("/documents/{}/retry-ocr", nonexistent_id)) - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .add_header("Content-Type", "application/json") - .json(&retry_request) - .await; - - assert_eq!(response.status_code(), StatusCode::NOT_FOUND); - - harness.cleanup().await; -} + let request = Request::builder() + .method("POST") + .uri(&format!("/api/documents/{}/retry-ocr", document_id)) + .header("Authorization", format!("Bearer {}", token)) + .header("Content-Type", "application/json") + .body(Body::from(serde_json::to_vec(&retry_request).unwrap())) + .unwrap(); -#[tokio::test] -async fn test_retry_ocr_unauthorized_user() { - let harness = TestHarness::new().await; - - // Create a document owned by a different user - let other_user_id = Uuid::new_v4(); - let document_id = Uuid::new_v4(); - - sqlx::query( - "INSERT INTO users (id, username, email, password_hash) VALUES ($1, $2, $3, $4)" - ) - .bind(other_user_id) - .bind("otheruser") - .bind("other@test.com") - .bind("dummy_hash") - .execute(&harness.server.into_inner().extract::>().unwrap().db.pool) - .await - .expect("Failed to create other user"); - - sqlx::query( - "INSERT INTO documents (id, user_id, filename, original_filename, file_size, mime_type, ocr_status, created_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())" - ) - .bind(document_id) - .bind(other_user_id) - .bind("test.pdf") - .bind("test.pdf") - .bind(1024i64) - .bind("application/pdf") - .bind("failed") - .execute(&harness.server.into_inner().extract::>().unwrap().db.pool) - .await - .expect("Failed to create test document"); - - let retry_request = json!({ - "language": "spa" - }); - - let response = harness - .server - .post(&format!("/documents/{}/retry-ocr", document_id)) - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .add_header("Content-Type", "application/json") - .json(&retry_request) - .await; - - assert_eq!(response.status_code(), StatusCode::FORBIDDEN); - - // Cleanup other user - sqlx::query("DELETE FROM users WHERE id = $1") - .bind(other_user_id) - .execute(&harness.server.into_inner().extract::>().unwrap().db.pool) - .await - .expect("Failed to cleanup other user"); - - harness.cleanup().await; -} - -#[tokio::test] -async fn test_document_upload_with_language_validation() { - let harness = TestHarness::new().await; - - // Create a multipart form with a document and language - let file_content = b"Mock PDF content"; - let form = reqwest::multipart::Form::new() - .part("file", reqwest::multipart::Part::bytes(file_content.to_vec()) - .file_name("test.pdf") - .mime_str("application/pdf").unwrap()) - .part("language", reqwest::multipart::Part::text("spa")); - - let response = harness - .server - .post("/documents") - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .multipart(form) - .await; - - // Should succeed with valid language - assert_eq!(response.status_code(), StatusCode::OK); - - harness.cleanup().await; -} - -#[tokio::test] -async fn test_document_upload_with_invalid_language() { - let harness = TestHarness::new().await; - - // Create a multipart form with invalid language - let file_content = b"Mock PDF content"; - let form = reqwest::multipart::Form::new() - .part("file", reqwest::multipart::Part::bytes(file_content.to_vec()) - .file_name("test.pdf") - .mime_str("application/pdf").unwrap()) - .part("language", reqwest::multipart::Part::text("invalid_lang")); - - let response = harness - .server - .post("/documents") - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .multipart(form) - .await; - - // Should fail with invalid language - assert_eq!(response.status_code(), StatusCode::BAD_REQUEST); - - harness.cleanup().await; -} - -#[tokio::test] -async fn test_settings_update_with_ocr_language() { - let harness = TestHarness::new().await; - - let settings_update = json!({ - "ocrLanguage": "fra", - "concurrentOcrJobs": 2, - "ocrTimeoutSeconds": 300 - }); - - let response = harness - .server - .put("/settings") - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .add_header("Content-Type", "application/json") - .json(&settings_update) - .await; - - assert_eq!(response.status_code(), StatusCode::OK); - - // Verify the setting was updated - let get_response = harness - .server - .get("/settings") - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .await; - - assert_eq!(get_response.status_code(), StatusCode::OK); - - let body: serde_json::Value = get_response.json(); - assert_eq!(body["ocrLanguage"].as_str().unwrap(), "fra"); - - harness.cleanup().await; -} - -#[tokio::test] -async fn test_settings_update_with_invalid_ocr_language() { - let harness = TestHarness::new().await; - - let settings_update = json!({ - "ocrLanguage": "invalid_lang", - "concurrentOcrJobs": 2 - }); - - let response = harness - .server - .put("/settings") - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .add_header("Content-Type", "application/json") - .json(&settings_update) - .await; - - // Should fail with invalid language - assert_eq!(response.status_code(), StatusCode::BAD_REQUEST); - - harness.cleanup().await; -} - -#[tokio::test] -async fn test_ocr_health_endpoint() { - let harness = TestHarness::new().await; - - let response = harness - .server - .get("/api/ocr/health") - .add_header("Authorization", &format!("Bearer {}", harness.token)) - .await; - - assert_eq!(response.status_code(), StatusCode::OK); - - let body: serde_json::Value = response.json(); - assert!(body.get("status").is_some()); - assert!(body.get("available_languages").is_some()); - - harness.cleanup().await; -} - -#[tokio::test] -async fn test_concurrent_language_requests() { - let harness = TestHarness::new().await; - - // Make multiple concurrent requests to the languages endpoint - let mut handles = vec![]; - - for _ in 0..5 { - let server_clone = harness.server.clone(); - let token_clone = harness.token.clone(); - let handle = tokio::spawn(async move { - server_clone - .get("/api/ocr/languages") - .add_header("Authorization", &format!("Bearer {}", token_clone)) - .await - }); - handles.push(handle); - } - - // All requests should succeed - for handle in handles { - let response = handle.await.expect("Task panicked"); - assert_eq!(response.status_code(), StatusCode::OK); - } - - harness.cleanup().await; + let response = ctx.app().clone().oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); } \ No newline at end of file