firefighter-incident 0.0.13__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 +16 -3
- firefighter/api/serializers.py +17 -8
- firefighter/api/urls.py +8 -1
- firefighter/api/views/_base.py +1 -1
- firefighter/api/views/components.py +5 -5
- firefighter/api/views/incidents.py +9 -9
- firefighter/confluence/signals/incident_updated.py +2 -2
- firefighter/firefighter/settings/components/raid.py +3 -0
- firefighter/incidents/admin.py +24 -24
- firefighter/incidents/enums.py +22 -2
- firefighter/incidents/factories.py +14 -5
- firefighter/incidents/forms/close_incident.py +4 -4
- firefighter/incidents/forms/closure_reason.py +45 -0
- firefighter/incidents/forms/create_incident.py +4 -4
- firefighter/incidents/forms/unified_incident.py +406 -0
- firefighter/incidents/forms/update_status.py +91 -5
- firefighter/incidents/menus.py +2 -2
- firefighter/incidents/migrations/0005_enable_from_p1_to_p5_priority.py +7 -5
- firefighter/incidents/migrations/0009_update_sla.py +7 -5
- firefighter/incidents/migrations/0020_create_incident_category_model.py +64 -0
- firefighter/incidents/migrations/0021_copy_component_data_to_incident_category.py +57 -0
- firefighter/incidents/migrations/0022_add_incident_category_fields.py +34 -0
- firefighter/incidents/migrations/0023_populate_incident_category_references.py +57 -0
- firefighter/incidents/migrations/0024_remove_component_fields_and_model.py +26 -0
- firefighter/incidents/migrations/0025_make_incident_category_required.py +24 -0
- firefighter/incidents/migrations/0026_alter_incidentcategory_options_and_more.py +39 -0
- 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/__init__.py +1 -1
- firefighter/incidents/models/group.py +1 -1
- firefighter/incidents/models/incident.py +47 -20
- firefighter/incidents/models/{component.py → incident_category.py} +30 -29
- firefighter/incidents/models/incident_update.py +3 -3
- firefighter/incidents/static/css/main.min.css +1 -1
- firefighter/incidents/tables.py +9 -9
- firefighter/incidents/templates/layouts/partials/incident_card.html +1 -1
- firefighter/incidents/templates/layouts/partials/incident_timeline.html +2 -2
- firefighter/incidents/templates/layouts/partials/status_pill.html +1 -1
- firefighter/incidents/templates/pages/{component_detail.html → incident_category_detail.html} +13 -13
- firefighter/incidents/templates/pages/{component_list.html → incident_category_list.html} +2 -2
- firefighter/incidents/templates/pages/incident_detail.html +3 -3
- firefighter/incidents/urls.py +6 -6
- firefighter/incidents/views/components/details.py +9 -9
- firefighter/incidents/views/components/list.py +9 -9
- firefighter/incidents/views/reports.py +5 -5
- firefighter/incidents/views/users/details.py +2 -2
- firefighter/incidents/views/views.py +7 -7
- firefighter/jira_app/client.py +1 -1
- firefighter/logging/custom_json_formatter.py +2 -1
- firefighter/pagerduty/tasks/trigger_oncall.py +1 -1
- firefighter/raid/admin.py +0 -11
- firefighter/raid/apps.py +9 -26
- firefighter/raid/client.py +5 -5
- firefighter/raid/forms.py +84 -213
- firefighter/raid/migrations/0003_delete_raidarea.py +16 -0
- firefighter/raid/models.py +2 -21
- firefighter/raid/serializers.py +5 -4
- firefighter/raid/service.py +29 -27
- firefighter/raid/signals/incident_created.py +42 -15
- firefighter/raid/signals/incident_updated.py +3 -2
- firefighter/raid/utils.py +1 -1
- firefighter/raid/views/__init__.py +1 -1
- firefighter/slack/admin.py +8 -8
- firefighter/slack/management/commands/switch_test_users.py +272 -0
- firefighter/slack/messages/slack_messages.py +24 -9
- firefighter/slack/migrations/0005_add_incident_categories_fields.py +33 -0
- firefighter/slack/migrations/0006_copy_components_to_incident_categories.py +57 -0
- firefighter/slack/migrations/0007_remove_components_fields.py +22 -0
- firefighter/slack/migrations/0008_alter_conversation_incident_categories_and_more.py +33 -0
- firefighter/slack/models/conversation.py +3 -3
- firefighter/slack/models/incident_channel.py +1 -1
- firefighter/slack/models/user.py +1 -1
- firefighter/slack/models/user_group.py +3 -3
- firefighter/slack/rules.py +2 -2
- firefighter/slack/signals/create_incident_conversation.py +6 -0
- firefighter/slack/signals/get_users.py +2 -2
- firefighter/slack/signals/incident_updated.py +8 -2
- firefighter/slack/utils.py +2 -2
- firefighter/slack/views/events/home.py +2 -2
- firefighter/slack/views/modals/__init__.py +4 -0
- firefighter/slack/views/modals/base_modal/form_utils.py +78 -0
- firefighter/slack/views/modals/close.py +18 -5
- firefighter/slack/views/modals/closure_reason.py +193 -0
- firefighter/slack/views/modals/open.py +83 -12
- firefighter/slack/views/modals/opening/check_current_incidents.py +2 -2
- firefighter/slack/views/modals/opening/details/unified.py +203 -0
- firefighter/slack/views/modals/opening/select_impact.py +5 -2
- 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 +32 -6
- firefighter/slack/views/modals/utils.py +51 -0
- firefighter_fixtures/incidents/{components.json → incident_categories.json} +52 -52
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/METADATA +2 -2
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/RECORD +133 -88
- firefighter_tests/conftest.py +4 -5
- firefighter_tests/test_api/test_api_landbot.py +1 -1
- firefighter_tests/test_firefighter/test_sso.py +146 -0
- 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_form_utils.py +15 -15
- 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_incident_urls.py +3 -3
- firefighter_tests/test_incidents/test_models/test_incident_category.py +165 -0
- firefighter_tests/test_incidents/test_models/test_incident_model.py +70 -2
- 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_priority_mapping.py +267 -0
- firefighter_tests/test_raid/test_raid_client.py +580 -0
- firefighter_tests/test_raid/test_raid_forms.py +552 -0
- firefighter_tests/test_raid/test_raid_models.py +185 -0
- firefighter_tests/test_raid/test_raid_serializers.py +507 -0
- firefighter_tests/test_raid/test_raid_service.py +442 -0
- firefighter_tests/test_raid/test_raid_signals.py +187 -0
- firefighter_tests/test_raid/test_raid_views.py +196 -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 +71 -9
- 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 +331 -7
- 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_fixtures/raid/area.json +0 -1
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import ANY, Mock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from django.test import TestCase
|
|
7
|
+
from slack_sdk.errors import SlackApiError
|
|
8
|
+
|
|
9
|
+
from firefighter.incidents.factories import (
|
|
10
|
+
IncidentFactory,
|
|
11
|
+
PriorityFactory,
|
|
12
|
+
UserFactory,
|
|
13
|
+
)
|
|
14
|
+
from firefighter.incidents.models.priority import Priority
|
|
15
|
+
from firefighter.jira_app.client import JiraAPIError, JiraUserNotFoundError
|
|
16
|
+
from firefighter.jira_app.models import JiraUser
|
|
17
|
+
from firefighter.raid.forms import (
|
|
18
|
+
PlatformChoices,
|
|
19
|
+
alert_slack_comment_ticket,
|
|
20
|
+
alert_slack_new_jira_ticket,
|
|
21
|
+
alert_slack_update_ticket,
|
|
22
|
+
get_business_impact,
|
|
23
|
+
get_internal_alert_conversations,
|
|
24
|
+
get_partner_alert_conversations,
|
|
25
|
+
initial_priority,
|
|
26
|
+
process_jira_issue,
|
|
27
|
+
send_message_to_watchers,
|
|
28
|
+
set_jira_ticket_watchers_raid,
|
|
29
|
+
)
|
|
30
|
+
from firefighter.raid.models import JiraTicket
|
|
31
|
+
from firefighter.slack.models.conversation import Conversation
|
|
32
|
+
from firefighter.slack.models.user import SlackUser
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestPlatformChoices(TestCase):
|
|
36
|
+
"""Test PlatformChoices enum."""
|
|
37
|
+
|
|
38
|
+
def test_platform_choices_values(self):
|
|
39
|
+
"""Test that platform choices have correct values."""
|
|
40
|
+
assert PlatformChoices.FR == "platform-FR"
|
|
41
|
+
assert PlatformChoices.DE == "platform-DE"
|
|
42
|
+
assert PlatformChoices.IT == "platform-IT"
|
|
43
|
+
assert PlatformChoices.ES == "platform-ES"
|
|
44
|
+
assert PlatformChoices.UK == "platform-UK"
|
|
45
|
+
assert PlatformChoices.ALL == "platform-All"
|
|
46
|
+
assert PlatformChoices.INTERNAL == "platform-Internal"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.django_db
|
|
50
|
+
class TestInitialPriority:
|
|
51
|
+
"""Test initial_priority function."""
|
|
52
|
+
|
|
53
|
+
def test_initial_priority_returns_default(self):
|
|
54
|
+
"""Test that initial_priority returns default priority."""
|
|
55
|
+
# Given
|
|
56
|
+
Priority.objects.all().delete() # Clean slate
|
|
57
|
+
default_priority = PriorityFactory(default=True, value=100)
|
|
58
|
+
PriorityFactory(default=False, value=101) # Create non-default priority
|
|
59
|
+
|
|
60
|
+
# When
|
|
61
|
+
result = initial_priority()
|
|
62
|
+
|
|
63
|
+
# Then
|
|
64
|
+
assert result == default_priority
|
|
65
|
+
|
|
66
|
+
|
|
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
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.django_db
|
|
79
|
+
class TestProcessJiraIssue:
|
|
80
|
+
"""Test process_jira_issue function."""
|
|
81
|
+
|
|
82
|
+
def setup_method(self):
|
|
83
|
+
"""Set up test data."""
|
|
84
|
+
self.user = UserFactory()
|
|
85
|
+
self.jira_user = JiraUser.objects.create(id="jira-123", user=self.user)
|
|
86
|
+
|
|
87
|
+
@patch("firefighter.raid.forms.alert_slack_new_jira_ticket")
|
|
88
|
+
@patch("firefighter.raid.forms.set_jira_ticket_watchers_raid")
|
|
89
|
+
@patch("firefighter.raid.forms.SelectImpactForm")
|
|
90
|
+
def test_process_jira_issue(self, mock_impact_form, mock_set_watchers, mock_alert_slack):
|
|
91
|
+
"""Test process_jira_issue function."""
|
|
92
|
+
# Given
|
|
93
|
+
issue_data = {
|
|
94
|
+
"id": "10001",
|
|
95
|
+
"key": "TEST-123",
|
|
96
|
+
"summary": "Test issue",
|
|
97
|
+
"reporter": self.jira_user,
|
|
98
|
+
}
|
|
99
|
+
impacts_data = {"business_impact": "High"}
|
|
100
|
+
|
|
101
|
+
mock_form_instance = Mock()
|
|
102
|
+
mock_impact_form.return_value = mock_form_instance
|
|
103
|
+
mock_set_watchers.return_value = None
|
|
104
|
+
mock_alert_slack.return_value = None
|
|
105
|
+
|
|
106
|
+
# When
|
|
107
|
+
process_jira_issue(issue_data, self.user, self.jira_user, impacts_data)
|
|
108
|
+
|
|
109
|
+
# Then
|
|
110
|
+
# Check that JiraTicket was created
|
|
111
|
+
assert JiraTicket.objects.filter(key="TEST-123").exists()
|
|
112
|
+
|
|
113
|
+
# Check that all functions were called
|
|
114
|
+
mock_impact_form.assert_called_once_with(impacts_data)
|
|
115
|
+
mock_form_instance.save.assert_called_once_with(incident=ANY)
|
|
116
|
+
mock_set_watchers.assert_called_once_with(ANY)
|
|
117
|
+
mock_alert_slack.assert_called_once_with(ANY)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.django_db
|
|
121
|
+
class TestSetJiraTicketWatchersRaid:
|
|
122
|
+
"""Test set_jira_ticket_watchers_raid function."""
|
|
123
|
+
|
|
124
|
+
def setup_method(self):
|
|
125
|
+
"""Set up test data."""
|
|
126
|
+
self.user = UserFactory()
|
|
127
|
+
self.jira_user = JiraUser.objects.create(id="jira-123", user=self.user)
|
|
128
|
+
self.jira_ticket = JiraTicket.objects.create(
|
|
129
|
+
id=10001,
|
|
130
|
+
key="TEST-123",
|
|
131
|
+
summary="Test ticket",
|
|
132
|
+
reporter=self.jira_user,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
136
|
+
def test_set_jira_ticket_watchers_success(self, mock_jira_client):
|
|
137
|
+
"""Test successful watcher addition."""
|
|
138
|
+
# Given
|
|
139
|
+
mock_default_user = Mock()
|
|
140
|
+
mock_jira_client.get_jira_user_from_jira_id.return_value = mock_default_user
|
|
141
|
+
mock_jira_client.jira.add_watcher.return_value = None
|
|
142
|
+
|
|
143
|
+
# When
|
|
144
|
+
set_jira_ticket_watchers_raid(self.jira_ticket)
|
|
145
|
+
|
|
146
|
+
# Then
|
|
147
|
+
mock_jira_client.jira.add_watcher.assert_called_once_with(
|
|
148
|
+
issue=10001, watcher="jira-123"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
152
|
+
def test_set_jira_ticket_watchers_jira_user_not_found(self, mock_jira_client):
|
|
153
|
+
"""Test when default JIRA user is not found."""
|
|
154
|
+
# Given
|
|
155
|
+
mock_jira_client.get_jira_user_from_jira_id.side_effect = JiraUserNotFoundError("Not found")
|
|
156
|
+
|
|
157
|
+
# When
|
|
158
|
+
set_jira_ticket_watchers_raid(self.jira_ticket)
|
|
159
|
+
|
|
160
|
+
# Then
|
|
161
|
+
# Should handle the exception and continue
|
|
162
|
+
|
|
163
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
164
|
+
def test_set_jira_ticket_watchers_add_watcher_error(self, mock_jira_client):
|
|
165
|
+
"""Test when adding watcher fails."""
|
|
166
|
+
# Given
|
|
167
|
+
mock_default_user = Mock()
|
|
168
|
+
mock_jira_client.get_jira_user_from_jira_id.return_value = mock_default_user
|
|
169
|
+
mock_jira_client.jira.add_watcher.side_effect = JiraAPIError("API Error")
|
|
170
|
+
mock_jira_client.jira.remove_watcher.side_effect = JiraAPIError("Remove Error")
|
|
171
|
+
|
|
172
|
+
# When
|
|
173
|
+
set_jira_ticket_watchers_raid(self.jira_ticket)
|
|
174
|
+
|
|
175
|
+
# Then
|
|
176
|
+
# Should handle both exceptions
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.mark.django_db
|
|
180
|
+
class TestAlertSlackNewJiraTicket:
|
|
181
|
+
"""Test alert_slack_new_jira_ticket function."""
|
|
182
|
+
|
|
183
|
+
def setup_method(self):
|
|
184
|
+
"""Set up test data."""
|
|
185
|
+
self.user = UserFactory(email="test@example.com")
|
|
186
|
+
self.slack_user = SlackUser.objects.create(
|
|
187
|
+
user=self.user, slack_id="U123456"
|
|
188
|
+
)
|
|
189
|
+
self.jira_user = JiraUser.objects.create(id="jira-123", user=self.user)
|
|
190
|
+
self.jira_ticket = JiraTicket.objects.create(
|
|
191
|
+
id=10001,
|
|
192
|
+
key="TEST-123",
|
|
193
|
+
summary="Test ticket",
|
|
194
|
+
reporter=self.jira_user,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def test_alert_slack_new_jira_ticket_with_incident_raises_error(self):
|
|
198
|
+
"""Test that function raises ValueError for critical incidents."""
|
|
199
|
+
# Given - Create an incident and link it to the ticket
|
|
200
|
+
incident = IncidentFactory()
|
|
201
|
+
self.jira_ticket.incident = incident
|
|
202
|
+
self.jira_ticket.save()
|
|
203
|
+
|
|
204
|
+
# When & Then
|
|
205
|
+
with pytest.raises(ValueError, match="This is a critical incident"):
|
|
206
|
+
alert_slack_new_jira_ticket(self.jira_ticket)
|
|
207
|
+
|
|
208
|
+
@patch("firefighter.raid.forms.get_partner_alert_conversations")
|
|
209
|
+
@patch("firefighter.raid.forms.get_internal_alert_conversations")
|
|
210
|
+
@patch("firefighter.raid.forms.SlackMessageRaidCreatedIssue")
|
|
211
|
+
def test_alert_slack_new_jira_ticket_no_reporter_user(
|
|
212
|
+
self, mock_message_class, mock_get_internal, mock_get_partner
|
|
213
|
+
):
|
|
214
|
+
"""Test when reporter user is None."""
|
|
215
|
+
# Given
|
|
216
|
+
mock_get_internal.return_value = Conversation.objects.none()
|
|
217
|
+
mock_get_partner.return_value = Conversation.objects.none()
|
|
218
|
+
|
|
219
|
+
# Mock message class to return proper strings instead of MagicMock
|
|
220
|
+
mock_message_instance = Mock()
|
|
221
|
+
mock_message_instance.get_text.return_value = "Test message"
|
|
222
|
+
mock_message_instance.get_blocks.return_value = []
|
|
223
|
+
mock_message_instance.get_metadata.return_value = {}
|
|
224
|
+
mock_message_class.return_value = mock_message_instance
|
|
225
|
+
|
|
226
|
+
# When
|
|
227
|
+
alert_slack_new_jira_ticket(self.jira_ticket, reporter_user=None)
|
|
228
|
+
|
|
229
|
+
# Then
|
|
230
|
+
# Should log warning and return early
|
|
231
|
+
|
|
232
|
+
@patch("firefighter.raid.forms.get_partner_alert_conversations")
|
|
233
|
+
@patch("firefighter.raid.forms.get_internal_alert_conversations")
|
|
234
|
+
@patch("firefighter.raid.forms.SlackMessageRaidCreatedIssue")
|
|
235
|
+
def test_alert_slack_new_jira_ticket_with_slack_user(
|
|
236
|
+
self, mock_message_class, mock_get_internal, mock_get_partner
|
|
237
|
+
):
|
|
238
|
+
"""Test with valid Slack user."""
|
|
239
|
+
# Given
|
|
240
|
+
mock_message = Mock()
|
|
241
|
+
mock_message_class.return_value = mock_message
|
|
242
|
+
mock_get_internal.return_value = Conversation.objects.none()
|
|
243
|
+
mock_get_partner.return_value = Conversation.objects.none()
|
|
244
|
+
|
|
245
|
+
# When
|
|
246
|
+
with patch.object(self.slack_user, "send_private_message") as mock_send:
|
|
247
|
+
alert_slack_new_jira_ticket(self.jira_ticket, reporter_user=self.user)
|
|
248
|
+
|
|
249
|
+
# Then
|
|
250
|
+
mock_send.assert_called_once_with(
|
|
251
|
+
mock_message, unfurl_links=False
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
@patch("firefighter.raid.forms.get_partner_alert_conversations")
|
|
255
|
+
@patch("firefighter.raid.forms.get_internal_alert_conversations")
|
|
256
|
+
@patch("firefighter.raid.forms.SlackMessageRaidCreatedIssue")
|
|
257
|
+
def test_alert_slack_new_jira_ticket_messages_disabled(
|
|
258
|
+
self, mock_message_class, mock_get_internal, mock_get_partner
|
|
259
|
+
):
|
|
260
|
+
"""Test when user has disabled private messages."""
|
|
261
|
+
# Given
|
|
262
|
+
mock_message = Mock()
|
|
263
|
+
mock_message_class.return_value = mock_message
|
|
264
|
+
mock_get_internal.return_value = Conversation.objects.none()
|
|
265
|
+
mock_get_partner.return_value = Conversation.objects.none()
|
|
266
|
+
|
|
267
|
+
slack_error = SlackApiError("Error", response={"error": "messages_tab_disabled"})
|
|
268
|
+
|
|
269
|
+
# When
|
|
270
|
+
with patch.object(self.slack_user, "send_private_message", side_effect=slack_error):
|
|
271
|
+
alert_slack_new_jira_ticket(self.jira_ticket, reporter_user=self.user)
|
|
272
|
+
|
|
273
|
+
# Then
|
|
274
|
+
# Should log warning about disabled messages
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@pytest.mark.django_db
|
|
278
|
+
class TestAlertSlackUpdateTicket:
|
|
279
|
+
"""Test alert_slack_update_ticket function."""
|
|
280
|
+
|
|
281
|
+
@patch("firefighter.raid.forms.send_message_to_watchers")
|
|
282
|
+
@patch("firefighter.raid.forms.SlackMessageRaidModifiedIssue")
|
|
283
|
+
def test_alert_slack_update_ticket(self, mock_message_class, mock_send_message):
|
|
284
|
+
"""Test alert_slack_update_ticket function."""
|
|
285
|
+
# Given
|
|
286
|
+
mock_message = Mock()
|
|
287
|
+
mock_message_class.return_value = mock_message
|
|
288
|
+
mock_send_message.return_value = True
|
|
289
|
+
|
|
290
|
+
# When
|
|
291
|
+
result = alert_slack_update_ticket(
|
|
292
|
+
jira_ticket_id=10001,
|
|
293
|
+
jira_ticket_key="TEST-123",
|
|
294
|
+
jira_author_name="John Doe",
|
|
295
|
+
jira_field_modified="Priority",
|
|
296
|
+
jira_field_from="High",
|
|
297
|
+
jira_field_to="Critical"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Then
|
|
301
|
+
assert result is True
|
|
302
|
+
mock_message_class.assert_called_once()
|
|
303
|
+
mock_send_message.assert_called_once_with(jira_issue_id=10001, message=mock_message)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@pytest.mark.django_db
|
|
307
|
+
class TestAlertSlackCommentTicket:
|
|
308
|
+
"""Test alert_slack_comment_ticket function."""
|
|
309
|
+
|
|
310
|
+
@patch("firefighter.raid.forms.send_message_to_watchers")
|
|
311
|
+
@patch("firefighter.raid.forms.SlackMessageRaidComment")
|
|
312
|
+
def test_alert_slack_comment_ticket(self, mock_message_class, mock_send_message):
|
|
313
|
+
"""Test alert_slack_comment_ticket function."""
|
|
314
|
+
# Given
|
|
315
|
+
mock_message = Mock()
|
|
316
|
+
mock_message_class.return_value = mock_message
|
|
317
|
+
mock_send_message.return_value = True
|
|
318
|
+
|
|
319
|
+
# When
|
|
320
|
+
result = alert_slack_comment_ticket(
|
|
321
|
+
webhook_event="comment_created",
|
|
322
|
+
jira_ticket_id=10001,
|
|
323
|
+
jira_ticket_key="TEST-123",
|
|
324
|
+
author_jira_name="Jane Smith",
|
|
325
|
+
comment="This is a test comment"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Then
|
|
329
|
+
assert result is True
|
|
330
|
+
mock_message_class.assert_called_once()
|
|
331
|
+
mock_send_message.assert_called_once_with(jira_issue_id=10001, message=mock_message)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@pytest.mark.django_db
|
|
335
|
+
class TestSendMessageToWatchers:
|
|
336
|
+
"""Test send_message_to_watchers function."""
|
|
337
|
+
|
|
338
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
339
|
+
def test_send_message_to_watchers_no_watchers(self, mock_jira_client):
|
|
340
|
+
"""Test when no watchers are found."""
|
|
341
|
+
# Given
|
|
342
|
+
mock_jira_client.get_watchers_from_jira_ticket.return_value = None
|
|
343
|
+
mock_message = Mock()
|
|
344
|
+
|
|
345
|
+
# When
|
|
346
|
+
result = send_message_to_watchers(10001, mock_message)
|
|
347
|
+
|
|
348
|
+
# Then
|
|
349
|
+
assert result is True
|
|
350
|
+
|
|
351
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
352
|
+
def test_send_message_to_watchers_with_app_watcher(self, mock_jira_client):
|
|
353
|
+
"""Test when watcher is an app (should be skipped)."""
|
|
354
|
+
# Given
|
|
355
|
+
watchers = [
|
|
356
|
+
{"accountId": "app-123", "accountType": "app"}
|
|
357
|
+
]
|
|
358
|
+
mock_jira_client.get_watchers_from_jira_ticket.return_value = watchers
|
|
359
|
+
mock_message = Mock()
|
|
360
|
+
|
|
361
|
+
# When
|
|
362
|
+
result = send_message_to_watchers(10001, mock_message)
|
|
363
|
+
|
|
364
|
+
# Then
|
|
365
|
+
assert result is True
|
|
366
|
+
|
|
367
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
368
|
+
def test_send_message_to_watchers_no_account_id(self, mock_jira_client):
|
|
369
|
+
"""Test when watcher has no accountId."""
|
|
370
|
+
# Given
|
|
371
|
+
watchers = [
|
|
372
|
+
{"displayName": "Test User"} # No accountId
|
|
373
|
+
]
|
|
374
|
+
mock_jira_client.get_watchers_from_jira_ticket.return_value = watchers
|
|
375
|
+
mock_message = Mock()
|
|
376
|
+
|
|
377
|
+
# When
|
|
378
|
+
result = send_message_to_watchers(10001, mock_message)
|
|
379
|
+
|
|
380
|
+
# Then
|
|
381
|
+
assert result is True
|
|
382
|
+
|
|
383
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
384
|
+
def test_send_message_to_watchers_successful(self, mock_jira_client):
|
|
385
|
+
"""Test successful message sending to watchers."""
|
|
386
|
+
# Given
|
|
387
|
+
user = UserFactory()
|
|
388
|
+
slack_user = SlackUser.objects.create(user=user, slack_id="U123456")
|
|
389
|
+
jira_user = JiraUser.objects.create(id="jira-watcher", user=user)
|
|
390
|
+
|
|
391
|
+
watchers = [
|
|
392
|
+
{"accountId": "jira-watcher", "accountType": "atlassian"}
|
|
393
|
+
]
|
|
394
|
+
mock_jira_client.get_watchers_from_jira_ticket.return_value = watchers
|
|
395
|
+
mock_jira_client.get_jira_user_from_jira_id.return_value = jira_user
|
|
396
|
+
mock_message = Mock()
|
|
397
|
+
|
|
398
|
+
# When
|
|
399
|
+
with patch.object(slack_user, "send_private_message") as mock_send:
|
|
400
|
+
result = send_message_to_watchers(10001, mock_message)
|
|
401
|
+
|
|
402
|
+
# Then
|
|
403
|
+
assert result is True
|
|
404
|
+
mock_send.assert_called_once_with(
|
|
405
|
+
mock_message, unfurl_links=False
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
409
|
+
def test_send_message_to_watchers_no_slack_user(self, mock_jira_client):
|
|
410
|
+
"""Test when watcher has no Slack user."""
|
|
411
|
+
# Given
|
|
412
|
+
user = UserFactory()
|
|
413
|
+
jira_user = JiraUser.objects.create(id="jira-watcher", user=user)
|
|
414
|
+
|
|
415
|
+
watchers = [
|
|
416
|
+
{"accountId": "jira-watcher", "accountType": "atlassian"}
|
|
417
|
+
]
|
|
418
|
+
mock_jira_client.get_watchers_from_jira_ticket.return_value = watchers
|
|
419
|
+
mock_jira_client.get_jira_user_from_jira_id.return_value = jira_user
|
|
420
|
+
mock_message = Mock()
|
|
421
|
+
|
|
422
|
+
# When
|
|
423
|
+
result = send_message_to_watchers(10001, mock_message)
|
|
424
|
+
|
|
425
|
+
# Then
|
|
426
|
+
assert result is True
|
|
427
|
+
|
|
428
|
+
@patch("firefighter.raid.forms.jira_client")
|
|
429
|
+
def test_send_message_to_watchers_slack_api_error(self, mock_jira_client):
|
|
430
|
+
"""Test when Slack API error occurs."""
|
|
431
|
+
# Given
|
|
432
|
+
user = UserFactory()
|
|
433
|
+
slack_user = SlackUser.objects.create(user=user, slack_id="U123456")
|
|
434
|
+
jira_user = JiraUser.objects.create(id="jira-watcher", user=user)
|
|
435
|
+
|
|
436
|
+
watchers = [
|
|
437
|
+
{"accountId": "jira-watcher", "accountType": "atlassian"}
|
|
438
|
+
]
|
|
439
|
+
mock_jira_client.get_watchers_from_jira_ticket.return_value = watchers
|
|
440
|
+
mock_jira_client.get_jira_user_from_jira_id.return_value = jira_user
|
|
441
|
+
mock_message = Mock()
|
|
442
|
+
|
|
443
|
+
# When
|
|
444
|
+
with patch.object(slack_user, "send_private_message", side_effect=SlackApiError("API Error", response={})):
|
|
445
|
+
result = send_message_to_watchers(10001, mock_message)
|
|
446
|
+
|
|
447
|
+
# Then
|
|
448
|
+
assert result is True
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@pytest.mark.django_db
|
|
452
|
+
class TestGetBusinessImpact:
|
|
453
|
+
"""Test get_business_impact function."""
|
|
454
|
+
|
|
455
|
+
@patch("firefighter.raid.forms.SelectImpactForm")
|
|
456
|
+
def test_get_business_impact(self, mock_impact_form):
|
|
457
|
+
"""Test get_business_impact function."""
|
|
458
|
+
# Given
|
|
459
|
+
impacts_data = {"business_impact": "High"}
|
|
460
|
+
mock_form_instance = Mock()
|
|
461
|
+
mock_form_instance.business_impact_new = "High"
|
|
462
|
+
mock_impact_form.return_value = mock_form_instance
|
|
463
|
+
|
|
464
|
+
# When
|
|
465
|
+
result = get_business_impact(impacts_data)
|
|
466
|
+
|
|
467
|
+
# Then
|
|
468
|
+
assert result == "High"
|
|
469
|
+
mock_impact_form.assert_called_once_with(impacts_data)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@pytest.mark.django_db
|
|
473
|
+
class TestGetPartnerAlertConversations:
|
|
474
|
+
"""Test get_partner_alert_conversations function."""
|
|
475
|
+
|
|
476
|
+
def test_get_partner_alert_conversations(self):
|
|
477
|
+
"""Test get_partner_alert_conversations function."""
|
|
478
|
+
# Given
|
|
479
|
+
domain = "example.com"
|
|
480
|
+
conversation = Conversation.objects.create(
|
|
481
|
+
channel_id="C123456",
|
|
482
|
+
name="test-channel",
|
|
483
|
+
tag=f"raid_alert__{domain}",
|
|
484
|
+
)
|
|
485
|
+
Conversation.objects.create(
|
|
486
|
+
channel_id="C789012",
|
|
487
|
+
name="other-channel",
|
|
488
|
+
tag="other_tag",
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# When
|
|
492
|
+
result = get_partner_alert_conversations(domain)
|
|
493
|
+
|
|
494
|
+
# Then
|
|
495
|
+
assert conversation in result
|
|
496
|
+
assert result.count() == 1
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@pytest.mark.django_db
|
|
500
|
+
class TestGetInternalAlertConversations:
|
|
501
|
+
"""Test get_internal_alert_conversations function."""
|
|
502
|
+
|
|
503
|
+
def setup_method(self):
|
|
504
|
+
"""Set up test data."""
|
|
505
|
+
self.user = UserFactory()
|
|
506
|
+
self.jira_user = JiraUser.objects.create(id="jira-123", user=self.user)
|
|
507
|
+
|
|
508
|
+
def test_get_internal_alert_conversations_high_impact_sbi(self):
|
|
509
|
+
"""Test with high business impact and SBI project."""
|
|
510
|
+
# Given
|
|
511
|
+
jira_ticket = JiraTicket.objects.create(
|
|
512
|
+
id=10001,
|
|
513
|
+
key="SBI-123",
|
|
514
|
+
summary="Test ticket",
|
|
515
|
+
reporter=self.jira_user,
|
|
516
|
+
business_impact="High",
|
|
517
|
+
project_key="SBI",
|
|
518
|
+
)
|
|
519
|
+
conversation = Conversation.objects.create(
|
|
520
|
+
channel_id="C123456",
|
|
521
|
+
name="sbi-high-channel",
|
|
522
|
+
tag="raid_alert__sbi_high",
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
# When
|
|
526
|
+
result = get_internal_alert_conversations(jira_ticket)
|
|
527
|
+
|
|
528
|
+
# Then
|
|
529
|
+
assert conversation in result
|
|
530
|
+
|
|
531
|
+
def test_get_internal_alert_conversations_normal_impact_incidents(self):
|
|
532
|
+
"""Test with normal business impact and non-SBI project."""
|
|
533
|
+
# Given
|
|
534
|
+
jira_ticket = JiraTicket.objects.create(
|
|
535
|
+
id=10002,
|
|
536
|
+
key="TEST-124",
|
|
537
|
+
summary="Test ticket",
|
|
538
|
+
reporter=self.jira_user,
|
|
539
|
+
business_impact="Medium",
|
|
540
|
+
project_key="OTHER",
|
|
541
|
+
)
|
|
542
|
+
conversation = Conversation.objects.create(
|
|
543
|
+
channel_id="C789012",
|
|
544
|
+
name="incidents-normal-channel",
|
|
545
|
+
tag="raid_alert__incidents_normal",
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# When
|
|
549
|
+
result = get_internal_alert_conversations(jira_ticket)
|
|
550
|
+
|
|
551
|
+
# Then
|
|
552
|
+
assert conversation in result
|