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,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration tests using local GCP emulators.
|
|
3
|
+
|
|
4
|
+
These tests use real Firestore and GCS emulators running locally,
|
|
5
|
+
providing true integration testing without cloud resources or costs.
|
|
6
|
+
|
|
7
|
+
Run with: ./scripts/run-emulator-tests.sh
|
|
8
|
+
|
|
9
|
+
NOTE: These are simplified integration tests that focus on the most critical
|
|
10
|
+
paths. They mock background workers to avoid race conditions and network issues.
|
|
11
|
+
"""
|
|
12
|
+
import pytest
|
|
13
|
+
import os
|
|
14
|
+
import time
|
|
15
|
+
from unittest.mock import AsyncMock, patch
|
|
16
|
+
from datetime import datetime, UTC
|
|
17
|
+
import requests
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def emulators_running() -> bool:
|
|
21
|
+
"""Check if GCP emulators are running."""
|
|
22
|
+
try:
|
|
23
|
+
requests.get("http://127.0.0.1:8080", timeout=1)
|
|
24
|
+
requests.get("http://127.0.0.1:4443", timeout=1)
|
|
25
|
+
return True
|
|
26
|
+
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Skip all tests in this module if emulators aren't running
|
|
31
|
+
pytestmark = pytest.mark.skipif(
|
|
32
|
+
not emulators_running(),
|
|
33
|
+
reason="GCP emulators not running. Start with: scripts/start-emulators.sh"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Set emulator environment variables before any imports (use 127.0.0.1 for IPv4)
|
|
37
|
+
os.environ["FIRESTORE_EMULATOR_HOST"] = "127.0.0.1:8080"
|
|
38
|
+
os.environ["STORAGE_EMULATOR_HOST"] = "http://127.0.0.1:4443"
|
|
39
|
+
os.environ["GOOGLE_CLOUD_PROJECT"] = "test-project"
|
|
40
|
+
os.environ["GCS_BUCKET_NAME"] = "test-bucket"
|
|
41
|
+
os.environ["FIRESTORE_COLLECTION"] = "test-jobs" # Use separate collection for tests
|
|
42
|
+
os.environ["ENVIRONMENT"] = "test"
|
|
43
|
+
os.environ["ADMIN_TOKENS"] = "test-admin-token"
|
|
44
|
+
|
|
45
|
+
# Only import if emulators are running
|
|
46
|
+
if emulators_running():
|
|
47
|
+
from fastapi.testclient import TestClient
|
|
48
|
+
from backend.main import app
|
|
49
|
+
from backend.models.job import JobStatus
|
|
50
|
+
else:
|
|
51
|
+
TestClient = None
|
|
52
|
+
app = None
|
|
53
|
+
JobStatus = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.fixture(scope="module", autouse=True)
|
|
57
|
+
def setup_gcs_bucket():
|
|
58
|
+
"""Create GCS bucket in emulator before tests."""
|
|
59
|
+
try:
|
|
60
|
+
response = requests.post(
|
|
61
|
+
"http://localhost:4443/storage/v1/b",
|
|
62
|
+
json={"name": "test-bucket"},
|
|
63
|
+
params={"project": "test-project"}
|
|
64
|
+
)
|
|
65
|
+
if response.status_code in [200, 409]:
|
|
66
|
+
print(f"✅ GCS bucket 'test-bucket' ready")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"⚠️ GCS bucket setup failed: {e}")
|
|
69
|
+
yield
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.fixture(scope="module")
|
|
73
|
+
def mock_worker_service():
|
|
74
|
+
"""Mock the worker service to prevent background tasks."""
|
|
75
|
+
with patch("backend.api.routes.jobs.worker_service") as mock:
|
|
76
|
+
mock.trigger_audio_worker = AsyncMock(return_value=True)
|
|
77
|
+
mock.trigger_lyrics_worker = AsyncMock(return_value=True)
|
|
78
|
+
mock.trigger_screens_worker = AsyncMock(return_value=True)
|
|
79
|
+
mock.trigger_video_worker = AsyncMock(return_value=True)
|
|
80
|
+
yield mock
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.fixture(scope="module")
|
|
84
|
+
def client(mock_worker_service):
|
|
85
|
+
"""Create FastAPI test client with mocked workers."""
|
|
86
|
+
with patch("backend.api.routes.file_upload.worker_service", mock_worker_service):
|
|
87
|
+
return TestClient(app)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.fixture
|
|
91
|
+
def auth_headers():
|
|
92
|
+
"""Auth headers for testing."""
|
|
93
|
+
return {"Authorization": "Bearer test-admin-token"}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TestEmulatorBasics:
|
|
97
|
+
"""Basic emulator connectivity tests."""
|
|
98
|
+
|
|
99
|
+
def test_health_endpoint(self, client, auth_headers):
|
|
100
|
+
"""Test health endpoint works."""
|
|
101
|
+
response = client.get("/api/health", )
|
|
102
|
+
assert response.status_code == 200
|
|
103
|
+
assert response.json()["status"] == "healthy"
|
|
104
|
+
|
|
105
|
+
def test_root_endpoint(self, client, auth_headers):
|
|
106
|
+
"""Test root endpoint works."""
|
|
107
|
+
response = client.get("/", )
|
|
108
|
+
assert response.status_code == 200
|
|
109
|
+
assert response.json()["service"] == "karaoke-gen-backend"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestJobCreation:
|
|
113
|
+
"""Test job creation with Firestore emulator."""
|
|
114
|
+
|
|
115
|
+
def test_create_job_simple(self, client, auth_headers):
|
|
116
|
+
"""Test creating a simple job."""
|
|
117
|
+
response = client.post(
|
|
118
|
+
"/api/jobs",
|
|
119
|
+
headers=auth_headers,
|
|
120
|
+
json={"url": "https://youtube.com/watch?v=test123"}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert response.status_code == 200
|
|
124
|
+
data = response.json()
|
|
125
|
+
assert data["status"] == "success"
|
|
126
|
+
assert "job_id" in data
|
|
127
|
+
|
|
128
|
+
def test_create_job_with_metadata(self, client, auth_headers):
|
|
129
|
+
"""Test creating a job with artist/title."""
|
|
130
|
+
response = client.post(
|
|
131
|
+
"/api/jobs",
|
|
132
|
+
headers=auth_headers,
|
|
133
|
+
json={
|
|
134
|
+
"url": "https://youtube.com/watch?v=test",
|
|
135
|
+
"artist": "Test Artist",
|
|
136
|
+
"title": "Test Song"
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
assert response.status_code == 200
|
|
141
|
+
data = response.json()
|
|
142
|
+
assert "job_id" in data
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TestJobRetrieval:
|
|
146
|
+
"""Test job retrieval from Firestore emulator."""
|
|
147
|
+
|
|
148
|
+
def test_create_and_get_job(self, client, auth_headers):
|
|
149
|
+
"""Test creating and then retrieving a job."""
|
|
150
|
+
# Create
|
|
151
|
+
create_resp = client.post(
|
|
152
|
+
"/api/jobs",
|
|
153
|
+
headers=auth_headers,
|
|
154
|
+
json={
|
|
155
|
+
"url": "https://youtube.com/watch?v=abc123",
|
|
156
|
+
"artist": "Test Artist",
|
|
157
|
+
"title": "Test Song"
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
assert create_resp.status_code == 200
|
|
161
|
+
job_id = create_resp.json()["job_id"]
|
|
162
|
+
|
|
163
|
+
# Small delay for emulator consistency
|
|
164
|
+
time.sleep(0.2)
|
|
165
|
+
|
|
166
|
+
# Retrieve
|
|
167
|
+
get_resp = client.get(f"/api/jobs/{job_id}", headers=auth_headers)
|
|
168
|
+
|
|
169
|
+
if get_resp.status_code != 200:
|
|
170
|
+
print(f"GET failed: {get_resp.status_code} - {get_resp.text}")
|
|
171
|
+
|
|
172
|
+
assert get_resp.status_code == 200
|
|
173
|
+
|
|
174
|
+
job = get_resp.json()
|
|
175
|
+
assert job["job_id"] == job_id
|
|
176
|
+
assert job["status"] == "pending"
|
|
177
|
+
assert job["artist"] == "Test Artist"
|
|
178
|
+
assert job["title"] == "Test Song"
|
|
179
|
+
|
|
180
|
+
def test_get_nonexistent_job(self, client, auth_headers):
|
|
181
|
+
"""Test fetching a job that doesn't exist."""
|
|
182
|
+
response = client.get("/api/jobs/nonexistent-id", headers=auth_headers)
|
|
183
|
+
assert response.status_code == 404
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class TestJobList:
|
|
187
|
+
"""Test listing jobs from Firestore."""
|
|
188
|
+
|
|
189
|
+
def test_list_jobs(self, client, auth_headers):
|
|
190
|
+
"""Test listing jobs."""
|
|
191
|
+
# Create a few jobs
|
|
192
|
+
for i in range(3):
|
|
193
|
+
client.post(
|
|
194
|
+
"/api/jobs",
|
|
195
|
+
headers=auth_headers,
|
|
196
|
+
json={"url": f"https://youtube.com/watch?v=list{i}"}
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
time.sleep(0.2)
|
|
200
|
+
|
|
201
|
+
# List
|
|
202
|
+
response = client.get("/api/jobs", headers=auth_headers)
|
|
203
|
+
assert response.status_code == 200
|
|
204
|
+
|
|
205
|
+
jobs = response.json()
|
|
206
|
+
assert isinstance(jobs, list)
|
|
207
|
+
# We should have at least the 3 we just created
|
|
208
|
+
assert len(jobs) >= 3
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestJobDeletion:
|
|
212
|
+
"""Test job deletion."""
|
|
213
|
+
|
|
214
|
+
def test_delete_job(self, client, auth_headers):
|
|
215
|
+
"""Test deleting a job."""
|
|
216
|
+
# Create
|
|
217
|
+
create_resp = client.post(
|
|
218
|
+
"/api/jobs",
|
|
219
|
+
headers=auth_headers,
|
|
220
|
+
json={"url": "https://youtube.com/watch?v=delete-me"}
|
|
221
|
+
)
|
|
222
|
+
job_id = create_resp.json()["job_id"]
|
|
223
|
+
|
|
224
|
+
time.sleep(0.2)
|
|
225
|
+
|
|
226
|
+
# Delete
|
|
227
|
+
del_resp = client.delete(f"/api/jobs/{job_id}", headers=auth_headers)
|
|
228
|
+
assert del_resp.status_code == 200
|
|
229
|
+
|
|
230
|
+
# Verify deleted
|
|
231
|
+
get_resp = client.get(f"/api/jobs/{job_id}", headers=auth_headers)
|
|
232
|
+
assert get_resp.status_code == 404
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class TestJobUpdates:
|
|
236
|
+
"""Test job status updates."""
|
|
237
|
+
|
|
238
|
+
def test_cancel_job(self, client, auth_headers):
|
|
239
|
+
"""Test cancelling a job."""
|
|
240
|
+
# Create
|
|
241
|
+
create_resp = client.post(
|
|
242
|
+
"/api/jobs",
|
|
243
|
+
headers=auth_headers,
|
|
244
|
+
json={"url": "https://youtube.com/watch?v=cancel-me"}
|
|
245
|
+
)
|
|
246
|
+
job_id = create_resp.json()["job_id"]
|
|
247
|
+
|
|
248
|
+
time.sleep(0.2)
|
|
249
|
+
|
|
250
|
+
# Cancel
|
|
251
|
+
cancel_resp = client.post(
|
|
252
|
+
f"/api/jobs/{job_id}/cancel",
|
|
253
|
+
headers=auth_headers,
|
|
254
|
+
json={"reason": "test cancellation"}
|
|
255
|
+
)
|
|
256
|
+
assert cancel_resp.status_code == 200
|
|
257
|
+
|
|
258
|
+
# Verify cancelled
|
|
259
|
+
get_resp = client.get(f"/api/jobs/{job_id}", headers=auth_headers)
|
|
260
|
+
assert get_resp.status_code == 200
|
|
261
|
+
job = get_resp.json()
|
|
262
|
+
assert job["status"] == "cancelled"
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class TestFileUpload:
|
|
266
|
+
"""Test file upload with GCS emulator."""
|
|
267
|
+
|
|
268
|
+
def test_upload_file(self, client, auth_headers):
|
|
269
|
+
"""Test uploading a file."""
|
|
270
|
+
test_file_content = b"fake audio data for testing"
|
|
271
|
+
|
|
272
|
+
response = client.post(
|
|
273
|
+
"/api/jobs/upload",
|
|
274
|
+
headers={"Authorization": auth_headers["Authorization"]},
|
|
275
|
+
files={"file": ("test.flac", test_file_content, "audio/flac")},
|
|
276
|
+
data={"artist": "Upload Artist", "title": "Upload Song"}
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
assert response.status_code == 200
|
|
280
|
+
data = response.json()
|
|
281
|
+
assert data["status"] == "success"
|
|
282
|
+
assert "job_id" in data
|
|
283
|
+
|
|
284
|
+
job_id = data["job_id"]
|
|
285
|
+
|
|
286
|
+
time.sleep(0.2)
|
|
287
|
+
|
|
288
|
+
# Verify job created with upload data
|
|
289
|
+
get_resp = client.get(f"/api/jobs/{job_id}", headers=auth_headers)
|
|
290
|
+
assert get_resp.status_code == 200
|
|
291
|
+
job = get_resp.json()
|
|
292
|
+
assert job["artist"] == "Upload Artist"
|
|
293
|
+
assert job["title"] == "Upload Song"
|
|
294
|
+
assert "input_media_gcs_path" in job
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class TestInternalEndpoints:
|
|
298
|
+
"""Test internal worker endpoints."""
|
|
299
|
+
|
|
300
|
+
def test_internal_workers_exist(self, client, auth_headers):
|
|
301
|
+
"""Test that internal worker endpoints exist and respond."""
|
|
302
|
+
# Create a job first
|
|
303
|
+
create_resp = client.post(
|
|
304
|
+
"/api/jobs",
|
|
305
|
+
headers=auth_headers,
|
|
306
|
+
json={"url": "https://youtube.com/watch?v=worker-test"}
|
|
307
|
+
)
|
|
308
|
+
job_id = create_resp.json()["job_id"]
|
|
309
|
+
|
|
310
|
+
time.sleep(0.2)
|
|
311
|
+
|
|
312
|
+
# Test audio worker endpoint exists
|
|
313
|
+
response = client.post(
|
|
314
|
+
"/api/internal/workers/audio",
|
|
315
|
+
headers=auth_headers,
|
|
316
|
+
json={"job_id": job_id}
|
|
317
|
+
)
|
|
318
|
+
assert response.status_code == 200
|
|
319
|
+
assert response.json()["status"] == "started"
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
print("✅ Emulator integration tests ready to run")
|