Expose explicit conflict when same ingredient+unit appears with different amounts#1063
Merged
dgee2 merged 1 commit intoMay 23, 2026
Conversation
…cipe When a recipe request contains the same ingredient+unit combination more than once with different amounts, the behavior was previously undefined: exact duplicates were silently absorbed by Distinct(), but conflicting-amount pairs both survived into UpsertRecipeIngredientsAsync where the composite PK (RecipeId, IngredientId, UnitId) would cause an unhandled DbUpdateException and an HTTP 500. This is a business-significant duplicate: the same ingredient+unit appearing with two different amounts in one request is contradictory and the user must resolve it. The fix adds explicit conflict detection in RecipeService.NormalizeRecipeIngredients: after Distinct() removes exact duplicates, any remaining (IngredientName, UnitName) group with more than one entry throws a BusinessValidationException with all conflicting pairs named. BusinessValidationExceptionHandler returns HTTP 422. Both recipe endpoints already declare ProducesProblem(422) in the OpenAPI contract, so no contract or frontend changes are required. Closes #980 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Contributor
There was a problem hiding this comment.
Pull request overview
This PR prevents an HTTP 500 when a recipe request includes the same ingredient+unit more than once with different amounts by detecting that business conflict in RecipeService and returning a client-visible validation error (HTTP 422) instead.
Changes:
- Add conflict detection in
RecipeService.NormalizeRecipeIngredientsafter exact-duplicate deduplication, throwingBusinessValidationExceptionfor ingredient+unit pairs that appear with multiple amounts. - Add unit test coverage to ensure conflicts throw and repository writes are not invoked.
- Add integration tests to verify
POST /api/recipeandPUT /api/recipe/{id}return HTTP 422 with problem details for conflicting ingredient amounts.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| backend/MenuApi/Services/RecipeService.cs | Adds ingredient+unit conflict detection after exact-duplicate removal and throws BusinessValidationException to prevent DB constraint crashes. |
| backend/MenuApi.Tests/Services/RecipeServiceTests.cs | Adds unit tests for conflicting ingredient amount detection and verifies no repository writes occur on conflict. |
| backend/MenuApi.Integration.Tests/RecipeWithIngredientsIntegrationTests.cs | Adds integration tests asserting HTTP 422 problem details for create/update requests with conflicting ingredient amounts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Why
When a recipe request contained the same ingredient+unit combination more than once with different amounts, the API would crash with HTTP 500.
NormalizeRecipeIngredientsusedDistinct()(structural equality on all three fields: ingredient name, unit, and amount), so exact duplicates were silently absorbed, but entries that differed only in amount both passed through. Both then enteredUpsertRecipeIngredientsAsync, where each attempted to insert a row with the same composite primary key(RecipeId, IngredientId, UnitId), causing an unhandledDbUpdateExceptionand a 500 response.This is a business-significant conflict: a recipe listing the same ingredient and unit twice with contradictory amounts is logically invalid, and the user must resolve the ambiguity before the request can succeed.
What changed
RecipeService.NormalizeRecipeIngredientsnow performs conflict detection after the existingDistinct()deduplication pass. Any(IngredientName, UnitName)group that still has more than one entry (meaning the same ingredient+unit with different amounts) triggers aBusinessValidationExceptionthat names all conflicting pairs.BusinessValidationExceptionHandlerconverts this to HTTP 422, which both recipe endpoints already advertise in their OpenAPI contract.The ordering matters: exact duplicates are still silently absorbed first (behaviour from #977), then the conflict check runs over what remains. No repository, schema, or OpenAPI contract changes are required.
Tests
CreateRecipeAsyncandUpdateRecipeAsyncthrowBusinessValidationExceptionand make no repository write calls when conflicting amounts are present; verify that all conflicting pairs are reported in the message; verify that mixed exact-duplicate + conflicting-amount input is still detected as a conflict.POST /recipeandPUT /recipe/{id}return HTTP 422 with a problem detail when the same ingredient+unit appears with different amounts.Fixes: #980