# Testing Requirements ## Coverage Threshold - **Minimum: 80% line coverage** for all backend code - Critical paths (auth, banning, scheduling, API endpoints): **100%** ## CI Enforcement `.github/workflows/ci.yml` runs pytest with `--cov-fail-under=80`. Build fails if coverage drops below threshold. ## Running Tests Locally ```bash cd backend pytest --cov=app --cov-report=term-missing ``` ## Coverage Reports - Terminal: `--cov-report=term-missing` - HTML: `--cov-report=html` (output in `htmlcov/`) ## Coverage Badge Add to README once CI runs successfully: ```md [![Coverage](https://codecov.io/gh//BanGUI/branch/main/graph/badge.svg)](https://codecov.io/gh//BanGUI) ``` Requires codecov.io integration with repository. ## Writing Tests - Follow pattern: `test___` - Mock external dependencies (fail2ban socket, aiohttp calls) - Test happy path AND error/edge cases - See `Docs/Backend-Development.md §9` for detailed testing guide ## E2E Testing An end-to-end test suite using **Robot Framework** with the Browser library (Playwright-backed) exercises the full running stack: frontend → backend → fail2ban → database. ### Running E2E Tests ```bash make e2e ``` Requires: - `BANGUI_SESSION_SECRET` env var must be set (see [Backend-Development.md](Backend-Development.md) for setup) - Stack must be startable via `make up` (Docker/Podman + compose installed) - `rfbrowser init` is run automatically by the `e2e` target (Playwright browsers downloaded on first run; re-run after `robotframework-browser` version changes) ### HTML Report After a run, open `e2e/results/report.html` in a browser to view the detailed HTML report with screenshots on failure. ### Writing New E2E Tests Place new `.robot` files in `e2e/tests/`. Use `e2e/resources/common.resource` for shared variables and setup/teardown, and `e2e/resources/auth.resource` for the `Login As Admin` keyword. ### E2E-3 — Ban Pipeline Timing Test **E2E-3** (`e2e/tests/02_ban_records.robot`: *Simulated Failed Logins Appear As Ban Records*) exercises the full ban pipeline: ``` simulate_failed_logins.sh → fail2ban log scan → ban recorded in fail2ban DB → backend polls socket (on-demand, no push) → /api/bans/active → history_sync archive (every 300 s) → /api/history ``` Key timing facts: - **fail2ban** (`manual-Jail`, `backend=polling`) re-reads `auth.log` on its own interval, not event-driven. - **maxretry=3** means a ban triggers after the 3rd matching line. `simulate_failed_logins.sh` writes 5 lines to ensure the threshold is crossed. - **15 s sleep** in the test gives fail2ban time to detect and record the ban before the first assertion. This is a heuristic — the actual polling interval depends on fail2ban's internal cycle. - **history_sync** runs every 300 s (`HISTORY_SYNC_INTERVAL` in `backend/app/tasks/history_sync.py`). The History page reads from the archive DB, so it may lag up to 300 s behind real-time. The E2E test uses `GET /api/bans/active` (direct socket query) for the API assertion to avoid this lag. - **Pagination**: the History page paginates results. Use `?page_size=500` to push the test IP onto the first page, or assert via the API. If the test fails at Step 2 (no ban detected via API) but `check_ban_status.sh` shows the IP is banned inside the container, the backend-to-fail2ban socket path is broken. If `check_ban_status.sh` also shows no ban, the log volume mapping is wrong (fail2ban is not reading the file `simulate_failed_logins.sh` writes to).