Files
xiaoxia-saas/tests/unit/test_subtitle.py
CI Bot 68e55df83b
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
style: auto-format with black + isort + prettier
2026-07-24 10:59:46 +00:00

228 lines
7.9 KiB
Python
Executable File

"""Subtitle 领域模型单测."""
import pytest
from packages.domain.subtitle import SubtitleSegment, SubtitleTimeline, SubtitleWord
class TestSubtitleWord:
def test_duration_normal(self):
word = SubtitleWord(text="你好", start=1.0, end=2.5)
assert word.duration == pytest.approx(1.5)
def test_duration_zero(self):
word = SubtitleWord(text="啊", start=3.0, end=3.0)
assert word.duration == 0.0
def test_duration_negative_returns_zero(self):
word = SubtitleWord(text="test", start=5.0, end=3.0)
assert word.duration == 0.0
class TestSubtitleSegment:
def test_duration(self):
seg = SubtitleSegment(text="大家好", start=0.0, end=3.0)
assert seg.duration == pytest.approx(3.0)
def test_char_count(self):
seg = SubtitleSegment(text="今天天气真好", start=0.0, end=5.0)
assert seg.char_count == 6
def test_empty_text(self):
seg = SubtitleSegment(text="", start=0.0, end=1.0)
assert seg.char_count == 0
def test_default_words_empty(self):
seg = SubtitleSegment(text="test", start=0.0, end=1.0)
assert seg.words == []
class TestSubtitleTimeline:
def test_empty_timeline(self):
tl = SubtitleTimeline()
assert tl.segment_count == 0
assert tl.total_chars == 0
assert tl.language == "zh"
assert tl.total_duration == 0.0
def test_segment_count(self):
tl = SubtitleTimeline(
segments=[
SubtitleSegment(text="第一段", start=0.0, end=2.0),
SubtitleSegment(text="第二段", start=2.0, end=5.0),
]
)
assert tl.segment_count == 2
assert tl.total_chars == 6
def test_total_chars(self):
tl = SubtitleTimeline(
segments=[
SubtitleSegment(text="abc", start=0, end=1),
SubtitleSegment(text="defg", start=1, end=2),
]
)
assert tl.total_chars == 7
class TestMergeShortSegments:
def test_single_segment_no_change(self):
tl = SubtitleTimeline(
segments=[
SubtitleSegment(text="你好", start=0.0, end=1.0),
]
)
result = tl.merge_short_segments(min_chars=8)
assert result.segment_count == 1
assert result.segments[0].text == "你好"
def test_empty_timeline(self):
tl = SubtitleTimeline()
result = tl.merge_short_segments()
assert result.segment_count == 0
def test_merge_short_segments(self):
tl = SubtitleTimeline(
segments=[
SubtitleSegment(text="你好", start=0.0, end=1.0),
SubtitleSegment(text="今天", start=1.0, end=2.0),
SubtitleSegment(text="天气", start=2.0, end=3.0),
SubtitleSegment(text="真好", start=3.0, end=4.0),
]
)
result = tl.merge_short_segments(min_chars=4)
# 每段2字,min=4,应该每2段合并
assert result.segment_count == 2
assert result.segments[0].text == "你好今天"
assert result.segments[0].start == 0.0
assert result.segments[0].end == 2.0
assert result.segments[1].text == "天气真好"
assert result.segments[1].start == 2.0
assert result.segments[1].end == 4.0
def test_remaining_merged_to_last(self):
# 3段,每段2字,min=5 → 前5字合并,剩余1字并到最后
tl = SubtitleTimeline(
segments=[
SubtitleSegment(text="一二", start=0, end=1),
SubtitleSegment(text="三四", start=1, end=2),
SubtitleSegment(text="五", start=2, end=3),
]
)
result = tl.merge_short_segments(min_chars=5)
assert result.segment_count == 1
assert result.segments[0].text == "一二三四五"
def test_merge_with_words(self):
tl = SubtitleTimeline(
segments=[
SubtitleSegment(
text="你好",
start=0.0,
end=1.0,
words=[
SubtitleWord(text="你", start=0.0, end=0.5),
SubtitleWord(text="好", start=0.5, end=1.0),
],
),
SubtitleSegment(
text="世界",
start=1.0,
end=2.0,
words=[
SubtitleWord(text="世", start=1.0, end=1.5),
SubtitleWord(text="界", start=1.5, end=2.0),
],
),
]
)
result = tl.merge_short_segments(min_chars=10)
assert result.segment_count == 1
assert len(result.segments[0].words) == 4
class TestSplitLongSegments:
def test_short_segments_no_split(self):
tl = SubtitleTimeline(
segments=[
SubtitleSegment(text="短文本", start=0.0, end=1.0),
]
)
result = tl.split_long_segments(max_chars=20)
assert result.segment_count == 1
def test_split_by_punctuation(self):
text = "今天天气真好。我们出去玩吧!"
tl = SubtitleTimeline(
segments=[
SubtitleSegment(text=text, start=0.0, end=5.0),
]
)
result = tl.split_long_segments(max_chars=10)
assert result.segment_count >= 2
# 合并起来应该等于原文
assert "".join(s.text for s in result.segments) == text
def test_split_preserves_time_order(self):
tl = SubtitleTimeline(
segments=[
SubtitleSegment(text="一二三四五六七八九十。十一二三四五六七八九十。", start=0.0, end=10.0),
]
)
result = tl.split_long_segments(max_chars=10)
# 时间应该是递增的
for i in range(len(result.segments) - 1):
assert result.segments[i].end <= result.segments[i + 1].start + 0.001
def test_empty_timeline(self):
tl = SubtitleTimeline()
result = tl.split_long_segments()
assert result.segment_count == 0
class TestSplitTextByPunctuation:
def test_no_punctuation_short(self):
result = SubtitleTimeline._split_text_by_punctuation("你好世界", 20)
assert len(result) == 1
assert result[0] == "你好世界"
def test_sentence_end_punctuation_long_enough(self):
# 每段超过 max_chars//2 才会在句末标点断开
text = "今天天气真的非常好。明天天气也不错。"
result = SubtitleTimeline._split_text_by_punctuation(text, 10)
assert len(result) >= 2
assert "".join(result) == text
def test_short_text_with_punctuation_no_split(self):
# 文本太短(< max_chars//2),即使有标点也不断开
result = SubtitleTimeline._split_text_by_punctuation("你好。世界。", 20)
assert len(result) == 1
def test_long_text_hard_split(self):
text = "一二三四五六七八九十十一二三四五六七八九十"
result = SubtitleTimeline._split_text_by_punctuation(text, 10)
assert len(result) >= 2
assert "".join(result) == text
def test_empty_text(self):
result = SubtitleTimeline._split_text_by_punctuation("", 10)
assert result == []
class TestMergeSegments:
def test_merge_two_segments(self):
segs = [
SubtitleSegment(text="你好", start=0.0, end=1.0),
SubtitleSegment(text="世界", start=1.0, end=2.0),
]
result = SubtitleTimeline._merge_segments(segs)
assert result.text == "你好世界"
assert result.start == 0.0
assert result.end == 2.0
def test_merge_empty_list(self):
result = SubtitleTimeline._merge_segments([])
assert result.text == ""
assert result.start == 0
assert result.end == 0