Files
ttstd-infinite-canvas/backend/internal/service/storyboard_prompt_test.go
WindC0X 0e251c3c8f feat(canvas): 画布媒体工作流 - 支持批量连线、失败重试与模型能力增强
合并 PR #230。保留当前主线样式与组件结构,吸收批量连线、失败批次重试、媒体模型能力和相关后端增强,并修复集成检查发现的分辨率选择器启用条件与测试稳定性问题。
2026-08-15 15:48:16 +08:00

125 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"strings"
"testing"
)
func TestStoryboardCinematicQualityContractIncludesRequestedCountAndDuration(t *testing.T) {
contract := storyboardCinematicQualityContract(30, 7)
if !strings.Contains(contract, "严格等于 30 秒") {
t.Fatalf("contract does not include requested duration: %s", contract)
}
if !strings.Contains(contract, "严格输出 7 个镜头") {
t.Fatalf("contract does not include requested shot count: %s", contract)
}
}
func TestStoryboardCinematicQualityContractIncludesCameraLanguageGuide(t *testing.T) {
contract := storyboardCinematicQualityContract(0, 0)
for _, term := range []string{"先确定观众此刻跟随谁", "大远景只承担空间", "一个镜头只保留一个主运镜", "明确人物左右位置", "避免每镜都推进"} {
if !strings.Contains(contract, term) {
t.Fatalf("camera language guide is missing %q: %s", term, contract)
}
}
}
func TestStoryboardPromptsLeaveAspectRatioToVideoNode(t *testing.T) {
contract := storyboardCinematicQualityContract(0, 0)
if !strings.Contains(contract, "不要讨论画幅配置") {
t.Fatalf("contract does not delegate aspect ratio: %s", contract)
}
if strings.Contains(defaultStoryboardPromptTemplate(), "2.39:1") {
t.Fatal("default storyboard prompt still hard-codes 2.39:1")
}
prompt := buildStoryboardVideoPrompt("【风格组合】仙侠·宏大叙事·3D 动漫", "高清、明亮、空间层次清楚", agentStoryboardShot{
Duration: 8, Description: "剑仙踏云进入画面", VisualPrompt: "剑仙位于画面左侧", VideoPrompt: "向前踏出一步后停住", Camera: "平视中景", Motion: "缓慢推近后停住", TimeBeats: "0-4秒:进入;4-8秒:停住",
})
if strings.Contains(prompt, "2.39:1") || strings.Contains(prompt, "画幅") {
t.Fatal("compiled storyboard video prompt still mentions output aspect ratio")
}
}
func TestRemoveFixedMediaRestrictionsKeepsCurrentProjectMedium(t *testing.T) {
template := "保留这一行\n- 不要在 visualPrompt 或 videoPrompt 中使用 3D动漫、动画、二次元、游戏CG、卡通渲染。\n继续保留"
filtered := removeFixedMediaRestrictions(template)
if strings.Contains(filtered, "不要在 visualPrompt") {
t.Fatal("fixed 3D/cartoon restriction was not removed")
}
if !strings.Contains(filtered, "保留这一行") || !strings.Contains(filtered, "继续保留") {
t.Fatal("unrelated template content was removed")
}
}
func TestValidateStoryboardShotCount(t *testing.T) {
plan := agentStoryboardPlan{Shots: make([]agentStoryboardShot, 3)}
if err := validateStoryboardShotCount(plan, 3); err != nil {
t.Fatalf("expected matching shot count to pass: %v", err)
}
if err := validateStoryboardShotCount(plan, 2); err == nil {
t.Fatal("expected mismatched shot count to fail")
}
if err := validateStoryboardShotCount(plan, 0); err != nil {
t.Fatalf("expected automatic shot count to pass: %v", err)
}
}
func TestValidateStoryboardComplexityRejectsOverloadedShot(t *testing.T) {
plan := agentStoryboardPlan{Shots: []agentStoryboardShot{{
Duration: 5, CharacterIDs: []string{"a", "b", "c"}, MustHave: []string{"一", "二", "三", "四"}, TimeBeats: "0-1秒:起,1-2秒:承,2-4秒:转,4-5秒:合", Motion: "航拍推进并升降",
}}}
if err := validateStoryboardComplexity(plan); err == nil {
t.Fatal("expected overloaded shot to fail complexity validation")
}
}
func TestValidateStoryboardPlanTreatsComplexityAsAdvisory(t *testing.T) {
plan := agentStoryboardPlan{Shots: []agentStoryboardShot{{
Duration: 8,
CharacterIDs: []string{"a", "b", "c"},
MustHave: []string{"一", "二", "三", "四"},
Motion: "航拍推进并升降",
}}}
characters := []storyboardCharacterCard{{AssetID: "a"}, {AssetID: "b"}, {AssetID: "c"}}
if err := validateStoryboardPlan(plan, 0, 0, characters); err != nil {
t.Fatalf("complexity should be advisory for an otherwise valid plan: %v", err)
}
}
func TestValidateStoryboardComplexityAcceptsSingleDirectedMove(t *testing.T) {
plan := agentStoryboardPlan{Shots: []agentStoryboardShot{{
Duration: 8, CharacterIDs: []string{"a", "b"}, MustHave: []string{"身份稳定", "动作落点", "结尾状态"}, TimeBeats: "0-2秒:建立;2-6秒:动作;6-8秒:反应", Motion: "缓慢推近后停住", Dialogue: "你终于来了。",
}}}
if err := validateStoryboardComplexity(plan); err != nil {
t.Fatalf("expected focused shot to pass complexity validation: %v", err)
}
}
func TestNormalizeAutomaticStoryboardDurationsPreservesLongDialogue(t *testing.T) {
plan := agentStoryboardPlan{Shots: []agentStoryboardShot{{
Duration: 8,
Dialogue: strings.Repeat("字", 50),
}}}
normalizeAutomaticStoryboardDurations(&plan, 0)
if plan.Shots[0].Duration < 10 {
t.Fatalf("duration = %d, want at least 10", plan.Shots[0].Duration)
}
if err := validateStoryboardComplexity(plan); err != nil {
t.Fatalf("normalized automatic duration should pass: %v", err)
}
}
func TestMatchStoryboardAssetsForShotPrioritizesCurrentCharacterCards(t *testing.T) {
assets := []storyboardAsset{
{ID: "environment", Title: "天宫", Tags: []string{"场景:天宫"}},
{ID: "character-node", Title: "青莲剑仙", CharacterAssetID: "character-1", CharacterVersionID: "version-3"},
}
matched := matchStoryboardAssetsForShot(assets, agentStoryboardShot{CharacterIDs: []string{"character-1"}, AssetTags: []string{"场景:天宫"}})
if len(matched) != 2 || matched[0].ID != "character-node" {
t.Fatalf("expected current character card before optional tagged assets: %#v", matched)
}
}