mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-05 20:01:06 +01:00
87 lines
3.6 KiB
Python
87 lines
3.6 KiB
Python
"""Cluster 1 — Cloudflare bypasser wiring + clean-failure behavior.
|
|
|
|
Reality discovered by running the stack: shelfmark's AA *search* and *detail*
|
|
fetches use ``html_get_page(allow_bypasser_fallback=False)``, so a search behind a
|
|
Cloudflare gate returns 503 **regardless** of the bypasser. The bypasser (internal
|
|
Chrome or external FlareSolverr) is a *download-time* mechanism
|
|
(``html_get_page(use_bypasser=True)``); it never runs for search.
|
|
|
|
So these tests assert what is actually true and host-observable:
|
|
* the external bypasser is configured from env, and
|
|
* a CF-gated search fails *cleanly* (a 503 the client can act on, not a hang or
|
|
a crash) — both with the bypasser on (it isn't used for search) and off.
|
|
|
|
Exercising shelfmark's *use* of the bypasser end-to-end (a real CF solve during a
|
|
download) needs the AA slow-download HTML flow mocked — see the README roadmap.
|
|
The bypass *mechanism* itself is verified to work: the mock FlareSolverr solves
|
|
the gate (manually confirmed; see README). Guards: #284 #226 #202 #1030 #410 #369.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
|
|
def _boot_log() -> str:
|
|
path = os.environ.get("E2E_SHELFMARK_LOG")
|
|
if not path or not Path(path).exists():
|
|
return ""
|
|
return Path(path).read_text(encoding="utf-8", errors="ignore")
|
|
|
|
|
|
def _cf_gated_search_has_no_releases(client) -> bool:
|
|
"""A CF-gated AA search must not yield releases (the gate isn't bypassed for
|
|
search).
|
|
|
|
NOTE (observed live): with ``USE_CF_BYPASS=false`` the search is *slow* to fail
|
|
— the app retries and can take ~60s, vs a fast 503 when the bypasser is enabled
|
|
(potential #1001 "hung on bypass protection"). We bound the wait and treat a
|
|
timeout the same as a clean failure: in both cases no releases were obtained,
|
|
which is the point of this negative control.
|
|
"""
|
|
try:
|
|
resp = client.get(
|
|
"/api/releases",
|
|
params={"source": "direct_download", "query": "Mistborn"},
|
|
timeout=30,
|
|
)
|
|
except requests.exceptions.Timeout:
|
|
return True # could not complete -> definitively no releases obtained
|
|
assert resp.status_code in (200, 404, 500, 503), (
|
|
f"CF-gated search returned an unexpected status: {resp.status_code} {resp.text[:200]}"
|
|
)
|
|
return not client.releases_from(resp)
|
|
|
|
|
|
@pytest.mark.profiles("bypasser-external")
|
|
def test_external_bypasser_is_configured(client) -> None:
|
|
"""The external (FlareSolverr) bypasser path is selected via env."""
|
|
assert client.get("/api/health").status_code == 200
|
|
log = _boot_log()
|
|
if not log:
|
|
pytest.skip("E2E_SHELFMARK_LOG not available")
|
|
assert "USING_EXTERNAL_BYPASSER" in log and "EXT_BYPASSER_URL" in log, (
|
|
"external bypasser config was not synced from env"
|
|
)
|
|
|
|
|
|
@pytest.mark.profiles("bypasser-external")
|
|
def test_cf_gated_search_fails_cleanly_with_external_bypasser(client) -> None:
|
|
"""Even with the external bypasser configured, a CF-gated *search* yields no
|
|
releases (the bypasser is download-time) — but it must fail cleanly."""
|
|
assert _cf_gated_search_has_no_releases(client)
|
|
|
|
|
|
@pytest.mark.profiles("bypasser-disabled")
|
|
def test_cf_gated_search_fails_when_bypasser_off(client) -> None:
|
|
"""Negative control: AA behind Cloudflare + bypasser OFF -> no releases, clean
|
|
failure. A regression that ignored the gate would wrongly return results."""
|
|
assert _cf_gated_search_has_no_releases(client), (
|
|
"results returned even though AA is Cloudflare-gated and the bypasser is "
|
|
"disabled — the challenge is being ignored (regression for #202/#410)"
|
|
)
|