"""Tests for /api/process/audio endpoint.""" from __future__ import annotations import io import json from fastapi.testclient import TestClient def _valid_options(**overrides: object) -> str: """Return a valid JSON options string with optional overrides.""" defaults = { "pitchShift": False, "speedChange": False, "bassBoost": False, "trimSilence": False, "mixAudio": False, "addNoise": False, } defaults.update(overrides) return json.dumps(defaults) def _fake_audio(content: bytes = b"\x00" * 1024) -> io.BytesIO: return io.BytesIO(content) def test_audio_valid_request_accepted(client: TestClient) -> None: """Valid audio file + valid options should not return 422.""" response = client.post( "/api/process/audio", data={"options": _valid_options()}, files={"file": ("test.wav", _fake_audio(), "audio/wav")}, ) # Should be processed (200) or a processing error (400/500) — never 422 assert response.status_code != 422 def test_audio_rejects_non_audio(client: TestClient) -> None: """Should reject non-audio content type with 400.""" response = client.post( "/api/process/audio", data={"options": _valid_options()}, files={"file": ("test.txt", _fake_audio(), "text/plain")}, ) assert response.status_code == 400 detail = response.json()["detail"] assert detail["code"] == "invalid_file_type" def test_audio_rejects_missing_content_type(client: TestClient) -> None: """Should reject file with empty content type as invalid.""" response = client.post( "/api/process/audio", data={"options": _valid_options()}, files={"file": ("test.bin", _fake_audio(), "")}, ) assert response.status_code == 400 detail = response.json()["detail"] assert detail["code"] == "invalid_file_type" def test_audio_rejects_missing_file(client: TestClient) -> None: """Should reject request without file.""" response = client.post( "/api/process/audio", data={"options": _valid_options()}, ) assert response.status_code == 422 def test_audio_rejects_invalid_options_json(client: TestClient) -> None: """Should reject malformed JSON in options with 422 + invalid_options code.""" response = client.post( "/api/process/audio", data={"options": "not-valid-json"}, files={"file": ("test.wav", _fake_audio(), "audio/wav")}, ) assert response.status_code == 422 detail = response.json()["detail"] assert detail["code"] == "invalid_options" def test_audio_accepts_camel_case_options(client: TestClient) -> None: """Frontend sends camelCase keys — should be accepted.""" response = client.post( "/api/process/audio", data={"options": _valid_options(pitchShift=True, addNoise=True)}, files={"file": ("test.wav", _fake_audio(), "audio/wav")}, ) assert response.status_code != 422 def test_audio_defaults_when_options_missing(client: TestClient) -> None: """Missing options field should default to all-off, not 422.""" response = client.post( "/api/process/audio", files={"file": ("test.wav", _fake_audio(), "audio/wav")}, ) # Should proceed to processing (200) or processing error — never 422 assert response.status_code != 422