test: add E2E user journey test suite (pytest + Playwright skeleton)
- tests/test_user_journey.py: 23 pytest tests covering auth, search, settings, and download flows using TestClient with mocked providers (no real network calls) - tests/e2e/user_journey.spec.ts: 6 fixme Playwright test placeholders for full UI journey (register, login, browse, search, settings, logout)
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* User Journey E2E Tests
|
||||
*
|
||||
* Simulates a complete user flow: register → login → browse → search → settings → logout.
|
||||
* All tests are serial because they share browser state (auth token, navigation).
|
||||
*
|
||||
* FORBIDDEN: Do NOT use page.waitForTimeout() — use waitForResponse() or waitForSelector()
|
||||
*/
|
||||
|
||||
test.describe('User Journey E2E', () => {
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test.fixme('should register a new user', async ({ page }) => {
|
||||
// TODO: Navigate to /web or /login
|
||||
// Switch to register tab (text=Inscription)
|
||||
// Fill #registerUsername with unique username
|
||||
// Fill #registerPassword and #registerPasswordConfirm
|
||||
// Click #registerSubmit
|
||||
// Wait for API response via waitForResponse(r => r.url().includes('/api/auth/register'))
|
||||
// Verify #authSuccess becomes visible or contains success message
|
||||
});
|
||||
|
||||
test.fixme('should login with registered credentials', async ({ page }) => {
|
||||
// TODO: Navigate to /login
|
||||
// Fill #loginUsername and #loginPassword
|
||||
// Click #loginSubmit
|
||||
// Wait for response via waitForResponse(r => r.url().includes('/api/auth/login'))
|
||||
// Verify redirect to /web or home page
|
||||
// Verify auth token is stored (check localStorage or cookie)
|
||||
});
|
||||
|
||||
test.fixme('should browse homepage without errors', async ({ page }) => {
|
||||
// TODO: Navigate to /web
|
||||
// Wait for page to load via waitForSelector for main content area
|
||||
// Verify no console errors
|
||||
// Verify page title or main heading is visible
|
||||
// Note: content may be empty in test env — just verify no crash
|
||||
});
|
||||
|
||||
test.fixme('should search for anime', async ({ page }) => {
|
||||
// TODO: Click on anime search tab (if tabs exist)
|
||||
// Fill #animeSearchInput with "Naruto"
|
||||
// Submit the search form (trigger HTMX request)
|
||||
// Wait for response via waitForResponse(r => r.url().includes('/api/anime/search'))
|
||||
// Verify search results appear or "no results" message shown
|
||||
// Verify results container has expected selectors
|
||||
});
|
||||
|
||||
test.fixme('should update settings', async ({ page }) => {
|
||||
// TODO: Click on settings tab or navigate to settings section
|
||||
// Wait for settings panel to load via waitForResponse(r => r.url().includes('/api/settings'))
|
||||
// Verify #default_lang dropdown exists
|
||||
// Change language setting (select different option)
|
||||
// Submit/save settings form
|
||||
// Wait for response via waitForResponse(r => r.url().includes('/api/settings') && r.request().method() === 'PATCH')
|
||||
// Verify success toast notification appears
|
||||
});
|
||||
|
||||
test.fixme('should logout successfully', async ({ page }) => {
|
||||
// TODO: Click logout button
|
||||
// Wait for response via waitForResponse(r => r.url().includes('/api/auth/logout'))
|
||||
// Verify redirect to login page or auth state is cleared
|
||||
// Verify protected content is no longer accessible
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,587 @@
|
||||
"""
|
||||
End-to-end user journey tests for Ohm Stream Downloader.
|
||||
|
||||
These tests verify complete user workflows including authentication,
|
||||
search, settings management, and download operations.
|
||||
|
||||
All tests use mocked providers and in-memory SQLite — no real network calls.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
|
||||
# FORCE DATABASE_URL to in-memory for ALL tests before ANY app imports
|
||||
os.environ["DATABASE_URL"] = "sqlite://"
|
||||
|
||||
import pytest
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from main import app
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MOCK DATA CONSTANTS
|
||||
# =============================================================================
|
||||
|
||||
MOCK_ANIME_SEARCH_RESULTS: Dict[str, List[Dict]] = {
|
||||
"anime-sama": [
|
||||
{
|
||||
"title": "Naruto Shippuden",
|
||||
"url": "https://anime-sama.si/catalogue/naruto/saison1/vostfr/",
|
||||
"cover_image": "https://example.com/naruto.jpg",
|
||||
"type": "search_result",
|
||||
},
|
||||
{
|
||||
"title": "One Piece",
|
||||
"url": "https://anime-sama.si/catalogue/one-piece/saison1/vostfr/",
|
||||
"cover_image": "https://example.com/onepiece.jpg",
|
||||
"type": "search_result",
|
||||
},
|
||||
],
|
||||
"anime-ultime": [
|
||||
{
|
||||
"title": "Naruto Shippuden",
|
||||
"url": "https://www.anime-ultime.net/naruto-shippuden",
|
||||
"cover_image": "https://example.com/naruto-au.jpg",
|
||||
"type": "search_result",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
MOCK_SERIES_SEARCH_RESULTS: Dict[str, Dict] = {
|
||||
"Breaking Bad": {
|
||||
"base_name": "Breaking Bad",
|
||||
"cover": "https://example.com/bb.jpg",
|
||||
"synopsis": "A chemistry teacher turns to manufacturing methamphetamine...",
|
||||
"seasons": {
|
||||
1: [
|
||||
{
|
||||
"id": "fs7",
|
||||
"url": "https://fs7.fr/breaking-bad/saison-1",
|
||||
"provider_id": "fs7",
|
||||
}
|
||||
],
|
||||
2: [
|
||||
{
|
||||
"id": "fs7",
|
||||
"url": "https://fs7.fr/breaking-bad/saison-2",
|
||||
"provider_id": "fs7",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
MOCK_EPISODE_LIST: List[Dict] = [
|
||||
{
|
||||
"episode": 1,
|
||||
"url": "https://example.com/video1.mp4|https://anime-sama.si/catalogue/naruto/s1/ep1|Naruto - Episode 1",
|
||||
},
|
||||
{
|
||||
"episode": 2,
|
||||
"url": "https://example.com/video2.mp4|https://anime-sama.si/catalogue/naruto/s1/ep2|Naruto - Episode 2",
|
||||
},
|
||||
{
|
||||
"episode": 3,
|
||||
"url": "https://example.com/video3.mp4|https://anime-sama.si/catalogue/naruto/s1/ep3|Naruto - Episode 3",
|
||||
},
|
||||
{
|
||||
"episode": 4,
|
||||
"url": "https://example.com/video4.mp4|https://anime-sama.si/catalogue/naruto/s1/ep4|Naruto - Episode 4",
|
||||
},
|
||||
{
|
||||
"episode": 5,
|
||||
"url": "https://example.com/video5.mp4|https://anime-sama.si/catalogue/naruto/s1/ep5|Naruto - Episode 5",
|
||||
},
|
||||
]
|
||||
|
||||
MOCK_DOWNLOAD_LINK: Tuple[str, str] = (
|
||||
"https://example.com/video1.mp4",
|
||||
"Naruto_Episode_1.mp4",
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FIXTURES
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def journey_client():
|
||||
"""
|
||||
Create a TestClient with a registered and logged-in user.
|
||||
|
||||
Uses 'with' context manager to ensure startup events fire
|
||||
and database tables are properly initialized.
|
||||
"""
|
||||
unique_id = f"{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
test_username = f"journey_user_{unique_id}"
|
||||
test_password = "TestPassword123!"
|
||||
test_email = f"{test_username}@test.example.com"
|
||||
|
||||
with TestClient(app) as client:
|
||||
register_response = client.post(
|
||||
"/api/auth/register",
|
||||
json={
|
||||
"username": test_username,
|
||||
"password": test_password,
|
||||
"email": test_email,
|
||||
},
|
||||
)
|
||||
assert register_response.status_code == 200, (
|
||||
f"Registration failed: {register_response.json()}"
|
||||
)
|
||||
|
||||
login_response = client.post(
|
||||
"/api/auth/login",
|
||||
json={
|
||||
"username": test_username,
|
||||
"password": test_password,
|
||||
},
|
||||
)
|
||||
assert login_response.status_code == 200, (
|
||||
f"Login failed: {login_response.json()}"
|
||||
)
|
||||
|
||||
access_token = login_response.json()["access_token"]
|
||||
client.headers["Authorization"] = f"Bearer {access_token}"
|
||||
|
||||
yield client
|
||||
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unauth_client():
|
||||
"""Create a plain TestClient without authentication."""
|
||||
with TestClient(app) as client:
|
||||
yield client
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TEST CLASSES
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestAuthJourney:
|
||||
"""Tests for the authentication user journey — registration, login, tokens."""
|
||||
|
||||
def test_register_new_user(self):
|
||||
unique_id = f"{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
username = f"auth_test_{unique_id}"
|
||||
with TestClient(app) as client:
|
||||
response = client.post(
|
||||
"/api/auth/register",
|
||||
json={"username": username, "password": "SecurePass123!"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "success"
|
||||
assert data["user"]["username"] == username
|
||||
assert "id" in data["user"]
|
||||
|
||||
def test_register_duplicate_user(self):
|
||||
username = f"dup_{uuid.uuid4().hex[:8]}"
|
||||
with TestClient(app) as client:
|
||||
first = client.post(
|
||||
"/api/auth/register",
|
||||
json={"username": username, "password": "SecurePass123!"},
|
||||
)
|
||||
assert first.status_code == 200
|
||||
|
||||
second = client.post(
|
||||
"/api/auth/register",
|
||||
json={"username": username, "password": "SecurePass123!"},
|
||||
)
|
||||
assert second.status_code in (400, 500)
|
||||
|
||||
def test_login_success(self):
|
||||
unique_id = f"{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
username = f"login_test_{unique_id}"
|
||||
password = "SecurePass123!"
|
||||
|
||||
with TestClient(app) as client:
|
||||
client.post(
|
||||
"/api/auth/register",
|
||||
json={"username": username, "password": password},
|
||||
)
|
||||
response = client.post(
|
||||
"/api/auth/login",
|
||||
json={"username": username, "password": password},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "access_token" in data
|
||||
assert data["token_type"] == "bearer"
|
||||
assert data["user"]["username"] == username
|
||||
|
||||
def test_login_wrong_password(self):
|
||||
unique_id = f"{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
username = f"wrong_pw_{unique_id}"
|
||||
|
||||
with TestClient(app) as client:
|
||||
client.post(
|
||||
"/api/auth/register",
|
||||
json={"username": username, "password": "CorrectPass123!"},
|
||||
)
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.post(
|
||||
"/api/auth/login",
|
||||
json={"username": username, "password": "WrongPass999!"},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_get_current_user(self, journey_client):
|
||||
response = journey_client.get("/api/auth/me")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "user" in data
|
||||
assert "username" in data["user"]
|
||||
assert "id" in data["user"]
|
||||
|
||||
def test_unauthorized_access(self):
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/api/auth/me")
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
class TestSearchJourney:
|
||||
"""Tests for the search user journey — anime/series search, episodes, metadata."""
|
||||
|
||||
@patch("app.routers.router_anime.providers_manager")
|
||||
@patch("app.routers.router_anime.get_metadata_enricher")
|
||||
def test_search_anime(self, mock_enricher, mock_pm, journey_client):
|
||||
mock_provider = Mock()
|
||||
mock_provider.id = "anime-sama"
|
||||
mock_provider.search = AsyncMock(
|
||||
return_value=[
|
||||
Mock(
|
||||
model_dump=Mock(
|
||||
return_value={
|
||||
"title": "Naruto Shippuden",
|
||||
"url": "https://anime-sama.si/catalogue/naruto/s1/vostfr/",
|
||||
"cover_image": "https://example.com/naruto.jpg",
|
||||
"type": "search_result",
|
||||
}
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
mock_pm.get_active_providers.return_value = [mock_provider]
|
||||
|
||||
mock_meta = AsyncMock()
|
||||
mock_meta.enrich_metadata = AsyncMock(return_value=None)
|
||||
mock_enricher.return_value = mock_meta
|
||||
|
||||
response = journey_client.get("/api/anime/search?q=naruto")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["query"] == "naruto"
|
||||
assert "results" in data
|
||||
|
||||
@patch("app.routers.router_anime.providers_manager")
|
||||
@patch("app.routers.router_anime.get_metadata_enricher")
|
||||
def test_search_anime_results_have_providers(
|
||||
self, mock_enricher, mock_pm, journey_client
|
||||
):
|
||||
mock_provider = Mock()
|
||||
mock_provider.id = "anime-sama"
|
||||
mock_provider.search = AsyncMock(
|
||||
return_value=[
|
||||
Mock(
|
||||
model_dump=Mock(
|
||||
return_value={
|
||||
"title": "Naruto Shippuden",
|
||||
"url": "https://anime-sama.si/catalogue/naruto/s1/vostfr/",
|
||||
"cover_image": "https://example.com/naruto.jpg",
|
||||
"type": "search_result",
|
||||
}
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
mock_pm.get_active_providers.return_value = [mock_provider]
|
||||
|
||||
mock_meta = AsyncMock()
|
||||
mock_meta.enrich_metadata = AsyncMock(return_value=None)
|
||||
mock_enricher.return_value = mock_meta
|
||||
|
||||
response = journey_client.get("/api/anime/search?q=naruto")
|
||||
data = response.json()
|
||||
assert "anime-sama" in data["results"]
|
||||
|
||||
@patch("app.routers.router_anime.get_series_providers")
|
||||
@patch("app.routers.router_anime.get_metadata_enricher")
|
||||
def test_search_series(self, mock_enricher, mock_series_providers, journey_client):
|
||||
mock_series_providers.return_value = {"fs7": {"name": "FS7"}}
|
||||
|
||||
mock_meta = AsyncMock()
|
||||
mock_meta.enrich_metadata = AsyncMock(return_value=None)
|
||||
mock_enricher.return_value = mock_meta
|
||||
|
||||
mock_fs7_instance = Mock()
|
||||
mock_fs7_instance.search_anime = AsyncMock(
|
||||
return_value=[
|
||||
{
|
||||
"title": "Breaking Bad",
|
||||
"url": "https://fs7.fr/breaking-bad/saison-1",
|
||||
"cover_image": "https://example.com/bb.jpg",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
with patch.dict(
|
||||
"sys.modules",
|
||||
{
|
||||
"app.downloaders.series_sites.fs7": Mock(
|
||||
FS7Downloader=Mock(return_value=mock_fs7_instance)
|
||||
)
|
||||
},
|
||||
):
|
||||
response = journey_client.get("/api/series/search?q=breaking+bad")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["query"] == "breaking bad"
|
||||
assert "results" in data
|
||||
|
||||
@patch("app.routers.router_anime.providers_manager")
|
||||
@patch("app.routers.router_anime.get_metadata_enricher")
|
||||
def test_search_anime_no_results(self, mock_enricher, mock_pm, journey_client):
|
||||
mock_provider = Mock()
|
||||
mock_provider.id = "anime-sama"
|
||||
mock_provider.search = AsyncMock(return_value=[])
|
||||
mock_pm.get_active_providers.return_value = [mock_provider]
|
||||
|
||||
mock_meta = AsyncMock()
|
||||
mock_meta.enrich_metadata = AsyncMock(return_value=None)
|
||||
mock_enricher.return_value = mock_meta
|
||||
|
||||
response = journey_client.get("/api/anime/search?q=test_no_results_xyz")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "results" in data
|
||||
assert "anime-sama" not in data["results"]
|
||||
|
||||
@patch("app.routers.router_anime.get_downloader")
|
||||
def test_get_episodes(self, mock_get_dl):
|
||||
mock_dl = Mock()
|
||||
mock_dl.get_episodes = AsyncMock(return_value=MOCK_EPISODE_LIST)
|
||||
mock_get_dl.return_value = mock_dl
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get(
|
||||
"/api/anime/episodes?url=https://anime-sama.si/test&lang=vostfr"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["lang"] == "vostfr"
|
||||
assert len(data["episodes"]) == 5
|
||||
assert data["episodes"][0]["episode"] == 1
|
||||
|
||||
@patch("app.routers.router_anime.get_downloader")
|
||||
def test_get_anime_metadata(self, mock_get_dl):
|
||||
mock_dl = Mock()
|
||||
mock_dl.get_anime_metadata = AsyncMock(
|
||||
return_value={
|
||||
"synopsis": "A ninja story",
|
||||
"genres": ["Action", "Adventure"],
|
||||
"rating": "8.5/10",
|
||||
}
|
||||
)
|
||||
mock_dl.hasattr = Mock(return_value=True)
|
||||
mock_get_dl.return_value = mock_dl
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get(
|
||||
"/api/anime/metadata?url=https://anime-sama.si/naruto"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["url"] == "https://anime-sama.si/naruto"
|
||||
assert data["metadata"]["synopsis"] == "A ninja story"
|
||||
|
||||
|
||||
class TestSettingsJourney:
|
||||
"""Tests for the settings management user journey — preferences, providers, theme."""
|
||||
|
||||
def test_get_default_settings(self, journey_client):
|
||||
response = journey_client.get("/api/settings")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["default_lang"] == "vostfr"
|
||||
assert data["theme"] == "dark"
|
||||
|
||||
def test_update_lang(self, journey_client):
|
||||
response = journey_client.patch("/api/settings", json={"default_lang": "vf"})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["default_lang"] == "vf"
|
||||
|
||||
verify = journey_client.get("/api/settings")
|
||||
assert verify.json()["default_lang"] == "vf"
|
||||
|
||||
def test_update_theme(self, journey_client):
|
||||
response = journey_client.patch("/api/settings", json={"theme": "oled"})
|
||||
assert response.status_code == 200
|
||||
assert response.json()["theme"] == "oled"
|
||||
|
||||
def test_settings_persist_across_requests(self, journey_client):
|
||||
journey_client.patch(
|
||||
"/api/settings",
|
||||
json={
|
||||
"default_lang": "vf",
|
||||
"theme": "oled",
|
||||
},
|
||||
)
|
||||
|
||||
first = journey_client.get("/api/settings").json()
|
||||
second = journey_client.get("/api/settings").json()
|
||||
|
||||
assert first["default_lang"] == "vf"
|
||||
assert first["theme"] == "oled"
|
||||
assert second["default_lang"] == first["default_lang"]
|
||||
assert second["theme"] == first["theme"]
|
||||
|
||||
@patch("app.routers.router_settings.providers_manager")
|
||||
def test_get_providers_availability(self, mock_pm, journey_client):
|
||||
mock_pm.get_all_status.return_value = {
|
||||
"anime-sama": {"status": "up"},
|
||||
"fs7": {"status": "up"},
|
||||
}
|
||||
|
||||
response = journey_client.get("/api/settings/providers/availability")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
provider_ids = {p["id"] for p in data}
|
||||
assert len(provider_ids) > 0
|
||||
|
||||
@patch("app.routers.router_settings.providers_manager")
|
||||
def test_toggle_provider(self, mock_pm, journey_client):
|
||||
mock_pm.get_all_status.return_value = {}
|
||||
|
||||
toggle = journey_client.post("/api/settings/providers/anime-sama/toggle")
|
||||
assert toggle.status_code == 200
|
||||
data = toggle.json()
|
||||
assert data["id"] == "anime-sama"
|
||||
assert data["enabled"] is False
|
||||
|
||||
toggle_back = journey_client.post("/api/settings/providers/anime-sama/toggle")
|
||||
assert toggle_back.json()["enabled"] is True
|
||||
|
||||
settings = journey_client.get("/api/settings").json()
|
||||
assert "anime-sama" not in settings["disabled_providers"]
|
||||
|
||||
|
||||
class TestDownloadJourney:
|
||||
"""Tests for the download management user journey — create, list, status, cancel."""
|
||||
|
||||
@patch("app.routers.router_anime.get_download_manager")
|
||||
def test_create_single_download(self, mock_get_dm, journey_client):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from app.download_manager import DownloadManager
|
||||
|
||||
tmp_dir = Path(tempfile.mkdtemp()) / "downloads"
|
||||
tmp_dir.mkdir(exist_ok=True)
|
||||
manager = DownloadManager(download_dir=str(tmp_dir), max_parallel=1)
|
||||
mock_get_dm.return_value = manager
|
||||
|
||||
response = journey_client.post(
|
||||
"/api/anime/download?url=https://example.com/video.mp4"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "task_id" in data
|
||||
|
||||
@patch("app.routers.router_downloads.get_download_manager")
|
||||
def test_list_downloads(self, mock_get_dm, journey_client):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from app.download_manager import DownloadManager
|
||||
|
||||
tmp_dir = Path(tempfile.mkdtemp()) / "downloads"
|
||||
tmp_dir.mkdir(exist_ok=True)
|
||||
manager = DownloadManager(download_dir=str(tmp_dir), max_parallel=1)
|
||||
mock_get_dm.return_value = manager
|
||||
|
||||
journey_client.post(
|
||||
"/api/downloads",
|
||||
json={"url": "https://example.com/video1.mp4"},
|
||||
)
|
||||
response = journey_client.get("/api/downloads")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "downloads" in data
|
||||
assert len(data["downloads"]) >= 1
|
||||
|
||||
@patch("app.routers.router_downloads.get_download_manager")
|
||||
def test_download_task_status(self, mock_get_dm, journey_client):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from app.download_manager import DownloadManager
|
||||
|
||||
tmp_dir = Path(tempfile.mkdtemp()) / "downloads"
|
||||
tmp_dir.mkdir(exist_ok=True)
|
||||
manager = DownloadManager(download_dir=str(tmp_dir), max_parallel=1)
|
||||
mock_get_dm.return_value = manager
|
||||
|
||||
task_resp = journey_client.post(
|
||||
"/api/downloads",
|
||||
json={"url": "https://example.com/status_test.mp4"},
|
||||
)
|
||||
task_id = task_resp.json()["id"]
|
||||
|
||||
status_resp = journey_client.get(f"/api/downloads/{task_id}")
|
||||
assert status_resp.status_code == 200
|
||||
assert status_resp.json()["id"] == task_id
|
||||
|
||||
@patch("app.routers.router_downloads.get_download_manager")
|
||||
def test_cancel_download(self, mock_get_dm, journey_client):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from app.download_manager import DownloadManager
|
||||
|
||||
tmp_dir = Path(tempfile.mkdtemp()) / "downloads"
|
||||
tmp_dir.mkdir(exist_ok=True)
|
||||
manager = DownloadManager(download_dir=str(tmp_dir), max_parallel=1)
|
||||
mock_get_dm.return_value = manager
|
||||
|
||||
task_resp = journey_client.post(
|
||||
"/api/downloads",
|
||||
json={"url": "https://example.com/cancel_test.mp4"},
|
||||
)
|
||||
assert task_resp.status_code == 200
|
||||
task_id = task_resp.json()["id"]
|
||||
|
||||
cancel_resp = journey_client.delete(f"/api/downloads/{task_id}")
|
||||
assert cancel_resp.status_code == 200
|
||||
|
||||
@patch("app.routers.router_anime.get_download_manager")
|
||||
@patch("app.routers.router_anime.get_downloader")
|
||||
def test_download_season(self, mock_get_dl, mock_get_dm, journey_client):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from app.download_manager import DownloadManager
|
||||
|
||||
mock_dl = Mock()
|
||||
mock_dl.get_episodes = AsyncMock(return_value=MOCK_EPISODE_LIST)
|
||||
mock_get_dl.return_value = mock_dl
|
||||
|
||||
tmp_dir = Path(tempfile.mkdtemp()) / "downloads"
|
||||
tmp_dir.mkdir(exist_ok=True)
|
||||
manager = DownloadManager(download_dir=str(tmp_dir), max_parallel=1)
|
||||
mock_get_dm.return_value = manager
|
||||
mock_get_dm.return_value = manager
|
||||
|
||||
response = journey_client.post(
|
||||
"/api/anime/download-season?url=https://anime-sama.si/test&lang=vostfr"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total_episodes"] == 5
|
||||
assert len(data["task_ids"]) == 5
|
||||
Reference in New Issue
Block a user