|
2 | 2 | import random |
3 | 3 | from datetime import datetime, timedelta |
4 | 4 | from time import time |
| 5 | +from typing import Any |
5 | 6 | from unittest import mock |
6 | 7 | from uuid import uuid4 |
7 | 8 |
|
8 | 9 | from snuba_sdk import Column, Condition, Entity, Function, Op, Query, Request |
9 | 10 |
|
10 | 11 | from sentry import deletions, nodestore |
11 | | -from sentry.deletions.defaults.group import ErrorEventsDeletionTask |
12 | 12 | from sentry.deletions.tasks.groups import delete_groups_for_project |
13 | 13 | from sentry.issues.grouptype import FeedbackGroup, GroupCategory |
14 | 14 | from sentry.issues.issue_occurrence import IssueOccurrence |
|
31 | 31 |
|
32 | 32 |
|
33 | 33 | class DeleteGroupTest(TestCase, SnubaTestCase): |
34 | | - def setUp(self) -> None: |
35 | | - super().setUp() |
36 | | - one_minute = before_now(minutes=1).isoformat() |
37 | | - group1_data = {"timestamp": one_minute, "fingerprint": ["group1"]} |
38 | | - group2_data = {"timestamp": one_minute, "fingerprint": ["group2"]} |
39 | | - |
40 | | - # Group 1 events |
41 | | - self.event = self.store_event( |
42 | | - data=group1_data | {"tags": {"foo": "bar"}}, project_id=self.project.id |
43 | | - ) |
44 | | - self.event_id = self.event.event_id |
45 | | - self.node_id = Event.generate_node_id(self.project.id, self.event_id) |
46 | | - group = self.event.group |
47 | | - self.event2 = self.store_event(data=group1_data, project_id=self.project.id) |
48 | | - self.node_id2 = Event.generate_node_id(self.project.id, self.event2.event_id) |
49 | 34 |
|
50 | | - # Group 2 event |
51 | | - self.keep_event = self.store_event(data=group2_data, project_id=self.project.id) |
52 | | - self.keep_node_id = Event.generate_node_id(self.project.id, self.keep_event.event_id) |
| 35 | + def _generate_data(self, fingerprint: str | None = None) -> dict[str, Any]: |
| 36 | + return { |
| 37 | + "fingerprint": [fingerprint or uuid4().hex], |
| 38 | + "timestamp": before_now(minutes=1).isoformat(), |
| 39 | + } |
| 40 | + |
| 41 | + def _get_node_id(self, event: Event) -> str: |
| 42 | + return Event.generate_node_id(event.project_id, event.event_id) |
53 | 43 |
|
| 44 | + def _create_event_with_many_group_children(self) -> Event: |
| 45 | + event = self.store_event( |
| 46 | + data=self._generate_data(fingerprint="group1"), |
| 47 | + project_id=self.project.id, |
| 48 | + ) |
54 | 49 | UserReport.objects.create( |
55 | | - group_id=group.id, project_id=self.event.project_id, name="With group id" |
| 50 | + group_id=event.group.id, project_id=event.project_id, name="With group id" |
56 | 51 | ) |
57 | 52 | UserReport.objects.create( |
58 | | - event_id=self.event.event_id, project_id=self.event.project_id, name="With event id" |
| 53 | + event_id=event.event_id, project_id=event.project_id, name="With event id" |
59 | 54 | ) |
60 | 55 | EventAttachment.objects.create( |
61 | | - event_id=self.event.event_id, |
62 | | - project_id=self.event.project_id, |
| 56 | + event_id=event.event_id, |
| 57 | + project_id=event.project_id, |
63 | 58 | name="hello.png", |
64 | 59 | content_type="image/png", |
65 | 60 | ) |
66 | | - GroupAssignee.objects.create(group=group, project=self.project, user_id=self.user.id) |
67 | | - GroupHash.objects.create(project=self.project, group=group, hash=uuid4().hex) |
68 | | - GroupMeta.objects.create(group=group, key="foo", value="bar") |
69 | | - GroupRedirect.objects.create(group_id=group.id, previous_group_id=1) |
| 61 | + GroupAssignee.objects.create(group=event.group, project=self.project, user_id=self.user.id) |
| 62 | + GroupHash.objects.create(project=self.project, group=event.group, hash=uuid4().hex) |
| 63 | + GroupMeta.objects.create(group=event.group, key="foo", value="bar") |
| 64 | + GroupRedirect.objects.create(group_id=event.group.id, previous_group_id=1) |
70 | 65 |
|
71 | | - def test_simple(self) -> None: |
72 | | - ErrorEventsDeletionTask.DEFAULT_CHUNK_SIZE = 1 # test chunking logic |
73 | | - group = self.event.group |
74 | | - assert nodestore.backend.get(self.node_id) |
75 | | - assert nodestore.backend.get(self.node_id2) |
76 | | - assert nodestore.backend.get(self.keep_node_id) |
| 66 | + return event |
| 67 | + |
| 68 | + def test_delete_group_with_many_related_children(self) -> None: |
| 69 | + event = self._create_event_with_many_group_children() |
| 70 | + assert event.group is not None |
| 71 | + |
| 72 | + event2 = self.store_event( |
| 73 | + data=self._generate_data(fingerprint="group1"), project_id=self.project.id |
| 74 | + ) |
| 75 | + assert event2.group is not None |
| 76 | + |
| 77 | + assert nodestore.backend.get(self._get_node_id(event)) |
| 78 | + assert nodestore.backend.get(self._get_node_id(event2)) |
77 | 79 |
|
78 | 80 | with self.tasks(): |
79 | 81 | delete_groups_for_project( |
80 | | - object_ids=[group.id], transaction_id=uuid4().hex, project_id=self.project.id |
| 82 | + object_ids=[event.group.id], transaction_id=uuid4().hex, project_id=self.project.id |
81 | 83 | ) |
82 | 84 |
|
83 | | - assert not UserReport.objects.filter(group_id=group.id).exists() |
84 | | - assert not UserReport.objects.filter(event_id=self.event.event_id).exists() |
85 | | - assert not EventAttachment.objects.filter(event_id=self.event.event_id).exists() |
| 85 | + assert not UserReport.objects.filter(group_id=event.group.id).exists() |
| 86 | + assert not UserReport.objects.filter(event_id=event.event_id).exists() |
| 87 | + assert not EventAttachment.objects.filter(event_id=event.event_id).exists() |
86 | 88 |
|
87 | | - assert not GroupRedirect.objects.filter(group_id=group.id).exists() |
88 | | - assert not GroupHash.objects.filter(group_id=group.id).exists() |
89 | | - assert not Group.objects.filter(id=group.id).exists() |
90 | | - assert not nodestore.backend.get(self.node_id) |
91 | | - assert not nodestore.backend.get(self.node_id2) |
92 | | - assert nodestore.backend.get(self.keep_node_id), "Does not remove from second group" |
93 | | - assert Group.objects.filter(id=self.keep_event.group_id).exists() |
| 89 | + assert not GroupRedirect.objects.filter(group_id=event.group.id).exists() |
| 90 | + assert not GroupHash.objects.filter(group_id=event.group.id).exists() |
| 91 | + assert not Group.objects.filter(id=event.group.id).exists() |
| 92 | + assert not nodestore.backend.get(self._get_node_id(event)) |
| 93 | + assert not nodestore.backend.get(self._get_node_id(event2)) |
94 | 94 |
|
95 | | - def test_simple_multiple_groups(self) -> None: |
| 95 | + def test_multiple_groups(self) -> None: |
| 96 | + event = self.store_event( |
| 97 | + data=self._generate_data(fingerprint="group1"), |
| 98 | + project_id=self.project.id, |
| 99 | + ) |
| 100 | + assert event.group is not None |
| 101 | + keep_event = self.store_event( |
| 102 | + data=self._generate_data(fingerprint="group2"), |
| 103 | + project_id=self.project.id, |
| 104 | + ) |
| 105 | + assert keep_event.group is not None |
96 | 106 | other_event = self.store_event( |
97 | | - data={"timestamp": before_now(minutes=1).isoformat(), "fingerprint": ["group3"]}, |
| 107 | + data=self._generate_data(fingerprint="group3"), |
98 | 108 | project_id=self.project.id, |
99 | 109 | ) |
100 | | - other_node_id = Event.generate_node_id(self.project.id, other_event.event_id) |
| 110 | + assert other_event.group is not None |
101 | 111 |
|
102 | | - group = self.event.group |
103 | 112 | with self.tasks(): |
104 | 113 | delete_groups_for_project( |
105 | | - object_ids=[group.id, other_event.group_id], |
| 114 | + object_ids=[event.group.id, other_event.group.id], |
106 | 115 | transaction_id=uuid4().hex, |
107 | 116 | project_id=self.project.id, |
108 | 117 | ) |
109 | 118 |
|
110 | | - assert not Group.objects.filter(id=group.id).exists() |
| 119 | + assert not Group.objects.filter(id=event.group.id).exists() |
111 | 120 | assert not Group.objects.filter(id=other_event.group_id).exists() |
112 | | - assert not nodestore.backend.get(self.node_id) |
113 | | - assert not nodestore.backend.get(other_node_id) |
| 121 | + assert not nodestore.backend.get(self._get_node_id(event)) |
| 122 | + assert not nodestore.backend.get(self._get_node_id(other_event)) |
114 | 123 |
|
115 | | - assert Group.objects.filter(id=self.keep_event.group_id).exists() |
116 | | - assert nodestore.backend.get(self.keep_node_id) |
| 124 | + assert Group.objects.filter(id=keep_event.group.id).exists() |
| 125 | + assert nodestore.backend.get(self._get_node_id(keep_event)) |
117 | 126 |
|
118 | 127 | def test_grouphistory_relation(self) -> None: |
119 | 128 | other_event = self.store_event( |
120 | | - data={"timestamp": before_now(minutes=1).isoformat(), "fingerprint": ["group3"]}, |
| 129 | + data=self._generate_data(fingerprint="other_group"), |
121 | 130 | project_id=self.project.id, |
122 | 131 | ) |
123 | 132 | other_group = other_event.group |
@@ -172,39 +181,45 @@ def test_delete_groups_delete_grouping_records_by_hash( |
172 | 181 | self, mock_delete_seer_grouping_records_by_hash_apply_async: mock.Mock |
173 | 182 | ) -> None: |
174 | 183 | self.project.update_option("sentry:similarity_backfill_completed", int(time())) |
| 184 | + event = self.store_event( |
| 185 | + data=self._generate_data(fingerprint="group1"), |
| 186 | + project_id=self.project.id, |
| 187 | + ) |
| 188 | + assert event.group |
| 189 | + keep_event = self.store_event( |
| 190 | + data=self._generate_data(fingerprint="group2"), |
| 191 | + project_id=self.project.id, |
| 192 | + ) |
| 193 | + assert keep_event.group |
175 | 194 | other_event = self.store_event( |
176 | | - data={ |
177 | | - "timestamp": before_now(minutes=1).isoformat(), |
178 | | - "fingerprint": ["group3"], |
179 | | - }, |
| 195 | + data=self._generate_data(fingerprint="group3"), |
180 | 196 | project_id=self.project.id, |
181 | 197 | ) |
182 | | - other_node_id = Event.generate_node_id(self.project.id, other_event.event_id) |
183 | 198 |
|
184 | 199 | hashes = [ |
185 | 200 | grouphash.hash |
186 | 201 | for grouphash in GroupHash.objects.filter( |
187 | | - project_id=self.project.id, group_id__in=[self.event.group.id, other_event.group_id] |
| 202 | + project_id=self.project.id, group_id__in=[event.group.id, other_event.group_id] |
188 | 203 | ) |
189 | 204 | ] |
190 | | - group = self.event.group |
| 205 | + |
191 | 206 | with self.tasks(): |
192 | 207 | delete_groups_for_project( |
193 | | - object_ids=[group.id, other_event.group_id], |
| 208 | + object_ids=[event.group.id, other_event.group_id], |
194 | 209 | transaction_id=uuid4().hex, |
195 | 210 | project_id=self.project.id, |
196 | 211 | ) |
197 | 212 |
|
198 | | - assert not Group.objects.filter(id=group.id).exists() |
| 213 | + assert not Group.objects.filter(id=event.group.id).exists() |
199 | 214 | assert not Group.objects.filter(id=other_event.group_id).exists() |
200 | | - assert not nodestore.backend.get(self.node_id) |
201 | | - assert not nodestore.backend.get(other_node_id) |
| 215 | + assert not nodestore.backend.get(self._get_node_id(event)) |
| 216 | + assert not nodestore.backend.get(self._get_node_id(other_event)) |
202 | 217 |
|
203 | | - assert Group.objects.filter(id=self.keep_event.group_id).exists() |
204 | | - assert nodestore.backend.get(self.keep_node_id) |
| 218 | + assert Group.objects.filter(id=keep_event.group_id).exists() |
| 219 | + assert nodestore.backend.get(self._get_node_id(keep_event)) |
205 | 220 |
|
206 | 221 | assert mock_delete_seer_grouping_records_by_hash_apply_async.call_args[1] == { |
207 | | - "args": [group.project.id, hashes, 0] |
| 222 | + "args": [event.project.id, hashes, 0] |
208 | 223 | } |
209 | 224 |
|
210 | 225 | @mock.patch( |
@@ -291,13 +306,14 @@ def test_delete_grouphashes_and_metadata(self) -> None: |
291 | 306 | project_id=self.project.id, |
292 | 307 | ) |
293 | 308 | grouphash_b = GroupHash.objects.get(hash=event_b.get_primary_hash()) |
294 | | - # assert grouphash_b.metadata is not None |
295 | 309 | assert grouphash_b.metadata is not None |
296 | 310 | metadata_b_id = grouphash_b.metadata.id |
297 | 311 |
|
298 | 312 | # Verify that seer matched event_b to event_a's hash |
299 | 313 | assert event_a.group_id == event_b.group_id |
| 314 | + |
300 | 315 | # Make sure it has not changed |
| 316 | + grouphash_a.refresh_from_db() |
301 | 317 | assert grouphash_a.metadata is not None |
302 | 318 | assert grouphash_a.metadata.seer_matched_grouphash is None |
303 | 319 | assert grouphash_b.metadata is not None |
|
0 commit comments