setting:view used to gate the whole /settings page, which mixed panel-wide configuration with a user's own account. Revoking it to keep a standard user out of the panel settings also took away their profile, password, 2FA and SSH keys, so the permission could not be used for what it names. The account half now lives at /profile: its endpoints are self-scoped and only require a session, so the page carries no permission at all and is reached from the user menu (and Ctrl+K) rather than a sidebar group. /settings keeps the panel-wide tabs behind setting:view, with saving gated on setting:update, security on setting:manage and the SMTP/Telegram channels on is_admin, matching what each endpoint checks. The shipped "user" role no longer gets setting:view. Migration 42 drops it from roles holding it without setting:update/manage, since those grants only existed to reach the profile page; it runs once (marker in panel_settings) so a later deliberate grant is not reverted on the next start.
Backend Tests
Smoke / unit tests for the FastAPI backend, focused on the security-critical
modules: app.crypto, app.auth, app.rbac.
Layout
backend/
├── pytest.ini # pytest config (testpaths=tests, markers, addopts)
├── requirements-dev.txt # test deps (-r requirements.txt + pytest …)
└── tests/
├── conftest.py # env setup + reusable fixtures
├── test_crypto.py # Fernet field-encryption helpers
├── test_auth.py # password hashing, JWT, IP parsing, perm helpers
└── test_rbac.py # PermissionEngine + registry
How conftest.py keeps tests hermetic
The application reads configuration at import time:
app.config.Settingsis apydantic-settingsmodel that auto-loads.envfrom the current working directory.app.dbconstructs the SQLAlchemy engine on import.
tests/conftest.py therefore, before any app.* import:
- Sets
DATABASE_URL,SECRET_KEY,ADMIN_PASSWORD,LOG_LEVELto safe test values viaos.environ.setdefault. - Generates a fresh
FERNET_KEYso encrypt/decrypt round-trips succeed. - Adds
backend/tosys.path. chdirs to a temp directory so the developer's local.env(which contains keys not declared onSettings) is not picked up. Without this step pydantic raisesextra_forbidden.
No real Postgres connection is opened — SQLAlchemy connects lazily, and these tests never issue a query.
Running locally
There is no system-wide Python on this host, so use the same Python image as CI:
docker run --rm -v "$PWD/backend:/app" -w /app python:3.12-slim bash -c '
pip install -r requirements-dev.txt &&
python -m pytest -p no:warnings
'
Run a single test:
python -m pytest tests/test_auth.py::TestJWT -v
CI
GitHub Actions runs the suite on every push / PR to main that touches
backend/** — see .github/workflows/backend-tests.yml.
Coverage is reported to the job log and uploaded as an artifact.
Adding more tests
- Pure / domain logic (no DB): use the
fake_user_factory/fake_role_factoryfixtures inconftest.py. They produceSimpleNamespaceobjects that satisfy duck-typed access inauth.pyandrbac/engine.py. - Touches the DB: build out a SQLite-backed fixture (override
app.db.enginewithsqlite:///:memory:andBase.metadata.create_all). Not required for the current smoke layer. - API endpoints: spin up
TestClient(app)onceget_dband any external Proxmox / SSH dependencies are dependency-overridden.
Markers
Defined in pytest.ini:
@pytest.mark.smoke— quickest sanity checks.@pytest.mark.unit— pure unit tests.@pytest.mark.integration— multi-module integration.
Run only smoke tests:
python -m pytest -m smoke