Skip to content

Ephemeral OpenProject (Docker) for integration tests — stop burning IDs on the live instance #7

Description

@AndreaV-Lsi

Motivation

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

  • Image: openproject/openproject:<pinned> (all-in-one; bundles PostgreSQL + memcached via
    supervisord). Pin the version to match the live instance (reported Core 17.5.1) so tests
    mirror real behavior (incl. the percentage_done progress mode — see Investigate/document percentage_done writability vs OpenProject progress calculation mode (status-based = read-only) #6).
  • 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)

  1. PoC: pull the pinned image, time first-boot, validate the token-mint + a create→delete round-trip.
  2. Add testcontainers (or a thin docker fixture) as a dev dep; implement the session fixture.
  3. Re-point the integration smoke test at the container behind an opt-in marker; keep the live-run
    path available via env override.
  4. 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).

References

Type: test infrastructure. Related: #1, #3 (pytest), #6 (progress mode). Label: backlog.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backlogLogged future work item, not yet scheduled

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions