Plugin neuheiten: Bearer-Token-Pflicht der BGG-XML-API2 (Fix für HTTP 401)
BoardGameGeek verlangt für die XML API2 jetzt Bearer-Authentifizierung
(Thread 3602374); ohne Token antwortet die API mit 401.
- BggClient: neuer Parameter token, sendet Authorization: Bearer … auf
allen Requests; Token kommt aus SPIELE_BGG_TOKEN (vor jedem Sync gelesen)
- HTTP 401 → klarer BggAuthFehler ('BGG hat die Anfrage abgelehnt (401) —
bitte gültigen API-Token in SPIELE_BGG_TOKEN hinterlegen.') statt
generischer Meldung, kein Retry
- Ohne konfigurierten Token wird der Sync übersprungen ('Kein BGG-API-Token
konfiguriert (SPIELE_BGG_TOKEN) — Sync übersprungen.'), statt Requests zu
feuern; Meldung erscheint im Sync-Ergebnis, UI-Banner und Protokoll
- SyncService: BggAuthFehler bricht den Lauf ab (weitere Suchbegriffe
scheitern zwangsläufig gleich), Teilergebnisse bleiben erhalten
- Tests: Bearer-Header, 401-Fall (Client/Sync/UI), Skip ohne Token;
bestehende Sync-UI-Tests setzen Test-Token
- README: neue Env-Variable SPIELE_BGG_TOKEN + Hinweis auf Token-Pflicht
This commit is contained in:
@@ -8,7 +8,13 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from redaktionskern.app import create_app
|
||||
|
||||
from tests._neuheiten import BggSpiel, Neuheit, SuchTreffer
|
||||
from tests._neuheiten import (
|
||||
BggAuthFehler,
|
||||
BggSpiel,
|
||||
MELDUNG_401,
|
||||
Neuheit,
|
||||
SuchTreffer,
|
||||
)
|
||||
|
||||
from tests.conftest import ADMIN_PASSWORD, lege_benutzer_an, melde_an
|
||||
|
||||
@@ -33,6 +39,18 @@ class FakeBggClient:
|
||||
pass
|
||||
|
||||
|
||||
class FehlerwerfenderFake(FakeBggClient):
|
||||
"""Wirft bei der Suche einen Fehler (z. B. BggAuthFehler bei HTTP 401)."""
|
||||
|
||||
def __init__(self, fehler: Exception):
|
||||
super().__init__()
|
||||
self.fehler = fehler
|
||||
|
||||
def suche(self, suchbegriff: str, *, max_treffer: int | None = None):
|
||||
self.suche_aufrufe.append(suchbegriff)
|
||||
raise self.fehler
|
||||
|
||||
|
||||
def _client_mit_fakes(monkeypatch, client: TestClient, fake: FakeBggClient) -> None:
|
||||
plugin = client.app.state.registry.get("neuheiten")
|
||||
# Instanz-Methode ersetzen; monkeypatch stellt nach dem Test wieder her
|
||||
@@ -165,6 +183,7 @@ def test_manueller_sync_legt_eintraege_an(monkeypatch, settings):
|
||||
],
|
||||
)
|
||||
|
||||
monkeypatch.setenv("SPIELE_BGG_TOKEN", "test-token")
|
||||
with TestClient(create_app(settings)) as client:
|
||||
melde_an(client)
|
||||
_client_mit_fakes(monkeypatch, client, fake)
|
||||
@@ -193,6 +212,7 @@ def test_manueller_sync_ohne_suchbegriff_nutzt_konfigurierte_begriffe(monkeypatc
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
monkeypatch.setenv("SPIELE_BGG_SUCHBEGRIFFE", "essen, familien")
|
||||
monkeypatch.setenv("SPIELE_BGG_TOKEN", "test-token")
|
||||
fake = FakeBggClient()
|
||||
with TestClient(create_app(settings)) as client:
|
||||
melde_an(client)
|
||||
@@ -201,6 +221,53 @@ def test_manueller_sync_ohne_suchbegriff_nutzt_konfigurierte_begriffe(monkeypatc
|
||||
assert fake.suche_aufrufe == ["essen", "familien"]
|
||||
|
||||
|
||||
def test_sync_ohne_token_wird_uebersprungen(monkeypatch, settings):
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
monkeypatch.delenv("SPIELE_BGG_TOKEN", raising=False)
|
||||
fake = FakeBggClient(treffer=[SuchTreffer(13, "Catan", 1995)])
|
||||
|
||||
with TestClient(create_app(settings)) as client:
|
||||
melde_an(client)
|
||||
_client_mit_fakes(monkeypatch, client, fake)
|
||||
|
||||
antwort = client.post(
|
||||
"/neuheiten/sync",
|
||||
data={"suchbegriff": "catan"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert antwort.status_code == 303
|
||||
folge = client.get(antwort.headers["location"])
|
||||
assert (
|
||||
"Kein BGG-API-Token konfiguriert (SPIELE_BGG_TOKEN) — "
|
||||
"Sync übersprungen." in folge.text
|
||||
)
|
||||
assert fake.suche_aufrufe == [] # kein Request an die BGG-API gefeuert
|
||||
|
||||
|
||||
def test_sync_401_zeigt_klare_fehlermeldung(monkeypatch, settings):
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
monkeypatch.setenv("SPIELE_BGG_TOKEN", "falscher-token")
|
||||
fake = FehlerwerfenderFake(BggAuthFehler(MELDUNG_401))
|
||||
|
||||
with TestClient(create_app(settings)) as client:
|
||||
melde_an(client)
|
||||
_client_mit_fakes(monkeypatch, client, fake)
|
||||
|
||||
antwort = client.post(
|
||||
"/neuheiten/sync",
|
||||
data={"suchbegriff": "catan"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert antwort.status_code == 303
|
||||
folge = client.get(antwort.headers["location"])
|
||||
assert MELDUNG_401 in folge.text
|
||||
assert "Unerwartete HTTP-Antwort" not in folge.text
|
||||
|
||||
|
||||
def test_htmx_request_liefert_nur_teilfragment(app, client):
|
||||
melde_an(client)
|
||||
antwort = client.get("/neuheiten", headers={"HX-Request": "true"})
|
||||
|
||||
Reference in New Issue
Block a user