Files
YesChef/CLAUDE.md
T
Josh Rogers 76e8de9484 Add TUnit-based unit and integration tests for backend
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>
2026-05-06 20:56:29 -05:00

6.2 KiB

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):

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):

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:

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):

docker compose up -d --build
docker compose logs -f backend

E2E (requires frontend running on :5173 and backend reachable):

node test-e2e.mjs
node test-e2e-multiuser.mjs

There is no backend test project and no test runner configured in the root package.jsonnpm 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.csGetUserId() / 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.tsapi<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.