Skip to content

Latest commit

 

History

History

README.md

doccano-django — keploy postgres-v3 simple-Query bind regression sample

Minimal reproducer for the doccano polymorphic-resourcetype failure that motivated keploy/integrations#177 ("fix(postgres-v3): extract simple-Query literals into bindValues").

The sample wraps doccano (Django + django-rest-polymorphic + psycopg2) at version v1.8.5 against postgres 13.3-alpine. The shape under test: a polymorphic Django model (Project with subclass TextClassificationProject) created over the REST API and re-read via DRF's polymorphic queryset. Without the integrations fix, every SELECT … FROM django_content_type WHERE app_label = $1 AND model = $2 at replay returns the same recorded mock (the matcher's pickSessionFallback FIFO-collapses every variant onto the first recording when the bind signature is empty), so the polymorphic serializer can't resolve the project's subclass and resourcetype flips from "TextClassificationProject" to "Project".

The bug is in keploy's recorder + replayer simple-Query path; doccano is just a vehicle. Same pattern would reproduce on any Django app that:

  • Uses a polymorphic ORM (django-polymorphic / django-rest-polymorphic).
  • Sends parameterised reads via psycopg2's simple-Query mode (literals interpolated into the SQL text rather than carried in a separate Bind packet).
  • Exercises the polymorphic queryset across multiple HTTP requests against the same recorded backend.

What's in here

  • Dockerfile — thin wrapper around doccano/doccano:backend pinning the upstream version this sample tracks and installing the sample entrypoint that honors DOCCANO_SKIP_BOOTSTRAP=1. Future doccano releases that change the bug-triggering shape are addressed by retagging here, not by scattering version pins across the lane scripts in keploy/integrations / keploy/enterprise.
  • docker-compose.yml — the orchestration: postgres-13 alongside the doccano backend, on a fixed subnet so the lane scripts can rely on stable IPs across record/replay phases.
  • flow.sh — the minimum reproducer traffic, ~10 HTTP calls. POST /v1/projects (creates a TextClassificationProject), then GET list / GET single / PATCH single / a few dependent reads. The GET / PATCH responses are what diverge under the bug — POST passes either way because the in-memory subclass instance shapes the response without consulting the DB.
  • keploy.yml.template — keploy config skeleton (proxy port, DNS port, container name placeholders) that lane scripts in keploy/integrations and keploy/enterprise envsubst into a per-job copy.

Running locally

Without keploy — smoke check

docker compose up -d
./flow.sh bootstrap

docker compose down
DOCCANO_SKIP_BOOTSTRAP=1 docker compose up -d
./flow.sh record-traffic

docker compose down -v

This is what the keploy/integrations and keploy/enterprise CI lanes wrap in keploy record / keploy test — the base compose is uninstrumented and runs unchanged inside those lanes. The first launch runs doccano's migrations and creates the admin user; the second launch skips bootstrap and starts gunicorn directly against the populated database volume.

Without keploy — measuring real Python line coverage

The base image is uninstrumented. Apply the coverage overlay to add coverage.py per-worker tracking:

mkdir -p coverage
docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d --build
./flow.sh bootstrap

docker compose -f docker-compose.yml -f docker-compose.coverage.yml down
DOCCANO_SKIP_BOOTSTRAP=1 docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d --build
./flow.sh record-traffic

docker compose -f docker-compose.yml -f docker-compose.coverage.yml kill -s SIGTERM backend
sleep 3
DOCCANO_SKIP_BOOTSTRAP=1 docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d backend
./flow.sh coverage
docker compose -f docker-compose.yml -f docker-compose.coverage.yml down -v

The overlay (Dockerfile.coverage + docker-compose.coverage.yml) adds coverage[toml] and a coverage_subprocess.pth so each gunicorn worker auto-starts coverage tracking. It is consumed ONLY by the standalone GH Actions workflow — keploy CI lanes ignore it and run the base compose, paying zero coverage cost.

With keploy — record + replay

docker compose up -d
./flow.sh bootstrap
docker compose down

# In one shell:
keploy record -c "DOCCANO_SKIP_BOOTSTRAP=1 docker compose up" --container-name doccano_backend \
  --proxy-port 18081 --dns-port 18082

# In another shell:
./flow.sh record-traffic
# SIGINT keploy when traffic returns

keploy test -c "DOCCANO_SKIP_BOOTSTRAP=1 docker compose up" --containerName doccano_backend \
  --apiTimeout 60 --delay 20 --host 127.0.0.1 --port 18080 \
  --proxy-port 18081 --dns-port 18082

Expected outcome with the integrations fix in place: 0 failures, all is_text_project: true / resourcetype: "TextClassificationProject" across the project-read responses.

Expected outcome without the fix: tests covering GET-after-POST project reads fail with is_text_project: true → false and resourcetype: "TextClassificationProject" → "Project".

CI lanes that consume this sample

  • keploy/integrations.woodpecker/doccano-postgres.yml / .ci/scripts/python/doccano/doccano-linux.sh. Three-way matrix (record-build × replay-build, record-latest × replay-build, record-build × replay-latest) — the cross-binary cells stay red until both keploy releases pick up the bind-extraction fix.
  • keploy/enterprise.woodpecker/doccano-linux.yml / .ci/scripts/doccano-linux.sh. Same three-way matrix wired to the enterprise compat-matrix harness.

Both clone this directory at the branch / tag pinned by the respective lane script.

Related