style: auto-format with black + isort + prettier
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

This commit is contained in:
CI Bot
2026-07-24 10:59:46 +00:00
parent 78afa40458
commit 68e55df83b
5 changed files with 134 additions and 100 deletions
+49 -42
View File
@@ -112,32 +112,44 @@ class TestDuplicateSegmentCreate:
def test_create_similarity_boundary_values(self):
# 0 和 100 都是合法的
seg0 = DuplicateSegment.create(
source_start=0.0, source_end=1.0,
matched_video_id="v1", matched_video_name="v",
matched_start=0.0, matched_end=1.0,
source_start=0.0,
source_end=1.0,
matched_video_id="v1",
matched_video_name="v",
matched_start=0.0,
matched_end=1.0,
similarity=0.0,
)
assert seg0.similarity == 0.0
seg100 = DuplicateSegment.create(
source_start=0.0, source_end=1.0,
matched_video_id="v1", matched_video_name="v",
matched_start=0.0, matched_end=1.0,
source_start=0.0,
source_end=1.0,
matched_video_id="v1",
matched_video_name="v",
matched_start=0.0,
matched_end=1.0,
similarity=100.0,
)
assert seg100.similarity == 100.0
def test_create_unique_ids(self):
seg1 = DuplicateSegment.create(
source_start=0.0, source_end=1.0,
matched_video_id="v1", matched_video_name="v",
matched_start=0.0, matched_end=1.0,
source_start=0.0,
source_end=1.0,
matched_video_id="v1",
matched_video_name="v",
matched_start=0.0,
matched_end=1.0,
similarity=50.0,
)
seg2 = DuplicateSegment.create(
source_start=0.0, source_end=1.0,
matched_video_id="v1", matched_video_name="v",
matched_start=0.0, matched_end=1.0,
source_start=0.0,
source_end=1.0,
matched_video_id="v1",
matched_video_name="v",
matched_start=0.0,
matched_end=1.0,
similarity=50.0,
)
assert seg1.id != seg2.id
@@ -213,20 +225,19 @@ class TestDuplicationRecordCreate:
class TestDuplicationRecordStatus:
def test_mark_processing(self):
record = DuplicationRecord.create(
user_id="u1", filename="t.mp4", file_size=100, storage_key="k1"
)
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k1")
record.mark_processing()
assert record.status == "processing"
def test_mark_completed(self):
record = DuplicationRecord.create(
user_id="u1", filename="t.mp4", file_size=100, storage_key="k1"
)
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k1")
seg = DuplicateSegment.create(
source_start=0.0, source_end=5.0,
matched_video_id="v1", matched_video_name="v",
matched_start=0.0, matched_end=5.0,
source_start=0.0,
source_end=5.0,
matched_video_id="v1",
matched_video_name="v",
matched_start=0.0,
matched_end=5.0,
similarity=90.0,
)
record.mark_completed(duplicate_rate=25.5, duplicate_count=3, segments=[seg])
@@ -236,26 +247,20 @@ class TestDuplicationRecordStatus:
assert len(record.segments) == 1
def test_mark_completed_invalid_rate_raises(self):
record = DuplicationRecord.create(
user_id="u1", filename="t.mp4", file_size=100, storage_key="k1"
)
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k1")
with pytest.raises(ValueError, match="duplicate_rate must be between 0 and 100"):
record.mark_completed(duplicate_rate=-1, duplicate_count=0, segments=[])
with pytest.raises(ValueError, match="duplicate_rate must be between 0 and 100"):
record.mark_completed(duplicate_rate=101, duplicate_count=0, segments=[])
def test_mark_failed(self):
record = DuplicationRecord.create(
user_id="u1", filename="t.mp4", file_size=100, storage_key="k1"
)
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k1")
record.mark_failed("网络超时")
assert record.status == "failed"
assert record.error_message == "网络超时"
def test_can_retry_only_failed(self):
record = DuplicationRecord.create(
user_id="u1", filename="t.mp4", file_size=100, storage_key="k1"
)
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k1")
assert record.can_retry() is False # pending
record.mark_processing()
@@ -265,25 +270,27 @@ class TestDuplicationRecordStatus:
assert record.can_retry() is True # failed
seg = DuplicateSegment.create(
source_start=0.0, source_end=1.0,
matched_video_id="v1", matched_video_name="v",
matched_start=0.0, matched_end=1.0,
source_start=0.0,
source_end=1.0,
matched_video_id="v1",
matched_video_name="v",
matched_start=0.0,
matched_end=1.0,
similarity=50.0,
)
record2 = DuplicationRecord.create(
user_id="u1", filename="t.mp4", file_size=100, storage_key="k1"
)
record2 = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k1")
record2.mark_completed(10.0, 1, [seg])
assert record2.can_retry() is False # completed
def test_reset_for_retry(self):
record = DuplicationRecord.create(
user_id="u1", filename="t.mp4", file_size=100, storage_key="k1"
)
record = DuplicationRecord.create(user_id="u1", filename="t.mp4", file_size=100, storage_key="k1")
seg = DuplicateSegment.create(
source_start=0.0, source_end=1.0,
matched_video_id="v1", matched_video_name="v",
matched_start=0.0, matched_end=1.0,
source_start=0.0,
source_end=1.0,
matched_video_id="v1",
matched_video_name="v",
matched_start=0.0,
matched_end=1.0,
similarity=50.0,
)
record.mark_completed(50.0, 2, [seg])
+2 -6
View File
@@ -140,15 +140,11 @@ class TestEditPlanClipStateTransitions:
class TestEditPlanClipProperties:
def test_end_time(self):
clip = EditPlanClip.create(
plan_id="p1", clip_type="v", order=1, start_time=10.0, duration=5.0
)
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, start_time=10.0, duration=5.0)
assert clip.end_time == 15.0
def test_end_time_zero_duration(self):
clip = EditPlanClip.create(
plan_id="p1", clip_type="v", order=1, start_time=5.0, duration=0.0
)
clip = EditPlanClip.create(plan_id="p1", clip_type="v", order=1, start_time=5.0, duration=0.0)
assert clip.end_time == 5.0
def test_has_asset_true(self):
+70 -42
View File
@@ -2,7 +2,7 @@
import pytest
from packages.domain.subtitle import SubtitleWord, SubtitleSegment, SubtitleTimeline
from packages.domain.subtitle import SubtitleSegment, SubtitleTimeline, SubtitleWord
class TestSubtitleWord:
@@ -46,26 +46,32 @@ class TestSubtitleTimeline:
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),
])
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),
])
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),
])
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 == "你好"
@@ -76,12 +82,14 @@ class TestMergeShortSegments:
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),
])
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
@@ -94,26 +102,40 @@ class TestMergeShortSegments:
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),
])
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),
]),
])
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
@@ -121,26 +143,32 @@ class TestMergeShortSegments:
class TestSplitLongSegments:
def test_short_segments_no_split(self):
tl = SubtitleTimeline(segments=[
SubtitleSegment(text="短文本", start=0.0, end=1.0),
])
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),
])
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),
])
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):
+12 -10
View File
@@ -40,16 +40,18 @@ class TestTtsConfigParse:
assert config.speed == 1.0
def test_parse_enabled_true_with_all_fields(self):
config = TtsConfig.parse({
"enabled": True,
"voice_id": "female_warm",
"speed": 1.5,
"pitch": 2.0,
"volume": 0.9,
"text": "你好世界",
"align_mode": "subtitle",
"overlap_mode": "mix",
})
config = TtsConfig.parse(
{
"enabled": True,
"voice_id": "female_warm",
"speed": 1.5,
"pitch": 2.0,
"volume": 0.9,
"text": "你好世界",
"align_mode": "subtitle",
"overlap_mode": "mix",
}
)
assert config.enabled is True
assert config.voice_id == "female_warm"
assert config.speed == 1.5
+1
View File
@@ -64,6 +64,7 @@ class TestVerificationCodeStatus:
code = VerificationCode.create(recipient="test@example.com", code_type="email_bind", ttl_seconds=0)
# 0秒TTL,立即可能过期(有极小概率因时间差没过)
import time
time.sleep(0.01)
assert code.is_expired is True
assert code.is_valid is False