firefighter-incident 0.0.17__py3-none-any.whl → 0.0.19__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/raid/client.py +2 -0
- firefighter/raid/forms.py +6 -2
- firefighter/raid/serializers.py +17 -0
- firefighter/raid/views/__init__.py +1 -0
- {firefighter_incident-0.0.17.dist-info → firefighter_incident-0.0.19.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.17.dist-info → firefighter_incident-0.0.19.dist-info}/RECORD +18 -15
- firefighter_tests/test_api/test_api_landbot.py +38 -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 +6 -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.17.dist-info → firefighter_incident-0.0.19.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.17.dist-info → firefighter_incident-0.0.19.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.17.dist-info → firefighter_incident-0.0.19.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.19'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 19)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
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
|
firefighter/raid/serializers.py
CHANGED
|
@@ -98,6 +98,13 @@ class LandbotIssueRequestSerializer(serializers.ModelSerializer[JiraTicket]):
|
|
|
98
98
|
allow_null=True,
|
|
99
99
|
allow_blank=True,
|
|
100
100
|
)
|
|
101
|
+
zendesk = serializers.CharField(
|
|
102
|
+
max_length=256,
|
|
103
|
+
write_only=True,
|
|
104
|
+
allow_null=True,
|
|
105
|
+
allow_blank=True,
|
|
106
|
+
required=False,
|
|
107
|
+
)
|
|
101
108
|
platform = serializers.ChoiceField(
|
|
102
109
|
write_only=True, choices=["ES", "IT", "FR", "UK", "DE", "All", "Internal"]
|
|
103
110
|
)
|
|
@@ -106,6 +113,8 @@ class LandbotIssueRequestSerializer(serializers.ModelSerializer[JiraTicket]):
|
|
|
106
113
|
labels = serializers.ListField(
|
|
107
114
|
required=False,
|
|
108
115
|
write_only=True,
|
|
116
|
+
allow_null=True,
|
|
117
|
+
default=list,
|
|
109
118
|
child=serializers.CharField(
|
|
110
119
|
max_length=128,
|
|
111
120
|
allow_blank=False,
|
|
@@ -165,6 +174,12 @@ class LandbotIssueRequestSerializer(serializers.ModelSerializer[JiraTicket]):
|
|
|
165
174
|
],
|
|
166
175
|
)
|
|
167
176
|
|
|
177
|
+
def validate_labels(self, value: list[str] | None) -> list[str]:
|
|
178
|
+
"""Transform null labels to empty list."""
|
|
179
|
+
if value is None:
|
|
180
|
+
return []
|
|
181
|
+
return value
|
|
182
|
+
|
|
168
183
|
def validate_environments(self, value: list[str] | None) -> list[str] | Any:
|
|
169
184
|
if not value:
|
|
170
185
|
return self.fields["environments"].default
|
|
@@ -198,6 +213,7 @@ class LandbotIssueRequestSerializer(serializers.ModelSerializer[JiraTicket]):
|
|
|
198
213
|
priority=validated_data["priority"],
|
|
199
214
|
seller_contract_id=validated_data["seller_contract_id"],
|
|
200
215
|
zoho_desk_ticket_id=validated_data["zoho"],
|
|
216
|
+
zendesk_ticket_id=validated_data.get("zendesk"),
|
|
201
217
|
platform=validated_data["platform"],
|
|
202
218
|
incident_category=validated_data["incident_category"],
|
|
203
219
|
business_impact=validated_data["business_impact"],
|
|
@@ -238,6 +254,7 @@ class LandbotIssueRequestSerializer(serializers.ModelSerializer[JiraTicket]):
|
|
|
238
254
|
"description",
|
|
239
255
|
"seller_contract_id",
|
|
240
256
|
"zoho",
|
|
257
|
+
"zendesk",
|
|
241
258
|
"platform",
|
|
242
259
|
"reporter_email",
|
|
243
260
|
"incident_category",
|
|
@@ -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.19
|
|
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=2t1q5JYEYkNwbHa29phdM-U8VWR1m6EwW48aFKWPj_w,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
|
|
@@ -311,12 +311,12 @@ 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
|
|
319
|
-
firefighter/raid/serializers.py,sha256=
|
|
319
|
+
firefighter/raid/serializers.py,sha256=hJspJBTPvIs3Y_zpXSwui0aGt4cyIxrwFHwhpT-G58k,11967
|
|
320
320
|
firefighter/raid/service.py,sha256=rxRjEpA5wg4JRe4UrGW-y8C8quHvGlJzQ0U8rdy-DyM,8490
|
|
321
321
|
firefighter/raid/types.py,sha256=0lFy_Wl3Ekyy_sxTHPRxMn3kjEJVWCTPjmefDlrPqMQ,438
|
|
322
322
|
firefighter/raid/urls.py,sha256=oESkDY2tfZcnPGUgULqixvbV3Z7YsZfeI10RX3A5tZY,924
|
|
@@ -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
|
|
@@ -438,7 +438,7 @@ firefighter_fixtures/incidents/priorities.json,sha256=ZPqfC2GD7vgLBnGNxVcDpjxCLt
|
|
|
438
438
|
firefighter_fixtures/incidents/severities.json,sha256=hsaG3TT0oaRVvZVaeU6BJGddFn_gewJ4E7Lk7ufII6U,2467
|
|
439
439
|
firefighter_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
440
440
|
firefighter_tests/conftest.py,sha256=fyVZNk4PX0ATjIjmq0BYjC9k3og26kkpC73npEgFlv8,3228
|
|
441
|
-
firefighter_tests/test_api/test_api_landbot.py,sha256=
|
|
441
|
+
firefighter_tests/test_api/test_api_landbot.py,sha256=a5Fk-8GCLCdwqJOovFzw75uDq6pn6sKIcTRHpbMzx_M,4602
|
|
442
442
|
firefighter_tests/test_api/test_api_urls.py,sha256=Nn7Flq7dWGYJjgZK5HGdGECFMwKiDdfDYI0g09gXK-c,1936
|
|
443
443
|
firefighter_tests/test_confluence/test_confluence_utils.py,sha256=hD5M4mYwrHlil9KPARNQ5OINdRc5njfYCIdq8a6jUB8,1847
|
|
444
444
|
firefighter_tests/test_firefighter/test_firefighter_utils.py,sha256=og79bkErCBQB-xkw88J-ronT0dDEChuqlMke2ElxATo,3590
|
|
@@ -464,17 +464,20 @@ firefighter_tests/test_incidents/test_utils/test_date_utils.py,sha256=ogP7qOEwIt
|
|
|
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
466
|
firefighter_tests/test_raid/conftest.py,sha256=i_TOquYIMLDyVQ97uqxTqPJszVz4qq7L_Q7YJxTuS1o,4090
|
|
467
|
-
firefighter_tests/test_raid/
|
|
467
|
+
firefighter_tests/test_raid/test_raid_alert_p4_p5.py,sha256=rz9orbt1E1vJ5POQyVZ6-SEPvqB55-xhwIWHicdfgDg,9356
|
|
468
|
+
firefighter_tests/test_raid/test_raid_client.py,sha256=KTqELERpWno7XhF9LpabpxkHoJiWWrryUg5LHi5Yfjo,22456
|
|
468
469
|
firefighter_tests/test_raid/test_raid_client_users.py,sha256=9uma1wBhaiCoG75XAZHqpT8oGTnqFJRMCi7a3XctNtM,3631
|
|
469
|
-
firefighter_tests/test_raid/test_raid_forms.py,sha256=
|
|
470
|
+
firefighter_tests/test_raid/test_raid_forms.py,sha256=lLZvhczAge0Ftt3OdFEH2BcE1OMF6_vvy1ghHAe3HWM,19758
|
|
470
471
|
firefighter_tests/test_raid/test_raid_models.py,sha256=nq-fVClB_P24W8WrZruOPt8wlHUVGYI7wxJR7tH6AnM,5042
|
|
471
|
-
firefighter_tests/test_raid/test_raid_serializers.py,sha256=
|
|
472
|
+
firefighter_tests/test_raid/test_raid_serializers.py,sha256=xkMK9npvDMAr2pUpP4mFbLszrR6qyTzsJRd-uNOzEhM,22560
|
|
472
473
|
firefighter_tests/test_raid/test_raid_service.py,sha256=AqVyrRjW2tr0sfbXS4lGlJ7mcxB2ACEXAR8Bv0pXnj0,16755
|
|
473
474
|
firefighter_tests/test_raid/test_raid_signals.py,sha256=twNxB3NQs58s8ZcP-wsZXG7iTHR4yKFC9x-zpq1ZuAo,7002
|
|
474
475
|
firefighter_tests/test_raid/test_raid_transitions.py,sha256=mtmMKwukxmZSM-R619BQ3Z_2AB-qY6imvDgUF0A3_tw,4784
|
|
475
476
|
firefighter_tests/test_raid/test_raid_utils.py,sha256=i6JBwim1G-qynwxprNZekxl9K7Vis4FFvNkw3wT2jTM,1016
|
|
476
477
|
firefighter_tests/test_raid/test_raid_views.py,sha256=paAhh4k2EDlmG1ehwNhMuYIhr1okqrvM7xlkaTAo2V0,6825
|
|
478
|
+
firefighter_tests/test_raid/test_zendesk_integration.py,sha256=RtHIKWog_YZ6g-uZSr37k5XjAGe0c8WEeBWqkt1ZNqk,7113
|
|
477
479
|
firefighter_tests/test_slack/conftest.py,sha256=MCg04JFQ0iBeYUN_moEvvH4PVA3wE48Q6LkrIw7Bic0,2015
|
|
480
|
+
firefighter_tests/test_slack/test_conversation_tags.py,sha256=nNqTZRRBfF6Z4wpFSYZ83DH7_mR9z9F9LAHSq8AHFN0,12744
|
|
478
481
|
firefighter_tests/test_slack/test_signals_downgrade.py,sha256=mgl4H5vwr2kImf6g4IZbhv7YEPmMzbYSaVr8E6taL88,5420
|
|
479
482
|
firefighter_tests/test_slack/test_slack_utils.py,sha256=9PLobMNXh3xDyFuwzcQFpKJhe4j__sIgf_WRHIpANJw,3957
|
|
480
483
|
firefighter_tests/test_slack/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -491,10 +494,10 @@ firefighter_tests/test_slack/views/modals/test_open.py,sha256=IzgG9le5NN_CvltehA
|
|
|
491
494
|
firefighter_tests/test_slack/views/modals/test_opening_unified.py,sha256=OejtLyc_mehav2TDaLzUnhilMNvhCzc6T4FodCqfQPk,17406
|
|
492
495
|
firefighter_tests/test_slack/views/modals/test_send_sos.py,sha256=_rE6jD-gOzcGyhlY0R9GzlGtPx65oOOguJYdENgxtLc,1289
|
|
493
496
|
firefighter_tests/test_slack/views/modals/test_status.py,sha256=oQzPfwdg2tkbo9nfkO1GfS3WydxqSC6vy1AZjZDKT30,2226
|
|
494
|
-
firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=
|
|
497
|
+
firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=OVGqVSeim7aancl0RnGzqwM21Qs3ZVFlaryZiOWi_wM,55748
|
|
495
498
|
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.
|
|
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,,
|
|
@@ -13,6 +13,7 @@ base_valid_data = {
|
|
|
13
13
|
"description": "Test description",
|
|
14
14
|
"seller_contract_id": "123456",
|
|
15
15
|
"zoho": "https://test_url.com",
|
|
16
|
+
"zendesk": "12345",
|
|
16
17
|
"platform": "FR",
|
|
17
18
|
"reporter_email": "test_email@example.com",
|
|
18
19
|
"incident_category": "Test Area",
|
|
@@ -56,6 +57,20 @@ def test_valid_data_no_labels() -> None:
|
|
|
56
57
|
|
|
57
58
|
# When & Then
|
|
58
59
|
assert serializer.is_valid()
|
|
60
|
+
assert serializer.validated_data["labels"] == []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_valid_data_labels_null() -> None:
|
|
64
|
+
# Given - labels is explicitly null in payload
|
|
65
|
+
valid_data = {
|
|
66
|
+
**base_valid_data,
|
|
67
|
+
"labels": None,
|
|
68
|
+
}
|
|
69
|
+
serializer = LandbotIssueRequestSerializer(data=valid_data)
|
|
70
|
+
|
|
71
|
+
# When & Then
|
|
72
|
+
assert serializer.is_valid()
|
|
73
|
+
assert serializer.validated_data["labels"] == [] # null transformed to empty list
|
|
59
74
|
|
|
60
75
|
|
|
61
76
|
def test_valid_data_no_environments() -> None:
|
|
@@ -97,6 +112,29 @@ def test_invalid_data_label_too_long() -> None:
|
|
|
97
112
|
assert "labels" in serializer.errors
|
|
98
113
|
|
|
99
114
|
|
|
115
|
+
def test_valid_data_with_zendesk() -> None:
|
|
116
|
+
# Given
|
|
117
|
+
valid_data = {
|
|
118
|
+
**base_valid_data,
|
|
119
|
+
"zendesk": "ZD-98765",
|
|
120
|
+
}
|
|
121
|
+
serializer = LandbotIssueRequestSerializer(data=valid_data)
|
|
122
|
+
|
|
123
|
+
# When & Then
|
|
124
|
+
assert serializer.is_valid()
|
|
125
|
+
assert serializer.validated_data["zendesk"] == "ZD-98765"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_valid_data_without_zendesk() -> None:
|
|
129
|
+
# Given
|
|
130
|
+
valid_data = {**base_valid_data}
|
|
131
|
+
del valid_data["zendesk"]
|
|
132
|
+
serializer = LandbotIssueRequestSerializer(data=valid_data)
|
|
133
|
+
|
|
134
|
+
# When & Then
|
|
135
|
+
assert serializer.is_valid()
|
|
136
|
+
|
|
137
|
+
|
|
100
138
|
def test_ignore_empty_string_list_field() -> None:
|
|
101
139
|
serializer_field = IgnoreEmptyStringListField()
|
|
102
140
|
|
|
@@ -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 = {
|
|
@@ -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
|
|
|
@@ -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"
|
|
@@ -331,28 +331,26 @@ class TestUpdateStatusModal:
|
|
|
331
331
|
trigger_incident_workflow.assert_not_called()
|
|
332
332
|
|
|
333
333
|
@staticmethod
|
|
334
|
-
def test_can_close_when_all_conditions_met(mocker: MockerFixture) -> None:
|
|
334
|
+
def test_can_close_when_all_conditions_met(mocker: MockerFixture, priority_factory, environment_factory) -> None:
|
|
335
335
|
"""Test that closing is allowed when all conditions are met for P3+ incidents."""
|
|
336
336
|
# Create a user first
|
|
337
337
|
user = UserFactory.build()
|
|
338
338
|
user.save()
|
|
339
339
|
|
|
340
|
+
# Create P3 priority (needs_postmortem=False) and non-PRD environment
|
|
341
|
+
p3_priority = priority_factory(value=3, name="P3", needs_postmortem=False)
|
|
342
|
+
stg_environment = environment_factory(value="STG", name="Staging")
|
|
343
|
+
|
|
340
344
|
# Create a P3+ incident in MITIGATED status with all conditions met
|
|
341
345
|
incident = IncidentFactory.build(
|
|
342
346
|
_status=IncidentStatus.MITIGATED,
|
|
343
347
|
created_by=user,
|
|
348
|
+
priority=p3_priority,
|
|
349
|
+
environment=stg_environment,
|
|
344
350
|
)
|
|
345
351
|
# IMPORTANT: Save the incident so it has an ID for the form to reference
|
|
346
352
|
incident.save()
|
|
347
353
|
|
|
348
|
-
# Mock needs_postmortem to return False (P3+ incident)
|
|
349
|
-
mocker.patch.object(
|
|
350
|
-
type(incident),
|
|
351
|
-
"needs_postmortem",
|
|
352
|
-
new_callable=PropertyMock,
|
|
353
|
-
return_value=False
|
|
354
|
-
)
|
|
355
|
-
|
|
356
354
|
# Mock can_be_closed to return True (all conditions met)
|
|
357
355
|
mocker.patch.object(
|
|
358
356
|
type(incident),
|
|
File without changes
|
{firefighter_incident-0.0.17.dist-info → firefighter_incident-0.0.19.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{firefighter_incident-0.0.17.dist-info → firefighter_incident-0.0.19.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|