Files
xiaoxia-saas/tests/unit/test_templates_editor_utils.py
T
CI Bot 7a72cfd709
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m9s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m19s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m10s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m7s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m7s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m17s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m37s
CI/CD Pipeline / Integration Tests (push) Successful in 2m18s
CI/CD Pipeline / Unit Tests (push) Failing after 4m59s
CI/CD Pipeline / Build Staging API Image (push) Successful in 8m39s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 27m2s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 30m15s
style: auto-format with black + isort + prettier
2026-07-25 00:16:10 +00:00

243 lines
7.1 KiB
Python
Executable File

"""模板编辑器工具函数测试 — _utils.py 纯函数."""
from __future__ import annotations
from dataclasses import dataclass
import pytest
from app.api.routes.templates_editor._utils import (
_clip_type_to_scene_label,
_clip_value,
_format_time,
_get_adjust_trim,
_get_adjust_volume,
_get_clip_config,
_validate_trim,
)
class TestFormatTime:
"""_format_time 秒数格式化测试."""
def test_zero(self):
"""0秒."""
assert _format_time(0) == "0:00"
def test_less_than_minute(self):
"""小于1分钟."""
assert _format_time(30) == "0:30"
assert _format_time(5) == "0:05"
assert _format_time(59) == "0:59"
def test_exact_minute(self):
"""整分钟."""
assert _format_time(60) == "1:00"
assert _format_time(120) == "2:00"
def test_minutes_and_seconds(self):
"""几分几秒."""
assert _format_time(65) == "1:05"
assert _format_time(125) == "2:05"
assert _format_time(600) == "10:00"
def test_float_seconds_truncated(self):
"""浮点秒数取整."""
assert _format_time(65.9) == "1:05"
assert _format_time(65.1) == "1:05"
class TestClipTypeToSceneLabel:
"""_clip_type_to_scene_label 片段类型转标签测试."""
def test_intro(self):
assert _clip_type_to_scene_label("intro", "") == "开场"
def test_title(self):
assert _clip_type_to_scene_label("title", "") == "标题"
def test_product(self):
assert _clip_type_to_scene_label("product", "") == "产品展示"
def test_showcase(self):
assert _clip_type_to_scene_label("showcase", "") == "场景展示"
def test_scene(self):
assert _clip_type_to_scene_label("scene", "") == "场景"
def test_subtitle(self):
assert _clip_type_to_scene_label("subtitle", "") == "字幕"
def test_text(self):
assert _clip_type_to_scene_label("text", "") == "文字"
def test_cta(self):
assert _clip_type_to_scene_label("cta", "") == "结尾 CTA"
def test_outro(self):
assert _clip_type_to_scene_label("outro", "") == "结尾"
def test_voiceover(self):
assert _clip_type_to_scene_label("voiceover", "") == "配音"
def test_transition(self):
assert _clip_type_to_scene_label("transition", "") == "转场"
def test_unknown_type_returns_itself(self):
"""未知类型返回类型名本身."""
assert _clip_type_to_scene_label("unknown_type", "") == "unknown_type"
def test_empty_type_fallback(self):
"""空类型fallback到片段."""
assert _clip_type_to_scene_label("", "") == "片段"
def test_with_text_content(self):
"""带文本内容时追加文本预览."""
result = _clip_type_to_scene_label("subtitle", "大家好今天")
assert "字幕 - 大家好今天" == result
def test_text_truncated_at_20_chars(self):
"""文本超过20字符截断."""
long_text = "一二三四五六七八九十一二三四五六七八九十"
result = _clip_type_to_scene_label("text", long_text + "extra")
# 前20个字符
assert long_text in result
assert "extra" not in result
def test_text_with_only_whitespace(self):
"""文本只有空白时不追加."""
result = _clip_type_to_scene_label("intro", " ")
assert result == "开场"
class TestValidateTrim:
"""_validate_trim 裁剪校验测试."""
def test_valid_trim(self):
"""合法裁剪."""
_validate_trim(1.0, 1.0, 5.0) # 不抛异常
def test_zero_trim(self):
"""不裁剪也合法."""
_validate_trim(0.0, 0.0, 5.0)
def test_trim_equals_total_raises(self):
"""裁剪总时长等于总时长→抛异常."""
with pytest.raises(ValueError, match="不能大于等于"):
_validate_trim(2.5, 2.5, 5.0)
def test_trim_exceeds_total_raises(self):
"""裁剪超过总时长→抛异常."""
with pytest.raises(ValueError):
_validate_trim(3.0, 3.0, 5.0)
def test_only_start_exceeds(self):
"""只有start就超过."""
with pytest.raises(ValueError):
_validate_trim(6.0, 0.0, 5.0)
def test_only_end_exceeds(self):
"""只有end就超过."""
with pytest.raises(ValueError):
_validate_trim(0.0, 6.0, 5.0)
class TestClipValue:
"""_clip_value 枚举/字符串值提取测试."""
def test_plain_string(self):
"""普通字符串返回自身."""
assert _clip_value("hello") == "hello"
def test_enum_value(self):
"""带value属性的对象返回value."""
class FakeEnum:
value = "enum_value"
assert _clip_value(FakeEnum()) == "enum_value"
def test_int_value(self):
"""整数转字符串."""
assert _clip_value(42) == "42"
@dataclass
class FakeClip:
"""测试用假Clip对象."""
id: str = "clip_1"
playback_speed: float = 1.0
duration: float = 10.0
config: dict | None = None
class TestGetClipConfig:
"""_get_clip_config 安全获取配置测试."""
def test_normal_config(self):
"""正常dict配置."""
clip = FakeClip(config={"volume": 0.5})
assert _get_clip_config(clip) == {"volume": 0.5}
def test_none_config(self):
"""config为None→返回空dict."""
clip = FakeClip(config=None)
assert _get_clip_config(clip) == {}
def test_non_dict_config(self):
"""config不是dict→返回空dict."""
clip = FakeClip(config="not_a_dict")
assert _get_clip_config(clip) == {}
def test_no_config_attribute(self):
"""没有config属性→返回空dict."""
class NoConfig:
pass
assert _get_clip_config(NoConfig()) == {}
class TestGetAdjustVolume:
"""_get_adjust_volume 获取音量测试."""
def test_default_volume(self):
"""无配置默认1.0."""
clip = FakeClip(config={})
assert _get_adjust_volume(clip) == pytest.approx(1.0)
def test_custom_volume(self):
"""自定义音量."""
clip = FakeClip(config={"volume": 0.7})
assert _get_adjust_volume(clip) == pytest.approx(0.7)
def test_none_config(self):
"""None config."""
clip = FakeClip(config=None)
assert _get_adjust_volume(clip) == pytest.approx(1.0)
class TestGetAdjustTrim:
"""_get_adjust_trim 获取裁剪测试."""
def test_default_trim(self):
"""无配置默认都是0."""
clip = FakeClip(config={})
start, end = _get_adjust_trim(clip)
assert start == pytest.approx(0.0)
assert end == pytest.approx(0.0)
def test_custom_trim(self):
"""自定义裁剪."""
clip = FakeClip(config={"trim_start": 1.5, "trim_end": 2.0})
start, end = _get_adjust_trim(clip)
assert start == pytest.approx(1.5)
assert end == pytest.approx(2.0)
def test_none_config(self):
"""None config."""
clip = FakeClip(config=None)
start, end = _get_adjust_trim(clip)
assert start == pytest.approx(0.0)
assert end == pytest.approx(0.0)