* 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.
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.shThis copies
apps/api/.env.example→apps/api/.env(along with the other app env files). The compose file readsapps/api/.env, so this step must run before the firstdocker composeinvocation.
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
--buildrebuilds theapi-testsimage whenDockerfile.devorrequirements/*.txtchange.--abort-on-container-exitstops the dependency services as soon asapi-testsexits.--exit-code-from api-testspropagates 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.shfrom 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-minioentrypoint creates the bucket named byAWS_S3_BUCKET_NAME(defaultuploads). Change the value inapps/api/.envand re-run. - Database state leaking between runs — confirm you ran
down -v(not justdown). The tmpfs mounts are torn down with the container, but the network and any externally created volumes need-vto clear.