Spaces:
Running on Zero
Running on Zero
Download tests/unit/test_api_endpoints.py from abalanescu/flow2: direct link, hf CLI and curl.
- Browser
- Download file 9.74 kB
-
https://huggingface.co/spaces/abalanescu/flow2/resolve/main/tests/unit/test_api_endpoints.py
- Command line
-
hf download hf://spaces/abalanescu/flow2/tests/unit/test_api_endpoints.py
-
curl -L -o test_api_endpoints.py https://huggingface.co/spaces/abalanescu/flow2/resolve/main/tests/unit/test_api_endpoints.py
9.74 kB
| """Unit tests for FastAPI endpoints in both app.py and studio_server.py. | |
| Uses FastAPI TestClient to test offline route contracts without GPU or network overhead. | |
| """ | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from app import fastapi_app as space_fastapi_app | |
| from studio_server import app as studio_fastapi_app | |
| def space_client(): | |
| headers = {} | |
| import os | |
| if os.environ.get("FLOW_API_KEY"): | |
| headers["Authorization"] = f"Bearer {os.environ['FLOW_API_KEY']}" | |
| return TestClient(space_fastapi_app, headers=headers) | |
| def studio_client(): | |
| headers = {} | |
| import os | |
| if os.environ.get("FLOW_API_KEY"): | |
| headers["Authorization"] = f"Bearer {os.environ['FLOW_API_KEY']}" | |
| return TestClient(studio_fastapi_app, headers=headers) | |
| def test_space_health_endpoint(space_client): | |
| """Verify Space /v1/health returns 200 and valid JSON schema.""" | |
| resp = space_client.get("/v1/health") | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| assert "service" in data | |
| assert "gpu" in data | |
| assert data["gpu"]["status"] == "healthy" | |
| def test_space_models_endpoint(space_client): | |
| """Verify Space /v1/models lists available model catalog.""" | |
| resp = space_client.get("/v1/models") | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| assert "data" in data | |
| assert len(data["data"]) > 0 | |
| def test_space_gpu_status_endpoint(space_client): | |
| """Verify Space /v1/gpu/status returns 200.""" | |
| resp = space_client.get("/v1/gpu/status") | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| assert "gpu" in data | |
| assert "total_vram_gb" in data["gpu"] | |
| def test_studio_stats_endpoint(studio_client): | |
| """Verify Studio server /api/stats returns 200 and statistics summary.""" | |
| resp = studio_client.get("/api/stats") | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| assert "personas_count" in data or "dictionary_entries" in data or "status" in data | |
| def test_studio_personas_endpoint(studio_client): | |
| """Verify Studio /api/personas lists registered personas.""" | |
| resp = studio_client.get("/api/personas") | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| assert "personas" in data | |
| assert len(data["personas"]) > 0 | |
| def test_studio_dialect_conversion_endpoint(studio_client): | |
| """Verify Studio /api/linguistics/convert endpoint returns translated dialect.""" | |
| payload = { | |
| "text": "Băieți, haideți să mergem puțin în oraș.", | |
| "level": 2 | |
| } | |
| resp = studio_client.post("/api/linguistics/convert", json=payload) | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| assert "converted_text" in data or "converted" in data or "dialect_text" in data | |
| def test_studio_media_route_aliases(studio_client): | |
| """Verify dual route aliases for frontend UI (/api/media/*).""" | |
| # 1. /api/media/stats | |
| r_stats = studio_client.get("/api/media/stats") | |
| assert r_stats.status_code == 200 | |
| assert "counts" in r_stats.json() | |
| assert "personas_count" in r_stats.json() | |
| # 2. /api/media/personas | |
| r_personas = studio_client.get("/api/media/personas") | |
| assert r_personas.status_code == 200 | |
| assert "personas" in r_personas.json() | |
| # 3. /api/media/dictionary | |
| r_dict = studio_client.get("/api/media/dictionary?page=1&limit=5") | |
| assert r_dict.status_code == 200 | |
| # 4. /api/media/datasets | |
| r_datasets = studio_client.get("/api/media/datasets") | |
| assert r_datasets.status_code == 200 | |
| assert "datasets" in r_datasets.json() | |
| # 5. /api/media/config | |
| r_config = studio_client.get("/api/media/config") | |
| assert r_config.status_code == 200 | |
| # 6. /api/media/vault | |
| r_vault = studio_client.get("/api/media/vault?type=ALL") | |
| assert r_vault.status_code == 200 | |
| assert "items" in r_vault.json() | |
| assert "assets" in r_vault.json() | |
| # 7. /api/media/vault/music & /api/media/vault/video | |
| r_vault_m = studio_client.get("/api/media/vault/music") | |
| assert r_vault_m.status_code == 200 | |
| assert "items" in r_vault_m.json() | |
| r_vault_v = studio_client.get("/api/media/vault/video") | |
| assert r_vault_v.status_code == 200 | |
| assert "items" in r_vault_v.json() | |
| # 8. /api/media/logs | |
| r_logs = studio_client.get("/api/media/logs") | |
| assert r_logs.status_code == 200 | |
| assert "logs" in r_logs.json() | |
| # 9. /api/media/dialect/convert | |
| r_dialect = studio_client.post("/api/media/dialect/convert", json={"text": "Salut prieteni", "level": 2}) | |
| assert r_dialect.status_code == 200 | |
| assert "dialect_text" in r_dialect.json() | |
| def test_space_models_catalog_includes_new_models(space_client): | |
| """Verify /v1/models includes petruhonk and Jackrong models.""" | |
| from app import resolve_model, list_gguf_files | |
| resp = space_client.get("/v1/models") | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| model_ids = [m["id"] for m in data["data"]] | |
| assert "Qwen3.8-9B-Distill-Heretic-Uncensored-Q8_0.gguf" in model_ids | |
| assert "DeepSeek-V4-Pro-Qwen3.5-9B-MTP-Q4_K_M.gguf" in model_ids | |
| assert "Qwen3.8-27B-TurboFCFusion-735-882-Here-Uncen-NEO-CODER-MAX-Q4_K_M.gguf" in model_ids | |
| assert "Wan-AI/Wan2.1-T2V-1.3B" in model_ids | |
| assert "MiniMaxAI/MiniMax-Music3" in model_ids | |
| # Verify alias resolution | |
| choices = list_gguf_files() | |
| assert resolve_model("premium", choices) == "Qwen3.8-9B-Q8_0.gguf" | |
| assert resolve_model("paid-premium", choices) == "Qwen3.8-9B-Q8_0.gguf" | |
| assert resolve_model("default", choices) == "Qwen3.8-9B-Q8_0.gguf" | |
| assert resolve_model("qwen-9b-distill-heretic", choices) == "Qwen3.8-9B-Distill-Heretic-Uncensored-Q8_0.gguf" | |
| assert resolve_model("deepseek-v4", choices) == "DeepSeek-V4-Pro-Qwen3.5-9B-MTP-Q4_K_M.gguf" | |
| assert resolve_model("deepseek-v4-pro", choices) == "DeepSeek-V4-Pro-Qwen3.5-9B-MTP-Q4_K_M.gguf" | |
| assert resolve_model("petruhonk/qwen3.8-9b-distill-uncensored-heretic", choices) == "Qwen3.8-9B-Distill-Heretic-Uncensored-Q8_0.gguf" | |
| assert resolve_model("jackrong/deepseek-v4-pro-qwen3.5-9b-mtp", choices) == "DeepSeek-V4-Pro-Qwen3.5-9B-MTP-Q4_K_M.gguf" | |
| assert resolve_model("qwen-turbo", choices) == "Qwen3.8-27B-TurboFCFusion-735-882-Here-Uncen-NEO-CODER-MAX-Q4_K_M.gguf" | |
| def test_studio_lyrics_compose_with_persona(studio_client): | |
| """Verify /api/media/lyrics/compose accepts persona_id and generates themed song sections.""" | |
| payload = { | |
| "topic": "Botanica vs Rîșcani", | |
| "genre": "Chișinău 808 Trap", | |
| "duration": 60, | |
| "persona_id": "ion_king_kong", | |
| "dialect_level": 3 | |
| } | |
| resp = studio_client.post("/api/media/lyrics/compose", json=payload) | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| assert "sections" in data | |
| assert len(data["sections"]) > 0 | |
| def test_studio_song_create_with_persona(studio_client, monkeypatch): | |
| """Verify /api/media/song/create passes persona_id and returns audio package. | |
| Policy: only MiniMax Music 3 is permitted for songs. Banned engines | |
| (voice_cloner, acestep, musicgen, synth808) must be rejected with HTTP 400. | |
| The MiniMax dispatch is mocked so the unit test stays offline (no ZeroGPU | |
| quota dependency). | |
| """ | |
| class _FakeDispatcher: | |
| def generate_music(self, *args, **kwargs): | |
| assert kwargs.get("engine") == "minimax_music3" | |
| return { | |
| "status": "success", | |
| "engine": "MiniMaxAI/MiniMax-Music3 (ZeroGPU)", | |
| "download_url": "/api/media/audio/stream/test_track.mp3", | |
| "audio_url": "/api/media/audio/stream/test_track.mp3", | |
| "filename": "test_track.mp3", | |
| "duration_seconds": 30, | |
| } | |
| def generate_image(self, *args, **kwargs): | |
| return {"image_url": "/api/media/images/test_cover.png", "filename": "test_cover.png"} | |
| from engine import media_creator as _mc_mod | |
| _RealMediaCreator = _mc_mod.MediaCreator | |
| def _fake_media_creator(*args, **kwargs): | |
| mc = _RealMediaCreator.__new__(_RealMediaCreator) | |
| mc.dispatcher = _FakeDispatcher() | |
| return mc | |
| monkeypatch.setattr( | |
| _mc_mod, | |
| "MediaCreator", | |
| _fake_media_creator, | |
| raising=False | |
| ) | |
| payload = { | |
| "topic": "King Kong Chișinău", | |
| "genre": "Chișinău 808 Trap", | |
| "duration": 30, | |
| "audio_engine": "minimax_music3", | |
| "persona_id": "ion_king_kong" | |
| } | |
| resp = studio_client.post("/api/media/song/create", json=payload) | |
| assert resp.status_code == 200 | |
| data = resp.json() | |
| assert "id" in data | |
| assert data["persona_id"] == "ion_king_kong" | |
| assert "audio_url" in data | |
| assert "full_lyrics" in data | |
| def test_space_audio_jobs_endpoint(space_client): | |
| """Verify /v1/audio/jobs creates background audio jobs and returns 202.""" | |
| payload = { | |
| "model": "MiniMaxAI/MiniMax-Music3", | |
| "instructions": "Moldovan balkan urban beat", | |
| "input": "Chișinău noaptea are ritmul lui", | |
| "duration": 60 | |
| } | |
| resp = space_client.post("/v1/audio/jobs", json=payload) | |
| assert resp.status_code == 202 | |
| data = resp.json() | |
| assert "job_id" in data | |
| assert data["status"] in ("queued", "processing") | |
| assert f"/v1/audio/jobs/{data['job_id']}" == data["check_status_url"] | |
| # Poll status | |
| poll_resp = space_client.get(data["check_status_url"]) | |
| assert poll_resp.status_code == 200 | |
| poll_data = poll_resp.json() | |
| assert poll_data["job_id"] == data["job_id"] | |
| # Also test /v1/audio/speech with ?async=true | |
| async_resp = space_client.post("/v1/audio/speech?async=true", json=payload) | |
| assert async_resp.status_code == 202 | |
| assert "job_id" in async_resp.json() | |