firefighter-incident 0.0.14__py3-none-any.whl → 0.0.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- firefighter/_version.py +2 -2
- firefighter/api/serializers.py +9 -0
- firefighter/confluence/signals/incident_updated.py +2 -2
- firefighter/incidents/enums.py +22 -2
- firefighter/incidents/forms/closure_reason.py +45 -0
- firefighter/incidents/forms/unified_incident.py +406 -0
- firefighter/incidents/forms/update_status.py +87 -1
- firefighter/incidents/migrations/0027_add_closure_fields.py +40 -0
- firefighter/incidents/migrations/0028_add_closure_reason_constraint.py +33 -0
- firefighter/incidents/migrations/0029_add_custom_fields_to_incident.py +22 -0
- firefighter/incidents/models/incident.py +32 -5
- firefighter/incidents/static/css/main.min.css +1 -1
- firefighter/incidents/templates/layouts/partials/status_pill.html +1 -1
- firefighter/incidents/views/reports.py +3 -3
- firefighter/raid/apps.py +9 -26
- firefighter/raid/client.py +2 -2
- firefighter/raid/forms.py +75 -238
- firefighter/raid/signals/incident_created.py +38 -13
- firefighter/raid/signals/incident_updated.py +3 -2
- firefighter/slack/messages/slack_messages.py +19 -4
- firefighter/slack/rules.py +1 -1
- firefighter/slack/signals/create_incident_conversation.py +6 -0
- firefighter/slack/signals/incident_updated.py +7 -1
- firefighter/slack/views/modals/__init__.py +4 -0
- firefighter/slack/views/modals/base_modal/form_utils.py +63 -0
- firefighter/slack/views/modals/close.py +15 -2
- firefighter/slack/views/modals/closure_reason.py +193 -0
- firefighter/slack/views/modals/open.py +59 -12
- firefighter/slack/views/modals/opening/details/unified.py +203 -0
- firefighter/slack/views/modals/opening/set_details.py +3 -2
- firefighter/slack/views/modals/postmortem.py +10 -2
- firefighter/slack/views/modals/update_status.py +28 -2
- firefighter/slack/views/modals/utils.py +51 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/RECORD +61 -37
- firefighter_tests/test_incidents/test_enums.py +100 -0
- firefighter_tests/test_incidents/test_forms/conftest.py +179 -0
- firefighter_tests/test_incidents/test_forms/test_closure_reason.py +91 -0
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form.py +570 -0
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form_integration.py +581 -0
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form_p4_p5.py +410 -0
- firefighter_tests/test_incidents/test_forms/test_update_status_workflow.py +343 -0
- firefighter_tests/test_incidents/test_forms/test_workflow_transitions.py +167 -0
- firefighter_tests/test_incidents/test_models/test_incident_model.py +68 -0
- firefighter_tests/test_raid/conftest.py +154 -0
- firefighter_tests/test_raid/test_p1_p3_jira_fields.py +372 -0
- firefighter_tests/test_raid/test_raid_forms.py +10 -253
- firefighter_tests/test_raid/test_raid_signals.py +187 -0
- firefighter_tests/test_slack/messages/__init__.py +0 -0
- firefighter_tests/test_slack/messages/test_slack_messages.py +367 -0
- firefighter_tests/test_slack/views/modals/conftest.py +140 -0
- firefighter_tests/test_slack/views/modals/test_close.py +65 -3
- firefighter_tests/test_slack/views/modals/test_closure_reason_modal.py +138 -0
- firefighter_tests/test_slack/views/modals/test_form_utils_multiple_choice.py +249 -0
- firefighter_tests/test_slack/views/modals/test_open.py +146 -2
- firefighter_tests/test_slack/views/modals/test_opening_unified.py +421 -0
- firefighter_tests/test_slack/views/modals/test_update_status.py +327 -3
- firefighter_tests/test_slack/views/modals/test_utils.py +135 -0
- firefighter/raid/views/open_normal.py +0 -139
- firefighter/slack/views/modals/opening/details/critical.py +0 -88
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,7 +7,6 @@ from django.test import TestCase
|
|
|
7
7
|
from slack_sdk.errors import SlackApiError
|
|
8
8
|
|
|
9
9
|
from firefighter.incidents.factories import (
|
|
10
|
-
IncidentCategoryFactory,
|
|
11
10
|
IncidentFactory,
|
|
12
11
|
PriorityFactory,
|
|
13
12
|
UserFactory,
|
|
@@ -16,12 +15,7 @@ from firefighter.incidents.models.priority import Priority
|
|
|
16
15
|
from firefighter.jira_app.client import JiraAPIError, JiraUserNotFoundError
|
|
17
16
|
from firefighter.jira_app.models import JiraUser
|
|
18
17
|
from firefighter.raid.forms import (
|
|
19
|
-
CreateNormalCustomerIncidentForm,
|
|
20
|
-
CreateRaidDocumentationRequestIncidentForm,
|
|
21
|
-
CreateRaidFeatureRequestIncidentForm,
|
|
22
|
-
CreateRaidInternalIncidentForm,
|
|
23
18
|
PlatformChoices,
|
|
24
|
-
RaidCreateIncidentSellerForm,
|
|
25
19
|
alert_slack_comment_ticket,
|
|
26
20
|
alert_slack_new_jira_ticket,
|
|
27
21
|
alert_slack_update_ticket,
|
|
@@ -33,7 +27,7 @@ from firefighter.raid.forms import (
|
|
|
33
27
|
send_message_to_watchers,
|
|
34
28
|
set_jira_ticket_watchers_raid,
|
|
35
29
|
)
|
|
36
|
-
from firefighter.raid.models import
|
|
30
|
+
from firefighter.raid.models import JiraTicket
|
|
37
31
|
from firefighter.slack.models.conversation import Conversation
|
|
38
32
|
from firefighter.slack.models.user import SlackUser
|
|
39
33
|
|
|
@@ -70,252 +64,15 @@ class TestInitialPriority:
|
|
|
70
64
|
assert result == default_priority
|
|
71
65
|
|
|
72
66
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
self.priority = PriorityFactory(value=1)
|
|
83
|
-
self.incident_category = IncidentCategoryFactory()
|
|
84
|
-
self.feature_team = FeatureTeam.objects.create(
|
|
85
|
-
name="Test Team", jira_project_key="TEST"
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
@patch("firefighter.raid.forms.process_jira_issue")
|
|
89
|
-
@patch("firefighter.raid.forms.create_issue_customer")
|
|
90
|
-
@patch("firefighter.raid.forms.get_jira_user_from_user")
|
|
91
|
-
@patch("firefighter.raid.forms.get_business_impact")
|
|
92
|
-
def test_trigger_incident_workflow(
|
|
93
|
-
self, mock_get_business_impact, mock_get_jira_user, mock_create_issue, mock_process_jira_issue
|
|
94
|
-
):
|
|
95
|
-
"""Test trigger_incident_workflow method."""
|
|
96
|
-
# Given
|
|
97
|
-
mock_get_jira_user.return_value = self.jira_user
|
|
98
|
-
mock_create_issue.return_value = {"id": "10001", "key": "TEST-123"}
|
|
99
|
-
mock_get_business_impact.return_value = "High"
|
|
100
|
-
mock_process_jira_issue.return_value = None
|
|
101
|
-
|
|
102
|
-
form_data = {
|
|
103
|
-
"incident_category": self.incident_category,
|
|
104
|
-
"platform": "platform-FR",
|
|
105
|
-
"title": "Test incident title",
|
|
106
|
-
"description": "Test incident description",
|
|
107
|
-
"priority": self.priority,
|
|
108
|
-
"suggested_team_routing": self.feature_team,
|
|
109
|
-
"zendesk_ticket_id": "12345",
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
form = CreateNormalCustomerIncidentForm()
|
|
113
|
-
form.cleaned_data = form_data
|
|
114
|
-
|
|
115
|
-
# When
|
|
116
|
-
form.trigger_incident_workflow(self.user, {})
|
|
117
|
-
|
|
118
|
-
# Then
|
|
119
|
-
mock_get_jira_user.assert_called_once_with(self.user)
|
|
120
|
-
mock_create_issue.assert_called_once()
|
|
121
|
-
mock_process_jira_issue.assert_called_once()
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@pytest.mark.django_db
|
|
125
|
-
class TestCreateRaidDocumentationRequestIncidentForm:
|
|
126
|
-
"""Test CreateRaidDocumentationRequestIncidentForm functionality."""
|
|
127
|
-
|
|
128
|
-
def setup_method(self):
|
|
129
|
-
"""Set up test data."""
|
|
130
|
-
Priority.objects.all().delete() # Clear existing priorities
|
|
131
|
-
self.user = UserFactory()
|
|
132
|
-
self.jira_user = JiraUser.objects.create(id="jira-doc-123", user=self.user)
|
|
133
|
-
self.priority = PriorityFactory(value=20)
|
|
134
|
-
self.incident_category = IncidentCategoryFactory()
|
|
135
|
-
self.feature_team = FeatureTeam.objects.create(
|
|
136
|
-
name="Documentation Team", jira_project_key="DOC"
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
@patch("firefighter.raid.forms.process_jira_issue")
|
|
140
|
-
@patch("firefighter.raid.forms.create_issue_documentation_request")
|
|
141
|
-
@patch("firefighter.raid.forms.get_jira_user_from_user")
|
|
142
|
-
def test_trigger_incident_workflow(
|
|
143
|
-
self, mock_get_jira_user, mock_create_issue, mock_process_jira_issue
|
|
144
|
-
):
|
|
145
|
-
"""Test trigger_incident_workflow method."""
|
|
146
|
-
# Given
|
|
147
|
-
mock_get_jira_user.return_value = self.jira_user
|
|
148
|
-
mock_create_issue.return_value = {"id": "10002", "key": "TEST-124"}
|
|
149
|
-
mock_process_jira_issue.return_value = None
|
|
150
|
-
|
|
151
|
-
form_data = {
|
|
152
|
-
"incident_category": self.incident_category,
|
|
153
|
-
"platform": "platform-DE",
|
|
154
|
-
"title": "Documentation request title",
|
|
155
|
-
"description": "Documentation request description",
|
|
156
|
-
"priority": self.priority,
|
|
157
|
-
"suggested_team_routing": self.feature_team,
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
form = CreateRaidDocumentationRequestIncidentForm()
|
|
161
|
-
form.cleaned_data = form_data
|
|
162
|
-
|
|
163
|
-
# When
|
|
164
|
-
form.trigger_incident_workflow(self.user, {})
|
|
165
|
-
|
|
166
|
-
# Then
|
|
167
|
-
mock_get_jira_user.assert_called_once_with(self.user)
|
|
168
|
-
mock_create_issue.assert_called_once()
|
|
169
|
-
mock_process_jira_issue.assert_called_once()
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@pytest.mark.django_db
|
|
173
|
-
class TestCreateRaidFeatureRequestIncidentForm:
|
|
174
|
-
"""Test CreateRaidFeatureRequestIncidentForm functionality."""
|
|
175
|
-
|
|
176
|
-
def setup_method(self):
|
|
177
|
-
"""Set up test data."""
|
|
178
|
-
self.user = UserFactory()
|
|
179
|
-
self.jira_user = JiraUser.objects.create(id="jira-feat-123", user=self.user)
|
|
180
|
-
self.priority = PriorityFactory(value=30)
|
|
181
|
-
self.incident_category = IncidentCategoryFactory()
|
|
182
|
-
self.feature_team = FeatureTeam.objects.create(
|
|
183
|
-
name="Feature Team", jira_project_key="FEAT"
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
@patch("firefighter.raid.forms.process_jira_issue")
|
|
187
|
-
@patch("firefighter.raid.forms.create_issue_feature_request")
|
|
188
|
-
@patch("firefighter.raid.forms.get_jira_user_from_user")
|
|
189
|
-
def test_trigger_incident_workflow(
|
|
190
|
-
self, mock_get_jira_user, mock_create_issue, mock_process_jira_issue
|
|
191
|
-
):
|
|
192
|
-
"""Test trigger_incident_workflow method."""
|
|
193
|
-
# Given
|
|
194
|
-
mock_get_jira_user.return_value = self.jira_user
|
|
195
|
-
mock_create_issue.return_value = {"id": "10003", "key": "TEST-125"}
|
|
196
|
-
mock_process_jira_issue.return_value = None
|
|
197
|
-
|
|
198
|
-
form_data = {
|
|
199
|
-
"incident_category": self.incident_category,
|
|
200
|
-
"platform": "platform-IT",
|
|
201
|
-
"title": "Feature request title",
|
|
202
|
-
"description": "Feature request description",
|
|
203
|
-
"priority": self.priority,
|
|
204
|
-
"suggested_team_routing": self.feature_team,
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
form = CreateRaidFeatureRequestIncidentForm()
|
|
208
|
-
form.cleaned_data = form_data
|
|
209
|
-
|
|
210
|
-
# When
|
|
211
|
-
form.trigger_incident_workflow(self.user, {})
|
|
212
|
-
|
|
213
|
-
# Then
|
|
214
|
-
mock_get_jira_user.assert_called_once_with(self.user)
|
|
215
|
-
mock_create_issue.assert_called_once()
|
|
216
|
-
mock_process_jira_issue.assert_called_once()
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
@pytest.mark.django_db
|
|
220
|
-
class TestCreateRaidInternalIncidentForm:
|
|
221
|
-
"""Test CreateRaidInternalIncidentForm functionality."""
|
|
222
|
-
|
|
223
|
-
def setup_method(self):
|
|
224
|
-
"""Set up test data."""
|
|
225
|
-
self.user = UserFactory()
|
|
226
|
-
self.jira_user = JiraUser.objects.create(id="jira-int-123", user=self.user)
|
|
227
|
-
self.priority = PriorityFactory(value=40)
|
|
228
|
-
self.incident_category = IncidentCategoryFactory()
|
|
229
|
-
self.feature_team = FeatureTeam.objects.create(
|
|
230
|
-
name="Internal Team", jira_project_key="INT"
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
@patch("firefighter.raid.forms.process_jira_issue")
|
|
234
|
-
@patch("firefighter.raid.forms.create_issue_internal")
|
|
235
|
-
@patch("firefighter.raid.forms.get_jira_user_from_user")
|
|
236
|
-
@patch("firefighter.raid.forms.get_business_impact")
|
|
237
|
-
def test_trigger_incident_workflow(
|
|
238
|
-
self, mock_get_business_impact, mock_get_jira_user, mock_create_issue, mock_process_jira_issue
|
|
239
|
-
):
|
|
240
|
-
"""Test trigger_incident_workflow method."""
|
|
241
|
-
# Given
|
|
242
|
-
mock_get_jira_user.return_value = self.jira_user
|
|
243
|
-
mock_create_issue.return_value = {"id": "10004", "key": "TEST-126"}
|
|
244
|
-
mock_get_business_impact.return_value = "Medium"
|
|
245
|
-
mock_process_jira_issue.return_value = None
|
|
246
|
-
|
|
247
|
-
form_data = {
|
|
248
|
-
"incident_category": self.incident_category,
|
|
249
|
-
"platform": "platform-ES",
|
|
250
|
-
"title": "Internal incident title",
|
|
251
|
-
"description": "Internal incident description",
|
|
252
|
-
"priority": self.priority,
|
|
253
|
-
"suggested_team_routing": self.feature_team,
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
form = CreateRaidInternalIncidentForm()
|
|
257
|
-
form.cleaned_data = form_data
|
|
258
|
-
|
|
259
|
-
# When
|
|
260
|
-
form.trigger_incident_workflow(self.user, {})
|
|
261
|
-
|
|
262
|
-
# Then
|
|
263
|
-
mock_get_jira_user.assert_called_once_with(self.user)
|
|
264
|
-
mock_create_issue.assert_called_once()
|
|
265
|
-
mock_process_jira_issue.assert_called_once()
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
@pytest.mark.django_db
|
|
269
|
-
class TestRaidCreateIncidentSellerForm:
|
|
270
|
-
"""Test RaidCreateIncidentSellerForm functionality."""
|
|
271
|
-
|
|
272
|
-
def setup_method(self):
|
|
273
|
-
"""Set up test data."""
|
|
274
|
-
self.user = UserFactory()
|
|
275
|
-
self.jira_user = JiraUser.objects.create(id="jira-sell-123", user=self.user)
|
|
276
|
-
self.priority = PriorityFactory(value=50)
|
|
277
|
-
self.incident_category = IncidentCategoryFactory()
|
|
278
|
-
self.feature_team = FeatureTeam.objects.create(
|
|
279
|
-
name="Seller Team", jira_project_key="SELL"
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
@patch("firefighter.raid.forms.process_jira_issue")
|
|
283
|
-
@patch("firefighter.raid.forms.create_issue_seller")
|
|
284
|
-
@patch("firefighter.raid.forms.get_jira_user_from_user")
|
|
285
|
-
@patch("firefighter.raid.forms.get_business_impact")
|
|
286
|
-
def test_trigger_incident_workflow(
|
|
287
|
-
self, mock_get_business_impact, mock_get_jira_user, mock_create_issue, mock_process_jira_issue
|
|
288
|
-
):
|
|
289
|
-
"""Test trigger_incident_workflow method."""
|
|
290
|
-
# Given
|
|
291
|
-
mock_get_jira_user.return_value = self.jira_user
|
|
292
|
-
mock_create_issue.return_value = {"id": "10005", "key": "TEST-127"}
|
|
293
|
-
mock_get_business_impact.return_value = "Low"
|
|
294
|
-
mock_process_jira_issue.return_value = None
|
|
295
|
-
|
|
296
|
-
form_data = {
|
|
297
|
-
"incident_category": self.incident_category,
|
|
298
|
-
"platform": "platform-UK",
|
|
299
|
-
"title": "Seller incident title",
|
|
300
|
-
"description": "Seller incident description",
|
|
301
|
-
"priority": self.priority,
|
|
302
|
-
"suggested_team_routing": self.feature_team,
|
|
303
|
-
"seller_contract_id": "SELLER123",
|
|
304
|
-
"is_key_account": True,
|
|
305
|
-
"is_seller_in_golden_list": False,
|
|
306
|
-
"zoho_desk_ticket_id": "ZOHO456",
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
form = RaidCreateIncidentSellerForm()
|
|
310
|
-
form.cleaned_data = form_data
|
|
311
|
-
|
|
312
|
-
# When
|
|
313
|
-
form.trigger_incident_workflow(self.user, {})
|
|
314
|
-
|
|
315
|
-
# Then
|
|
316
|
-
mock_get_jira_user.assert_called_once_with(self.user)
|
|
317
|
-
mock_create_issue.assert_called_once()
|
|
318
|
-
mock_process_jira_issue.assert_called_once()
|
|
67
|
+
# NOTE: Form class tests (TestCreateNormalCustomerIncidentForm,
|
|
68
|
+
# TestCreateRaidDocumentationRequestIncidentForm, TestCreateRaidFeatureRequestIncidentForm,
|
|
69
|
+
# TestCreateRaidInternalIncidentForm, TestRaidCreateIncidentSellerForm) have been removed.
|
|
70
|
+
#
|
|
71
|
+
# These forms have been replaced by the unified incident form:
|
|
72
|
+
# firefighter.incidents.forms.unified_incident.UnifiedIncidentForm
|
|
73
|
+
#
|
|
74
|
+
# Tests for the unified form should be added in a new file:
|
|
75
|
+
# tests/test_incidents/test_forms/test_unified_incident.py
|
|
319
76
|
|
|
320
77
|
|
|
321
78
|
@pytest.mark.django_db
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Tests for RAID signal handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import Mock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from firefighter.incidents.enums import IncidentStatus
|
|
10
|
+
from firefighter.incidents.models.incident_update import IncidentUpdate
|
|
11
|
+
from firefighter.raid.signals.incident_updated import (
|
|
12
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.django_db
|
|
17
|
+
class TestIncidentUpdatedCloseJiraTicket:
|
|
18
|
+
"""Test that Jira tickets are closed when incidents reach terminal statuses."""
|
|
19
|
+
|
|
20
|
+
@patch("firefighter.raid.signals.incident_updated.client.close_issue")
|
|
21
|
+
def test_close_jira_ticket_when_status_changes_to_mitigated(
|
|
22
|
+
self, mock_close_issue: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Test that Jira ticket is closed when incident status changes to MITIGATED."""
|
|
25
|
+
user = user_factory()
|
|
26
|
+
incident = incident_factory(created_by=user)
|
|
27
|
+
jira_ticket = jira_ticket_factory(incident=incident)
|
|
28
|
+
incident.jira_ticket = jira_ticket
|
|
29
|
+
|
|
30
|
+
incident_update = IncidentUpdate(
|
|
31
|
+
incident=incident,
|
|
32
|
+
status=IncidentStatus.MITIGATED,
|
|
33
|
+
created_by=user,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Call the signal handler
|
|
37
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
38
|
+
sender="update_status",
|
|
39
|
+
incident=incident,
|
|
40
|
+
incident_update=incident_update,
|
|
41
|
+
updated_fields=["_status"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Verify close_issue was called
|
|
45
|
+
mock_close_issue.assert_called_once_with(issue_id=jira_ticket.id)
|
|
46
|
+
|
|
47
|
+
@patch("firefighter.raid.signals.incident_updated.client.close_issue")
|
|
48
|
+
def test_close_jira_ticket_when_status_changes_to_postmortem(
|
|
49
|
+
self, mock_close_issue: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Test that Jira ticket is closed when incident status changes to POST_MORTEM."""
|
|
52
|
+
user = user_factory()
|
|
53
|
+
incident = incident_factory(created_by=user)
|
|
54
|
+
jira_ticket = jira_ticket_factory(incident=incident)
|
|
55
|
+
incident.jira_ticket = jira_ticket
|
|
56
|
+
|
|
57
|
+
incident_update = IncidentUpdate(
|
|
58
|
+
incident=incident,
|
|
59
|
+
status=IncidentStatus.POST_MORTEM,
|
|
60
|
+
created_by=user,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Call the signal handler
|
|
64
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
65
|
+
sender="update_status",
|
|
66
|
+
incident=incident,
|
|
67
|
+
incident_update=incident_update,
|
|
68
|
+
updated_fields=["_status"],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Verify close_issue was called
|
|
72
|
+
mock_close_issue.assert_called_once_with(issue_id=jira_ticket.id)
|
|
73
|
+
|
|
74
|
+
@patch("firefighter.raid.signals.incident_updated.client.close_issue")
|
|
75
|
+
def test_close_jira_ticket_when_status_changes_to_closed(
|
|
76
|
+
self, mock_close_issue: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Test that Jira ticket is closed when incident status changes to CLOSED (direct close)."""
|
|
79
|
+
user = user_factory()
|
|
80
|
+
incident = incident_factory(created_by=user)
|
|
81
|
+
jira_ticket = jira_ticket_factory(incident=incident)
|
|
82
|
+
incident.jira_ticket = jira_ticket
|
|
83
|
+
|
|
84
|
+
incident_update = IncidentUpdate(
|
|
85
|
+
incident=incident,
|
|
86
|
+
status=IncidentStatus.CLOSED,
|
|
87
|
+
created_by=user,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Call the signal handler
|
|
91
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
92
|
+
sender="update_status",
|
|
93
|
+
incident=incident,
|
|
94
|
+
incident_update=incident_update,
|
|
95
|
+
updated_fields=["_status"],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Verify close_issue was called
|
|
99
|
+
mock_close_issue.assert_called_once_with(issue_id=jira_ticket.id)
|
|
100
|
+
|
|
101
|
+
@patch("firefighter.raid.signals.incident_updated.client.close_issue")
|
|
102
|
+
def test_do_not_close_jira_ticket_when_status_not_terminal(
|
|
103
|
+
self, mock_close_issue: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Test that Jira ticket is NOT closed for non-terminal statuses."""
|
|
106
|
+
user = user_factory()
|
|
107
|
+
incident = incident_factory(created_by=user)
|
|
108
|
+
jira_ticket = jira_ticket_factory(incident=incident)
|
|
109
|
+
incident.jira_ticket = jira_ticket
|
|
110
|
+
|
|
111
|
+
incident_update = IncidentUpdate(
|
|
112
|
+
incident=incident,
|
|
113
|
+
status=IncidentStatus.INVESTIGATING,
|
|
114
|
+
created_by=user,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Call the signal handler
|
|
118
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
119
|
+
sender="update_status",
|
|
120
|
+
incident=incident,
|
|
121
|
+
incident_update=incident_update,
|
|
122
|
+
updated_fields=["_status"],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Verify close_issue was NOT called
|
|
126
|
+
mock_close_issue.assert_not_called()
|
|
127
|
+
|
|
128
|
+
@patch("firefighter.raid.signals.incident_updated.client.close_issue")
|
|
129
|
+
def test_do_not_close_jira_ticket_when_status_not_updated(
|
|
130
|
+
self, mock_close_issue: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Test that Jira ticket is NOT closed when _status is not in updated_fields."""
|
|
133
|
+
user = user_factory()
|
|
134
|
+
incident = incident_factory(created_by=user)
|
|
135
|
+
jira_ticket = jira_ticket_factory(incident=incident)
|
|
136
|
+
incident.jira_ticket = jira_ticket
|
|
137
|
+
|
|
138
|
+
incident_update = IncidentUpdate(
|
|
139
|
+
incident=incident,
|
|
140
|
+
status=IncidentStatus.CLOSED,
|
|
141
|
+
created_by=user,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Call the signal handler with updated_fields that don't include _status
|
|
145
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
146
|
+
sender="update_status",
|
|
147
|
+
incident=incident,
|
|
148
|
+
incident_update=incident_update,
|
|
149
|
+
updated_fields=["priority_id"], # Not _status
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Verify close_issue was NOT called
|
|
153
|
+
mock_close_issue.assert_not_called()
|
|
154
|
+
|
|
155
|
+
@patch("firefighter.raid.signals.incident_updated.client.close_issue")
|
|
156
|
+
@patch("firefighter.raid.signals.incident_updated.logger")
|
|
157
|
+
def test_do_not_crash_when_jira_ticket_missing(
|
|
158
|
+
self,
|
|
159
|
+
mock_logger: Mock,
|
|
160
|
+
mock_close_issue: Mock,
|
|
161
|
+
incident_factory,
|
|
162
|
+
user_factory,
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Test that signal handler handles gracefully when Jira ticket is missing."""
|
|
165
|
+
user = user_factory()
|
|
166
|
+
incident = incident_factory(created_by=user)
|
|
167
|
+
# No jira_ticket attached to incident
|
|
168
|
+
|
|
169
|
+
incident_update = IncidentUpdate(
|
|
170
|
+
incident=incident,
|
|
171
|
+
status=IncidentStatus.CLOSED,
|
|
172
|
+
created_by=user,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Call the signal handler - should not crash
|
|
176
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
177
|
+
sender="update_status",
|
|
178
|
+
incident=incident,
|
|
179
|
+
incident_update=incident_update,
|
|
180
|
+
updated_fields=["_status"],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Verify close_issue was NOT called
|
|
184
|
+
mock_close_issue.assert_not_called()
|
|
185
|
+
|
|
186
|
+
# Verify a warning was logged
|
|
187
|
+
mock_logger.warning.assert_called_once()
|
|
File without changes
|