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
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
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."""
|
firefighter/jira_app/client.py
CHANGED
|
@@ -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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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(
|
firefighter/raid/client.py
CHANGED
|
@@ -72,6 +72,7 @@ class RaidJiraClient(JiraClient):
|
|
|
72
72
|
extra_args["customfield_10896"] = str(zoho_desk_ticket_id)
|
|
73
73
|
if zendesk_ticket_id:
|
|
74
74
|
extra_args["customfield_10895"] = str(zendesk_ticket_id)
|
|
75
|
+
|
|
75
76
|
if seller_contract_id:
|
|
76
77
|
description_addendum.append(
|
|
77
78
|
f"Seller link to TOOLBOX: {TOOLBOX_URL}?seller_id={seller_contract_id}"
|
|
@@ -109,6 +110,7 @@ class RaidJiraClient(JiraClient):
|
|
|
109
110
|
project = (
|
|
110
111
|
feature_team.jira_project_key if feature_team else RAID_JIRA_PROJECT_KEY
|
|
111
112
|
)
|
|
113
|
+
|
|
112
114
|
issue = self.jira.create_issue(
|
|
113
115
|
project=project,
|
|
114
116
|
summary=summary,
|
firefighter/raid/forms.py
CHANGED
|
@@ -107,8 +107,12 @@ def alert_slack_new_jira_ticket(
|
|
|
107
107
|
reporter_user: User | None = None,
|
|
108
108
|
reporter_email: str | None = None,
|
|
109
109
|
) -> None:
|
|
110
|
-
# These alerts are not
|
|
111
|
-
if
|
|
110
|
+
# These alerts are for P4-P5 incidents only, not P1-P3 critical incidents
|
|
111
|
+
if (
|
|
112
|
+
hasattr(jira_ticket, "incident")
|
|
113
|
+
and jira_ticket.incident
|
|
114
|
+
and jira_ticket.incident.priority.value <= 3
|
|
115
|
+
):
|
|
112
116
|
raise ValueError("This is a critical incident, not a raid incident.")
|
|
113
117
|
|
|
114
118
|
# Get the reporter's email and user from ticket if not provided
|
|
@@ -35,6 +35,7 @@ if TYPE_CHECKING:
|
|
|
35
35
|
"description": "Description test where you want to depict your issue",
|
|
36
36
|
"seller_contract_id": "12345678",
|
|
37
37
|
"zoho": "https://crmplus.zoho.eu/mycrmlink/index.do/cxapp/agent/mycompany/all/tickets/details/123456789",
|
|
38
|
+
"zendesk": "12345",
|
|
38
39
|
"platform": "FR",
|
|
39
40
|
"reporter_email": "john.doe@mycompany.com",
|
|
40
41
|
"incident_category": "Payment Processing",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firefighter-incident
|
|
3
|
-
Version: 0.0.
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
@@ -311,8 +311,8 @@ firefighter/pagerduty/views/oncall_trigger.py,sha256=LYHpWyEaR6O8NazmsTl5ydtw1XH
|
|
|
311
311
|
firefighter/raid/__init__.py,sha256=nMNmvHCSkyLQsdhTow7myMU62vXk1e755gUntVfFFlY,154
|
|
312
312
|
firefighter/raid/admin.py,sha256=WhIHaRAv7JPp2NH27w7_0JfvGHrvoyRJhYr3_WwedrA,1117
|
|
313
313
|
firefighter/raid/apps.py,sha256=olDKua1rqhhIJUhCu6A2PnPWloW_jbeD4XWL94b2owo,1117
|
|
314
|
-
firefighter/raid/client.py,sha256=
|
|
315
|
-
firefighter/raid/forms.py,sha256=
|
|
314
|
+
firefighter/raid/client.py,sha256=5PzW7Ub0rU4Z_gdGqPB42CMcM5TCUoWeOZRZZmnhJXg,8126
|
|
315
|
+
firefighter/raid/forms.py,sha256=XivNADFHOl2ewNRXev17HthDIUvwt4pdH9t4So-BE-A,11968
|
|
316
316
|
firefighter/raid/messages.py,sha256=e75kwi0hCe5ChwU4t-_6Q3Rcy22MLLdVSsYyjvG2SCM,5542
|
|
317
317
|
firefighter/raid/models.py,sha256=29Smci739K1ZdcMu7uXYvoVEhgDpwLQoCzBbc5wvwhs,2211
|
|
318
318
|
firefighter/raid/resources.py,sha256=39GhITs3OAWA1eSPZme-rLd818kuz7gwYzdN38zNz8Y,436
|
|
@@ -328,7 +328,7 @@ firefighter/raid/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
|
328
328
|
firefighter/raid/signals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
329
329
|
firefighter/raid/signals/incident_updated.py,sha256=5LYeqvgTrfINu_SOwkZa3hD6rvTsl8BL8Py-immqK3I,1374
|
|
330
330
|
firefighter/raid/tasks/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
331
|
-
firefighter/raid/views/__init__.py,sha256=
|
|
331
|
+
firefighter/raid/views/__init__.py,sha256=C3WhAJfEoUasi2afHPuLpKiuRYixK-tc3j0-2Rw_g3E,5210
|
|
332
332
|
firefighter/slack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
333
333
|
firefighter/slack/admin.py,sha256=pNJbA-szxUUrghxv_Z0BNezu6lULDzFcOu_K5i4m7Cs,13963
|
|
334
334
|
firefighter/slack/apps.py,sha256=gR0zWTtqT58tjPayBX22ZSzMkLiNpmoOvLShNvhJA6Q,664
|
|
@@ -463,18 +463,24 @@ 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
|
-
firefighter_tests/test_raid/
|
|
470
|
+
firefighter_tests/test_raid/test_raid_alert_p4_p5.py,sha256=rz9orbt1E1vJ5POQyVZ6-SEPvqB55-xhwIWHicdfgDg,9356
|
|
471
|
+
firefighter_tests/test_raid/test_raid_client.py,sha256=KTqELERpWno7XhF9LpabpxkHoJiWWrryUg5LHi5Yfjo,22456
|
|
468
472
|
firefighter_tests/test_raid/test_raid_client_users.py,sha256=9uma1wBhaiCoG75XAZHqpT8oGTnqFJRMCi7a3XctNtM,3631
|
|
469
|
-
firefighter_tests/test_raid/test_raid_forms.py,sha256=
|
|
473
|
+
firefighter_tests/test_raid/test_raid_forms.py,sha256=8hiXftYPO_lY0heKHqoreUW2s8AcedUme48wTq4hwNE,21931
|
|
470
474
|
firefighter_tests/test_raid/test_raid_models.py,sha256=nq-fVClB_P24W8WrZruOPt8wlHUVGYI7wxJR7tH6AnM,5042
|
|
471
|
-
firefighter_tests/test_raid/test_raid_serializers.py,sha256=
|
|
475
|
+
firefighter_tests/test_raid/test_raid_serializers.py,sha256=xkMK9npvDMAr2pUpP4mFbLszrR6qyTzsJRd-uNOzEhM,22560
|
|
472
476
|
firefighter_tests/test_raid/test_raid_service.py,sha256=AqVyrRjW2tr0sfbXS4lGlJ7mcxB2ACEXAR8Bv0pXnj0,16755
|
|
473
477
|
firefighter_tests/test_raid/test_raid_signals.py,sha256=twNxB3NQs58s8ZcP-wsZXG7iTHR4yKFC9x-zpq1ZuAo,7002
|
|
474
478
|
firefighter_tests/test_raid/test_raid_transitions.py,sha256=mtmMKwukxmZSM-R619BQ3Z_2AB-qY6imvDgUF0A3_tw,4784
|
|
475
479
|
firefighter_tests/test_raid/test_raid_utils.py,sha256=i6JBwim1G-qynwxprNZekxl9K7Vis4FFvNkw3wT2jTM,1016
|
|
476
480
|
firefighter_tests/test_raid/test_raid_views.py,sha256=paAhh4k2EDlmG1ehwNhMuYIhr1okqrvM7xlkaTAo2V0,6825
|
|
481
|
+
firefighter_tests/test_raid/test_zendesk_integration.py,sha256=RtHIKWog_YZ6g-uZSr37k5XjAGe0c8WEeBWqkt1ZNqk,7113
|
|
477
482
|
firefighter_tests/test_slack/conftest.py,sha256=MCg04JFQ0iBeYUN_moEvvH4PVA3wE48Q6LkrIw7Bic0,2015
|
|
483
|
+
firefighter_tests/test_slack/test_conversation_tags.py,sha256=nNqTZRRBfF6Z4wpFSYZ83DH7_mR9z9F9LAHSq8AHFN0,12744
|
|
478
484
|
firefighter_tests/test_slack/test_signals_downgrade.py,sha256=mgl4H5vwr2kImf6g4IZbhv7YEPmMzbYSaVr8E6taL88,5420
|
|
479
485
|
firefighter_tests/test_slack/test_slack_utils.py,sha256=9PLobMNXh3xDyFuwzcQFpKJhe4j__sIgf_WRHIpANJw,3957
|
|
480
486
|
firefighter_tests/test_slack/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -491,10 +497,10 @@ firefighter_tests/test_slack/views/modals/test_open.py,sha256=IzgG9le5NN_CvltehA
|
|
|
491
497
|
firefighter_tests/test_slack/views/modals/test_opening_unified.py,sha256=OejtLyc_mehav2TDaLzUnhilMNvhCzc6T4FodCqfQPk,17406
|
|
492
498
|
firefighter_tests/test_slack/views/modals/test_send_sos.py,sha256=_rE6jD-gOzcGyhlY0R9GzlGtPx65oOOguJYdENgxtLc,1289
|
|
493
499
|
firefighter_tests/test_slack/views/modals/test_status.py,sha256=oQzPfwdg2tkbo9nfkO1GfS3WydxqSC6vy1AZjZDKT30,2226
|
|
494
|
-
firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=
|
|
500
|
+
firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=OVGqVSeim7aancl0RnGzqwM21Qs3ZVFlaryZiOWi_wM,55748
|
|
495
501
|
firefighter_tests/test_slack/views/modals/test_utils.py,sha256=DJd2n9q6fFu8UuCRdiq9U_Cn19MdnC5c-ydLLrk6rkc,5218
|
|
496
|
-
firefighter_incident-0.0.
|
|
497
|
-
firefighter_incident-0.0.
|
|
498
|
-
firefighter_incident-0.0.
|
|
499
|
-
firefighter_incident-0.0.
|
|
500
|
-
firefighter_incident-0.0.
|
|
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
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Tests for RAID alert notifications for P4-P5 incidents with linked Incident objects.
|
|
2
|
+
|
|
3
|
+
This module tests the alert_slack_new_jira_ticket() function behavior when called
|
|
4
|
+
with P4-P5 JiraTickets that have associated Incident objects (since 0.0.17 unified workflow).
|
|
5
|
+
|
|
6
|
+
Before 0.0.17: P4-P5 created only JiraTicket (no Incident) → alerts worked
|
|
7
|
+
Since 0.0.17: P4-P5 create Incident + JiraTicket → alerts broken due to incorrect check
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from unittest.mock import patch
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from firefighter.incidents.factories import (
|
|
17
|
+
IncidentCategoryFactory,
|
|
18
|
+
IncidentFactory,
|
|
19
|
+
UserFactory,
|
|
20
|
+
)
|
|
21
|
+
from firefighter.incidents.models.priority import Priority
|
|
22
|
+
from firefighter.jira_app.models import JiraUser
|
|
23
|
+
from firefighter.raid.forms import (
|
|
24
|
+
alert_slack_new_jira_ticket,
|
|
25
|
+
get_internal_alert_conversations,
|
|
26
|
+
)
|
|
27
|
+
from firefighter.raid.models import JiraTicket
|
|
28
|
+
from firefighter.slack.models.conversation import Conversation
|
|
29
|
+
from firefighter.slack.models.user import SlackUser
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.django_db
|
|
33
|
+
class TestAlertSlackNewJiraTicketWithIncident:
|
|
34
|
+
"""Test alert_slack_new_jira_ticket for P4-P5 with linked Incident (unified workflow)."""
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def p4_priority(self):
|
|
38
|
+
"""Get or create P4 priority."""
|
|
39
|
+
priority, _ = Priority.objects.get_or_create(value=4, defaults={"name": "P4"})
|
|
40
|
+
return priority
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def p5_priority(self):
|
|
44
|
+
"""Get or create P5 priority."""
|
|
45
|
+
priority, _ = Priority.objects.get_or_create(value=5, defaults={"name": "P5"})
|
|
46
|
+
return priority
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def incident_category(self):
|
|
50
|
+
"""Create incident category."""
|
|
51
|
+
return IncidentCategoryFactory()
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def reporter_user_with_slack(self):
|
|
55
|
+
"""Create user with Slack account."""
|
|
56
|
+
user = UserFactory(email="reporter@manomano.com")
|
|
57
|
+
jira_user = JiraUser.objects.create(id="jira-123", user=user)
|
|
58
|
+
slack_user = SlackUser.objects.create(user=user, slack_id="U12345")
|
|
59
|
+
return user, jira_user, slack_user
|
|
60
|
+
|
|
61
|
+
@pytest.fixture
|
|
62
|
+
def raid_alert_channel_sbi(self):
|
|
63
|
+
"""Create the raid_alert__sbi_normal channel for SBI tickets."""
|
|
64
|
+
return Conversation.objects.create(
|
|
65
|
+
name="incidents",
|
|
66
|
+
channel_id="C_INCIDENTS",
|
|
67
|
+
tag="raid_alert__sbi_normal",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def test_alert_p4_incident_with_linked_incident_should_succeed(
|
|
71
|
+
self,
|
|
72
|
+
p4_priority,
|
|
73
|
+
incident_category,
|
|
74
|
+
reporter_user_with_slack,
|
|
75
|
+
raid_alert_channel_sbi, # noqa: ARG002 - fixture creates channel in DB
|
|
76
|
+
):
|
|
77
|
+
"""Test that P4 JiraTicket with linked Incident can send raid_alert notifications.
|
|
78
|
+
|
|
79
|
+
This test reproduces the CURRENT BROKEN behavior since 0.0.17:
|
|
80
|
+
- UnifiedIncidentForm creates Incident + JiraTicket for P4-P5
|
|
81
|
+
- alert_slack_new_jira_ticket() raises ValueError because jira_ticket.incident exists
|
|
82
|
+
- Notifications to #incidents channel are never sent
|
|
83
|
+
|
|
84
|
+
Expected: Should send notifications (test will FAIL until bug is fixed)
|
|
85
|
+
"""
|
|
86
|
+
user, jira_user, _ = reporter_user_with_slack
|
|
87
|
+
|
|
88
|
+
# Create P4 incident (simulating UnifiedIncidentForm behavior)
|
|
89
|
+
incident = IncidentFactory(
|
|
90
|
+
priority=p4_priority,
|
|
91
|
+
incident_category=incident_category,
|
|
92
|
+
created_by=user,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Create JiraTicket linked to Incident (UNIFIED workflow since 0.0.17)
|
|
96
|
+
jira_ticket = JiraTicket.objects.create(
|
|
97
|
+
id=12345,
|
|
98
|
+
key="SBI-12345",
|
|
99
|
+
summary="P4 incident with linked Incident",
|
|
100
|
+
business_impact="N/A",
|
|
101
|
+
project_key="SBI",
|
|
102
|
+
reporter=jira_user,
|
|
103
|
+
incident=incident, # ← This is what breaks alert_slack_new_jira_ticket()
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Mock Slack API to capture messages sent
|
|
107
|
+
with (
|
|
108
|
+
patch(
|
|
109
|
+
"firefighter.slack.models.user.SlackUser.send_private_message"
|
|
110
|
+
) as mock_dm,
|
|
111
|
+
patch(
|
|
112
|
+
"firefighter.slack.models.conversation.Conversation.send_message_and_save"
|
|
113
|
+
) as mock_channel_msg,
|
|
114
|
+
):
|
|
115
|
+
# This should NOT raise ValueError and should send notifications
|
|
116
|
+
alert_slack_new_jira_ticket(
|
|
117
|
+
jira_ticket, reporter_user=user, reporter_email=user.email
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Verify DM was sent to reporter
|
|
121
|
+
assert mock_dm.called, "Should send DM to reporter"
|
|
122
|
+
|
|
123
|
+
# Verify message was sent to #incidents channel
|
|
124
|
+
assert (
|
|
125
|
+
mock_channel_msg.called
|
|
126
|
+
), "Should send message to raid_alert channel"
|
|
127
|
+
|
|
128
|
+
def test_alert_p5_incident_with_linked_incident_should_succeed(
|
|
129
|
+
self,
|
|
130
|
+
p5_priority,
|
|
131
|
+
incident_category,
|
|
132
|
+
reporter_user_with_slack,
|
|
133
|
+
raid_alert_channel_sbi, # noqa: ARG002 - fixture creates channel in DB
|
|
134
|
+
):
|
|
135
|
+
"""Test that P5 JiraTicket with linked Incident can send raid_alert notifications."""
|
|
136
|
+
user, jira_user, _ = reporter_user_with_slack
|
|
137
|
+
|
|
138
|
+
# Create P5 incident
|
|
139
|
+
incident = IncidentFactory(
|
|
140
|
+
priority=p5_priority,
|
|
141
|
+
incident_category=incident_category,
|
|
142
|
+
created_by=user,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Create JiraTicket linked to Incident
|
|
146
|
+
jira_ticket = JiraTicket.objects.create(
|
|
147
|
+
id=12346,
|
|
148
|
+
key="SBI-12346",
|
|
149
|
+
summary="P5 incident with linked Incident",
|
|
150
|
+
business_impact="N/A",
|
|
151
|
+
project_key="SBI",
|
|
152
|
+
reporter=jira_user,
|
|
153
|
+
incident=incident,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Mock Slack API
|
|
157
|
+
with (
|
|
158
|
+
patch(
|
|
159
|
+
"firefighter.slack.models.user.SlackUser.send_private_message"
|
|
160
|
+
) as mock_dm,
|
|
161
|
+
patch(
|
|
162
|
+
"firefighter.slack.models.conversation.Conversation.send_message_and_save"
|
|
163
|
+
) as mock_channel_msg,
|
|
164
|
+
):
|
|
165
|
+
# Should succeed for P5 as well
|
|
166
|
+
alert_slack_new_jira_ticket(
|
|
167
|
+
jira_ticket, reporter_user=user, reporter_email=user.email
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
assert mock_dm.called
|
|
171
|
+
assert mock_channel_msg.called
|
|
172
|
+
|
|
173
|
+
def test_alert_p1_incident_should_fail(
|
|
174
|
+
self, incident_category, reporter_user_with_slack
|
|
175
|
+
):
|
|
176
|
+
"""Test that P1 JiraTicket with linked Incident correctly raises ValueError.
|
|
177
|
+
|
|
178
|
+
P1-P3 incidents should NOT use raid_alert notifications.
|
|
179
|
+
They have dedicated Slack channels and use different notification flow.
|
|
180
|
+
"""
|
|
181
|
+
user, jira_user, _ = reporter_user_with_slack
|
|
182
|
+
p1_priority, _ = Priority.objects.get_or_create(value=1, defaults={"name": "P1"})
|
|
183
|
+
|
|
184
|
+
incident = IncidentFactory(
|
|
185
|
+
priority=p1_priority,
|
|
186
|
+
incident_category=incident_category,
|
|
187
|
+
created_by=user,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
jira_ticket = JiraTicket.objects.create(
|
|
191
|
+
id=12347,
|
|
192
|
+
key="SBI-12347",
|
|
193
|
+
summary="P1 critical incident",
|
|
194
|
+
business_impact="High",
|
|
195
|
+
project_key="SBI",
|
|
196
|
+
reporter=jira_user,
|
|
197
|
+
incident=incident,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# P1 should correctly raise ValueError
|
|
201
|
+
with pytest.raises(
|
|
202
|
+
ValueError, match="This is a critical incident, not a raid incident"
|
|
203
|
+
):
|
|
204
|
+
alert_slack_new_jira_ticket(
|
|
205
|
+
jira_ticket, reporter_user=user, reporter_email=user.email
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def test_get_internal_alert_conversations_for_normal_impact(
|
|
209
|
+
self, raid_alert_channel_sbi # noqa: ARG002 - fixture creates channel in DB
|
|
210
|
+
):
|
|
211
|
+
"""Test that get_internal_alert_conversations finds raid_alert__sbi_normal channel."""
|
|
212
|
+
# Create a JiraTicket with normal/N/A business impact for SBI project
|
|
213
|
+
jira_user = JiraUser.objects.create(id="jira-999", user=UserFactory())
|
|
214
|
+
jira_ticket = JiraTicket.objects.create(
|
|
215
|
+
id=99999,
|
|
216
|
+
key="SBI-99999",
|
|
217
|
+
summary="Test ticket",
|
|
218
|
+
business_impact="N/A",
|
|
219
|
+
project_key="SBI",
|
|
220
|
+
reporter=jira_user,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Should find the raid_alert__sbi_normal channel
|
|
224
|
+
channels = get_internal_alert_conversations(jira_ticket)
|
|
225
|
+
channel_list = list(channels)
|
|
226
|
+
|
|
227
|
+
assert len(channel_list) == 1
|
|
228
|
+
assert channel_list[0].tag == "raid_alert__sbi_normal"
|
|
229
|
+
assert channel_list[0].name == "incidents"
|
|
230
|
+
|
|
231
|
+
def test_get_internal_alert_conversations_for_high_impact(self):
|
|
232
|
+
"""Test that get_internal_alert_conversations finds raid_alert__sbi_high channel."""
|
|
233
|
+
# Create channel for high impact on SBI
|
|
234
|
+
Conversation.objects.create(
|
|
235
|
+
name="incidents-high-impact",
|
|
236
|
+
channel_id="C_INCIDENTS_HIGH",
|
|
237
|
+
tag="raid_alert__sbi_high",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Create JiraTicket with High business impact for SBI
|
|
241
|
+
jira_user = JiraUser.objects.create(id="jira-998", user=UserFactory())
|
|
242
|
+
jira_ticket = JiraTicket.objects.create(
|
|
243
|
+
id=99998,
|
|
244
|
+
key="SBI-99998",
|
|
245
|
+
summary="Test high impact ticket",
|
|
246
|
+
business_impact="High",
|
|
247
|
+
project_key="SBI",
|
|
248
|
+
reporter=jira_user,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
channels = get_internal_alert_conversations(jira_ticket)
|
|
252
|
+
channel_list = list(channels)
|
|
253
|
+
|
|
254
|
+
assert len(channel_list) == 1
|
|
255
|
+
assert channel_list[0].tag == "raid_alert__sbi_high"
|
|
@@ -368,6 +368,38 @@ class TestRaidJiraClientBasics:
|
|
|
368
368
|
priority=1,
|
|
369
369
|
)
|
|
370
370
|
|
|
371
|
+
def test_create_issue_zendesk_field_mapping(self, mock_jira_client):
|
|
372
|
+
"""Test that zendesk_ticket_id is correctly mapped to customfield_10895."""
|
|
373
|
+
mock_issue = Mock()
|
|
374
|
+
mock_issue.raw = {
|
|
375
|
+
"id": "12355",
|
|
376
|
+
"key": "TEST-130",
|
|
377
|
+
"fields": {
|
|
378
|
+
"summary": "Test zendesk mapping",
|
|
379
|
+
"description": "Test description",
|
|
380
|
+
"reporter": {"accountId": "reporter123"},
|
|
381
|
+
"issuetype": {"name": "Bug"},
|
|
382
|
+
},
|
|
383
|
+
}
|
|
384
|
+
mock_jira_client.jira.create_issue.return_value = mock_issue
|
|
385
|
+
|
|
386
|
+
result = mock_jira_client.create_issue(
|
|
387
|
+
issuetype="Bug",
|
|
388
|
+
summary="Test zendesk mapping",
|
|
389
|
+
description="Test description",
|
|
390
|
+
assignee=None,
|
|
391
|
+
reporter="test_reporter",
|
|
392
|
+
priority=1,
|
|
393
|
+
zendesk_ticket_id="ZD-98765",
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# Verify create_issue was called with customfield_10895
|
|
397
|
+
call_kwargs = mock_jira_client.jira.create_issue.call_args[1]
|
|
398
|
+
assert "customfield_10895" in call_kwargs
|
|
399
|
+
assert call_kwargs["customfield_10895"] == "ZD-98765"
|
|
400
|
+
assert result["id"] == 12355
|
|
401
|
+
assert result["key"] == "TEST-130"
|
|
402
|
+
|
|
371
403
|
def test_jira_object_static_method(self):
|
|
372
404
|
"""Test _jira_object static method."""
|
|
373
405
|
test_issue = {
|