Nouvelle version réécrite de zéro : recherche multi-sources (Vostfree, French-Manga), extraction 2 niveaux, proxy vidéo intégré, streaming/téléchargement HLS, métadonnées Kitsu, bibliothèque locale, comptes JWT + administration, découverte fusionnée, indexeur Torznab (Sonarr/Prowlarr).
124 lines
4.8 KiB
Python
124 lines
4.8 KiB
Python
"""Tests d'intégration API (auth, downloads, favoris, streaming, admin)."""
|
|
|
|
import pytest
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
from app.config import get_settings
|
|
from app.db import db
|
|
from app.main import app
|
|
|
|
|
|
@pytest.fixture
|
|
async def client() -> AsyncClient:
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as c:
|
|
yield c
|
|
|
|
|
|
@pytest.fixture
|
|
async def admin_cookies(client: AsyncClient):
|
|
r = await client.post("/auth/register", data={"username": "admin", "password": "secret123"})
|
|
assert r.status_code == 303
|
|
return r.cookies
|
|
|
|
|
|
async def test_health(client):
|
|
r = await client.get("/health")
|
|
assert r.status_code == 200 and r.json() == {"status": "ok"}
|
|
|
|
|
|
async def test_pages_require_auth(client):
|
|
r = await client.get("/", follow_redirects=False)
|
|
assert r.status_code == 303 and r.headers["location"] == "/login"
|
|
r = await client.get("/api/downloads", follow_redirects=False)
|
|
assert r.status_code == 303
|
|
|
|
|
|
async def test_register_login_me(client, admin_cookies):
|
|
r = await client.get("/auth/me", cookies=admin_cookies)
|
|
data = r.json()
|
|
assert data["username"] == "admin" and data["is_admin"] is True
|
|
|
|
|
|
async def test_duplicate_username(client):
|
|
await client.post("/auth/register", data={"username": "toto", "password": "secret123"})
|
|
r = await client.post("/auth/register", data={"username": "toto", "password": "secret123"})
|
|
assert r.status_code == 409
|
|
|
|
|
|
async def test_enqueue_bad_internal_url(client, admin_cookies):
|
|
r = await client.post(
|
|
"/api/downloads", json={"internal_url": "invalide"}, cookies=admin_cookies
|
|
)
|
|
assert r.status_code == 422
|
|
|
|
|
|
async def test_favorites_flow(client, admin_cookies):
|
|
fav = {"source": "vostfree", "source_id": "1-x", "title": "Frieren"}
|
|
r = await client.post("/api/favorites", json=fav, cookies=admin_cookies)
|
|
assert r.status_code == 201
|
|
r = await client.post("/api/favorites", json=fav, cookies=admin_cookies)
|
|
assert r.status_code == 409
|
|
r = await client.get("/api/favorites", cookies=admin_cookies)
|
|
assert r.json()["total"] == 1
|
|
fav_id = r.json()["items"][0]["id"]
|
|
r = await client.delete(f"/api/favorites/{fav_id}", cookies=admin_cookies)
|
|
assert r.status_code == 200
|
|
|
|
|
|
async def test_stream_range(client, admin_cookies):
|
|
payload = b"video-bytes" * 1000
|
|
path = get_settings().download_dir / "ep1.mp4"
|
|
path.write_bytes(payload)
|
|
await db.execute(
|
|
"INSERT INTO downloads (source_key, video_url, page_url, title, file_path, status, "
|
|
"total_bytes, downloaded_bytes) VALUES ('k', 'u', 'p', 'Ep 1', 'ep1.mp4', 'done', ?, ?)",
|
|
(len(payload), len(payload)),
|
|
)
|
|
r = await client.get("/api/stream/1", cookies=admin_cookies)
|
|
assert r.status_code == 200 and r.content == payload
|
|
r = await client.get("/api/stream/1", headers={"Range": "bytes=10-19"}, cookies=admin_cookies)
|
|
assert r.status_code == 206
|
|
assert r.content == payload[10:20]
|
|
assert r.headers["content-range"] == f"bytes 10-19/{len(payload)}"
|
|
r = await client.get(
|
|
"/api/stream/1", headers={"Range": "bytes=999999999-"}, cookies=admin_cookies
|
|
)
|
|
assert r.status_code == 416
|
|
|
|
|
|
async def test_admin_required(client):
|
|
await client.post("/auth/register", data={"username": "admin2", "password": "secret123"})
|
|
r = await client.post("/auth/register", data={"username": "user", "password": "secret123"})
|
|
user_cookies = r.cookies
|
|
r = await client.get("/api/admin/users", cookies=user_cookies)
|
|
assert r.status_code == 403
|
|
|
|
|
|
async def test_admin_user_management(client, admin_cookies):
|
|
await client.post("/auth/register", data={"username": "user", "password": "secret123"})
|
|
r = await client.get("/api/admin/users", cookies=admin_cookies)
|
|
users = {u["username"]: u["id"] for u in r.json()}
|
|
assert set(users) == {"admin", "user"}
|
|
admin_id, user_id = users["admin"], users["user"]
|
|
r = await client.post(f"/api/admin/users/{user_id}/toggle-active", cookies=admin_cookies)
|
|
assert r.json() == {"is_active": False}
|
|
r = await client.post(f"/api/admin/users/{user_id}/toggle-admin", cookies=admin_cookies)
|
|
assert r.json() == {"is_admin": True}
|
|
# pas d'auto-modification
|
|
r = await client.post(f"/api/admin/users/{admin_id}/toggle-admin", cookies=admin_cookies)
|
|
assert r.status_code == 400
|
|
r = await client.delete(f"/api/admin/users/{user_id}", cookies=admin_cookies)
|
|
assert r.status_code == 200
|
|
|
|
|
|
async def test_source_toggle(client, admin_cookies):
|
|
r = await client.post(
|
|
"/api/admin/sources/vostfree/toggle", json={"enabled": False}, cookies=admin_cookies
|
|
)
|
|
assert r.json()["enabled"] is False
|
|
r = await client.get("/api/sources", cookies=admin_cookies)
|
|
states = {s["name"]: s["enabled"] for s in r.json()}
|
|
assert states["vostfree"] is False
|
|
assert states["french_manga"] is True
|