You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Integration/smoke tests currently run against the live OpenProject instance. Every create_work_package / create_project consumes an auto-increment ID that is not reclaimed on
delete, so repeated testing slowly burns IDs and pollutes the production-adjacent instance. An ephemeral, disposable OpenProject spun up per test session would remove that cost entirely and
also let us test destructive scenarios and different server settings freely.
Feasibility verdict: feasible, moderate effort
Local check: Docker 29.4.0 + Compose v5.1.1 installed, 16 GB RAM (an all-in-one OpenProject needs
~1.5–2 GB). testcontainers not yet a dep. (Note: ensure the Docker daemon/Docker Desktop is
actually running — docker info returned empty in the check.)
Recommended shape — single all-in-one container, opt-in session fixture
Admin bootstrap:OPENPROJECT_SEED__ADMIN__USER__PASSWORD=<known> + OPENPROJECT_SEED__ADMIN__USER__PASSWORD__RESET=false → reproducible admin, no forced reset.
API-token bootstrap (the tricky part — no env for it): after the container is healthy, mint a
token for admin via docker exec <c> bundle exec rails runner "puts Token::API.create!(user: User.find_by!(login: 'admin')).plain_value" and feed it as OPENPROJECT_API_KEY (our client
already uses apikey:<token> basic auth). Version-pin the image because the Token model API has
changed across releases. (Alternative to evaluate: global basic auth OPENPROJECT_AUTHENTICATION_GLOBAL__BASIC__AUTH_USER=apikey + _PASSWORD=<known> so the client's apikey:<pw> matches — but the user/author context for writes is uncertain; test before relying.)
pytest wiring: a session-scoped fixture starts the container, waits on an HTTP healthcheck
(GET /api/v3) with a generous timeout (~300s first boot), mints the token, exports OPENPROJECT_URL/OPENPROJECT_API_KEY, yields, then stops+removes the container. Re-point tests/test_integration_smoke.py (and future integration tests) at it. Gate behind an opt-in
(e.g. OPENPROJECT_DOCKER=1 / a @pytest.mark.integration marker) so the default network-free
suite is unchanged and CI without Docker still passes.
Options compared
Option
Fit for tests
Notes
All-in-one Docker container
✅ recommended
One container, simplest to wire (testcontainers or a thin docker wrapper); DB bundled.
docker-compose
⚠️ heavier
Prod-like (separate web/worker/db/cache); more moving parts than tests need.
Helm / Kubernetes
❌ overkill
Only worth it if CI already runs on K8s; not for unit/integration.
Costs / risks
Boot latency: all-in-one first boot runs migrations + seeding (~2–5 min). Mitigate with a
session-scoped fixture (boot once), opt-in gating, and image caching in CI.
Docker requirement: local Docker Desktop must be running; CI runners need Docker-in-Docker.
Version drift: image version must track the live instance for fidelity (esp. progress mode).
Token bootstrap fragility: rails-runner snippet is version-sensitive → pin the image.
Proposed scope (if approved)
PoC: pull the pinned image, time first-boot, validate the token-mint + a create→delete round-trip.
Add testcontainers (or a thin docker fixture) as a dev dep; implement the session fixture.
Re-point the integration smoke test at the container behind an opt-in marker; keep the live-run
path available via env override.
Document how to run it (README/dev guide) and in CI.
Acceptance criteria
OPENPROJECT_DOCKER=1 uv run pytest -m integration spins up an ephemeral OP, runs the
create→read→update→delete smoke test, and tears it down — no calls to the live instance.
Default uv run pytest is unchanged (no Docker needed).
Motivation
Integration/smoke tests currently run against the live OpenProject instance. Every
create_work_package/create_projectconsumes an auto-increment ID that is not reclaimed ondelete, so repeated testing slowly burns IDs and pollutes the production-adjacent instance. An
ephemeral, disposable OpenProject spun up per test session would remove that cost entirely and
also let us test destructive scenarios and different server settings freely.
Feasibility verdict: feasible, moderate effort
Local check: Docker 29.4.0 + Compose v5.1.1 installed, 16 GB RAM (an all-in-one OpenProject needs
~1.5–2 GB).
testcontainersnot yet a dep. (Note: ensure the Docker daemon/Docker Desktop isactually running —
docker inforeturned empty in the check.)Recommended shape — single all-in-one container, opt-in session fixture
openproject/openproject:<pinned>(all-in-one; bundles PostgreSQL + memcached viasupervisord). Pin the version to match the live instance (reported
Core 17.5.1) so testsmirror real behavior (incl. the
percentage_doneprogress mode — see Investigate/document percentage_done writability vs OpenProject progress calculation mode (status-based = read-only) #6).OPENPROJECT_SEED__ADMIN__USER__PASSWORD=<known>+OPENPROJECT_SEED__ADMIN__USER__PASSWORD__RESET=false→ reproducible admin, no forced reset.token for admin via
docker exec <c> bundle exec rails runner "puts Token::API.create!(user: User.find_by!(login: 'admin')).plain_value"and feed it asOPENPROJECT_API_KEY(our clientalready uses
apikey:<token>basic auth). Version-pin the image because the Token model API haschanged across releases. (Alternative to evaluate: global basic auth
OPENPROJECT_AUTHENTICATION_GLOBAL__BASIC__AUTH_USER=apikey+_PASSWORD=<known>so the client'sapikey:<pw>matches — but the user/author context for writes is uncertain; test before relying.)(
GET /api/v3) with a generous timeout (~300s first boot), mints the token, exportsOPENPROJECT_URL/OPENPROJECT_API_KEY, yields, then stops+removes the container. Re-pointtests/test_integration_smoke.py(and future integration tests) at it. Gate behind an opt-in(e.g.
OPENPROJECT_DOCKER=1/ a@pytest.mark.integrationmarker) so the default network-freesuite is unchanged and CI without Docker still passes.
Options compared
dockerwrapper); DB bundled.Costs / risks
session-scoped fixture (boot once), opt-in gating, and image caching in CI.
Proposed scope (if approved)
testcontainers(or a thin docker fixture) as a dev dep; implement the session fixture.path available via env override.
Acceptance criteria
OPENPROJECT_DOCKER=1 uv run pytest -m integrationspins up an ephemeral OP, runs thecreate→read→update→delete smoke test, and tears it down — no calls to the live instance.
uv run pytestis unchanged (no Docker needed).References
Type: test infrastructure. Related: #1, #3 (pytest), #6 (progress mode). Label: backlog.