From 2c28c4cba271a4671e283c735f9ec8cc2ff65ce7 Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Tue, 24 Mar 2026 14:27:35 +0800 Subject: [PATCH 1/2] refactor(dev): share postgres across main and worktrees --- .env.example | 1 - CLAUDE.md | 13 +- LOCAL_DEVELOPMENT.md | 471 ++++++++++++++++++++++++ Makefile | 61 +-- README.md | 40 +- docker-compose.yml | 6 +- e2e/comments.spec.ts | 19 +- e2e/fixtures.ts | 36 +- e2e/helpers.ts | 26 +- e2e/issues.spec.ts | 2 +- scripts/check.sh | 29 +- scripts/ensure-postgres.sh | 42 +++ scripts/init-worktree-env.sh | 13 +- server/cmd/seed/main.go | 221 ----------- server/cmd/server/integration_test.go | 111 +++++- server/internal/handler/handler_test.go | 107 ++++-- 16 files changed, 839 insertions(+), 359 deletions(-) create mode 100644 LOCAL_DEVELOPMENT.md create mode 100644 scripts/ensure-postgres.sh delete mode 100644 server/cmd/seed/main.go diff --git a/.env.example b/.env.example index 3c410d41..312ec87a 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,4 @@ # Database -COMPOSE_PROJECT_NAME=super_multica POSTGRES_DB=multica POSTGRES_USER=multica POSTGRES_PASSWORD=multica diff --git a/CLAUDE.md b/CLAUDE.md index 57c1342a..10776878 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,10 +22,10 @@ Multica is an AI-native task management platform — like Linear, but with AI ag ```bash # One-click setup & run -make setup # First-time: install deps, start DB, migrate -make seed # Optional: load example data +make setup # First-time: ensure shared DB, create app DB, migrate make start # Start backend + frontend together -make stop # Stop everything +make stop # Stop app processes for the current checkout +make db-down # Stop the shared PostgreSQL container # Frontend pnpm install @@ -41,11 +41,10 @@ make test # Go tests make sqlc # Regenerate sqlc code make migrate-up # Run database migrations make migrate-down # Rollback migrations -make seed # Seed example data # Infrastructure -docker compose up -d # Start PostgreSQL -docker compose down # Stop PostgreSQL +make db-up # Start shared PostgreSQL +make db-down # Stop shared PostgreSQL ``` ## 4. Coding Rules @@ -69,7 +68,7 @@ docker compose down # Stop PostgreSQL ## 6. Testing Rules - **TypeScript**: Vitest. Mock external/third-party dependencies only. -- **Go**: Standard `go test`. Use testcontainers or test database for DB tests. +- **Go**: Standard `go test`. Tests should create their own fixture data in a test database. ## 7. Commit Rules diff --git a/LOCAL_DEVELOPMENT.md b/LOCAL_DEVELOPMENT.md new file mode 100644 index 00000000..b93af57b --- /dev/null +++ b/LOCAL_DEVELOPMENT.md @@ -0,0 +1,471 @@ +# Local Development Guide + +This guide documents the intended local development workflow for Multica. + +It covers: + +- first-time setup +- day-to-day development in the main checkout +- isolated worktree development +- the shared PostgreSQL model +- testing and verification +- troubleshooting and destructive reset options + +## Development Model + +Local development uses one shared PostgreSQL container and one database per checkout. + +- the main checkout usually uses `.env` and `POSTGRES_DB=multica` +- each Git worktree uses its own `.env.worktree` +- every checkout connects to the same PostgreSQL host: `localhost:5432` +- isolation happens at the database level, not by starting a separate Docker Compose project +- backend and frontend ports are still unique per worktree + +This keeps Docker simple while still isolating schema and data. + +## Prerequisites + +- Node.js `v20+` +- `pnpm` `v10.28+` +- Go `v1.26+` +- Docker + +## Important Rules + +- The main checkout should use `.env`. +- A worktree should use `.env.worktree`. +- Do not copy `.env` into a worktree directory. + +Why: + +- the current command flow prefers `.env` over `.env.worktree` +- if a worktree contains `.env`, it can accidentally point back to the main database + +## Environment Files + +### Main Checkout + +Create `.env` once: + +```bash +cp .env.example .env +``` + +By default, `.env` points to: + +```bash +POSTGRES_DB=multica +POSTGRES_PORT=5432 +DATABASE_URL=postgres://multica:multica@localhost:5432/multica?sslmode=disable +PORT=8080 +FRONTEND_PORT=3000 +``` + +### Worktree + +Generate `.env.worktree` from inside the worktree: + +```bash +make worktree-env +``` + +That generates values like: + +```bash +POSTGRES_DB=multica_super_multica_702 +POSTGRES_PORT=5432 +PORT=18782 +FRONTEND_PORT=13702 +DATABASE_URL=postgres://multica:multica@localhost:5432/multica_super_multica_702?sslmode=disable +``` + +Notes: + +- `POSTGRES_DB` is unique per worktree +- `POSTGRES_PORT` stays fixed at `5432` +- backend and frontend ports are derived from the worktree path hash +- `make worktree-env` refuses to overwrite an existing `.env.worktree` + +To regenerate a worktree env file: + +```bash +FORCE=1 make worktree-env +``` + +## First-Time Setup + +### Main Checkout + +From the main checkout: + +```bash +cp .env.example .env +make setup-main +``` + +What `make setup-main` does: + +- installs JavaScript dependencies with `pnpm install` +- ensures the shared PostgreSQL container is running +- creates the application database if it does not exist +- runs all migrations against that database + +Start the app: + +```bash +make start-main +``` + +Stop the app processes: + +```bash +make stop-main +``` + +This does not stop PostgreSQL. + +### Worktree + +From the worktree directory: + +```bash +make worktree-env +make setup-worktree +``` + +What `make setup-worktree` does: + +- uses `.env.worktree` +- ensures the shared PostgreSQL container is running +- creates the worktree database if it does not exist +- runs migrations against the worktree database + +Start the worktree app: + +```bash +make start-worktree +``` + +Stop the worktree app processes: + +```bash +make stop-worktree +``` + +## Recommended Daily Workflow + +### Main Checkout + +Use the main checkout when you want a stable local environment for `main`. + +```bash +make start-main +make stop-main +make check-main +``` + +### Feature Worktree + +Use a worktree when you want isolated data and separate app ports. + +```bash +git worktree add ../super-multica-feature -b feat/my-change main +cd ../super-multica-feature +make worktree-env +make setup-worktree +make start-worktree +``` + +After that, day-to-day commands are: + +```bash +make start-worktree +make stop-worktree +make check-worktree +``` + +## Running Main and Worktree at the Same Time + +This is a first-class workflow. + +Example: + +- main checkout + - database: `multica` + - backend: `8080` + - frontend: `3000` +- worktree checkout + - database: `multica_super_multica_702` + - backend: generated worktree port such as `18782` + - frontend: generated worktree port such as `13702` + +Both checkouts use: + +- the same PostgreSQL container +- the same PostgreSQL port: `5432` + +But they do not share application data, because each uses a different database. + +## Command Reference + +### Shared Infrastructure + +Start the shared PostgreSQL container: + +```bash +make db-up +``` + +Stop the shared PostgreSQL container: + +```bash +make db-down +``` + +Important: + +- `make db-down` stops the container but keeps the Docker volume +- your local databases are preserved + +### App Lifecycle + +Main checkout: + +```bash +make setup-main +make start-main +make stop-main +make check-main +``` + +Worktree: + +```bash +make worktree-env +make setup-worktree +make start-worktree +make stop-worktree +make check-worktree +``` + +Generic targets for the current checkout: + +```bash +make setup +make start +make stop +make check +make dev +make test +make migrate-up +make migrate-down +``` + +These generic targets require a valid env file in the current directory. + +## How Database Creation Works + +Database creation is automatic. + +The following commands all ensure the target database exists before they continue: + +- `make setup` +- `make start` +- `make dev` +- `make test` +- `make migrate-up` +- `make migrate-down` +- `make check` + +That logic lives in `scripts/ensure-postgres.sh`. + +## Testing + +Run all local checks: + +```bash +make check-main +``` + +Or from a worktree: + +```bash +make check-worktree +``` + +This runs: + +1. TypeScript typecheck +2. TypeScript unit tests +3. Go tests +4. Playwright E2E tests + +Notes: + +- Go tests create their own fixture data +- E2E tests create their own workspace and issue fixtures +- the check flow starts backend/frontend only if they are not already running + +## Local Codex Daemon + +Run the local daemon: + +```bash +make daemon +``` + +Normal flow: + +1. start the daemon +2. open the pairing link it prints +3. choose the workspace in the browser +4. let the daemon register its local runtime + +Debug shortcut: + +- you can set `MULTICA_WORKSPACE_ID` in your env file +- this skips normal pairing +- treat it as a local shortcut, not the default workflow + +## Troubleshooting + +### Missing Env File + +If you see: + +```text +Missing env file: .env +``` + +or: + +```text +Missing env file: .env.worktree +``` + +then create the expected env file first. + +Main checkout: + +```bash +cp .env.example .env +``` + +Worktree: + +```bash +make worktree-env +``` + +### Check Which Database a Checkout Uses + +Inspect the env file: + +```bash +cat .env +cat .env.worktree +``` + +Look for: + +- `POSTGRES_DB` +- `DATABASE_URL` +- `PORT` +- `FRONTEND_PORT` + +### List All Local Databases in Shared PostgreSQL + +```bash +docker compose exec -T postgres psql -U multica -d postgres -At -c "select datname from pg_database order by datname;" +``` + +### Worktree Is Accidentally Using the Main Database + +Check whether the worktree contains `.env`. + +It should not. + +The safe worktree setup is: + +```bash +make worktree-env +make setup-worktree +make start-worktree +``` + +### App Stops but PostgreSQL Keeps Running + +That is expected. + +- `make stop` +- `make stop-main` +- `make stop-worktree` + +only stop backend/frontend processes. + +To stop the shared PostgreSQL container: + +```bash +make db-down +``` + +## Destructive Reset + +If you want to stop PostgreSQL and keep your local databases: + +```bash +make db-down +``` + +If you want to wipe all local PostgreSQL data for this repo: + +```bash +docker compose down -v +``` + +Warning: + +- this deletes the shared Docker volume +- this deletes the main database and every worktree database in that volume +- after that you must run `make setup-main` or `make setup-worktree` again + +## Typical Flows + +### Stable Main Environment + +```bash +cp .env.example .env +make setup-main +make start-main +``` + +### Feature Worktree + +```bash +git worktree add ../super-multica-feature -b feat/my-change main +cd ../super-multica-feature +make worktree-env +make setup-worktree +make start-worktree +``` + +### Return to a Previously Configured Worktree + +```bash +cd ../super-multica-feature +make start-worktree +``` + +### Validate Before Pushing + +Main checkout: + +```bash +make check-main +``` + +Worktree: + +```bash +make check-worktree +``` diff --git a/Makefile b/Makefile index 9fe220c2..f9deb94d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: dev daemon build test migrate-up migrate-down sqlc seed clean setup start stop check worktree-env setup-main start-main stop-main check-main setup-worktree start-worktree stop-worktree check-worktree +.PHONY: dev daemon build test migrate-up migrate-down sqlc clean setup start stop check worktree-env setup-main start-main stop-main check-main setup-worktree start-worktree stop-worktree check-worktree db-up db-down MAIN_ENV_FILE ?= .env WORKTREE_ENV_FILE ?= .env.worktree @@ -20,47 +20,40 @@ NEXT_PUBLIC_API_URL ?= http://localhost:$(PORT) NEXT_PUBLIC_WS_URL ?= ws://localhost:$(PORT)/ws GOOGLE_REDIRECT_URI ?= $(FRONTEND_ORIGIN)/auth/callback MULTICA_SERVER_URL ?= ws://localhost:$(PORT)/ws -COMPOSE_PROJECT_NAME ?= super_multica export -COMPOSE := docker compose --env-file $(ENV_FILE) +COMPOSE := docker compose + +define REQUIRE_ENV + @if [ ! -f "$(ENV_FILE)" ]; then \ + echo "Missing env file: $(ENV_FILE)"; \ + echo "Create .env from .env.example, or run 'make worktree-env' and use .env.worktree."; \ + exit 1; \ + fi +endef # ---------- One-click commands ---------- # First-time setup: install deps, start DB, run migrations setup: + $(REQUIRE_ENV) @echo "==> Using env file: $(ENV_FILE)" @echo "==> Installing dependencies..." pnpm install - @echo "==> Starting PostgreSQL..." - @if pg_isready -h localhost -p $(POSTGRES_PORT) -U $(POSTGRES_USER) -d $(POSTGRES_DB) > /dev/null 2>&1; then \ - echo " PostgreSQL already running, skipping docker compose up."; \ - else \ - $(COMPOSE) up -d; \ - echo "==> Waiting for PostgreSQL to be ready..."; \ - until $(COMPOSE) exec -T postgres pg_isready -U $(POSTGRES_USER) -d $(POSTGRES_DB) > /dev/null 2>&1; do \ - sleep 1; \ - done; \ - fi + @bash scripts/ensure-postgres.sh "$(ENV_FILE)" @echo "==> Running migrations..." cd server && go run ./cmd/migrate up @echo "" - @echo "✓ Setup complete! Run 'make seed' if you want example data, then 'make start' to launch the app." + @echo "✓ Setup complete! Run 'make start' to launch the app." # Start all services (backend + frontend) start: + $(REQUIRE_ENV) @echo "Using env file: $(ENV_FILE)" @echo "Backend: http://localhost:$(PORT)" @echo "Frontend: http://localhost:$(FRONTEND_PORT)" - @if pg_isready -h localhost -p $(POSTGRES_PORT) -U $(POSTGRES_USER) -d $(POSTGRES_DB) > /dev/null 2>&1; then \ - echo "PostgreSQL already running, skipping docker compose up."; \ - else \ - $(COMPOSE) up -d; \ - until $(COMPOSE) exec -T postgres pg_isready -U $(POSTGRES_USER) -d $(POSTGRES_DB) > /dev/null 2>&1; do \ - sleep 1; \ - done; \ - fi + @bash scripts/ensure-postgres.sh "$(ENV_FILE)" @echo "Starting backend and frontend..." @trap 'kill 0' EXIT; \ (cd server && go run ./cmd/server) & \ @@ -69,15 +62,22 @@ start: # Stop all services stop: + $(REQUIRE_ENV) @echo "Stopping services..." @-lsof -ti:$(PORT) | xargs kill -9 2>/dev/null @-lsof -ti:$(FRONTEND_PORT) | xargs kill -9 2>/dev/null - $(COMPOSE) down - @echo "✓ All services stopped." + @echo "✓ App processes stopped. Shared PostgreSQL is still running on localhost:5432." # Full verification: typecheck + unit tests + Go tests + E2E check: - @bash scripts/check.sh + $(REQUIRE_ENV) + @ENV_FILE="$(ENV_FILE)" bash scripts/check.sh + +db-up: + @$(COMPOSE) up -d postgres + +db-down: + @$(COMPOSE) down worktree-env: @bash scripts/init-worktree-env.sh .env.worktree @@ -110,6 +110,8 @@ check-worktree: # Go server dev: + $(REQUIRE_ENV) + @bash scripts/ensure-postgres.sh "$(ENV_FILE)" cd server && go run ./cmd/server daemon: @@ -120,21 +122,24 @@ build: cd server && go build -o bin/daemon ./cmd/daemon test: + $(REQUIRE_ENV) + @bash scripts/ensure-postgres.sh "$(ENV_FILE)" cd server && go test ./... # Database migrate-up: + $(REQUIRE_ENV) + @bash scripts/ensure-postgres.sh "$(ENV_FILE)" cd server && go run ./cmd/migrate up migrate-down: + $(REQUIRE_ENV) + @bash scripts/ensure-postgres.sh "$(ENV_FILE)" cd server && go run ./cmd/migrate down sqlc: cd server && sqlc generate -seed: - cd server && go run ./cmd/seed - # Cleanup clean: rm -rf server/bin server/tmp diff --git a/README.md b/README.md index e821dbf8..eec73dcf 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ AI-native task management platform — like Linear, but with AI agents as first-class citizens. +For the full local development workflow, see [Local Development Guide](LOCAL_DEVELOPMENT.md). + ## Prerequisites - [Node.js](https://nodejs.org/) (v20+) @@ -18,19 +20,16 @@ pnpm install # 2. Copy environment variables for the shared main environment cp .env.example .env -# 3. One-time setup: start DB and run migrations +# 3. One-time setup: ensure shared PostgreSQL, create the app DB, run migrations make setup -# 4. Optional: load example data -make seed - -# 5. Start backend + frontend +# 4. Start backend + frontend make start ``` Open your configured `FRONTEND_ORIGIN` in the browser. By default that is [http://localhost:3000](http://localhost:3000). -Default behavior now prefers the shared main environment in `.env`. If you want an isolated environment for a Git worktree, generate `.env.worktree` and use the explicit worktree targets: +Main checkout uses `.env`. A Git worktree should generate its own `.env.worktree` and use the explicit worktree targets: ```bash make worktree-env @@ -38,13 +37,19 @@ make setup-worktree make start-worktree ``` -This lets you keep `.env` connected to your main database while using `.env.worktree` only for isolated feature testing. +Every checkout shares the same PostgreSQL container on `localhost:5432`. Isolation now happens at the database level: + +- `.env` typically uses `POSTGRES_DB=multica` +- each `.env.worktree` gets its own `POSTGRES_DB`, such as `multica_super_multica_702` +- backend/frontend ports still stay unique per worktree + +That keeps one Docker container and one volume, while still isolating schema and data per worktree. ## Project Structure ``` ├── server/ # Go backend (Chi + sqlc + gorilla/websocket) -│ ├── cmd/ # server, daemon, migrate, seed +│ ├── cmd/ # server, daemon, migrate │ ├── internal/ # Core business logic │ ├── migrations/ # SQL migrations │ └── sqlc.yaml # sqlc config @@ -87,11 +92,10 @@ This lets you keep `.env` connected to your main database while using `.env.work | Command | Description | |---------|-------------| -| `docker compose up -d` | Start PostgreSQL | -| `docker compose down` | Stop PostgreSQL | -| `make migrate-up` | Run database migrations | -| `make migrate-down` | Rollback database migrations | -| `make seed` | Seed example data | +| `make db-up` | Start the shared PostgreSQL container | +| `make db-down` | Stop the shared PostgreSQL container | +| `make migrate-up` | Ensure the current DB exists, then run migrations | +| `make migrate-down` | Rollback database migrations for the current DB | | `make worktree-env` | Generate an isolated `.env.worktree` for the current worktree | | `make setup-main` / `make start-main` | Force use of the shared main `.env` | | `make setup-worktree` / `make start-worktree` | Force use of isolated `.env.worktree` | @@ -101,8 +105,8 @@ This lets you keep `.env` connected to your main database while using `.env.work See [`.env.example`](.env.example) for all available variables: - `DATABASE_URL` — PostgreSQL connection string -- `COMPOSE_PROJECT_NAME` — Docker Compose project name -- `POSTGRES_DB` / `POSTGRES_PORT` — Per-worktree PostgreSQL database and host port +- `POSTGRES_DB` — Database name for the current checkout or worktree +- `POSTGRES_PORT` — Shared PostgreSQL host port (fixed to `5432`) - `PORT` — Backend server port (default: 8080) - `FRONTEND_PORT` / `FRONTEND_ORIGIN` — Frontend port and browser origin - `JWT_SECRET` — JWT signing secret @@ -130,3 +134,9 @@ The local daemon currently supports one local runtime type: `codex`. 8. The daemon claims the task, runs `codex exec`, and reports the final comment back to the issue. For local development you can still set `MULTICA_WORKSPACE_ID` directly to skip pairing, but that should be treated as a debug shortcut rather than the normal flow. + +## Local Development Notes + +- `make setup`, `make start`, `make dev`, and `make test` now require an env file. They fail fast if `.env` or `.env.worktree` is missing. +- `make stop` only stops the backend/frontend processes for the current checkout. It does not stop the shared PostgreSQL container. +- Use `make db-down` only when you explicitly want to shut down the shared local PostgreSQL instance for every checkout. diff --git a/docker-compose.yml b/docker-compose.yml index 21b005f3..0c4b21b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,14 @@ +name: super_multica + services: postgres: image: pgvector/pgvector:pg17 environment: - POSTGRES_DB: ${POSTGRES_DB:-multica} + POSTGRES_DB: multica POSTGRES_USER: ${POSTGRES_USER:-multica} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-multica} ports: - - "${POSTGRES_PORT:-5432}:5432" + - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data diff --git a/e2e/comments.spec.ts b/e2e/comments.spec.ts index d33107fd..68f55251 100644 --- a/e2e/comments.spec.ts +++ b/e2e/comments.spec.ts @@ -1,10 +1,21 @@ import { test, expect } from "@playwright/test"; -import { loginAsDefault } from "./helpers"; +import { createTestApi, loginAsDefault } from "./helpers"; +import type { TestApiClient } from "./fixtures"; test.describe("Comments", () => { - test("can add a comment on an issue", async ({ page }) => { - await loginAsDefault(page); + let api: TestApiClient; + test.beforeEach(async ({ page }) => { + api = await createTestApi(); + await api.createIssue("E2E Comment Test " + Date.now()); + await loginAsDefault(page); + }); + + test.afterEach(async () => { + await api.cleanup(); + }); + + test("can add a comment on an issue", async ({ page }) => { // Wait for issues to load and click first one const issueLink = page.locator('a[href^="/issues/"]').first(); await expect(issueLink).toBeVisible({ timeout: 5000 }); @@ -31,8 +42,6 @@ test.describe("Comments", () => { }); test("comment submit button is disabled when empty", async ({ page }) => { - await loginAsDefault(page); - const issueLink = page.locator('a[href^="/issues/"]').first(); await expect(issueLink).toBeVisible({ timeout: 5000 }); await issueLink.click(); diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts index 4c3b2a09..4e153ccf 100644 --- a/e2e/fixtures.ts +++ b/e2e/fixtures.ts @@ -7,6 +7,12 @@ const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? `http://localhost:${process.env.PORT ?? "8080"}`; +interface TestWorkspace { + id: string; + name: string; + slug: string; +} + export class TestApiClient { private token: string | null = null; private workspaceId: string | null = null; @@ -23,7 +29,7 @@ export class TestApiClient { return data; } - async getWorkspaces() { + async getWorkspaces(): Promise { const res = await this.authedFetch("/api/workspaces"); return res.json(); } @@ -32,6 +38,34 @@ export class TestApiClient { this.workspaceId = id; } + async ensureWorkspace(name = "E2E Workspace", slug = "e2e-workspace") { + const workspaces = await this.getWorkspaces(); + const workspace = workspaces.find((item) => item.slug === slug) ?? workspaces[0]; + if (workspace) { + this.workspaceId = workspace.id; + return workspace; + } + + const res = await this.authedFetch("/api/workspaces", { + method: "POST", + body: JSON.stringify({ name, slug }), + }); + if (res.ok) { + const created = (await res.json()) as TestWorkspace; + this.workspaceId = created.id; + return created; + } + + const refreshed = await this.getWorkspaces(); + const created = refreshed.find((item) => item.slug === slug) ?? refreshed[0]; + if (created) { + this.workspaceId = created.id; + return created; + } + + throw new Error(`Failed to ensure workspace ${slug}: ${res.status} ${res.statusText}`); + } + async createIssue(title: string, opts?: Record) { const res = await this.authedFetch("/api/issues", { method: "POST", diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 150ddd0e..3cc93d93 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -1,31 +1,33 @@ import { type Page } from "@playwright/test"; import { TestApiClient } from "./fixtures"; +const DEFAULT_E2E_NAME = "E2E User"; +const DEFAULT_E2E_EMAIL = "e2e@multica.ai"; +const DEFAULT_E2E_WORKSPACE = "e2e-workspace"; + /** - * Login as the seeded user (has workspace and issues). + * Log in as the default E2E user and ensure the workspace exists first. */ export async function loginAsDefault(page: Page) { + const api = new TestApiClient(); + await api.login(DEFAULT_E2E_EMAIL, DEFAULT_E2E_NAME); + await api.ensureWorkspace("E2E Workspace", DEFAULT_E2E_WORKSPACE); + await page.goto("/login"); - await page.fill('input[placeholder="Name"]', "Jiayuan Zhang"); - await page.fill('input[placeholder="Email"]', "jiayuan@multica.ai"); + await page.fill('input[placeholder="Name"]', DEFAULT_E2E_NAME); + await page.fill('input[placeholder="Email"]', DEFAULT_E2E_EMAIL); await page.click('button[type="submit"]'); await page.waitForURL("**/issues", { timeout: 10000 }); } /** - * Open the workspace switcher dropdown menu. - */ -/** - * Create a TestApiClient logged in as the default seeded user. + * Create a TestApiClient logged in as the default E2E user. * Call api.cleanup() in afterEach to remove test data created during the test. */ export async function createTestApi(): Promise { const api = new TestApiClient(); - await api.login("jiayuan@multica.ai", "Jiayuan Zhang"); - const workspaces = await api.getWorkspaces(); - if (workspaces.length > 0) { - api.setWorkspaceId(workspaces[0].id); - } + await api.login(DEFAULT_E2E_EMAIL, DEFAULT_E2E_NAME); + await api.ensureWorkspace("E2E Workspace", DEFAULT_E2E_WORKSPACE); return api; } diff --git a/e2e/issues.spec.ts b/e2e/issues.spec.ts index 1feeb7c0..2f9403c4 100644 --- a/e2e/issues.spec.ts +++ b/e2e/issues.spec.ts @@ -49,7 +49,7 @@ test.describe("Issues", () => { }); test("can navigate to issue detail page", async ({ page }) => { - // Create a known issue via API so we don't depend on seed data + // Create a known issue via API so the test controls its own fixture const issue = await api.createIssue("E2E Detail Test " + Date.now()); // Reload to see the new issue diff --git a/scripts/check.sh b/scripts/check.sh index ef4af067..3c11e026 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -6,14 +6,18 @@ set -euo pipefail # Usage: bash scripts/check.sh # ========================================================================== -ENV_FILE="${ENV_FILE:-$(if [ -f .env ]; then echo .env; elif [ -f .env.worktree ]; then echo .env.worktree; else echo .env; fi)}" -if [ -f "$ENV_FILE" ]; then - set -a - # shellcheck disable=SC1090 - . "$ENV_FILE" - set +a +ENV_FILE="${ENV_FILE:-.env}" +if [ ! -f "$ENV_FILE" ]; then + echo "Missing env file: $ENV_FILE" + echo "Create .env from .env.example, or run 'make worktree-env' and use .env.worktree." + exit 1 fi +set -a +# shellcheck disable=SC1090 +. "$ENV_FILE" +set +a + POSTGRES_DB="${POSTGRES_DB:-multica}" POSTGRES_USER="${POSTGRES_USER:-multica}" POSTGRES_PORT="${POSTGRES_PORT:-5432}" @@ -22,8 +26,6 @@ FRONTEND_PORT="${FRONTEND_PORT:-3000}" PLAYWRIGHT_BASE_URL="${PLAYWRIGHT_BASE_URL:-http://localhost:${FRONTEND_PORT}}" export PLAYWRIGHT_BASE_URL -COMPOSE_CMD=(docker compose --env-file "$ENV_FILE") - BACKEND_PID="" FRONTEND_PID="" STARTED_BACKEND=false @@ -77,16 +79,7 @@ wait_for_port() { # -------------------------------------------------------------------------- echo "==> Using env file: $ENV_FILE" echo "==> Checking PostgreSQL..." -if pg_isready -h localhost -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" > /dev/null 2>&1; then - echo " Already running." -else - echo " Starting via docker compose..." - "${COMPOSE_CMD[@]}" up -d - until "${COMPOSE_CMD[@]}" exec -T postgres pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" > /dev/null 2>&1; do - sleep 1 - done - echo " PostgreSQL ready." -fi +bash scripts/ensure-postgres.sh "$ENV_FILE" # -------------------------------------------------------------------------- # Step 1: TypeScript typecheck diff --git a/scripts/ensure-postgres.sh b/scripts/ensure-postgres.sh new file mode 100644 index 00000000..78d7345b --- /dev/null +++ b/scripts/ensure-postgres.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +ENV_FILE="${1:-.env}" + +if [ ! -f "$ENV_FILE" ]; then + echo "Missing env file: $ENV_FILE" + echo "Create .env from .env.example, or run 'make worktree-env' and use .env.worktree." + exit 1 +fi + +set -a +# shellcheck disable=SC1090 +. "$ENV_FILE" +set +a + +POSTGRES_DB="${POSTGRES_DB:-multica}" +POSTGRES_USER="${POSTGRES_USER:-multica}" +POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-multica}" + +export PGPASSWORD="$POSTGRES_PASSWORD" + +echo "==> Ensuring shared PostgreSQL container is running on localhost:5432..." +docker compose up -d postgres + +echo "==> Waiting for PostgreSQL to be ready..." +until docker compose exec -T postgres pg_isready -U "$POSTGRES_USER" -d postgres > /dev/null 2>&1; do + sleep 1 +done + +echo "==> Ensuring database '$POSTGRES_DB' exists..." +db_exists="$(docker compose exec -T postgres \ + psql -U "$POSTGRES_USER" -d postgres -Atqc "SELECT 1 FROM pg_database WHERE datname = '$POSTGRES_DB'")" + +if [ "$db_exists" != "1" ]; then + docker compose exec -T postgres \ + psql -U "$POSTGRES_USER" -d postgres -v ON_ERROR_STOP=1 \ + -c "CREATE DATABASE \"$POSTGRES_DB\"" \ + > /dev/null +fi + +echo "✓ PostgreSQL ready. Application database: $POSTGRES_DB" diff --git a/scripts/init-worktree-env.sh b/scripts/init-worktree-env.sh index cf123bef..38b4db73 100644 --- a/scripts/init-worktree-env.sh +++ b/scripts/init-worktree-env.sh @@ -17,15 +17,13 @@ fi hash_value="$(printf '%s' "$PWD" | cksum | awk '{print $1}')" offset=$((hash_value % 1000)) -postgres_db="multica_${slug}" -postgres_port=$((15432 + offset)) +postgres_db="multica_${slug}_${offset}" +postgres_port=5432 backend_port=$((18080 + offset)) frontend_port=$((13000 + offset)) frontend_origin="http://localhost:${frontend_port}" -compose_project_name="multica_${slug}_${offset}" cat > "$ENV_FILE" < Date: Tue, 24 Mar 2026 14:29:56 +0800 Subject: [PATCH 2/2] fix(ci): provision postgres for backend tests --- .github/workflows/ci.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 033c78a1..afe63e0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,22 @@ jobs: backend: runs-on: ubuntu-latest + services: + postgres: + image: pgvector/pgvector:pg17 + env: + POSTGRES_DB: multica + POSTGRES_USER: multica + POSTGRES_PASSWORD: multica + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U multica -d multica" + --health-interval 5s + --health-timeout 5s + --health-retries 20 + env: + DATABASE_URL: postgres://multica:multica@localhost:5432/multica?sslmode=disable steps: - name: Checkout uses: actions/checkout@v6 @@ -56,5 +72,8 @@ jobs: - name: Build run: cd server && go build ./... + - name: Run migrations + run: cd server && go run ./cmd/migrate up + - name: Test run: cd server && go test ./...