development.md 2.8 KB

Development

Setup

python3 -m venv venv
venv/bin/pip install -r requirements-dev.txt

Running locally without Okta or a real PPS

Use a dev config (never edit the real config.toml) with static auth:

[auth]
mode = "static"
[auth.static]
username = "admin"
password = "admin"
email = "dev@example.invalid"
role = "admin"
PPSQ_CONFIG=/tmp/dev-config.toml PPSQ_ALLOW_INSECURE=1 venv/bin/python wsgi.py
  • PPSQ_CONFIG points at your dev file so nothing touches the real config.
  • PPSQ_ALLOW_INSECURE=1 allows an http:// PPS base_url.
  • Static mode logs a loud warning and refuses non-loopback binds (override with PPSQ_ALLOW_INSECURE_AUTH=1).
  • Point [pps] base_url at a mock server, or a throwaway PPS folder.

Tests

venv/bin/python -m pytest        # ~57 tests, < 1s

tests/conftest.py guarantees isolation: a session-scoped autouse fixture sets PPSQ_CONFIG to a temp copy of config.example.toml before any app import, and ConfigStore refuses to open the default config.toml under pytest. No test starts a worker thread — they call queue._process(job) synchronously with a FakePPS that records every call.

Suites:

File Covers
test_config_store.py round-trip (comments preserved), atomic write/perms/backup, redaction, validation, restart-vs-live, client rebuild, migration, isolation guard
test_auth.py is_allowed/safe_next (pure), static login, CSRF, admin gate
test_oidc.py OIDC callback allow/deny/role/demotion (monkeypatched token)
test_report_release.py pipeline: move-first order, fresh re-read per step, no-deletedfolder rule, fallback, destinations, deferred (non-blocking) step delay
test_paging.py cursor from raw batch, has_more boundary, _clamp_limit
test_prefs.py override/default/fallback/isolation
test_jobs_visibility.py user attribution, targetfolder validation, per-user scoping

Project layout

See AGENTS.md for the module map and the invariants you must not break. In short:

  • wsgi.py constructs everything; app.py is a factory with no import-time side effects.
  • Config flows through immutable ConfigStore snapshots (one per request, one per job).
  • Report & Release is data-driven in pipeline.py — add a step there, not in worker.py.
  • Frontend is vanilla JS (static/app.js, static/admin.js); no build step.

Adding things

  • A pipeline step: @step("name") in pipeline.py + add to ALLOWED_STEPS.
  • A config key: config.example.toml (with a comment) → read from snapshot → if admin-editable, add to ADMIN_EDITABLE + validation in config_store.py.
  • A preference: a PrefSpec in prefs.py PREF_SPECS.

Match surrounding style: from __future__ import annotations, pps.* logger names, %-style logging, _-prefixed privates.