76e8de9484
Set up YesChef.Api.UnitTests and YesChef.Api.IntegrationTests projects running on TUnit + Microsoft.Testing.Platform. Integration tests use a single Postgres 17 Testcontainer per session and clone a migrated template database per test (`CREATE DATABASE … TEMPLATE …`) so tests remain fully isolated and run in parallel without replaying migrations each time. Test-author DX is built around fluent entity builders, a TestDataFactory for common scenarios, and a two-level base hierarchy (IntegrationTest / AuthenticatedIntegrationTest) whose `[Before(Test)]` hooks stand up the per-test database, app factory, default user, and authenticated HttpClient — leaving each test body focused on the action under test. Adds src/backend/global.json to opt `dotnet test` into MTP mode on the .NET 10 SDK, and updates CLAUDE.md with how to run the tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
93 lines
6.2 KiB
Markdown
93 lines
6.2 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Overview
|
|
|
|
YesChef is a family shopping list and recipe app. Single-tenant per deployment, gated by a shared `FAMILY_CODE` invite phrase at registration. Real-time list collaboration is delivered over SignalR.
|
|
|
|
The repo is a small monorepo with two deployables plus an e2e harness at the root:
|
|
|
|
- `src/backend/YesChef.Api/` — ASP.NET Core (net10.0) minimal API + EF Core (Npgsql) + SignalR
|
|
- `src/frontend/` — SvelteKit 2 (Svelte 5 runes) + TypeScript + Tailwind v4, deployed via `@sveltejs/adapter-node`
|
|
- `docker-compose.yml` (root) — production-style stack: Postgres 17 + backend + frontend, fronted by Traefik (TLS via Let's Encrypt). Frontend and backend share the same `DOMAIN`; Traefik routes `/api`, `/hubs`, and `/health` to the backend, everything else to the frontend.
|
|
- `test-e2e.mjs` / `test-e2e-multiuser.mjs` — Playwright scripts run against `http://localhost:5173` (Vite dev) hitting a local backend.
|
|
|
|
## Common commands
|
|
|
|
Frontend (run from `src/frontend`, use `npm --prefix src/frontend <cmd>` to avoid `cd`):
|
|
|
|
```powershell
|
|
npm --prefix src/frontend install
|
|
npm --prefix src/frontend run dev # Vite dev server on :5173
|
|
npm --prefix src/frontend run build # adapter-node build → src/frontend/build
|
|
npm --prefix src/frontend run check # svelte-kit sync + svelte-check (type/lint check)
|
|
```
|
|
|
|
Backend (use `dotnet --project` to avoid `cd`):
|
|
|
|
```powershell
|
|
dotnet run --project src/backend/YesChef.Api # listens per launchSettings.json
|
|
dotnet build src/backend/YesChef.Api/YesChef.Api.csproj
|
|
dotnet ef migrations add <Name> --project src/backend/YesChef.Api
|
|
dotnet ef database update --project src/backend/YesChef.Api
|
|
```
|
|
|
|
Note: `Program.cs` calls `db.Database.MigrateAsync()` on startup, so running the API auto-applies pending migrations against the configured `ConnectionStrings:DefaultConnection`.
|
|
|
|
Backend tests (TUnit on Microsoft.Testing.Platform; integration tests use Testcontainers + Postgres). The `src/backend/global.json` opts `dotnet test` into MTP mode, so use `--solution` / `--project` flags:
|
|
|
|
```powershell
|
|
dotnet test --solution src/backend/YesChef.slnx # all tests
|
|
dotnet test --project src/backend/YesChef.Api.UnitTests/YesChef.Api.UnitTests.csproj
|
|
dotnet test --project src/backend/YesChef.Api.IntegrationTests/YesChef.Api.IntegrationTests.csproj
|
|
```
|
|
|
|
Integration tests start a single Postgres 17 container per test session, create a migrated `yeschef_template` database, then clone a fresh DB per test via `CREATE DATABASE … TEMPLATE …`. Tests run in parallel (capped by `IntegrationTestParallelLimit`) and require Docker (Rancher Desktop or Docker Desktop).
|
|
|
|
Full stack via Docker (requires `.env` populated from `.env.example`):
|
|
|
|
```powershell
|
|
docker compose up -d --build
|
|
docker compose logs -f backend
|
|
```
|
|
|
|
E2E (requires frontend running on :5173 and backend reachable):
|
|
|
|
```powershell
|
|
node test-e2e.mjs
|
|
node test-e2e-multiuser.mjs
|
|
```
|
|
|
|
There is no backend test project and no test runner configured in the root `package.json` — `npm test` is a no-op stub.
|
|
|
|
## Architecture
|
|
|
|
### Backend — feature-folder minimal API
|
|
|
|
`Program.cs` is the composition root. It wires EF Core (`YesChefDb`), JWT bearer auth, SignalR, runs migrations, then maps endpoints by feature folder:
|
|
|
|
- `Auth/` — `/api/auth` register/login/me. Registration requires the `FamilyCode` config value (env `FamilyCode`). Passwords use `PasswordHasher<User>`. JWTs are signed with HS256 from `Jwt:Secret`; issuer/audience are NOT validated. The `OnMessageReceived` event also accepts `?access_token=` for the `/hubs/*` SignalR paths.
|
|
- `Features/Stores/`, `Features/ShoppingLists/`, `Features/Recipes/` — each exposes a static `Map…Endpoints(this RouteGroupBuilder)` extension. All are mapped under `RequireAuthorization()`.
|
|
- `Features/ShoppingLists/ShoppingListHub.cs` — SignalR hub at `/hubs/shopping-list` for live list updates.
|
|
- `Data/YesChefDb.cs` — single `DbContext`. Notable model rules: `User.Name` and `Store.Name` are unique; `ShoppingList → Items` cascade-deletes; `ShoppingListItem.CheckedByUser` and `ShoppingListItem.Recipe` set null on delete; `Recipe → Ingredients` cascades.
|
|
- `Auth/ClaimsPrincipalExtensions.cs` — `GetUserId()` / `GetUserName()` helpers used by endpoints to scope writes.
|
|
- `Migrations/` — EF Core migrations. Add new ones with `dotnet ef migrations add` (see commands above).
|
|
|
|
When adding a feature: create `Features/<Name>/<Name>Endpoints.cs` with a `Map<Name>Endpoints` extension, register the group in `Program.cs`, and add any new entities to `Entities/` plus configuration in `YesChefDb.OnModelCreating`.
|
|
|
|
### Frontend — SvelteKit with runes
|
|
|
|
- `svelte.config.js` forces `runes: true` for all non-`node_modules` files. Use Svelte 5 runes (`$state`, `$derived`, `$effect`); do not use legacy reactive `$:` syntax.
|
|
- `src/lib/api.ts` — `api<T>(path, opts)` helper. Stores the JWT in `localStorage` under `token`, attaches `Authorization: Bearer …`, and on 401 it clears the token and `goto('/login')`.
|
|
- `src/lib/auth.svelte.ts` — runes-based auth state.
|
|
- `src/lib/signalr.ts` — SignalR client wiring (passes the JWT via the `?access_token=` query string the backend's `OnMessageReceived` handler expects).
|
|
- `src/routes/` — file-based routes: `/login`, `/lists`, `/lists/[id]`, `/recipes`, `/recipes/new`, `/recipes/[id]`, `/stores`.
|
|
- `src/service-worker.ts` — PWA service worker.
|
|
- `$lib` is aliased to `src/lib` (see `svelte.config.js`).
|
|
- The frontend calls relative paths like `/api/...`; in production Traefik routes those to the backend on the same host. For local dev against a backend on a different port, configure a Vite proxy or run the backend behind the same origin.
|
|
|
|
### Configuration & secrets
|
|
|
|
`.env.example` documents the four required environment variables for the Docker stack: `POSTGRES_PASSWORD`, `JWT_SECRET`, `FAMILY_CODE`, `DOMAIN`. The backend reads `Jwt:Secret`, `FamilyCode`, and `ConnectionStrings:DefaultConnection` via `IConfiguration` — these can come from `appsettings.Development.json` for local runs or from environment variables (double-underscore form, e.g. `ConnectionStrings__DefaultConnection`) in containers.
|