Skip to content

fix: return 404 when deleting non-existent schedule#952

Open
TomerFi wants to merge 2 commits intodevfrom
fix/delete-schedule-validate-existence
Open

fix: return 404 when deleting non-existent schedule#952
TomerFi wants to merge 2 commits intodevfrom
fix/delete-schedule-validate-existence

Conversation

@TomerFi
Copy link
Owner

@TomerFi TomerFi commented Feb 16, 2026

Summary

  • Validate schedule existence before deletion by calling get_schedules first.
  • Return a 404 with {"error": "schedule <id> does not exist"} when the requested schedule ID is not found on the device.
  • Add parametrized tests for the 404 case and update existing tests to mock get_schedules.

Closes #723

Validation

  • All 68 tests pass locally (66 existing + 2 new).

Summary by Sourcery

Return a 404 error when attempting to delete a schedule that does not exist on the device.

Bug Fixes:

  • Validate schedule existence before deletion and return a 404 response with an error payload when the requested schedule ID is not found.

Tests:

  • Add parametrized tests covering successful deletion, nonexistent schedule deletion returning 404, and error paths, including mocking of get_schedules where appropriate.

Summary by CodeRabbit

  • Bug Fixes

    • Delete schedule operations now validate that the requested schedule exists before attempting deletion, returning a clear 404 error message if not found.
  • Tests

    • Enhanced delete schedule test coverage to verify validation checks occur before deletion.
    • Added test for proper error handling when attempting to delete non-existent schedules.

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>
@sourcery-ai
Copy link

sourcery-ai bot commented Feb 16, 2026

Reviewer's Guide

Ensure delete_schedule validates schedule existence via get_schedules before deletion, returning a 404 error with a descriptive JSON body when the schedule is not found, and add/update tests to cover both successful and failure paths with proper mocking of get_schedules.

File-Level Changes

Change Details Files
Add existence check before deleting a schedule and return 404 with error payload when schedule ID does not exist on device.
  • Call get_schedules in delete_schedule handler to retrieve current schedules before attempting deletion.
  • Build a list of existing schedule_ids from the get_schedules response and compare against the requested schedule_id.
  • Return a JSON response with an error message and 404 status when the requested schedule_id is missing instead of calling delete_schedule.
  • Keep the existing success path by returning the serialized result of delete_schedule when the schedule exists.
app/webapp.py
Update and extend tests to cover the new 404 behavior and the new dependency on get_schedules in delete_schedule.
  • Extend successful delete_schedule test to mock get_schedules with a matching schedule_id and assert it is called before delete_schedule.
  • Add a new parametrized test ensuring delete_schedule returns 404 and does not call delete_schedule/serializer when the schedule_id is not found.
  • Update the erroneous delete_schedule test to mock get_schedules with a matching schedule_id and assert it is invoked prior to the failing delete_schedule call.
app/tests/test_web_app.py

Assessment against linked issues

Issue Objective Addressed Explanation
#723 Update the delete_schedule endpoint so that when a non-existing schedule ID is requested, it returns an error response (not 200) indicating that no such schedule exists.
#723 Add or update automated tests to cover the new behavior for deleting a non-existent schedule ID.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@TomerFi
Copy link
Owner Author

TomerFi commented Feb 16, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@auto-me-bot auto-me-bot bot added the status: needs review Pull request needs a review label Feb 16, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

@TomerFi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

The delete_schedule endpoint now validates that a schedule exists before attempting deletion. When a non-existent schedule ID is provided, the endpoint returns a 404 error instead of a 200 response. Tests are updated to verify this pre-check behavior and cover the 404 case.

Changes

Cohort / File(s) Summary
Delete Schedule Validation
app/webapp.py
Adds existence check in delete_schedule endpoint; fetches schedules and validates requested ID before deletion, returning 404 if not found.
Delete Schedule Tests
app/tests/test_web_app.py
Extends existing delete-schedule tests to verify get_schedules pre-check; adds new test to validate 404 response for non-existent schedule IDs.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A pre-check before the hop,
No phantom deletes drop,
404 when not found—
Proper errors all around! 🐾

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding 404 validation when deleting non-existent schedules, which is the core objective of the pull request.
Linked Issues check ✅ Passed The PR successfully implements all requirements from issue #723: validates schedule existence before deletion and returns 404 with appropriate error message when schedule does not exist.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #723 requirements: delete_schedule validation logic, test coverage for 404 case, and updates to existing tests.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into dev
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/delete-schedule-validate-existence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

Test Results

69 tests   69 ✅  1s ⏱️
 1 suites   0 💤
 1 files     0 ❌

Results for commit 7c0dfc6.

♻️ This comment has been updated with latest results.

@codecov
Copy link

codecov bot commented Feb 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.07%. Comparing base (9cc8700) to head (7c0dfc6).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #952      +/-   ##
==========================================
+ Coverage   96.02%   96.07%   +0.04%     
==========================================
  Files           1        1              
  Lines         327      331       +4     
==========================================
+ Hits          314      318       +4     
  Misses         13       13              
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In delete_schedule, consider defensive handling for cases where schedules_response.schedules is None or missing to avoid potential TypeError when iterating.
  • The test setup for stubbing get_schedules with a matching schedule ID is repeated in multiple tests; extracting a small helper or fixture could reduce duplication and make the intent clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `delete_schedule`, consider defensive handling for cases where `schedules_response.schedules` is `None` or missing to avoid potential `TypeError` when iterating.
- The test setup for stubbing `get_schedules` with a matching schedule ID is repeated in multiple tests; extracting a small helper or fixture could reduce duplication and make the intent clearer.

## Individual Comments

### Comment 1
<location> `app/webapp.py:263-264` </location>
<code_context>
     async with SwitcherApi(
         device_type, request.query[KEY_IP], request.query[KEY_ID], login_key
     ) as swapi:
+        schedules_response = await swapi.get_schedules()
+        existing_ids = [s.schedule_id for s in schedules_response.schedules]
+        if schedule_id not in existing_ids:
+            return web.json_response(
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Avoid extra round-trip by letting `delete_schedule` surface non-existence errors directly.

This adds a network call on every delete and still has a race (the schedule might be removed between `get_schedules` and `delete_schedule`). If the API can distinguish not-found, it’s better to call `delete_schedule` directly, catch that specific error, and map it to a 404 instead of pre-validating with `get_schedules`.

Suggested implementation:

```python
    async with SwitcherApi(
        device_type, request.query[KEY_IP], request.query[KEY_ID], login_key
    ) as swapi:
        try:
            delete_result = await swapi.delete_schedule(schedule_id)
        except SwitcherScheduleNotFoundError:
            return web.json_response(
                {"error": f"schedule {schedule_id} does not exist"},
                status=404,
            )

        return web.json_response(_serialize_object(delete_result))

```

1. Add an import for the not-found exception type at the top of `app/webapp.py`, matching the actual API surface, for example:
   `from switcher_api import SwitcherScheduleNotFoundError`
   or whatever specific exception the `SwitcherApi.delete_schedule` coroutine raises when a schedule does not exist.
2. If the API uses a more generic error type (e.g. `SwitcherApiError`) with an error code or message indicating not-found, replace `SwitcherScheduleNotFoundError` in the `except` block with the correct exception and add the appropriate conditional logic inside the `except` to distinguish not-found from other errors. Map only the not-found case to a 404, and re-raise or handle other errors consistently with the rest of the file.
</issue_to_address>

### Comment 2
<location> `app/tests/test_web_app.py:514-523` </location>
<code_context>
     api_client,
     api_uri,
 ):
+    # stub get_schedules to return a schedule with matching id
+    schedule_mock = Mock()
+    schedule_mock.schedule_id = "5"
+    schedules_response = Mock()
+    schedules_response.schedules = [schedule_mock]
+    api_get_schedules.return_value = schedules_response
     # stub api_delete_schedule to return mock response
     api_delete_schedule.return_value = response_mock
     # send delete request for delete_schedule endpoint
     response = await api_client.delete(api_uri, json={webapp.KEY_SCHEDULE: "5"})
     # verify mocks calling
     api_connect.assert_called_once()
+    api_get_schedules.assert_called_once()
     api_delete_schedule.assert_called_once_with("5")
     response_serializer.assert_called_once_with(response_mock)
</code_context>

<issue_to_address>
**suggestion (testing):** Add a test for when `get_schedules` itself fails (e.g. raises) to document and protect the new pre-check behavior.

Since `delete_schedule` now always calls `get_schedules` first, a failure in `get_schedules` will change the endpoint’s behavior (likely returning a 500). Please add a test that patches `SwitcherApi.get_schedules` with `side_effect=Exception("…")`, and asserts the resulting status code/body and that `delete_schedule` is not called. You can mirror `test_errorneous_delete_schedule_delete_request`, but raise from `get_schedules` instead of `delete_schedule`.

Suggested implementation:

```python
    response_serializer.assert_called_once_with(response_mock)
    api_disconnect.assert_called_once()
    assert_that(await response.json()).contains_entry(fake_serialized_data)


@patch("aioswitcher.api.SwitcherApi.delete_schedule")
@patch("aioswitcher.api.SwitcherApi.get_schedules")
async def test_errorneous_delete_schedule_get_schedules_failure(
    api_get_schedules,
    api_delete_schedule,
    response_serializer,
    api_client,
    api_uri,
):
    # stub get_schedules to raise an exception
    api_get_schedules.side_effect = Exception("get_schedules failure")
    # send delete request for delete_schedule endpoint
    response = await api_client.delete(api_uri, json={webapp.KEY_SCHEDULE: "5"})
    # verify mocks calling
    api_connect.assert_called_once()
    api_get_schedules.assert_called_once()
    api_delete_schedule.assert_not_called()
    api_disconnect.assert_called_once()
    # assert that the failure in get_schedules results in an error response
    assert response.status == 500


@mark.parametrize(
    "api_uri",
    [
        (delete_schedule_uri),
        (delete_schedule_uri2),
    ],

```

1. If your test suite uses a specific helper or constant for asserting error responses (for example, checking a JSON body like `fake_error_data` or using a custom assertion helper), mirror whatever is done in `test_errorneous_delete_schedule_delete_request` inside `test_errorneous_delete_schedule_get_schedules_failure` instead of the simple `assert response.status == 500`.
2. Ensure that `api_connect` and `api_disconnect` are available in this test scope as in the surrounding tests (likely via fixtures or additional `@patch` decorators); if they are provided differently, adjust the assertions to match the existing pattern.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@auto-me-bot auto-me-bot bot added status: review started Pull review in progress and removed status: needs review Pull request needs a review labels Feb 16, 2026
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>
@TomerFi
Copy link
Owner Author

TomerFi commented Feb 16, 2026

Hey @dmatik , would you mind taking a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: review started Pull review in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Return proper error when trying to delete non existing schedule

1 participant

Comments