firefighter-incident 0.0.19__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 CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.19'
32
- __version_tuple__ = version_tuple = (0, 0, 19)
31
+ __version__ = version = '0.0.20'
32
+ __version_tuple__ = version_tuple = (0, 0, 20)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -71,7 +71,7 @@ class SelectImpactForm(forms.Form):
71
71
  if impact_name.impact_type.name == "Business Impact":
72
72
  impact_value = impact_name.value
73
73
 
74
- return LevelChoices(impact_value).label if impact_value else None
74
+ return str(LevelChoices(impact_value).label) if impact_value else None
75
75
 
76
76
  def save(self, incident: HasImpactProtocol) -> None:
77
77
  """Save the impact choices to the incident."""
@@ -254,14 +254,24 @@ class JiraClient:
254
254
  ValueError: Empty issue id
255
255
 
256
256
  Returns:
257
- list(JiraAPIUser): List of Jira users object
257
+ list(JiraAPIUser): List of Jira users object, or empty list if ticket doesn't exist
258
258
  """
259
- watchers = self.jira.watchers(jira_issue_id).raw.get("watchers")
260
- if len(watchers) == 0:
261
- logger.warning(
262
- "Watchers not found for jira_account_id '%s'.", jira_issue_id
263
- )
264
- return watchers
259
+ try:
260
+ watchers = self.jira.watchers(jira_issue_id).raw.get("watchers")
261
+ except exceptions.JIRAError as e:
262
+ if e.status_code == 404:
263
+ logger.warning(
264
+ "Jira ticket %s not found or no permission to access it. Cannot fetch watchers.",
265
+ jira_issue_id,
266
+ )
267
+ return []
268
+ raise
269
+ else:
270
+ if len(watchers) == 0:
271
+ logger.debug(
272
+ "No watchers found for jira_issue_id '%s'.", jira_issue_id
273
+ )
274
+ return watchers
265
275
 
266
276
  @staticmethod
267
277
  def _create_user_from_jira_info(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firefighter-incident
3
- Version: 0.0.19
3
+ Version: 0.0.20
4
4
  Summary: Incident Management tool made for Slack using Django
5
5
  Project-URL: Repository, https://github.com/ManoManoTech/firefighter-incident
6
6
  Project-URL: Documentation, https://manomanotech.github.io/firefighter-incident/latest/
@@ -6,7 +6,7 @@ gunicorn.conf.py,sha256=vHsTGjaKOr8FDMp6fTKYTX4AtokmPgYvvt5Mr0Q6APc,273
6
6
  main.py,sha256=CsbprHoOYhjCLpTJmq9Z_aRYFoFgWxoz2pDLuwm8Eqg,1558
7
7
  manage.py,sha256=5ivHGD13C6nJ8QvltKsJ9T9akA5he8da70HLWaEP3k8,689
8
8
  firefighter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- firefighter/_version.py,sha256=2t1q5JYEYkNwbHa29phdM-U8VWR1m6EwW48aFKWPj_w,706
9
+ firefighter/_version.py,sha256=8FWqMenH0vIxIRfUQxFCzTnv2dsyjwSoAhVLD84lkZ4,706
10
10
  firefighter/api/__init__.py,sha256=JQW0Bv6xwGqy7ioxx3h6UGMzkkJ4DntDpbvV1Ncgi8k,136
11
11
  firefighter/api/admin.py,sha256=x9Ysy-GiYjb0rynmFdS9g56e6n24fkN0ouGy5QD9Yrc,4629
12
12
  firefighter/api/apps.py,sha256=P5uU1_gMrDfzurdMbfqw1Bnb2uNKKcMq17WBPg2sLhc,204
@@ -141,7 +141,7 @@ firefighter/incidents/forms/close_incident.py,sha256=wyLIlNXx6eU183SkR8H--k9YEOV
141
141
  firefighter/incidents/forms/closure_reason.py,sha256=rwWC9Ks8iuuelCv2oqSpGUjL13yogpdbWHdG2yM23Rc,1564
142
142
  firefighter/incidents/forms/create_incident.py,sha256=cm5EWIvkJ1BZ-JfRJrh4TAE2wYYLV694gQ3MRIkcrGQ,2764
143
143
  firefighter/incidents/forms/edit.py,sha256=RxjmYpSeVo9Xbrs09hbmKO6siy3-PusKqg1VV5xAVr4,1051
144
- firefighter/incidents/forms/select_impact.py,sha256=jLbzVj4UeUGwOYYa5P92PXkEu1J_6H43UATZYzDgSLY,4630
144
+ firefighter/incidents/forms/select_impact.py,sha256=pH7neqP3d4Bxol4FuizD0Zcp6OP5wtvbhh1M3DjyDVA,4635
145
145
  firefighter/incidents/forms/unified_incident.py,sha256=3xDB3IFJVwrRe9C_G52SjQ9-Xeqe1ivAGb0e8xtXJaY,19564
146
146
  firefighter/incidents/forms/update_key_events.py,sha256=1Xmnxe5OgZqLFS2HmMzQm3VGFPQipsdrLgKSwdh-fKc,4441
147
147
  firefighter/incidents/forms/update_roles.py,sha256=Q26UPfwAj-8N23RNZLQkvmHGnS1_j_X5KQWjJmPjMKY,3635
@@ -271,7 +271,7 @@ firefighter/incidents/views/users/details.py,sha256=xqDUEMorplAdSJNQ2vcNzntKKn6U
271
271
  firefighter/jira_app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
272
272
  firefighter/jira_app/admin.py,sha256=YywHfi1dlpyyW8XPYvjtOW3fbTZGm5bKXvHbDkr0D_8,702
273
273
  firefighter/jira_app/apps.py,sha256=W6iM_e2X-dQ4YSeDPuFPM4wp-zIgpWsEiehtng0fk48,348
274
- firefighter/jira_app/client.py,sha256=pf0uqs6ccLKy0wCEIcP9FtKrsgTRkpipA9J6Mkj9p2I,16140
274
+ firefighter/jira_app/client.py,sha256=vT0Oj8W1QJabUCA_Gkgg2ZarxgAjBL_krdP2EkaJN84,16535
275
275
  firefighter/jira_app/models.py,sha256=m-ay74zeI9zMyKuTBfN7W8DPDc9zPmiDESC8mnGBjR4,1741
276
276
  firefighter/jira_app/types.py,sha256=Ukak1U1EhcH2jQPN-UoEL6AMZ-kzPsQ8c7FUr7GmahE,956
277
277
  firefighter/jira_app/utils.py,sha256=3xuzr8viZCBm6j2J9oFzA4bUvVW8TN1DOdlpbruJ_TE,3443
@@ -463,11 +463,14 @@ firefighter_tests/test_incidents/test_models/test_migrations/test_incident_migra
463
463
  firefighter_tests/test_incidents/test_utils/test_date_utils.py,sha256=ogP7qOEwItL4YGI5gbQPVssOS9ilwiuZC8OrT2qngBY,6568
464
464
  firefighter_tests/test_incidents/test_views/test_incident_detail_view.py,sha256=gKKFWIZVrD_P4p6DJjeHCW5uGXBUBVlCd95gJJYDpWQ,680
465
465
  firefighter_tests/test_incidents/test_views/test_index_view.py,sha256=InpxbaWOFwRn4YWeIKZhj17vMymrQQf2p2LFhe2Bcdw,816
466
+ firefighter_tests/test_jira_app/__init__.py,sha256=JxZ3v-0kiHOoO-N3kR8NHTmD8tEvuEYKW1GX_S1ZLMY,33
467
+ firefighter_tests/test_jira_app/conftest.py,sha256=HmZd7EBZgng-rb3kIaB14TPVMixMG4YEvnShVqgjodE,545
468
+ firefighter_tests/test_jira_app/test_jira_client_watchers.py,sha256=IBrFjwhOP0rfm58BBq339CySxjdJkPYjGmISC4oQhZc,4803
466
469
  firefighter_tests/test_raid/conftest.py,sha256=i_TOquYIMLDyVQ97uqxTqPJszVz4qq7L_Q7YJxTuS1o,4090
467
470
  firefighter_tests/test_raid/test_raid_alert_p4_p5.py,sha256=rz9orbt1E1vJ5POQyVZ6-SEPvqB55-xhwIWHicdfgDg,9356
468
471
  firefighter_tests/test_raid/test_raid_client.py,sha256=KTqELERpWno7XhF9LpabpxkHoJiWWrryUg5LHi5Yfjo,22456
469
472
  firefighter_tests/test_raid/test_raid_client_users.py,sha256=9uma1wBhaiCoG75XAZHqpT8oGTnqFJRMCi7a3XctNtM,3631
470
- firefighter_tests/test_raid/test_raid_forms.py,sha256=lLZvhczAge0Ftt3OdFEH2BcE1OMF6_vvy1ghHAe3HWM,19758
473
+ firefighter_tests/test_raid/test_raid_forms.py,sha256=8hiXftYPO_lY0heKHqoreUW2s8AcedUme48wTq4hwNE,21931
471
474
  firefighter_tests/test_raid/test_raid_models.py,sha256=nq-fVClB_P24W8WrZruOPt8wlHUVGYI7wxJR7tH6AnM,5042
472
475
  firefighter_tests/test_raid/test_raid_serializers.py,sha256=xkMK9npvDMAr2pUpP4mFbLszrR6qyTzsJRd-uNOzEhM,22560
473
476
  firefighter_tests/test_raid/test_raid_service.py,sha256=AqVyrRjW2tr0sfbXS4lGlJ7mcxB2ACEXAR8Bv0pXnj0,16755
@@ -496,8 +499,8 @@ firefighter_tests/test_slack/views/modals/test_send_sos.py,sha256=_rE6jD-gOzcGyh
496
499
  firefighter_tests/test_slack/views/modals/test_status.py,sha256=oQzPfwdg2tkbo9nfkO1GfS3WydxqSC6vy1AZjZDKT30,2226
497
500
  firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=OVGqVSeim7aancl0RnGzqwM21Qs3ZVFlaryZiOWi_wM,55748
498
501
  firefighter_tests/test_slack/views/modals/test_utils.py,sha256=DJd2n9q6fFu8UuCRdiq9U_Cn19MdnC5c-ydLLrk6rkc,5218
499
- firefighter_incident-0.0.19.dist-info/METADATA,sha256=3oyEnmXZl2Ps9iciGESrGodxRAXbOJfiDcOaY2uh8NU,5541
500
- firefighter_incident-0.0.19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
501
- firefighter_incident-0.0.19.dist-info/entry_points.txt,sha256=c13meJbv7YNmYz7MipMOQwzQ5IeFOPXUBYAJ44XMQsM,61
502
- firefighter_incident-0.0.19.dist-info/licenses/LICENSE,sha256=krRiGp-a9-1nH1bWpBEdxyTKLhjLmn6DMVVoIb0zF90,1087
503
- firefighter_incident-0.0.19.dist-info/RECORD,,
502
+ firefighter_incident-0.0.20.dist-info/METADATA,sha256=yI3gWfRG__v2fGUUA1H0zC2W4LpRJzv4xkjSq1RNufY,5541
503
+ firefighter_incident-0.0.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
504
+ firefighter_incident-0.0.20.dist-info/entry_points.txt,sha256=c13meJbv7YNmYz7MipMOQwzQ5IeFOPXUBYAJ44XMQsM,61
505
+ firefighter_incident-0.0.20.dist-info/licenses/LICENSE,sha256=krRiGp-a9-1nH1bWpBEdxyTKLhjLmn6DMVVoIb0zF90,1087
506
+ firefighter_incident-0.0.20.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ """Tests for jira_app module."""
@@ -0,0 +1,24 @@
1
+ """Fixtures for jira_app tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from unittest.mock import Mock, patch
6
+
7
+ import pytest
8
+
9
+ from firefighter.jira_app.client import JiraClient
10
+
11
+
12
+ @pytest.fixture
13
+ def mock_jira_api():
14
+ """Create a mock JIRA API object."""
15
+ return Mock()
16
+
17
+
18
+ @pytest.fixture
19
+ def jira_client(mock_jira_api):
20
+ """Create a JiraClient with mocked JIRA API."""
21
+ with patch("firefighter.jira_app.client.JIRA", return_value=mock_jira_api):
22
+ client = JiraClient()
23
+ client.jira = mock_jira_api
24
+ return client
@@ -0,0 +1,135 @@
1
+ """Tests for JiraClient.get_watchers_from_jira_ticket method."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from unittest.mock import Mock
6
+
7
+ import pytest
8
+ from jira.exceptions import JIRAError
9
+
10
+
11
+ @pytest.mark.django_db
12
+ class TestGetWatchersFromJiraTicket:
13
+ """Test get_watchers_from_jira_ticket method."""
14
+
15
+ def test_get_watchers_success_with_watchers(self, jira_client, mock_jira_api):
16
+ """Test successful retrieval of watchers when watchers exist."""
17
+ # Given
18
+ mock_watchers_response = Mock()
19
+ mock_watchers_response.raw = {
20
+ "watchers": [
21
+ {"accountId": "user1", "displayName": "User One"},
22
+ {"accountId": "user2", "displayName": "User Two"},
23
+ ]
24
+ }
25
+ mock_jira_api.watchers.return_value = mock_watchers_response
26
+
27
+ # When
28
+ result = jira_client.get_watchers_from_jira_ticket(12345)
29
+
30
+ # Then
31
+ mock_jira_api.watchers.assert_called_once_with(12345)
32
+ assert len(result) == 2
33
+ assert result[0]["accountId"] == "user1"
34
+ assert result[1]["accountId"] == "user2"
35
+
36
+ def test_get_watchers_success_empty_list(
37
+ self, jira_client, mock_jira_api, caplog
38
+ ):
39
+ """Test successful retrieval when no watchers exist."""
40
+ # Given
41
+ mock_watchers_response = Mock()
42
+ mock_watchers_response.raw = {"watchers": []}
43
+ mock_jira_api.watchers.return_value = mock_watchers_response
44
+
45
+ # When
46
+ result = jira_client.get_watchers_from_jira_ticket(12345)
47
+
48
+ # Then
49
+ mock_jira_api.watchers.assert_called_once_with(12345)
50
+ assert result == []
51
+ # Should log debug message
52
+ assert "No watchers found for jira_issue_id '12345'" in caplog.text
53
+
54
+ def test_get_watchers_404_ticket_not_found(
55
+ self, jira_client, mock_jira_api, caplog
56
+ ):
57
+ """Test handling of 404 error when ticket doesn't exist."""
58
+ # Given
59
+ jira_error = JIRAError(
60
+ status_code=404,
61
+ text="Issue does not exist or you do not have permission to see it.",
62
+ url="https://jira.example.com/rest/api/2/issue/404295/watchers",
63
+ )
64
+ mock_jira_api.watchers.side_effect = jira_error
65
+
66
+ # When
67
+ result = jira_client.get_watchers_from_jira_ticket(404295)
68
+
69
+ # Then
70
+ mock_jira_api.watchers.assert_called_once_with(404295)
71
+ assert result == []
72
+ # Should log warning
73
+ assert (
74
+ "Jira ticket 404295 not found or no permission to access it"
75
+ in caplog.text
76
+ )
77
+
78
+ def test_get_watchers_404_no_permission(self, jira_client, mock_jira_api, caplog):
79
+ """Test handling of 404 error when bot has no permission."""
80
+ # Given
81
+ jira_error = JIRAError(
82
+ status_code=404,
83
+ text="Issue does not exist or you do not have permission to see it.",
84
+ url="https://jira.example.com/rest/api/2/issue/999999/watchers",
85
+ )
86
+ mock_jira_api.watchers.side_effect = jira_error
87
+
88
+ # When
89
+ result = jira_client.get_watchers_from_jira_ticket("999999")
90
+
91
+ # Then
92
+ assert result == []
93
+ assert "not found or no permission" in caplog.text
94
+
95
+ def test_get_watchers_other_jira_error_raised(self, jira_client, mock_jira_api):
96
+ """Test that non-404 JIRA errors are re-raised."""
97
+ # Given
98
+ jira_error = JIRAError(
99
+ status_code=500, text="Internal Server Error", url="https://jira.example.com"
100
+ )
101
+ mock_jira_api.watchers.side_effect = jira_error
102
+
103
+ # When / Then
104
+ with pytest.raises(JIRAError) as exc_info:
105
+ jira_client.get_watchers_from_jira_ticket(12345)
106
+
107
+ assert exc_info.value.status_code == 500
108
+
109
+ def test_get_watchers_403_error_raised(self, jira_client, mock_jira_api):
110
+ """Test that 403 (Forbidden) errors are re-raised."""
111
+ # Given
112
+ jira_error = JIRAError(
113
+ status_code=403, text="Forbidden", url="https://jira.example.com"
114
+ )
115
+ mock_jira_api.watchers.side_effect = jira_error
116
+
117
+ # When / Then
118
+ with pytest.raises(JIRAError) as exc_info:
119
+ jira_client.get_watchers_from_jira_ticket(12345)
120
+
121
+ assert exc_info.value.status_code == 403
122
+
123
+ def test_get_watchers_with_string_id(self, jira_client, mock_jira_api):
124
+ """Test get_watchers with string issue ID."""
125
+ # Given
126
+ mock_watchers_response = Mock()
127
+ mock_watchers_response.raw = {"watchers": [{"accountId": "user1"}]}
128
+ mock_jira_api.watchers.return_value = mock_watchers_response
129
+
130
+ # When
131
+ result = jira_client.get_watchers_from_jira_ticket("INCIDENT-123")
132
+
133
+ # Then
134
+ mock_jira_api.watchers.assert_called_once_with("INCIDENT-123")
135
+ assert len(result) == 1
@@ -553,3 +553,58 @@ class TestGetInternalAlertConversations:
553
553
 
554
554
  # Then
555
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