diff --git a/src/channels/web/CLAUDE.md b/src/channels/web/CLAUDE.md index df5cd6cf..8db9a6b7 100644 --- a/src/channels/web/CLAUDE.md +++ b/src/channels/web/CLAUDE.md @@ -197,7 +197,7 @@ All responses include: - `X-Content-Type-Options: nosniff` - `X-Frame-Options: DENY` -**Request body limit:** 1 MB (`DefaultBodyLimit::max(1024 * 1024)`). Larger payloads return 413. +**Request body limit:** 10 MB (`DefaultBodyLimit::max(10 * 1024 * 1024)`), sized for image uploads (#725). Larger payloads return 413. ## Pending Approvals diff --git a/tests/openai_compat_integration.rs b/tests/openai_compat_integration.rs index c6dd9a11..939f39eb 100644 --- a/tests/openai_compat_integration.rs +++ b/tests/openai_compat_integration.rs @@ -729,21 +729,45 @@ async fn test_no_llm_provider_returns_503() { #[tokio::test] async fn test_chat_completions_body_too_large() { - let (addr, _state, _mock_state) = start_test_server().await; - let url = format!("http://{}/v1/chat/completions", addr); + use axum::{Router, body::Body, extract::DefaultBodyLimit, middleware, routing::post}; + use tower::ServiceExt; - // Build a payload over 10 MB (the gateway's DefaultBodyLimit) + let mock_state = Arc::new(MockLlmState::default()); + let llm_provider: Arc = Arc::new(MockLlmProvider::new(mock_state)); + let state = ironclaw::channels::web::test_helpers::TestGatewayBuilder::new() + .llm_provider(llm_provider) + .build(); + let auth_state = ironclaw::channels::web::auth::AuthState { + token: AUTH_TOKEN.to_string(), + }; + + let app = Router::new() + .route( + "/v1/chat/completions", + post(ironclaw::channels::web::openai_compat::chat_completions_handler), + ) + .route_layer(middleware::from_fn_with_state( + auth_state, + ironclaw::channels::web::auth::auth_middleware, + )) + .layer(DefaultBodyLimit::max(10 * 1024 * 1024)) + .with_state(state); + + // Build a payload over 10 MB (the gateway's DefaultBodyLimit). let big_content = "x".repeat(11 * 1024 * 1024); - let resp = client() - .post(&url) - .bearer_auth(AUTH_TOKEN) - .json(&serde_json::json!({ - "model": "mock-model-v1", - "messages": [{"role": "user", "content": big_content}] - })) - .send() - .await + let body = serde_json::to_vec(&serde_json::json!({ + "model": "mock-model-v1", + "messages": [{"role": "user", "content": big_content}] + })) + .unwrap(); + let req = axum::http::Request::builder() + .method("POST") + .uri("/v1/chat/completions") + .header("authorization", format!("Bearer {}", AUTH_TOKEN)) + .header("content-type", "application/json") + .body(Body::from(body)) .unwrap(); + let resp = app.oneshot(req).await.unwrap(); assert_eq!(resp.status(), 413); }