Add origin field and filter for ban sources (Tasks 1 & 2)

- Task 1: Mark imported blocklist IP addresses
  - Add BanOrigin type and _derive_origin() to ban.py model
  - Populate origin field in ban_service list_bans() and bans_by_country()
  - BanTable and MapPage companion table show origin badge column
  - Tests: origin derivation in test_ban_service.py and test_dashboard.py

- Task 2: Add origin filter to dashboard and world map
  - ban_service: _origin_sql_filter() helper; origin param on list_bans()
    and bans_by_country()
  - dashboard router: optional origin query param forwarded to service
  - Frontend: BanOriginFilter type + BAN_ORIGIN_FILTER_LABELS in ban.ts
  - fetchBans / fetchBansByCountry forward origin to API
  - useBans / useMapData accept and pass origin; page resets on change
  - BanTable accepts origin prop; DashboardPage adds segmented filter
  - MapPage adds origin Select next to time-range picker
  - Tests: origin filter assertions in test_ban_service and test_dashboard
This commit is contained in:
2026-03-07 20:03:43 +01:00
parent 706d2e1df8
commit 53d664de4f
28 changed files with 1637 additions and 103 deletions

View File

@@ -447,3 +447,90 @@ class TestPreviewLog:
data = resp.json()
assert data["total_lines"] == 1
assert data["matched_count"] == 1
# ---------------------------------------------------------------------------
# GET /api/config/map-color-thresholds
# ---------------------------------------------------------------------------
class TestGetMapColorThresholds:
"""Tests for ``GET /api/config/map-color-thresholds``."""
async def test_200_returns_thresholds(self, config_client: AsyncClient) -> None:
"""GET /api/config/map-color-thresholds returns 200 with current values."""
resp = await config_client.get("/api/config/map-color-thresholds")
assert resp.status_code == 200
data = resp.json()
assert "threshold_high" in data
assert "threshold_medium" in data
assert "threshold_low" in data
# Should return defaults after setup
assert data["threshold_high"] == 100
assert data["threshold_medium"] == 50
assert data["threshold_low"] == 20
# ---------------------------------------------------------------------------
# PUT /api/config/map-color-thresholds
# ---------------------------------------------------------------------------
class TestUpdateMapColorThresholds:
"""Tests for ``PUT /api/config/map-color-thresholds``."""
async def test_200_updates_thresholds(self, config_client: AsyncClient) -> None:
"""PUT /api/config/map-color-thresholds returns 200 and updates settings."""
update_payload = {
"threshold_high": 200,
"threshold_medium": 80,
"threshold_low": 30,
}
resp = await config_client.put(
"/api/config/map-color-thresholds", json=update_payload
)
assert resp.status_code == 200
data = resp.json()
assert data["threshold_high"] == 200
assert data["threshold_medium"] == 80
assert data["threshold_low"] == 30
# Verify the values persist
get_resp = await config_client.get("/api/config/map-color-thresholds")
assert get_resp.status_code == 200
get_data = get_resp.json()
assert get_data["threshold_high"] == 200
assert get_data["threshold_medium"] == 80
assert get_data["threshold_low"] == 30
async def test_400_for_invalid_order(self, config_client: AsyncClient) -> None:
"""PUT /api/config/map-color-thresholds returns 400 if thresholds are misordered."""
invalid_payload = {
"threshold_high": 50,
"threshold_medium": 50,
"threshold_low": 20,
}
resp = await config_client.put(
"/api/config/map-color-thresholds", json=invalid_payload
)
assert resp.status_code == 400
assert "high > medium > low" in resp.json()["detail"]
async def test_400_for_non_positive_values(
self, config_client: AsyncClient
) -> None:
"""PUT /api/config/map-color-thresholds returns 422 for non-positive values (Pydantic validation)."""
invalid_payload = {
"threshold_high": 100,
"threshold_medium": 50,
"threshold_low": 0,
}
resp = await config_client.put(
"/api/config/map-color-thresholds", json=invalid_payload
)
# Pydantic validates ge=1 constraint before our service code runs
assert resp.status_code == 422