fixed tests

This commit is contained in:
2026-05-15 20:41:05 +02:00
parent 96ce516ecf
commit 77df5d5d65
50 changed files with 1482 additions and 5089 deletions

View File

@@ -15,11 +15,11 @@ under the key ``"blocklist_schedule"``.
from __future__ import annotations
import json
from datetime import UTC
from typing import TYPE_CHECKING
import aiohttp
import aiosqlite
from app.utils.logging_compat import get_logger
from app.exceptions import BlocklistSourceHasLogsError
from app.models.blocklist import (
@@ -37,6 +37,7 @@ from app.repositories import blocklist_repo, import_log_repo, settings_repo
from app.services.blocklist_downloader import BlocklistDownloader
from app.services.blocklist_import_workflow import BlocklistImportWorkflow
from app.services.blocklist_parser import BlocklistParser
from app.utils.logging_compat import get_logger
from app.utils.pagination import create_pagination_metadata
if TYPE_CHECKING:
@@ -200,9 +201,7 @@ async def update_source(
await validate_blocklist_url(url)
updated = await blocklist_repo.update_source(
db, source_id, name=name, url=url, enabled=enabled
)
updated = await blocklist_repo.update_source(db, source_id, name=name, url=url, enabled=enabled)
if not updated:
return None
source = await get_source(db, source_id)
@@ -473,8 +472,7 @@ async def get_schedule(db: aiosqlite.Connection) -> ScheduleConfig:
if raw is None:
return _DEFAULT_SCHEDULE
try:
data = json.loads(raw)
return ScheduleConfig.model_validate(data)
return ScheduleConfig.model_validate_json(raw)
except (json.JSONDecodeError, ValueError) as exc:
log.warning("blocklist_schedule_invalid", raw=raw, error=type(exc).__name__)
return _DEFAULT_SCHEDULE
@@ -493,9 +491,7 @@ async def set_schedule(
Returns:
The saved configuration (same object after validation).
"""
await settings_repo.set_setting(
db, _SCHEDULE_SETTINGS_KEY, config.model_dump_json()
)
await settings_repo.set_setting(db, _SCHEDULE_SETTINGS_KEY, config.model_dump_json())
log.info("blocklist_schedule_updated", frequency=config.frequency, hour=config.hour)
return config
@@ -517,8 +513,12 @@ async def get_schedule_info(
"""
config = await get_schedule(db)
last_log = await import_log_repo.get_last_log(db)
last_run_at = last_log["timestamp"] if last_log else None
last_run_errors: bool | None = (last_log["errors"] is not None) if last_log else None
last_run_at = None
if last_log is not None:
from datetime import datetime
last_run_at = datetime.fromtimestamp(last_log.timestamp, tz=UTC).isoformat()
last_run_errors: bool | None = (last_log.errors is not None) if last_log else None
return ScheduleInfo(
config=config,
next_run_at=next_run_at,
@@ -574,9 +574,7 @@ async def list_import_logs(
Returns:
:class:`~app.models.blocklist.ImportLogListResponse`.
"""
items, total = await import_log_repo.list_logs(
db, source_id=source_id, page=page, page_size=page_size
)
items, total = await import_log_repo.list_logs(db, source_id=source_id, page=page, page_size=page_size)
return ImportLogListResponse(
items=[ImportLogEntry.model_validate(i) for i in items],

View File

@@ -13,8 +13,6 @@ import re
import tempfile
from pathlib import Path
from app.utils.logging_compat import get_logger
from app.exceptions import (
ConfigWriteError,
FilterAlreadyExistsError,
@@ -27,6 +25,7 @@ from app.exceptions import (
)
from app.models.config import (
AssignFilterRequest,
FilterConfig,
FilterConfigUpdate,
FilterCreateRequest,
FilterUpdateRequest,
@@ -46,6 +45,7 @@ from app.utils.config_file_utils import (
set_jail_local_key_sync,
)
from app.utils.jail_socket import reload_all
from app.utils.logging_compat import get_logger
from app.utils.regex_validator import RegexTimeoutError, validate_regex_pattern
log = get_logger(__name__)
@@ -54,6 +54,7 @@ log = get_logger(__name__)
# Internal wrappers for shared config helpers.
# ---------------------------------------------------------------------------
def _parse_jails_sync(config_dir: Path) -> tuple[dict[str, dict[str, str]], Path]:
return _config_file_parse_jails_sync(config_dir)
@@ -85,6 +86,7 @@ def _resolve_filter(raw_filter: str, jail_name: str, mode: str) -> str:
result = result.replace("%(mode)s", mode)
return result
# ---------------------------------------------------------------------------
# Internal helpers imported from the shared config helper module.
# ---------------------------------------------------------------------------
@@ -366,7 +368,7 @@ async def list_filters(
)
log.info("filters_listed", total=len(filters), active=sum(1 for f in filters if f.active))
return DomainFilterList(filters=filters, total=len(filters))
return DomainFilterList(items=filters, total=len(filters))
async def get_filter(
@@ -428,7 +430,7 @@ async def get_filter(
else:
raise FilterNotFoundError(base_name)
content, has_local, source_path = await run_blocking( _read)
content, has_local, source_path = await run_blocking(_read)
cfg = conffile_parser.parse_filter_file(content, name=base_name, filename=f"{base_name}.conf")
@@ -524,7 +526,7 @@ async def update_filter(
content = conffile_parser.serialize_filter_config(merged)
filter_d = Path(config_dir) / "filter.d"
await run_blocking( _write_filter_local_sync, filter_d, base_name, content)
await run_blocking(_write_filter_local_sync, filter_d, base_name, content)
if do_reload:
try:
@@ -580,7 +582,7 @@ async def create_filter(
if conf_path.is_file() or local_path.is_file():
raise FilterAlreadyExistsError(req.name)
await run_blocking( _check_not_exists)
await run_blocking(_check_not_exists)
# Validate regex patterns.
patterns: list[str] = list(req.failregex) + list(req.ignoreregex)
@@ -598,7 +600,7 @@ async def create_filter(
)
content = conffile_parser.serialize_filter_config(cfg)
await run_blocking( _write_filter_local_sync, filter_d, req.name, content)
await run_blocking(_write_filter_local_sync, filter_d, req.name, content)
if do_reload:
try:
@@ -663,7 +665,7 @@ async def delete_filter(
log.info("filter_local_deleted", filter=base_name, path=str(local_path))
await run_blocking( _delete)
await run_blocking(_delete)
async def assign_filter_to_jail(
@@ -713,9 +715,10 @@ async def assign_filter_to_jail(
if not conf_exists and not local_exists:
raise FilterNotFoundError(req.filter_name)
await run_blocking( _check_filter)
await run_blocking(_check_filter)
await run_blocking(set_jail_local_key_sync,
await run_blocking(
set_jail_local_key_sync,
Path(config_dir),
jail_name,
"filter",

View File

@@ -21,10 +21,10 @@ import time
from typing import TYPE_CHECKING
import aiohttp
from app.utils.logging_compat import get_logger
from app.models.geo import GeoInfo
from app.repositories import geo_cache_repo
from app.utils.logging_compat import get_logger
if TYPE_CHECKING:
import collections.abc
@@ -40,14 +40,10 @@ log = get_logger(__name__)
# ---------------------------------------------------------------------------
#: ip-api.com single-IP lookup endpoint (HTTP only on the free tier).
_API_URL: str = (
"http://ip-api.com/json/{ip}?fields=status,message,country,countryCode,org,as"
)
_API_URL: str = "http://ip-api.com/json/{ip}?fields=status,message,country,countryCode,org,as"
#: ip-api.com batch endpoint — accepts up to 100 IPs per POST.
_BATCH_API_URL: str = (
"http://ip-api.com/batch?fields=status,message,country,countryCode,org,as,query"
)
_BATCH_API_URL: str = "http://ip-api.com/batch?fields=status,message,country,countryCode,org,as,query"
#: Maximum IPs per batch request (ip-api.com hard limit is 100).
_BATCH_SIZE: int = 100
@@ -217,9 +213,7 @@ class GeoCache:
await self.clear_neg_cache()
geo_map = await self.lookup_batch(unresolved, http_session, db=db)
resolved_count = sum(
1 for info in geo_map.values() if info.country_code is not None
)
resolved_count = sum(1 for info in geo_map.values() if info.country_code is not None)
log.info(
"geo_re_resolve_complete",
@@ -398,7 +392,7 @@ class GeoCache:
asn=result.asn,
org=result.org,
)
except (OSError) as exc:
except OSError as exc:
log.warning("geo_persist_failed", ip=ip, error=type(exc).__name__)
log.debug("geo_lookup_success_mmdb", ip=ip, country=result.country_code)
return result
@@ -412,7 +406,7 @@ class GeoCache:
if db is not None:
try:
await geo_cache_repo.upsert_neg_entry_and_commit(db=db, ip=ip)
except (OSError) as exc:
except OSError as exc:
log.warning("geo_persist_neg_failed", ip=ip, error=type(exc).__name__)
return GeoInfo(country_code=None, country_name=None, asn=None, org=None)
@@ -439,7 +433,7 @@ class GeoCache:
asn=result.asn,
org=result.org,
)
except (OSError) as exc:
except OSError as exc:
log.warning("geo_persist_failed", ip=ip, error=type(exc).__name__)
log.debug("geo_lookup_success_http", ip=ip, country=result.country_code, asn=result.asn)
return result
@@ -448,7 +442,7 @@ class GeoCache:
ip=ip,
message=data.get("message", "unknown"),
)
except (TimeoutError, aiohttp.ClientError, ValueError) as exc:
except (TimeoutError, aiohttp.ClientError, ValueError, OSError) as exc:
log.warning(
"geo_lookup_http_request_failed",
ip=ip,
@@ -585,7 +579,7 @@ class GeoCache:
if db is not None and pos_rows:
try:
await geo_cache_repo.bulk_upsert_entries_and_commit(db, pos_rows)
except (OSError) as exc:
except OSError as exc:
log.warning(
"geo_batch_persist_mmdb_failed",
count=len(pos_rows),
@@ -604,7 +598,7 @@ class GeoCache:
if db is not None and neg_ips:
try:
await geo_cache_repo.bulk_upsert_neg_entries_and_commit(db, neg_ips)
except (OSError) as exc:
except OSError as exc:
log.warning(
"geo_batch_persist_neg_failed",
count=len(neg_ips),
@@ -637,9 +631,7 @@ class GeoCache:
# If every IP in the chunk came back with country_code=None and the
# batch wasn't tiny, that almost certainly means the whole request
# was rejected (connection reset / 429). Retry after a back-off.
all_failed = all(
info.country_code is None for info in chunk_result.values()
)
all_failed = all(info.country_code is None for info in chunk_result.values())
if not all_failed or attempt >= _BATCH_MAX_RETRIES:
break
backoff = _BATCH_DELAY * (2 ** (attempt + 1))
@@ -659,9 +651,7 @@ class GeoCache:
await self._store(ip, info)
geo_result[ip] = info
if db is not None:
pos_rows.append(
(ip, info.country_code, info.country_name, info.asn, info.org)
)
pos_rows.append((ip, info.country_code, info.country_name, info.asn, info.org))
else:
# HTTP failed — record as negative cache.
async with self._cache_lock:
@@ -677,7 +667,7 @@ class GeoCache:
pos_rows,
neg_ips,
)
except (OSError) as exc:
except OSError as exc:
log.warning(
"geo_batch_persist_failed",
positive_count=len(pos_rows),
@@ -724,7 +714,7 @@ class GeoCache:
log.warning("geo_batch_non_200", status=resp.status, count=len(ips))
return fallback
data: list[dict[str, object]] = await resp.json(content_type=None)
except (TimeoutError, aiohttp.ClientError, ValueError) as exc:
except (TimeoutError, aiohttp.ClientError, ValueError, OSError) as exc:
log.warning(
"geo_batch_request_failed",
count=len(ips),
@@ -836,7 +826,7 @@ class GeoCache:
try:
await geo_cache_repo.bulk_upsert_entries_and_commit(db, rows)
except (OSError) as exc:
except OSError as exc:
log.warning("geo_flush_dirty_failed", error=type(exc).__name__)
# Re-add to dirty so they are retried on the next flush cycle.
self._dirty.update(to_flush)