firefighter-incident 0.0.35__py3-none-any.whl → 0.0.37__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/incidents/models/incident.py +25 -13
- firefighter/incidents/models/incident_update.py +2 -8
- firefighter/incidents/static/css/main.min.css +1 -1
- firefighter/incidents/templates/pages/incident_detail.html +15 -2
- firefighter/incidents/views/views.py +28 -0
- firefighter/raid/client.py +5 -0
- firefighter/raid/serializers.py +311 -26
- firefighter/raid/signals/incident_updated.py +332 -17
- firefighter/raid/utils.py +13 -3
- {firefighter_incident-0.0.35.dist-info → firefighter_incident-0.0.37.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.35.dist-info → firefighter_incident-0.0.37.dist-info}/RECORD +20 -18
- firefighter_tests/test_incidents/test_views/test_incident_detail_jira_postmortem.py +63 -0
- firefighter_tests/test_raid/test_jira_status_sync.py +209 -0
- firefighter_tests/test_raid/test_raid_serializers.py +106 -46
- firefighter_tests/test_raid/test_raid_signals.py +43 -31
- firefighter_tests/test_slack/views/modals/test_update_status.py +28 -0
- {firefighter_incident-0.0.35.dist-info → firefighter_incident-0.0.37.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.35.dist-info → firefighter_incident-0.0.37.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.35.dist-info → firefighter_incident-0.0.37.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from unittest.mock import Mock, patch
|
|
5
|
+
from unittest.mock import ANY, Mock, patch
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
from django.test import override_settings
|
|
@@ -10,6 +10,7 @@ from django.test import override_settings
|
|
|
10
10
|
from firefighter.incidents.enums import IncidentStatus
|
|
11
11
|
from firefighter.incidents.models.incident_update import IncidentUpdate
|
|
12
12
|
from firefighter.raid.signals.incident_updated import (
|
|
13
|
+
IMPACT_TO_JIRA_STATUS_MAP,
|
|
13
14
|
incident_updated_close_ticket_when_mitigated_or_postmortem,
|
|
14
15
|
)
|
|
15
16
|
|
|
@@ -18,11 +19,16 @@ from firefighter.raid.signals.incident_updated import (
|
|
|
18
19
|
class TestIncidentUpdatedCloseJiraTicket:
|
|
19
20
|
"""Test that Jira tickets are closed when incidents reach terminal statuses."""
|
|
20
21
|
|
|
21
|
-
@patch("firefighter.raid.signals.incident_updated.client.
|
|
22
|
+
@patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
|
|
22
23
|
def test_close_jira_ticket_when_status_changes_to_mitigated(
|
|
23
|
-
self,
|
|
24
|
+
self,
|
|
25
|
+
mock_transition: Mock,
|
|
26
|
+
incident_factory,
|
|
27
|
+
user_factory,
|
|
28
|
+
jira_ticket_factory,
|
|
29
|
+
priority_factory,
|
|
24
30
|
) -> None:
|
|
25
|
-
"""
|
|
31
|
+
"""Impact → Jira: MITIGATED transitions to Reporter validation for P3+."""
|
|
26
32
|
user = user_factory()
|
|
27
33
|
# Create P3 priority (no postmortem needed)
|
|
28
34
|
p3_priority = priority_factory(value=3, name="P3", needs_postmortem=False)
|
|
@@ -44,13 +50,19 @@ class TestIncidentUpdatedCloseJiraTicket:
|
|
|
44
50
|
updated_fields=["_status"],
|
|
45
51
|
)
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
target = IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.MITIGATED]
|
|
54
|
+
mock_transition.assert_called_once_with(jira_ticket.id, target, ANY)
|
|
49
55
|
|
|
50
56
|
@override_settings(ENABLE_JIRA_POSTMORTEM=True)
|
|
51
|
-
@patch("firefighter.raid.signals.incident_updated.client.
|
|
57
|
+
@patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
|
|
52
58
|
def test_do_not_close_jira_ticket_when_p1_mitigated(
|
|
53
|
-
self,
|
|
59
|
+
self,
|
|
60
|
+
mock_transition: Mock,
|
|
61
|
+
incident_factory,
|
|
62
|
+
user_factory,
|
|
63
|
+
jira_ticket_factory,
|
|
64
|
+
priority_factory,
|
|
65
|
+
environment_factory,
|
|
54
66
|
) -> None:
|
|
55
67
|
"""Test that Jira ticket is NOT closed when P1 incident status changes to MITIGATED.
|
|
56
68
|
|
|
@@ -61,7 +73,9 @@ class TestIncidentUpdatedCloseJiraTicket:
|
|
|
61
73
|
# Create P1 priority (needs postmortem)
|
|
62
74
|
p1_priority = priority_factory(value=1, name="P1", needs_postmortem=True)
|
|
63
75
|
prd_env = environment_factory(value="PRD", name="Production")
|
|
64
|
-
incident = incident_factory(
|
|
76
|
+
incident = incident_factory(
|
|
77
|
+
created_by=user, priority=p1_priority, environment=prd_env
|
|
78
|
+
)
|
|
65
79
|
jira_ticket = jira_ticket_factory(incident=incident)
|
|
66
80
|
incident.jira_ticket = jira_ticket
|
|
67
81
|
|
|
@@ -79,12 +93,12 @@ class TestIncidentUpdatedCloseJiraTicket:
|
|
|
79
93
|
updated_fields=["_status"],
|
|
80
94
|
)
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
target = IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.MITIGATED]
|
|
97
|
+
mock_transition.assert_called_once_with(jira_ticket.id, target, ANY)
|
|
84
98
|
|
|
85
|
-
@patch("firefighter.raid.signals.incident_updated.client.
|
|
99
|
+
@patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
|
|
86
100
|
def test_do_not_close_jira_ticket_when_status_changes_to_postmortem(
|
|
87
|
-
self,
|
|
101
|
+
self, mock_transition: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
88
102
|
) -> None:
|
|
89
103
|
"""Test that Jira ticket is NOT closed when incident status changes to POST_MORTEM.
|
|
90
104
|
|
|
@@ -110,12 +124,12 @@ class TestIncidentUpdatedCloseJiraTicket:
|
|
|
110
124
|
updated_fields=["_status"],
|
|
111
125
|
)
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
target = IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.POST_MORTEM]
|
|
128
|
+
mock_transition.assert_called_once_with(jira_ticket.id, target, ANY)
|
|
115
129
|
|
|
116
|
-
@patch("firefighter.raid.signals.incident_updated.client.
|
|
130
|
+
@patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
|
|
117
131
|
def test_close_jira_ticket_when_status_changes_to_closed(
|
|
118
|
-
self,
|
|
132
|
+
self, mock_transition: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
119
133
|
) -> None:
|
|
120
134
|
"""Test that Jira ticket is closed when incident status changes to CLOSED (direct close)."""
|
|
121
135
|
user = user_factory()
|
|
@@ -137,12 +151,11 @@ class TestIncidentUpdatedCloseJiraTicket:
|
|
|
137
151
|
updated_fields=["_status"],
|
|
138
152
|
)
|
|
139
153
|
|
|
140
|
-
|
|
141
|
-
mock_close_issue.assert_called_once_with(issue_id=jira_ticket.id)
|
|
154
|
+
mock_transition.assert_called_once_with(jira_ticket.id, "Closed", ANY)
|
|
142
155
|
|
|
143
|
-
@patch("firefighter.raid.signals.incident_updated.client.
|
|
156
|
+
@patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
|
|
144
157
|
def test_do_not_close_jira_ticket_when_status_not_terminal(
|
|
145
|
-
self,
|
|
158
|
+
self, mock_transition: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
146
159
|
) -> None:
|
|
147
160
|
"""Test that Jira ticket is NOT closed for non-terminal statuses."""
|
|
148
161
|
user = user_factory()
|
|
@@ -164,12 +177,12 @@ class TestIncidentUpdatedCloseJiraTicket:
|
|
|
164
177
|
updated_fields=["_status"],
|
|
165
178
|
)
|
|
166
179
|
|
|
167
|
-
|
|
168
|
-
|
|
180
|
+
target = IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.INVESTIGATING]
|
|
181
|
+
mock_transition.assert_called_once_with(jira_ticket.id, target, ANY)
|
|
169
182
|
|
|
170
|
-
@patch("firefighter.raid.signals.incident_updated.client.
|
|
183
|
+
@patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
|
|
171
184
|
def test_do_not_close_jira_ticket_when_status_not_updated(
|
|
172
|
-
self,
|
|
185
|
+
self, mock_transition: Mock, incident_factory, user_factory, jira_ticket_factory
|
|
173
186
|
) -> None:
|
|
174
187
|
"""Test that Jira ticket is NOT closed when _status is not in updated_fields."""
|
|
175
188
|
user = user_factory()
|
|
@@ -191,15 +204,14 @@ class TestIncidentUpdatedCloseJiraTicket:
|
|
|
191
204
|
updated_fields=["priority_id"], # Not _status
|
|
192
205
|
)
|
|
193
206
|
|
|
194
|
-
|
|
195
|
-
mock_close_issue.assert_not_called()
|
|
207
|
+
mock_transition.assert_not_called()
|
|
196
208
|
|
|
197
|
-
@patch("firefighter.raid.signals.incident_updated.client.
|
|
209
|
+
@patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
|
|
198
210
|
@patch("firefighter.raid.signals.incident_updated.logger")
|
|
199
211
|
def test_do_not_crash_when_jira_ticket_missing(
|
|
200
212
|
self,
|
|
201
213
|
mock_logger: Mock,
|
|
202
|
-
|
|
214
|
+
mock_transition: Mock,
|
|
203
215
|
incident_factory,
|
|
204
216
|
user_factory,
|
|
205
217
|
) -> None:
|
|
@@ -222,8 +234,8 @@ class TestIncidentUpdatedCloseJiraTicket:
|
|
|
222
234
|
updated_fields=["_status"],
|
|
223
235
|
)
|
|
224
236
|
|
|
225
|
-
# Verify
|
|
226
|
-
|
|
237
|
+
# Verify transition was NOT called
|
|
238
|
+
mock_transition.assert_not_called()
|
|
227
239
|
|
|
228
240
|
# Verify a warning was logged
|
|
229
241
|
mock_logger.warning.assert_called_once()
|
|
@@ -10,6 +10,8 @@ from pytest_mock import MockerFixture
|
|
|
10
10
|
from firefighter.incidents.enums import IncidentStatus
|
|
11
11
|
from firefighter.incidents.factories import IncidentFactory, UserFactory
|
|
12
12
|
from firefighter.incidents.models import Incident, MilestoneType
|
|
13
|
+
from firefighter.incidents.models.environment import Environment
|
|
14
|
+
from firefighter.incidents.models.priority import Priority
|
|
13
15
|
from firefighter.slack.views import UpdateStatusModal
|
|
14
16
|
|
|
15
17
|
logger = logging.getLogger(__name__)
|
|
@@ -223,9 +225,35 @@ class TestUpdateStatusModal:
|
|
|
223
225
|
|
|
224
226
|
# Create a P1/P2 incident in POST_MORTEM status
|
|
225
227
|
# This incident will have missing milestones (detected, started)
|
|
228
|
+
priority = (
|
|
229
|
+
Priority.objects.filter(value=1).first()
|
|
230
|
+
or Priority.objects.create(
|
|
231
|
+
value=1,
|
|
232
|
+
name="P1",
|
|
233
|
+
order=1,
|
|
234
|
+
emoji="🔴",
|
|
235
|
+
needs_postmortem=True,
|
|
236
|
+
enabled_create=True,
|
|
237
|
+
enabled_update=True,
|
|
238
|
+
default=False,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
environment = (
|
|
242
|
+
Environment.objects.filter(value="PRD").first()
|
|
243
|
+
or Environment.objects.create(
|
|
244
|
+
value="PRD",
|
|
245
|
+
name="Production",
|
|
246
|
+
description="Production",
|
|
247
|
+
order=1,
|
|
248
|
+
default=False,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
|
|
226
252
|
incident = IncidentFactory.create(
|
|
227
253
|
_status=IncidentStatus.POST_MORTEM,
|
|
228
254
|
created_by=user,
|
|
255
|
+
priority=priority,
|
|
256
|
+
environment=environment,
|
|
229
257
|
)
|
|
230
258
|
|
|
231
259
|
# Verify that can_be_closed returns False due to missing milestones
|
|
File without changes
|
{firefighter_incident-0.0.35.dist-info → firefighter_incident-0.0.37.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{firefighter_incident-0.0.35.dist-info → firefighter_incident-0.0.37.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|