"""统一模板 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