fix: delay wooden fish audio upload

This commit is contained in:
kdletters
2026-06-06 22:53:05 +08:00
parent d5b51a4242
commit ff7a2f6284
13 changed files with 1771 additions and 38 deletions

View File

@@ -1504,6 +1504,88 @@ mod tests {
assert!(!body_text.contains("length limit exceeded"));
}
#[tokio::test]
async fn wooden_fish_session_creation_accepts_legacy_audio_body_above_default_limit() {
let state = AppState::new(AppConfig::default()).expect("state should build");
let seed_user =
seed_phone_user_with_password(&state, "13800138026", TEST_PASSWORD).await;
let token = sign_test_user_token(&state, &seed_user, "sess_wooden_fish_audio_body");
let app = build_router(state);
let request_body = format!(
"{{\"templateId\":\"wooden-fish\",\"hitSoundAsset\":{{\"audioSrc\":\"data:audio/webm;base64,{}\"}}",
"A".repeat(3 * 1024 * 1024)
);
assert!(request_body.len() > 2 * 1024 * 1024);
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/creation/wooden-fish/sessions")
.header("authorization", format!("Bearer {token}"))
.header("content-type", "application/json")
.body(Body::from(request_body))
.expect("request should build"),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let body = response
.into_body()
.collect()
.await
.expect("response body should collect")
.to_bytes();
let body_text = String::from_utf8_lossy(&body);
assert!(
body_text.contains("hitSoundAsset") || body_text.contains("missing field"),
"handler should parse the oversized wooden fish payload before rejecting invalid JSON fields: {body_text}"
);
assert!(!body_text.contains("length limit exceeded"));
}
#[tokio::test]
async fn wooden_fish_actions_accept_legacy_audio_body_above_default_limit() {
let state = AppState::new(AppConfig::default()).expect("state should build");
let seed_user =
seed_phone_user_with_password(&state, "13800138027", TEST_PASSWORD).await;
let token = sign_test_user_token(&state, &seed_user, "sess_wooden_fish_action_body");
let app = build_router(state);
let request_body = format!(
"{{\"actionType\":\"replace-hit-sound\",\"hitSoundAsset\":{{\"audioSrc\":\"data:audio/webm;base64,{}\"}}",
"A".repeat(3 * 1024 * 1024)
);
assert!(request_body.len() > 2 * 1024 * 1024);
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/creation/wooden-fish/sessions/wooden-fish-session-large/actions")
.header("authorization", format!("Bearer {token}"))
.header("content-type", "application/json")
.body(Body::from(request_body))
.expect("request should build"),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let body = response
.into_body()
.collect()
.await
.expect("response body should collect")
.to_bytes();
let body_text = String::from_utf8_lossy(&body);
assert!(
body_text.contains("hitSoundAsset") || body_text.contains("missing field"),
"handler should parse the oversized wooden fish action payload before rejecting invalid JSON fields: {body_text}"
);
assert!(!body_text.contains("length limit exceeded"));
}
#[tokio::test]
async fn password_entry_rejects_unknown_phone_without_registration() {
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
@@ -4327,4 +4409,4 @@ mod tests {
assert_eq!(response.status(), StatusCode::NOT_FOUND, "{path}");
}
}
}
}

View File

@@ -1,27 +1,36 @@
use axum::{
Router, middleware,
routing::{delete, get, post},
Router,
extract::DefaultBodyLimit,
middleware,
routing::{get, post},
};
use crate::{
auth::{require_bearer_auth, require_runtime_principal_auth},
state::AppState,
wooden_fish::{
checkpoint_wooden_fish_run, create_wooden_fish_session, delete_wooden_fish_work,
execute_wooden_fish_action, finish_wooden_fish_run, get_wooden_fish_gallery_detail,
get_wooden_fish_runtime_work, get_wooden_fish_session, list_wooden_fish_gallery,
list_wooden_fish_works, publish_wooden_fish_work, start_wooden_fish_run,
checkpoint_wooden_fish_run, create_wooden_fish_session, execute_wooden_fish_action,
finish_wooden_fish_run, get_wooden_fish_gallery_detail, get_wooden_fish_runtime_work,
get_wooden_fish_session, list_wooden_fish_gallery, list_wooden_fish_works,
publish_wooden_fish_work, start_wooden_fish_run,
},
};
const WOODEN_FISH_CREATION_BODY_LIMIT_BYTES: usize = 32 * 1024 * 1024;
pub fn router(state: AppState) -> Router<AppState> {
Router::new()
.route(
"/api/creation/wooden-fish/sessions",
post(create_wooden_fish_session).route_layer(middleware::from_fn_with_state(
state.clone(),
require_bearer_auth,
)),
post(create_wooden_fish_session)
// 中文注释:兼容旧小程序把参考图或录音 Data URL 放进创作 JSON 的请求;新前端音频会先直传 OSS。
.layer(DefaultBodyLimit::max(
WOODEN_FISH_CREATION_BODY_LIMIT_BYTES,
))
.route_layer(middleware::from_fn_with_state(
state.clone(),
require_bearer_auth,
)),
)
.route(
"/api/creation/wooden-fish/sessions/{session_id}",
@@ -32,10 +41,15 @@ pub fn router(state: AppState) -> Router<AppState> {
)
.route(
"/api/creation/wooden-fish/sessions/{session_id}/actions",
post(execute_wooden_fish_action).route_layer(middleware::from_fn_with_state(
state.clone(),
require_bearer_auth,
)),
post(execute_wooden_fish_action)
// 中文注释compile/regenerate 会携带参考图旧兼容输入,避免 Axum 默认 2MB 先于 handler 拦截。
.layer(DefaultBodyLimit::max(
WOODEN_FISH_CREATION_BODY_LIMIT_BYTES,
))
.route_layer(middleware::from_fn_with_state(
state.clone(),
require_bearer_auth,
)),
)
.route(
"/api/creation/wooden-fish/works",
@@ -44,13 +58,6 @@ pub fn router(state: AppState) -> Router<AppState> {
require_bearer_auth,
)),
)
.route(
"/api/creation/wooden-fish/works/{profile_id}",
delete(delete_wooden_fish_work).route_layer(middleware::from_fn_with_state(
state.clone(),
require_bearer_auth,
)),
)
.route(
"/api/creation/wooden-fish/works/{profile_id}/publish",
post(publish_wooden_fish_work).route_layer(middleware::from_fn_with_state(
@@ -91,4 +98,4 @@ pub fn router(state: AppState) -> Router<AppState> {
"/api/runtime/wooden-fish/gallery/{public_work_code}",
get(get_wooden_fish_gallery_detail),
)
}
}