Spaces:
Running
Running
| from __future__ import annotations | |
| import numpy as np | |
| from fastapi.testclient import TestClient | |
| from openmusic_analysis.api import create_app | |
| from openmusic_analysis.settings import Settings, TemporalAudioConfig | |
| from .conftest import FailingAudioEncoder, make_service | |
| def client_for(service) -> TestClient: | |
| return TestClient(create_app(service=service, settings=Settings())) | |
| def upload(content: bytes = b"valid fake audio"): | |
| return {"audio": ("track.mp3", content, "audio/mpeg")} | |
| def test_valid_audio_only_request(service_bundle): | |
| service, _, _ = service_bundle | |
| response = client_for(service).post("/v1/tracks/analyze", files=upload()) | |
| assert response.status_code == 200 | |
| assert set(response.json()["representations"]) == {"audio.global", "audio.temporal"} | |
| def test_audio_and_multilingual_lyrics(service_bundle): | |
| service, _, _ = service_bundle | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", | |
| files=upload(), | |
| data={"lyrics": "[Verse]\nHello world\n\n[Припев]\nПривет, мир!"}, | |
| ) | |
| assert response.status_code == 200 | |
| assert "lyrics.global" in response.json()["representations"] | |
| def test_requested_representations_avoid_unrequested_work(service_bundle): | |
| service, decoder, encoder = service_bundle | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", | |
| files=upload(), | |
| data={ | |
| "lyrics": "lyrics that must not be encoded", | |
| "requested_representations": "audio.global", | |
| }, | |
| ) | |
| assert response.status_code == 200 | |
| assert list(response.json()["representations"]) == ["audio.global"] | |
| assert decoder.calls == 1 | |
| assert len(encoder.calls) == 1 | |
| def test_invalid_file_returns_structured_decode_error(service_bundle): | |
| service, _, _ = service_bundle | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", files=upload(b"bad bytes") | |
| ) | |
| assert response.status_code == 422 | |
| assert response.json()["error"]["code"] == "AUDIO_DECODE_FAILED" | |
| def test_missing_audio_is_structured(service_bundle): | |
| service, _, _ = service_bundle | |
| response = client_for(service).post("/v1/tracks/analyze", data={"lyrics": "text"}) | |
| assert response.status_code == 422 | |
| assert response.json()["error"]["code"] == "MISSING_AUDIO" | |
| def test_unsupported_representation(service_bundle): | |
| service, _, _ = service_bundle | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", | |
| files=upload(), | |
| data={"requested_representations": "audio.emotion"}, | |
| ) | |
| assert response.status_code == 422 | |
| assert response.json()["error"]["code"] == "UNSUPPORTED_REPRESENTATION" | |
| def test_unsupported_extension(service_bundle): | |
| service, _, _ = service_bundle | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", files={"audio": ("track.txt", b"data", "text/plain")} | |
| ) | |
| assert response.status_code == 415 | |
| assert response.json()["error"]["code"] == "UNSUPPORTED_AUDIO_FORMAT" | |
| def test_explicit_lyrics_representation_requires_lyrics(service_bundle): | |
| service, _, _ = service_bundle | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", | |
| files=upload(), | |
| data={"requested_representations": "lyrics.global"}, | |
| ) | |
| assert response.status_code == 422 | |
| assert response.json()["error"]["code"] == "LYRICS_REQUIRED" | |
| def test_invalid_empty_lyrics(service_bundle): | |
| service, _, _ = service_bundle | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", files=upload(), data={"lyrics": " \n "} | |
| ) | |
| assert response.status_code == 422 | |
| assert response.json()["error"]["code"] == "INVALID_LYRICS" | |
| def test_model_failure_does_not_leak_internal_exception(): | |
| service, _, _ = make_service(audio_encoder=FailingAudioEncoder()) | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", | |
| files=upload(), | |
| data={"requested_representations": "audio.global"}, | |
| ) | |
| assert response.status_code == 500 | |
| assert response.json()["error"]["code"] == "MODEL_INFERENCE_FAILED" | |
| assert "internal model detail" not in response.text | |
| def test_temporal_api_response_uses_adjacent_transition_index(): | |
| service, _, _ = make_service(waveform=np.arange(150, dtype=np.float32)) | |
| service.registry.analyzer("audio.temporal").config = TemporalAudioConfig( | |
| sample_rate=10, | |
| window_seconds=1, | |
| hop_seconds=1, | |
| max_segments=15, | |
| minimum_audio_seconds=1, | |
| ) | |
| response = client_for(service).post( | |
| "/v1/tracks/analyze", | |
| files=upload(), | |
| data={"requested_representations": "audio.temporal"}, | |
| ) | |
| assert response.status_code == 200 | |
| temporal = response.json()["representations"]["audio.temporal"] | |
| assert len(temporal["segments"]) == 15 | |
| assert temporal["summary"]["number_of_segments"] == 15 | |
| assert 0 <= temporal["summary"]["largest_transition_index"] <= 13 | |