fix(recipe): measurement_interval_minutes e image_path si possono di nuovo azzerare (#37.3)
recipe_editor.html saveRecipe(): i due campi vengono sempre inviati nel payload (valore o null esplicito), non più omessi quando svuotati. recipe_service.py _header_fields(): per i due campi (unica eccezione concordata al confine dei lotti, righe ~37-60) distingue "non inviato" da "inviato null" con model_fields_set, invece di getattr(...) is not None — altrimenti un null esplicito veniva scartato allo stesso modo di un campo assente e il valore restava bloccato per sempre. Gli altri campi header (name, description, requires_*, allow_manual_input) non toccati. Test backend in test_recipes.py: TestUpdateRecipeClearNullableFields. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -250,6 +250,79 @@ class TestUpdateRecipe:
|
||||
assert version_numbers == [1, 2]
|
||||
|
||||
|
||||
class TestUpdateRecipeClearNullableFields:
|
||||
"""gitea#37.3: measurement_interval_minutes/image_path, once set, could
|
||||
never be cleared again - the request set them and the update endpoint
|
||||
could not tell "not sent" apart from "sent as null" (both landed on the
|
||||
same getattr(...) is not None check in recipe_service._header_fields)."""
|
||||
|
||||
async def test_explicit_null_clears_measurement_interval(
|
||||
self, client: AsyncClient, maker_user: User, db_session: AsyncSession,
|
||||
):
|
||||
recipe = await create_test_recipe(db_session, maker_user.id)
|
||||
|
||||
set_resp = await client.put(
|
||||
f"/api/recipes/{recipe.id}",
|
||||
headers=auth_headers(maker_user),
|
||||
json={"measurement_interval_minutes": 30},
|
||||
)
|
||||
assert set_resp.status_code == 200
|
||||
assert set_resp.json()["measurement_interval_minutes"] == 30
|
||||
|
||||
clear_resp = await client.put(
|
||||
f"/api/recipes/{recipe.id}",
|
||||
headers=auth_headers(maker_user),
|
||||
json={"measurement_interval_minutes": None},
|
||||
)
|
||||
assert clear_resp.status_code == 200
|
||||
assert clear_resp.json()["measurement_interval_minutes"] is None
|
||||
|
||||
async def test_explicit_null_clears_image_path(
|
||||
self, client: AsyncClient, maker_user: User, db_session: AsyncSession,
|
||||
):
|
||||
recipe = await create_test_recipe(db_session, maker_user.id)
|
||||
|
||||
set_resp = await client.put(
|
||||
f"/api/recipes/{recipe.id}",
|
||||
headers=auth_headers(maker_user),
|
||||
json={"image_path": "uploads/1/1/preview.png"},
|
||||
)
|
||||
assert set_resp.status_code == 200
|
||||
assert set_resp.json()["image_path"] == "uploads/1/1/preview.png"
|
||||
|
||||
clear_resp = await client.put(
|
||||
f"/api/recipes/{recipe.id}",
|
||||
headers=auth_headers(maker_user),
|
||||
json={"image_path": None},
|
||||
)
|
||||
assert clear_resp.status_code == 200
|
||||
assert clear_resp.json()["image_path"] is None
|
||||
|
||||
async def test_field_not_sent_leaves_existing_value_untouched(
|
||||
self, client: AsyncClient, maker_user: User, db_session: AsyncSession,
|
||||
):
|
||||
"""Regression guard for the other half of the distinction: a request
|
||||
that never mentions the field must still leave it as it was."""
|
||||
recipe = await create_test_recipe(db_session, maker_user.id)
|
||||
|
||||
set_resp = await client.put(
|
||||
f"/api/recipes/{recipe.id}",
|
||||
headers=auth_headers(maker_user),
|
||||
json={"measurement_interval_minutes": 45},
|
||||
)
|
||||
assert set_resp.status_code == 200
|
||||
assert set_resp.json()["measurement_interval_minutes"] == 45
|
||||
|
||||
unrelated_resp = await client.put(
|
||||
f"/api/recipes/{recipe.id}",
|
||||
headers=auth_headers(maker_user),
|
||||
json={"name": "Renamed Recipe"},
|
||||
)
|
||||
assert unrelated_resp.status_code == 200
|
||||
assert unrelated_resp.json()["measurement_interval_minutes"] == 45
|
||||
assert unrelated_resp.json()["name"] == "Renamed Recipe"
|
||||
|
||||
|
||||
class TestDeleteRecipe:
|
||||
"""DELETE /api/recipes/{recipe_id} tests."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user