mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 03:55:05 +00:00
Renames 314 defs and 41 module/class-level constants. Keeps 11 names underscored where the stripped form collides in scope, plus 5.5.0 shims for the three symbols external service repos call.
117 lines
4.5 KiB
Python
117 lines
4.5 KiB
Python
"""E2E: full session lifecycle per service (create → info → titles → tracks → delete)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
pytestmark = [pytest.mark.live, pytest.mark.slow]
|
|
|
|
|
|
def create_session(http_session, server_url: str, service: str, conf: dict) -> str:
|
|
payload = {"service": service, "title_id": conf["title_url"]}
|
|
r = http_session.post(f"{server_url}/api/session/create", json=payload, timeout=120)
|
|
if r.status_code >= 400:
|
|
pytest.skip(f"Auth/setup not available for {service}: {r.status_code} {r.text[:200]}")
|
|
body = r.json()
|
|
sid = body.get("session_id")
|
|
assert sid, f"no session_id in body: {body}"
|
|
return sid
|
|
|
|
|
|
def wait_for_titles(http_session, server_url: str, sid: str, timeout: float = 120.0):
|
|
"""Poll /titles until auth completes. Returns (status_code, response_json).
|
|
|
|
Server returns 400 + auth_status=authenticating while auth is in-flight,
|
|
200 when authenticated, and other 4xx/5xx on real failure.
|
|
"""
|
|
deadline = time.monotonic() + timeout
|
|
while time.monotonic() < deadline:
|
|
r = http_session.get(f"{server_url}/api/session/{sid}/titles", timeout=30)
|
|
if r.status_code == 200:
|
|
return r.status_code, r.json()
|
|
if r.status_code == 400:
|
|
try:
|
|
body = r.json()
|
|
except Exception:
|
|
body = {}
|
|
auth_status = (body.get("details") or {}).get("auth_status")
|
|
if auth_status in ("authenticating", "pending_input"):
|
|
time.sleep(2.0)
|
|
continue
|
|
return r.status_code, r.json() if r.text else {}
|
|
return 408, {"message": "timeout waiting for auth"}
|
|
|
|
|
|
def delete_session(http_session, server_url: str, sid: str) -> None:
|
|
http_session.delete(f"{server_url}/api/session/{sid}", timeout=30)
|
|
|
|
|
|
def test_session_create_then_delete(http_session, server_url: str, service_case) -> None:
|
|
service, conf = service_case
|
|
sid = create_session(http_session, server_url, service, conf)
|
|
try:
|
|
r = http_session.get(f"{server_url}/api/session/{sid}", timeout=30)
|
|
assert r.status_code == 200
|
|
info = r.json()
|
|
assert info.get("session", {}).get("service_tag", service) == service or service in str(info)
|
|
finally:
|
|
delete_session(http_session, server_url, sid)
|
|
|
|
# After delete, info should 404
|
|
r2 = http_session.get(f"{server_url}/api/session/{sid}", timeout=30)
|
|
assert r2.status_code == 404
|
|
|
|
|
|
def test_session_titles_returns_list(http_session, server_url: str, service_case) -> None:
|
|
service, conf = service_case
|
|
sid = create_session(http_session, server_url, service, conf)
|
|
try:
|
|
code, body = wait_for_titles(http_session, server_url, sid)
|
|
if code != 200:
|
|
pytest.skip(f"{service}: titles fetch failed {code}: {str(body)[:200]}")
|
|
assert "titles" in body
|
|
assert isinstance(body["titles"], list)
|
|
assert len(body["titles"]) >= 1
|
|
first = body["titles"][0]
|
|
assert "id" in first
|
|
assert "type" in first
|
|
finally:
|
|
delete_session(http_session, server_url, sid)
|
|
|
|
|
|
def test_session_tracks_for_first_title(http_session, server_url: str, service_case) -> None:
|
|
service, conf = service_case
|
|
sid = create_session(http_session, server_url, service, conf)
|
|
try:
|
|
code, body = wait_for_titles(http_session, server_url, sid)
|
|
if code != 200:
|
|
pytest.skip(f"{service}: titles fetch failed {code}: {str(body)[:200]}")
|
|
titles = body.get("titles") or []
|
|
if not titles:
|
|
pytest.skip(f"{service}: no titles returned")
|
|
title_id = titles[0]["id"]
|
|
r = http_session.post(
|
|
f"{server_url}/api/session/{sid}/tracks",
|
|
json={"title_id": title_id},
|
|
timeout=240,
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
# Server returns a flat payload with `video`, `audio`, `subtitles`,
|
|
# `chapters`, `manifests`, `attachments` keys.
|
|
assert "video" in body or "audio" in body, body
|
|
assert body.get("video") or body.get("audio"), body
|
|
finally:
|
|
delete_session(http_session, server_url, sid)
|
|
|
|
|
|
def test_session_delete_idempotent_returns_404_after(http_session, server_url: str, service_case) -> None:
|
|
service, conf = service_case
|
|
sid = create_session(http_session, server_url, service, conf)
|
|
delete_session(http_session, server_url, sid)
|
|
|
|
r = http_session.delete(f"{server_url}/api/session/{sid}", timeout=30)
|
|
assert r.status_code in (404, 200) # tolerate both depending on server semantics
|