firefighter-incident 0.0.36__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/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.36.dist-info → firefighter_incident-0.0.37.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.36.dist-info → firefighter_incident-0.0.37.dist-info}/RECORD +17 -16
- 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.36.dist-info → firefighter_incident-0.0.37.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.36.dist-info → firefighter_incident-0.0.37.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.36.dist-info → firefighter_incident-0.0.37.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from django.test.utils import override_settings
|
|
8
|
+
|
|
9
|
+
from firefighter.incidents.enums import IncidentStatus
|
|
10
|
+
from firefighter.incidents.models.environment import Environment
|
|
11
|
+
from firefighter.incidents.models.group import Group
|
|
12
|
+
from firefighter.incidents.models.incident import Incident
|
|
13
|
+
from firefighter.incidents.models.incident_category import IncidentCategory
|
|
14
|
+
from firefighter.incidents.models.priority import Priority
|
|
15
|
+
from firefighter.incidents.models.user import User
|
|
16
|
+
from firefighter.raid.serializers import JiraWebhookUpdateSerializer
|
|
17
|
+
from firefighter.raid.signals.incident_updated import (
|
|
18
|
+
IMPACT_TO_JIRA_STATUS_MAP,
|
|
19
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture(autouse=True)
|
|
24
|
+
def patch_alert_slack_update_ticket(mocker: pytest.MockFixture) -> MagicMock:
|
|
25
|
+
"""Avoid real Slack/Jira watcher calls during webhook status tests."""
|
|
26
|
+
return mocker.patch(
|
|
27
|
+
"firefighter.raid.serializers.alert_slack_update_ticket", return_value=True
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.django_db
|
|
32
|
+
@override_settings(MIGRATION_MODULES={"incidents": None})
|
|
33
|
+
def test_jira_webhook_status_maps_and_sets_event_type(mocker) -> None:
|
|
34
|
+
"""Jira → Impact: status change maps and sets event_type=jira_status_sync."""
|
|
35
|
+
creator = User.objects.create(
|
|
36
|
+
email="creator@example.com",
|
|
37
|
+
username="creator",
|
|
38
|
+
first_name="c",
|
|
39
|
+
last_name="u",
|
|
40
|
+
)
|
|
41
|
+
category = IncidentCategory.objects.first()
|
|
42
|
+
if category is None:
|
|
43
|
+
group = Group.objects.first() or Group.objects.create(
|
|
44
|
+
name="Default Group", description="grp", order=0
|
|
45
|
+
)
|
|
46
|
+
category = IncidentCategory.objects.create(
|
|
47
|
+
name="Default Category", description="", order=0, group=group
|
|
48
|
+
)
|
|
49
|
+
incident = Incident.objects.create(
|
|
50
|
+
title="inc",
|
|
51
|
+
description="desc",
|
|
52
|
+
priority=Priority.get_default(), # required by model
|
|
53
|
+
incident_category=category,
|
|
54
|
+
environment=Environment.get_default(),
|
|
55
|
+
created_by=creator,
|
|
56
|
+
)
|
|
57
|
+
incident.create_incident_update = MagicMock()
|
|
58
|
+
|
|
59
|
+
webhook_payload = {
|
|
60
|
+
"issue": {"id": "123", "key": "IMPACT-1"},
|
|
61
|
+
"changelog": {
|
|
62
|
+
"items": [
|
|
63
|
+
{
|
|
64
|
+
"field": "status",
|
|
65
|
+
"fromString": "In Progress",
|
|
66
|
+
"toString": "Reporter validation",
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"user": {"displayName": "jira-user"},
|
|
71
|
+
"webhookEvent": "jira:issue_updated",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
mock_ticket = SimpleNamespace(incident=incident)
|
|
75
|
+
fake_qs = SimpleNamespace(get=lambda **kwargs: mock_ticket)
|
|
76
|
+
with patch(
|
|
77
|
+
"firefighter.raid.serializers.JiraTicket.objects.select_related",
|
|
78
|
+
return_value=fake_qs,
|
|
79
|
+
):
|
|
80
|
+
serializer = JiraWebhookUpdateSerializer(data=webhook_payload)
|
|
81
|
+
assert serializer.is_valid(), serializer.errors
|
|
82
|
+
serializer.save()
|
|
83
|
+
|
|
84
|
+
incident.create_incident_update.assert_called_once()
|
|
85
|
+
kwargs = incident.create_incident_update.call_args.kwargs
|
|
86
|
+
assert kwargs["status"] == IncidentStatus.MITIGATED
|
|
87
|
+
assert kwargs["event_type"] == "jira_status_sync"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.django_db
|
|
91
|
+
def test_signal_skips_when_event_from_jira(mocker) -> None:
|
|
92
|
+
"""Impact → Jira: skip close/transition when event_type=jira_status_sync."""
|
|
93
|
+
incident = SimpleNamespace(jira_ticket=SimpleNamespace(id="123"))
|
|
94
|
+
incident_update = SimpleNamespace(
|
|
95
|
+
status=IncidentStatus.CLOSED, event_type="jira_status_sync"
|
|
96
|
+
)
|
|
97
|
+
mock_close = mocker.patch(
|
|
98
|
+
"firefighter.raid.signals.incident_updated.client.close_issue"
|
|
99
|
+
)
|
|
100
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
101
|
+
sender="update_status",
|
|
102
|
+
incident=incident,
|
|
103
|
+
incident_update=incident_update,
|
|
104
|
+
updated_fields=["_status"],
|
|
105
|
+
)
|
|
106
|
+
mock_close.assert_not_called()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.django_db
|
|
110
|
+
def test_signal_transitions_non_close_status(mocker) -> None:
|
|
111
|
+
"""Impact → Jira: transitions mapped statuses to Jira."""
|
|
112
|
+
incident = SimpleNamespace(
|
|
113
|
+
jira_ticket=SimpleNamespace(id="123"),
|
|
114
|
+
needs_postmortem=False,
|
|
115
|
+
)
|
|
116
|
+
incident_update = SimpleNamespace(status=IncidentStatus.MITIGATING, event_type=None)
|
|
117
|
+
mock_transition = mocker.patch(
|
|
118
|
+
"firefighter.raid.signals.incident_updated.client.transition_issue_auto"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
122
|
+
sender="update_status",
|
|
123
|
+
incident=incident,
|
|
124
|
+
incident_update=incident_update,
|
|
125
|
+
updated_fields=["_status"],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Mitigating triggers two steps: Pending resolution then in progress
|
|
129
|
+
mock_transition.assert_has_calls(
|
|
130
|
+
[
|
|
131
|
+
mocker.call("123", "Pending resolution", mocker.ANY),
|
|
132
|
+
mocker.call("123", "in progress", mocker.ANY),
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@pytest.mark.django_db
|
|
138
|
+
def test_signal_transitions_mitigated_closes_for_p3_plus(mocker) -> None:
|
|
139
|
+
"""Impact → Jira: MITIGATED transitions to Reporter validation for P3+ (no postmortem)."""
|
|
140
|
+
incident = SimpleNamespace(
|
|
141
|
+
jira_ticket=SimpleNamespace(id="123"),
|
|
142
|
+
needs_postmortem=False,
|
|
143
|
+
)
|
|
144
|
+
incident_update = SimpleNamespace(status=IncidentStatus.MITIGATED, event_type=None)
|
|
145
|
+
mock_transition = mocker.patch(
|
|
146
|
+
"firefighter.raid.signals.incident_updated.client.transition_issue_auto"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
150
|
+
sender="update_status",
|
|
151
|
+
incident=incident,
|
|
152
|
+
incident_update=incident_update,
|
|
153
|
+
updated_fields=["_status"],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
mock_transition.assert_called_once_with(
|
|
157
|
+
"123", IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.MITIGATED], mocker.ANY
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@pytest.mark.django_db
|
|
162
|
+
def test_signal_mitigated_needs_postmortem_does_not_close(mocker) -> None:
|
|
163
|
+
"""Impact → Jira: MITIGATED does not close Jira when postmortem is needed."""
|
|
164
|
+
incident = SimpleNamespace(
|
|
165
|
+
jira_ticket=SimpleNamespace(id="123"),
|
|
166
|
+
needs_postmortem=True,
|
|
167
|
+
)
|
|
168
|
+
incident_update = SimpleNamespace(status=IncidentStatus.MITIGATED, event_type=None)
|
|
169
|
+
mock_transition = mocker.patch(
|
|
170
|
+
"firefighter.raid.signals.incident_updated.client.transition_issue_auto"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
174
|
+
sender="update_status",
|
|
175
|
+
incident=incident,
|
|
176
|
+
incident_update=incident_update,
|
|
177
|
+
updated_fields=["_status"],
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
mock_transition.assert_called_once_with(
|
|
181
|
+
"123", IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.MITIGATED], mocker.ANY
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@pytest.mark.django_db
|
|
186
|
+
def test_signal_mitigating_runs_two_transitions(mocker) -> None:
|
|
187
|
+
"""Impact → Jira: MITIGATING triggers Pending resolution then in progress."""
|
|
188
|
+
incident = SimpleNamespace(
|
|
189
|
+
jira_ticket=SimpleNamespace(id="123"),
|
|
190
|
+
needs_postmortem=False,
|
|
191
|
+
)
|
|
192
|
+
incident_update = SimpleNamespace(status=IncidentStatus.MITIGATING, event_type=None)
|
|
193
|
+
mock_transition = mocker.patch(
|
|
194
|
+
"firefighter.raid.signals.incident_updated.client.transition_issue_auto"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
incident_updated_close_ticket_when_mitigated_or_postmortem(
|
|
198
|
+
sender="update_status",
|
|
199
|
+
incident=incident,
|
|
200
|
+
incident_update=incident_update,
|
|
201
|
+
updated_fields=["_status"],
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
mock_transition.assert_has_calls(
|
|
205
|
+
[
|
|
206
|
+
mocker.call("123", "Pending resolution", mocker.ANY),
|
|
207
|
+
mocker.call("123", "in progress", mocker.ANY),
|
|
208
|
+
]
|
|
209
|
+
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Test raid serializers, especially uncovered functionality."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
from unittest.mock import Mock, patch
|
|
@@ -101,7 +102,9 @@ class TestGetReporterUserFromEmail(TestCase):
|
|
|
101
102
|
jira_user = JiraUser.objects.create(id="jira-123", user=self.user)
|
|
102
103
|
mock_jira_client.get_jira_user_from_user.return_value = jira_user
|
|
103
104
|
|
|
104
|
-
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
105
|
+
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
106
|
+
"test@manomano.com"
|
|
107
|
+
)
|
|
105
108
|
|
|
106
109
|
assert reporter_user == self.user
|
|
107
110
|
assert reporter == jira_user
|
|
@@ -110,16 +113,22 @@ class TestGetReporterUserFromEmail(TestCase):
|
|
|
110
113
|
|
|
111
114
|
@patch("firefighter.raid.serializers.jira_client")
|
|
112
115
|
@patch("firefighter.raid.serializers.SlackUser")
|
|
113
|
-
def test_user_not_found_with_slack_fallback(
|
|
116
|
+
def test_user_not_found_with_slack_fallback(
|
|
117
|
+
self, mock_slack_user, mock_jira_client
|
|
118
|
+
):
|
|
114
119
|
"""Test when user is not found but Slack user exists."""
|
|
115
120
|
# Setup mocks
|
|
116
121
|
slack_user = SlackUserFactory()
|
|
117
122
|
mock_slack_user.objects.upsert_by_email.return_value = slack_user.user
|
|
118
123
|
|
|
119
|
-
default_jira_user = JiraUser.objects.create(
|
|
124
|
+
default_jira_user = JiraUser.objects.create(
|
|
125
|
+
id="default-123", user=UserFactory()
|
|
126
|
+
)
|
|
120
127
|
mock_jira_client.get_jira_user_from_jira_id.return_value = default_jira_user
|
|
121
128
|
|
|
122
|
-
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
129
|
+
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
130
|
+
"nonexistent@example.com"
|
|
131
|
+
)
|
|
123
132
|
|
|
124
133
|
assert reporter_user == slack_user.user
|
|
125
134
|
assert reporter == default_jira_user
|
|
@@ -127,31 +136,48 @@ class TestGetReporterUserFromEmail(TestCase):
|
|
|
127
136
|
|
|
128
137
|
@patch("firefighter.raid.serializers.jira_client")
|
|
129
138
|
@patch("firefighter.raid.serializers.SlackUser")
|
|
130
|
-
@patch(
|
|
131
|
-
|
|
139
|
+
@patch(
|
|
140
|
+
"firefighter.raid.serializers.JIRA_USER_IDS",
|
|
141
|
+
{"example.com": "domain-specific-123"},
|
|
142
|
+
)
|
|
143
|
+
def test_user_not_found_with_domain_specific_jira_user(
|
|
144
|
+
self, mock_slack_user, mock_jira_client
|
|
145
|
+
):
|
|
132
146
|
"""Test when user is not found but domain has specific JIRA user."""
|
|
133
147
|
# Setup mocks
|
|
134
148
|
mock_slack_user.objects.upsert_by_email.return_value = None
|
|
135
149
|
|
|
136
|
-
domain_jira_user = JiraUser.objects.create(
|
|
150
|
+
domain_jira_user = JiraUser.objects.create(
|
|
151
|
+
id="domain-specific-123", user=UserFactory()
|
|
152
|
+
)
|
|
137
153
|
mock_jira_client.get_jira_user_from_jira_id.return_value = domain_jira_user
|
|
138
154
|
|
|
139
|
-
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
155
|
+
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
156
|
+
"test@example.com"
|
|
157
|
+
)
|
|
140
158
|
|
|
141
159
|
assert reporter_user == domain_jira_user.user
|
|
142
160
|
assert reporter == domain_jira_user
|
|
143
161
|
assert user_domain == "example.com"
|
|
144
|
-
mock_jira_client.get_jira_user_from_jira_id.assert_called_once_with(
|
|
162
|
+
mock_jira_client.get_jira_user_from_jira_id.assert_called_once_with(
|
|
163
|
+
"domain-specific-123"
|
|
164
|
+
)
|
|
145
165
|
|
|
146
166
|
@patch("firefighter.raid.serializers.jira_client")
|
|
147
167
|
def test_jira_user_not_found_exception(self, mock_jira_client):
|
|
148
168
|
"""Test when JiraUserNotFoundError is raised."""
|
|
149
|
-
mock_jira_client.get_jira_user_from_user.side_effect = JiraUserNotFoundError(
|
|
169
|
+
mock_jira_client.get_jira_user_from_user.side_effect = JiraUserNotFoundError(
|
|
170
|
+
"User not found"
|
|
171
|
+
)
|
|
150
172
|
|
|
151
|
-
default_jira_user = JiraUser.objects.create(
|
|
173
|
+
default_jira_user = JiraUser.objects.create(
|
|
174
|
+
id="default-123", user=UserFactory()
|
|
175
|
+
)
|
|
152
176
|
mock_jira_client.get_jira_user_from_jira_id.return_value = default_jira_user
|
|
153
177
|
|
|
154
|
-
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
178
|
+
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
179
|
+
"test@manomano.com"
|
|
180
|
+
)
|
|
155
181
|
|
|
156
182
|
assert reporter_user == self.user
|
|
157
183
|
assert reporter == default_jira_user
|
|
@@ -184,7 +210,9 @@ class TestLandbotIssueRequestSerializer(TestCase):
|
|
|
184
210
|
@patch("firefighter.raid.serializers.alert_slack_new_jira_ticket")
|
|
185
211
|
@patch("firefighter.raid.serializers.get_reporter_user_from_email")
|
|
186
212
|
@patch("firefighter.raid.serializers.jira_client")
|
|
187
|
-
def test_create_with_attachments_error(
|
|
213
|
+
def test_create_with_attachments_error(
|
|
214
|
+
self, mock_jira_client, mock_get_reporter, mock_alert_slack
|
|
215
|
+
):
|
|
188
216
|
"""Test create method when JIRA returns no issue ID."""
|
|
189
217
|
# Setup mocks
|
|
190
218
|
user = UserFactory()
|
|
@@ -220,7 +248,9 @@ class TestLandbotIssueRequestSerializer(TestCase):
|
|
|
220
248
|
@patch("firefighter.raid.serializers.alert_slack_new_jira_ticket")
|
|
221
249
|
@patch("firefighter.raid.serializers.get_reporter_user_from_email")
|
|
222
250
|
@patch("firefighter.raid.serializers.jira_client")
|
|
223
|
-
def test_create_with_attachments(
|
|
251
|
+
def test_create_with_attachments(
|
|
252
|
+
self, mock_jira_client, mock_get_reporter, mock_alert_slack
|
|
253
|
+
):
|
|
224
254
|
"""Test create method with attachments."""
|
|
225
255
|
# Setup mocks
|
|
226
256
|
user = UserFactory()
|
|
@@ -267,7 +297,9 @@ class TestLandbotIssueRequestSerializer(TestCase):
|
|
|
267
297
|
@patch("firefighter.raid.serializers.alert_slack_new_jira_ticket")
|
|
268
298
|
@patch("firefighter.raid.serializers.get_reporter_user_from_email")
|
|
269
299
|
@patch("firefighter.raid.serializers.jira_client")
|
|
270
|
-
def test_create_external_user_description(
|
|
300
|
+
def test_create_external_user_description(
|
|
301
|
+
self, mock_jira_client, mock_get_reporter, mock_alert_slack
|
|
302
|
+
):
|
|
271
303
|
"""Test create method adds email to description for external users."""
|
|
272
304
|
# Setup mocks - external domain
|
|
273
305
|
user = UserFactory()
|
|
@@ -314,7 +346,9 @@ class TestLandbotIssueRequestSerializer(TestCase):
|
|
|
314
346
|
@patch("firefighter.raid.serializers.alert_slack_new_jira_ticket")
|
|
315
347
|
@patch("firefighter.raid.serializers.get_reporter_user_from_email")
|
|
316
348
|
@patch("firefighter.raid.serializers.jira_client")
|
|
317
|
-
def test_create_with_zendesk_field(
|
|
349
|
+
def test_create_with_zendesk_field(
|
|
350
|
+
self, mock_jira_client, mock_get_reporter, mock_alert_slack
|
|
351
|
+
):
|
|
318
352
|
"""Test create method passes zendesk field to jira_client."""
|
|
319
353
|
# Setup mocks
|
|
320
354
|
user = UserFactory()
|
|
@@ -373,14 +407,15 @@ class TestJiraWebhookUpdateSerializer(TestCase):
|
|
|
373
407
|
"changelog": {
|
|
374
408
|
"items": [
|
|
375
409
|
{
|
|
376
|
-
|
|
377
|
-
"
|
|
378
|
-
"
|
|
410
|
+
# Jira priority lives in customfield_11064; values are numeric strings 1-5.
|
|
411
|
+
"field": "customfield_11064",
|
|
412
|
+
"fromString": "3",
|
|
413
|
+
"toString": "4",
|
|
379
414
|
}
|
|
380
415
|
]
|
|
381
416
|
},
|
|
382
417
|
"user": {"displayName": "John Doe"},
|
|
383
|
-
"webhookEvent": "jira:issue_updated"
|
|
418
|
+
"webhookEvent": "jira:issue_updated",
|
|
384
419
|
}
|
|
385
420
|
|
|
386
421
|
result = serializer.create(validated_data)
|
|
@@ -398,12 +433,12 @@ class TestJiraWebhookUpdateSerializer(TestCase):
|
|
|
398
433
|
{
|
|
399
434
|
"field": "labels", # Not tracked
|
|
400
435
|
"fromString": "old",
|
|
401
|
-
"toString": "new"
|
|
436
|
+
"toString": "new",
|
|
402
437
|
}
|
|
403
438
|
]
|
|
404
439
|
},
|
|
405
440
|
"user": {"displayName": "John Doe"},
|
|
406
|
-
"webhookEvent": "jira:issue_updated"
|
|
441
|
+
"webhookEvent": "jira:issue_updated",
|
|
407
442
|
}
|
|
408
443
|
|
|
409
444
|
result = serializer.create(validated_data)
|
|
@@ -420,15 +455,11 @@ class TestJiraWebhookUpdateSerializer(TestCase):
|
|
|
420
455
|
"issue": {"id": "12345", "key": "TEST-123"},
|
|
421
456
|
"changelog": {
|
|
422
457
|
"items": [
|
|
423
|
-
{
|
|
424
|
-
"field": "status",
|
|
425
|
-
"fromString": "Open",
|
|
426
|
-
"toString": "Closed"
|
|
427
|
-
}
|
|
458
|
+
{"field": "status", "fromString": "Open", "toString": "Closed"}
|
|
428
459
|
]
|
|
429
460
|
},
|
|
430
461
|
"user": {"displayName": "John Doe"},
|
|
431
|
-
"webhookEvent": "jira:issue_updated"
|
|
462
|
+
"webhookEvent": "jira:issue_updated",
|
|
432
463
|
}
|
|
433
464
|
|
|
434
465
|
with pytest.raises(SlackNotificationError):
|
|
@@ -454,9 +485,9 @@ class TestJiraWebhookCommentSerializer(TestCase):
|
|
|
454
485
|
"issue": {"id": "12345", "key": "TEST-123"},
|
|
455
486
|
"comment": {
|
|
456
487
|
"author": {"displayName": "John Doe"},
|
|
457
|
-
"body": "This is a test comment"
|
|
488
|
+
"body": "This is a test comment",
|
|
458
489
|
},
|
|
459
|
-
"webhookEvent": "comment_created"
|
|
490
|
+
"webhookEvent": "comment_created",
|
|
460
491
|
}
|
|
461
492
|
|
|
462
493
|
result = serializer.create(validated_data)
|
|
@@ -466,7 +497,7 @@ class TestJiraWebhookCommentSerializer(TestCase):
|
|
|
466
497
|
jira_ticket_id="12345",
|
|
467
498
|
jira_ticket_key="TEST-123",
|
|
468
499
|
author_jira_name="John Doe",
|
|
469
|
-
comment="This is a test comment"
|
|
500
|
+
comment="This is a test comment",
|
|
470
501
|
)
|
|
471
502
|
|
|
472
503
|
@patch("firefighter.raid.serializers.alert_slack_comment_ticket")
|
|
@@ -479,9 +510,9 @@ class TestJiraWebhookCommentSerializer(TestCase):
|
|
|
479
510
|
"issue": {"id": "12345", "key": "TEST-123"},
|
|
480
511
|
"comment": {
|
|
481
512
|
"author": {"displayName": "John Doe"},
|
|
482
|
-
"body": "This is a test comment"
|
|
513
|
+
"body": "This is a test comment",
|
|
483
514
|
},
|
|
484
|
-
"webhookEvent": "comment_updated"
|
|
515
|
+
"webhookEvent": "comment_updated",
|
|
485
516
|
}
|
|
486
517
|
|
|
487
518
|
with pytest.raises(SlackNotificationError):
|
|
@@ -500,15 +531,21 @@ class TestGetReporterUserFromEmailAdditional:
|
|
|
500
531
|
|
|
501
532
|
@patch("firefighter.raid.serializers.jira_client")
|
|
502
533
|
@patch("firefighter.raid.serializers.SlackUser")
|
|
503
|
-
def test_user_does_not_exist_no_slack_fallback(
|
|
534
|
+
def test_user_does_not_exist_no_slack_fallback(
|
|
535
|
+
self, mock_slack_user, mock_jira_client
|
|
536
|
+
):
|
|
504
537
|
"""Test when User.DoesNotExist and no Slack user exists."""
|
|
505
538
|
# Setup mocks
|
|
506
539
|
mock_slack_user.objects.upsert_by_email.return_value = None
|
|
507
540
|
|
|
508
|
-
default_jira_user = JiraUser.objects.create(
|
|
541
|
+
default_jira_user = JiraUser.objects.create(
|
|
542
|
+
id="default-123", user=UserFactory()
|
|
543
|
+
)
|
|
509
544
|
mock_jira_client.get_jira_user_from_jira_id.return_value = default_jira_user
|
|
510
545
|
|
|
511
|
-
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
546
|
+
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
547
|
+
"test@example.com"
|
|
548
|
+
)
|
|
512
549
|
|
|
513
550
|
assert reporter_user == default_jira_user.user
|
|
514
551
|
assert reporter == default_jira_user
|
|
@@ -516,16 +553,25 @@ class TestGetReporterUserFromEmailAdditional:
|
|
|
516
553
|
|
|
517
554
|
@patch("firefighter.raid.serializers.jira_client")
|
|
518
555
|
@patch("firefighter.raid.serializers.SlackUser")
|
|
519
|
-
def test_slack_user_exists_but_reporter_user_tmp_is_none(
|
|
556
|
+
def test_slack_user_exists_but_reporter_user_tmp_is_none(
|
|
557
|
+
self, mock_slack_user, mock_jira_client
|
|
558
|
+
):
|
|
520
559
|
"""Test when Slack upsert returns None and we use default JIRA user."""
|
|
521
560
|
# Setup mocks - simulate User.DoesNotExist
|
|
522
561
|
mock_slack_user.objects.upsert_by_email.return_value = None
|
|
523
562
|
|
|
524
|
-
default_jira_user = JiraUser.objects.create(
|
|
563
|
+
default_jira_user = JiraUser.objects.create(
|
|
564
|
+
id="default-123", user=UserFactory()
|
|
565
|
+
)
|
|
525
566
|
mock_jira_client.get_jira_user_from_jira_id.return_value = default_jira_user
|
|
526
567
|
|
|
527
|
-
with patch(
|
|
528
|
-
|
|
568
|
+
with patch(
|
|
569
|
+
"firefighter.raid.serializers.User.objects.get",
|
|
570
|
+
side_effect=User.DoesNotExist,
|
|
571
|
+
):
|
|
572
|
+
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
573
|
+
"test@example.com"
|
|
574
|
+
)
|
|
529
575
|
|
|
530
576
|
# Should use default JIRA user's user since reporter_user_tmp is None
|
|
531
577
|
assert reporter_user == default_jira_user.user
|
|
@@ -534,21 +580,35 @@ class TestGetReporterUserFromEmailAdditional:
|
|
|
534
580
|
|
|
535
581
|
@patch("firefighter.raid.serializers.jira_client")
|
|
536
582
|
@patch("firefighter.raid.serializers.SlackUser")
|
|
537
|
-
@patch(
|
|
538
|
-
|
|
583
|
+
@patch(
|
|
584
|
+
"firefighter.raid.serializers.JIRA_USER_IDS",
|
|
585
|
+
{"special.com": "special-user-123"},
|
|
586
|
+
)
|
|
587
|
+
def test_domain_specific_jira_user_with_slack_fallback(
|
|
588
|
+
self, mock_slack_user, mock_jira_client
|
|
589
|
+
):
|
|
539
590
|
"""Test domain-specific JIRA user when Slack user exists."""
|
|
540
591
|
# Setup mocks
|
|
541
592
|
slack_user = UserFactory(email="test@special.com")
|
|
542
593
|
mock_slack_user.objects.upsert_by_email.return_value = slack_user
|
|
543
594
|
|
|
544
|
-
domain_jira_user = JiraUser.objects.create(
|
|
595
|
+
domain_jira_user = JiraUser.objects.create(
|
|
596
|
+
id="special-user-123", user=UserFactory()
|
|
597
|
+
)
|
|
545
598
|
mock_jira_client.get_jira_user_from_jira_id.return_value = domain_jira_user
|
|
546
599
|
|
|
547
|
-
with patch(
|
|
548
|
-
|
|
600
|
+
with patch(
|
|
601
|
+
"firefighter.raid.serializers.User.objects.get",
|
|
602
|
+
side_effect=User.DoesNotExist,
|
|
603
|
+
):
|
|
604
|
+
reporter_user, reporter, user_domain = get_reporter_user_from_email(
|
|
605
|
+
"test@special.com"
|
|
606
|
+
)
|
|
549
607
|
|
|
550
608
|
# Should use slack_user since reporter_user_tmp is not None
|
|
551
609
|
assert reporter_user == slack_user
|
|
552
610
|
assert reporter == domain_jira_user
|
|
553
611
|
assert user_domain == "special.com"
|
|
554
|
-
mock_jira_client.get_jira_user_from_jira_id.assert_called_once_with(
|
|
612
|
+
mock_jira_client.get_jira_user_from_jira_id.assert_called_once_with(
|
|
613
|
+
"special-user-123"
|
|
614
|
+
)
|