Files
plane/apps/api/tests/RUNNING_TESTS.md
T
sriram veeraghanta 9f77ea5ebb fix: Add docker pytest runner and fix bugs the suite surfaced (#9138)
* chore(api): add docker compose test runner

Adds docker-compose-test.yml at the repo root that boots an isolated
postgres / valkey / rabbitmq / minio stack with health checks and tmpfs
data dirs, then runs pytest against it and exits. Includes a usage doc
under apps/api/tests/RUNNING_TESTS.md and a pointer in AGENTS.md.

Prereq: ./setup.sh (generates apps/api/.env).

Usage:
  docker compose -f docker-compose-test.yml up --build \
    --abort-on-container-exit --exit-code-from api-tests
  docker compose -f docker-compose-test.yml down -v

* fix(api): correct bugs surfaced by the pytest suite

Five small bugs caught by enabling the pytest contract suite end-to-end.
Each is independently justifiable:

- api/serializers/cycle.py + api/views/cycle.py: CycleCreateSerializer.validate
  required project_id in the request body, but the view only ever passes
  it through the URL kwarg. Cycle create/update via the public API was
  returning 400 "Project ID is required". Read project_id from
  serializer context (passed by the view) in addition to body/instance.

- app/views/api.py: ApiTokenEndpoint.get(pk) and patch(pk) did not filter
  out is_service=True tokens, so a user could read and modify service
  tokens through the user token endpoint. The list mode and delete
  already filter is_service=False; aligned the other two.

- bgtasks/work_item_link_task.py: validate_url_ip checked hostname before
  scheme, so file:///etc/passwd raised "No hostname found" instead of
  the documented "Only HTTP and HTTPS" error. Swapped the order so the
  scheme guard matches the docstring intent.

- utils/path_validator.py: get_allowed_hosts used `WEB_URL or APP_BASE_URL`
  so when both are configured to different hosts (the standard local
  setup: WEB_URL=:8000, APP_BASE_URL=:3000), only one was added to the
  allow-list. Redirects to APP_BASE_URL then had their next_path stripped
  because the host wasn't allowed. Include every configured base URL.

* chore(api): align pytest tests with current behavior, clear warnings

Test-side fixes paired with the product fixes in the previous commit, plus
deprecation cleanup that drops the test run from 104 warnings to 0.

Tests:
- tests/contract/api/test_cycles.py: project fixture sets cycle_view=True;
  the Project model defaults the flag to False, so cycle create/update
  always tripped "Cycles are not enabled for this project".
- tests/contract/app/test_authentication.py: next_path uses "/workspaces"
  (validate_next_path rejects values without a leading slash and returns
  empty, which dropped the path from the redirect URL).
- tests/unit/bg_tasks/test_copy_s3_objects.py: mocked sync_with_external_service
  now returns description_json; the task unconditionally writes the value
  back to the Issue, and Issue.description_json is NOT NULL on UPDATE.
- tests/unit/utils/test_url.py: three length-limit tests placed the URL at
  char 970+ on a single line, which contains_url truncates away as ReDoS
  defense (500-char per-line cap). Restructured to keep test intent intact
  while staying inside the per-line window.

Warning cleanup (104 → 0):
- settings/common.py: removed USE_L10N=True (deprecated in Django 4.0,
  removed in 5.0; default is True).
- celery.py, settings/local.py, settings/production.py: pythonjsonlogger
  moved jsonlogger → json; update the import / formatter path.
2026-05-26 01:21:37 +05:30

4.3 KiB

Running the API Test Suite

This guide covers running the Django/pytest suite for apps/api inside Docker via docker-compose-test.yml at the repo root. The compose file boots an isolated stack — Postgres, Valkey (Redis), RabbitMQ, MinIO — with tmpfs-backed data dirs, so every run begins from a clean slate and a single teardown command removes everything.

For background on the test layout, markers, and fixtures, see TESTING_GUIDE.md and README.md.

Prerequisites

  • Docker and Docker Compose v2 (docker compose ...)

  • Env files generated via the setup script:

    ./setup.sh
    

    This copies apps/api/.env.exampleapps/api/.env (along with the other app env files). The compose file reads apps/api/.env, so this step must run before the first docker compose invocation.

Running the suite

All commands are run from the repo root.

Full suite

docker compose -f docker-compose-test.yml up \
  --build \
  --abort-on-container-exit \
  --exit-code-from api-tests
  • --build rebuilds the api-tests image when Dockerfile.dev or requirements/*.txt change.
  • --abort-on-container-exit stops the dependency services as soon as api-tests exits.
  • --exit-code-from api-tests propagates pytest's exit code so this works in CI.

Filtered runs

Use docker compose run to override the default pytest command. Anything you pass after the service name is forwarded to pytest.

# Only unit tests (marker defined in pytest.ini)
docker compose -f docker-compose-test.yml run --rm --build api-tests pytest -m unit

# A single directory, filtered by name
docker compose -f docker-compose-test.yml run --rm api-tests \
  pytest plane/tests/unit -k "test_workspace"

# Single file with verbose output
docker compose -f docker-compose-test.yml run --rm api-tests \
  pytest plane/tests/unit/models/test_workspace.py -vv

The available markers (unit, contract, smoke, slow) are declared in apps/api/pytest.ini.

Teardown

docker compose -f docker-compose-test.yml down -v

-v removes the ephemeral volumes and the test_env network. Because the data directories are tmpfs, no host state survives a teardown — every run starts clean. Run this between unrelated test sessions to free Docker resources.

How it works

Service Image Purpose
test-db postgres:15.7-alpine Application database
test-redis valkey/valkey:7.2.11-alpine Cache / Celery broker
test-mq rabbitmq:3.13.6-management-alpine Task queue
test-minio minio/minio S3-compatible object storage
api-tests built from apps/api/Dockerfile.dev Installs requirements/test.txt, runs pytest

All four dependencies expose health checks; api-tests waits for service_healthy on each via depends_on, so pytest only starts once the stack is ready.

Test-time env overrides live in the compose file itself (POSTGRES_HOST=test-db, REDIS_URL=redis://test-redis:6379/, AWS_S3_ENDPOINT_URL=http://test-minio:9000, DJANGO_SETTINGS_MODULE=plane.settings.test). Everything else is inherited from apps/api/.env.

Troubleshooting

  • ./apps/api/.env: no such file or directory — run ./setup.sh from the repo root.
  • Port already in use — none of the test services publish host ports; if you see this it's coming from a different compose stack. Stop the local stack (docker compose -f docker-compose-local.yml down).
  • Stale image after dependency changes — rebuild explicitly: docker compose -f docker-compose-test.yml build --no-cache api-tests.
  • MinIO bucket missing — the test-minio entrypoint creates the bucket named by AWS_S3_BUCKET_NAME (default uploads). Change the value in apps/api/.env and re-run.
  • Database state leaking between runs — confirm you ran down -v (not just down). The tmpfs mounts are torn down with the container, but the network and any externally created volumes need -v to clear.