diff --git a/.env.example b/.env.example index a95c63b..5325fc5 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ ENTERPRISE_MODE=false # Database — local Docker Postgres for dev -DATABASE_URL=postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess +DATABASE_URL=postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess # Anthropic API (used in Phase 1 with dummy data only) ANTHROPIC_API_KEY=sk-ant-... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43bec51..33aca29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest # Postgres service container — mirrors the local docker-compose setup. - # Tests connect as dess/dess_dev_only on 127.0.0.1:5433 against the + # Tests connect as dess/dess_dev_only on 127.0.0.1:5434 against the # dess_test database. Credentials match app/config.py defaults so # no env-var fiddling is needed. services: @@ -23,7 +23,7 @@ jobs: POSTGRES_PASSWORD: dess_dev_only POSTGRES_DB: dess_test ports: - - 5433:5432 + - 5434:5432 options: >- --health-cmd="pg_isready -U dess" --health-interval=5s @@ -53,22 +53,22 @@ jobs: - name: Create dev database env: PGPASSWORD: dess_dev_only - run: psql -h localhost -p 5433 -U dess -d dess_test -c "CREATE DATABASE dess OWNER dess;" + run: psql -h localhost -p 5434 -U dess -d dess_test -c "CREATE DATABASE dess OWNER dess;" - name: Apply Alembic migrations to test DB env: - DATABASE_URL: postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess_test + DATABASE_URL: postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess_test run: alembic upgrade head - name: Apply Alembic migrations to dev DB (for safety-rail tests) env: - DATABASE_URL: postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess + DATABASE_URL: postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess run: alembic upgrade head - name: Run pytest env: - DATABASE_URL: postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess - TEST_DATABASE_URL: postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess_test + DATABASE_URL: postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess + TEST_DATABASE_URL: postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess_test run: pytest -q # Check that frontend/src/api/generated_types.ts matches what the diff --git a/RUNBOOK.md b/RUNBOOK.md index a12db7c..9bf019e 100644 --- a/RUNBOOK.md +++ b/RUNBOOK.md @@ -7,16 +7,24 @@ The demo dataset is the only dataset: nothing here touches real data. | Piece | What it is | Where it runs | |---|---|---| -| Database | Postgres 15 in Docker, Compose project `dess`, data kept in the volume `dess_pgdata` | `localhost:5433` (not 5432 — see below) | +| Database | Postgres 15 in Docker, Compose project `dess`, data kept in the volume `dess_pgdata` | `localhost:5434` (not 5432 or 5433 — see below) | | Databases inside it | `dess` (the app) and `dess_test` (the tests; wiped by every test run) | same server | | Backend | FastAPI app in `app/`, schema changes in `alembic/` | `http://localhost:8000` | | Frontend | React + Vite in `frontend/` | `http://localhost:5173` (forwards `/api` to 8000) | | Python environment | `.venv/` in the repo folder, Python 3.11 or newer | — | | Settings | `.env` (copied from `.env.example`, never committed) | — | -**Why port 5433.** Other Postgres servers on the same machine usually take -5432. DESS stays off it so it can never connect to someone else's database -by mistake. `tests/test_database_port.py` fails if anything moves it back. +**Why port 5434.** Other Postgres servers on the same machine usually take +5432, and 5433 is where a second Postgres or a Cloud SQL Auth Proxy tunnel +conventionally listens. DESS stays off both, so it can never connect to +someone else's database by mistake, and never sits on a port another tool +expects to be its own tunnel. `tests/test_database_port.py` fails if +anything moves it back. + +**Moving from 5433.** A container started before this change still +publishes 5433. Recreate it with `docker compose up -d db`: the data +survives, because the volume is named by the Compose project, not the +port. A local `.env` copied earlier needs its `5433` changed to `5434`. ## Once — first-time setup @@ -105,7 +113,7 @@ docker compose down -v # ALSO DELETES THE DATA | Symptom | Cause | Fix | |---|---|---| -| `connection refused` on 5433 | the database is not running | `docker compose up -d db` | +| `connection refused` on 5434 | the database is not running | `docker compose up -d db` | | `Cannot connect to the Docker daemon` | Docker Desktop is closed or signed out | open Docker Desktop, sign in, retry | | tests fail with `database "dess_test" does not exist` | first run on a new volume | `docker compose exec db createdb -U dess dess_test` | | `NoSuchTableError` in a test that uses the app database | `dess` was never migrated | `.venv/bin/alembic upgrade head` | diff --git a/alembic.ini b/alembic.ini index 9925870..6d85714 100644 --- a/alembic.ini +++ b/alembic.ini @@ -2,7 +2,7 @@ script_location = alembic prepend_sys_path = . version_path_separator = os -sqlalchemy.url = postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess +sqlalchemy.url = postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess [loggers] keys = root,sqlalchemy,alembic diff --git a/app/config.py b/app/config.py index ff38b90..f2c1100 100644 --- a/app/config.py +++ b/app/config.py @@ -26,9 +26,9 @@ class Settings(BaseSettings): enterprise_mode: bool = False # Database - database_url: str = "postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess" + database_url: str = "postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess" test_database_url: str = ( - "postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess_test" + "postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess_test" ) # Anthropic diff --git a/docker-compose.yml b/docker-compose.yml index f773bf6..7a4c2dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: POSTGRES_USER: dess POSTGRES_PASSWORD: dess_dev_only ports: - - "5433:5432" + - "5434:5432" volumes: - pgdata:/var/lib/postgresql/data healthcheck: diff --git a/tests/conftest.py b/tests/conftest.py index f264b99..9f13748 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ setup outside this file: docker compose exec db createdb -U dess dess_test - DATABASE_URL=postgresql+psycopg://dess:dess_dev_only@localhost:5433/dess_test \ + DATABASE_URL=postgresql+psycopg://dess:dess_dev_only@localhost:5434/dess_test \ .venv/bin/alembic upgrade head After that, the autouse `_apply_migrations` fixture keeps the schema diff --git a/tests/test_database_port.py b/tests/test_database_port.py index 91fa12b..28ee3c7 100644 --- a/tests/test_database_port.py +++ b/tests/test_database_port.py @@ -1,11 +1,17 @@ -"""DESS runs its own Postgres, on 5433 — never on the default 5432. +"""DESS runs its own Postgres, on 5434 — never on 5432 or 5433. Another Postgres on the same machine very often holds 5432. When it did, and DESS's own container was stopped, DESS's migrations connected to that other server and got as far as a permission error. A demo tree must never be one stopped container away from somebody else's database, so the port -is a contract, not a habit: every default names 5433, and no tracked file -points DESS at 5432. +is a contract, not a habit: every default names 5434, and no tracked file +points DESS at 5432 or 5433. + +5433 is taken too, the other way round: it is where a second Postgres, or +a Cloud SQL Auth Proxy tunnel, conventionally listens. While DESS sat on +5433, a wrapper on the same machine that checks only "is something +listening on 5433?" took DESS's container for its tunnel, skipped starting +the real one, and every run it made failed on the password against DESS. Needs no database — it reads the files. """ @@ -20,10 +26,10 @@ from app.config import Settings REPO = Path(__file__).resolve().parent.parent -PORT = 5433 +PORT = 5434 -# A host-side 5432: a URL's `host:5432`, or a Compose mapping `"5432:…"`. -DEFAULT_PORT = re.compile(r"(?:localhost|127\.0\.0\.1):5432\b|[\"']?\b5432:\d+") +# A host-side 5432 or 5433: a URL's `host:543x`, or a Compose mapping `"543x:…"`. +TAKEN_PORT = re.compile(r"(?:localhost|127\.0\.0\.1):543[23]\b|[\"']?\b543[23]:\d+") def _tracked_text_files() -> list[Path]: @@ -41,44 +47,46 @@ def _tracked_text_files() -> list[Path]: return files -def test_compose_is_its_own_project_on_5433() -> None: +def test_compose_is_its_own_project_on_5434() -> None: compose = yaml.safe_load((REPO / "docker-compose.yml").read_text()) assert compose.get("name") == "dess", "the Compose project must be named dess" ports = compose["services"]["db"]["ports"] assert ports == [f"{PORT}:5432"], f"db must publish host {PORT}, got {ports}" -def test_settings_defaults_name_5433() -> None: +def test_settings_defaults_name_5434() -> None: fields = Settings.model_fields for key in ("database_url", "test_database_url"): default = fields[key].default assert f"localhost:{PORT}/" in default, f"{key} default is {default}" -def test_alembic_ini_names_5433() -> None: +def test_alembic_ini_names_5434() -> None: ini = ConfigParser() ini.read(REPO / "alembic.ini") url = ini.get("alembic", "sqlalchemy.url") assert f"localhost:{PORT}/" in url, url -def test_env_example_names_5433() -> None: +def test_env_example_names_5434() -> None: text = (REPO / ".env.example").read_text() assert f"localhost:{PORT}/dess" in text -def test_no_tracked_file_points_dess_at_5432() -> None: +def test_no_tracked_file_points_dess_at_5432_or_5433() -> None: offenders = [] for path in _tracked_text_files(): if path == Path(__file__).resolve(): continue for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): - if DEFAULT_PORT.search(line): + if TAKEN_PORT.search(line): offenders.append(f"{path.relative_to(REPO)}:{i} {line.strip()}") - assert not offenders, "DESS must not use port 5432:\n" + "\n".join(offenders) + assert not offenders, "DESS must not use port 5432 or 5433:\n" + "\n".join( + offenders + ) -def test_ci_database_commands_name_5433() -> None: +def test_ci_database_commands_name_5434() -> None: """`psql -h localhost` with no `-p` means 5432 — implicitly. The number never appears, so the scan above cannot see it; this does. It is the gap that turned CI red on the day the port moved.""" @@ -90,6 +98,6 @@ def test_ci_database_commands_name_5433() -> None: and "-h localhost" in line and f"-p {PORT}" not in line ] - assert not offenders, "CI database commands must say -p 5433:\n" + "\n".join( + assert not offenders, f"CI database commands must say -p {PORT}:\n" + "\n".join( offenders )