Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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-...
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
18 changes: 13 additions & 5 deletions RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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` |
Expand Down
2 changes: 1 addition & 1 deletion alembic.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 23 additions & 15 deletions tests/test_database_port.py
Original file line number Diff line number Diff line change
@@ -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.
"""
Expand All @@ -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]:
Expand All @@ -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."""
Expand All @@ -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
)
Loading