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
42 changes: 42 additions & 0 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Backend CI

on:
pull_request:
branches: [main]
paths:
- "backend/**"

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
test:
runs-on: ubuntu-latest

defaults:
run:
working-directory: backend

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.25"
cache-dependency-path: backend/go.sum

- name: Install dependencies
run: go mod download

- name: Vet
run: go vet ./...

- name: Build
run: make build

- name: Run tests
run: make test

- name: Run integration tests
run: make itest
39 changes: 39 additions & 0 deletions .github/workflows/mobile-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Mobile CI

on:
pull_request:
branches: [main]
paths:
- "mobile/**"

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
ci:
runs-on: ubuntu-latest

defaults:
run:
working-directory: mobile

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: temurin

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4

- name: Make gradlew executable
run: chmod +x gradlew

- name: Lint
run: ./gradlew lint

- name: Run unit tests
run: ./gradlew test
44 changes: 44 additions & 0 deletions .github/workflows/web-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Web CI

on:
pull_request:
branches: [main]
paths:
- "web/**"

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
ci:
runs-on: ubuntu-latest

defaults:
run:
working-directory: web

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: web/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Lint
run: pnpm lint

- name: Unit tests
run: pnpm test

- name: Build
run: pnpm build
11 changes: 9 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ cd mobile && ./gradlew installDebug # build and install on connected devic

## Testing

**TDD is required.** Write failing tests first, then implement.

```bash
# Backend (Docker must be running)
cd backend && make test # unit + integration
cd backend && make itest # integration only

# Web
cd web && pnpm test # Vitest unit + component tests
cd web && pnpm test:watch # watch mode during development
cd web && pnpm lint
cd web && pnpm build

Expand All @@ -49,7 +53,9 @@ cd mobile && ./gradlew test # unit tests (no device needed)
cd mobile && ./gradlew connectedAndroidTest # instrumented tests (emulator/device required)
```

All backend DB tests use **Testcontainers** (real PostgreSQL). Never mock the database.
- Backend DB and cache tests use **Testcontainers** (real PostgreSQL, real Redis). Never mock the database.
- Web unit/component tests use **Vitest + @testing-library/react**. Server Component flows use Playwright (future).
- Mobile Composable tests use **Compose test rules** in instrumented tests.

---

Expand Down Expand Up @@ -93,6 +99,7 @@ All backend DB tests use **Testcontainers** (real PostgreSQL). Never mock the da
## PR instructions

- Branch from `main` — no direct pushes to `main`
- Run `make test` (backend), `pnpm lint && pnpm build` (web), and `./gradlew lint && ./gradlew test` (mobile) before opening a PR
- Before opening a PR, all tests must pass: `make test` (backend), `pnpm test && pnpm lint && pnpm build` (web), `./gradlew lint && ./gradlew test` (mobile)
- No new feature without tests — every route, component, utility, use case, and Composable must ship with tests
- One logical change per PR
- PR title: concise description of what changed, not implementation details
40 changes: 32 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ cd web && pnpm dev # web dev → :3000
cd backend && make test # all tests
cd backend && make itest # integration tests only (requires Docker)
cd web && pnpm lint && pnpm build
cd web && pnpm test # Vitest unit + component tests
cd web && pnpm test:watch # watch mode during TDD

cd mobile && ./gradlew assembleDebug # build Android APK
cd mobile && ./gradlew lint && ./gradlew test # mobile quality gate
Expand All @@ -39,9 +41,10 @@ make migrate-version # print current schema version
1. **Check docs first** — delegate to `docs` agent: find relevant topic docs, verify they match the current code
2. **Fix stale docs** — if docs diverge from code, update docs before implementing
3. **Migrate** — if the feature needs new or changed tables: `make migrate-create name=<slug>`, write the SQL, `make migrate-up`
4. **Implement** — delegate to `backend` or `web` agent, passing the relevant doc content as context
5. **Update docs** — delegate to `docs` agent: update `last_verified`, add new topics if introduced
6. **Quality gate** — run `/project:check` before declaring done
4. **Write tests first (TDD)** — write failing tests that describe the intended behaviour before writing any implementation code
5. **Implement** — delegate to `backend` or `web` agent, passing the relevant doc content as context; implementation is complete when all tests pass
6. **Update docs** — delegate to `docs` agent: update `last_verified`, add new topics if introduced
7. **Quality gate** — run `/project:check` before declaring done

Use `/project:implement` to run this workflow end-to-end.

Expand Down Expand Up @@ -162,11 +165,31 @@ cd backend && make swagger # regenerate docs/swagger/ after annotation changes
- Accept `modifier: Modifier = Modifier` as the last defaulted parameter in all public Composables.

## Testing — non-negotiable
- **Never mock the database.** Always use Testcontainers.
- Follow the `TestMain` + `mustStartPostgresContainer()` pattern in `internal/infrastructure/database/postgres/health_repository_test.go`.
- DB integration tests live in `internal/infrastructure/database/postgres/` (`package postgres`).
- Handler unit tests live in `internal/transport/handlers/` and may use mock use cases — that is not mocking the database.
- Docker must be running for integration tests.

**Test-Driven Development is the required approach.** Write failing tests first; make them pass; then refactor.

### Backend (Go)
- **Never mock the database.** Always use Testcontainers (real PostgreSQL, real Redis).
- Follow the `TestMain` + `mustStartPostgresContainer()` pattern from `internal/infrastructure/database/postgres/health_repository_test.go`.
- DB/cache integration tests live in their respective `infrastructure/` package.
- Handler unit tests live in `internal/transport/handlers/` and may use mock use case interfaces — that is not mocking the database.
- Usecase unit tests live in `internal/usecase/` and mock the repository interface (not the database).
- Docker must be running for any integration test.
- See `backend/docs/testing.md` for full patterns.

### Web (Next.js)
- **Unit/component tests:** Vitest + `@testing-library/react` with `jsdom` environment. Run with `pnpm test`.
- **Client Components:** test with `render()` + `screen` assertions.
- **Server Components and full-page flows:** test end-to-end with Playwright (future work; not yet set up).
- New utility functions in `lib/` **must** have Vitest unit tests.
- New Client Components **must** have rendering tests.
- See `web/docs/testing.md` for full setup and patterns.

### Mobile (Android)
- **Unit tests** (`src/test/`): JUnit 4, JVM only, no Android framework. For pure Kotlin logic and ViewModels.
- **Instrumented tests** (`src/androidTest/`): JUnit 4 + Compose test rules (`createComposeRule()`). For Composables and Activity-level tests.
- Every new public `@Composable` function must have a corresponding instrumented test.
- See `mobile/docs/testing.md` for full patterns.

## Hard rules (hooks enforce some of these)
- No secrets in committed files.
Expand All @@ -175,3 +198,4 @@ cd backend && make swagger # regenerate docs/swagger/ after annotation changes
- No `"use client"` without a concrete browser requirement.
- No database mocks in tests.
- No `any` in TypeScript.
- **No new feature without tests.** Every new route, component, utility function, use case, and Composable must ship with tests.
24 changes: 18 additions & 6 deletions backend/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,28 @@ Read the relevant doc before implementing. These are kept in sync with the code

## Testing instructions

All database tests run against a **real PostgreSQL instance** via Testcontainers. Docker must be running.
**TDD is required.** Write failing tests first, then implement.

All database and cache tests run against **real instances** via Testcontainers. Docker must be running.

```bash
make test # unit + integration
make itest # integration only
go test ./internal/database -v -run TestHealth # single test
make test # unit + integration (requires Docker)
make itest # integration only
go test ./internal/usecase/... -v # usecase unit tests (no Docker needed)
go test ./internal/transport/handlers/... -v # handler unit tests (no Docker needed)
go test ./internal/infrastructure/... -v # DB + cache integration tests (requires Docker)
```

Adding a new test: follow the `TestMain` + `mustStartPostgresContainer()` pattern in `internal/database/database_test.go`.
Use table-driven tests for multiple input cases.
### Test placement
| Layer | Package | What is mocked |
|---|---|---|
| `usecase/` | `package usecase` | Repository interfaces (not the DB) |
| `transport/handlers/` | `package handlers` | Use case interfaces |
| `infrastructure/database/postgres/` | `package postgres` | Nothing — real DB via Testcontainers |
| `infrastructure/cache/redis/` | `package redis` | Nothing — real Redis via Testcontainers |
| `bootstrap/` | `package bootstrap` | Pinger interface; env vars via `t.Setenv` |

See `backend/docs/testing.md` for full patterns including `TestMain`, table-driven tests, and mock examples.

---

Expand Down
2 changes: 1 addition & 1 deletion backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ test:
# Integration tests (requires Docker)
itest:
@echo "Running integration tests..."
@go test ./internal/infrastructure/database/postgres/... -v
@go test ./internal/infrastructure/... -v

# Clean the binary
clean:
Expand Down
2 changes: 1 addition & 1 deletion backend/docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ The `docs` agent reads this index first to locate the right file before diving i
| Database connection & query patterns | [database.md](database.md) | `internal/infrastructure/database/postgres/db.go`, `internal/infrastructure/database/postgres/health_repository.go`, `internal/domain/health.go`, `internal/usecase/health_usecase.go` |
| Schema migrations (goose) | [migrations.md](migrations.md) | `cmd/migrate/main.go`, `internal/infrastructure/database/migrations/`, `Makefile` |
| HTTP routing & handler patterns | [routing.md](routing.md) | `internal/handler/handler.go`, `internal/handler/routes.go`, `internal/handler/hello_handler.go`, `internal/handler/health_handler.go`, `internal/server/server.go` |
| Integration testing with Testcontainers | [testing.md](testing.md) | `internal/infrastructure/database/postgres/health_repository_test.go`, `internal/transport/handlers/hello_handler_test.go` |
| Testing patterns (unit, handler, Redis, bootstrap) | [testing.md](testing.md) | `internal/infrastructure/database/postgres/health_repository_test.go`, `internal/transport/handlers/hello_handler_test.go`, `internal/transport/handlers/health_handler_test.go`, `internal/transport/middleware/logger_test.go`, `internal/usecase/health_usecase_test.go`, `internal/infrastructure/cache/redis/cache_test.go`, `internal/bootstrap/bootstrap_test.go` |
| Error handling conventions | [error-handling.md](error-handling.md) | `internal/repository/postgres/health_repository.go`, `internal/handler/health_handler.go`, `cmd/api/main.go` |
| Environment variables | [environment.md](environment.md) | `.env`, `internal/bootstrap/bootstrap.go`, `internal/repository/postgres/db.go` |
Loading
Loading