fixed tests
This commit is contained in:
@@ -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],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user