Skip to content

Commit 92572f0

Browse files
authored
ref(seer): Type deletion code + improve tests (#94818)
This adds stronger typing and we change the tests to assert that the task gets scheduled rather than checking for logs existence. Fixes [ID-759](https://linear.app/getsentry/issue/ID-759/strongly-type-seer-deletion-code).
1 parent 6a10dae commit 92572f0

File tree

4 files changed

+42
-35
lines changed

4 files changed

+42
-35
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ module = [
384384
"sentry.tasks.beacon",
385385
"sentry.tasks.codeowners.*",
386386
"sentry.tasks.commit_context",
387+
"sentry.tasks.delete_seer_grouping_records",
387388
"sentry.tasks.embeddings_grouping.backfill_seer_grouping_records_for_project",
388389
"sentry.tasks.on_demand_metrics",
389390
"sentry.tasks.reprocessing2",
@@ -541,6 +542,7 @@ module = [
541542
"tests.sentry.snuba.metrics.test_metrics_query_layer.*",
542543
"tests.sentry.tasks.integrations.*",
543544
"tests.sentry.tasks.test_code_owners",
545+
"tests.sentry.tasks.test_delete_seer_grouping_records",
544546
"tests.sentry.tasks.test_on_demand_metrics",
545547
"tests.sentry.types.*",
546548
"tests.sentry.types.test_actor",

src/sentry/seer/similarity/grouping_records.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
from __future__ import annotations
2+
13
import logging
4+
from collections.abc import Sequence
25
from typing import NotRequired, TypedDict
36

47
from django.conf import settings
@@ -122,7 +125,7 @@ def delete_project_grouping_records(
122125
return False
123126

124127

125-
def delete_grouping_records_by_hash(project_id: int, hashes: list[str]) -> bool:
128+
def delete_grouping_records_by_hash(project_id: int, hashes: Sequence[str]) -> bool:
126129
extra = {"project_id": project_id, "hashes": hashes}
127130
try:
128131
body = {"project_id": project_id, "hash_list": hashes}

src/sentry/tasks/delete_seer_grouping_records.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from collections.abc import Sequence
23
from typing import Any
34

45
from sentry import options
@@ -31,7 +32,7 @@
3132
)
3233
def delete_seer_grouping_records_by_hash(
3334
project_id: int,
34-
hashes: list[str],
35+
hashes: Sequence[str],
3536
last_deleted_index: int = 0,
3637
*args: Any,
3738
**kwargs: Any,
@@ -54,7 +55,7 @@ def delete_seer_grouping_records_by_hash(
5455

5556

5657
def call_delete_seer_grouping_records_by_hash(
57-
group_ids: list[int],
58+
group_ids: Sequence[int],
5859
) -> None:
5960
project = None
6061
if group_ids:
Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from time import time
2-
from unittest.mock import patch
2+
from unittest.mock import MagicMock, patch
33

44
from sentry.models.grouphash import GroupHash
55
from sentry.tasks.delete_seer_grouping_records import (
66
call_delete_seer_grouping_records_by_hash,
7-
call_seer_delete_project_grouping_records,
87
delete_seer_grouping_records_by_hash,
98
)
109
from sentry.testutils.cases import TestCase
@@ -19,9 +18,9 @@ class TestDeleteSeerGroupingRecordsByHash(TestCase):
1918
)
2019
def test_delete_seer_grouping_records_by_hash_batches(
2120
self,
22-
mock_delete_seer_grouping_records_by_hash_apply_async,
23-
mock_delete_grouping_records_by_hash,
24-
):
21+
mock_delete_seer_grouping_records_by_hash_apply_async: MagicMock,
22+
mock_delete_grouping_records_by_hash: MagicMock,
23+
) -> None:
2524
"""
2625
Test that when delete_seer_grouping_records_by_hash is called with over 20 hashes, it spawns
2726
another task with the end index of the previous batch.
@@ -33,50 +32,52 @@ def test_delete_seer_grouping_records_by_hash_batches(
3332
"args": [project_id, hashes, 100]
3433
}
3534

36-
@patch("sentry.tasks.delete_seer_grouping_records.logger")
37-
def test_call_delete_seer_grouping_records_by_hash_simple(self, mock_logger):
35+
@patch(
36+
"sentry.tasks.delete_seer_grouping_records.delete_seer_grouping_records_by_hash.apply_async"
37+
)
38+
def test_call_delete_seer_grouping_records_by_hash_simple(
39+
self, mock_apply_async: MagicMock
40+
) -> None:
41+
"""
42+
Test that call_delete_seer_grouping_records_by_hash correctly collects hashes
43+
and calls the deletion task with the expected parameters.
44+
"""
3845
self.project.update_option("sentry:similarity_backfill_completed", int(time()))
3946

40-
group_ids, hashes = [], []
47+
group_ids, expected_hashes = [], []
4148
for i in range(5):
4249
group = self.create_group(project=self.project)
4350
group_ids.append(group.id)
4451
group_hash = GroupHash.objects.create(
4552
project=self.project, hash=str(i) * 32, group_id=group.id
4653
)
47-
hashes.append(group_hash.hash)
54+
expected_hashes.append(group_hash.hash)
55+
4856
call_delete_seer_grouping_records_by_hash(group_ids)
49-
mock_logger.info.assert_called_with(
50-
"calling seer record deletion by hash",
51-
extra={"project_id": self.project.id, "hashes": hashes},
52-
)
5357

54-
@patch("sentry.tasks.delete_seer_grouping_records.delete_seer_grouping_records_by_hash")
55-
@patch("sentry.tasks.delete_seer_grouping_records.logger")
58+
# Verify that the task was called with the correct parameters
59+
mock_apply_async.assert_called_once_with(args=[self.project.id, expected_hashes, 0])
60+
61+
@patch(
62+
"sentry.tasks.delete_seer_grouping_records.delete_seer_grouping_records_by_hash.apply_async"
63+
)
5664
def test_call_delete_seer_grouping_records_by_hash_no_hashes(
57-
self, mock_logger, mock_delete_seer_grouping_records_by_hash
58-
):
65+
self, mock_apply_async: MagicMock
66+
) -> None:
5967
self.project.update_option("sentry:similarity_backfill_completed", int(time()))
6068

6169
group_ids = []
6270
for _ in range(5):
6371
group = self.create_group(project=self.project)
6472
group_ids.append(group.id)
6573
call_delete_seer_grouping_records_by_hash(group_ids)
66-
mock_logger.info.assert_called_with(
67-
"calling seer record deletion by hash",
68-
extra={"project_id": self.project.id, "hashes": []},
69-
)
70-
mock_delete_seer_grouping_records_by_hash.assert_not_called()
74+
mock_apply_async.assert_not_called()
7175

72-
@patch("sentry.tasks.delete_seer_grouping_records.logger")
73-
def test_call_delete_seer_grouping_records_by_hash_no_group_ids(self, mock_logger):
76+
@patch(
77+
"sentry.tasks.delete_seer_grouping_records.delete_seer_grouping_records_by_hash.apply_async"
78+
)
79+
def test_call_delete_seer_grouping_records_by_hash_no_group_ids(
80+
self, mock_apply_async: MagicMock
81+
) -> None:
7482
call_delete_seer_grouping_records_by_hash([])
75-
mock_logger.info.assert_not_called()
76-
77-
@patch("sentry.tasks.delete_seer_grouping_records.delete_project_grouping_records")
78-
def test_call_delete_project_and_delete_grouping_records(
79-
self, mock_delete_project_grouping_records
80-
):
81-
call_seer_delete_project_grouping_records(self.project.id)
82-
mock_delete_project_grouping_records.assert_called_once()
83+
mock_apply_async.assert_not_called()

0 commit comments

Comments
 (0)