jam-tracks / backend /tests /test_api.py
Mina Emadi
updated the MVP-Initial upload
a0fcd39
"""API integration tests."""
import pytest
import io
import numpy as np
import soundfile as sf
from fastapi.testclient import TestClient
from backend.main import app
@pytest.fixture
def client():
"""Create test client."""
return TestClient(app)
def make_wav_bytes(audio: np.ndarray, sr: int) -> bytes:
"""Helper: encode numpy array to WAV bytes."""
buf = io.BytesIO()
sf.write(buf, audio, sr, format='WAV')
buf.seek(0)
return buf.read()
@pytest.fixture
def uploaded_session(client, sample_stems):
"""Upload stems and return session_id."""
stems, sr = sample_stems
files = []
for name, audio in stems.items():
wav_bytes = make_wav_bytes(audio, sr)
files.append(("files", (f"{name}.wav", wav_bytes, "audio/wav")))
response = client.post("/api/upload", files=files)
assert response.status_code == 200
return response.json()["session_id"]
def test_health_check(client):
"""Health endpoint should return healthy status."""
response = client.get("/api/health")
assert response.status_code == 200
assert response.json()["status"] == "healthy"
def test_upload_returns_session(client, sample_stems):
"""Upload should return a valid session with stem names."""
stems, sr = sample_stems
files = []
for name, audio in stems.items():
wav_bytes = make_wav_bytes(audio, sr)
files.append(("files", (f"{name}.wav", wav_bytes, "audio/wav")))
response = client.post("/api/upload", files=files)
assert response.status_code == 200
data = response.json()
assert "session_id" in data
assert set(data["stems"]) == {"bass", "drums", "guitar"}
def test_detection_returns_bpm_and_key(client, uploaded_session):
"""Detection should return BPM and key."""
response = client.post(f"/api/detect/{uploaded_session}")
assert response.status_code == 200
data = response.json()
assert "bpm" in data
assert "key" in data
assert "mode" in data
assert data["bpm"] > 0
def test_process_pitch_shift(client, uploaded_session):
"""Processing with pitch shift should succeed."""
# First detect
client.post(f"/api/detect/{uploaded_session}")
# Then process
response = client.post(f"/api/process/{uploaded_session}", json={
"semitones": 2,
"target_bpm": None
})
assert response.status_code == 200
assert response.json()["success"] is True
def test_get_stem_after_processing(client, uploaded_session):
"""Should be able to fetch a processed stem as audio."""
client.post(f"/api/detect/{uploaded_session}")
client.post(f"/api/process/{uploaded_session}", json={"semitones": 2})
response = client.get(f"/api/stem/{uploaded_session}/bass?processed=true")
assert response.status_code == 200
assert response.headers["content-type"] in ["audio/wav", "audio/x-wav"]
def test_invalid_session_404(client):
"""Requesting a nonexistent session should return 404."""
response = client.post("/api/detect/nonexistent-id")
assert response.status_code == 404
def test_upload_no_files_422(client):
"""Uploading with no files should return 422."""
response = client.post("/api/upload", files=[])
assert response.status_code == 422
def test_upload_non_wav_400(client):
"""Uploading non-WAV files should return 400."""
files = [("files", ("test.txt", b"not audio", "text/plain"))]
response = client.post("/api/upload", files=files)
assert response.status_code == 400
def test_list_stems(client, uploaded_session):
"""Should be able to list all stems."""
response = client.get(f"/api/stems/{uploaded_session}")
assert response.status_code == 200
data = response.json()
assert "stems" in data
assert len(data["stems"]) == 3
def test_get_original_stem(client, uploaded_session):
"""Should be able to fetch original stem without processing."""
response = client.get(f"/api/stem/{uploaded_session}/bass?processed=false")
assert response.status_code == 200
def test_get_nonexistent_stem_404(client, uploaded_session):
"""Requesting nonexistent stem should return 404."""
response = client.get(f"/api/stem/{uploaded_session}/nonexistent")
assert response.status_code == 404
def test_process_without_detection_400(client, uploaded_session):
"""Processing without detection should return 400."""
response = client.post(f"/api/process/{uploaded_session}", json={
"semitones": 2
})
assert response.status_code == 400
def test_upload_with_mix_file(client, sample_stems):
"""Upload with a mix file should recognize it."""
stems, sr = sample_stems
files = []
for name, audio in stems.items():
wav_bytes = make_wav_bytes(audio, sr)
files.append(("files", (f"{name}.wav", wav_bytes, "audio/wav")))
# Add a mix file
mix_audio = np.sum([a for a in stems.values()], axis=0) / 3
mix_bytes = make_wav_bytes(mix_audio.astype(np.float32), sr)
files.append(("files", ("full_mix.wav", mix_bytes, "audio/wav")))
response = client.post("/api/upload", files=files)
assert response.status_code == 200
data = response.json()
assert data["has_full_mix"] is True