firefighter-incident 0.0.18__py3-none-any.whl → 0.0.20__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/forms/select_impact.py +1 -1
- firefighter/jira_app/client.py +17 -7
- firefighter/raid/client.py +2 -0
- firefighter/raid/forms.py +6 -2
- firefighter/raid/views/__init__.py +1 -0
- {firefighter_incident-0.0.18.dist-info → firefighter_incident-0.0.20.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.18.dist-info → firefighter_incident-0.0.20.dist-info}/RECORD +21 -15
- firefighter_tests/test_jira_app/__init__.py +1 -0
- firefighter_tests/test_jira_app/conftest.py +24 -0
- firefighter_tests/test_jira_app/test_jira_client_watchers.py +135 -0
- firefighter_tests/test_raid/test_raid_alert_p4_p5.py +255 -0
- firefighter_tests/test_raid/test_raid_client.py +32 -0
- firefighter_tests/test_raid/test_raid_forms.py +61 -3
- firefighter_tests/test_raid/test_raid_serializers.py +47 -0
- firefighter_tests/test_raid/test_zendesk_integration.py +198 -0
- firefighter_tests/test_slack/test_conversation_tags.py +344 -0
- firefighter_tests/test_slack/views/modals/test_update_status.py +7 -9
- {firefighter_incident-0.0.18.dist-info → firefighter_incident-0.0.20.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.18.dist-info → firefighter_incident-0.0.20.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.18.dist-info → firefighter_incident-0.0.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -195,9 +195,12 @@ class TestAlertSlackNewJiraTicket:
|
|
|
195
195
|
)
|
|
196
196
|
|
|
197
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
|
|
200
|
-
|
|
198
|
+
"""Test that function raises ValueError for P1-P3 critical incidents."""
|
|
199
|
+
# Given - Create a P1 incident and link it to the ticket
|
|
200
|
+
p1_priority = Priority.objects.get_or_create(value=1, defaults={"name": "P1"})[
|
|
201
|
+
0
|
|
202
|
+
]
|
|
203
|
+
incident = IncidentFactory(priority=p1_priority)
|
|
201
204
|
self.jira_ticket.incident = incident
|
|
202
205
|
self.jira_ticket.save()
|
|
203
206
|
|
|
@@ -550,3 +553,58 @@ class TestGetInternalAlertConversations:
|
|
|
550
553
|
|
|
551
554
|
# Then
|
|
552
555
|
assert conversation in result
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
@pytest.mark.django_db
|
|
559
|
+
class TestAlertSlackNewJiraTicketSlackApiError:
|
|
560
|
+
"""Test SlackApiError handling in alert_slack_new_jira_ticket."""
|
|
561
|
+
|
|
562
|
+
@pytest.fixture(autouse=True)
|
|
563
|
+
def setup(self):
|
|
564
|
+
"""Set up test fixtures."""
|
|
565
|
+
self.user = UserFactory()
|
|
566
|
+
self.jira_user = JiraUser.objects.create(id="jira-slack-error", user=self.user)
|
|
567
|
+
self.jira_ticket = JiraTicket.objects.create(
|
|
568
|
+
id=88888,
|
|
569
|
+
key="SLACK-888",
|
|
570
|
+
summary="Slack error ticket",
|
|
571
|
+
reporter=self.jira_user,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
@patch("firefighter.raid.forms.get_partner_alert_conversations")
|
|
575
|
+
@patch("firefighter.raid.forms.get_internal_alert_conversations")
|
|
576
|
+
@patch("firefighter.raid.forms.SlackMessageRaidCreatedIssue")
|
|
577
|
+
def test_alert_slack_new_jira_ticket_slack_api_error_on_channel_send(
|
|
578
|
+
self, mock_message_class, mock_get_internal, mock_get_partner, caplog
|
|
579
|
+
):
|
|
580
|
+
"""Test SlackApiError when sending to channel - should log exception and continue."""
|
|
581
|
+
# Given: Create a conversation that will fail to send
|
|
582
|
+
channel = Conversation.objects.create(
|
|
583
|
+
channel_id="C_FAIL_TEST",
|
|
584
|
+
name="fail-channel-test",
|
|
585
|
+
tag="raid_alert__test_fail",
|
|
586
|
+
)
|
|
587
|
+
mock_get_internal.return_value = Conversation.objects.filter(id=channel.id)
|
|
588
|
+
mock_get_partner.return_value = Conversation.objects.none()
|
|
589
|
+
|
|
590
|
+
# Mock message
|
|
591
|
+
mock_message = Mock()
|
|
592
|
+
mock_message_class.return_value = mock_message
|
|
593
|
+
|
|
594
|
+
# Mock channel.send_message_and_save to raise SlackApiError
|
|
595
|
+
with patch.object(
|
|
596
|
+
Conversation,
|
|
597
|
+
"send_message_and_save",
|
|
598
|
+
side_effect=SlackApiError(
|
|
599
|
+
message="channel_not_found",
|
|
600
|
+
response={"error": "channel_not_found"}
|
|
601
|
+
)
|
|
602
|
+
):
|
|
603
|
+
# When
|
|
604
|
+
alert_slack_new_jira_ticket(self.jira_ticket)
|
|
605
|
+
|
|
606
|
+
# Then
|
|
607
|
+
# Should log exception about not being able to send
|
|
608
|
+
assert "Couldn't send message to channel" in caplog.text
|
|
609
|
+
assert str(self.jira_ticket.id) in caplog.text
|
|
610
|
+
# Function should continue and not raise
|
|
@@ -311,6 +311,53 @@ class TestLandbotIssueRequestSerializer(TestCase):
|
|
|
311
311
|
result, reporter_user=user, reporter_email="test@external.com"
|
|
312
312
|
)
|
|
313
313
|
|
|
314
|
+
@patch("firefighter.raid.serializers.alert_slack_new_jira_ticket")
|
|
315
|
+
@patch("firefighter.raid.serializers.get_reporter_user_from_email")
|
|
316
|
+
@patch("firefighter.raid.serializers.jira_client")
|
|
317
|
+
def test_create_with_zendesk_field(self, mock_jira_client, mock_get_reporter, mock_alert_slack):
|
|
318
|
+
"""Test create method passes zendesk field to jira_client."""
|
|
319
|
+
# Setup mocks
|
|
320
|
+
user = UserFactory()
|
|
321
|
+
jira_user = JiraUser.objects.create(id="test-123", user=user)
|
|
322
|
+
mock_get_reporter.return_value = (user, jira_user, "manomano.com")
|
|
323
|
+
mock_alert_slack.return_value = None
|
|
324
|
+
|
|
325
|
+
mock_jira_client.create_issue.return_value = {
|
|
326
|
+
"id": "12345",
|
|
327
|
+
"key": "TEST-123",
|
|
328
|
+
"summary": "Test Issue",
|
|
329
|
+
"reporter": jira_user,
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
serializer = LandbotIssueRequestSerializer()
|
|
333
|
+
validated_data = {
|
|
334
|
+
"reporter_email": "test@manomano.com",
|
|
335
|
+
"issue_type": "Incident",
|
|
336
|
+
"summary": "Test Issue",
|
|
337
|
+
"description": "Test Description",
|
|
338
|
+
"labels": [],
|
|
339
|
+
"priority": 1,
|
|
340
|
+
"seller_contract_id": None,
|
|
341
|
+
"zoho": None,
|
|
342
|
+
"zendesk": "ZD-12345", # Zendesk ticket ID
|
|
343
|
+
"platform": "FR",
|
|
344
|
+
"incident_category": None,
|
|
345
|
+
"business_impact": None,
|
|
346
|
+
"environments": ["PRD"],
|
|
347
|
+
"suggested_team_routing": "TEAM1",
|
|
348
|
+
"project": "SBI",
|
|
349
|
+
"attachments": None,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
result = serializer.create(validated_data)
|
|
353
|
+
|
|
354
|
+
# Verify zendesk_ticket_id was passed to jira_client.create_issue
|
|
355
|
+
create_call = mock_jira_client.create_issue.call_args[1]
|
|
356
|
+
assert "zendesk_ticket_id" in create_call
|
|
357
|
+
assert create_call["zendesk_ticket_id"] == "ZD-12345"
|
|
358
|
+
assert isinstance(result, JiraTicket)
|
|
359
|
+
mock_alert_slack.assert_called_once()
|
|
360
|
+
|
|
314
361
|
|
|
315
362
|
class TestJiraWebhookUpdateSerializer(TestCase):
|
|
316
363
|
"""Test JiraWebhookUpdateSerializer functionality."""
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Comprehensive integration tests for zendesk field flow from API endpoint to Jira."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import Mock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from firefighter.incidents.factories import UserFactory
|
|
10
|
+
from firefighter.jira_app.models import JiraUser
|
|
11
|
+
from firefighter.raid.client import RaidJiraClient
|
|
12
|
+
from firefighter.raid.models import JiraTicket
|
|
13
|
+
from firefighter.raid.serializers import LandbotIssueRequestSerializer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.django_db
|
|
17
|
+
class TestZendeskFieldIntegration:
|
|
18
|
+
"""Integration tests for zendesk field flow from serializer to Jira API."""
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def mock_jira_client(self):
|
|
22
|
+
"""Create a mock RaidJiraClient with mocked Jira connection."""
|
|
23
|
+
with patch("firefighter.jira_app.client.JiraClient.__init__", return_value=None):
|
|
24
|
+
client = RaidJiraClient()
|
|
25
|
+
client.jira = Mock()
|
|
26
|
+
return client
|
|
27
|
+
|
|
28
|
+
@pytest.mark.parametrize(
|
|
29
|
+
("zendesk_value", "should_be_in_jira"),
|
|
30
|
+
[
|
|
31
|
+
("ZD-12345", True), # Valid value - should be sent to Jira
|
|
32
|
+
("", False), # Empty string - should NOT be sent to Jira
|
|
33
|
+
(None, False), # None - should NOT be sent to Jira
|
|
34
|
+
("0", True), # "0" is truthy in Python - should be sent
|
|
35
|
+
],
|
|
36
|
+
)
|
|
37
|
+
def test_zendesk_field_mapping_edge_cases(
|
|
38
|
+
self, mock_jira_client, zendesk_value, should_be_in_jira
|
|
39
|
+
):
|
|
40
|
+
"""Test zendesk field mapping with various edge case values."""
|
|
41
|
+
mock_issue = Mock()
|
|
42
|
+
mock_issue.raw = {
|
|
43
|
+
"id": "12345",
|
|
44
|
+
"key": "TEST-123",
|
|
45
|
+
"fields": {
|
|
46
|
+
"summary": "Test",
|
|
47
|
+
"description": "Test",
|
|
48
|
+
"reporter": {"accountId": "reporter123"},
|
|
49
|
+
"issuetype": {"name": "Bug"},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
mock_jira_client.jira.create_issue.return_value = mock_issue
|
|
53
|
+
|
|
54
|
+
# Call create_issue with zendesk value
|
|
55
|
+
result = mock_jira_client.create_issue(
|
|
56
|
+
issuetype="Bug",
|
|
57
|
+
summary="Test",
|
|
58
|
+
description="Test",
|
|
59
|
+
assignee=None,
|
|
60
|
+
reporter="test_reporter",
|
|
61
|
+
priority=1,
|
|
62
|
+
zendesk_ticket_id=zendesk_value,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Check that the Jira API was called
|
|
66
|
+
assert mock_jira_client.jira.create_issue.called
|
|
67
|
+
|
|
68
|
+
# Verify customfield_10895 presence based on expected behavior
|
|
69
|
+
call_kwargs = mock_jira_client.jira.create_issue.call_args[1]
|
|
70
|
+
|
|
71
|
+
if should_be_in_jira:
|
|
72
|
+
assert "customfield_10895" in call_kwargs, (
|
|
73
|
+
f"customfield_10895 should be present for zendesk_value={zendesk_value!r}"
|
|
74
|
+
)
|
|
75
|
+
assert call_kwargs["customfield_10895"] == str(zendesk_value)
|
|
76
|
+
else:
|
|
77
|
+
assert "customfield_10895" not in call_kwargs, (
|
|
78
|
+
f"customfield_10895 should NOT be present for zendesk_value={zendesk_value!r}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
assert result["id"] == 12345
|
|
82
|
+
|
|
83
|
+
@pytest.mark.parametrize(
|
|
84
|
+
("zendesk_value", "expected_param"),
|
|
85
|
+
[
|
|
86
|
+
("ZD-12345", "ZD-12345"), # Valid value
|
|
87
|
+
("", ""), # Empty string
|
|
88
|
+
(None, None), # None
|
|
89
|
+
],
|
|
90
|
+
)
|
|
91
|
+
@patch("firefighter.raid.serializers.alert_slack_new_jira_ticket")
|
|
92
|
+
@patch("firefighter.raid.serializers.get_reporter_user_from_email")
|
|
93
|
+
@patch("firefighter.raid.serializers.jira_client")
|
|
94
|
+
def test_serializer_to_client_zendesk_flow(
|
|
95
|
+
self,
|
|
96
|
+
mock_jira_client,
|
|
97
|
+
mock_get_reporter,
|
|
98
|
+
mock_alert_slack,
|
|
99
|
+
zendesk_value,
|
|
100
|
+
expected_param,
|
|
101
|
+
):
|
|
102
|
+
"""Test that serializer correctly passes zendesk value to jira_client."""
|
|
103
|
+
# Setup mocks
|
|
104
|
+
user = UserFactory()
|
|
105
|
+
jira_user = JiraUser.objects.create(id="test-123", user=user)
|
|
106
|
+
mock_get_reporter.return_value = (user, jira_user, "manomano.com")
|
|
107
|
+
mock_alert_slack.return_value = None
|
|
108
|
+
|
|
109
|
+
mock_jira_client.create_issue.return_value = {
|
|
110
|
+
"id": "12345",
|
|
111
|
+
"key": "TEST-123",
|
|
112
|
+
"summary": "Test",
|
|
113
|
+
"reporter": jira_user,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Create serializer and validated_data
|
|
117
|
+
serializer = LandbotIssueRequestSerializer()
|
|
118
|
+
validated_data = {
|
|
119
|
+
"reporter_email": "test@manomano.com",
|
|
120
|
+
"issue_type": "Incident",
|
|
121
|
+
"summary": "Test",
|
|
122
|
+
"description": "Test",
|
|
123
|
+
"labels": [],
|
|
124
|
+
"priority": 1,
|
|
125
|
+
"seller_contract_id": None,
|
|
126
|
+
"zoho": None,
|
|
127
|
+
"zendesk": zendesk_value,
|
|
128
|
+
"platform": "FR",
|
|
129
|
+
"incident_category": None,
|
|
130
|
+
"business_impact": None,
|
|
131
|
+
"environments": ["PRD"],
|
|
132
|
+
"suggested_team_routing": "TEAM1",
|
|
133
|
+
"project": "SBI",
|
|
134
|
+
"attachments": None,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Call create
|
|
138
|
+
result = serializer.create(validated_data)
|
|
139
|
+
|
|
140
|
+
# Verify jira_client.create_issue was called
|
|
141
|
+
assert mock_jira_client.create_issue.called
|
|
142
|
+
|
|
143
|
+
# Check zendesk_ticket_id parameter
|
|
144
|
+
create_call = mock_jira_client.create_issue.call_args[1]
|
|
145
|
+
assert "zendesk_ticket_id" in create_call
|
|
146
|
+
assert create_call["zendesk_ticket_id"] == expected_param
|
|
147
|
+
|
|
148
|
+
assert isinstance(result, JiraTicket)
|
|
149
|
+
mock_alert_slack.assert_called_once()
|
|
150
|
+
|
|
151
|
+
@patch("firefighter.raid.serializers.alert_slack_new_jira_ticket")
|
|
152
|
+
@patch("firefighter.raid.serializers.get_reporter_user_from_email")
|
|
153
|
+
@patch("firefighter.raid.serializers.jira_client")
|
|
154
|
+
def test_serializer_without_zendesk_field(
|
|
155
|
+
self, mock_jira_client, mock_get_reporter, mock_alert_slack
|
|
156
|
+
):
|
|
157
|
+
"""Test that serializer works when zendesk field is not provided."""
|
|
158
|
+
# Setup mocks
|
|
159
|
+
user = UserFactory()
|
|
160
|
+
jira_user = JiraUser.objects.create(id="test-456", user=user)
|
|
161
|
+
mock_get_reporter.return_value = (user, jira_user, "manomano.com")
|
|
162
|
+
mock_alert_slack.return_value = None
|
|
163
|
+
|
|
164
|
+
mock_jira_client.create_issue.return_value = {
|
|
165
|
+
"id": "12346",
|
|
166
|
+
"key": "TEST-124",
|
|
167
|
+
"summary": "Test",
|
|
168
|
+
"reporter": jira_user,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
serializer = LandbotIssueRequestSerializer()
|
|
172
|
+
validated_data = {
|
|
173
|
+
"reporter_email": "test@manomano.com",
|
|
174
|
+
"issue_type": "Incident",
|
|
175
|
+
"summary": "Test without zendesk",
|
|
176
|
+
"description": "Test",
|
|
177
|
+
"labels": [],
|
|
178
|
+
"priority": 1,
|
|
179
|
+
"seller_contract_id": None,
|
|
180
|
+
"zoho": None,
|
|
181
|
+
# zendesk field NOT provided
|
|
182
|
+
"platform": "FR",
|
|
183
|
+
"incident_category": None,
|
|
184
|
+
"business_impact": None,
|
|
185
|
+
"environments": ["PRD"],
|
|
186
|
+
"suggested_team_routing": "TEAM1",
|
|
187
|
+
"project": "SBI",
|
|
188
|
+
"attachments": None,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
result = serializer.create(validated_data)
|
|
192
|
+
|
|
193
|
+
# Verify zendesk_ticket_id parameter is None
|
|
194
|
+
create_call = mock_jira_client.create_issue.call_args[1]
|
|
195
|
+
assert "zendesk_ticket_id" in create_call
|
|
196
|
+
assert create_call["zendesk_ticket_id"] is None
|
|
197
|
+
|
|
198
|
+
assert isinstance(result, JiraTicket)
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Tests for Slack Conversation tags usage across the application.
|
|
2
|
+
|
|
3
|
+
This module verifies that all documented conversation tags are properly used
|
|
4
|
+
and that the logic for finding and using tagged channels works correctly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from django.db import IntegrityError
|
|
11
|
+
|
|
12
|
+
from firefighter.incidents.enums import IncidentStatus
|
|
13
|
+
from firefighter.incidents.factories import (
|
|
14
|
+
IncidentCategoryFactory,
|
|
15
|
+
IncidentFactory,
|
|
16
|
+
UserFactory,
|
|
17
|
+
)
|
|
18
|
+
from firefighter.incidents.models.environment import Environment
|
|
19
|
+
from firefighter.incidents.models.priority import Priority
|
|
20
|
+
from firefighter.jira_app.models import JiraUser
|
|
21
|
+
from firefighter.raid.forms import get_internal_alert_conversations
|
|
22
|
+
from firefighter.raid.models import JiraTicket
|
|
23
|
+
from firefighter.slack.models.conversation import Conversation
|
|
24
|
+
from firefighter.slack.rules import (
|
|
25
|
+
should_publish_in_general_channel,
|
|
26
|
+
should_publish_in_it_deploy_channel,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.django_db
|
|
31
|
+
class TestTechIncidentsTag:
|
|
32
|
+
"""Test the tech_incidents tag usage for general incident announcements."""
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def tech_incidents_channel(self):
|
|
36
|
+
"""Create tech_incidents channel."""
|
|
37
|
+
return Conversation.objects.create(
|
|
38
|
+
name="tech-incidents",
|
|
39
|
+
channel_id="C_TECH_INCIDENTS",
|
|
40
|
+
tag="tech_incidents",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def p1_prd_incident(self):
|
|
45
|
+
"""Create P1 incident in PRD."""
|
|
46
|
+
p1 = Priority.objects.get_or_create(value=1, defaults={"name": "P1"})[0]
|
|
47
|
+
prd = Environment.objects.get_or_create(value="PRD", defaults={"value": "PRD"})[
|
|
48
|
+
0
|
|
49
|
+
]
|
|
50
|
+
category = IncidentCategoryFactory()
|
|
51
|
+
return IncidentFactory(
|
|
52
|
+
priority=p1,
|
|
53
|
+
environment=prd,
|
|
54
|
+
incident_category=category,
|
|
55
|
+
status=IncidentStatus.OPEN,
|
|
56
|
+
private=False,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def test_tech_incidents_channel_can_be_found(
|
|
60
|
+
self, tech_incidents_channel # noqa: ARG002 - fixture creates channel in DB
|
|
61
|
+
):
|
|
62
|
+
"""Test that tech_incidents channel can be retrieved by tag."""
|
|
63
|
+
channel = Conversation.objects.get_or_none(tag="tech_incidents")
|
|
64
|
+
assert channel is not None
|
|
65
|
+
assert channel.name == "tech-incidents"
|
|
66
|
+
assert channel.tag == "tech_incidents"
|
|
67
|
+
|
|
68
|
+
def test_should_publish_p1_prd_incident_in_tech_incidents(self, p1_prd_incident):
|
|
69
|
+
"""Test that P1 PRD incidents should be published to tech_incidents."""
|
|
70
|
+
should_publish = should_publish_in_general_channel(
|
|
71
|
+
p1_prd_incident, incident_update=None
|
|
72
|
+
)
|
|
73
|
+
assert should_publish is True
|
|
74
|
+
|
|
75
|
+
def test_should_not_publish_p4_in_tech_incidents(self):
|
|
76
|
+
"""Test that P4 incidents should NOT be published to tech_incidents."""
|
|
77
|
+
p4 = Priority.objects.get_or_create(value=4, defaults={"name": "P4"})[0]
|
|
78
|
+
prd = Environment.objects.get_or_create(value="PRD", defaults={"value": "PRD"})[
|
|
79
|
+
0
|
|
80
|
+
]
|
|
81
|
+
category = IncidentCategoryFactory()
|
|
82
|
+
incident = IncidentFactory(
|
|
83
|
+
priority=p4,
|
|
84
|
+
environment=prd,
|
|
85
|
+
incident_category=category,
|
|
86
|
+
status=IncidentStatus.OPEN,
|
|
87
|
+
private=False,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
should_publish = should_publish_in_general_channel(
|
|
91
|
+
incident, incident_update=None
|
|
92
|
+
)
|
|
93
|
+
assert should_publish is False
|
|
94
|
+
|
|
95
|
+
def test_should_not_publish_private_incident_in_tech_incidents(self):
|
|
96
|
+
"""Test that private incidents should NOT be published to tech_incidents."""
|
|
97
|
+
p1 = Priority.objects.get_or_create(value=1, defaults={"name": "P1"})[0]
|
|
98
|
+
prd = Environment.objects.get_or_create(value="PRD", defaults={"value": "PRD"})[
|
|
99
|
+
0
|
|
100
|
+
]
|
|
101
|
+
category = IncidentCategoryFactory()
|
|
102
|
+
incident = IncidentFactory(
|
|
103
|
+
priority=p1,
|
|
104
|
+
environment=prd,
|
|
105
|
+
incident_category=category,
|
|
106
|
+
status=IncidentStatus.OPEN,
|
|
107
|
+
private=True, # Private incident
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
should_publish = should_publish_in_general_channel(
|
|
111
|
+
incident, incident_update=None
|
|
112
|
+
)
|
|
113
|
+
assert should_publish is False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.mark.django_db
|
|
117
|
+
class TestItDeployTag:
|
|
118
|
+
"""Test the it_deploy tag usage for deployment warnings."""
|
|
119
|
+
|
|
120
|
+
@pytest.fixture
|
|
121
|
+
def it_deploy_channel(self):
|
|
122
|
+
"""Create it_deploy channel."""
|
|
123
|
+
return Conversation.objects.create(
|
|
124
|
+
name="it-deploy", channel_id="C_IT_DEPLOY", tag="it_deploy"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
@pytest.fixture
|
|
128
|
+
def deploy_warning_category(self):
|
|
129
|
+
"""Create incident category with deploy_warning=True."""
|
|
130
|
+
return IncidentCategoryFactory(deploy_warning=True)
|
|
131
|
+
|
|
132
|
+
def test_it_deploy_channel_can_be_found(
|
|
133
|
+
self, it_deploy_channel # noqa: ARG002 - fixture creates channel in DB
|
|
134
|
+
):
|
|
135
|
+
"""Test that it_deploy channel can be retrieved by tag."""
|
|
136
|
+
channel = Conversation.objects.get_or_none(tag="it_deploy")
|
|
137
|
+
assert channel is not None
|
|
138
|
+
assert channel.name == "it-deploy"
|
|
139
|
+
assert channel.tag == "it_deploy"
|
|
140
|
+
|
|
141
|
+
def test_should_publish_p1_deploy_warning_in_it_deploy(
|
|
142
|
+
self, deploy_warning_category
|
|
143
|
+
):
|
|
144
|
+
"""Test that P1 incidents with deploy_warning should be published to it_deploy."""
|
|
145
|
+
p1 = Priority.objects.get_or_create(value=1, defaults={"name": "P1"})[0]
|
|
146
|
+
prd = Environment.objects.get_or_create(value="PRD", defaults={"value": "PRD"})[
|
|
147
|
+
0
|
|
148
|
+
]
|
|
149
|
+
incident = IncidentFactory(
|
|
150
|
+
priority=p1,
|
|
151
|
+
environment=prd,
|
|
152
|
+
incident_category=deploy_warning_category,
|
|
153
|
+
status=IncidentStatus.OPEN,
|
|
154
|
+
private=False,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
should_publish = should_publish_in_it_deploy_channel(incident)
|
|
158
|
+
assert should_publish is True
|
|
159
|
+
|
|
160
|
+
def test_should_not_publish_p2_in_it_deploy(self, deploy_warning_category):
|
|
161
|
+
"""Test that P2 incidents should NOT be published to it_deploy (P1 only)."""
|
|
162
|
+
p2 = Priority.objects.get_or_create(value=2, defaults={"name": "P2"})[0]
|
|
163
|
+
prd = Environment.objects.get_or_create(value="PRD", defaults={"value": "PRD"})[
|
|
164
|
+
0
|
|
165
|
+
]
|
|
166
|
+
incident = IncidentFactory(
|
|
167
|
+
priority=p2,
|
|
168
|
+
environment=prd,
|
|
169
|
+
incident_category=deploy_warning_category,
|
|
170
|
+
status=IncidentStatus.OPEN,
|
|
171
|
+
private=False,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
should_publish = should_publish_in_it_deploy_channel(incident)
|
|
175
|
+
assert should_publish is False
|
|
176
|
+
|
|
177
|
+
def test_should_not_publish_p1_without_deploy_warning_in_it_deploy(self):
|
|
178
|
+
"""Test that P1 without deploy_warning should NOT be published to it_deploy."""
|
|
179
|
+
p1 = Priority.objects.get_or_create(value=1, defaults={"name": "P1"})[0]
|
|
180
|
+
prd = Environment.objects.get_or_create(value="PRD", defaults={"value": "PRD"})[
|
|
181
|
+
0
|
|
182
|
+
]
|
|
183
|
+
category = IncidentCategoryFactory(deploy_warning=False)
|
|
184
|
+
incident = IncidentFactory(
|
|
185
|
+
priority=p1,
|
|
186
|
+
environment=prd,
|
|
187
|
+
incident_category=category,
|
|
188
|
+
status=IncidentStatus.OPEN,
|
|
189
|
+
private=False,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
should_publish = should_publish_in_it_deploy_channel(incident)
|
|
193
|
+
assert should_publish is False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@pytest.mark.django_db
|
|
197
|
+
class TestInvitedForAllPublicP1Tag:
|
|
198
|
+
"""Test the invited_for_all_public_p1 usergroup tag."""
|
|
199
|
+
|
|
200
|
+
@pytest.fixture
|
|
201
|
+
def p1_usergroup(self):
|
|
202
|
+
"""Create P1 usergroup conversation (represents Slack usergroup)."""
|
|
203
|
+
return Conversation.objects.create(
|
|
204
|
+
name="leadership-p1",
|
|
205
|
+
channel_id="S_LEADERSHIP", # S prefix for usergroup
|
|
206
|
+
tag="invited_for_all_public_p1",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def test_p1_usergroup_can_be_found(
|
|
210
|
+
self, p1_usergroup # noqa: ARG002 - fixture creates usergroup in DB
|
|
211
|
+
):
|
|
212
|
+
"""Test that P1 usergroup can be retrieved by tag."""
|
|
213
|
+
usergroup = Conversation.objects.get_or_none(tag="invited_for_all_public_p1")
|
|
214
|
+
assert usergroup is not None
|
|
215
|
+
assert usergroup.tag == "invited_for_all_public_p1"
|
|
216
|
+
|
|
217
|
+
def test_p1_usergroup_tag_exists_in_code(
|
|
218
|
+
self, p1_usergroup # noqa: ARG002 - fixture creates usergroup in DB
|
|
219
|
+
):
|
|
220
|
+
"""Test that P1 usergroup tag is referenced in codebase."""
|
|
221
|
+
# Just verify the tag can be found - the actual Slack integration
|
|
222
|
+
# is tested in the get_users tests
|
|
223
|
+
usergroup = Conversation.objects.get_or_none(tag="invited_for_all_public_p1")
|
|
224
|
+
assert usergroup is not None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@pytest.mark.django_db
|
|
228
|
+
class TestDevFirefighterTag:
|
|
229
|
+
"""Test the dev_firefighter tag for support channel."""
|
|
230
|
+
|
|
231
|
+
@pytest.fixture
|
|
232
|
+
def support_channel(self):
|
|
233
|
+
"""Create dev_firefighter support channel."""
|
|
234
|
+
return Conversation.objects.create(
|
|
235
|
+
name="firefighter-support",
|
|
236
|
+
channel_id="C_SUPPORT",
|
|
237
|
+
tag="dev_firefighter",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def test_support_channel_can_be_found(
|
|
241
|
+
self, support_channel # noqa: ARG002 - fixture creates channel in DB
|
|
242
|
+
):
|
|
243
|
+
"""Test that support channel can be retrieved by tag."""
|
|
244
|
+
channel = Conversation.objects.get_or_none(tag="dev_firefighter")
|
|
245
|
+
assert channel is not None
|
|
246
|
+
assert channel.name == "firefighter-support"
|
|
247
|
+
assert channel.tag == "dev_firefighter"
|
|
248
|
+
|
|
249
|
+
def test_support_channel_used_in_templating(self, support_channel):
|
|
250
|
+
"""Test that support channel tag is referenced in slack_templating module."""
|
|
251
|
+
# The support channel is retrieved in slack_templating.py:76
|
|
252
|
+
# Just verify it can be found
|
|
253
|
+
channel = Conversation.objects.get_or_none(tag="dev_firefighter")
|
|
254
|
+
assert channel is not None
|
|
255
|
+
assert channel == support_channel
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@pytest.mark.django_db
|
|
259
|
+
class TestRaidAlertTags:
|
|
260
|
+
"""Test RAID alert tags patterns (already tested in test_raid_alert_p4_p5.py).
|
|
261
|
+
|
|
262
|
+
These tests verify the tag patterns work correctly for P4-P5 incidents.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
def test_raid_alert_sbi_normal_tag_format(self):
|
|
266
|
+
"""Test that raid_alert__sbi_normal follows correct format."""
|
|
267
|
+
# Create channel
|
|
268
|
+
Conversation.objects.create(
|
|
269
|
+
name="incidents", channel_id="C_INC", tag="raid_alert__sbi_normal"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Create ticket
|
|
273
|
+
jira_user = JiraUser.objects.create(id="test-jira", user=UserFactory())
|
|
274
|
+
ticket = JiraTicket.objects.create(
|
|
275
|
+
id=1,
|
|
276
|
+
key="SBI-1",
|
|
277
|
+
summary="Test",
|
|
278
|
+
project_key="SBI",
|
|
279
|
+
business_impact="N/A",
|
|
280
|
+
reporter=jira_user,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
channels = list(get_internal_alert_conversations(ticket))
|
|
284
|
+
assert len(channels) == 1
|
|
285
|
+
assert channels[0].tag == "raid_alert__sbi_normal"
|
|
286
|
+
|
|
287
|
+
def test_raid_alert_sbi_high_tag_format(self):
|
|
288
|
+
"""Test that raid_alert__sbi_high follows correct format."""
|
|
289
|
+
# Create channel
|
|
290
|
+
Conversation.objects.create(
|
|
291
|
+
name="incidents-high", channel_id="C_INC_H", tag="raid_alert__sbi_high"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Create ticket with high impact
|
|
295
|
+
jira_user = JiraUser.objects.create(id="test-jira2", user=UserFactory())
|
|
296
|
+
ticket = JiraTicket.objects.create(
|
|
297
|
+
id=2,
|
|
298
|
+
key="SBI-2",
|
|
299
|
+
summary="Test",
|
|
300
|
+
project_key="SBI",
|
|
301
|
+
business_impact="High",
|
|
302
|
+
reporter=jira_user,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
channels = list(get_internal_alert_conversations(ticket))
|
|
306
|
+
assert len(channels) == 1
|
|
307
|
+
assert channels[0].tag == "raid_alert__sbi_high"
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@pytest.mark.django_db
|
|
311
|
+
class TestTagUniqueness:
|
|
312
|
+
"""Test that tags are unique and constraints work."""
|
|
313
|
+
|
|
314
|
+
def test_cannot_create_duplicate_tags(self):
|
|
315
|
+
"""Test that duplicate non-empty tags are rejected."""
|
|
316
|
+
Conversation.objects.create(
|
|
317
|
+
name="channel1", channel_id="C_001", tag="unique_tag"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
with pytest.raises(IntegrityError):
|
|
321
|
+
Conversation.objects.create(
|
|
322
|
+
name="channel2", channel_id="C_002", tag="unique_tag"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def test_can_have_multiple_empty_tags(self):
|
|
326
|
+
"""Test that multiple channels can have empty tags."""
|
|
327
|
+
conv1 = Conversation.objects.create(name="channel1", channel_id="C_001", tag="")
|
|
328
|
+
conv2 = Conversation.objects.create(name="channel2", channel_id="C_002", tag="")
|
|
329
|
+
|
|
330
|
+
assert conv1.tag == ""
|
|
331
|
+
assert conv2.tag == ""
|
|
332
|
+
|
|
333
|
+
def test_tag_case_sensitivity(self):
|
|
334
|
+
"""Test that tags are case-sensitive."""
|
|
335
|
+
Conversation.objects.create(
|
|
336
|
+
name="channel1", channel_id="C_001", tag="tech_incidents"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Different case should be allowed (but not recommended)
|
|
340
|
+
conv2 = Conversation.objects.create(
|
|
341
|
+
name="channel2", channel_id="C_002", tag="TECH_INCIDENTS"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
assert conv2.tag == "TECH_INCIDENTS"
|