Restructure 5 existing .robot files into 10 numbered files, one per feature area in Docs/Features.md. Each file is independently runnable. Add api.resource + data.resource for CSRF/XFF-aware wrappers and RFC5737 IP generators. Coverage: 110 new tests across login, dashboard, map, jails, config, history, blocklists, layout. Uses existing data-testid/aria-label/role selectors only — no frontend changes. Tests bypass per-IP rate limits via X-Forwarded-For header rotation. Hard rule preserved: failures are findings, never app-code fixes.
17 KiB
E2E Tests — Running Robot Framework Tests
Test File Structure
The E2E suite is organized one .robot file per feature area defined in Docs/Features.md. Each file is independently runnable.
| File | Feature |
|---|---|
01_setup_and_auth.robot |
Setup wizard (formerly 05_setup.robot) — form fields, password strength, validation, full submit |
02_login.robot |
Login page — wrong password, rate limit (429), session validation 401, logout |
03_dashboard.robot |
Ban Overview (Dashboard) — status bar, time-range presets, data-source badges, API endpoints |
04_map.robot |
World Map View — country fills, click-to-filter, zoom controls, sticky table header/footer |
05_jails.robot |
Jail Management — list, ban/unban API, IP lookup, ignore list, jail controls |
06_config_jails_filters_actions.robot |
Configuration View — Jails/Filters/Actions tabs, inline edit, raw config, regex tester |
07_config_log_and_serversettings.robot |
Server settings + log viewer + log observation allowlist |
08_history.robot |
Ban History — table, filters, per-IP timeline, archive vs fail2ban source |
09_blocklists.robot |
External Blocklist Importer — CRUD, SSRF validation, schedule, import log, delete restriction |
10_general_layout.robot |
General UI/layout — sidebar nav, theme toggle, session persistence, health endpoints |
02_ban_records.robot |
(pre-existing) end-to-end ban pipeline: fail2ban log → history |
03_blocklist_import.robot |
(pre-existing) blocklist manual import via UI |
04_config_edit.robot |
(pre-existing) config field auto-save round trip |
Resource Files
Shared keywords live in resources/:
| File | Purpose |
|---|---|
common.resource |
Wait For Backend Health, Wait For Frontend, Page Should Contain wrapper, XFF helpers, IP/jail name generators |
auth.resource |
Login As Admin, Login Via HTTP, Logout, Verify Session Invalid, Login With Wrong Password, Login Exceeds Rate Limit |
api.resource |
Api Get/Post/Put/Delete wrappers that auto-inject CSRF + X-Forwarded-For headers |
data.resource |
Unique IP / jail name / blocklist name generators (RFC5737 ranges) |
Setup
pip install -r requirements.txt
rfbrowser init
Run All Tests
robot --outputdir results --log log.html --report report.html tests/
Or via the Makefile from the repo root:
make e2e
Run Specific Test File
robot --outputdir results tests/02_login.robot
robot --outputdir results tests/08_history.robot
Run with Browser Visible
robot --outputdir results --variable BROWSER:chromium tests/
Rate-Limit Workaround
BanGUI rate-limits several endpoints per source IP:
| Bucket | Default Limit | Window |
|---|---|---|
POST /api/v1/auth/login |
5 / IP | 60 s |
POST /api/v1/blocklists/import |
10 / IP | 3600 s |
POST /api/v1/bans |
10 000 / IP | 60 s |
PUT /api/v1/config/jails/{name} |
10 000 / IP | 60 s |
Tests bypass these by sending a fresh X-Forwarded-For: 192.0.2.<n> header per test. The Set Random Xff Header keyword in common.resource rotates the IP. The auth.resource Login Via HTTP and the api.resource Api Get/Post/Put/Delete wrappers all accept and propagate ${XFF_HEADER} automatically.
Test-IP Convention
All test data uses RFC5737 documentation-only ranges to avoid colliding with real internet addresses:
| Range | Purpose |
|---|---|
192.0.2.0/24 (TEST-NET-1) |
X-Forwarded-For headers |
198.51.100.0/24 (TEST-NET-2) |
Geo-lookup test IPs |
203.0.113.0/24 (TEST-NET-3) |
Ban / unban test IPs |
View Results
Open results/log.html or results/report.html in a browser.
Failure Protocol
Per project policy, test failures are NOT fixed by editing app code. If a test fails:
- Stop.
- Report the failure with: test name, expected vs actual, log excerpt, API request/response.
- Do not edit the test to weaken assertions.
- Do not edit frontend / backend / fail2ban config to make the test pass.
- The failure is a finding — separate from any bug-fix task.
AI Agent — General Instructions
You are an autonomous coding agent working on BanGUI, a web application for monitoring, managing, and configuring fail2ban through a clean web interface. This document defines how you operate, what rules you follow, and which workflow you repeat for every task.
Read this document completely before starting any work.
1. Project Context
BanGUI consists of two main parts:
| Layer | Stack | Docs |
|---|---|---|
| Backend | Python 3.12+, FastAPI, Pydantic v2, aiosqlite, structlog | Backend-Development.md |
| Frontend | TypeScript, React, Fluent UI v9, Vite | Web-Development.md |
Supporting documentation you must know and respect:
| Document | Purpose |
|---|---|
| Features.md | Complete feature list and expected behaviour |
| Architekture.md | System architecture, component relationships, data flow |
| Web-Design.md | Visual design rules, theming, layout, spacing, motion |
| Backend-Development.md | Backend coding rules, project structure, conventions |
| Web-Development.md | Frontend coding rules, project structure, conventions |
Always consult the relevant document before writing code. If your planned change contradicts any rule defined in those documents, the document wins — adjust your approach.
2. General Rules
2.1 Follow the Docs
- Every coding convention, naming rule, project structure decision, and library choice is defined in the development docs. Do not deviate.
- Backend code follows Backend-Development.md — strict typing, async only, structlog, Pydantic models, layered architecture (routers → services → repositories).
- Frontend code follows Web-Development.md — strict TypeScript, Fluent UI v9 only,
makeStylesfor styling, typed API calls, hooks for state. - Visual decisions follow Web-Design.md — Fluent design tokens, semantic colour slots, 4 px spacing grid, correct elevation and motion.
2.2 Write Production-Quality Code
- Write code as if it ships today. No TODOs, no placeholders, no half-implementations.
- Every function has explicit type annotations (Python) or type signatures (TypeScript).
- Every public function has a docstring (Python — Google style) or JSDoc comment (TypeScript).
- No
anyin TypeScript. NoAnyin Python (unless justified with a comment). - No magic numbers or strings — use named constants.
- No dead code, no commented-out blocks, no unused imports.
2.3 Keep It Small and Focused
- One function does one thing.
- One component per file.
- One service per domain.
- If a file grows beyond ~150 lines (components) or ~200 lines (services), split it.
2.4 Never Break Existing Code
- Before changing any file, understand what it does and who depends on it.
- Run the existing test suite before and after your changes. If tests fail after your change, fix them before moving on.
- Do not remove or rename public APIs without updating all callers.
2.5 Think Before You Code
- Read the task description carefully. If it is ambiguous, check Features.md and Architekture.md for clarification.
- Plan your changes before writing code. Identify which files are affected, which layers are involved, and what tests are needed.
- Prefer the simplest correct solution. Do not over-engineer.
3. Task Workflow
Repeat the following cycle for every task. Do not skip steps.
Step 1 — Plan Your Steps
- Break the task into concrete implementation steps.
- Identify which files need to be created, modified, or deleted.
- Identify which layers are affected (router, service, repository, model, component, hook, page, type, etc.).
- Identify edge cases and error scenarios.
- Write down your plan before touching any code.
Step 2 — Write Code
- Implement the feature or fix following the plan.
- Follow all rules from the relevant development docs:
- Backend → Backend-Development.md
- Frontend → Web-Development.md
- Design → Web-Design.md
- Architecture → Architekture.md
- Write clean, well-structured, fully typed code.
- Keep commits atomic — one logical change per commit.
Step 3 — Add Logging
- Add structured log statements at key points in new or modified code.
- Backend: use structlog with contextual key-value pairs — never
print(). - Log at appropriate levels:
infofor operational events,warningfor recoverable issues,errorfor failures. - Never log sensitive data (passwords, tokens, session IDs).
Step 4 — Write Tests
- Write tests for every new or changed piece of functionality.
- Backend: use
pytest+pytest-asyncio+httpx.AsyncClient. See Backend-Development.md § 9. - Frontend: test components and hooks according to the frontend test setup.
- Test the happy path and error/edge cases.
- Mock external dependencies — tests must never touch real infrastructure.
- Follow the naming pattern:
test_<unit>_<scenario>_<expected>.
Step 5 — Review Your Code
Run a thorough self-review before considering the task done. Check all of the following:
5.1 — Warnings and Errors
- Backend: run
ruff checkandmypy --strict(orpyright --strict). Fix every warning and error. - Frontend: run
tsc --noEmitandeslint. Fix every warning and error. - Zero warnings, zero errors — no exceptions.
5.2 — Test Coverage
- Run the test suite with coverage enabled.
- Aim for >80 % line coverage overall.
- Critical paths (auth, banning, scheduling, API endpoints) must be 100 % covered.
- If coverage is below the threshold, write additional tests before proceeding.
5.3 — Coding Principles
Verify your code against the coding principles defined in Backend-Development.md § 13 and Web-Development.md:
- Clean Code — Meaningful names, small functions, no magic values, guard clauses over deep nesting.
- Separation of Concerns — Each module has a single, well-defined responsibility. Layers are not mixed.
- Single Responsibility Principle — Every class and function has one reason to change.
- DRY — No duplicated logic. Shared behaviour is extracted.
- KISS — The simplest correct solution is used. No over-engineering.
- Type Safety — All types are explicit. No
any/Any. No# type: ignorewithout justification.
5.4 — Architecture Compliance
Verify against Architekture.md and the project structure rules:
- Files are in the correct directories (routers in
routers/, services inservices/, components incomponents/, etc.). - Dependencies flow in the right direction (routers → services → repositories; pages → components → hooks).
- No circular imports.
- No business logic in routers or components.
- No HTTP/framework concerns in services or repositories.
- Pydantic models separate request, response, and domain shapes.
- Frontend types live in
types/, not scattered across components.
Step 6 — Update Documentation
- If your change introduces new features, new endpoints, new components, or changes existing behaviour, update the relevant docs:
- Features.md — if feature behaviour changed.
- Architekture.md — if new modules, services, or data flows were added.
- Backend-Development.md or Web-Development.md — if new conventions were established.
- Keep documentation accurate and in sync with the code. Outdated docs are worse than no docs.
5. When You Are Stuck
- Re-read the task description and the relevant docs.
- Search the existing codebase for similar patterns — follow established conventions.
- Check the fail2ban source code in
fail2ban-master/if you need to understand how fail2ban works internally. - If a decision is genuinely ambiguous and no document covers it, choose the simplest option that is consistent with existing code and document your reasoning in a code comment.
6. What You Must Never Do
- Never commit code that does not compile or has type errors.
- Never commit code without tests.
- Never use libraries that are explicitly forbidden in the development docs.
- Never bypass the linter or type checker with blanket ignores.
- Never hard-code secrets, passwords, or tokens.
- Never push directly to
main— always use feature branches. - Never skip the review step — sloppy code compounds over time.
- Never leave a task half-done — finish it or revert it.
7. First-Run Setup
Initialize the Development Environment
Before starting the stack for the first time, set up the required environment variables:
-
Copy the example environment file:
cp .env.example .env -
Generate a session secret:
python -c 'import secrets; print(secrets.token_hex(32))'Copy the output and paste it as the value for
BANGUI_SESSION_SECRETin your.envfile. -
Optional: Customize other settings
- Edit
.envto adjust timezone, port numbers, or other settings - Default values are sensible for development (UTC, ports 8000/5173)
- Edit
-
Start the stack:
make up
Note: The session secret is critical for security. Do not commit .env to version control — it is already in .gitignore. Each environment (dev, staging, production) must have its own unique secret.
8. Dev Quick-Reference
Start / stop the stack
make up # start all containers (from repo root)
make down # stop all containers
make logs # tail logs
Backend: http://127.0.0.1:8000 · Frontend (Vite proxy): http://127.0.0.1:5173
API login (dev)
The backend accepts plaintext passwords (no frontend hashing).
The session cookie is named bangui_session.
# Dev master password: Hallo123!
TOKEN=$(curl -s -X POST http://127.0.0.1:8000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"password":"Hallo123!"}' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["expires_at"])')
# Login sets bangui_session cookie automatically.
# Use the cookie in subsequent requests:
curl -b "bangui_session=$(cat ~/.bangui_session)" http://127.0.0.1:8000/api/v1/dashboard/status
E2E Testing Notes
Debugging failures: open results/log.html (not output.xml) for full request/response traces.
Auth (RequestsLibrary vs Browser)
Login As Admin uses browser JavaScript (Evaluate JavaScript) — session cookie lives in the browser context only.
Any RequestsLibrary keyword (GET/POST to API) in the same test needs its own auth session.
Use Login Via HTTP from auth.resource to get a session cookie for RequestsLibrary calls.
RequestsLibrary and Browser library share no state.
CSRF Protection
Backend enforces CSRF on all POST/PUT/DELETE via X-BanGUI-Request: 1 header.
RequestsLibrary sessions must include this header on creation:
Create Session bangsess ${BACKEND_URL} headers=${headers} # headers = {X-BanGUI-Request: 1}
Then all requests on that session inherit it.
Robot Variable Type Rules
Bare values in Create Dictionary are strings, not Python types:
enabled=true→"true"(string) — backend Pydantic expects boolean → validation fails- Fix:
enabled=${TRUE}for booleans,${60}for integers ${NONE}is Robot's null/None equivalent${len(sources)}is invalid — useGet Length ${sources}keyword instead
Network Mode (podman-compose)
With network_mode: host, containers share the host network namespace.
- Backend can reach host's
127.0.0.1:PORT— needed for mock HTTP servers - Frontend must set
VITE_BACKEND_URL=http://localhost:8000sincelocalhostnow resolves to host - Vite proxy works when frontend also uses
network_mode: host(same namespace)
SSRF Protection
Blocklist source URLs are validated: hostname must resolve to a public IP.
127.0.0.1and other loopback addresses are rejected- In dev mode (
BANGUI_LOG_LEVEL=debug), loopback is allowed for e2e testing - For real e2e tests, use a public blocklist URL or ensure mock server is reachable
API Response Keys
Always verify response shape in results/log.html — field names differ from expectations:
- Blocklist import log:
itemsnotentries - Error responses:
{"code": "...", "detail": "..."}
Process Library Teardown
Terminate Process fails if the process alias doesn't exist (mock server not started).
Wrap in Run Keyword And Return Status to avoid teardown cascading failures.