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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions