Complete history archive support for dashboard/map data and mark task finished
Add source=archive option for dashboard endpoints and history service; update Docs/Tasks.md; include archive branch for list_bans, bans_by_country, ban_trend, bans_by_jail; tests for archive paths.
This commit is contained in:
@@ -11,6 +11,7 @@ from unittest.mock import AsyncMock, patch
|
||||
import aiosqlite
|
||||
import pytest
|
||||
|
||||
from app.db import init_db
|
||||
from app.services import ban_service
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -143,6 +144,29 @@ async def empty_f2b_db_path(tmp_path: Path) -> str:
|
||||
return path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def app_db_with_archive(tmp_path: Path) -> aiosqlite.Connection:
|
||||
"""Return an app database connection pre-populated with archived ban rows."""
|
||||
db_path = str(tmp_path / "app_archive.db")
|
||||
db = await aiosqlite.connect(db_path)
|
||||
db.row_factory = aiosqlite.Row
|
||||
await init_db(db)
|
||||
|
||||
await db.execute(
|
||||
"INSERT INTO history_archive (jail, ip, timeofban, bancount, data, action) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
("sshd", "1.2.3.4", _ONE_HOUR_AGO, 1, '{"matches": ["fail"], "failures": 1}', "ban"),
|
||||
)
|
||||
await db.execute(
|
||||
"INSERT INTO history_archive (jail, ip, timeofban, bancount, data, action) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
("nginx", "5.6.7.8", _ONE_HOUR_AGO, 1, '{"matches": ["fail"], "failures": 2}', "ban"),
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
yield db
|
||||
|
||||
await db.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# list_bans — happy path
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -233,6 +257,20 @@ class TestListBansHappyPath:
|
||||
|
||||
assert result.total == 3
|
||||
|
||||
async def test_source_archive_reads_from_archive(
|
||||
self, app_db_with_archive: aiosqlite.Connection
|
||||
) -> None:
|
||||
"""Using source='archive' reads from the BanGUI archive table."""
|
||||
result = await ban_service.list_bans(
|
||||
"/fake/sock",
|
||||
"24h",
|
||||
source="archive",
|
||||
app_db=app_db_with_archive,
|
||||
)
|
||||
|
||||
assert result.total == 2
|
||||
assert {item.ip for item in result.items} == {"1.2.3.4", "5.6.7.8"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# list_bans — geo enrichment
|
||||
@@ -616,6 +654,20 @@ class TestOriginFilter:
|
||||
|
||||
assert result.total == 3
|
||||
|
||||
async def test_bans_by_country_source_archive_reads_archive(
|
||||
self, app_db_with_archive: aiosqlite.Connection
|
||||
) -> None:
|
||||
"""``bans_by_country`` accepts source='archive' and reads archived rows."""
|
||||
result = await ban_service.bans_by_country(
|
||||
"/fake/sock",
|
||||
"24h",
|
||||
source="archive",
|
||||
app_db=app_db_with_archive,
|
||||
)
|
||||
|
||||
assert result.total == 2
|
||||
assert len(result.bans) == 2
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# bans_by_country — background geo resolution (Task 3)
|
||||
@@ -802,6 +854,19 @@ class TestBanTrend:
|
||||
timestamps = [b.timestamp for b in result.buckets]
|
||||
assert timestamps == sorted(timestamps)
|
||||
|
||||
async def test_ban_trend_source_archive_reads_archive(
|
||||
self, app_db_with_archive: aiosqlite.Connection
|
||||
) -> None:
|
||||
"""``ban_trend`` accepts source='archive' and uses archived rows."""
|
||||
result = await ban_service.ban_trend(
|
||||
"/fake/sock",
|
||||
"24h",
|
||||
source="archive",
|
||||
app_db=app_db_with_archive,
|
||||
)
|
||||
|
||||
assert sum(b.count for b in result.buckets) == 2
|
||||
|
||||
async def test_bans_counted_in_correct_bucket(self, tmp_path: Path) -> None:
|
||||
"""A ban at a known time appears in the expected bucket."""
|
||||
import time as _time
|
||||
@@ -1018,6 +1083,20 @@ class TestBansByJail:
|
||||
assert result.total == 3
|
||||
assert len(result.jails) == 3
|
||||
|
||||
async def test_bans_by_jail_source_archive_reads_archive(
|
||||
self, app_db_with_archive: aiosqlite.Connection
|
||||
) -> None:
|
||||
"""``bans_by_jail`` accepts source='archive' and aggregates archived rows."""
|
||||
result = await ban_service.bans_by_jail(
|
||||
"/fake/sock",
|
||||
"24h",
|
||||
source="archive",
|
||||
app_db=app_db_with_archive,
|
||||
)
|
||||
|
||||
assert result.total == 2
|
||||
assert any(j.jail == "sshd" for j in result.jails)
|
||||
|
||||
async def test_diagnostic_warning_when_zero_results_despite_data(
|
||||
self, tmp_path: Path
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user