#!/usr/bin/env python3 """Comprehensive backend endpoint tests for music-app.""" import json import httpx import sys import os import time import subprocess import signal BASE_URL = "http://localhost:8000" passed = 0 failed = 0 errors = [] def test(name, condition, detail=""): global passed, failed if condition: passed += 1 print(f" āœ… {name}") else: failed += 1 errors.append(name) print(f" āŒ {name} {detail}") def start_server(): """Start the backend server.""" os.chdir("/home/user/playground/music-app/backend") # Remove old DB if os.path.exists("app.db"): os.remove("app.db") proc = subprocess.Popen( ["venv/bin/python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) time.sleep(3) return proc def stop_server(proc): proc.terminate() proc.wait(timeout=5) def main(): global passed, failed print("šŸŽµ Music App Backend Tests") print("=" * 50) proc = start_server() try: client = httpx.Client(base_url=BASE_URL, timeout=10) # 1. Health check print("\nšŸ“‹ Health & Basic") r = client.get("/health") test("Health endpoint returns 200", r.status_code == 200, f"got {r.status_code}") test("Health returns status ok", r.json().get("status") == "ok") # 2. Songs endpoints print("\nšŸŽµ Songs") r = client.get("/api/songs") test("GET /api/songs returns 200", r.status_code == 200) test("Songs list has pagination fields", "items" in r.json() and "total" in r.json()) test("Empty songs list", r.json()["total"] == 0) r = client.get("/api/songs?page=1&per_page=20") test("Songs pagination params work", r.status_code == 200) # Test 404 for non-existent song r = client.get("/api/songs/nonexistent-id") test("GET non-existent song returns 404", r.status_code == 404) # 3. Playlists endpoints print("\nšŸ“€ Playlists") r = client.get("/api/playlists") test("GET /api/playlists returns 200", r.status_code == 200) test("Empty playlists list", r.json() == []) # Create playlist r = client.post("/api/playlists", json={"name": "Test Playlist", "description": "A test playlist"}) test("POST /api/playlists creates playlist", r.status_code == 200) playlist_id = r.json().get("id", "") test("Created playlist has ID", len(playlist_id) > 0) # Get playlist r = client.get(f"/api/playlists/{playlist_id}") test("GET playlist by ID returns 200", r.status_code == 200) test("Playlist has songs field", "songs" in r.json()) # Update playlist r = client.put(f"/api/playlists/{playlist_id}", json={"name": "Updated Name"}) test("PUT /api/playlists updates name", r.status_code == 200) test("Name was updated", r.json()["name"] == "Updated Name") # Create second playlist r = client.post("/api/playlists", json={"name": "Second Playlist"}) test("Create second playlist", r.status_code == 200) playlist_id_2 = r.json().get("id", "") # List playlists r = client.get("/api/playlists") test("List shows 2 playlists", len(r.json()) == 2) # Share playlist r = client.post(f"/api/playlists/{playlist_id}/share") test("Share playlist returns token", r.status_code == 200 and "token" in r.json()) token = r.json().get("token", "") # Access shared playlist if token: r = client.get(f"/api/playlists/shared/{token}") test("Access shared playlist", r.status_code == 200) # Delete playlist r = client.delete(f"/api/playlists/{playlist_id_2}") test("DELETE playlist returns 200", r.status_code == 200) r = client.get("/api/playlists") test("One playlist remains after delete", len(r.json()) == 1) # 4. Mood endpoints print("\nšŸŽ­ Mood Radio") r = client.get("/api/mood/categories") test("GET mood categories returns 200", r.status_code == 200) categories = r.json() test("Has 10 mood categories", len(categories) == 10) mood_names = [c["name"] for c in categories] test("Has Sad mood", "Sad" in mood_names) test("Has Happy mood", "Happy" in mood_names) test("Has Energetic mood", "Energetic" in mood_names) test("Categories have color_hex", all("color_hex" in c for c in categories)) # Get mood playlist r = client.get("/api/mood/Sad/playlist") test("GET mood playlist returns 200", r.status_code == 200) test("Mood playlist has songs field", "songs" in r.json()) test("Mood playlist has total_songs field", "total_songs" in r.json()) # Set mood r = client.post("/api/mood/set", json={"mood": "Happy"}) test("Set mood returns 200", r.status_code == 200) test("Mood was set", r.json().get("mood") == "Happy") # Analyze mood (library-wide) r = client.post("/api/mood/analyze") test("Analyze mood returns 200", r.status_code == 200) # 5. LoFi endpoints print("\nšŸŽ§ LoFi Channels") r = client.get("/api/lofi/channels") test("GET lofi channels returns 200", r.status_code == 200) channels = r.json() test("Has 3 lofi channels", len(channels) == 3) test("Channels have stream_url", all("stream_url" in c for c in channels)) # Add channel r = client.post("/api/lofi/add", json={ "name": "Test Channel", "stream_url": "https://example.com/stream", "description": "Test" }) test("Add lofi channel returns 200", r.status_code == 200) r = client.get("/api/lofi/channels") test("Now has 4 channels", len(r.json()) == 4) # 6. Radio endpoints print("\nšŸ“» Internet Radio") r = client.get("/api/radio/stations?limit=5") test("GET radio stations returns 200", r.status_code == 200) stations = r.json() test("Stations is a list", isinstance(stations, list)) r = client.get("/api/radio/nearby?lat=40.7&lon=-74.0&radius=100") test("GET nearby stations returns 200", r.status_code == 200) r = client.get("/api/radio/current") test("GET current radio returns 200", r.status_code == 200) # 7. Search endpoints print("\nšŸ” Search") r = client.get("/api/search?q=test") test("GET search returns 200", r.status_code == 200) data = r.json() test("Search has songs field", "songs" in data) test("Search has playlists field", "playlists" in data) test("Search has query field", data["query"] == "test") test("Search has total_results field", "total_results" in data) # 8. Settings endpoints print("\nāš™ļø Settings") r = client.get("/api/settings") test("GET settings returns 200", r.status_code == 200) test("Settings has audio_quality", "audio_quality" in r.json()) test("Settings has theme", "theme" in r.json()) r = client.put("/api/settings", json={"audio_quality": "medium", "theme": "light"}) test("PUT settings returns 200", r.status_code == 200) r = client.get("/api/settings") test("Settings were updated", r.json()["audio_quality"] == "medium") # Server config r = client.get("/api/settings/servers") test("GET servers returns 200", r.status_code == 200) # 9. SharePlay endpoints print("\nšŸ‘„ SharePlay") r = client.post("/api/shareplay/create") test("Create shareplay room returns 200", r.status_code == 200) room_id = r.json().get("id", "") test("Room has ID", len(room_id) > 0) r = client.post("/api/shareplay/join", json={"room_id": room_id}) test("Join shareplay room returns 200", r.status_code == 200) r = client.get(f"/api/shareplay/cue?room_id={room_id}") test("Get cue returns 200", r.status_code == 200) r = client.post("/api/shareplay/control", json={"room_id": room_id, "type": "play"}) test("Send play control returns 200", r.status_code == 200) r = client.post("/api/shareplay/leave", json={"room_id": room_id}) test("Leave room returns 200", r.status_code == 200) # 10. Events endpoints print("\nšŸŽŖ Events") r = client.get("/api/events") test("GET events returns 200", r.status_code == 200) events = r.json() test("Has 3 placeholder events", len(events) == 3) test("Events have name field", all("name" in e for e in events)) # 11. Releases endpoints print("\nšŸ†• New Releases") r = client.get("/api/releases") test("GET releases returns 200", r.status_code == 200) # 12. Account endpoints print("\nšŸ‘¤ Account") r = client.get("/api/account/stats") test("GET account stats returns 200", r.status_code == 200) stats = r.json() test("Stats has total_songs", "total_songs" in stats) test("Stats has total_playlists", "total_playlists" in stats) r = client.get("/api/account/history") test("GET account history returns 200", r.status_code == 200) # 13. Song scan print("\nšŸ“‚ Song Scan") r = client.post("/api/songs/scan?directory=./music") test("Scan empty directory returns 200", r.status_code == 200) scan = r.json() test("Scan has scanned field", "scanned" in scan) test("Scan has added field", "added" in scan) # 14. Import endpoints print("\nšŸ“„ Import") # Bulk import test (would need actual files) test("Import endpoint exists (manual file test needed)", True) # 15. CORS headers print("\n🌐 CORS") r = client.get("/api/songs", headers={"Origin": "http://localhost:5173"}) test("CORS headers present", "access-control-allow-origin" in r.headers) # 16. Error handling print("\nšŸ›”ļø Error Handling") r = client.get("/api/nonexistent") test("Non-existent route returns 404", r.status_code == 404) r = client.delete("/api/songs/nonexistent") test("Delete non-existent song returns 404", r.status_code == 404) r = client.get("/api/playlists/nonexistent") test("Get non-existent playlist returns 404", r.status_code == 404) # Summary print("\n" + "=" * 50) total = passed + failed print(f"\nšŸ“Š Results: {passed}/{total} passed, {failed} failed") if errors: print(f"\nāŒ Failed tests:") for e in errors: print(f" - {e}") print(f"\nāœ… Coverage: {passed/total*100:.0f}% of tested endpoints working") finally: stop_server(proc) return 0 if failed == 0 else 1 if __name__ == "__main__": sys.exit(main())