87cca302f4
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 0s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 13s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (push) Failing after 1s
CI/CD Pipeline / Frontend Lint (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 - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m54s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 6m25s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m40s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m56s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m56s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m40s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m23s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m44s
AI Code Review / AI Code Review (pull_request) Successful in 11m40s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + 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 / 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
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
210 lines
6.8 KiB
Python
210 lines
6.8 KiB
Python
"""统一模板 segments 数据源单元测试。
|
||
|
||
验证 template_repository 从 template_clip_configs 读取 segments,
|
||
写入走 template_clip_configs,回退兼容 template_segments。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
import uuid
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api"))
|
||
|
||
import pytest
|
||
from sqlalchemy import create_engine
|
||
from sqlalchemy.orm import sessionmaker
|
||
|
||
from packages.adapters.sqlalchemy_impl.models import (
|
||
Base,
|
||
TemplateClipConfigModel,
|
||
TemplateModel,
|
||
TemplateSegmentModel,
|
||
)
|
||
from packages.adapters.sqlalchemy_impl.template_repository import (
|
||
SQLAlchemyTemplateRepository,
|
||
)
|
||
from packages.domain.template import Template, TemplateSegment
|
||
|
||
|
||
@pytest.fixture()
|
||
def session():
|
||
engine = create_engine("sqlite:///:memory:")
|
||
Base.metadata.create_all(engine)
|
||
Session = sessionmaker(bind=engine)
|
||
s = Session()
|
||
try:
|
||
yield s
|
||
finally:
|
||
s.close()
|
||
|
||
|
||
@pytest.fixture()
|
||
def repo(session):
|
||
return SQLAlchemyTemplateRepository(session)
|
||
|
||
|
||
def _make_template(template_id=None, user_id="u1", name="测试模板", mode="one_take"):
|
||
tid = template_id or str(uuid.uuid4())
|
||
return Template(
|
||
id=tid,
|
||
user_id=user_id,
|
||
name=name,
|
||
mode=mode,
|
||
category="",
|
||
tags=[],
|
||
estimated_duration=30.0,
|
||
is_active=True,
|
||
segments=[],
|
||
)
|
||
|
||
|
||
def _make_segment(template_id, order=1, material_type=None):
|
||
return TemplateSegment(
|
||
id=str(uuid.uuid4()),
|
||
template_id=template_id,
|
||
segment_order=order,
|
||
duration_min=5.0,
|
||
duration_max=10.0,
|
||
material_type=material_type,
|
||
)
|
||
|
||
|
||
class TestCreateSegments:
|
||
def test_writes_to_clip_configs(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
seg = _make_segment(tpl.id, order=1)
|
||
repo.create_segments([seg])
|
||
clips = session.query(TemplateClipConfigModel).filter(TemplateClipConfigModel.template_id == tpl.id).all()
|
||
assert len(clips) == 1
|
||
assert clips[0].clip_type == "main"
|
||
assert clips[0].order == 1
|
||
assert clips[0].min_duration == 5.0
|
||
|
||
def test_material_type_stored_in_config(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
seg = _make_segment(tpl.id, order=1, material_type="voiceover")
|
||
repo.create_segments([seg])
|
||
clip = session.query(TemplateClipConfigModel).filter(TemplateClipConfigModel.template_id == tpl.id).first()
|
||
assert clip.config["material_type"] == "voiceover"
|
||
|
||
|
||
class TestListSegments:
|
||
def test_reads_from_clip_configs(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
seg = _make_segment(tpl.id, order=1, material_type="voiceover")
|
||
repo.create_segments([seg])
|
||
result = repo.list_segments(tpl.id)
|
||
assert len(result) == 1
|
||
assert result[0].material_type == "voiceover"
|
||
|
||
def test_fallback_to_old_table(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
old = TemplateSegmentModel(
|
||
id=str(uuid.uuid4()),
|
||
template_id=tpl.id,
|
||
segment_order=1,
|
||
duration_min=3.0,
|
||
duration_max=8.0,
|
||
material_type="场景",
|
||
)
|
||
session.add(old)
|
||
session.commit()
|
||
result = repo.list_segments(tpl.id)
|
||
assert len(result) == 1
|
||
assert result[0].material_type == "场景"
|
||
|
||
def test_clip_configs_takes_priority(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
seg = _make_segment(tpl.id, order=1)
|
||
repo.create_segments([seg])
|
||
old = TemplateSegmentModel(
|
||
id=str(uuid.uuid4()), template_id=tpl.id, segment_order=1, duration_min=1.0, duration_max=2.0
|
||
)
|
||
session.add(old)
|
||
session.commit()
|
||
result = repo.list_segments(tpl.id)
|
||
assert len(result) == 1
|
||
assert result[0].duration_min == 5.0
|
||
|
||
|
||
class TestListByUser:
|
||
def test_batch_loads_from_clip_configs(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
seg = _make_segment(tpl.id, order=1, material_type="人物")
|
||
repo.create_segments([seg])
|
||
result = repo.list_by_user("u1")
|
||
assert len(result) == 1
|
||
assert len(result[0].segments) == 1
|
||
assert result[0].segments[0].material_type == "人物"
|
||
|
||
def test_fallback_for_old_data(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
old = TemplateSegmentModel(
|
||
id=str(uuid.uuid4()), template_id=tpl.id, segment_order=1, duration_min=2.0, duration_max=6.0
|
||
)
|
||
session.add(old)
|
||
session.commit()
|
||
result = repo.list_by_user("u1")
|
||
assert len(result) == 1
|
||
assert len(result[0].segments) == 1
|
||
assert result[0].segments[0].duration_min == 2.0
|
||
|
||
|
||
class TestCopyTemplate:
|
||
def test_copy_writes_to_clip_configs(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
seg = _make_segment(tpl.id, order=1, material_type="voiceover")
|
||
repo.create_segments([seg])
|
||
copied = repo.copy_template(tpl.id, "u1", "副本模板")
|
||
assert copied.id != tpl.id
|
||
clips = session.query(TemplateClipConfigModel).filter(TemplateClipConfigModel.template_id == copied.id).all()
|
||
assert len(clips) == 1
|
||
assert clips[0].config["material_type"] == "voiceover"
|
||
|
||
def test_copy_empty_segments(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
copied = repo.copy_template(tpl.id, "u1", "空副本")
|
||
assert len(copied.segments) == 0
|
||
|
||
|
||
class TestDelete:
|
||
def test_delete_cleans_both_tables(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
seg = _make_segment(tpl.id, order=1)
|
||
repo.create_segments([seg])
|
||
old = TemplateSegmentModel(
|
||
id=str(uuid.uuid4()), template_id=tpl.id, segment_order=1, duration_min=1.0, duration_max=2.0
|
||
)
|
||
session.add(old)
|
||
session.commit()
|
||
repo.delete(tpl.id, "u1")
|
||
c1 = session.query(TemplateClipConfigModel).filter(TemplateClipConfigModel.template_id == tpl.id).count()
|
||
c2 = session.query(TemplateSegmentModel).filter(TemplateSegmentModel.template_id == tpl.id).count()
|
||
assert c1 == 0
|
||
assert c2 == 0
|
||
|
||
def test_delete_segments_by_template(self, repo, session):
|
||
tpl = _make_template()
|
||
repo.create(tpl)
|
||
seg = _make_segment(tpl.id, order=1)
|
||
repo.create_segments([seg])
|
||
old = TemplateSegmentModel(
|
||
id=str(uuid.uuid4()), template_id=tpl.id, segment_order=2, duration_min=1.0, duration_max=2.0
|
||
)
|
||
session.add(old)
|
||
session.commit()
|
||
count = repo.delete_segments_by_template(tpl.id)
|
||
assert count == 2
|