audio streaming app plyr.fm
38
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 2025.1210.055059 160 lines 5.6 kB view raw
1"""integration tests for track upload/delete using real API token. 2 3these tests require: 4- PLYRFM_API_TOKEN or PLYR_TOKEN env var 5- running backend (local or remote) 6- set PLYR_API_URL for non-local testing (default: http://localhost:8001) 7 8run with: uv run pytest tests/test_integration_upload.py -m integration -v 9""" 10 11import json 12import os 13import struct 14import tempfile 15from collections.abc import Generator 16from pathlib import Path 17 18import httpx 19import pytest 20 21API_URL = os.getenv("PLYR_API_URL", "http://localhost:8001") 22TOKEN = os.getenv("PLYR_TOKEN") or os.getenv("PLYRFM_API_TOKEN") 23 24 25def generate_wav_file(duration_seconds: float = 1.0, sample_rate: int = 44100) -> bytes: 26 """generate a minimal valid WAV file with silence.""" 27 num_channels = 1 28 bits_per_sample = 16 29 num_samples = int(sample_rate * duration_seconds) 30 data_size = num_samples * num_channels * (bits_per_sample // 8) 31 32 # WAV header 33 header = struct.pack( 34 "<4sI4s4sIHHIIHH4sI", 35 b"RIFF", 36 36 + data_size, # file size - 8 37 b"WAVE", 38 b"fmt ", 39 16, # fmt chunk size 40 1, # audio format (PCM) 41 num_channels, 42 sample_rate, 43 sample_rate * num_channels * (bits_per_sample // 8), # byte rate 44 num_channels * (bits_per_sample // 8), # block align 45 bits_per_sample, 46 b"data", 47 data_size, 48 ) 49 50 # silence (zeros) 51 audio_data = b"\x00" * data_size 52 53 return header + audio_data 54 55 56@pytest.fixture 57def test_audio_file() -> Generator[Path, None, None]: 58 """create a temporary test audio file.""" 59 wav_data = generate_wav_file(duration_seconds=1.0) 60 61 with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f: 62 f.write(wav_data) 63 path = Path(f.name) 64 65 yield path 66 67 # cleanup 68 path.unlink(missing_ok=True) 69 70 71@pytest.mark.integration 72async def test_upload_and_delete_track(test_audio_file: Path): 73 """integration test: upload a track, wait for processing, then delete it.""" 74 if not TOKEN: 75 pytest.skip("PLYR_TOKEN or PLYRFM_API_TOKEN not set") 76 77 async with httpx.AsyncClient(timeout=120.0) as client: 78 # 1. verify auth works 79 auth_response = await client.get( 80 f"{API_URL}/auth/me", 81 headers={"Authorization": f"Bearer {TOKEN}"}, 82 ) 83 if auth_response.status_code == 401: 84 pytest.skip("token is invalid or expired") 85 assert auth_response.status_code == 200, f"auth failed: {auth_response.text}" 86 user = auth_response.json() 87 print(f"authenticated as: {user['handle']}") 88 89 # 2. upload track 90 with open(test_audio_file, "rb") as f: 91 files = {"file": ("test_integration.wav", f, "audio/wav")} 92 data = {"title": "Integration Test Track (DELETE ME)"} 93 94 upload_response = await client.post( 95 f"{API_URL}/tracks/", 96 headers={"Authorization": f"Bearer {TOKEN}"}, 97 files=files, 98 data=data, 99 ) 100 101 if upload_response.status_code == 403: 102 detail = upload_response.json().get("detail", "") 103 if "artist_profile_required" in detail: 104 pytest.skip("user needs artist profile setup") 105 if "scope_upgrade_required" in detail: 106 pytest.skip("token needs re-authorization with new scopes") 107 108 assert upload_response.status_code == 200, ( 109 f"upload failed: {upload_response.text}" 110 ) 111 112 upload_data = upload_response.json() 113 upload_id = upload_data["upload_id"] 114 print(f"upload started: {upload_id}") 115 116 # 3. poll for completion via SSE 117 track_id = None 118 async with client.stream( 119 "GET", 120 f"{API_URL}/tracks/uploads/{upload_id}/progress", 121 headers={"Authorization": f"Bearer {TOKEN}"}, 122 ) as response: 123 async for line in response.aiter_lines(): 124 if line.startswith("data: "): 125 data = json.loads(line[6:]) 126 status = data.get("status") 127 print(f" status: {status} - {data.get('message', '')}") 128 129 if status == "completed": 130 track_id = data.get("track_id") 131 print(f"upload complete! track_id: {track_id}") 132 break 133 elif status == "failed": 134 error = data.get("error", "unknown error") 135 pytest.fail(f"upload failed: {error}") 136 137 assert track_id is not None, "upload completed but no track_id returned" 138 139 # 4. verify track exists 140 track_response = await client.get(f"{API_URL}/tracks/{track_id}") 141 assert track_response.status_code == 200, ( 142 f"track not found: {track_response.text}" 143 ) 144 track = track_response.json() 145 print(f"track created: {track['title']} by {track['artist']['handle']}") 146 147 # 5. delete track 148 delete_response = await client.delete( 149 f"{API_URL}/tracks/{track_id}", 150 headers={"Authorization": f"Bearer {TOKEN}"}, 151 ) 152 assert delete_response.status_code == 200, ( 153 f"delete failed: {delete_response.text}" 154 ) 155 print(f"track {track_id} deleted successfully") 156 157 # 6. verify track is gone 158 verify_response = await client.get(f"{API_URL}/tracks/{track_id}") 159 assert verify_response.status_code == 404, "track should be deleted" 160 print("verified track no longer exists")