From 848531c13428bb1b1cbd291165ccefdc20f9f208 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sun, 21 Jun 2026 11:21:20 +0200 Subject: [PATCH] docs: update tasks from E2E test run; add proxy server - Docs/Tasks.md: document 122 E2E test failures (fail2ban missing) - e2e/proxy_server.py: add HTTP proxy for frontend dev server - e2e/resources/common.resource: update test resource --- Docs/Tasks.md | 2298 ++++++++++++++++++++++++++++++++- e2e/proxy_server.py | 93 ++ e2e/resources/common.resource | 9 +- 3 files changed, 2367 insertions(+), 33 deletions(-) create mode 100644 e2e/proxy_server.py diff --git a/Docs/Tasks.md b/Docs/Tasks.md index e074e4f..f1c624a 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -1,44 +1,2282 @@ -## Task: Investigate Orphaned SQLite Shared Memory Files on Startup +# E2E Test Failure Tasks -### Issue in Detail +> Generated from Robot Framework test run on 2026-06-21. +> **Overall Result:** 123 tests, 0 passed, 122 failed, 1 skipped. +> **Root Cause:** Backend health check returns HTTP 503 because fail2ban is not installed/running (`ModuleNotFoundError: No module named 'fail2ban'`). All suites fail at `Suite Setup` → `Wait For Backend Health`. -The log shows repeated warnings: -``` -event=orphaned_sqlite_file_removed path=/data/bangui.db-shm -``` +--- -This occurs at `19:39:48` and again at `19:49:39` (after restart). The `-shm` file is SQLite's shared memory file for WAL mode. Its presence indicates **unclean shutdowns** (crashes or SIGKILL instead of graceful SIGTERM). +## Task: Fix E2E Suite Setup — Backend Health Check -### Why This Happens +**Problem:** Every test suite fails during `Suite Setup` because `Wait For Backend Health` in `e2e/resources/common.resource` polls `GET /api/v1/health` and expects HTTP 200. The backend returns HTTP 503 with body `{"status":"unavailable","fail2ban":"offline","database":"ok","scheduler":"running","cache":"initialised"}`. -1. **Docker stop timeout:** Docker sends SIGTERM, waits `stop_grace_period` (default 10s), then sends SIGKILL. The backend allows 25s for graceful shutdown, but if the container's `stop_grace_period` is shorter, the process is killed before cleanup completes. -2. **Missing connection close:** If the application crashes or is killed, SQLite connections are not closed, leaving `.wal` and `.shm` files behind. -3. **`_cleanup_wal_files()` is a workaround, not a fix:** It removes stale files on the *next* startup, but the underlying cause (unclean shutdown) remains. +**Step That Fails:** `Suite Setup` → `Wait For Backend Health` keyword (line 24 in `common.resource`). -### How to Fix It +**Files to Check:** +- `e2e/resources/common.resource` — `Wait For Backend Health` keyword +- `backend/app/routers/health.py` — health endpoint logic +- `backend/app/utils/fail2ban_client.py` — fail2ban module import +- `Docker/compose.debug.yml` — how fail2ban is wired in the Docker stack -1. **Verify Docker Compose `stop_grace_period`:** In `Docker/compose.prod.yml`, ensure the backend service has `stop_grace_period: 30s` (matching the 25s internal timeout + margin). -2. **Improve shutdown logging:** Add explicit logs when the database connection is closed during lifespan shutdown. -3. **Consider `PRAGMA journal_mode = DELETE` for single-process setups:** WAL mode is beneficial for concurrent readers, but if BanGUI runs with a single worker and single process, DELETE mode eliminates `.wal`/`.shm` files entirely. Evaluate the tradeoff. +**Reference Docs:** +- `e2e/Instructions.md` — "Requires: stack up (make up)" +- `Docs/Deployment.md` — fail2ban dependency setup +- `Makefile` — `make e2e` target -### Issues and Trapfalls +**Expected Behavior:** Either: +1. The health check accepts 503 as "backend is up but degraded" and proceeds with tests, OR +2. fail2ban is installed and running so health returns 200. -1. **WAL mode is required for concurrent reads:** If you switch to DELETE mode, readers block writers. This may degrade API performance under load. -2. **The `_cleanup_wal_files()` 10-second threshold:** Files modified within 10 seconds are skipped. If the container restarts rapidly (e.g., health check failure → restart), the files may not be cleaned up. +--- -### Documentation References +## Task: 01 Setup And Auth — Setup Page Renders All Form Fields -- **`Docs/Deployment.md`:** Docker deployment configuration and graceful shutdown behavior. -- **`Docs/Architekture.md`:** Deployment constraints and process-local state. +**Test:** `Setup Page Renders All Form Fields` +**Suite:** `01_setup_and_auth.robot` -### Tests to Write +**Step That Fails:** Parent suite setup (`Wait For Backend Health`) — backend never becomes healthy within 120s timeout. -#### 1. `test_cleanup_wal_files_removes_stale_files` -- **Setup:** Create fake `.wal` and `.shm` files with mtime > 10s ago. -- **Action:** Call `_cleanup_wal_files()`. -- **Assert:** Files are removed. +**Files to Check:** +- `e2e/tests/01_setup_and_auth.robot` +- `e2e/resources/common.resource` +- `frontend/src/pages/SetupPage.tsx` (or equivalent setup page component) -#### 2. `test_cleanup_wal_files_skips_recent_files` -- **Setup:** Create fake `.wal` and `.shm` files with mtime < 10s ago. -- **Action:** Call `_cleanup_wal_files()`. -- **Assert:** Files are NOT removed. \ No newline at end of file +**Reference Docs:** +- `e2e/Instructions.md` — Setup wizard feature coverage +- `Docs/Features.md` — Setup wizard form fields, password strength, validation + +**Expected Behavior:** Setup page loads with all required form fields visible (Master Password, Confirm Password, Database Path, fail2ban Socket Path, Timezone, Session Duration, Submit button). + +--- + +## Task: 01 Setup And Auth — Password Strength Indicator Updates On Input + +**Test:** `Password Strength Indicator Updates On Input` +**Suite:** `01_setup_and_auth.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/01_setup_and_auth.robot` +- `frontend/src/components/PasswordStrengthIndicator.tsx` (or equivalent) + +**Reference Docs:** +- `Docs/Features.md` — Password strength indicator behavior + +**Expected Behavior:** Four-segment strength bar updates dynamically as user types password. Weak password shows 1 segment, strong password shows 4 segments. + +--- + +## Task: 01 Setup And Auth — Password Mismatch Shows Validation Error + +**Test:** `Password Mismatch Shows Validation Error` +**Suite:** `01_setup_and_auth.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/01_setup_and_auth.robot` +- `frontend/src/pages/SetupPage.tsx` + +**Reference Docs:** +- `Docs/Features.md` — Setup form validation + +**Expected Behavior:** Submitting with non-matching passwords surfaces an error on the Confirm Password field. + +--- + +## Task: 01 Setup And Auth — Empty Required Fields Show Validation Errors + +**Test:** `Empty Required Fields Show Validation Errors` +**Suite:** `01_setup_and_auth.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/01_setup_and_auth.robot` +- `frontend/src/pages/SetupPage.tsx` + +**Reference Docs:** +- `Docs/Features.md` — Required field validation + +**Expected Behavior:** Submitting with blank required fields shows field-level error messages for each empty required input. + +--- + +## Task: 01 Setup And Auth — Invalid Session Duration Shows Validation Error + +**Test:** `Invalid Session Duration Shows Validation Error` +**Suite:** `01_setup_and_auth.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/01_setup_and_auth.robot` +- `frontend/src/pages/SetupPage.tsx` +- `backend/app/models/request.py` — session duration validation + +**Reference Docs:** +- `Docs/Features.md` — Session duration validation rules + +**Expected Behavior:** Submitting with an invalid session duration (e.g., negative, zero, or non-numeric) shows a validation error. + +--- + +## Task: 01 Setup And Auth — Incomplete Password Shows Complexity Error + +**Test:** `Incomplete Password Shows Complexity Error` +**Suite:** `01_setup_and_auth.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/01_setup_and_auth.robot` +- `frontend/src/pages/SetupPage.tsx` +- `frontend/src/utils/passwordValidation.ts` (or equivalent) + +**Reference Docs:** +- `Docs/Features.md` — Password complexity requirements + +**Expected Behavior:** Submitting a password that does not meet complexity rules (length, special chars, etc.) shows a complexity error message. + +--- + +## Task: 01 Setup And Auth — Setup Completes Successfully And Redirects To Login + +**Test:** `Setup Completes Successfully And Redirects To Login` +**Suite:** `01_setup_and_auth.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/01_setup_and_auth.robot` +- `frontend/src/pages/SetupPage.tsx` +- `backend/app/routers/setup.py` — setup completion endpoint +- `backend/app/services/setup_service.py` + +**Reference Docs:** +- `Docs/Features.md` — Setup completion flow +- `e2e/Instructions.md` — Setup wizard full submit + +**Expected Behavior:** Filling all required fields with valid data and submitting completes setup, creates the admin user, initializes the database, and redirects to `/login`. + +--- + +## Task: 02 Ban Records — Simulated Failed Logins Appear As Ban Records + +**Test:** `Simulated Failed Logins Appear As Ban Records` +**Suite:** `02_ban_records.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). +**Specific Error:** `'' does not contain '192.168.100.99'` (from earlier partial run before backend was fully down). + +**Files to Check:** +- `e2e/tests/02_ban_records.robot` +- `backend/app/routers/bans.py` — ban records endpoint +- `backend/app/services/ban_service.py` +- `Docker/simulate_failed_logins.sh` — simulation script + +**Reference Docs:** +- `e2e/Instructions.md` — "end-to-end ban pipeline: fail2ban log → history" +- `Docs/Features.md` — Ban pipeline flow + +**Expected Behavior:** Simulating failed SSH logins triggers fail2ban to ban the IP, and the banned IP appears in the dashboard/history within a reasonable time. + +--- + +## Task: 02 Login — Login Page Renders Password Input + +**Test:** `Login Page Renders Password Input` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `frontend/src/pages/LoginPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — Login page feature coverage +- `Docs/Features.md` — Login page UI + +**Expected Behavior:** Login page shows a single password input field (no username field) and a submit button. + +--- + +## Task: 02 Login — Login Page Has No Username Field + +**Test:** `Login Page Has No Username Field` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `frontend/src/pages/LoginPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Login page must NOT ask for a username" + +**Expected Behavior:** The login page does NOT contain a username/email input field. Only password is required. + +--- + +## Task: 02 Login — Login With Wrong Password Shows Error + +**Test:** `Login With Wrong Password Shows Error` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `frontend/src/pages/LoginPage.tsx` +- `backend/app/routers/auth.py` — login endpoint +- `backend/app/services/auth_service.py` + +**Reference Docs:** +- `e2e/Instructions.md` — "wrong password" test case + +**Expected Behavior:** Submitting an incorrect password displays an error message (e.g., "Invalid credentials") without revealing whether the user exists. + +--- + +## Task: 02 Login — Login Rate Limits After Multiple Failures + +**Test:** `Login Rate Limits After Multiple Failures` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `backend/app/routers/auth.py` — rate limiting +- `backend/app/middleware/rate_limit.py` (or equivalent) + +**Reference Docs:** +- `e2e/Instructions.md` — "Per-IP rate limit trial" +- `Docs/Security.md` — Rate limiting rules + +**Expected Behavior:** After 5 failed login attempts from the same IP within 60 seconds, subsequent attempts receive HTTP 429 Too Many Requests. + +--- + +## Task: 02 Login — Session Endpoint Returns 401 Without Cookie + +**Test:** `Session Endpoint Returns 401 Without Cookie` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `backend/app/routers/auth.py` — session validation endpoint +- `backend/app/middleware/session.py` (or equivalent) + +**Reference Docs:** +- `e2e/Instructions.md` — "session validation 401" + +**Expected Behavior:** Calling `GET /api/v1/auth/session` without a valid session cookie returns HTTP 401 Unauthorized. + +--- + +## Task: 02 Login — Direct Access To Protected Route Redirects To Login + +**Test:** `Direct Access To Protected Route Redirects To Login` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `frontend/src/App.tsx` — route guards +- `frontend/src/components/ProtectedRoute.tsx` (or equivalent) + +**Reference Docs:** +- `Docs/Features.md` — Route protection + +**Expected Behavior:** Navigating directly to a protected route (e.g., `/dashboard`) without being logged in redirects to `/login`. + +--- + +## Task: 02 Login — Session Validation 401 On Mount Redirects To Login + +**Test:** `Session Validation 401 On Mount Redirects To Login` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `frontend/src/hooks/useSession.ts` (or equivalent) +- `frontend/src/components/ProtectedRoute.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "When the backend session check returns 401, the UI redirects" + +**Expected Behavior:** When the frontend mounts and the session validation API returns 401, the user is redirected to the login page. + +--- + +## Task: 02 Login — Logout Clears Session + +**Test:** `Logout Clears Session` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `frontend/src/components/Sidebar.tsx` — logout button +- `backend/app/routers/auth.py` — logout endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "Clicking the Sign Out button clears the session" + +**Expected Behavior:** Clicking "Sign Out" clears the session cookie and invalidates the session on the backend. + +--- + +## Task: 02 Login — After Logout Protected Pages Redirect To Login + +**Test:** `After Logout Protected Pages Redirect To Login` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `frontend/src/App.tsx` — route guards + +**Reference Docs:** +- `e2e/Instructions.md` — Post-logout behavior + +**Expected Behavior:** After logging out, attempting to navigate to any protected page redirects to `/login`. + +--- + +## Task: 02 Login — Login Preserves Originally Requested Page Via Next Parameter + +**Test:** `Login Preserves Originally Requested Page Via Next Parameter` +**Suite:** `02_login.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/02_login.robot` +- `frontend/src/pages/LoginPage.tsx` — `next` query param handling + +**Reference Docs:** +- `e2e/Instructions.md` — "After successful login, user is redirected to the originally requested page" + +**Expected Behavior:** If an unauthenticated user tries to access `/jails`, they are redirected to `/login?next=/jails`. After successful login, they are redirected to `/jails`. + +--- + +## Task: 03 Blocklist Import — Manual Blocklist Import Completes Without Error + +**Test:** `Manual Blocklist Import Completes Without Error` +**Suite:** `03_blocklist_import.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). +**Specific Error:** `Url: http://localhost:8000/api/v1/blocklists Expected status: 401 != 200` + +**Files to Check:** +- `e2e/tests/03_blocklist_import.robot` +- `backend/app/routers/blocklists.py` — blocklist import endpoint +- `backend/app/services/blocklist_service.py` + +**Reference Docs:** +- `e2e/Instructions.md` — "blocklist manual import via UI" +- `Docs/Features.md` — Blocklist importer + +**Expected Behavior:** A logged-in admin can trigger a manual blocklist import via the UI/API, and it completes without errors. The endpoint should return 200 (not 401, which implies auth/session issue). + +--- + +## Task: 03 Dashboard — Dashboard Page Renders Status Bar + +**Test:** `Dashboard Page Renders Status Bar` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `frontend/src/pages/DashboardPage.tsx` +- `backend/app/routers/dashboard.py` — status endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "status bar, time-range presets, data-source badges" +- `Docs/Features.md` — Dashboard overview + +**Expected Behavior:** The dashboard page renders a status bar showing fail2ban server status (online/offline), version, and other key metrics. + +--- + +## Task: 03 Dashboard — Dashboard Ban List Renders Columns + +**Test:** `Dashboard Ban List Renders Columns` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `frontend/src/components/BanList.tsx` (or equivalent) +- `backend/app/routers/dashboard.py` — bans endpoint + +**Reference Docs:** +- `Docs/Features.md` — Dashboard ban list table + +**Expected Behavior:** The ban list table contains expected columns: IP Address, Country, Banned At, Expires At, Jail, Actions. + +--- + +## Task: 03 Dashboard — Dashboard Time Range 24h Shows Live Source + +**Test:** `Dashboard Time Range 24h Shows Live Source` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `frontend/src/pages/DashboardPage.tsx` — time range selector +- `backend/app/routers/dashboard.py` — bans endpoint with range filter + +**Reference Docs:** +- `e2e/Instructions.md` — "Selecting Last 24 hours shows Live source badge" + +**Expected Behavior:** Selecting "Last 24 hours" from the time range dropdown shows a "Live" data source badge, indicating data comes directly from fail2ban. + +--- + +## Task: 03 Dashboard — Dashboard Time Range 7d Shows Archive Source + +**Test:** `Dashboard Time Range 7d Shows Archive Source` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `frontend/src/pages/DashboardPage.tsx` +- `backend/app/routers/dashboard.py` + +**Reference Docs:** +- `e2e/Instructions.md` — "Selecting Last 7 days shows Archive source badge" + +**Expected Behavior:** Selecting "Last 7 days" shows an "Archive" data source badge, indicating data comes from the SQLite archive. + +--- + +## Task: 03 Dashboard — Dashboard Time Range 30d Shows Archive Source + +**Test:** `Dashboard Time Range 30d Shows Archive Source` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `frontend/src/pages/DashboardPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Selecting Last 30 days shows Archive source badge" + +**Expected Behavior:** Selecting "Last 30 days" shows an "Archive" data source badge. + +--- + +## Task: 03 Dashboard — Dashboard Time Range 365d Shows Archive Source + +**Test:** `Dashboard Time Range 365d Shows Archive Source` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `frontend/src/pages/DashboardPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Selecting Last 365 days shows Archive source badge" + +**Expected Behavior:** Selecting "Last 365 days" shows an "Archive" data source badge. + +--- + +## Task: 03 Dashboard — Dashboard Bans Endpoint Returns Expected Shape + +**Test:** `Dashboard Bans Endpoint Returns Expected Shape` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `backend/app/routers/dashboard.py` — `GET /api/v1/dashboard/bans` +- `backend/app/models/response.py` — ban response schema + +**Reference Docs:** +- `Docs/API-Reference.md` — Dashboard bans endpoint contract + +**Expected Behavior:** `GET /api/v1/dashboard/bans?range=24h&page=1&page_size=100` returns a paginated response with `items`, `total`, `page`, `page_size` fields, and each item has `ip`, `country`, `banned_at`, `expires_at`, `jail`. + +--- + +## Task: 03 Dashboard — Dashboard Status Endpoint Returns Version + +**Test:** `Dashboard Status Endpoint Returns Version` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `backend/app/routers/dashboard.py` — `GET /api/v1/dashboard/status` + +**Reference Docs:** +- `Docs/API-Reference.md` — Dashboard status endpoint + +**Expected Behavior:** `GET /api/v1/dashboard/status` returns JSON containing `version`, `online`, `jails_count`, and other status fields. + +--- + +## Task: 03 Dashboard — Dashboard Bans By Country Endpoint + +**Test:** `Dashboard Bans By Country Endpoint` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `backend/app/routers/dashboard.py` — `GET /api/v1/dashboard/bans/by-country` + +**Reference Docs:** +- `Docs/API-Reference.md` — Geo aggregation endpoint + +**Expected Behavior:** `GET /api/v1/dashboard/bans/by-country?range=24h` returns a list of `{country_code, country_name, count}` objects. + +--- + +## Task: 03 Dashboard — Dashboard Bans Trend Endpoint + +**Test:** `Dashboard Bans Trend Endpoint` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `backend/app/routers/dashboard.py` — `GET /api/v1/dashboard/bans/trend` + +**Reference Docs:** +- `Docs/API-Reference.md` — Trend endpoint + +**Expected Behavior:** `GET /api/v1/dashboard/bans/trend?range=24h` returns time-series data for chart rendering. + +--- + +## Task: 03 Dashboard — Dashboard Bans By Jail Endpoint + +**Test:** `Dashboard Bans By Jail Endpoint` +**Suite:** `03_dashboard.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/03_dashboard.robot` +- `backend/app/routers/dashboard.py` — jail aggregation endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Jail aggregation endpoint + +**Expected Behavior:** Returns bans grouped by jail name with counts. + +--- + +## Task: 04 Map — Map Page Renders World Map And Companion Table + +**Test:** `Map Page Renders World Map And Companion Table` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `frontend/src/pages/MapPage.tsx` +- `frontend/src/components/WorldMap.tsx` (or equivalent) + +**Reference Docs:** +- `e2e/Instructions.md` — "World Map View — country fills, click-to-filter" +- `Docs/Features.md` — Map view + +**Expected Behavior:** The map page renders an interactive world map and a companion data table below it. + +--- + +## Task: 04 Map — Map Page Renders Time Range Selector + +**Test:** `Map Page Renders Time Range Selector` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `frontend/src/pages/MapPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — Map time range presets + +**Expected Behavior:** The map page includes a time range selector (24h, 7d, 30d, 365d). + +--- + +## Task: 04 Map — Map Page 24h Preset Shows Live Source Badge + +**Test:** `Map Page 24h Preset Shows Live Source Badge` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `frontend/src/pages/MapPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Selecting Last 24 hours shows Live source badge" + +**Expected Behavior:** Selecting 24h on the map page shows a "Live" data source badge. + +--- + +## Task: 04 Map — Map Page 7d Preset Shows Archive Source Badge + +**Test:** `Map Page 7d Preset Shows Archive Source Badge` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `frontend/src/pages/MapPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Selecting Last 7 days shows Archive source badge" + +**Expected Behavior:** Selecting 7d on the map page shows an "Archive" data source badge. + +--- + +## Task: 04 Map — Map Companion Table Is Sticky Header + +**Test:** `Map Companion Table Is Sticky Header` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `frontend/src/components/MapCompanionTable.tsx` (or equivalent) + +**Reference Docs:** +- `e2e/Instructions.md` — "sticky table header/footer" + +**Expected Behavior:** The companion table below the map has a sticky header that remains visible while scrolling. + +--- + +## Task: 04 Map — Map Page Has Zoom Controls + +**Test:** `Map Page Has Zoom Controls` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `frontend/src/components/WorldMap.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "zoom controls" + +**Expected Behavior:** The map includes zoom in/out controls (buttons or mouse wheel support). + +--- + +## Task: 04 Map — Map Bans By Country API Endpoint + +**Test:** `Map Bans By Country API Endpoint` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `backend/app/routers/dashboard.py` — geo endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Geo/country endpoint + +**Expected Behavior:** `GET /api/v1/dashboard/bans/by-country` returns country-level ban counts for map coloring. + +--- + +## Task: 04 Map — Map Threshold Config Endpoint Exists + +**Test:** `Map Threshold Config Endpoint Exists` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `backend/app/routers/config_misc.py` (or equivalent) + +**Reference Docs:** +- `Docs/API-Reference.md` — Map threshold configuration + +**Expected Behavior:** A configuration endpoint exists for setting map color thresholds (e.g., low/medium/high ban count ranges). + +--- + +## Task: 04 Map — Map Threshold Config Returns Thresholds + +**Test:** `Map Threshold Config Returns Thresholds` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `backend/app/routers/config_misc.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Map threshold configuration + +**Expected Behavior:** The threshold config endpoint returns the currently configured threshold values. + +--- + +## Task: 04 Map — Map Filter Clears And Resets Companion Table + +**Test:** `Map Filter Clears And Resets Companion Table` +**Suite:** `04_map.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/04_map.robot` +- `frontend/src/pages/MapPage.tsx` +- `frontend/src/components/MapCompanionTable.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "click-to-filter, Clear filter" + +**Expected Behavior:** Clicking a country on the map filters the companion table to that country. Clicking "Clear filter" resets the table to show all countries. + +--- + +## Task: 05 Jails — Jails Page Lists Active Jails + +**Test:** `Jails Page Lists Active Jails` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `frontend/src/pages/JailsPage.tsx` +- `backend/app/routers/jails.py` — jails list endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "Jail Management — list" +- `Docs/Features.md` — Jail management + +**Expected Behavior:** The jails page displays a table of all active jails with status, backend, banned count, failed count, find time, ban time, and max retry. + +--- + +## Task: 05 Jails — Jails API Returns Active Jails + +**Test:** `Jails API Returns Active Jails` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — `GET /api/v1/jails` + +**Reference Docs:** +- `Docs/API-Reference.md` — Jails endpoint + +**Expected Behavior:** `GET /api/v1/jails` returns a list of active jail configurations and their current status. + +--- + +## Task: 05 Jails — Jail Detail Page Loads For First Active Jail + +**Test:** `Jail Detail Page Loads For First Active Jail` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `frontend/src/pages/JailDetailPage.tsx` +- `backend/app/routers/jails.py` — jail detail endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "Jail Management — list, ban/unban API, IP lookup" + +**Expected Behavior:** Navigating to `/jails/{jail-name}` loads the detail page for that jail, showing banned IPs, ignore list, and jail controls. + +--- + +## Task: 05 Jails — Ban An IP Via API + +**Test:** `Ban An IP Via API` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — `POST /api/v1/bans` +- `backend/app/services/ban_service.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Ban endpoint +- `e2e/Instructions.md` — "Ban / Unban IP" + +**Expected Behavior:** `POST /api/v1/bans` with `{ip, jail}` bans the specified IP in the specified jail. + +--- + +## Task: 05 Jails — Unban The IP We Just Banned + +**Test:** `Unban The IP We Just Banned` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — `DELETE /api/v1/bans/{ip}` + +**Reference Docs:** +- `Docs/API-Reference.md` — Unban endpoint + +**Expected Behavior:** `DELETE /api/v1/bans/{ip}` (or equivalent unban endpoint) removes the ban for the specified IP. + +--- + +## Task: 05 Jails — Unban All Endpoint Accepts Request + +**Test:** `Unban All Endpoint Accepts Request` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — bulk unban endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Bulk unban endpoint + +**Expected Behavior:** An endpoint exists to unban all IPs (either globally or per-jail) and returns success. + +--- + +## Task: 05 Jails — Active Bans Endpoint Returns List + +**Test:** `Active Bans Endpoint Returns List` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — active bans endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Active bans endpoint + +**Expected Behavior:** `GET /api/v1/jails/{name}/bans` returns a paginated list of currently banned IPs for the specified jail. + +--- + +## Task: 05 Jails — IP Lookup Endpoint Returns Geo + +**Test:** `IP Lookup Endpoint Returns Geo` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/geo.py` — geo lookup endpoint +- `backend/app/services/geo_service.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Geo lookup endpoint +- `Docs/Features.md` — IP geolocation + +**Expected Behavior:** `GET /api/v1/geo/{ip}` returns geolocation data (country, city, ASN, org) for the given IP. + +--- + +## Task: 05 Jails — Ignore List Add And Remove Via API + +**Test:** `Ignore List Add And Remove Via API` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — ignore list endpoints + +**Reference Docs:** +- `Docs/API-Reference.md` — Ignore list endpoints + +**Expected Behavior:** `POST /api/v1/jails/{name}/ignore` adds an IP to the ignore list. `DELETE /api/v1/jails/{name}/ignore/{ip}` removes it. + +--- + +## Task: 05 Jails — Ignore Self Toggle Via API + +**Test:** `Ignore Self Toggle Via API` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — ignore self toggle + +**Reference Docs:** +- `Docs/API-Reference.md` — Ignore self toggle + +**Expected Behavior:** An endpoint exists to toggle "ignore self" (exclude the server's own IP from banning) for a jail. + +--- + +## Task: 05 Jails — Jail Reload Endpoint Works + +**Test:** `Jail Reload Endpoint Works` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — reload endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Jail control endpoints + +**Expected Behavior:** `POST /api/v1/jails/{name}/reload` reloads the jail configuration and returns success. + +--- + +## Task: 05 Jails — Jail Stop Endpoint Works + +**Test:** `Jail Stop Endpoint Works` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — stop endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Jail control endpoints + +**Expected Behavior:** `POST /api/v1/jails/{name}/stop` stops the jail and returns success. + +--- + +## Task: 05 Jails — Jail Start Endpoint Works + +**Test:** `Jail Start Endpoint Works` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — start endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Jail control endpoints + +**Expected Behavior:** `POST /api/v1/jails/{name}/start` starts the jail and returns success. + +--- + +## Task: 05 Jails — Jail Idle Endpoint Works + +**Test:** `Jail Idle Endpoint Works` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — idle endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Jail control endpoints + +**Expected Behavior:** `POST /api/v1/jails/{name}/idle` sets the jail to idle mode (monitoring without banning) and returns success. + +--- + +## Task: 05 Jails — Reload All Jails Endpoint Works + +**Test:** `Reload All Jails Endpoint Works` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/jails.py` — reload all endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Jail control endpoints + +**Expected Behavior:** `POST /api/v1/jails/reload` reloads all jails and returns success. + +--- + +## Task: 05 Jails — Geo Stats Endpoint Returns Counters + +**Test:** `Geo Stats Endpoint Returns Counters` +**Suite:** `05_jails.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/05_jails.robot` +- `backend/app/routers/geo.py` — geo stats endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Geo stats endpoint + +**Expected Behavior:** `GET /api/v1/geo/stats` returns aggregated geolocation statistics (bans by country, etc.). + +--- + +## Task: 06 Config Jails Filters Actions — Config Page Renders All Required Tabs + +**Test:** `Config Page Renders All Required Tabs` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `frontend/src/pages/ConfigPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Configuration View — Jails/Filters/Actions tabs" +- `Docs/Features.md` — Configuration view + +**Expected Behavior:** The configuration page renders tabs for Jails, Filters, Actions, Server, and Regex Tester. + +--- + +## Task: 06 Config Jails Filters Actions — Config Jails Tab Defaults To Active + +**Test:** `Config Jails Tab Defaults To Active` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `frontend/src/pages/ConfigPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Jails/Filters/Actions tabs" + +**Expected Behavior:** When navigating to `/config`, the "Jails" tab is active by default. + +--- + +## Task: 06 Config Jails Filters Actions — Config Filters Tab Loads + +**Test:** `Config Filters Tab Loads` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `frontend/src/pages/ConfigPage.tsx` +- `backend/app/routers/config_misc.py` — filters endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "Filters tab" + +**Expected Behavior:** Clicking the "Filters" tab loads and displays the list of fail2ban filter configurations. + +--- + +## Task: 06 Config Jails Filters Actions — Config Actions Tab Loads + +**Test:** `Config Actions Tab Loads` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `frontend/src/pages/ConfigPage.tsx` +- `backend/app/routers/config_misc.py` — actions endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "Actions tab" + +**Expected Behavior:** Clicking the "Actions" tab loads and displays the list of fail2ban action configurations. + +--- + +## Task: 06 Config Jails Filters Actions — Config Server Tab Loads + +**Test:** `Config Server Tab Loads` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `frontend/src/pages/ConfigPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Server settings + log viewer" + +**Expected Behavior:** The Server tab loads and displays server-wide settings. + +--- + +## Task: 06 Config Jails Filters Actions — Config Regex Tester Tab Loads + +**Test:** `Config Regex Tester Tab Loads` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `frontend/src/pages/ConfigPage.tsx` +- `frontend/src/components/RegexTester.tsx` (or equivalent) + +**Reference Docs:** +- `e2e/Instructions.md` — "regex tester" + +**Expected Behavior:** The Regex Tester tab loads with input fields for regex pattern and test log lines. + +--- + +## Task: 06 Config Jails Filters Actions — Config Regex Tester API Endpoint Validates Pattern + +**Test:** `Config Regex Tester API Endpoint Validates Pattern` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/config_misc.py` — regex tester endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Regex tester endpoint + +**Expected Behavior:** `POST /api/v1/config/regex-test` (or equivalent) accepts a regex pattern and test lines, returns matches/non-matches. + +--- + +## Task: 06 Config Jails Filters Actions — Config Jails Endpoint Lists Jail Configs + +**Test:** `Config Jails Endpoint Lists Jail Configs` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/jail_config.py` — jail configs endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Jail config endpoint + +**Expected Behavior:** `GET /api/v1/config/jails` returns a list of jail configuration files and their parsed settings. + +--- + +## Task: 06 Config Jails Filters Actions — Config Filters Endpoint Lists Filter Configs + +**Test:** `Config Filters Endpoint Lists Filter Configs` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/jail_config.py` — filter configs endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Filter config endpoint + +**Expected Behavior:** `GET /api/v1/config/filters` returns a list of filter configuration files. + +--- + +## Task: 06 Config Jails Filters Actions — Config Actions Endpoint Lists Action Configs + +**Test:** `Config Actions Endpoint Lists Action Configs` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/jail_config.py` — action configs endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Action config endpoint + +**Expected Behavior:** `GET /api/v1/config/actions` returns a list of action configuration files. + +--- + +## Task: 06 Config Jails Filters Actions — Config Global Settings Endpoint + +**Test:** `Config Global Settings Endpoint` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/jail_config.py` — global settings endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Global settings endpoint + +**Expected Behavior:** `GET /api/v1/config/global` returns the global fail2ban configuration (e.g., `fail2ban.conf`). + +--- + +## Task: 06 Config Jails Filters Actions — Config Service Status Endpoint + +**Test:** `Config Service Status Endpoint` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/config_misc.py` — service status endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Service status endpoint + +**Expected Behavior:** `GET /api/v1/config/service-status` returns fail2ban service status and log configuration. + +--- + +## Task: 06 Config Jails Filters Actions — Config Security Headers Endpoint + +**Test:** `Config Security Headers Endpoint` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/config_misc.py` — security headers endpoint + +**Reference Docs:** +- `Docs/Security.md` — Security headers + +**Expected Behavior:** An endpoint returns the currently configured security headers (CSP, X-Frame-Options, etc.). + +--- + +## Task: 06 Config Jails Filters Actions — Config Inline Edit Round Trip For First Jail + +**Test:** `Config Inline Edit Round Trip For First Jail` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `frontend/src/pages/ConfigPage.tsx` +- `backend/app/routers/jail_config.py` — update endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "inline edit" +- `Docs/Features.md` — Config inline editing + +**Expected Behavior:** Editing a jail config field inline, saving, and refreshing the page persists the change. + +--- + +## Task: 06 Config Jails Filters Actions — Config Raw Section Lazy Load + +**Test:** `Config Raw Section Lazy Load` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `frontend/src/pages/ConfigPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "raw config" + +**Expected Behavior:** The raw config section is lazily loaded (not fetched until expanded) to improve initial page load performance. + +--- + +## Task: 06 Config Jails Filters Actions — Config Raw Action File Endpoint + +**Test:** `Config Raw Action File Endpoint` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/jail_config.py` — raw file endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Raw config endpoint + +**Expected Behavior:** `GET /api/v1/config/actions/{name}/raw` returns the raw contents of an action config file. + +--- + +## Task: 06 Config Jails Filters Actions — Config Jail Files Endpoint + +**Test:** `Config Jail Files Endpoint` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/jail_config.py` — jail files endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Jail files endpoint + +**Expected Behavior:** `GET /api/v1/config/jails/files` returns a list of available jail configuration files. + +--- + +## Task: 06 Config Jails Filters Actions — Config Invalid Regex Returns 4xx + +**Test:** `Config Invalid Regex Returns 4xx` +**Suite:** `06_config_jails_filters_actions.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/06_config_jails_filters_actions.robot` +- `backend/app/routers/config_misc.py` — regex tester endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Regex tester validation + +**Expected Behavior:** Submitting an invalid regex pattern to the regex tester endpoint returns HTTP 400 (or other 4xx) with a descriptive error message. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings GET Returns Expected Keys + +**Test:** `Server Settings GET Returns Expected Keys` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` — server settings endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "Server settings" +- `Docs/API-Reference.md` — Server settings endpoint + +**Expected Behavior:** `GET /api/v1/config/server` returns JSON with expected keys: `log_level`, `db_purge_age`, `max_matches`, etc. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings Update Log Level + +**Test:** `Server Settings Update Log Level` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` — server settings update endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Server settings update + +**Expected Behavior:** `PUT /api/v1/config/server` with `{log_level: "DEBUG"}` updates the log level and returns the updated config. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings Reject Invalid Log Level + +**Test:** `Server Settings Reject Invalid Log Level` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` — validation + +**Reference Docs:** +- `Docs/API-Reference.md` — Server settings validation + +**Expected Behavior:** Submitting an invalid log level (e.g., `"INVALID"`) returns HTTP 400 with a validation error. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings Update DB Purge Age + +**Test:** `Server Settings Update DB Purge Age` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Server settings update + +**Expected Behavior:** Updating `db_purge_age` persists the new value and applies it to the archive cleanup schedule. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings Update Max Matches + +**Test:** `Server Settings Update Max Matches` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Server settings update + +**Expected Behavior:** Updating `max_matches` persists the new value for log parsing. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings Reject Path Outside Allowlist + +**Test:** `Server Settings Reject Path Outside Allowlist` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` — path validation + +**Reference Docs:** +- `Docs/Security.md` — Path traversal prevention + +**Expected Behavior:** Submitting a log file path outside the allowed directory (e.g., `"/etc/passwd"`) returns HTTP 400. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings Accept Stdout Special Target + +**Test:** `Server Settings Accept Stdout Special Target` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Server settings special targets + +**Expected Behavior:** `"stdout"` is accepted as a special log target value. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings Accept Syslog Special Target + +**Test:** `Server Settings Accept Syslog Special Target` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Server settings special targets + +**Expected Behavior:** `"syslog"` is accepted as a special log target value. + +--- + +## Task: 07 Config Log And Serversettings — Server Settings Accept Safe File Path + +**Test:** `Server Settings Accept Safe File Path` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Server settings path validation + +**Expected Behavior:** A safe, allowlisted file path (e.g., `"/var/log/auth.log"`) is accepted. + +--- + +## Task: 07 Config Log And Serversettings — Flush Logs Endpoint Works + +**Test:** `Flush Logs Endpoint Works` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` — flush logs endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Log management endpoints + +**Expected Behavior:** `POST /api/v1/config/logs/flush` flushes buffered logs and returns success. + +--- + +## Task: 07 Config Log And Serversettings — Log Preview Endpoint Returns Content + +**Test:** `Log Preview Endpoint Returns Content` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` — log preview endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Log preview endpoint + +**Expected Behavior:** `GET /api/v1/config/logs/preview` returns the most recent log lines as text or JSON. + +--- + +## Task: 07 Config Log And Serversettings — Log Endpoint Returns Content Or 404 + +**Test:** `Log Endpoint Returns Content Or 404` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` — log endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Log endpoint + +**Expected Behavior:** `GET /api/v1/config/logs` returns log content if available, or HTTP 404 if no log file is configured/found. + +--- + +## Task: 07 Config Log And Serversettings — Log Observation Add Rejects Path Outside Allowlist + +**Test:** `Log Observation Add Rejects Path Outside Allowlist` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` — log observation endpoint + +**Reference Docs:** +- `Docs/Security.md` — Path traversal prevention +- `e2e/Instructions.md` — "log observation allowlist" + +**Expected Behavior:** Adding a log observation path outside the allowlist returns HTTP 400 with a security error. + +--- + +## Task: 07 Config Log And Serversettings — Log Observation Add Endpoint Exists + +**Test:** `Log Observation Add Endpoint Exists` +**Suite:** `07_config_log_and_serversettings.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/07_config_log_and_serversettings.robot` +- `backend/app/routers/config_misc.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — Log observation endpoints + +**Expected Behavior:** `POST /api/v1/config/logs/observation` exists and accepts `{path: "/var/log/auth.log"}`. + +--- + +## Task: 08 History — History Page Renders + +**Test:** `History Page Renders` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `frontend/src/pages/HistoryPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "Ban History — table, filters, per-IP timeline" +- `Docs/Features.md` — History view + +**Expected Behavior:** The history page renders with a table, filters, and pagination controls. + +--- + +## Task: 08 History — History Page Shows Archive Source Badge By Default + +**Test:** `History Page Shows Archive Source Badge By Default` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `frontend/src/pages/HistoryPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "archive vs fail2ban source" + +**Expected Behavior:** The history page shows an "Archive" data source badge by default (since history comes from the SQLite archive, not live fail2ban). + +--- + +## Task: 08 History — History Page Default 7d Range + +**Test:** `History Page Default 7d Range` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `frontend/src/pages/HistoryPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — History time range defaults + +**Expected Behavior:** The history page defaults to showing the last 7 days of ban history. + +--- + +## Task: 08 History — History Endpoint Returns Paginated Data + +**Test:** `History Endpoint Returns Paginated Data` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `backend/app/routers/history.py` — history endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — History endpoint + +**Expected Behavior:** `GET /api/v1/history?page=1&page_size=50` returns paginated ban history with `items`, `total`, `page`, `page_size`. + +--- + +## Task: 08 History — History Archive Endpoint Returns Data + +**Test:** `History Archive Endpoint Returns Data` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `backend/app/routers/history.py` — archive endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — History archive endpoint + +**Expected Behavior:** `GET /api/v1/history/archive` returns archived ban records. + +--- + +## Task: 08 History — History Per IP Endpoint Returns Data + +**Test:** `History Per IP Endpoint Returns Data` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `backend/app/routers/history.py` — per-IP endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Per-IP history endpoint + +**Expected Behavior:** `GET /api/v1/history/ip/{ip}` returns the ban timeline for a specific IP address. + +--- + +## Task: 08 History — History Filter By Jail Returns Data + +**Test:** `History Filter By Jail Returns Data` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `backend/app/routers/history.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — History filtering + +**Expected Behavior:** `GET /api/v1/history?jail=ssh` returns only bans from the specified jail. + +--- + +## Task: 08 History — History Filter By Source Fail2ban + +**Test:** `History Filter By Source Fail2ban` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `backend/app/routers/history.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — History source filtering + +**Expected Behavior:** Filtering by `source=fail2ban` returns only live fail2ban-sourced records. + +--- + +## Task: 08 History — History Filter By Source Archive + +**Test:** `History Filter By Source Archive` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `backend/app/routers/history.py` + +**Reference Docs:** +- `Docs/API-Reference.md` — History source filtering + +**Expected Behavior:** Filtering by `source=archive` returns only archived records. + +--- + +## Task: 08 History — History URL Params Honored + +**Test:** `History URL Params Honored` +**Suite:** `08_history.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/08_history.robot` +- `frontend/src/pages/HistoryPage.tsx` — URL param parsing + +**Reference Docs:** +- `Docs/Features.md` — Deep-linking with URL params + +**Expected Behavior:** Navigating to `/history?range=30d&jail=ssh` pre-selects the 30-day range and ssh jail filter. + +--- + +## Task: 09 Blocklists — Blocklists Page Renders + +**Test:** `Blocklists Page Renders` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `frontend/src/pages/BlocklistsPage.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "External Blocklist Importer — CRUD" +- `Docs/Features.md` — Blocklist management + +**Expected Behavior:** The blocklists page renders with a table of configured sources, import controls, and schedule settings. + +--- + +## Task: 09 Blocklists — Blocklists Sources List Endpoint + +**Test:** `Blocklists Sources List Endpoint` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — sources list endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Blocklist sources endpoint + +**Expected Behavior:** `GET /api/v1/blocklists` returns a list of configured blocklist sources. + +--- + +## Task: 09 Blocklists — Blocklist Source Create Rejects Invalid Scheme + +**Test:** `Blocklist Source Create Rejects Invalid Scheme` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — create endpoint validation + +**Reference Docs:** +- `Docs/Security.md` — SSRF prevention +- `e2e/Instructions.md` — "SSRF validation" + +**Expected Behavior:** Creating a blocklist source with an invalid URL scheme (e.g., `ftp://`, `file://`) returns HTTP 400. + +--- + +## Task: 09 Blocklists — Blocklist Source Create Rejects Loopback URL + +**Test:** `Blocklist Source Create Rejects Loopback URL` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — SSRF validation + +**Reference Docs:** +- `Docs/Security.md` — SSRF prevention + +**Expected Behavior:** Creating a blocklist source with a loopback URL (`http://127.0.0.1/...`, `http://localhost/...`) returns HTTP 400. + +--- + +## Task: 09 Blocklists — Blocklist Source Create Rejects Private IP URL + +**Test:** `Blocklist Source Create Rejects Private IP URL` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — SSRF validation + +**Reference Docs:** +- `Docs/Security.md` — SSRF prevention + +**Expected Behavior:** Creating a blocklist source with a private IP URL (`http://192.168.1.1/...`, `http://10.0.0.1/...`) returns HTTP 400. + +--- + +## Task: 09 Blocklists — Blocklist Source Create Rejects Link Local URL + +**Test:** `Blocklist Source Create Rejects Link Local URL` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — SSRF validation + +**Reference Docs:** +- `Docs/Security.md` — SSRF prevention + +**Expected Behavior:** Creating a blocklist source with a link-local URL (`http://169.254.1.1/...`) returns HTTP 400. + +--- + +## Task: 09 Blocklists — Blocklist Schedule Endpoint Returns Config + +**Test:** `Blocklist Schedule Endpoint Returns Config` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — schedule endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Blocklist schedule endpoint + +**Expected Behavior:** `GET /api/v1/blocklists/schedule` returns the current import schedule configuration (cron expression, enabled status). + +--- + +## Task: 09 Blocklists — Blocklist Schedule Update Works + +**Test:** `Blocklist Schedule Update Works` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — schedule update endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Blocklist schedule update + +**Expected Behavior:** `PUT /api/v1/blocklists/schedule` updates the import schedule and returns the updated config. + +--- + +## Task: 09 Blocklists — Blocklist Manual Import Endpoint Reachable + +**Test:** `Blocklist Manual Import Endpoint Reachable` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — manual import endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Blocklist manual import + +**Expected Behavior:** `POST /api/v1/blocklists/import` triggers a manual import and returns a job ID or success status. + +--- + +## Task: 09 Blocklists — Blocklist Import Log Endpoint Returns Paginated Data + +**Test:** `Blocklist Import Log Endpoint Returns Paginated Data` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — import log endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Blocklist import log + +**Expected Behavior:** `GET /api/v1/blocklists/log` returns paginated import log entries with timestamps, source, status, and error counts. + +--- + +## Task: 09 Blocklists — Blocklist Delete Non Existent Returns 404 + +**Test:** `Blocklist Delete Non Existent Returns 404` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — delete endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Blocklist delete endpoint + +**Expected Behavior:** `DELETE /api/v1/blocklists/{id}` for a non-existent source returns HTTP 404. + +--- + +## Task: 09 Blocklists — Blocklist Create And Delete Cycle + +**Test:** `Blocklist Create And Delete Cycle` +**Suite:** `09_blocklists.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/09_blocklists.robot` +- `backend/app/routers/blocklists.py` — create and delete endpoints + +**Reference Docs:** +- `e2e/Instructions.md` — "CRUD" + +**Expected Behavior:** Creating a valid blocklist source, verifying it appears in the list, and then deleting it removes it from the list. + +--- + +## Task: 10 General Layout — Sidebar Is Visible On Dashboard + +**Test:** `Sidebar Is Visible On Dashboard` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `frontend/src/components/Sidebar.tsx` (or `Layout.tsx`) + +**Reference Docs:** +- `e2e/Instructions.md` — "sidebar nav" +- `Docs/Features.md` — Layout + +**Expected Behavior:** After login, the sidebar navigation is visible on the dashboard page. + +--- + +## Task: 10 General Layout — Sidebar Lists All Required Pages + +**Test:** `Sidebar Lists All Required Pages` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `frontend/src/components/Sidebar.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "sidebar nav" + +**Expected Behavior:** The sidebar contains links to: Dashboard, World Map, Jails, History, Blocklists, Configuration. + +--- + +## Task: 10 General Layout — Sidebar Sign Out Logs User Out + +**Test:** `Sidebar Sign Out Logs User Out` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `frontend/src/components/Sidebar.tsx` +- `backend/app/routers/auth.py` — logout endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "Sign Out button" + +**Expected Behavior:** Clicking "Sign Out" in the sidebar clears the session and redirects to `/login`. + +--- + +## Task: 10 General Layout — Theme Toggle Is Present In Sidebar + +**Test:** `Theme Toggle Is Present In Sidebar` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `frontend/src/components/Sidebar.tsx` +- `frontend/src/hooks/useTheme.ts` (or equivalent) + +**Reference Docs:** +- `e2e/Instructions.md` — "theme toggle" + +**Expected Behavior:** The sidebar contains a theme toggle button (light/dark mode). + +--- + +## Task: 10 General Layout — Active Page Highlighted In Sidebar + +**Test:** `Active Page Highlighted In Sidebar` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `frontend/src/components/Sidebar.tsx` + +**Reference Docs:** +- `e2e/Instructions.md` — "active link highlighting" + +**Expected Behavior:** The current page's sidebar link is visually highlighted (different background/text color). + +--- + +## Task: 10 General Layout — Session Persists Across Page Reload + +**Test:** `Session Persists Across Page Reload` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `frontend/src/hooks/useSession.ts` +- `backend/app/routers/auth.py` — session validation + +**Reference Docs:** +- `e2e/Instructions.md` — "session persistence" + +**Expected Behavior:** Reloading the page while logged in does NOT redirect to login; the session cookie is still valid. + +--- + +## Task: 10 General Layout — Theme Toggle Changes Color Mode + +**Test:** `Theme Toggle Changes Color Mode` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `frontend/src/components/Sidebar.tsx` +- `frontend/src/hooks/useTheme.ts` + +**Reference Docs:** +- `e2e/Instructions.md` — "theme toggle" + +**Expected Behavior:** Clicking the theme toggle switches the UI between light and dark mode, and the preference persists in `localStorage`. + +--- + +## Task: 10 General Layout — Health Endpoint Returns Component Status + +**Test:** `Health Endpoint Returns Component Status` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `backend/app/routers/health.py` — health endpoint + +**Reference Docs:** +- `e2e/Instructions.md` — "health endpoints" +- `Docs/API-Reference.md` — Health endpoint + +**Expected Behavior:** `GET /api/v1/health` returns JSON with component statuses: `database`, `fail2ban`, `scheduler`, `cache`, etc. + +--- + +## Task: 10 General Layout — Liveness Endpoint Returns 200 + +**Test:** `Liveness Endpoint Returns 200` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `backend/app/routers/health.py` — liveness endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Liveness probe +- `Docs/Deployment.md` — Kubernetes probes + +**Expected Behavior:** `GET /api/v1/health/live` always returns HTTP 200 (even if fail2ban is down), indicating the Python process is alive. + +--- + +## Task: 10 General Layout — Metrics Endpoint Returns Prometheus Text + +**Test:** `Metrics Endpoint Returns Prometheus Text` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `backend/app/routers/metrics.py` (or equivalent) + +**Reference Docs:** +- `Docs/Observability.md` — Prometheus metrics +- `Docs/API-Reference.md` — Metrics endpoint + +**Expected Behavior:** `GET /api/v1/metrics` returns Prometheus-formatted text with application metrics (request counts, ban counts, etc.). + +--- + +## Task: 10 General Layout — Setup Timezone Endpoint Returns IANA String + +**Test:** `Setup Timezone Endpoint Returns IANA String` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `backend/app/routers/setup.py` — timezone endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Setup timezone endpoint + +**Expected Behavior:** `GET /api/v1/setup/timezone` returns a valid IANA timezone string (e.g., `"UTC"`, `"Europe/Berlin"`). + +--- + +## Task: 10 General Layout — Setup Status Endpoint Returns Completed Flag + +**Test:** `Setup Status Endpoint Returns Completed Flag` +**Suite:** `10_general_layout.robot` + +**Step That Fails:** Parent suite setup (`Wait For Backend Health`). + +**Files to Check:** +- `e2e/tests/10_general_layout.robot` +- `backend/app/routers/setup.py` — setup status endpoint + +**Reference Docs:** +- `Docs/API-Reference.md` — Setup status endpoint + +**Expected Behavior:** `GET /api/v1/setup` returns `{completed: true|false}` indicating whether initial setup has been completed. diff --git a/e2e/proxy_server.py b/e2e/proxy_server.py new file mode 100644 index 0000000..814294c --- /dev/null +++ b/e2e/proxy_server.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Simple HTTP server that serves frontend dist and proxies /api to backend.""" + +import http.server +import os +import socketserver +import urllib.request + +PORT = 5173 +BACKEND_URL = "http://localhost:8000" +DIST_DIR = "/home/lukas/Volume/repo/BanGUI/frontend/dist" + + +class ProxyHandler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=DIST_DIR, **kwargs) + + def do_GET(self): + if self.path.startswith("/api/"): + self.proxy_request("GET") + else: + super().do_GET() + + def do_POST(self): + if self.path.startswith("/api/"): + self.proxy_request("POST") + else: + self.send_error(405) + + def do_PUT(self): + if self.path.startswith("/api/"): + self.proxy_request("PUT") + else: + self.send_error(405) + + def do_DELETE(self): + if self.path.startswith("/api/"): + self.proxy_request("DELETE") + else: + self.send_error(405) + + def do_PATCH(self): + if self.path.startswith("/api/"): + self.proxy_request("PATCH") + else: + self.send_error(405) + + def proxy_request(self, method): + url = BACKEND_URL + self.path + content_length = self.headers.get("Content-Length") + data = None + if content_length: + data = self.rfile.read(int(content_length)) + + req = urllib.request.Request(url, method=method, data=data) + for key, value in self.headers.items(): + if key.lower() not in ("host", "content-length"): + req.add_header(key, value) + + try: + with urllib.request.urlopen(req) as resp: + self.send_response(resp.status) + for key, value in resp.headers.items(): + if key.lower() not in ("transfer-encoding", "content-encoding"): + self.send_header(key, value) + self.end_headers() + self.wfile.write(resp.read()) + except urllib.error.HTTPError as e: + self.send_response(e.code) + for key, value in e.headers.items(): + self.send_header(key, value) + self.end_headers() + self.wfile.write(e.read()) + except Exception as e: + self.send_error(502, str(e)) + + def end_headers(self): + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") + self.send_header("Access-Control-Allow-Headers", "*") + super().end_headers() + + def do_OPTIONS(self): + self.send_response(204) + self.end_headers() + + +if __name__ == "__main__": + os.chdir(DIST_DIR) + with socketserver.TCPServer(("", PORT), ProxyHandler) as httpd: + print(f"Serving frontend at http://localhost:{PORT}") + print(f"Proxying /api to {BACKEND_URL}") + httpd.serve_forever() diff --git a/e2e/resources/common.resource b/e2e/resources/common.resource index e4bf70c..1091b69 100644 --- a/e2e/resources/common.resource +++ b/e2e/resources/common.resource @@ -14,14 +14,17 @@ ${XFF_HEADER} ${EMPTY} *** Keywords *** Wait For Backend Health - [Documentation] Polls /api/v1/health until 200 or timeout. + [Documentation] Polls /api/v1/health/live until 200 or timeout. + ... Uses the liveness probe because it is independent of + ... fail2ban availability, unlike the combined /api/v1/health + ... which returns 503 when fail2ban is offline. [Arguments] ${timeout}=120 ${interval}=5 ${deadline}= Evaluate time.time() + ${timeout} WHILE True ${now}= Evaluate time.time() IF ${now} >= ${deadline} FAIL Backend did not become healthy within ${timeout} seconds - ${response}= GET ${BACKEND_URL}/api/v1/health expected_status=any - IF ${response.status} == 200 BREAK + ${response}= GET ${BACKEND_URL}/api/v1/health/live expected_status=any + IF ${response.status_code} == 200 BREAK Sleep ${interval} END Log Backend is healthy.