"""TTS 合成 API 单元测试。""" from __future__ import annotations from datetime import datetime, timezone from unittest.mock import MagicMock import pytest from packages.application.tts_job.use_cases import ( CreateTTSJobUseCase, DeleteTTSJobUseCase, GetTTSJobStatusUseCase, GetTTSJobUseCase, ListTTSJobsUseCase, TTSJobNotFoundError, ) from packages.domain.tts_job import TTSJob, TTSJobStatus def _make_job(**kwargs) -> TTSJob: defaults = { "id": "test_job_001", "user_id": "user_001", "input_text": "测试文本", "voice_id": "voice_001", "voice_model": "", "project_id": "", "voice_clone_profile_id": "", "status": TTSJobStatus.PENDING, "output_audio_url": "", "output_audio_key": "", "duration": 0.0, "file_size": 0, "sample_rate": 22050, "format": "mp3", "error_message": "", "retry_count": 0, "max_retries": 3, "metadata": {}, "started_at": None, "completed_at": None, "created_at": datetime.now(timezone.utc), "updated_at": datetime.now(timezone.utc), } defaults.update(kwargs) return TTSJob(**defaults) class TestCreateTTSJobUseCase: """测试创建 TTS 合成任务。""" def test_create_success(self) -> None: """正常创建。""" repo = MagicMock() repo.create.side_effect = lambda j: j use_case = CreateTTSJobUseCase(repo) job = use_case.execute( user_id="user_001", input_text="你好世界", voice_id="voice_001", ) assert job.user_id == "user_001" assert job.input_text == "你好世界" assert job.status == TTSJobStatus.PENDING repo.create.assert_called_once() def test_create_with_metadata(self) -> None: """带元数据创建。""" repo = MagicMock() repo.create.side_effect = lambda j: j use_case = CreateTTSJobUseCase(repo) job = use_case.execute( user_id="user_001", input_text="测试", metadata={"source": "api"}, ) assert job.metadata == {"source": "api"} def test_create_empty_text_raises(self) -> None: """空文本应报错。""" repo = MagicMock() use_case = CreateTTSJobUseCase(repo) with pytest.raises(ValueError, match="input_text"): use_case.execute(user_id="user_001", input_text=" ") class TestListTTSJobsUseCase: """测试列出 TTS 合成任务。""" def test_list_empty(self) -> None: """空列表。""" repo = MagicMock() repo.list_by_user.return_value = [] repo.count_by_user.return_value = 0 use_case = ListTTSJobsUseCase(repo) items, total = use_case.execute("user_001") assert items == [] assert total == 0 repo.list_by_user.assert_called_once_with("user_001", status=None, limit=50, offset=0) def test_list_with_pagination(self) -> None: """分页查询。""" repo = MagicMock() jobs = [_make_job(id=f"job_{i}") for i in range(3)] repo.list_by_user.return_value = jobs repo.count_by_user.return_value = 10 use_case = ListTTSJobsUseCase(repo) items, total = use_case.execute("user_001", skip=5, limit=3) assert len(items) == 3 assert total == 10 repo.list_by_user.assert_called_once_with("user_001", status=None, limit=3, offset=5) def test_list_with_status_filter(self) -> None: """按状态过滤。""" repo = MagicMock() repo.list_by_user.return_value = [] repo.count_by_user.return_value = 0 use_case = ListTTSJobsUseCase(repo) use_case.execute("user_001", status="completed") repo.list_by_user.assert_called_once_with("user_001", status="completed", limit=50, offset=0) class TestGetTTSJobUseCase: """测试获取 TTS 任务详情。""" def test_get_success(self) -> None: """正常获取。""" job = _make_job() repo = MagicMock() repo.get.return_value = job use_case = GetTTSJobUseCase(repo) result = use_case.execute("test_job_001", "user_001") assert result.id == "test_job_001" assert result.user_id == "user_001" def test_get_not_found(self) -> None: """任务不存在。""" repo = MagicMock() repo.get.return_value = None use_case = GetTTSJobUseCase(repo) with pytest.raises(TTSJobNotFoundError): use_case.execute("nonexistent", "user_001") def test_get_wrong_user(self) -> None: """用户不匹配。""" job = _make_job(user_id="other_user") repo = MagicMock() repo.get.return_value = job use_case = GetTTSJobUseCase(repo) with pytest.raises(TTSJobNotFoundError): use_case.execute("test_job_001", "user_001") class TestGetTTSJobStatusUseCase: """测试查询 TTS 任务状态。""" def test_status_success(self) -> None: """正常查询状态。""" job = _make_job(status=TTSJobStatus.COMPLETED, output_audio_url="https://example.com/audio.mp3") repo = MagicMock() repo.get.return_value = job use_case = GetTTSJobStatusUseCase(repo) result = use_case.execute("test_job_001", "user_001") assert result.status == TTSJobStatus.COMPLETED assert result.output_audio_url == "https://example.com/audio.mp3" def test_status_not_found(self) -> None: """任务不存在。""" repo = MagicMock() repo.get.return_value = None use_case = GetTTSJobStatusUseCase(repo) with pytest.raises(TTSJobNotFoundError): use_case.execute("nonexistent", "user_001") class TestDeleteTTSJobUseCase: """测试删除 TTS 任务。""" def test_delete_success(self) -> None: """正常删除。""" job = _make_job() repo = MagicMock() repo.get.return_value = job repo.delete.return_value = True use_case = DeleteTTSJobUseCase(repo) result = use_case.execute("test_job_001", "user_001") assert result is True repo.delete.assert_called_once_with("test_job_001") def test_delete_not_found(self) -> None: """任务不存在。""" repo = MagicMock() repo.get.return_value = None use_case = DeleteTTSJobUseCase(repo) result = use_case.execute("nonexistent", "user_001") assert result is False def test_delete_wrong_user(self) -> None: """用户不匹配。""" job = _make_job(user_id="other_user") repo = MagicMock() repo.get.return_value = job use_case = DeleteTTSJobUseCase(repo) result = use_case.execute("test_job_001", "user_001") assert result is False