test: privatehd icin live e2e testlerini ve test dokumantasyonunu ekle

This commit is contained in:
2026-03-13 08:27:50 +03:00
parent 259531949b
commit b8e99ebbd2
4 changed files with 343 additions and 79 deletions

View File

@@ -227,6 +227,105 @@ Bu script:
Kurulum daha önce tamamsa script aynı işlemleri baştan yapmaz; sadece eksikleri tamamlar. Kurulum daha önce tamamsa script aynı işlemleri baştan yapmaz; sadece eksikleri tamamlar.
## Testler
`wscraper` içinde canlı sistemlere karşı çalışan `pytest` tabanlı e2e testleri vardır. Bunlar varsayılan olarak kapalıdır; yalnızca açıkça etkinleştirildiğinde çalışırlar.
Test dosyaları:
- `tests/e2e/test_happyfappy_live.py`
- `tests/e2e/test_privatehd_live.py`
- `tests/e2e/_helpers.py`
### Testleri Etkinleştirme
Tüm live testler için:
```bash
export WSCRAPER_E2E=1
```
Bu değişken yoksa veya `1` değilse, e2e testleri `skip` olur.
### HappyFappy Live Testleri
Mevcut test kapsamı:
- `get-bookmarks`
- `download-torrent-files`
Kullanılan env değişkenleri:
- `WSCRAPER_COOKIE_FILE`
- `WSCRAPER_TEST_TORRENT_URL`
Örnek:
```bash
export WSCRAPER_E2E=1
export WSCRAPER_COOKIE_FILE=/absolute/path/to/happyfappy-cookies.txt
export WSCRAPER_TEST_TORRENT_URL="https://www.happyfappy.net/torrents.php?id=110178"
pytest tests/e2e/test_happyfappy_live.py -m e2e -s
```
### PrivateHD Live Testleri
PrivateHD için eklenen test kapsamı:
- `get-bookmarks`
- `download-torrent-files`
- `remove-bookmark`
Kullanılan env değişkenleri:
- `WSCRAPER_PRIVATEHD_COOKIE_FILE`
- `WSCRAPER_PRIVATEHD_WISHLIST_URL`
- `WSCRAPER_PRIVATEHD_TEST_TORRENT_URL`
- `WSCRAPER_PRIVATEHD_TEST_DOWNLOAD_URL`
- `WSCRAPER_PRIVATEHD_TEST_REMOVE_URL`
- `WSCRAPER_PRIVATEHD_TEST_REMOVE_TOKEN`
Fallback kuralı:
- `WSCRAPER_PRIVATEHD_COOKIE_FILE` yoksa `WSCRAPER_COOKIE_FILE` kullanılır
Örnek:
```bash
export WSCRAPER_E2E=1
export WSCRAPER_PRIVATEHD_COOKIE_FILE=/absolute/path/to/privatehd-cookies.txt
export WSCRAPER_PRIVATEHD_WISHLIST_URL="https://privatehd.to/profile/blackdockers/wishlist"
export WSCRAPER_PRIVATEHD_TEST_TORRENT_URL="https://privatehd.to/torrent/12345-example"
export WSCRAPER_PRIVATEHD_TEST_DOWNLOAD_URL="https://privatehd.to/download/torrent/12345.example.torrent"
pytest tests/e2e/test_privatehd_live.py -m e2e -s
```
### remove-bookmark Testi Hakkında
`PrivateHD remove-bookmark` testi gerçek wishlist kaydını sildiği için özellikle dikkatli kullanılmalıdır.
Bu test:
- yalnızca `WSCRAPER_PRIVATEHD_TEST_REMOVE_URL` ve `WSCRAPER_PRIVATEHD_TEST_REMOVE_TOKEN` verilirse çalışır
- aksi halde güvenli şekilde `skip` olur
Örnek:
```bash
export WSCRAPER_E2E=1
export WSCRAPER_PRIVATEHD_COOKIE_FILE=/absolute/path/to/privatehd-cookies.txt
export WSCRAPER_PRIVATEHD_WISHLIST_URL="https://privatehd.to/profile/blackdockers/wishlist"
export WSCRAPER_PRIVATEHD_TEST_REMOVE_URL="https://privatehd.to/torrent/12345-example"
export WSCRAPER_PRIVATEHD_TEST_REMOVE_TOKEN="467471"
pytest tests/e2e/test_privatehd_live.py -m e2e -s -k remove
```
### Notlar
- Bu testler gerçek tracker hesaplarına ve geçerli cookie'lere ihtiyaç duyar
- `remove-bookmark` testi mutasyon yapar; test datası bilinçli seçilmelidir
- `tests/e2e/_helpers.py`, tüm tracker live testlerinde ortak CLI çalıştırma ve loglama yardımcılarını içerir
## Dizin Yapısı ## Dizin Yapısı
```text ```text

75
tests/e2e/_helpers.py Normal file
View File

@@ -0,0 +1,75 @@
from __future__ import annotations
import os
import subprocess
import sys
import time
from pathlib import Path
def e2e_enabled() -> bool:
return os.getenv("WSCRAPER_E2E", "").strip() == "1"
def base_env() -> dict[str, str]:
env = os.environ.copy()
src_path = str(Path.cwd() / "src")
current_pythonpath = env.get("PYTHONPATH", "").strip()
env["PYTHONPATH"] = f"{src_path}{os.pathsep}{current_pythonpath}" if current_pythonpath else src_path
return env
def log(tr, message: str, kind: str = "info") -> None:
icon = ""
style: dict[str, bool] = {}
if kind == "ok":
icon = ""
style = {"green": True}
elif kind == "err":
icon = ""
style = {"red": True}
elif kind == "warn":
icon = "⚠️"
style = {"yellow": True}
elif kind == "run":
icon = "🚀"
style = {"cyan": True}
if tr is not None:
tr.write_line(f"{icon} {message}", **style)
else:
print(f"{icon} {message}")
def run_cli_live(args: list[str], tr, timeout: int = 900) -> tuple[int, str]:
cmd = [sys.executable, "-m", "wscraper"] + args
log(tr, f"Running: {' '.join(cmd)}", kind="run")
started = time.time()
proc = subprocess.Popen(
cmd,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=base_env(),
)
output_lines: list[str] = []
assert proc.stdout is not None
for line in proc.stdout:
output_lines.append(line)
clean = line.rstrip("\n")
if clean:
if tr is not None:
tr.write_line(f" {clean}")
else:
print(f" {clean}")
return_code = proc.wait(timeout=timeout)
duration = time.time() - started
if return_code == 0:
log(tr, f"Command finished successfully in {duration:.2f}s", kind="ok")
else:
log(tr, f"Command failed with exit code {return_code} in {duration:.2f}s", kind="err")
return return_code, "".join(output_lines)

View File

@@ -2,100 +2,32 @@ from __future__ import annotations
import json import json
import os import os
import subprocess
import sys
import time
from pathlib import Path from pathlib import Path
import pytest import pytest
from tests.e2e._helpers import e2e_enabled
from tests.e2e._helpers import log
from tests.e2e._helpers import run_cli_live
pytestmark = [pytest.mark.e2e] pytestmark = [pytest.mark.e2e]
def _e2e_enabled() -> bool:
return os.getenv("WSCRAPER_E2E", "").strip() == "1"
def _base_env() -> dict[str, str]:
env = os.environ.copy()
src_path = str(Path.cwd() / "src")
current_pythonpath = env.get("PYTHONPATH", "").strip()
env["PYTHONPATH"] = f"{src_path}{os.pathsep}{current_pythonpath}" if current_pythonpath else src_path
return env
def _log(tr, message: str, kind: str = "info") -> None:
icon = ""
style: dict[str, bool] = {}
if kind == "ok":
icon = ""
style = {"green": True}
elif kind == "err":
icon = ""
style = {"red": True}
elif kind == "warn":
icon = "⚠️"
style = {"yellow": True}
elif kind == "run":
icon = "🚀"
style = {"cyan": True}
if tr is not None:
tr.write_line(f"{icon} {message}", **style)
else:
print(f"{icon} {message}")
def _run_cli_live(args: list[str], tr, timeout: int = 900) -> tuple[int, str]:
cmd = [sys.executable, "-m", "wscraper"] + args
_log(tr, f"Running: {' '.join(cmd)}", kind="run")
started = time.time()
proc = subprocess.Popen(
cmd,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=_base_env(),
)
output_lines: list[str] = []
assert proc.stdout is not None
for line in proc.stdout:
output_lines.append(line)
clean = line.rstrip("\n")
if clean:
if tr is not None:
tr.write_line(f" {clean}")
else:
print(f" {clean}")
return_code = proc.wait(timeout=timeout)
duration = time.time() - started
if return_code == 0:
_log(tr, f"Command finished successfully in {duration:.2f}s", kind="ok")
else:
_log(tr, f"Command failed with exit code {return_code} in {duration:.2f}s", kind="err")
return return_code, "".join(output_lines)
@pytest.fixture @pytest.fixture
def tr(request): def tr(request):
return request.config.pluginmanager.getplugin("terminalreporter") return request.config.pluginmanager.getplugin("terminalreporter")
@pytest.mark.skipif(not _e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests") @pytest.mark.skipif(not e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests")
def test_get_bookmarks_live(tmp_path: Path, tr) -> None: def test_get_bookmarks_live(tmp_path: Path, tr) -> None:
cookie_file = Path(os.getenv("WSCRAPER_COOKIE_FILE", "cookies.txt")) cookie_file = Path(os.getenv("WSCRAPER_COOKIE_FILE", "cookies.txt"))
if not cookie_file.exists(): if not cookie_file.exists():
pytest.skip(f"Cookie file not found: {cookie_file}") pytest.skip(f"Cookie file not found: {cookie_file}")
output_file = tmp_path / "bookmarks.json" output_file = tmp_path / "bookmarks.json"
_log(tr, f"Output file: {output_file}") log(tr, f"Output file: {output_file}")
return_code, output_text = _run_cli_live( return_code, output_text = run_cli_live(
[ [
"happyfappy", "happyfappy",
"--action", "--action",
@@ -113,7 +45,7 @@ def test_get_bookmarks_live(tmp_path: Path, tr) -> None:
data = json.loads(output_file.read_text(encoding="utf-8")) data = json.loads(output_file.read_text(encoding="utf-8"))
assert isinstance(data, list), "bookmarks output must be a JSON list" assert isinstance(data, list), "bookmarks output must be a JSON list"
assert len(data) >= 1, "expected at least one bookmark record" assert len(data) >= 1, "expected at least one bookmark record"
_log(tr, f"Extracted records: {len(data)}", kind="ok") log(tr, f"Extracted records: {len(data)}", kind="ok")
first = data[0] first = data[0]
assert isinstance(first, dict), "bookmark entry must be an object" assert isinstance(first, dict), "bookmark entry must be an object"
@@ -124,7 +56,7 @@ def test_get_bookmarks_live(tmp_path: Path, tr) -> None:
assert isinstance(first["title"], str) and first["title"].strip() != "" assert isinstance(first["title"], str) and first["title"].strip() != ""
@pytest.mark.skipif(not _e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests") @pytest.mark.skipif(not e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests")
def test_download_torrent_file_live(tmp_path: Path, tr) -> None: def test_download_torrent_file_live(tmp_path: Path, tr) -> None:
cookie_file = Path(os.getenv("WSCRAPER_COOKIE_FILE", "cookies.txt")) cookie_file = Path(os.getenv("WSCRAPER_COOKIE_FILE", "cookies.txt"))
if not cookie_file.exists(): if not cookie_file.exists():
@@ -135,9 +67,9 @@ def test_download_torrent_file_live(tmp_path: Path, tr) -> None:
"https://www.happyfappy.net/torrents.php?id=110178", "https://www.happyfappy.net/torrents.php?id=110178",
) )
output_dir = tmp_path / "torrent" output_dir = tmp_path / "torrent"
_log(tr, f"Output dir: {output_dir}") log(tr, f"Output dir: {output_dir}")
return_code, output_text = _run_cli_live( return_code, output_text = run_cli_live(
[ [
"happyfappy", "happyfappy",
"--action", "--action",
@@ -156,7 +88,7 @@ def test_download_torrent_file_live(tmp_path: Path, tr) -> None:
torrent_files = list(output_dir.glob("*.torrent")) torrent_files = list(output_dir.glob("*.torrent"))
assert len(torrent_files) >= 1, "expected at least one .torrent file" assert len(torrent_files) >= 1, "expected at least one .torrent file"
_log(tr, f"Downloaded .torrent files: {len(torrent_files)}", kind="ok") log(tr, f"Downloaded .torrent files: {len(torrent_files)}", kind="ok")
content = torrent_files[0].read_bytes() content = torrent_files[0].read_bytes()
assert content.startswith(b"d"), "torrent file should start with bencode dictionary token 'd'" assert content.startswith(b"d"), "torrent file should start with bencode dictionary token 'd'"

View File

@@ -0,0 +1,158 @@
from __future__ import annotations
import json
import os
from pathlib import Path
import pytest
from tests.e2e._helpers import e2e_enabled
from tests.e2e._helpers import log
from tests.e2e._helpers import run_cli_live
pytestmark = [pytest.mark.e2e]
@pytest.fixture
def tr(request):
return request.config.pluginmanager.getplugin("terminalreporter")
def _privatehd_cookie_file() -> Path:
path = os.getenv("WSCRAPER_PRIVATEHD_COOKIE_FILE") or os.getenv("WSCRAPER_COOKIE_FILE", "cookies.txt")
return Path(path)
def _privatehd_wishlist_url() -> str:
return os.getenv("WSCRAPER_PRIVATEHD_WISHLIST_URL", "").strip()
@pytest.mark.skipif(not e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests")
def test_get_bookmarks_live(tmp_path: Path, tr) -> None:
cookie_file = _privatehd_cookie_file()
if not cookie_file.exists():
pytest.skip(f"Cookie file not found: {cookie_file}")
wishlist_url = _privatehd_wishlist_url()
if not wishlist_url:
pytest.skip("Set WSCRAPER_PRIVATEHD_WISHLIST_URL to run PrivateHD live bookmark test")
output_file = tmp_path / "bookmarks.json"
log(tr, f"Output file: {output_file}")
return_code, output_text = run_cli_live(
[
"privatehd",
"--action",
"get-bookmarks",
"-c",
str(cookie_file),
"--wishlist-url",
wishlist_url,
"-o",
str(output_file),
],
tr,
)
assert return_code == 0, f"CLI failed:\n{output_text}"
assert output_file.exists(), "bookmarks.json was not created"
data = json.loads(output_file.read_text(encoding="utf-8"))
assert isinstance(data, list), "bookmarks output must be a JSON list"
assert len(data) >= 1, "expected at least one bookmark record"
log(tr, f"Extracted records: {len(data)}", kind="ok")
first = data[0]
assert isinstance(first, dict), "bookmark entry must be an object"
for required_key in ("pageURL", "title", "backgroundImage", "downloadURL", "removeToken"):
assert required_key in first, f"missing key: {required_key}"
assert isinstance(first["pageURL"], str) and first["pageURL"].startswith("http")
assert isinstance(first["title"], str) and first["title"].strip() != ""
assert isinstance(first["downloadURL"], str) and first["downloadURL"].startswith("http")
assert isinstance(first["removeToken"], str) and first["removeToken"].strip() != ""
@pytest.mark.skipif(not e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests")
def test_download_torrent_file_live(tmp_path: Path, tr) -> None:
cookie_file = _privatehd_cookie_file()
if not cookie_file.exists():
pytest.skip(f"Cookie file not found: {cookie_file}")
wishlist_url = _privatehd_wishlist_url()
if not wishlist_url:
pytest.skip("Set WSCRAPER_PRIVATEHD_WISHLIST_URL to run PrivateHD live download test")
test_url = os.getenv("WSCRAPER_PRIVATEHD_TEST_TORRENT_URL", "").strip()
download_url = os.getenv("WSCRAPER_PRIVATEHD_TEST_DOWNLOAD_URL", "").strip()
if not test_url or not download_url:
pytest.skip("Set WSCRAPER_PRIVATEHD_TEST_TORRENT_URL and WSCRAPER_PRIVATEHD_TEST_DOWNLOAD_URL")
output_dir = tmp_path / "torrent"
log(tr, f"Output dir: {output_dir}")
return_code, output_text = run_cli_live(
[
"privatehd",
"--action",
"download-torrent-files",
"-u",
test_url,
"--download-url",
download_url,
"-c",
str(cookie_file),
"--wishlist-url",
wishlist_url,
"-o",
str(output_dir),
],
tr,
)
assert return_code == 0, f"CLI failed:\n{output_text}"
assert output_dir.exists(), "torrent output directory was not created"
torrent_files = list(output_dir.glob("*.torrent"))
assert len(torrent_files) >= 1, "expected at least one .torrent file"
log(tr, f"Downloaded .torrent files: {len(torrent_files)}", kind="ok")
content = torrent_files[0].read_bytes()
assert content.startswith(b"d"), "torrent file should start with bencode dictionary token 'd'"
assert b"4:info" in content[:4096], "torrent file should include 'info' dictionary marker"
@pytest.mark.skipif(not e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests")
def test_remove_bookmark_live(tr) -> None:
cookie_file = _privatehd_cookie_file()
if not cookie_file.exists():
pytest.skip(f"Cookie file not found: {cookie_file}")
wishlist_url = _privatehd_wishlist_url()
if not wishlist_url:
pytest.skip("Set WSCRAPER_PRIVATEHD_WISHLIST_URL to run PrivateHD live remove test")
test_url = os.getenv("WSCRAPER_PRIVATEHD_TEST_REMOVE_URL", "").strip()
remove_token = os.getenv("WSCRAPER_PRIVATEHD_TEST_REMOVE_TOKEN", "").strip()
if not test_url or not remove_token:
pytest.skip("Set WSCRAPER_PRIVATEHD_TEST_REMOVE_URL and WSCRAPER_PRIVATEHD_TEST_REMOVE_TOKEN")
return_code, output_text = run_cli_live(
[
"privatehd",
"--action",
"remove-bookmark",
"-u",
test_url,
"--remove-token",
remove_token,
"-c",
str(cookie_file),
"--wishlist-url",
wishlist_url,
],
tr,
timeout=240,
)
assert return_code == 0, f"CLI failed:\n{output_text}"
assert "Bookmark removed successfully." in output_text
log(tr, "PrivateHD bookmark removal completed", kind="ok")