|
| 1 | +# fastapi-sqlalchemy-pg-catalog |
| 2 | + |
| 3 | +Minimal FastAPI + SQLAlchemy 2.x + psycopg2 + Postgres 13 sample that |
| 4 | +reproduces the Postgres v3 dispatcher's simple-query `ClassCatalog` |
| 5 | +asymmetry (keploy/integrations#193). |
| 6 | + |
| 7 | +## What the bug looks like |
| 8 | + |
| 9 | +At app boot, SQLAlchemy's `Base.metadata.create_all(engine)` issues a |
| 10 | +`pg_catalog.pg_class` probe per declared table to decide whether to |
| 11 | +skip `CREATE TABLE`. With psycopg2 + parameter-less SQL the probe |
| 12 | +goes through the **simple-query** protocol path. |
| 13 | + |
| 14 | +In `pkg/postgres/v3/replayer/dispatcher/dispatcher.go`: |
| 15 | + |
| 16 | +* The **extended-query** path (`runEngineForPortal`, `case |
| 17 | + match.ClassCatalog`) consults the recorded transactional mock first |
| 18 | + and only falls back to the synthetic `Engines.Catalog.Execute` on |
| 19 | + miss. |
| 20 | +* The **simple-query** path (`dispatchBySQLHash`, `case |
| 21 | + match.ClassCatalog`) goes straight to the synthetic engine — even |
| 22 | + though a recorded `type: query` mock with `class: CATALOG` and the |
| 23 | + correct rows is sitting in `mocks.yaml`. |
| 24 | + |
| 25 | +With no `type: catalog` snapshot present, the synthetic engine |
| 26 | +answers `rows: 0, cc: "SELECT 0"`. SQLAlchemy reads zero rows as |
| 27 | +"table missing", issues `CREATE TABLE project ...`, and the |
| 28 | +transactional engine misses (because the recording never captured a |
| 29 | +CREATE TABLE — at record time the table already existed). The app |
| 30 | +worker dies with `psycopg2.DatabaseError: keploy-pg-v3: no recorded |
| 31 | +invocation matched`, every HTTP testcase that follows fails with |
| 32 | +connection-reset. |
| 33 | + |
| 34 | +## Reproducing locally |
| 35 | + |
| 36 | +```bash |
| 37 | +cd fastapi-sqlalchemy-pg-catalog |
| 38 | +docker compose build |
| 39 | + |
| 40 | +# Baseline (no keploy) — should pass |
| 41 | +docker compose up -d |
| 42 | +bash flow.sh |
| 43 | +docker compose down -v |
| 44 | + |
| 45 | +# Record |
| 46 | +( bash flow.sh > flow-record.log 2>&1 ) & |
| 47 | +sudo -E keploy record \ |
| 48 | + -c "docker compose -f docker-compose.yml up" \ |
| 49 | + --container-name pg-catalog-repro-app \ |
| 50 | + --cmd-type docker-compose \ |
| 51 | + --record-timer 60s |
| 52 | + |
| 53 | +# Replay (pre-fix: FAILS with "no recorded invocation matched" on CREATE TABLE) |
| 54 | +sudo -E keploy test \ |
| 55 | + -c "docker compose -f docker-compose.yml up" \ |
| 56 | + --container-name pg-catalog-repro-app \ |
| 57 | + --cmd-type docker-compose \ |
| 58 | + --apiTimeout 120 --delay 15 --disableMockUpload |
| 59 | +``` |
| 60 | + |
| 61 | +## Layout |
| 62 | + |
| 63 | +| File | Purpose | |
| 64 | +|-----------------------------|---------------------------------------------------------------------------| |
| 65 | +| `app/main.py` | FastAPI app with one declarative `Project` model + lifespan create_all | |
| 66 | +| `app/Dockerfile` | Python 3.12-slim + requirements | |
| 67 | +| `app/requirements.txt` | fastapi, uvicorn, sqlalchemy 2.0.36, psycopg2-binary 2.9.10 | |
| 68 | +| `docker-compose.yml` | postgres:13.22-alpine + app, app published at host port 8123 | |
| 69 | +| `init.sql` | Pre-creates the `project` table so record-time create_all is a no-op | |
| 70 | +| `flow.sh` | Drives `GET /health` and `GET /projects` against the app | |
| 71 | + |
| 72 | +## Compose env knobs |
| 73 | + |
| 74 | +Set these to isolate concurrent runs (the CI lane drives a 3-cell |
| 75 | +matrix on one Docker daemon and overrides each): |
| 76 | + |
| 77 | +| Env var | Default | Purpose | |
| 78 | +|------------------|-------------------------|------------------------------------------| |
| 79 | +| `APP_CONTAINER` | `pg-catalog-repro-app` | App container name (keploy `--container-name`) | |
| 80 | +| `DB_CONTAINER` | `pg-catalog-repro-db` | Postgres container name | |
| 81 | +| `APP_HOST_PORT` | `8123` | Host-side port mapped to app's 8000 | |
| 82 | +| `COMPOSE_NET` | `reprnet` | Docker network name | |
| 83 | + |
| 84 | +## Used by |
| 85 | + |
| 86 | +* `keploy/integrations` Woodpecker lane |
| 87 | + `.woodpecker/sqlalchemy-pg-catalog-postgres.yml` |
0 commit comments