From d156f71f60f3e9af2ad5427b95fbd99002b262f4 Mon Sep 17 00:00:00 2001 From: Deep Santoshwar Date: Tue, 28 Jul 2026 19:21:45 +0530 Subject: [PATCH] fix(Segments): make delete_segment task idempotent when already deleted The async delete_segment task fetches the segment by id before deleting it, but concurrent execution (e.g. a cascading feature or project delete racing with a prior run of the same task) can remove the row first, causing Segment.DoesNotExist to crash the task. --- api/segments/tasks.py | 7 +++- .../unit/segments/test_unit_segments_tasks.py | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 api/tests/unit/segments/test_unit_segments_tasks.py diff --git a/api/segments/tasks.py b/api/segments/tasks.py index f9cc34fd25aa..1bfcb02a14cd 100644 --- a/api/segments/tasks.py +++ b/api/segments/tasks.py @@ -11,7 +11,12 @@ @register_task_handler() def delete_segment(segment_id: int) -> None: - Segment.objects.get(pk=segment_id).delete() + try: + Segment.objects.get(pk=segment_id).delete() + except Segment.DoesNotExist: + # Already deleted, e.g. by a cascading delete of its parent segment + # or feature, or a previous run of this task. + return @register_task_handler() diff --git a/api/tests/unit/segments/test_unit_segments_tasks.py b/api/tests/unit/segments/test_unit_segments_tasks.py new file mode 100644 index 000000000000..9a6ada6edf33 --- /dev/null +++ b/api/tests/unit/segments/test_unit_segments_tasks.py @@ -0,0 +1,32 @@ +from projects.models import Project +from segments.models import Segment +from segments.tasks import delete_segment + + +def test_delete_segment__segment_exists__soft_deletes_segment( + project: Project, +) -> None: + # Given + segment = Segment.objects.create(name="Test Segment", project=project) + + # When + delete_segment(segment_id=segment.id) + + # Then + segment.refresh_from_db() + assert segment.deleted_at is not None + + +def test_delete_segment__segment_already_deleted__does_not_raise( + project: Project, +) -> None: + # Given + segment = Segment.objects.create(name="Test Segment", project=project) + segment_id = segment.id + segment.hard_delete() + + # When + delete_segment(segment_id=segment_id) # should not raise + + # Then + assert not Segment.objects.all_with_deleted().filter(id=segment_id).exists()