feat(shopping): add household-scoped ingredient request endpoint + tests and spec
This commit is contained in:
parent
75d098cd7a
commit
978c970fb6
3 changed files with 32 additions and 12 deletions
|
|
@ -13,6 +13,7 @@ from api.shopping_models import (
|
|||
ShoppingListOut,
|
||||
PurchasedShoppingList,
|
||||
_to_ingredient_item,
|
||||
ListIngredientItem,
|
||||
_to_meal_item,
|
||||
_to_shopping_list_out,
|
||||
RequestedMealItem,
|
||||
|
|
@ -202,6 +203,33 @@ class IngredientIdWrapper(_ApiModel):
|
|||
ingredient_id: int
|
||||
|
||||
|
||||
@router.post(
|
||||
"/current/ingredients",
|
||||
response_model=ListIngredientItem,
|
||||
operation_id="requestIngredientV2",
|
||||
summary="Request an ingredient for shopping (scoped)",
|
||||
)
|
||||
async def request_ingredient_scoped(
|
||||
r: IngredientIdWrapper,
|
||||
request: Request,
|
||||
household=Depends(get_household_from_slug),
|
||||
user=Depends(get_current_user),
|
||||
conn: aiosqlite.Connection = Depends(get_db),
|
||||
):
|
||||
hid = household["id"]
|
||||
from ingredients.repository import find_ingredient_by_id
|
||||
|
||||
ingredient = await find_ingredient_by_id(conn, r.ingredient_id)
|
||||
if not ingredient:
|
||||
return error_response(request, 404, "Ingredient not found")
|
||||
|
||||
try:
|
||||
item = await shopping.request_ingredient_scoped(conn, ingredient, hid, user.id)
|
||||
except ValueError as e:
|
||||
return error_response(request, 400, str(e))
|
||||
return _to_ingredient_item(item)
|
||||
|
||||
|
||||
# Re-export shared DTOs for importers
|
||||
__all__ = [
|
||||
"router",
|
||||
|
|
|
|||
|
|
@ -121,9 +121,7 @@ class TestRecipesHouseholdV2(unittest.IsolatedAsyncioTestCase):
|
|||
assert "createdBy" in r.json() and "createdById" in r.json()
|
||||
|
||||
# Delete it via v2 scoped route
|
||||
r2 = self.client.delete(
|
||||
f"/api/v1/households/{self.h1}/recipes/{rid}", headers=self.headers
|
||||
)
|
||||
r2 = self.client.delete(f"/api/v1/households/{self.h1}/recipes/{rid}", headers=self.headers)
|
||||
assert r2.status_code == 200, r2.text
|
||||
# hiddenBy is populated on delete
|
||||
body_del = r2.json()
|
||||
|
|
@ -131,9 +129,7 @@ class TestRecipesHouseholdV2(unittest.IsolatedAsyncioTestCase):
|
|||
assert body_del["hiddenBy"]["displayName"] == "R"
|
||||
|
||||
# Subsequent get in same household should be 404
|
||||
r3 = self.client.get(
|
||||
f"/api/v1/households/{self.h1}/recipes/{rid}", headers=self.headers
|
||||
)
|
||||
r3 = self.client.get(f"/api/v1/households/{self.h1}/recipes/{rid}", headers=self.headers)
|
||||
assert r3.status_code == 404
|
||||
|
||||
def test_create_recipe_requires_ingredients_and_cursor_edge(self):
|
||||
|
|
|
|||
|
|
@ -75,16 +75,12 @@ class TestShoppingRequestIngredientV2(unittest.IsolatedAsyncioTestCase):
|
|||
assert item["mealId"] is None
|
||||
|
||||
# Visible in H1 current, not in H2
|
||||
r1 = self.client.get(
|
||||
f"/api/v1/households/{self.h1}/shopping/current", headers=self.headers
|
||||
)
|
||||
r1 = self.client.get(f"/api/v1/households/{self.h1}/shopping/current", headers=self.headers)
|
||||
assert r1.status_code == 200, r1.text
|
||||
cur1 = r1.json()
|
||||
assert any(i.get("ingredientId") == self.milk_id for i in cur1["outstandingItems"])
|
||||
|
||||
r2 = self.client.get(
|
||||
f"/api/v1/households/{self.h2}/shopping/current", headers=self.headers
|
||||
)
|
||||
r2 = self.client.get(f"/api/v1/households/{self.h2}/shopping/current", headers=self.headers)
|
||||
assert r2.status_code == 200, r2.text
|
||||
cur2 = r2.json()
|
||||
assert not any(i.get("ingredientId") == self.milk_id for i in cur2["outstandingItems"])
|
||||
|
|
|
|||
Loading…
Reference in a new issue