测试:完成BGM提示词优化收口并切换助手模型
增加 TypeScript/Rust 共用 canonicalization fixture 补齐 BGM 跨层等值、SFX 和后端边界测试 将 BGM 补全与简化请求模型改为 gpt-5.6-luna,保留画布 Agent gpt-5.4-mini 同步权威设计、项目记忆和 T6 发布门禁记录 验证本地定向测试与真实 VectorEngine 补全/简化请求
This commit is contained in:
@@ -4,7 +4,7 @@ use axum::{
|
||||
http::StatusCode,
|
||||
response::Response,
|
||||
};
|
||||
use platform_llm::{EDITOR_AGENT_GPT5_MODEL, LlmClient, LlmMessage, LlmRunRequest, LlmRunResponse};
|
||||
use platform_llm::{LlmClient, LlmMessage, LlmRunRequest, LlmRunResponse};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::assets::{
|
||||
@@ -25,6 +25,7 @@ use crate::{
|
||||
};
|
||||
|
||||
const BACKGROUND_MUSIC_PROMPT_ASSIST_MAX_OUTPUT_TOKENS: u32 = 1024;
|
||||
const BACKGROUND_MUSIC_PROMPT_ASSIST_MODEL: &str = "gpt-5.6-luna";
|
||||
const BACKGROUND_MUSIC_PROMPT_MAX_CHARS: usize =
|
||||
platform_audio::SUNO_GPT_DESCRIPTION_PROMPT_MAX_CHARS;
|
||||
const BACKGROUND_MUSIC_SIMPLIFICATION_MAX_CHARS: usize =
|
||||
@@ -210,7 +211,7 @@ fn build_background_music_prompt_llm_request(
|
||||
LlmMessage::system(system_prompt),
|
||||
LlmMessage::user(user_prompt),
|
||||
])
|
||||
.with_model(EDITOR_AGENT_GPT5_MODEL)
|
||||
.with_model(BACKGROUND_MUSIC_PROMPT_ASSIST_MODEL)
|
||||
.with_openai_chat()
|
||||
.with_max_output_tokens(BACKGROUND_MUSIC_PROMPT_ASSIST_MAX_OUTPUT_TOKENS)
|
||||
}
|
||||
@@ -466,7 +467,7 @@ mod tests {
|
||||
let request =
|
||||
build_background_music_prompt_llm_request("系统".to_string(), "用户".to_string());
|
||||
|
||||
assert_eq!(request.model.as_deref(), Some(EDITOR_AGENT_GPT5_MODEL));
|
||||
assert_eq!(request.model.as_deref(), Some("gpt-5.6-luna"));
|
||||
assert_eq!(request.api_kind, LlmApiKind::OpenAiChat);
|
||||
assert_eq!(
|
||||
request.max_output_tokens,
|
||||
@@ -875,7 +876,10 @@ mod tests {
|
||||
.request_line
|
||||
.starts_with("POST /v1/chat/completions HTTP/1.1")
|
||||
);
|
||||
assert_eq!(requests[0].body["model"], EDITOR_AGENT_GPT5_MODEL);
|
||||
assert_eq!(
|
||||
requests[0].body["model"],
|
||||
BACKGROUND_MUSIC_PROMPT_ASSIST_MODEL
|
||||
);
|
||||
assert!(requests[0].body.get("tools").is_none());
|
||||
assert!(requests[0].body.get("tool_choice").is_none());
|
||||
let input = user_message_json(&requests[0].body);
|
||||
@@ -1431,7 +1435,7 @@ mod tests {
|
||||
}
|
||||
json!({
|
||||
"id": "chat_bgm_prompt_assist",
|
||||
"model": EDITOR_AGENT_GPT5_MODEL,
|
||||
"model": BACKGROUND_MUSIC_PROMPT_ASSIST_MODEL,
|
||||
"choices": [choice],
|
||||
})
|
||||
.to_string()
|
||||
|
||||
@@ -529,6 +529,35 @@ pub(crate) async fn enqueue_editor_background_music_generation_for_owner(
|
||||
.await
|
||||
}
|
||||
|
||||
fn build_editor_background_music_generate_response(
|
||||
audio_src: String,
|
||||
generated: shared_contracts::creation_audio::GeneratedAudioAssetResponse,
|
||||
normalized: NormalizedEditorBackgroundMusicRequest,
|
||||
project: Option<Value>,
|
||||
resource: Option<Value>,
|
||||
asset: Option<Value>,
|
||||
) -> assets::EditorAudioGenerateResponse {
|
||||
assets::EditorAudioGenerateResponse {
|
||||
ok: true,
|
||||
audio_src,
|
||||
object_key: generated.object_key,
|
||||
asset_object_id: generated.asset_object_id,
|
||||
width: EDITOR_AUDIO_WIDTH,
|
||||
height: EDITOR_AUDIO_HEIGHT,
|
||||
source_type: "generated".to_string(),
|
||||
prompt: normalized.gpt_description_prompt.clone(),
|
||||
actual_prompt: Some(normalized.gpt_description_prompt),
|
||||
model: platform_audio::SUNO_DEFAULT_MODEL.to_string(),
|
||||
task_id: generated.task_id,
|
||||
price_mud_points: normalized.price_mud_points,
|
||||
audio_kind: "background-music".to_string(),
|
||||
project,
|
||||
resource,
|
||||
asset,
|
||||
queue_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_editor_background_music_for_owner(
|
||||
state: AppState,
|
||||
request_context: RequestContext,
|
||||
@@ -586,6 +615,7 @@ pub(crate) async fn generate_editor_background_music_for_owner(
|
||||
.map_err(|error| error.into_response_with_context(Some(&request_context)))?;
|
||||
let audio_src = generated
|
||||
.audio_src
|
||||
.clone()
|
||||
.ok_or_else(|| vector_engine_bad_gateway("背景音乐生成完成但缺少播放地址"))
|
||||
.map_err(|error| error.into_response_with_context(Some(&request_context)))?;
|
||||
let (resource, asset) = persist_editor_generated_media_asset(
|
||||
@@ -678,25 +708,14 @@ pub(crate) async fn generate_editor_background_music_for_owner(
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
assets::EditorAudioGenerateResponse {
|
||||
ok: true,
|
||||
build_editor_background_music_generate_response(
|
||||
audio_src,
|
||||
object_key: generated.object_key,
|
||||
asset_object_id: generated.asset_object_id,
|
||||
width: EDITOR_AUDIO_WIDTH,
|
||||
height: EDITOR_AUDIO_HEIGHT,
|
||||
source_type: "generated".to_string(),
|
||||
prompt: normalized.gpt_description_prompt.clone(),
|
||||
actual_prompt: Some(normalized.gpt_description_prompt),
|
||||
model: platform_audio::SUNO_DEFAULT_MODEL.to_string(),
|
||||
task_id: generated.task_id,
|
||||
price_mud_points: normalized.price_mud_points,
|
||||
audio_kind: "background-music".to_string(),
|
||||
project: completed_project,
|
||||
generated,
|
||||
normalized,
|
||||
completed_project,
|
||||
resource,
|
||||
asset,
|
||||
queue_state: None,
|
||||
},
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -727,15 +746,38 @@ fn editor_audio_bad_request(message: impl Into<String>) -> AppError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use shared_contracts::assets;
|
||||
|
||||
use super::{
|
||||
CanonicalEditorBackgroundMusicSubmissionPayload, normalize_editor_background_music_request,
|
||||
prepare_editor_background_music_queue_job,
|
||||
CanonicalEditorBackgroundMusicSubmissionPayload, NormalizedEditorBackgroundMusicRequest,
|
||||
build_editor_background_music_generate_response, normalize_editor_background_music_request,
|
||||
normalize_editor_sound_effect_request, prepare_editor_background_music_queue_job,
|
||||
};
|
||||
use crate::editor_generation_queue::serialize_editor_generation_job_payload_for_test;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct BackgroundMusicPromptFixture {
|
||||
name: String,
|
||||
input: String,
|
||||
prompt: String,
|
||||
char_count: usize,
|
||||
effective_char_count: usize,
|
||||
}
|
||||
|
||||
fn background_music_prompt_fixture(name: &str) -> BackgroundMusicPromptFixture {
|
||||
let fixtures: Vec<BackgroundMusicPromptFixture> = serde_json::from_str(include_str!(
|
||||
"../../../../../packages/shared/test-fixtures/background-music-prompt-canonicalization.json"
|
||||
))
|
||||
.expect("shared BGM prompt fixtures should be valid JSON");
|
||||
fixtures
|
||||
.into_iter()
|
||||
.find(|fixture| fixture.name == name)
|
||||
.unwrap_or_else(|| panic!("missing shared BGM prompt fixture: {name}"))
|
||||
}
|
||||
|
||||
fn background_music_payload(
|
||||
prompt: impl Into<String>,
|
||||
make_instrumental: bool,
|
||||
@@ -751,42 +793,67 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn sound_effect_payload(
|
||||
prompt: impl Into<String>,
|
||||
model: Option<&str>,
|
||||
duration: u8,
|
||||
) -> assets::EditorSoundEffectGenerateRequest {
|
||||
assets::EditorSoundEffectGenerateRequest {
|
||||
prompt: prompt.into(),
|
||||
model: model.map(str::to_string),
|
||||
duration,
|
||||
project_id: Some("project-1".to_string()),
|
||||
canvas_completion: None,
|
||||
generation_inputs: None,
|
||||
asset_folder_id: None,
|
||||
asset_label: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_background_music_normalization_uses_canonical_generation_prompt() {
|
||||
let prompt = "\u{0085} \u{2003}森林 \r\n\u{200b}\u{feff}音乐 \u{00a0}";
|
||||
let normalized =
|
||||
normalize_editor_background_music_request(background_music_payload(prompt, false))
|
||||
.expect("canonical BGM prompt should be accepted");
|
||||
let representative = background_music_prompt_fixture("representative-complex");
|
||||
let normalized = normalize_editor_background_music_request(background_music_payload(
|
||||
representative.input,
|
||||
false,
|
||||
))
|
||||
.expect("canonical BGM prompt should be accepted");
|
||||
|
||||
assert_eq!(
|
||||
normalized.gpt_description_prompt,
|
||||
"森林 \r\n\u{200b}\u{feff}音乐"
|
||||
);
|
||||
assert_eq!(normalized.gpt_description_prompt, representative.prompt);
|
||||
assert!(normalized.make_instrumental);
|
||||
|
||||
for invalid_prompt in [" \r\n\u{0085}", &"乐".repeat(201)] {
|
||||
for valid_prompt in ["A".to_string(), "乐".repeat(200)] {
|
||||
let normalized = normalize_editor_background_music_request(background_music_payload(
|
||||
valid_prompt.clone(),
|
||||
true,
|
||||
))
|
||||
.expect("1-200 canonical code points should be accepted");
|
||||
assert_eq!(normalized.gpt_description_prompt, valid_prompt);
|
||||
}
|
||||
|
||||
let unicode_white_space = background_music_prompt_fixture("unicode-white-space-only").input;
|
||||
for invalid_prompt in [String::new(), unicode_white_space, "乐".repeat(201)] {
|
||||
let error = normalize_editor_background_music_request(background_music_payload(
|
||||
invalid_prompt,
|
||||
true,
|
||||
))
|
||||
.expect_err("invalid canonical BGM prompt should be rejected");
|
||||
.expect_err("empty, whitespace-only, and 201-code-point prompts should be rejected");
|
||||
assert_eq!(error.status_code(), axum::http::StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logged_in_background_music_queue_preparation_uses_canonical_prompt_and_frozen_price() {
|
||||
let representative = background_music_prompt_fixture("representative-complex");
|
||||
let pricing =
|
||||
crate::editor_generation_config::load_editor_generation_pricing_from_paths(None)
|
||||
.expect("default model pricing should be valid");
|
||||
let prepared =
|
||||
CanonicalEditorBackgroundMusicSubmissionPayload::new(background_music_payload(
|
||||
"\u{0085}\u{2003}森林 \r\n\u{200b}\u{feff}音乐\u{00a0}",
|
||||
false,
|
||||
))
|
||||
.expect("logged-in submission should canonicalize")
|
||||
.into_queue_job(&pricing)
|
||||
.expect("logged-in queue job should prepare");
|
||||
let prepared = CanonicalEditorBackgroundMusicSubmissionPayload::new(
|
||||
background_music_payload(representative.input, false),
|
||||
)
|
||||
.expect("logged-in submission should canonicalize")
|
||||
.into_queue_job(&pricing)
|
||||
.expect("logged-in queue job should prepare");
|
||||
let request_payload_json =
|
||||
serialize_editor_generation_job_payload_for_test(&prepared.payload)
|
||||
.expect("the actual queue serializer should accept the canonical payload");
|
||||
@@ -795,7 +862,23 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
request_payload["gptDescriptionPrompt"],
|
||||
"森林 \r\n\u{200b}\u{feff}音乐"
|
||||
representative.prompt
|
||||
);
|
||||
assert_eq!(
|
||||
platform_audio::background_music_prompt_char_count(
|
||||
request_payload["gptDescriptionPrompt"]
|
||||
.as_str()
|
||||
.expect("queue prompt should be a string"),
|
||||
),
|
||||
representative.char_count,
|
||||
);
|
||||
assert_eq!(
|
||||
platform_audio::background_music_prompt_effective_char_count(
|
||||
request_payload["gptDescriptionPrompt"]
|
||||
.as_str()
|
||||
.expect("queue prompt should be a string"),
|
||||
),
|
||||
representative.effective_char_count,
|
||||
);
|
||||
assert_eq!(request_payload["makeInstrumental"], true);
|
||||
assert!(request_payload.get("priceMudPoints").is_none());
|
||||
@@ -805,6 +888,114 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_background_music_inline_response_keeps_canonical_prompt_equal() {
|
||||
let representative = background_music_prompt_fixture("representative-complex");
|
||||
let response = build_editor_background_music_generate_response(
|
||||
"/generated-editor-audios/background-music.wav".to_string(),
|
||||
shared_contracts::creation_audio::GeneratedAudioAssetResponse {
|
||||
kind:
|
||||
shared_contracts::creation_audio::CreationAudioGenerationKind::BackgroundMusic,
|
||||
task_id: "task-background-music-1".to_string(),
|
||||
provider: platform_audio::VECTOR_ENGINE_PROVIDER.to_string(),
|
||||
status: "completed".to_string(),
|
||||
asset_object_id: Some("asset-object-1".to_string()),
|
||||
object_key: Some("generated/editor/background-music.wav".to_string()),
|
||||
asset_kind: Some("editor_background_music".to_string()),
|
||||
audio_src: Some("/generated-editor-audios/background-music.wav".to_string()),
|
||||
},
|
||||
NormalizedEditorBackgroundMusicRequest {
|
||||
gpt_description_prompt: representative.prompt.clone(),
|
||||
make_instrumental: true,
|
||||
price_mud_points: 5,
|
||||
},
|
||||
Some(serde_json::json!({ "projectId": "project-1" })),
|
||||
Some(serde_json::json!({ "resourceId": "resource-1" })),
|
||||
Some(serde_json::json!({ "assetId": "asset-1" })),
|
||||
);
|
||||
|
||||
assert_eq!(response.prompt, representative.prompt);
|
||||
assert_eq!(
|
||||
response.actual_prompt.as_deref(),
|
||||
Some(response.prompt.as_str())
|
||||
);
|
||||
let serialized =
|
||||
serde_json::to_value(response).expect("inline response should serialize to JSON");
|
||||
assert_eq!(serialized["prompt"], serialized["actualPrompt"]);
|
||||
assert_eq!(serialized["prompt"], representative.prompt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_sound_effect_normalizer_preserves_model_prompt_and_dynamic_price() {
|
||||
let pricing =
|
||||
crate::editor_generation_config::load_editor_generation_pricing_from_paths(None)
|
||||
.expect("default model pricing should be valid");
|
||||
let normalized = normalize_editor_sound_effect_request(sound_effect_payload(
|
||||
" 金币掉落叮当声 ",
|
||||
None,
|
||||
7,
|
||||
))
|
||||
.expect("default Vidu sound effect request should normalize");
|
||||
|
||||
assert_eq!(normalized.prompt, "金币掉落叮当声");
|
||||
assert_eq!(normalized.model, platform_audio::VIDU_AUDIO_MODEL);
|
||||
assert_eq!(normalized.duration, 7);
|
||||
assert_eq!(
|
||||
normalized.price_mud_points,
|
||||
pricing.sound_effect_model_mud_points(Some(platform_audio::VIDU_AUDIO_MODEL))
|
||||
);
|
||||
|
||||
let explicit_model = normalize_editor_sound_effect_request(sound_effect_payload(
|
||||
"按钮确认短促音",
|
||||
Some(" audio1.0 "),
|
||||
5,
|
||||
))
|
||||
.expect("the only supported Vidu model should be accepted");
|
||||
assert_eq!(explicit_model.model, platform_audio::VIDU_AUDIO_MODEL);
|
||||
|
||||
let error = normalize_editor_sound_effect_request(sound_effect_payload(
|
||||
"按钮确认短促音",
|
||||
Some(platform_audio::SUNO_DEFAULT_MODEL),
|
||||
5,
|
||||
))
|
||||
.expect_err("Suno text-to-sound should remain unavailable for editor SFX");
|
||||
assert_eq!(error.status_code(), axum::http::StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_sound_effect_normalizer_enforces_prompt_and_duration_boundaries() {
|
||||
let max_prompt = "声".repeat(platform_audio::VIDU_PROMPT_MAX_CHARS);
|
||||
let normalized = normalize_editor_sound_effect_request(sound_effect_payload(
|
||||
format!(" {max_prompt} "),
|
||||
None,
|
||||
2,
|
||||
))
|
||||
.expect("1500 canonical code points and the 2-second boundary should pass");
|
||||
assert_eq!(normalized.prompt, max_prompt);
|
||||
assert_eq!(normalized.duration, 2);
|
||||
|
||||
let normalized =
|
||||
normalize_editor_sound_effect_request(sound_effect_payload("环境循环音", None, 10))
|
||||
.expect("the 10-second boundary should pass");
|
||||
assert_eq!(normalized.duration, 10);
|
||||
|
||||
let overlong_prompt = "声".repeat(platform_audio::VIDU_PROMPT_MAX_CHARS + 1);
|
||||
let error =
|
||||
normalize_editor_sound_effect_request(sound_effect_payload(overlong_prompt, None, 5))
|
||||
.expect_err("1501 canonical code points should fail");
|
||||
assert_eq!(error.status_code(), axum::http::StatusCode::BAD_REQUEST);
|
||||
|
||||
for duration in [1, 11] {
|
||||
let error = normalize_editor_sound_effect_request(sound_effect_payload(
|
||||
"环境循环音",
|
||||
None,
|
||||
duration,
|
||||
))
|
||||
.expect_err("duration outside the existing 2-10 range should fail");
|
||||
assert_eq!(error.status_code(), axum::http::StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_background_music_queue_preparation_preserves_the_raw_payload() {
|
||||
let pricing =
|
||||
|
||||
@@ -12,6 +12,13 @@ use platform_audio::{
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
fn background_music_prompt_canonicalization_fixture() -> serde_json::Value {
|
||||
serde_json::from_str(include_str!(
|
||||
"../../../../packages/shared/test-fixtures/background-music-prompt-canonicalization.json"
|
||||
))
|
||||
.expect("background music prompt canonicalization fixture should be valid JSON")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_music_simplification_limit_is_ten_times_the_generation_limit() {
|
||||
assert_eq!(
|
||||
@@ -22,59 +29,52 @@ fn background_music_simplification_limit_is_ten_times_the_generation_limit() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_music_prompt_canonicalization_is_idempotent() {
|
||||
let prompt = " \t\u{0085}\u{00a0}音 \r\n\u{200b}\u{feff}😀\u{2003}\n ";
|
||||
let canonical = canonicalize_background_music_prompt(prompt);
|
||||
fn background_music_prompt_canonicalization_matches_shared_fixture() {
|
||||
let fixture = background_music_prompt_canonicalization_fixture();
|
||||
let cases = fixture
|
||||
.as_array()
|
||||
.expect("background music prompt canonicalization fixture should be an array");
|
||||
|
||||
assert_eq!(canonical, "音 \r\n\u{200b}\u{feff}😀");
|
||||
assert_eq!(canonicalize_background_music_prompt(canonical), canonical);
|
||||
}
|
||||
for case in cases {
|
||||
let name = case["name"]
|
||||
.as_str()
|
||||
.expect("fixture case name should be a string");
|
||||
let input = case["input"]
|
||||
.as_str()
|
||||
.expect("fixture case input should be a string");
|
||||
let expected_prompt = case["prompt"]
|
||||
.as_str()
|
||||
.expect("fixture case prompt should be a string");
|
||||
let expected_char_count = case["charCount"]
|
||||
.as_u64()
|
||||
.expect("fixture case charCount should be an unsigned integer")
|
||||
as usize;
|
||||
let expected_effective_char_count = case["effectiveCharCount"]
|
||||
.as_u64()
|
||||
.expect("fixture case effectiveCharCount should be an unsigned integer")
|
||||
as usize;
|
||||
let canonical_prompt = canonicalize_background_music_prompt(input);
|
||||
|
||||
#[test]
|
||||
fn background_music_prompt_canonicalization_matches_unicode_white_space_test_vector() {
|
||||
let unicode_white_space: String = [
|
||||
'\u{0009}', '\u{000a}', '\u{000b}', '\u{000c}', '\u{000d}', '\u{0020}', '\u{0085}',
|
||||
'\u{00a0}', '\u{1680}', '\u{2000}', '\u{2001}', '\u{2002}', '\u{2003}', '\u{2004}',
|
||||
'\u{2005}', '\u{2006}', '\u{2007}', '\u{2008}', '\u{2009}', '\u{200a}', '\u{2028}',
|
||||
'\u{2029}', '\u{202f}', '\u{205f}', '\u{3000}',
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let internal_white_space = "A \t\r\n\u{0085}\u{00a0}\u{3000}B";
|
||||
let prompt = format!("{unicode_white_space}{internal_white_space}{unicode_white_space}");
|
||||
|
||||
assert_eq!(unicode_white_space.chars().count(), 25);
|
||||
assert!(unicode_white_space.chars().all(char::is_whitespace));
|
||||
assert_eq!(
|
||||
canonicalize_background_music_prompt(&prompt),
|
||||
internal_white_space
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_music_prompt_counts_canonical_unicode_code_points_and_effective_characters() {
|
||||
let prompt = " \t\n\u{00a0}\u{2003}\u{200b}😀\u{0085}";
|
||||
let internal_whitespace = "A \t\n\u{00a0}\u{2003}\u{0085} B";
|
||||
|
||||
assert_eq!(background_music_prompt_char_count(prompt), 2);
|
||||
assert_eq!(background_music_prompt_effective_char_count(prompt), 2);
|
||||
assert_eq!(background_music_prompt_char_count(internal_whitespace), 9);
|
||||
assert_eq!(
|
||||
background_music_prompt_effective_char_count(internal_whitespace),
|
||||
2
|
||||
);
|
||||
assert_eq!(background_music_prompt_char_count("\r\n"), 0);
|
||||
assert_eq!(background_music_prompt_effective_char_count("\r\n"), 0);
|
||||
assert_eq!(background_music_prompt_char_count("e\u{0301}"), 2);
|
||||
assert_eq!(background_music_prompt_effective_char_count("e\u{0301}"), 2);
|
||||
assert_eq!(background_music_prompt_char_count("👩💻"), 3);
|
||||
assert_eq!(background_music_prompt_effective_char_count("👩💻"), 3);
|
||||
assert_eq!(background_music_prompt_char_count("\u{0085}"), 0);
|
||||
assert_eq!(background_music_prompt_effective_char_count("\u{0085}"), 0);
|
||||
assert_eq!(background_music_prompt_char_count("\u{200b}"), 1);
|
||||
assert_eq!(background_music_prompt_effective_char_count("\u{200b}"), 1);
|
||||
assert_eq!(background_music_prompt_char_count("\u{feff}"), 1);
|
||||
assert_eq!(background_music_prompt_effective_char_count("\u{feff}"), 1);
|
||||
assert_eq!(
|
||||
canonical_prompt, expected_prompt,
|
||||
"canonical prompt mismatch for fixture case {name}"
|
||||
);
|
||||
assert_eq!(
|
||||
background_music_prompt_char_count(input),
|
||||
expected_char_count,
|
||||
"char count mismatch for fixture case {name}"
|
||||
);
|
||||
assert_eq!(
|
||||
background_music_prompt_effective_char_count(input),
|
||||
expected_effective_char_count,
|
||||
"effective char count mismatch for fixture case {name}"
|
||||
);
|
||||
assert_eq!(
|
||||
canonicalize_background_music_prompt(canonical_prompt),
|
||||
canonical_prompt,
|
||||
"canonicalization should be idempotent for fixture case {name}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -286,19 +286,28 @@ fn sound_effect_request_rejects_overlong_prompt() {
|
||||
|
||||
#[test]
|
||||
fn editor_background_music_request_body_uses_gpt_description_prompt_and_instrumental() {
|
||||
let fixture = background_music_prompt_canonicalization_fixture();
|
||||
let representative_case = fixture
|
||||
.as_array()
|
||||
.expect("background music prompt canonicalization fixture should be an array")
|
||||
.iter()
|
||||
.find(|case| case["name"].as_str() == Some("representative-complex"))
|
||||
.expect("fixture should contain the representative-complex case");
|
||||
let input = representative_case["input"]
|
||||
.as_str()
|
||||
.expect("representative-complex input should be a string");
|
||||
let expected_prompt = representative_case["prompt"]
|
||||
.as_str()
|
||||
.expect("representative-complex prompt should be a string");
|
||||
let body = build_editor_background_music_task_body(EditorBackgroundMusicTaskRequest {
|
||||
gpt_description_prompt: "\u{0085}\u{2003}轻快森林 \r\n\u{200b}\u{feff}冒险背景音乐\u{00a0}"
|
||||
.to_string(),
|
||||
gpt_description_prompt: input.to_string(),
|
||||
make_instrumental: true,
|
||||
model: None,
|
||||
})
|
||||
.expect("editor background music body should be valid");
|
||||
|
||||
assert_eq!(body["mv"], SUNO_DEFAULT_MODEL);
|
||||
assert_eq!(
|
||||
body["gpt_description_prompt"],
|
||||
"轻快森林 \r\n\u{200b}\u{feff}冒险背景音乐"
|
||||
);
|
||||
assert_eq!(body["gpt_description_prompt"], expected_prompt);
|
||||
assert_eq!(body["make_instrumental"], true);
|
||||
assert_eq!(
|
||||
body.as_object()
|
||||
|
||||
Reference in New Issue
Block a user