Fix: dedup BGG-Client nutzt Token; Sync-Filter Jahr+deutscher Titel; Defaults für deutsche Neuerscheinungen

This commit is contained in:
Flo Hartmann
2026-08-25 17:28:35 +00:00
parent d91402881a
commit 929cea1887
10 changed files with 202 additions and 19 deletions

6
.env
View File

@@ -1,3 +1,9 @@
SPIELE_SESSION_SECRET=48ebf7ddae261a931652b5a55cff708aa21cf4936a8bdeb287eeac7cec4ee3e2 SPIELE_SESSION_SECRET=48ebf7ddae261a931652b5a55cff708aa21cf4936a8bdeb287eeac7cec4ee3e2
SPIELE_INITIAL_ADMIN_PASSWORD=3fb93f84014ff45406b36ec3 SPIELE_INITIAL_ADMIN_PASSWORD=3fb93f84014ff45406b36ec3
SPIELE_BGG_TOKEN=6fa6f9f6-32fd-443d-a5c3-ac2527f6a6fc SPIELE_BGG_TOKEN=6fa6f9f6-32fd-443d-a5c3-ac2527f6a6fc
# BGG-Sync: deutsche Neuerscheinungen ab aktuellem Jahr
SPIELE_BGG_SUCHBEGRIFFE=brettspiel,gesellschaftsspiel,familienspiel,strategiespiel,kartenspiel,würfelspiel
SPIELE_BGG_MAX_TREFFER_PRO_SUCHE=50
SPIELE_BGG_JAHR_FILTER=1
SPIELE_BGG_DEUTSCHE_TITEL_FILTER=1

View File

@@ -55,9 +55,11 @@ Beim ersten Start wird automatisch ein Admin-Konto angelegt:
| `SPIELE_TELEGRAM_BOT_TOKEN` | *(leer)* | Bot-Token für den Telegram-Kanal; leer = Dev-Fallback (nur Protokoll) | | `SPIELE_TELEGRAM_BOT_TOKEN` | *(leer)* | Bot-Token für den Telegram-Kanal; leer = Dev-Fallback (nur Protokoll) |
| `SPIELE_BGG_SYNC_AKTIV` | `1` | Hintergrund-Sync des Neuheiten-Plugins an (`1`) oder aus (`0`) | | `SPIELE_BGG_SYNC_AKTIV` | `1` | Hintergrund-Sync des Neuheiten-Plugins an (`1`) oder aus (`0`) |
| `SPIELE_BGG_SYNC_INTERVALL_STUNDEN` | `24` | Intervall des BGG-Syncs in Stunden (min. 1) | | `SPIELE_BGG_SYNC_INTERVALL_STUNDEN` | `24` | Intervall des BGG-Syncs in Stunden (min. 1) |
| `SPIELE_BGG_SUCHBEGRIFFE` | `brettspiel` | Komma-getrennte Suchbegriffe für den regelmäßigen Sync | | `SPIELE_BGG_SUCHBEGRIFFE` | `brettspiel, gesellschaftsspiel, familienspiel, strategiespiel, kartenspiel, würfelspiel` | Komma-getrennte Suchbegriffe für den regelmäßigen Sync (deutsche Spielbegriffe) |
| `SPIELE_BGG_MAX_TREFFER_PRO_SUCHE` | `25` | Obergrenze Treffer je Suchbegriff (schont das BGG-Rate-Limit) | | `SPIELE_BGG_MAX_TREFFER_PRO_SUCHE` | `50` | Obergrenze Treffer je Suchbegriff (schont das BGG-Rate-Limit) |
| `SPIELE_BGG_TOKEN` | *(leer)* | API-Token für die BGG-XML-API2, wird als `Authorization: Bearer …`-Header gesendet; seit der Token-Pflicht von BGG erforderlich (siehe [BGG-Thread 3602374](https://boardgamegeek.com/thread/3602374)) — ohne Token wird der Sync übersprungen | | `SPIELE_BGG_JAHR_FILTER` | `1` | Nur Spiele mit Erscheinungsjahr ≥ aktuelles Jahr übernehmen (`1`) oder alle (`0`) |
| `SPIELE_BGG_DEUTSCHE_TITEL_FILTER` | `1` | Nur Spiele mit deutsch klingendem Titel übernehmen (`1`) oder alle (`0`) |
| `SPIELE_BGG_TOKEN` | *(leer)* | API-Token für die BGG-XML-API2, wird als `Authorization: Bearer *** gesendet; seit der Token-Pflicht von BGG erforderlich (siehe [BGG-Thread 3602374](https://boardgamegeek.com/thread/3602374)) — ohne Token wird der Sync übersprungen |
| `SPIELE_DEDUP_BGG_AKTIV` | `1` | BGG-Zusatzdaten für die Dedup-Prüfung an (`1`) oder aus (`0`): Alternate-Names und Erweiterungs-Relationen | | `SPIELE_DEDUP_BGG_AKTIV` | `1` | BGG-Zusatzdaten für die Dedup-Prüfung an (`1`) oder aus (`0`): Alternate-Names und Erweiterungs-Relationen |
| `SPIELE_DEDUP_LLM_AKTIV` | `0` | LLM-Zweitprüfung der Dedup-Grenzfälle an (`1`) oder aus (`0`) | | `SPIELE_DEDUP_LLM_AKTIV` | `0` | LLM-Zweitprüfung der Dedup-Grenzfälle an (`1`) oder aus (`0`) |
| `SPIELE_DEDUP_LLM_BASIS_URL` | *(leer)* | Basis-URL einer OpenAI-kompatiblen Chat-Completions-API, z. B. `https://api.openai.com/v1` | | `SPIELE_DEDUP_LLM_BASIS_URL` | *(leer)* | Basis-URL einer OpenAI-kompatiblen Chat-Completions-API, z. B. `https://api.openai.com/v1` |

View File

@@ -8,6 +8,8 @@ services:
- SPIELE_SESSION_SECRET=${SPIELE_SESSION_SECRET:-change-me-in-production} - SPIELE_SESSION_SECRET=${SPIELE_SESSION_SECRET:-change-me-in-production}
# Vor dem ersten Start setzen, sonst wird der Admin mit "admin" angelegt: # Vor dem ersten Start setzen, sonst wird der Admin mit "admin" angelegt:
- SPIELE_INITIAL_ADMIN_PASSWORD=${SPIELE_INITIAL_ADMIN_PASSWORD:-admin} - SPIELE_INITIAL_ADMIN_PASSWORD=${SPIELE_INITIAL_ADMIN_PASSWORD:-admin}
# BGG XML API2 (Pflicht seit Token-Umstellung): siehe .env
- SPIELE_BGG_TOKEN=${SPIELE_BGG_TOKEN}
# Benachrichtigungen (optional): ohne SMTP-Host/Bot-Token wird nur protokolliert # Benachrichtigungen (optional): ohne SMTP-Host/Bot-Token wird nur protokolliert
#- SPIELE_SMTP_HOST=smtp.example.org #- SPIELE_SMTP_HOST=smtp.example.org
#- SPIELE_SMTP_PORT=587 #- SPIELE_SMTP_PORT=587

View File

@@ -192,7 +192,8 @@ class DedupPlugin(BasePlugin):
if not self._bgg_aktiv or self.context is None: if not self._bgg_aktiv or self.context is None:
return None return None
if self._bgg_client is None: if self._bgg_client is None:
self._bgg_client = BggPruefClient() token = (os.environ.get("SPIELE_BGG_TOKEN") or "").strip() or None
self._bgg_client = BggPruefClient(token=token)
return self._bgg_client return self._bgg_client
def _protokolliere(self, ergebnis: PruefErgebnis, user: User | None) -> None: def _protokolliere(self, ergebnis: PruefErgebnis, user: User | None) -> None:

View File

@@ -33,6 +33,7 @@ class BggPruefClient:
self, self,
mindestabstand_sekunden: float = 1.0, mindestabstand_sekunden: float = 1.0,
*, *,
token: str | None = None,
transport: httpx.BaseTransport | None = None, transport: httpx.BaseTransport | None = None,
uhr: Callable[[], float] = time.monotonic, uhr: Callable[[], float] = time.monotonic,
schlafen: Callable[[float], None] = time.sleep, schlafen: Callable[[float], None] = time.sleep,
@@ -42,8 +43,11 @@ class BggPruefClient:
self._zuletzt: float | None = None self._zuletzt: float | None = None
self._uhr = uhr self._uhr = uhr
self._schlafen = schlafen self._schlafen = schlafen
headers: dict[str, str] = {}
if token:
headers["Authorization"] = f"Bearer {token}"
self._client = httpx.Client( self._client = httpx.Client(
base_url=BASIS_URL, timeout=timeout, transport=transport base_url=BASIS_URL, timeout=timeout, transport=transport, headers=headers
) )
def schliessen(self) -> None: def schliessen(self) -> None:

View File

@@ -87,6 +87,8 @@ class NeuheitenPlugin(BasePlugin):
self._quellen_sperre = threading.Lock() self._quellen_sperre = threading.Lock()
self._suchbegriffe: list[str] = [] self._suchbegriffe: list[str] = []
self._max_treffer_pro_suche = 25 self._max_treffer_pro_suche = 25
self._jahr_filter = True
self._deutscher_titel_filter = True
@self.router.get("/neuheiten") @self.router.get("/neuheiten")
def seite( def seite(
@@ -396,13 +398,23 @@ class NeuheitenPlugin(BasePlugin):
super().on_load(context) super().on_load(context)
import os import os
self._suchbegriffe = _umgebung_liste("SPIELE_BGG_SUCHBEGRIFFE", "brettspiel") self._suchbegriffe = _umgebung_liste(
"SPIELE_BGG_SUCHBEGRIFFE",
"brettspiel, gesellschaftsspiel, familienspiel, strategiespiel, "
"kartenspiel, würfelspiel",
)
try: try:
self._max_treffer_pro_suche = int( self._max_treffer_pro_suche = int(
os.environ.get("SPIELE_BGG_MAX_TREFFER_PRO_SUCHE", "25") os.environ.get("SPIELE_BGG_MAX_TREFFER_PRO_SUCHE", "50")
) )
except ValueError: except ValueError:
self._max_treffer_pro_suche = 25 self._max_treffer_pro_suche = 50
self._jahr_filter = (
os.environ.get("SPIELE_BGG_JAHR_FILTER", "1").strip() != "0"
)
self._deutscher_titel_filter = (
os.environ.get("SPIELE_BGG_DEUTSCHE_TITEL_FILTER", "1").strip() != "0"
)
if os.environ.get("SPIELE_BGG_SYNC_AKTIV", "1").strip() == "1": if os.environ.get("SPIELE_BGG_SYNC_AKTIV", "1").strip() == "1":
self._scheduler_starten(os.environ.get("SPIELE_BGG_SYNC_INTERVALL_STUNDEN")) self._scheduler_starten(os.environ.get("SPIELE_BGG_SYNC_INTERVALL_STUNDEN"))
@@ -445,6 +457,8 @@ class NeuheitenPlugin(BasePlugin):
self.context.session_factory, self.context.session_factory,
client, client,
max_treffer_pro_suchbegriff=self._max_treffer_pro_suche, max_treffer_pro_suchbegriff=self._max_treffer_pro_suche,
jahr_filter=self._jahr_filter,
deutscher_titel_filter=self._deutscher_titel_filter,
) )
return service.synchronisiere(suchbegriffe) return service.synchronisiere(suchbegriffe)
finally: finally:

View File

@@ -3,16 +3,19 @@
- Update statt Duplikat: bestehende Einträge werden über die eindeutige - Update statt Duplikat: bestehende Einträge werden über die eindeutige
BGG-ID gefunden und aktualisiert (Status bleibt erhalten). BGG-ID gefunden und aktualisiert (Status bleibt erhalten).
- Erweiterungen werden im Client und hier auf Elementebene gefiltert. - Erweiterungen werden im Client und hier auf Elementebene gefiltert.
- Prototypen: Die BGG-XML-API2 liefert keinen verlässlichen Prototyp-Marker; - Prototypen: Titel mit Prototyp-Schlüsselwörtern werden ausgeschlossen.
als Heuristik werden Titel mit Prototyp-Schlüsselwörtern ausgeschlossen - Jahresfilter: optional nur Spiele mit Erscheinungsjahr ≥ aktuelles Jahr.
(dokumentierte Einschränkung, siehe README). - Deutsch-Filter: optional nur Spiele mit deutsch klingendem Titel.
""" """
from __future__ import annotations from __future__ import annotations
import logging import logging
import os
import re import re
import unicodedata
from collections.abc import Iterable, Sequence from collections.abc import Iterable, Sequence
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import date
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.orm import Session, sessionmaker
@@ -28,12 +31,58 @@ PROTOTYP_MUSTER = re.compile(
THING_BATCH_GROESSE = 20 THING_BATCH_GROESSE = 20
#: Funktionswörter, die stark auf einen deutschen Spieltitel hindeuten.
_DEUTSCHE_INDIKATOREN = frozenset({
"der", "die", "das", "den", "dem", "des",
"ein", "eine", "einen", "einem", "einer", "eines",
"und", "für", "von", "vom", "zu", "zum", "zur",
"im", "am", "beim", "auf", "mit", "aus",
})
#: Typische Begriffe deutscher Spieltitel (+2 Punkte je Treffer).
_DEUTSCHE_SPIELWOERTER = frozenset({
"abenteuer", "berg", "blitz", "burg", "dorf", "drache", "drachen",
"erde", "feuer", "gärten", "haus", "himmel", "insel", "jagd",
"kaiser", "könig", "könige", "königreich", "land", "länder",
"meister", "mond", "norden", "osten", "pfad", "reich", "ritter",
"schiff", "schloss", "see", "spiel", "spiele", "spielen",
"stadt", "stern", "süden", "turm", "wald", "weg", "welt",
"westen", "wiese", "wilder", "wüste", "zauber", "zeit",
})
#: Wörter, die auf einen englischen Titel hindeuten (1 Punkt je Treffer).
_ENGLISCHE_INDIKATOREN = frozenset({
"the", "of", "and", "game", "games", "card", "cards", "dice", "board",
})
_UM_LAUTE = frozenset("äöüßÄÖÜ")
_WORT_MUSTER = re.compile(r"[^\W\d_]+", re.UNICODE)
def ist_deutscher_titel(titel: str) -> bool:
"""Heuristik: > 0 spricht für einen deutschen Titel.
Umlaute/ß zählen am stärksten (+3), deutsche Funktionswörter +1,
typische deutsche Spielbegriffe +2, englische Indikatoren 1.
"""
if not titel:
return False
score = 0
if any(zeichen in _UM_LAUTE for zeichen in titel):
score += 3
woerter = {wort.casefold() for wort in _WORT_MUSTER.findall(titel)}
score += sum(1 for wort in woerter if wort in _DEUTSCHE_INDIKATOREN)
score += sum(2 for wort in woerter if wort in _DEUTSCHE_SPIELWOERTER)
score -= sum(1 for wort in woerter if wort in _ENGLISCHE_INDIKATOREN)
return score > 0
def ist_moeglicher_prototyp(titel: str) -> bool: def ist_moeglicher_prototyp(titel: str) -> bool:
"""Heuristik: Titel klingt nach Prototyp/Protoversion.""" """Heuristik: Titel klingt nach Prototyp/Protoversion."""
return bool(PROTOTYP_MUSTER.search(titel)) return bool(PROTOTYP_MUSTER.search(titel))
def aktuelles_jahr() -> int:
return date.today().year
@dataclass @dataclass
class SyncErgebnis: class SyncErgebnis:
"""Zusammenfassung eines Sync-Laufs.""" """Zusammenfassung eines Sync-Laufs."""
@@ -51,7 +100,7 @@ class SyncErgebnis:
return self.abbruch return self.abbruch
text = ( text = (
f"{self.neu} neu, {self.aktualisiert} aktualisiert, " f"{self.neu} neu, {self.aktualisiert} aktualisiert, "
f"{self.gefiltert} gefiltert (Erweiterungen/Prototypen)" f"{self.gefiltert} gefiltert (Erweiterungen/Prototypen/Jahr/Sprache)"
) )
if self.fehler: if self.fehler:
text += f"; {len(self.fehler)} Fehler" text += f"; {len(self.fehler)} Fehler"
@@ -96,11 +145,16 @@ class SyncService:
*, *,
thing_batch_groesse: int = THING_BATCH_GROESSE, thing_batch_groesse: int = THING_BATCH_GROESSE,
max_treffer_pro_suchbegriff: int | None = None, max_treffer_pro_suchbegriff: int | None = None,
jahr_filter: bool = True,
deutscher_titel_filter: bool = True,
) -> None: ) -> None:
self.session_factory = session_factory self.session_factory = session_factory
self.client = client self.client = client
self.thing_batch_groesse = thing_batch_groesse self.thing_batch_groesse = thing_batch_groesse
self.max_treffer_pro_suchbegriff = max_treffer_pro_suchbegriff self.max_treffer_pro_suchbegriff = max_treffer_pro_suchbegriff
self.jahr_filter = jahr_filter
self.deutscher_titel_filter = deutscher_titel_filter
self._min_jahr = aktuelles_jahr()
def synchronisiere(self, suchbegriffe: Iterable[str]) -> SyncErgebnis: def synchronisiere(self, suchbegriffe: Iterable[str]) -> SyncErgebnis:
ergebnis = SyncErgebnis() ergebnis = SyncErgebnis()
@@ -139,6 +193,13 @@ class SyncService:
if ist_moeglicher_prototyp(spiel.titel): if ist_moeglicher_prototyp(spiel.titel):
ergebnis.gefiltert += 1 ergebnis.gefiltert += 1
continue continue
if self.jahr_filter and spiel.erscheinungsjahr is not None:
if spiel.erscheinungsjahr < self._min_jahr:
ergebnis.gefiltert += 1
continue
if self.deutscher_titel_filter and not ist_deutscher_titel(spiel.titel):
ergebnis.gefiltert += 1
continue
if speichere_oder_aktualisiere(db, spiel): if speichere_oder_aktualisiere(db, spiel):
ergebnis.neu += 1 ergebnis.neu += 1
else: else:

View File

@@ -389,3 +389,25 @@ def test_bgg_pruefclient_toleroert_fehler():
assert client.vorgaenger(99) == [] assert client.vorgaenger(99) == []
finally: finally:
client.schliessen() client.schliessen()
def test_bgg_pruefclient_sendet_token_header():
from plugins.dedup.bgg import BggPruefClient
gesehen: list[httpx.Request] = []
def handler(request: httpx.Request) -> httpx.Response:
gesehen.append(request)
return httpx.Response(200, content=THING_XML.encode())
client = BggPruefClient(
mindestabstand_sekunden=0.0,
token="mein-token",
transport=httpx.MockTransport(handler),
)
try:
assert client.alternativen(99) == ["Catan Expansion", "Siedler-Erweiterung"]
finally:
client.schliessen()
assert gesehen
assert gesehen[0].headers["Authorization"] == "Bearer mein-token"

View File

@@ -94,7 +94,9 @@ def test_sync_legt_eintraege_an(session_factory):
suchergebnisse={"catan": [SuchTreffer(13, "Catan", 1995)]}, suchergebnisse={"catan": [SuchTreffer(13, "Catan", 1995)]},
details_pro_id={13: _spiel()}, details_pro_id={13: _spiel()},
) )
service = SyncService(session_factory, client) service = SyncService(
session_factory, client, jahr_filter=False, deutscher_titel_filter=False
)
ergebnis = service.synchronisiere(["catan"]) ergebnis = service.synchronisiere(["catan"])
@@ -117,7 +119,9 @@ def test_sync_aktualisiert_bestehende_eintraege_statt_duplikate(session_factory)
suchergebnisse={"catan": [SuchTreffer(13, "Catan", 1995)]}, suchergebnisse={"catan": [SuchTreffer(13, "Catan", 1995)]},
details_pro_id={13: _spiel()}, details_pro_id={13: _spiel()},
) )
service = SyncService(session_factory, client) service = SyncService(
session_factory, client, jahr_filter=False, deutscher_titel_filter=False
)
service.synchronisiere(["catan"]) service.synchronisiere(["catan"])
@@ -142,7 +146,9 @@ def test_sync_veraenderter_titel_gleiche_bgg_id_kein_duplikat(session_factory):
suchergebnisse={"catan": [SuchTreffer(13, "Catan", 1995)]}, suchergebnisse={"catan": [SuchTreffer(13, "Catan", 1995)]},
details_pro_id={13: _spiel()}, details_pro_id={13: _spiel()},
) )
service = SyncService(session_factory, client) service = SyncService(
session_factory, client, jahr_filter=False, deutscher_titel_filter=False
)
service.synchronisiere(["catan"]) service.synchronisiere(["catan"])
client.details_pro_id[13] = _spiel(titel="Catan (Neuausgabe)") client.details_pro_id[13] = _spiel(titel="Catan (Neuausgabe)")
@@ -176,7 +182,9 @@ def test_sync_filtert_erweiterungen(session_factory):
55: _spiel(55, "Catan: Erweiterung", typ="boardgameexpansion"), 55: _spiel(55, "Catan: Erweiterung", typ="boardgameexpansion"),
}, },
) )
service = SyncService(session_factory, client) service = SyncService(
session_factory, client, jahr_filter=False, deutscher_titel_filter=False
)
ergebnis = service.synchronisiere(["catan"]) ergebnis = service.synchronisiere(["catan"])
@@ -200,7 +208,9 @@ def test_sync_filtert_prototypen(session_factory):
78: _spiel(78, "My Prototype Game"), 78: _spiel(78, "My Prototype Game"),
}, },
) )
service = SyncService(session_factory, client) service = SyncService(
session_factory, client, jahr_filter=False, deutscher_titel_filter=False
)
ergebnis = service.synchronisiere(["essen"]) ergebnis = service.synchronisiere(["essen"])
@@ -216,6 +226,61 @@ def test_prototyp_heuristik():
assert not ist_moeglicher_prototyp("Die Burgen von Burgund") assert not ist_moeglicher_prototyp("Die Burgen von Burgund")
# ---------- Filter: Jahr und deutscher Titel ----------
def test_sync_filtert_nach_jahr_und_deutschem_titel(session_factory):
aktuelles = 2026
client = FakeBggClient(
suchergebnisse={
"mix": [
SuchTreffer(13, "Catan", aktuelles - 1),
SuchTreffer(42, "Die neuen Siedler", aktuelles),
SuchTreffer(99, "Future Game", aktuelles + 1),
SuchTreffer(88, "The Old English Game", aktuelles),
]
},
details_pro_id={
13: _spiel(13, "Catan", erscheinungsjahr=aktuelles - 1),
42: _spiel(42, "Die neuen Siedler", erscheinungsjahr=aktuelles),
99: _spiel(99, "Future Game", erscheinungsjahr=aktuelles + 1),
88: _spiel(88, "The Old English Game", erscheinungsjahr=aktuelles),
},
)
service = SyncService(session_factory, client)
service._min_jahr = aktuelles
ergebnis = service.synchronisiere(["mix"])
assert ergebnis.neu == 1 # nur "Die neuen Siedler" überlebt beide Filter
assert ergebnis.gefiltert == 3 # 1 zu alt + 1 zukünftig aber englisch + 1 aktuell aber englisch
eintraege = _alle_eintraege(session_factory)
assert [e.bgg_id for e in eintraege] == [42]
def test_sync_ohne_filter_behält_alle_jahre_und_sprachen(session_factory):
client = FakeBggClient(
suchergebnisse={
"mix": [
SuchTreffer(13, "Catan", 1995),
SuchTreffer(88, "The English Game", 2026),
]
},
details_pro_id={
13: _spiel(13, "Catan", erscheinungsjahr=1995),
88: _spiel(88, "The English Game", erscheinungsjahr=2026),
},
)
service = SyncService(
session_factory, client, jahr_filter=False, deutscher_titel_filter=False
)
ergebnis = service.synchronisiere(["mix"])
assert ergebnis.neu == 2
assert ergebnis.gefiltert == 0
# ---------- Robustheit ---------- # ---------- Robustheit ----------
@@ -233,7 +298,9 @@ def test_401_bricht_sync_ab_mit_klarer_meldung(session_factory):
suchergebnisse={"gut": [SuchTreffer(13, "Catan", 1995)]}, suchergebnisse={"gut": [SuchTreffer(13, "Catan", 1995)]},
details_pro_id={13: _spiel()}, details_pro_id={13: _spiel()},
) )
service = SyncService(session_factory, client) service = SyncService(
session_factory, client, jahr_filter=False, deutscher_titel_filter=False
)
ergebnis = service.synchronisiere(["gut", "abgelehnt", "dritter"]) ergebnis = service.synchronisiere(["gut", "abgelehnt", "dritter"])
@@ -255,7 +322,9 @@ def test_fehler_bei_einem_suchbegriff_bricht_lauf_nicht_ab(session_factory):
suchergebnisse={"gut": [SuchTreffer(13, "Catan", 1995)]}, suchergebnisse={"gut": [SuchTreffer(13, "Catan", 1995)]},
details_pro_id={13: _spiel()}, details_pro_id={13: _spiel()},
) )
service = SyncService(session_factory, client) service = SyncService(
session_factory, client, jahr_filter=False, deutscher_titel_filter=False
)
ergebnis = service.synchronisiere(["kaputt", "gut"]) ergebnis = service.synchronisiere(["kaputt", "gut"])

View File

@@ -194,6 +194,8 @@ def test_sync_endpunkt_rollen_pruefung(app, settings):
def test_manueller_sync_legt_eintraege_an(monkeypatch, settings): def test_manueller_sync_legt_eintraege_an(monkeypatch, settings):
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
monkeypatch.setenv("SPIELE_BGG_JAHR_FILTER", "0")
monkeypatch.setenv("SPIELE_BGG_DEUTSCHE_TITEL_FILTER", "0")
fake = FakeBggClient( fake = FakeBggClient(
treffer=[SuchTreffer(13, "Catan", 1995)], treffer=[SuchTreffer(13, "Catan", 1995)],
spiel_details=[ spiel_details=[