fix(upload): resolve issue with Axum not having config values set

This commit is contained in:
perfectra1n 2025-07-12 14:04:54 -07:00
parent 6c22cccefb
commit 9e143649d4
3 changed files with 233 additions and 0 deletions

View File

@ -1,4 +1,5 @@
use axum::{
extract::DefaultBodyLimit,
routing::get,
Router,
};
@ -531,6 +532,7 @@ async fn main() -> anyhow::Result<()> {
.precompressed_br()
.fallback(ServeFile::new(&index_file))
)
.layer(DefaultBodyLimit::max(config.max_file_size_mb as usize * 1024 * 1024))
.layer(CorsLayer::permissive())
.with_state(web_state.clone());

View File

@ -72,6 +72,14 @@ pub async fn upload_document(
StatusCode::BAD_REQUEST
})?;
// Validate file size against configured limit
let max_file_size_bytes = state.config.max_file_size_mb as usize * 1024 * 1024;
if data.len() > max_file_size_bytes {
error!("File '{}' size ({} bytes) exceeds maximum allowed size ({} bytes / {}MB)",
filename, data.len(), max_file_size_bytes, state.config.max_file_size_mb);
return Err(StatusCode::PAYLOAD_TOO_LARGE);
}
info!("Uploading document: {} ({} bytes)", filename, data.len());
// Create FileIngestionInfo from uploaded data

View File

@ -0,0 +1,223 @@
use reqwest::Client;
use axum::http::StatusCode;
use std::time::Duration;
use uuid::Uuid;
use readur::models::{CreateUser, LoginRequest, LoginResponse, UserRole};
fn get_base_url() -> String {
std::env::var("API_URL").unwrap_or_else(|_| "http://localhost:8000".to_string())
}
const TIMEOUT: Duration = Duration::from_secs(30);
/// Large file upload test client
struct LargeFileTestClient {
client: Client,
token: Option<String>,
}
impl LargeFileTestClient {
fn new() -> Self {
Self {
client: Client::new(),
token: None,
}
}
/// Register a new user and login to get auth token
async fn register_and_login(&mut self, role: UserRole) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let unique_id = Uuid::new_v4();
let username = format!("large_file_test_{}", unique_id);
let email = format!("large_file_test_{}@test.com", unique_id);
// Register user
let user_data = CreateUser {
username: username.clone(),
email: email.clone(),
password: "testpass123".to_string(),
role: Some(role),
};
let register_response = self.client
.post(&format!("{}/api/auth/register", get_base_url()))
.json(&user_data)
.send()
.await?;
if !register_response.status().is_success() {
return Err(format!("Registration failed: {}", register_response.text().await?).into());
}
// Login to get token
let login_data = LoginRequest {
username: username.clone(),
password: "testpass123".to_string(),
};
let login_response = self.client
.post(&format!("{}/api/auth/login", get_base_url()))
.json(&login_data)
.send()
.await?;
if !login_response.status().is_success() {
return Err(format!("Login failed: {}", login_response.text().await?).into());
}
let login_result: LoginResponse = login_response.json().await?;
self.token = Some(login_result.token.clone());
Ok(login_result.token)
}
/// Upload a file with specified content and filename
async fn upload_file(&self, content: Vec<u8>, filename: &str, mime_type: &str) -> Result<reqwest::Response, Box<dyn std::error::Error + Send + Sync>> {
let token = self.token.as_ref().ok_or("Not authenticated")?;
let part = reqwest::multipart::Part::bytes(content)
.file_name(filename.to_string())
.mime_str(mime_type)?;
let form = reqwest::multipart::Form::new().part("file", part);
let response = self.client
.post(&format!("{}/api/documents", get_base_url()))
.header("Authorization", format!("Bearer {}", token))
.multipart(form)
.send()
.await?;
Ok(response)
}
}
/// Test uploading files of various sizes to verify body limit configuration
#[tokio::test]
async fn test_file_size_limits() {
println!("🧪 Testing file size limits and body limit configuration...");
let mut client = LargeFileTestClient::new();
client.register_and_login(UserRole::User).await
.expect("Failed to create test user and login");
// Test 1: Small file (should succeed)
println!("📄 Testing small file upload...");
let small_content = "Small test file content.".repeat(100).into_bytes(); // ~2.5KB
let small_response = client.upload_file(small_content, "small_test.txt", "text/plain")
.await
.expect("Small file upload should complete");
println!("✅ Small file upload response: {}", small_response.status());
assert!(small_response.status().is_success(), "Small file upload should succeed");
// Test 2: Medium file (should succeed) - 3MB
println!("📄 Testing medium file upload (3MB)...");
let medium_content = "Medium test file content. ".repeat(125000).into_bytes(); // ~3MB
let medium_response = client.upload_file(medium_content, "medium_test.txt", "text/plain")
.await
.expect("Medium file upload should complete");
println!("✅ Medium file upload response: {}", medium_response.status());
assert!(medium_response.status().is_success(), "Medium file upload should succeed");
// Test 3: Large file (should succeed) - 15MB
println!("📄 Testing large file upload (15MB)...");
let large_content = "Large test file content. ".repeat(625000).into_bytes(); // ~15MB
let large_response = client.upload_file(large_content, "large_test.txt", "text/plain")
.await
.expect("Large file upload should complete");
println!("✅ Large file upload response: {}", large_response.status());
assert!(large_response.status().is_success(), "Large file upload should succeed");
// Test 4: Oversized file (should fail) - 60MB
println!("📄 Testing oversized file upload (60MB) - should fail...");
let oversized_content = vec![b'X'; 60 * 1024 * 1024]; // 60MB
let oversized_response = client.upload_file(oversized_content, "oversized_test.bin", "application/octet-stream")
.await
.expect("Oversized file upload request should complete");
println!("✅ Oversized file upload response: {}", oversized_response.status());
// Accept either 413 (app-level rejection) or 400 (Axum body limit rejection)
assert!(
oversized_response.status() == StatusCode::PAYLOAD_TOO_LARGE ||
oversized_response.status() == StatusCode::BAD_REQUEST,
"Oversized file upload should fail with 413 Payload Too Large or 400 Bad Request, got: {}",
oversized_response.status()
);
println!("🎉 File size limit tests passed!");
}
/// Test specifically with the problematic PDF from the GitHub issue
#[tokio::test]
async fn test_problematic_pdf_upload() {
println!("🧪 Testing upload with the problematic PDF file...");
let mut client = LargeFileTestClient::new();
client.register_and_login(UserRole::User).await
.expect("Failed to create test user and login");
// Try to read the problematic PDF file
let pdf_path = "test_files/porters-handbook_en.pdf";
if !std::path::Path::new(pdf_path).exists() {
println!("⚠️ Problematic PDF file not found at {}, skipping test", pdf_path);
return;
}
let pdf_data = std::fs::read(pdf_path)
.expect("Should be able to read PDF file");
println!("📄 PDF file size: {} bytes ({:.2} MB)",
pdf_data.len(), pdf_data.len() as f64 / (1024.0 * 1024.0));
let pdf_response = client.upload_file(pdf_data, "porters-handbook_en.pdf", "application/pdf")
.await
.expect("PDF upload request should complete");
println!("✅ PDF upload response: {}", pdf_response.status());
if pdf_response.status().is_success() {
println!("🎉 Problematic PDF uploaded successfully!");
// Verify the response contains expected data
let response_body: serde_json::Value = pdf_response.json().await
.expect("Should get JSON response");
assert!(response_body.get("id").is_some(), "Response should contain document ID");
assert_eq!(response_body.get("filename").and_then(|v| v.as_str()),
Some("porters-handbook_en.pdf"), "Filename should match");
println!("✅ Upload response data verified");
} else {
let status = pdf_response.status();
let error_text = pdf_response.text().await.unwrap_or_default();
panic!("PDF upload failed with status: {} - {}", status, error_text);
}
}
/// Test that error messages are helpful for oversized files
#[tokio::test]
async fn test_oversized_file_error_handling() {
println!("🧪 Testing error handling for oversized files...");
let mut client = LargeFileTestClient::new();
client.register_and_login(UserRole::User).await
.expect("Failed to create test user and login");
// Create a file that exceeds the 50MB limit
let oversized_content = vec![b'X'; 60 * 1024 * 1024]; // 60MB
let response = client.upload_file(oversized_content, "huge_file.bin", "application/octet-stream")
.await
.expect("Request should complete");
println!("✅ Oversized file response status: {}", response.status());
// Accept either 413 (app-level rejection) or 400 (Axum body limit rejection)
assert!(
response.status() == StatusCode::PAYLOAD_TOO_LARGE ||
response.status() == StatusCode::BAD_REQUEST,
"Should return 413 Payload Too Large or 400 Bad Request for oversized files, got: {}",
response.status()
);
println!("🎉 Error handling test passed!");
}