diff --git a/.github/skills/add-api-endpoint/SKILL.md b/.github/skills/add-api-endpoint/SKILL.md new file mode 100644 index 00000000..e20fcd36 --- /dev/null +++ b/.github/skills/add-api-endpoint/SKILL.md @@ -0,0 +1,94 @@ +--- +name: add-api-endpoint +description: Step-by-step guide for adding a new Minimal API endpoint to MenuApi following the layered model pattern (EF Entity → DBModel → ViewModel, Mapperly, repository, service, MapGroup registration). +user-invokable: true +context: inline +--- + +# Adding a New API Endpoint + +## Purpose + +Use this skill when adding any new HTTP endpoint to `MenuApi`. It enforces the layered model pattern and prevents common mistakes such as layer-mixing, missing Mapperly attributes, or unregistered DI services. + +## Repository facts + +- Backend solution root: `backend/` +- API project: `backend/MenuApi/` +- DB project: `backend/MenuDB/` + +## The three model layers + +Never mix these layers. Each has a distinct location and purpose: + +| Layer | Location | Purpose | +|---|---|---| +| EF Entities | `backend/MenuDB/Data/` | Database rows (e.g. `RecipeEntity`) | +| DB Models | `backend/MenuApi/DBModel/` | Intermediate records with Vogen value objects (e.g. `DBModel.Recipe`) | +| ViewModels | `backend/MenuApi/ViewModel/` | API request/response DTOs (e.g. `ViewModel.Recipe`, `NewRecipe`) | + +## Steps + +### 1. Add ViewModel DTOs + +Create or update files in `backend/MenuApi/ViewModel/`. Each DTO is a C# `class` (not a `record`) to support DataAnnotations such as `[Required]`. Use Vogen value objects for all typed properties (see `add-value-object` skill); never use raw `int`, `string`, or `Guid` for domain identifiers or constrained values. + +### 2. Add DB model records (if needed) + +If the endpoint requires a new data shape that differs from existing DB models, create the corresponding `record` in `backend/MenuApi/DBModel/`. + +### 3. Update Mapperly mappings + +Open `backend/MenuApi/MappingProfiles/ViewModelMapper.cs` and add or update `[MapProperty]` attributes for any renamed or new properties. Mapperly is source-generated and zero-reflection; missing attributes cause compile errors that `TreatWarningsAsErrors` will surface immediately. + +### 4. Add the repository method + +- Declare the method signature on the interface: `backend/MenuApi/Repositories/IRepository.cs` +- Implement it in: `backend/MenuApi/Repositories/Repository.cs` +- Use `.Value` to unwrap Vogen types when writing EF queries. +- Use `TypeName.From(x)` to wrap raw values returned from EF into Vogen types. +- Add `ConfigureAwait(false)` on every `await` call. + +### 5. Add the service method + +- Declare the method on the interface: `backend/MenuApi/Services/IService.cs` +- Implement it in: `backend/MenuApi/Services/Service.cs` +- Services call repositories and apply business rules. Add `ConfigureAwait(false)` on every `await` call. + +### 6. Register the endpoint + +Add the endpoint in the relevant `backend/MenuApi/Recipes/*Api.cs` file using the `MapGroup` pattern. Use `.Produces(statusCode)` and `.ProducesProblem(statusCode)` to document response types — the existing endpoints do not use `.WithName()` or `.WithOpenApi()`. Example: + +```csharp +group.MapGet("/{id}", GetByIdAsync) + .Produces(StatusCodes.Status200OK) + .ProducesProblem(StatusCodes.Status404NotFound); +``` + +### 7. Register DI services + +Add any new repository and service registrations to `backend/MenuApi/Program.cs`: + +```csharp +builder.Services.AddScoped(); +builder.Services.AddScoped(); +``` + +## Validation + +After implementing, run from `backend/`: + +```bash +dotnet restore MenuApi.sln +dotnet build MenuApi.sln --configuration Release --no-restore +dotnet test --project MenuApi.Tests/MenuApi.Tests.csproj --configuration Release --no-build +``` + +Then regenerate the OpenAPI spec and validate the frontend (see `openapi-sync` skill): + +```bash +cd ../ui/menu-website +pnpm run generate-openapi +pnpm run lint +pnpm run build +``` diff --git a/.github/skills/add-value-object/SKILL.md b/.github/skills/add-value-object/SKILL.md new file mode 100644 index 00000000..f710fec6 --- /dev/null +++ b/.github/skills/add-value-object/SKILL.md @@ -0,0 +1,79 @@ +--- +name: add-value-object +description: How to create a new Vogen value object in MenuApi — covering the attribute, assembly-wide defaults, EF Core value converter registration, Swagger mapping, and repository wrapping conventions. +user-invokable: true +context: inline +--- + +# Adding a New Vogen Value Object + +## Purpose + +Use this skill when wrapping a primitive type in a Vogen value object. All domain identifiers and constrained values in `MenuApi` must be Vogen types — never raw `int`, `string`, or `Guid`. + +## Repository facts + +- Value objects live in: `backend/MenuApi/ValueObjects/` +- Assembly-wide Vogen defaults: `backend/MenuApi/VogenDefaults.cs` (note: at the project root, not inside the `ValueObjects/` folder) +- The assembly-level attribute in `VogenDefaults.cs` enables EF Core value converters and Swagger schema mapping automatically for all value objects in the assembly. + +## Steps + +### 1. Create the value object file + +Add a new file in `backend/MenuApi/ValueObjects/`, e.g. `RecipeId.cs`: + +```csharp +[ValueObject] +public readonly partial struct RecipeId { } +``` + +For a string-backed value object: + +```csharp +[ValueObject] +public readonly partial struct RecipeName { } +``` + +Supported backing types include `int`, `long`, `string`, `Guid`, `decimal`, and any other primitive that Vogen supports. + +### 2. No additional EF or Swagger registration needed + +`VogenDefaults.cs` contains an assembly-level attribute that instructs Vogen to generate EF Core value converters and Swagger schema mappings for every value object in the assembly. No manual registration is required. + +### 3. Use the value object in DB models and ViewModels + +- **DB models** (`backend/MenuApi/DBModel/`): use the Vogen type directly as the property type. +- **ViewModels** (`backend/MenuApi/ViewModel/`): use the Vogen type for request and response DTOs. +- **EF entities** (`backend/MenuDB/Data/`): store the raw primitive; EF will use the generated value converter automatically. + +### 4. Wrap and unwrap in repositories + +In repository implementations, convert between the raw EF primitive and the Vogen type: + +- **Wrap** (raw → Vogen): `RecipeId.From(entity.Id)` +- **Unwrap** (Vogen → raw): `recipeId.Value` + +```csharp +// Wrapping when reading from EF +var recipeId = RecipeId.From(entity.Id); + +// Unwrapping when writing an EF query predicate +var entity = await _context.Recipes + .FirstOrDefaultAsync(r => r.Id == recipeId.Value) + .ConfigureAwait(false); +``` + +### 5. Update Mapperly if needed + +If the new value object appears in a mapping profile, open `backend/MenuApi/MappingProfiles/ViewModelMapper.cs` and add the appropriate `[MapProperty]` attribute. Mapperly handles Vogen conversions automatically when both sides use the same Vogen type, but explicit attributes are needed for renamed properties. + +## Validation + +```bash +cd backend +dotnet restore MenuApi.sln +dotnet build MenuApi.sln --configuration Release --no-restore +``` + +`TreatWarningsAsErrors` is enabled, so any Mapperly mapping gap or Vogen configuration error will surface as a build failure. diff --git a/.github/skills/code-review-checklist/SKILL.md b/.github/skills/code-review-checklist/SKILL.md new file mode 100644 index 00000000..50880e5a --- /dev/null +++ b/.github/skills/code-review-checklist/SKILL.md @@ -0,0 +1,68 @@ +--- +name: code-review-checklist +description: Structured checklist for reviewing pull requests in the Menu repository — layer separation, Vogen coverage, Mapperly attributes, async conventions, and StyleCop compliance. +user-invokable: true +context: inline +--- + +# Code Review Checklist + +## Purpose + +Use this skill when reviewing a pull request against the Menu repository. Work through each section in order and flag any item that fails. + +## 1. Layer separation + +The three model layers must never be mixed: + +- [ ] EF entities (`backend/MenuDB/Data/`) are not referenced directly from `MenuApi` controllers or services — only from repository implementations. +- [ ] DB models (`backend/MenuApi/DBModel/`) are not returned from API endpoints — only from repository methods. +- [ ] ViewModels (`backend/MenuApi/ViewModel/`) are not passed into repository methods — only into and out of service methods. +- [ ] No raw EF `DbContext` usage outside of repository implementations. + +## 2. Vogen value objects + +- [ ] New domain identifiers and constrained values use Vogen types — not raw `int`, `string`, or `Guid`. +- [ ] Repository code uses `.Value` to unwrap and `TypeName.From(x)` to wrap. +- [ ] New value objects are placed in `backend/MenuApi/ValueObjects/`. + +## 3. Mapperly mappings + +- [ ] Any new or renamed property on a ViewModel or DB model has a corresponding `[MapProperty]` attribute update in `backend/MenuApi/MappingProfiles/ViewModelMapper.cs`. +- [ ] The solution builds without warnings (build with `--configuration Release` to confirm, since `TreatWarningsAsErrors` is enabled). + +## 4. Async conventions + +- [ ] Every `await` call in service and repository implementations has `ConfigureAwait(false)`. +- [ ] No blocking calls (`.Result`, `.Wait()`) on `Task` in service or repository code. + +## 5. Dependency injection + +- [ ] New repositories and services are registered in `backend/MenuApi/Program.cs`. +- [ ] Interfaces are used for DI registrations (e.g. `IRecipeService`, not `RecipeService`). + +## 6. API endpoints + +- [ ] Each new endpoint is documented with `.Produces(statusCode)`, `.ProducesProblem(statusCode)`, and/or `.ProducesValidationProblem()` — do not use `.WithName(...)` or `.WithOpenApi()`. +- [ ] The generated frontend types (`src/generated/open-api/menu-api.ts`) are updated in the same commit as the endpoint change (run `pnpm generate-openapi` in `ui/menu-website/`). Note: `open-api/menu-api.json` is gitignored and should not be committed. + +## 7. StyleCop and code style + +- [ ] No StyleCop suppressions added without a justifying comment. +- [ ] `using` directives are ordered per StyleCop rules. +- [ ] `public partial class Program` remains in `Program.cs` for integration test `WebApplicationFactory` compatibility. + +## 8. Tests + +- [ ] New service or repository logic has corresponding unit tests in `MenuApi.Tests`. +- [ ] Unit tests use `[CustomAutoData]` (not bare `[AutoData]`) to handle Vogen specimen construction. +- [ ] Assertions use AwesomeAssertions (`.Should()`) — not xUnit raw assertions or FluentAssertions. +- [ ] New integration tests carry `[Collection("API Host Collection")]`. +- [ ] Integration test string parameters use `[StringLength(N, MinimumLength = 1)]` to avoid `varchar` overflow and `[NoAutoProperties]` to prevent constraint violations from auto-populated collections. + +## 9. Frontend (if applicable) + +- [ ] No hand-written types in `src/generated/` — types are always generated by `pnpm generate-openapi`. +- [ ] New components have a co-located Storybook story (`*.stories.ts`). +- [ ] Pages and components use the service layer — never the API layer (`recipe-api.ts`) directly. +- [ ] `import type` is used for type-only imports. diff --git a/.github/skills/ef-migration/SKILL.md b/.github/skills/ef-migration/SKILL.md new file mode 100644 index 00000000..c0dfbf07 --- /dev/null +++ b/.github/skills/ef-migration/SKILL.md @@ -0,0 +1,65 @@ +--- +name: ef-migration +description: Canonical commands and safety rules for EF Core migration management in the Menu repository — adding, removing, and applying migrations correctly. +user-invokable: true +context: inline +--- + +# EF Core Migration Management + +## Purpose + +Use this skill when adding, removing, or reasoning about EF Core migrations in the Menu repository. The project layout requires specific flags that differ from the EF CLI defaults. + +## Repository facts + +- Migrations project: `backend/MenuDB` (contains `MenuDbContext` and the `Migrations/` folder) +- Startup project: `backend/MenuApi` (required for EF tool configuration) +- Always run EF CLI commands from the **backend solution root** (`backend/`), not from a project subfolder. + +## Commands + +### Add a migration + +```bash +cd backend +dotnet ef migrations add --project MenuDB --startup-project MenuApi +``` + +`` should be PascalCase and describe the schema change, e.g. `AddRecipeIngredientsTable`. + +### Remove the most recent migration (before it is applied) + +```bash +cd backend +dotnet ef migrations remove --project MenuDB --startup-project MenuApi +``` + +Only remove a migration that has **not** been applied to any environment. If the migration is already in the database, revert it first with a new migration. + +### List applied and pending migrations + +```bash +cd backend +dotnet ef migrations list --project MenuDB --startup-project MenuApi +``` + +## How migrations are applied at runtime + +`Menu.MigrationService` is a `BackgroundService` that applies all pending EF migrations on startup and then exits. `MenuApi` is configured with `WaitForCompletion` on `Menu.MigrationService`, so the API never starts before migrations complete. + +**Do not run `dotnet ef database update` against a shared or production database.** Let `Menu.MigrationService` manage migration application at runtime. + +## EF entity configuration + +Entity mappings live in `backend/MenuDB/Data/`. Column constraints (max length, required, etc.) are configured inside `MenuDbContext.OnModelCreating`. After adding a migration, always inspect the generated migration file to confirm the SQL it will execute matches your intent before committing. + +## Validation + +After adding a migration, build the solution to confirm there are no EF model snapshot conflicts: + +```bash +cd backend +dotnet restore MenuApi.sln +dotnet build MenuApi.sln --configuration Release --no-restore +``` diff --git a/.github/skills/frontend-component/SKILL.md b/.github/skills/frontend-component/SKILL.md new file mode 100644 index 00000000..8bae66fc --- /dev/null +++ b/.github/skills/frontend-component/SKILL.md @@ -0,0 +1,117 @@ +--- +name: frontend-component +description: Conventions for adding a new Vue 3 + Quasar component in the Menu frontend — correct folder, TypeScript Composition API style, co-located Storybook story, and the two-layer API service pattern. +user-invokable: true +context: inline +--- + +# Adding a New Frontend Component + +## Purpose + +Use this skill when creating a new Vue 3 component in `ui/menu-website/`. It covers folder placement, coding style, Storybook stories, and the mandatory two-layer API pattern. + +## Repository facts + +- Frontend root: `ui/menu-website/` +- Source: `ui/menu-website/src/` +- Path alias: `@/` maps to `src/` + +## Component folder placement + +Place components in the correct subfolder of `src/components/`: + +| Type | Location | Example | +|---|---|---| +| Simple/reusable UI atoms | `src/components/atoms/form/` | `text-field`, `select-field` | +| Reusable header elements | `src/components/atoms/header/` | `header-button` | +| Compound components | `src/components/molecules/recipe/` | recipe molecules | +| Recipe field sub-components | `src/components/molecules/recipe/fields/` | field components | +| Navigation components | `src/components/molecules/navigation/` | nav molecules | +| Complex sections | `src/components/organisms/recipe/` | recipe organisms | +| Navigation organisms | `src/components/organisms/navigation/` | nav organisms | +| Layout shells | `src/components/templates/` | `main-shell-template` | + +Pages live in `src/pages/` and are route-level components only. + +## Component file conventions + +Use Vue 3 Composition API with ` + + +``` + +- Always use TypeScript (`lang="ts"`). +- Use `import type` for type-only imports (`@typescript-eslint/consistent-type-imports` is enforced). +- Use Quasar components (`q-*`) for UI elements; do not add additional UI libraries. +- Reference other source files with the `@/` alias, not relative `../` paths where avoidable. + +## Co-located Storybook story + +Every component must have a co-located story file named `.stories.ts` in the same folder. Example: + +```ts +import type { Meta, StoryObj } from '@storybook/vue3' +import MyComponent from './MyComponent.vue' + +const meta: Meta = { + component: MyComponent, +} +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + label: 'Example', + }, +} +``` + +## Two-layer API service pattern + +Pages and components must **never** call the API layer (`recipe-api.ts`) directly. Always use the service layer: + +| Layer | File | What it does | +|---|---|---| +| API layer | `src/services/recipe-api.ts` | Low-level `openapi-fetch` calls with typed functions | +| Service layer | `src/services/recipe-service.ts` | TanStack Query composables (`useRecipes`, `useCreateRecipe`); handles cache invalidation | + +In a component, import and use a composable from the service layer: + +```ts +import { useRecipeService } from '@/services/recipe-service' + +const { useRecipes } = useRecipeService() +const { data: recipes, isLoading } = useRecipes() +``` + +## Routing + +- Unauthenticated routes: add to `src/router/public.routes.ts` +- Authenticated routes: add to `src/router/authenticated.routes.ts` (protected by `authGuard`) +- Both groups use `MainLayout.vue` as the parent layout. + +## Validation + +Run from `ui/menu-website/`: + +```bash +pnpm run lint +pnpm run build +pnpm run test +``` diff --git a/.github/skills/openapi-sync/SKILL.md b/.github/skills/openapi-sync/SKILL.md new file mode 100644 index 00000000..3eb41965 --- /dev/null +++ b/.github/skills/openapi-sync/SKILL.md @@ -0,0 +1,63 @@ +--- +name: openapi-sync +description: Keep the frontend API types in sync with the backend after any endpoint change — regenerate menu-api.ts from the OpenAPI spec produced by the .NET build. +user-invokable: true +context: inline +--- + +# OpenAPI Sync + +## Purpose + +Use this skill after any change to a `MenuApi` endpoint (new endpoint, changed request/response shape, renamed route). The frontend uses generated TypeScript types from the backend's OpenAPI spec; stale types cause type errors and runtime mismatches. + +## How it works + +1. The .NET build writes `open-api/menu-api.json` at the repository root. +2. `pnpm generate-openapi` (from `ui/menu-website/`) reads that file and outputs `src/generated/open-api/menu-api.ts`. +3. `src/services/recipe-api.ts` consumes the generated types via `openapi-fetch`. + +**Never hand-write types in `src/generated/`** — the entire `src/generated/` folder is overwritten on each run. + +## Steps + +### 1. Build the backend to produce the spec + +```bash +cd backend +dotnet restore MenuApi.sln +dotnet build MenuApi.sln --configuration Release --no-restore +``` + +This writes `open-api/menu-api.json`. + +### 2. Regenerate the frontend types + +```bash +cd ui/menu-website +pnpm generate-openapi +``` + +This reads `../../open-api/menu-api.json` and outputs `src/generated/open-api/menu-api.ts`. + +### 3. Commit the generated types + +Commit `src/generated/open-api/menu-api.ts` in the same commit as the backend change that caused the update. + +`open-api/menu-api.json` is generated by the .NET build and is gitignored (`open-api/.gitignore` ignores `*.json`) — do not attempt to commit it. It is consumed as a build artifact at generation time and uploaded to CI artefacts for the frontend job. + +## Validation + +After regeneration, confirm the frontend still compiles and passes lint: + +```bash +cd ui/menu-website +pnpm run lint +pnpm run build +``` + +## Common mistakes + +- Running `pnpm generate-openapi` before building the backend — the spec file will be stale or missing. +- Editing `src/generated/open-api/menu-api.ts` by hand — changes will be silently overwritten on the next generation run. +- Trying to commit `open-api/menu-api.json` — it is gitignored and is consumed as a build-time artefact, not a tracked file. diff --git a/.github/skills/pr-comment-identity/SKILL.md b/.github/skills/pr-comment-identity/SKILL.md new file mode 100644 index 00000000..f29c67d8 --- /dev/null +++ b/.github/skills/pr-comment-identity/SKILL.md @@ -0,0 +1,48 @@ +--- +name: pr-comment-identity +description: Ensure all PR review thread comments posted by Copilot are clearly attributed as automated so reviewers and contributors can tell immediately who wrote the comment. +user-invokable: false +context: inline +--- + +# PR Comment Identity + +## Purpose + +Use this skill whenever Copilot posts a comment on a pull request review thread — whether responding to a review request, addressing a specific line comment, or replying in a general PR conversation thread. + +## Required attribution + +Every PR review thread comment must begin with the following attribution block, on its own line, before any substantive content: + +``` +> 🤖 **This comment was written by GitHub Copilot.** +``` + +## Rules + +- Always prepend the attribution line as the very first line of the comment. +- Leave one blank line between the attribution line and the body of the comment. +- Do **not** add the attribution line to: + - Pull request titles or descriptions + - Commit messages + - Issue body text or issue comments + - Any content that is not a PR review thread comment +- Do **not** remove or reword the attribution in a follow-up edit to the same comment. +- Do **not** replace the attribution with a different phrasing such as "As an AI" or "I am Copilot" — use the canonical block above exactly. + +## Example + +````markdown +> 🤖 **This comment was written by GitHub Copilot.** + +The `RecipeService.CreateAsync` method currently calls `_repository.InsertAsync` without first +deduplicating the ingredient list. Per the `set-like-write-dedupe` pattern, duplicates should be +collapsed at the service layer before the repository is called. + +Suggested fix: + +```csharp +var ingredients = [.. ViewModelMapper.Map(newRecipe.Ingredients).Distinct()]; +``` +```` diff --git a/.github/skills/write-tests/SKILL.md b/.github/skills/write-tests/SKILL.md new file mode 100644 index 00000000..bdbf4f50 --- /dev/null +++ b/.github/skills/write-tests/SKILL.md @@ -0,0 +1,112 @@ +--- +name: write-tests +description: Patterns for writing unit and integration tests in the Menu repository — xUnit, AutoFixture, FakeItEasy, AwesomeAssertions, CustomAutoData, and Aspire integration test conventions. +user-invokable: true +context: inline +--- + +# Writing Tests + +## Purpose + +Use this skill when writing or reviewing tests in `MenuApi.Tests` (unit) or `MenuApi.Integration.Tests` (integration). The two projects use different fixture patterns and have different infrastructure requirements. + +## Unit tests (`MenuApi.Tests`) + +### Stack + +- **xUnit** — test runner +- **AutoFixture** — anonymous test data generation +- **FakeItEasy** — fakes / mocks +- **AwesomeAssertions** — fluent assertions (use `.Should()`) + +### The `[CustomAutoData]` attribute + +Use `[CustomAutoData]` on every test method that needs AutoFixture-generated parameters. This attribute wires up a custom `ValueObjectSpecimenBuilder` (defined in `CustomGenerator.cs`) that knows how to construct Vogen value objects via reflection. Without it, AutoFixture cannot create Vogen types and will throw. + +```csharp +[Theory] +[CustomAutoData] +public async Task CreateRecipe_ReturnsCreatedRecipe( + NewRecipe newRecipe, + [Frozen] IRecipeRepository repository, + RecipeService sut) +{ + // Arrange + A.CallTo(() => repository.InsertAsync(A._)) + .Returns(Task.CompletedTask); + + // Act + var result = await sut.CreateAsync(newRecipe); + + // Assert + result.Should().NotBeNull(); +} +``` + +### Assertion style + +Always use **AwesomeAssertions** (`.Should()`). Do **not** use FluentAssertions — these are two different packages and the APIs diverge. + +```csharp +// Correct +result.Should().NotBeNull(); +result.Name.Should().Be(expected.Name); + +// Wrong — do not use xUnit raw assertions where AwesomeAssertions can be used +Assert.NotNull(result); +``` + +### ConfigureAwait + +Add `ConfigureAwait(false)` on every `await` in test helpers and non-test-method code. Test methods themselves do not need it. + +## Integration tests (`MenuApi.Integration.Tests`) + +### Infrastructure + +- Uses **Aspire Testing** to spin up the full `AppHost` including a containerised SQL Server. +- Requires Docker to be running. +- Requires Auth0 secrets (set via environment variables or user secrets): + - `parameters__Auth0TestClientId` + - `parameters__Auth0TestClientSecret` + - `parameters__Auth0Domain` + - `parameters__Auth0Audience` + +### Mandatory collection attribute + +Every integration test class **must** use: + +```csharp +[Collection("API Host Collection")] +``` + +This ensures all integration test classes run sequentially against the single shared `AppHost` instance. Omitting it causes parallelism issues and flaky tests. + +### Data annotation attributes for integration tests + +Integration tests use `[Theory, AutoData]` combined with per-parameter AutoFixture attributes to constrain generated values to fit database constraints. Key attributes: + +- `[StringLength(N, MinimumLength = 1)]` — limits a `string` parameter to at most N characters (matches the `varchar(N)` column size). +- `[NoAutoProperties]` — tells AutoFixture not to populate collection/navigation properties that would cause constraint violations. + +```csharp +[Theory, AutoData] +public async Task PostRecipe_ReturnsCreated( + [StringLength(500, MinimumLength = 1)] string recipeName, + HttpClient client) +{ + var response = await client.PostAsJsonAsync("/recipes", new NewRecipe { Name = recipeName }) + .ConfigureAwait(false); + response.StatusCode.Should().Be(HttpStatusCode.Created); +} +``` + +### Running integration tests + +```bash +cd backend +dotnet test --project MenuApi.Integration.Tests/MenuApi.Integration.Tests.csproj --configuration Release +``` + +Integration tests are slower (Docker startup + SQL container). Run unit tests first to catch logic errors before running integration tests.