karaoke-gen 0.90.1__py3-none-any.whl → 0.99.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backend/.coveragerc +20 -0
- backend/.gitignore +37 -0
- backend/Dockerfile +43 -0
- backend/Dockerfile.base +74 -0
- backend/README.md +242 -0
- backend/__init__.py +0 -0
- backend/api/__init__.py +0 -0
- backend/api/dependencies.py +457 -0
- backend/api/routes/__init__.py +0 -0
- backend/api/routes/admin.py +835 -0
- backend/api/routes/audio_search.py +913 -0
- backend/api/routes/auth.py +348 -0
- backend/api/routes/file_upload.py +2112 -0
- backend/api/routes/health.py +409 -0
- backend/api/routes/internal.py +435 -0
- backend/api/routes/jobs.py +1629 -0
- backend/api/routes/review.py +652 -0
- backend/api/routes/themes.py +162 -0
- backend/api/routes/users.py +1513 -0
- backend/config.py +172 -0
- backend/main.py +157 -0
- backend/middleware/__init__.py +5 -0
- backend/middleware/audit_logging.py +124 -0
- backend/models/__init__.py +0 -0
- backend/models/job.py +519 -0
- backend/models/requests.py +123 -0
- backend/models/theme.py +153 -0
- backend/models/user.py +254 -0
- backend/models/worker_log.py +164 -0
- backend/pyproject.toml +29 -0
- backend/quick-check.sh +93 -0
- backend/requirements.txt +29 -0
- backend/run_tests.sh +60 -0
- backend/services/__init__.py +0 -0
- backend/services/audio_analysis_service.py +243 -0
- backend/services/audio_editing_service.py +278 -0
- backend/services/audio_search_service.py +702 -0
- backend/services/auth_service.py +630 -0
- backend/services/credential_manager.py +792 -0
- backend/services/discord_service.py +172 -0
- backend/services/dropbox_service.py +301 -0
- backend/services/email_service.py +1093 -0
- backend/services/encoding_interface.py +454 -0
- backend/services/encoding_service.py +502 -0
- backend/services/firestore_service.py +512 -0
- backend/services/flacfetch_client.py +573 -0
- backend/services/gce_encoding/README.md +72 -0
- backend/services/gce_encoding/__init__.py +22 -0
- backend/services/gce_encoding/main.py +589 -0
- backend/services/gce_encoding/requirements.txt +16 -0
- backend/services/gdrive_service.py +356 -0
- backend/services/job_logging.py +258 -0
- backend/services/job_manager.py +853 -0
- backend/services/job_notification_service.py +271 -0
- backend/services/langfuse_preloader.py +98 -0
- backend/services/local_encoding_service.py +590 -0
- backend/services/local_preview_encoding_service.py +407 -0
- backend/services/lyrics_cache_service.py +216 -0
- backend/services/metrics.py +413 -0
- backend/services/nltk_preloader.py +122 -0
- backend/services/packaging_service.py +287 -0
- backend/services/rclone_service.py +106 -0
- backend/services/spacy_preloader.py +65 -0
- backend/services/storage_service.py +209 -0
- backend/services/stripe_service.py +371 -0
- backend/services/structured_logging.py +254 -0
- backend/services/template_service.py +330 -0
- backend/services/theme_service.py +469 -0
- backend/services/tracing.py +543 -0
- backend/services/user_service.py +721 -0
- backend/services/worker_service.py +558 -0
- backend/services/youtube_service.py +112 -0
- backend/services/youtube_upload_service.py +445 -0
- backend/tests/__init__.py +4 -0
- backend/tests/conftest.py +224 -0
- backend/tests/emulator/__init__.py +7 -0
- backend/tests/emulator/conftest.py +109 -0
- backend/tests/emulator/test_e2e_cli_backend.py +1053 -0
- backend/tests/emulator/test_emulator_integration.py +356 -0
- backend/tests/emulator/test_style_loading_direct.py +436 -0
- backend/tests/emulator/test_worker_logs_direct.py +229 -0
- backend/tests/emulator/test_worker_logs_subcollection.py +443 -0
- backend/tests/requirements-test.txt +10 -0
- backend/tests/requirements.txt +6 -0
- backend/tests/test_admin_email_endpoints.py +411 -0
- backend/tests/test_api_integration.py +460 -0
- backend/tests/test_api_routes.py +93 -0
- backend/tests/test_audio_analysis_service.py +294 -0
- backend/tests/test_audio_editing_service.py +386 -0
- backend/tests/test_audio_search.py +1398 -0
- backend/tests/test_audio_services.py +378 -0
- backend/tests/test_auth_firestore.py +231 -0
- backend/tests/test_config_extended.py +68 -0
- backend/tests/test_credential_manager.py +377 -0
- backend/tests/test_dependencies.py +54 -0
- backend/tests/test_discord_service.py +244 -0
- backend/tests/test_distribution_services.py +820 -0
- backend/tests/test_dropbox_service.py +472 -0
- backend/tests/test_email_service.py +492 -0
- backend/tests/test_emulator_integration.py +322 -0
- backend/tests/test_encoding_interface.py +412 -0
- backend/tests/test_file_upload.py +1739 -0
- backend/tests/test_flacfetch_client.py +632 -0
- backend/tests/test_gdrive_service.py +524 -0
- backend/tests/test_instrumental_api.py +431 -0
- backend/tests/test_internal_api.py +343 -0
- backend/tests/test_job_creation_regression.py +583 -0
- backend/tests/test_job_manager.py +356 -0
- backend/tests/test_job_manager_notifications.py +329 -0
- backend/tests/test_job_notification_service.py +443 -0
- backend/tests/test_jobs_api.py +283 -0
- backend/tests/test_local_encoding_service.py +423 -0
- backend/tests/test_local_preview_encoding_service.py +567 -0
- backend/tests/test_main.py +87 -0
- backend/tests/test_models.py +918 -0
- backend/tests/test_packaging_service.py +382 -0
- backend/tests/test_requests.py +201 -0
- backend/tests/test_routes_jobs.py +282 -0
- backend/tests/test_routes_review.py +337 -0
- backend/tests/test_services.py +556 -0
- backend/tests/test_services_extended.py +112 -0
- backend/tests/test_spacy_preloader.py +119 -0
- backend/tests/test_storage_service.py +448 -0
- backend/tests/test_style_upload.py +261 -0
- backend/tests/test_template_service.py +295 -0
- backend/tests/test_theme_service.py +516 -0
- backend/tests/test_unicode_sanitization.py +522 -0
- backend/tests/test_upload_api.py +256 -0
- backend/tests/test_validate.py +156 -0
- backend/tests/test_video_worker_orchestrator.py +847 -0
- backend/tests/test_worker_log_subcollection.py +509 -0
- backend/tests/test_worker_logging.py +365 -0
- backend/tests/test_workers.py +1116 -0
- backend/tests/test_workers_extended.py +178 -0
- backend/tests/test_youtube_service.py +247 -0
- backend/tests/test_youtube_upload_service.py +568 -0
- backend/utils/test_data.py +27 -0
- backend/validate.py +173 -0
- backend/version.py +27 -0
- backend/workers/README.md +597 -0
- backend/workers/__init__.py +11 -0
- backend/workers/audio_worker.py +618 -0
- backend/workers/lyrics_worker.py +683 -0
- backend/workers/render_video_worker.py +483 -0
- backend/workers/screens_worker.py +535 -0
- backend/workers/style_helper.py +198 -0
- backend/workers/video_worker.py +1277 -0
- backend/workers/video_worker_orchestrator.py +701 -0
- backend/workers/worker_logging.py +278 -0
- karaoke_gen/instrumental_review/static/index.html +7 -4
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +6 -1
- karaoke_gen/utils/__init__.py +163 -8
- karaoke_gen/video_background_processor.py +9 -4
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/METADATA +1 -1
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/RECORD +196 -46
- lyrics_transcriber/correction/agentic/agent.py +17 -6
- lyrics_transcriber/correction/agentic/providers/config.py +9 -5
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +96 -93
- lyrics_transcriber/correction/agentic/providers/model_factory.py +27 -6
- lyrics_transcriber/correction/anchor_sequence.py +151 -37
- lyrics_transcriber/correction/corrector.py +192 -130
- lyrics_transcriber/correction/handlers/syllables_match.py +44 -2
- lyrics_transcriber/correction/operations.py +24 -9
- lyrics_transcriber/correction/phrase_analyzer.py +18 -0
- lyrics_transcriber/frontend/package-lock.json +2 -2
- lyrics_transcriber/frontend/package.json +1 -1
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +1 -1
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +11 -7
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +31 -5
- lyrics_transcriber/frontend/src/components/EditModal.tsx +28 -10
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +123 -27
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +112 -60
- lyrics_transcriber/frontend/src/components/Header.tsx +90 -76
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +53 -31
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +44 -13
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +66 -50
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +124 -30
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +1 -1
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +12 -5
- lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +3 -3
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +1 -1
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +11 -7
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +4 -2
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +103 -1
- lyrics_transcriber/frontend/src/theme.ts +42 -15
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/frontend/vite.config.js +5 -0
- lyrics_transcriber/frontend/web_assets/assets/{index-BECn1o8Q.js → index-BSMgOq4Z.js} +6959 -5782
- lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/index.html +6 -2
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.svg +5 -0
- lyrics_transcriber/output/generator.py +17 -3
- lyrics_transcriber/output/video.py +60 -95
- lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +0 -1
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for jobs.py API routes using FastAPI TestClient.
|
|
3
|
+
|
|
4
|
+
These tests mock the underlying services and test the route logic directly.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
from datetime import datetime, UTC
|
|
8
|
+
from unittest.mock import MagicMock, AsyncMock, patch
|
|
9
|
+
from fastapi.testclient import TestClient
|
|
10
|
+
|
|
11
|
+
from backend.models.job import Job, JobStatus
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def mock_job():
|
|
16
|
+
"""Create a standard mock job for testing."""
|
|
17
|
+
return Job(
|
|
18
|
+
job_id="test123",
|
|
19
|
+
status=JobStatus.PENDING,
|
|
20
|
+
created_at=datetime.now(UTC),
|
|
21
|
+
updated_at=datetime.now(UTC),
|
|
22
|
+
artist="Test Artist",
|
|
23
|
+
title="Test Song"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def mock_job_manager(mock_job):
|
|
29
|
+
"""Create a mock JobManager with common methods."""
|
|
30
|
+
manager = MagicMock()
|
|
31
|
+
manager.get_job.return_value = mock_job
|
|
32
|
+
manager.list_jobs.return_value = [mock_job]
|
|
33
|
+
manager.create_job.return_value = mock_job
|
|
34
|
+
manager.delete_job.return_value = True
|
|
35
|
+
manager.cancel_job.return_value = mock_job
|
|
36
|
+
manager.update_job_status.return_value = None
|
|
37
|
+
manager.update_state_data.return_value = None
|
|
38
|
+
return manager
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def mock_worker_service():
|
|
43
|
+
"""Create a mock WorkerService."""
|
|
44
|
+
service = MagicMock()
|
|
45
|
+
service.trigger_audio_worker = AsyncMock(return_value=True)
|
|
46
|
+
service.trigger_lyrics_worker = AsyncMock(return_value=True)
|
|
47
|
+
service.trigger_screens_worker = AsyncMock(return_value=True)
|
|
48
|
+
service.trigger_video_worker = AsyncMock(return_value=True)
|
|
49
|
+
return service
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
def mock_theme_service():
|
|
54
|
+
"""Create a mock ThemeService that returns 'nomad' as default theme."""
|
|
55
|
+
service = MagicMock()
|
|
56
|
+
service.get_default_theme_id.return_value = "nomad"
|
|
57
|
+
service.get_theme.return_value = None
|
|
58
|
+
return service
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.fixture
|
|
62
|
+
def client(mock_job_manager, mock_worker_service, mock_theme_service):
|
|
63
|
+
"""Create TestClient with mocked dependencies."""
|
|
64
|
+
mock_creds = MagicMock()
|
|
65
|
+
mock_creds.universe_domain = 'googleapis.com'
|
|
66
|
+
|
|
67
|
+
# Create a JobManager class that returns our mock instance
|
|
68
|
+
def mock_job_manager_factory(*args, **kwargs):
|
|
69
|
+
return mock_job_manager
|
|
70
|
+
|
|
71
|
+
# Patch at the module level where jobs.py imports them
|
|
72
|
+
# Also patch JobManager class used in dependencies.py for auth checks
|
|
73
|
+
with patch('backend.api.routes.jobs.job_manager', mock_job_manager), \
|
|
74
|
+
patch('backend.api.routes.jobs.worker_service', mock_worker_service), \
|
|
75
|
+
patch('backend.api.routes.jobs.get_theme_service', return_value=mock_theme_service), \
|
|
76
|
+
patch('backend.services.job_manager.JobManager', mock_job_manager_factory), \
|
|
77
|
+
patch('backend.services.firestore_service.firestore'), \
|
|
78
|
+
patch('backend.services.storage_service.storage'), \
|
|
79
|
+
patch('google.auth.default', return_value=(mock_creds, 'test-project')):
|
|
80
|
+
from backend.main import app
|
|
81
|
+
yield TestClient(app)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestGetJob:
|
|
85
|
+
"""Tests for GET /api/jobs/{job_id}."""
|
|
86
|
+
|
|
87
|
+
def test_get_job_returns_200(self, client, mock_job_manager, mock_job, auth_headers):
|
|
88
|
+
"""Test getting an existing job returns 200."""
|
|
89
|
+
response = client.get("/api/jobs/test123", headers=auth_headers)
|
|
90
|
+
assert response.status_code == 200
|
|
91
|
+
|
|
92
|
+
def test_get_job_returns_job_data(self, client, mock_job_manager, mock_job, auth_headers):
|
|
93
|
+
"""Test response contains job data."""
|
|
94
|
+
response = client.get("/api/jobs/test123", headers=auth_headers)
|
|
95
|
+
data = response.json()
|
|
96
|
+
assert data["job_id"] == "test123"
|
|
97
|
+
assert data["status"] == "pending"
|
|
98
|
+
assert data["artist"] == "Test Artist"
|
|
99
|
+
|
|
100
|
+
def test_get_nonexistent_job_returns_404(self, mock_worker_service, auth_headers):
|
|
101
|
+
"""Test getting non-existent job returns 404."""
|
|
102
|
+
mock_job_manager = MagicMock()
|
|
103
|
+
mock_job_manager.get_job.return_value = None
|
|
104
|
+
mock_creds = MagicMock()
|
|
105
|
+
mock_creds.universe_domain = 'googleapis.com'
|
|
106
|
+
|
|
107
|
+
def mock_job_manager_factory(*args, **kwargs):
|
|
108
|
+
return mock_job_manager
|
|
109
|
+
|
|
110
|
+
with patch('backend.api.routes.jobs.job_manager', mock_job_manager), \
|
|
111
|
+
patch('backend.api.routes.jobs.worker_service', mock_worker_service), \
|
|
112
|
+
patch('backend.services.job_manager.JobManager', mock_job_manager_factory), \
|
|
113
|
+
patch('backend.services.firestore_service.firestore'), \
|
|
114
|
+
patch('backend.services.storage_service.storage'), \
|
|
115
|
+
patch('google.auth.default', return_value=(mock_creds, 'test-project')):
|
|
116
|
+
from backend.main import app
|
|
117
|
+
client = TestClient(app)
|
|
118
|
+
response = client.get("/api/jobs/nonexistent", headers=auth_headers)
|
|
119
|
+
assert response.status_code == 404
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class TestListJobs:
|
|
123
|
+
"""Tests for GET /api/jobs."""
|
|
124
|
+
|
|
125
|
+
def test_list_jobs_returns_200(self, client, auth_headers):
|
|
126
|
+
"""Test listing jobs returns 200."""
|
|
127
|
+
response = client.get("/api/jobs", headers=auth_headers)
|
|
128
|
+
assert response.status_code == 200
|
|
129
|
+
|
|
130
|
+
def test_list_jobs_returns_array(self, client, mock_job_manager, auth_headers):
|
|
131
|
+
"""Test response is an array of jobs."""
|
|
132
|
+
response = client.get("/api/jobs", headers=auth_headers)
|
|
133
|
+
data = response.json()
|
|
134
|
+
assert isinstance(data, list)
|
|
135
|
+
assert len(data) == 1
|
|
136
|
+
assert data[0]["job_id"] == "test123"
|
|
137
|
+
|
|
138
|
+
def test_list_jobs_with_status_filter(self, client, mock_job_manager, auth_headers):
|
|
139
|
+
"""Test listing jobs with status filter."""
|
|
140
|
+
response = client.get("/api/jobs?status=pending", headers=auth_headers)
|
|
141
|
+
assert response.status_code == 200
|
|
142
|
+
|
|
143
|
+
def test_list_jobs_with_limit(self, client, mock_job_manager, auth_headers):
|
|
144
|
+
"""Test listing jobs with limit."""
|
|
145
|
+
response = client.get("/api/jobs?limit=10", headers=auth_headers)
|
|
146
|
+
assert response.status_code == 200
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestCreateJob:
|
|
150
|
+
"""Tests for POST /api/jobs."""
|
|
151
|
+
|
|
152
|
+
def test_create_job_with_url_returns_200(self, client, mock_job_manager, auth_headers):
|
|
153
|
+
"""Test creating job with URL returns 200."""
|
|
154
|
+
response = client.post(
|
|
155
|
+
"/api/jobs",
|
|
156
|
+
json={"url": "https://youtube.com/watch?v=test123"},
|
|
157
|
+
headers=auth_headers
|
|
158
|
+
)
|
|
159
|
+
assert response.status_code == 200
|
|
160
|
+
|
|
161
|
+
def test_create_job_returns_job_id(self, client, mock_job_manager, auth_headers):
|
|
162
|
+
"""Test create response contains job_id."""
|
|
163
|
+
response = client.post(
|
|
164
|
+
"/api/jobs",
|
|
165
|
+
json={"url": "https://youtube.com/watch?v=test"},
|
|
166
|
+
headers=auth_headers
|
|
167
|
+
)
|
|
168
|
+
data = response.json()
|
|
169
|
+
assert "job_id" in data
|
|
170
|
+
|
|
171
|
+
def test_create_job_with_artist_title(self, client, mock_job_manager, auth_headers):
|
|
172
|
+
"""Test creating job with artist and title."""
|
|
173
|
+
response = client.post(
|
|
174
|
+
"/api/jobs",
|
|
175
|
+
json={
|
|
176
|
+
"url": "https://youtube.com/watch?v=test",
|
|
177
|
+
"artist": "Test Artist",
|
|
178
|
+
"title": "Test Song"
|
|
179
|
+
},
|
|
180
|
+
headers=auth_headers
|
|
181
|
+
)
|
|
182
|
+
assert response.status_code == 200
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class TestDeleteJob:
|
|
186
|
+
"""Tests for DELETE /api/jobs/{job_id}."""
|
|
187
|
+
|
|
188
|
+
def test_delete_job_returns_200(self, client, mock_job_manager, auth_headers):
|
|
189
|
+
"""Test deleting job returns 200."""
|
|
190
|
+
response = client.delete("/api/jobs/test123", headers=auth_headers)
|
|
191
|
+
assert response.status_code == 200
|
|
192
|
+
|
|
193
|
+
def test_delete_nonexistent_job(self, mock_worker_service, auth_headers):
|
|
194
|
+
"""Test deleting non-existent job."""
|
|
195
|
+
mock_job_manager = MagicMock()
|
|
196
|
+
mock_job_manager.get_job.return_value = None # Job doesn't exist
|
|
197
|
+
mock_job_manager.delete_job.return_value = False
|
|
198
|
+
mock_creds = MagicMock()
|
|
199
|
+
mock_creds.universe_domain = 'googleapis.com'
|
|
200
|
+
|
|
201
|
+
def mock_job_manager_factory(*args, **kwargs):
|
|
202
|
+
return mock_job_manager
|
|
203
|
+
|
|
204
|
+
with patch('backend.api.routes.jobs.job_manager', mock_job_manager), \
|
|
205
|
+
patch('backend.api.routes.jobs.worker_service', mock_worker_service), \
|
|
206
|
+
patch('backend.services.job_manager.JobManager', mock_job_manager_factory), \
|
|
207
|
+
patch('backend.services.firestore_service.firestore'), \
|
|
208
|
+
patch('backend.services.storage_service.storage'), \
|
|
209
|
+
patch('google.auth.default', return_value=(mock_creds, 'test-project')):
|
|
210
|
+
from backend.main import app
|
|
211
|
+
client = TestClient(app)
|
|
212
|
+
response = client.delete("/api/jobs/nonexistent", headers=auth_headers)
|
|
213
|
+
# Either 200 or 404 depending on implementation
|
|
214
|
+
assert response.status_code in [200, 404]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class TestCancelJob:
|
|
218
|
+
"""Tests for POST /api/jobs/{job_id}/cancel."""
|
|
219
|
+
|
|
220
|
+
def test_cancel_job_returns_200(self, client, mock_job_manager, mock_job, auth_headers):
|
|
221
|
+
"""Test cancelling job returns 200."""
|
|
222
|
+
mock_job_manager.cancel_job.return_value = mock_job
|
|
223
|
+
response = client.post(
|
|
224
|
+
"/api/jobs/test123/cancel",
|
|
225
|
+
json={},
|
|
226
|
+
headers=auth_headers
|
|
227
|
+
)
|
|
228
|
+
assert response.status_code == 200
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestSubmitCorrections:
|
|
232
|
+
"""Tests for POST /api/jobs/{job_id}/corrections."""
|
|
233
|
+
|
|
234
|
+
def test_submit_corrections_returns_200(self, client, mock_job_manager, mock_job, auth_headers):
|
|
235
|
+
"""Test submitting corrections returns 200."""
|
|
236
|
+
# Job needs to be in AWAITING_REVIEW or IN_REVIEW status
|
|
237
|
+
review_job = Job(
|
|
238
|
+
job_id="test123",
|
|
239
|
+
status=JobStatus.IN_REVIEW,
|
|
240
|
+
created_at=datetime.now(UTC),
|
|
241
|
+
updated_at=datetime.now(UTC),
|
|
242
|
+
artist="Test",
|
|
243
|
+
title="Test"
|
|
244
|
+
)
|
|
245
|
+
mock_job_manager.get_job.return_value = review_job
|
|
246
|
+
|
|
247
|
+
response = client.post(
|
|
248
|
+
"/api/jobs/test123/corrections",
|
|
249
|
+
json={
|
|
250
|
+
"corrections": {
|
|
251
|
+
"lines": [],
|
|
252
|
+
"metadata": {"source": "test"}
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
headers=auth_headers
|
|
256
|
+
)
|
|
257
|
+
# Should be 200 or validation error
|
|
258
|
+
assert response.status_code in [200, 400, 422]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class TestSelectInstrumental:
|
|
262
|
+
"""Tests for POST /api/jobs/{job_id}/select-instrumental."""
|
|
263
|
+
|
|
264
|
+
def test_select_instrumental_requires_selection_field(self, client, mock_job_manager, mock_job, auth_headers):
|
|
265
|
+
"""Test instrumental selection requires selection field."""
|
|
266
|
+
response = client.post(
|
|
267
|
+
"/api/jobs/test123/select-instrumental",
|
|
268
|
+
json={}, # Missing selection field
|
|
269
|
+
headers=auth_headers
|
|
270
|
+
)
|
|
271
|
+
# Missing field should cause validation error
|
|
272
|
+
assert response.status_code == 422
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class TestStartReview:
|
|
276
|
+
"""Tests for POST /api/jobs/{job_id}/start-review."""
|
|
277
|
+
|
|
278
|
+
def test_start_review_endpoint_exists(self, client, mock_job_manager, mock_job, auth_headers):
|
|
279
|
+
"""Test start review endpoint exists."""
|
|
280
|
+
response = client.post("/api/jobs/test123/start-review", headers=auth_headers)
|
|
281
|
+
# Should not be 404 or 405
|
|
282
|
+
assert response.status_code not in [404, 405]
|
|
283
|
+
|