Refactor blocklist log retrieval via service layer and add fail2ban DB repo
This commit is contained in:
138
backend/tests/test_repositories/test_fail2ban_db_repo.py
Normal file
138
backend/tests/test_repositories/test_fail2ban_db_repo.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""Tests for the fail2ban_db repository.
|
||||
|
||||
These tests use an in-memory sqlite file created under pytest's tmp_path and
|
||||
exercise the core query functions used by the services.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import aiosqlite
|
||||
import pytest
|
||||
|
||||
from app.repositories import fail2ban_db_repo
|
||||
|
||||
|
||||
async def _create_bans_table(db: aiosqlite.Connection) -> None:
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE bans (
|
||||
jail TEXT,
|
||||
ip TEXT,
|
||||
timeofban INTEGER,
|
||||
bancount INTEGER,
|
||||
data TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_db_nonempty_returns_false_when_table_is_empty(tmp_path: Path) -> None:
|
||||
db_path = str(tmp_path / "fail2ban.db")
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await _create_bans_table(db)
|
||||
|
||||
assert await fail2ban_db_repo.check_db_nonempty(db_path) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_db_nonempty_returns_true_when_row_exists(tmp_path: Path) -> None:
|
||||
db_path = str(tmp_path / "fail2ban.db")
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await _create_bans_table(db)
|
||||
await db.execute(
|
||||
"INSERT INTO bans (jail, ip, timeofban, bancount, data) VALUES (?, ?, ?, ?, ?)",
|
||||
("jail1", "1.2.3.4", 123, 1, "{}"),
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
assert await fail2ban_db_repo.check_db_nonempty(db_path) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_currently_banned_filters_and_pagination(tmp_path: Path) -> None:
|
||||
db_path = str(tmp_path / "fail2ban.db")
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await _create_bans_table(db)
|
||||
# Three bans; one is from the blocklist-import jail.
|
||||
await db.executemany(
|
||||
"INSERT INTO bans (jail, ip, timeofban, bancount, data) VALUES (?, ?, ?, ?, ?)",
|
||||
[
|
||||
("jail1", "1.1.1.1", 10, 1, "{}"),
|
||||
("blocklist-import", "2.2.2.2", 20, 2, "{}"),
|
||||
("jail1", "3.3.3.3", 30, 3, "{}"),
|
||||
],
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
records, total = await fail2ban_db_repo.get_currently_banned(
|
||||
db_path=db_path,
|
||||
since=15,
|
||||
origin="selfblock",
|
||||
limit=10,
|
||||
offset=0,
|
||||
)
|
||||
|
||||
# Only the non-blocklist row with timeofban >= 15 should remain.
|
||||
assert total == 1
|
||||
assert len(records) == 1
|
||||
assert records[0].ip == "3.3.3.3"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_ban_counts_by_bucket_ignores_out_of_range_buckets(tmp_path: Path) -> None:
|
||||
db_path = str(tmp_path / "fail2ban.db")
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await _create_bans_table(db)
|
||||
await db.executemany(
|
||||
"INSERT INTO bans (jail, ip, timeofban, bancount, data) VALUES (?, ?, ?, ?, ?)",
|
||||
[
|
||||
("jail1", "1.1.1.1", 5, 1, "{}"),
|
||||
("jail1", "2.2.2.2", 15, 1, "{}"),
|
||||
("jail1", "3.3.3.3", 35, 1, "{}"),
|
||||
],
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
counts = await fail2ban_db_repo.get_ban_counts_by_bucket(
|
||||
db_path=db_path,
|
||||
since=0,
|
||||
bucket_secs=10,
|
||||
num_buckets=3,
|
||||
)
|
||||
|
||||
assert counts == [1, 1, 0]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_history_page_and_for_ip(tmp_path: Path) -> None:
|
||||
db_path = str(tmp_path / "fail2ban.db")
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await _create_bans_table(db)
|
||||
await db.executemany(
|
||||
"INSERT INTO bans (jail, ip, timeofban, bancount, data) VALUES (?, ?, ?, ?, ?)",
|
||||
[
|
||||
("jail1", "1.1.1.1", 100, 1, "{}"),
|
||||
("jail1", "1.1.1.1", 200, 2, "{}"),
|
||||
("jail1", "2.2.2.2", 300, 3, "{}"),
|
||||
],
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
page, total = await fail2ban_db_repo.get_history_page(
|
||||
db_path=db_path,
|
||||
since=None,
|
||||
jail="jail1",
|
||||
ip_filter="1.1.1",
|
||||
page=1,
|
||||
page_size=10,
|
||||
)
|
||||
|
||||
assert total == 2
|
||||
assert len(page) == 2
|
||||
assert page[0].ip == "1.1.1.1"
|
||||
|
||||
history_for_ip = await fail2ban_db_repo.get_history_for_ip(db_path=db_path, ip="2.2.2.2")
|
||||
assert len(history_for_ip) == 1
|
||||
assert history_for_ip[0].ip == "2.2.2.2"
|
||||
62
backend/tests/test_repositories/test_geo_cache_repo.py
Normal file
62
backend/tests/test_repositories/test_geo_cache_repo.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""Tests for the geo cache repository."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import aiosqlite
|
||||
import pytest
|
||||
|
||||
from app.repositories import geo_cache_repo
|
||||
|
||||
|
||||
async def _create_geo_cache_table(db: aiosqlite.Connection) -> None:
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS geo_cache (
|
||||
ip TEXT PRIMARY KEY,
|
||||
country_code TEXT,
|
||||
country_name TEXT,
|
||||
asn TEXT,
|
||||
org TEXT,
|
||||
cached_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
)
|
||||
"""
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unresolved_ips_returns_empty_when_none_exist(tmp_path: Path) -> None:
|
||||
db_path = str(tmp_path / "geo_cache.db")
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await _create_geo_cache_table(db)
|
||||
await db.execute(
|
||||
"INSERT INTO geo_cache (ip, country_code, country_name, asn, org) VALUES (?, ?, ?, ?, ?)",
|
||||
("1.1.1.1", "DE", "Germany", "AS123", "Test"),
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
ips = await geo_cache_repo.get_unresolved_ips(db)
|
||||
|
||||
assert ips == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unresolved_ips_returns_pending_ips(tmp_path: Path) -> None:
|
||||
db_path = str(tmp_path / "geo_cache.db")
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await _create_geo_cache_table(db)
|
||||
await db.executemany(
|
||||
"INSERT INTO geo_cache (ip, country_code) VALUES (?, ?)",
|
||||
[
|
||||
("2.2.2.2", None),
|
||||
("3.3.3.3", None),
|
||||
("4.4.4.4", "US"),
|
||||
],
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
ips = await geo_cache_repo.get_unresolved_ips(db)
|
||||
|
||||
assert sorted(ips) == ["2.2.2.2", "3.3.3.3"]
|
||||
Reference in New Issue
Block a user