Skip to content

Commit 264bf20

Browse files
TomerFicursoragent
andauthored
fix: return 404 when deleting non-existent schedule (#952)
* fix: return 404 when deleting non-existent schedule Validate schedule existence by calling get_schedules before delete_schedule. Return a 404 error with a descriptive message if the requested schedule ID is not found on the device. Closes #723 Signed-off-by: Tomer Figenblat <tomer@figenblat.com> Co-authored-by: Cursor <cursoragent@cursor.com> * test: add test for get_schedules failure in delete_schedule Cover the case where get_schedules raises an exception during the pre-deletion validation, ensuring a 500 is returned and delete_schedule is never called. Signed-off-by: Tomer Figenblat <tomer@figenblat.com> Co-authored-by: Cursor <cursoragent@cursor.com> * fix: correct errorneous typo to erroneous in test names Fix misspelling in test_errorneous_delete_schedule_get_schedules_failure, test_errorneous_delete_schedule_delete_request, and test_errorneous_create_schedule. Signed-off-by: Tomer Figenblat <tomer@figenblat.com> Co-authored-by: Cursor <cursoragent@cursor.com> --------- Signed-off-by: Tomer Figenblat <tomer@figenblat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent dbec365 commit 264bf20

2 files changed

Lines changed: 101 additions & 3 deletions

File tree

app/tests/test_web_app.py

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,9 @@ async def test_erroneous_get_schedules_get_request(
500500
],
501501
)
502502
@patch("aioswitcher.api.SwitcherApi.delete_schedule")
503+
@patch("aioswitcher.api.SwitcherApi.get_schedules")
503504
async def test_successful_delete_schedule_delete_request(
505+
api_get_schedules,
504506
api_delete_schedule,
505507
response_serializer,
506508
response_mock,
@@ -509,12 +511,19 @@ async def test_successful_delete_schedule_delete_request(
509511
api_client,
510512
api_uri,
511513
):
514+
# stub get_schedules to return a schedule with matching id
515+
schedule_mock = Mock()
516+
schedule_mock.schedule_id = "5"
517+
schedules_response = Mock()
518+
schedules_response.schedules = [schedule_mock]
519+
api_get_schedules.return_value = schedules_response
512520
# stub api_delete_schedule to return mock response
513521
api_delete_schedule.return_value = response_mock
514522
# send delete request for delete_schedule endpoint
515523
response = await api_client.delete(api_uri, json={webapp.KEY_SCHEDULE: "5"})
516524
# verify mocks calling
517525
api_connect.assert_called_once()
526+
api_get_schedules.assert_called_once()
518527
api_delete_schedule.assert_called_once_with("5")
519528
response_serializer.assert_called_once_with(response_mock)
520529
api_disconnect.assert_called_once()
@@ -523,6 +532,45 @@ async def test_successful_delete_schedule_delete_request(
523532
assert_that(await response.json()).contains_entry(fake_serialized_data)
524533

525534

535+
@mark.parametrize(
536+
"api_uri",
537+
[
538+
(delete_schedule_uri),
539+
(delete_schedule_uri2),
540+
],
541+
)
542+
@patch("aioswitcher.api.SwitcherApi.delete_schedule")
543+
@patch("aioswitcher.api.SwitcherApi.get_schedules")
544+
async def test_delete_schedule_returns_404_for_nonexistent_schedule(
545+
api_get_schedules,
546+
api_delete_schedule,
547+
response_serializer,
548+
api_connect,
549+
api_disconnect,
550+
api_client,
551+
api_uri,
552+
):
553+
# stub get_schedules to return schedules that don't include the requested id
554+
schedule_mock = Mock()
555+
schedule_mock.schedule_id = "3"
556+
schedules_response = Mock()
557+
schedules_response.schedules = [schedule_mock]
558+
api_get_schedules.return_value = schedules_response
559+
# send delete request for delete_schedule endpoint
560+
response = await api_client.delete(api_uri, json={webapp.KEY_SCHEDULE: "8"})
561+
# verify mocks calling
562+
api_connect.assert_called_once()
563+
api_get_schedules.assert_called_once()
564+
api_delete_schedule.assert_not_called()
565+
response_serializer.assert_not_called()
566+
api_disconnect.assert_called_once()
567+
# assert the expected response
568+
assert_that(response.status).is_equal_to(404)
569+
assert_that(await response.json()).contains_entry(
570+
{"error": "schedule 8 does not exist"}
571+
)
572+
573+
526574
@patch("aioswitcher.api.SwitcherApi.delete_schedule")
527575
async def test_delete_schedule_with_faulty_no_schedule_delete_request(
528576
api_delete_schedule, response_serializer, api_connect, api_disconnect, api_client
@@ -541,16 +589,59 @@ async def test_delete_schedule_with_faulty_no_schedule_delete_request(
541589
)
542590

543591

592+
@patch("aioswitcher.api.SwitcherApi.delete_schedule")
593+
@patch(
594+
"aioswitcher.api.SwitcherApi.get_schedules",
595+
side_effect=Exception("get_schedules failure"),
596+
)
597+
async def test_erroneous_delete_schedule_get_schedules_failure(
598+
api_get_schedules,
599+
api_delete_schedule,
600+
response_serializer,
601+
api_connect,
602+
api_disconnect,
603+
api_client,
604+
):
605+
# send delete request for delete_schedule endpoint
606+
response = await api_client.delete(
607+
delete_schedule_uri, json={webapp.KEY_SCHEDULE: "5"}
608+
)
609+
# verify mocks calling
610+
api_connect.assert_called_once()
611+
api_get_schedules.assert_called_once()
612+
api_delete_schedule.assert_not_called()
613+
response_serializer.assert_not_called()
614+
api_disconnect.assert_called_once()
615+
# assert the expected response
616+
assert_that(response.status).is_equal_to(500)
617+
assert_that(await response.json()).contains_entry(
618+
{"error": "get_schedules failure"}
619+
)
620+
621+
544622
@patch("aioswitcher.api.SwitcherApi.delete_schedule", side_effect=Exception("blabla"))
545-
async def test_errorneous_delete_schedule_delete_request(
546-
api_delete_schedule, response_serializer, api_connect, api_disconnect, api_client
623+
@patch("aioswitcher.api.SwitcherApi.get_schedules")
624+
async def test_erroneous_delete_schedule_delete_request(
625+
api_get_schedules,
626+
api_delete_schedule,
627+
response_serializer,
628+
api_connect,
629+
api_disconnect,
630+
api_client,
547631
):
632+
# stub get_schedules to return a schedule with matching id
633+
schedule_mock = Mock()
634+
schedule_mock.schedule_id = "5"
635+
schedules_response = Mock()
636+
schedules_response.schedules = [schedule_mock]
637+
api_get_schedules.return_value = schedules_response
548638
# send delete request for delete_schedule endpoint
549639
response = await api_client.delete(
550640
delete_schedule_uri, json={webapp.KEY_SCHEDULE: "5"}
551641
)
552642
# verify mocks calling
553643
api_connect.assert_called_once()
644+
api_get_schedules.assert_called_once()
554645
api_delete_schedule.assert_called_once_with("5")
555646
response_serializer.assert_not_called()
556647
api_disconnect.assert_called_once()
@@ -708,7 +799,7 @@ async def test_create_schedule_with_faulty_missing_key_post_request(
708799

709800

710801
@patch("aioswitcher.api.SwitcherApi.create_schedule", side_effect=Exception("blabla"))
711-
async def test_errorneous_create_schedule(
802+
async def test_erroneous_create_schedule(
712803
api_create_schedule, response_serializer, api_connect, api_disconnect, api_client
713804
):
714805
json_body = {webapp.KEY_START: "11:00", webapp.KEY_STOP: "11:15"}

app/webapp.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,13 @@ async def delete_schedule(request: web.Request) -> web.Response:
260260
async with SwitcherApi(
261261
device_type, request.query[KEY_IP], request.query[KEY_ID], login_key
262262
) as swapi:
263+
schedules_response = await swapi.get_schedules()
264+
existing_ids = [s.schedule_id for s in schedules_response.schedules]
265+
if schedule_id not in existing_ids:
266+
return web.json_response(
267+
{"error": f"schedule {schedule_id} does not exist"},
268+
status=404,
269+
)
263270
return web.json_response(
264271
_serialize_object(await swapi.delete_schedule(schedule_id))
265272
)

0 commit comments

Comments
 (0)