firefighter-incident 0.0.16__py3-none-any.whl → 0.0.17__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.
Files changed (31) hide show
  1. firefighter/_version.py +2 -2
  2. firefighter/incidents/forms/edit.py +5 -3
  3. firefighter/incidents/forms/unified_incident.py +180 -56
  4. firefighter/incidents/forms/update_status.py +94 -58
  5. firefighter/incidents/forms/utils.py +14 -0
  6. firefighter/incidents/models/incident.py +3 -2
  7. firefighter/raid/apps.py +0 -1
  8. firefighter/slack/signals/__init__.py +16 -0
  9. firefighter/slack/signals/incident_updated.py +43 -1
  10. firefighter/slack/utils.py +43 -6
  11. firefighter/slack/views/modals/base_modal/form_utils.py +3 -1
  12. firefighter/slack/views/modals/downgrade_workflow.py +3 -1
  13. firefighter/slack/views/modals/edit.py +53 -7
  14. firefighter/slack/views/modals/opening/set_details.py +20 -0
  15. firefighter_fixtures/incidents/priorities.json +1 -1
  16. {firefighter_incident-0.0.16.dist-info → firefighter_incident-0.0.17.dist-info}/METADATA +1 -1
  17. {firefighter_incident-0.0.16.dist-info → firefighter_incident-0.0.17.dist-info}/RECORD +28 -29
  18. firefighter_tests/test_incidents/test_forms/test_unified_incident_form_integration.py +160 -23
  19. firefighter_tests/test_incidents/test_forms/test_unified_incident_form_p4_p5.py +38 -60
  20. firefighter_tests/test_incidents/test_forms/test_update_status_workflow.py +35 -20
  21. firefighter_tests/test_incidents/test_forms/test_workflow_transitions.py +8 -6
  22. firefighter_tests/test_slack/test_signals_downgrade.py +147 -0
  23. firefighter_tests/test_slack/views/modals/test_edit.py +324 -0
  24. firefighter_tests/test_slack/views/modals/test_opening_unified.py +42 -0
  25. firefighter_tests/test_slack/views/modals/test_update_status.py +72 -2
  26. firefighter/raid/signals/incident_created.py +0 -129
  27. firefighter_tests/test_raid/test_p1_p3_jira_fields.py +0 -372
  28. firefighter_tests/test_raid/test_priority_mapping.py +0 -267
  29. {firefighter_incident-0.0.16.dist-info → firefighter_incident-0.0.17.dist-info}/WHEEL +0 -0
  30. {firefighter_incident-0.0.16.dist-info → firefighter_incident-0.0.17.dist-info}/entry_points.txt +0 -0
  31. {firefighter_incident-0.0.16.dist-info → firefighter_incident-0.0.17.dist-info}/licenses/LICENSE +0 -0
@@ -1,129 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from typing import TYPE_CHECKING, Any
5
-
6
- from django.conf import settings
7
- from django.dispatch.dispatcher import receiver
8
-
9
- from firefighter.jira_app.client import JiraAPIError, JiraUserNotFoundError
10
- from firefighter.raid.client import client
11
- from firefighter.raid.forms import prepare_jira_fields
12
- from firefighter.raid.models import JiraTicket
13
- from firefighter.raid.service import get_jira_user_from_user
14
- from firefighter.slack.messages.slack_messages import (
15
- SlackMessageIncidentDeclaredAnnouncement,
16
- )
17
- from firefighter.slack.signals import incident_channel_done
18
-
19
- if TYPE_CHECKING:
20
- from firefighter.incidents.models.incident import Incident
21
- from firefighter.slack.models.incident_channel import IncidentChannel
22
-
23
- logger = logging.getLogger(__name__)
24
- RAID_DEFAULT_JIRA_QRAFT_USER_ID: str = settings.RAID_DEFAULT_JIRA_QRAFT_USER_ID
25
- APP_DISPLAY_NAME: str = settings.APP_DISPLAY_NAME
26
-
27
-
28
- @receiver(signal=incident_channel_done)
29
- def create_ticket(
30
- sender: Any, incident: Incident, channel: IncidentChannel, **kwargs: Any
31
- ) -> JiraTicket:
32
- # pylint: disable=unused-argument
33
-
34
- # Extract jira_extra_fields and impacts_data from kwargs (passed from unified incident form)
35
- jira_extra_fields = kwargs.get("jira_extra_fields", {})
36
- impacts_data = kwargs.get("impacts_data", {})
37
- logger.info(f"CREATE_TICKET - kwargs keys: {list(kwargs.keys())}")
38
- logger.info(f"CREATE_TICKET - jira_extra_fields received: {jira_extra_fields}")
39
-
40
- jira_user = get_jira_user_from_user(incident.created_by)
41
- account_id = jira_user.id
42
-
43
- # Map Impact priority (1-5) to JIRA priority (1-5), fallback to P1 for invalid values
44
- priority: int = incident.priority.value if 1 <= incident.priority.value <= 5 else 1
45
-
46
- # Build enhanced description with incident metadata
47
- description = f"""{incident.description}\n
48
- \n
49
- 🧯 This incident has been created for a critical incident. Links below to Slack and {APP_DISPLAY_NAME}.\n
50
- 📦 Incident category: {incident.incident_category.name} ({incident.incident_category.group.name})\n
51
- {incident.priority.emoji} Priority: {incident.priority.name}\n"""
52
-
53
- # Prepare all Jira fields using the common function
54
- # P1-P3 use first environment only (for backward compatibility)
55
- environments = jira_extra_fields.get("environments", [incident.environment.value])
56
- platforms = jira_extra_fields.get("platforms", ["platform-All"])
57
-
58
- jira_fields = prepare_jira_fields(
59
- title=incident.title,
60
- description=description,
61
- priority=priority,
62
- reporter=account_id,
63
- incident_category=incident.incident_category.name,
64
- environments=[environments[0]] if environments else [incident.environment.value], # P1-P3: first only
65
- platforms=platforms,
66
- impacts_data=impacts_data,
67
- optional_fields={
68
- "zendesk_ticket_id": jira_extra_fields.get("zendesk_ticket_id", ""),
69
- "seller_contract_id": jira_extra_fields.get("seller_contract_id", ""),
70
- "zoho_desk_ticket_id": jira_extra_fields.get("zoho_desk_ticket_id", ""),
71
- "is_key_account": jira_extra_fields.get("is_key_account"),
72
- "is_seller_in_golden_list": jira_extra_fields.get("is_seller_in_golden_list"),
73
- "suggested_team_routing": jira_extra_fields.get("suggested_team_routing"),
74
- },
75
- )
76
-
77
- # Create Jira issue with all prepared fields
78
- issue = client.create_issue(**jira_fields)
79
- issue_id = issue.get("id")
80
- if issue_id is None:
81
- logger.error(f"Could not create Jira ticket for incident {incident.id}")
82
- raise JiraAPIError("Could not create Jira ticket")
83
- try:
84
- default_jira_user = client.get_jira_user_from_jira_id(
85
- RAID_DEFAULT_JIRA_QRAFT_USER_ID
86
- )
87
- except JiraUserNotFoundError:
88
- logger.exception(
89
- f"Could not find Jira default reporter user with account id {RAID_DEFAULT_JIRA_QRAFT_USER_ID}"
90
- )
91
- # Add watcher reporter
92
- try:
93
- client.jira.add_watcher(issue=issue_id, watcher=account_id)
94
- except JiraAPIError:
95
- logger.exception(
96
- f"Could not add the watcher with account id {account_id} to the ticket {issue_id}"
97
- )
98
- # Removing default watcher TeamQraft
99
- try:
100
- client.jira.remove_watcher(issue=issue_id, watcher=default_jira_user)
101
- except JiraAPIError:
102
- logger.exception(
103
- f"Could not remove the watcher {default_jira_user} to the ticket {issue_id}"
104
- )
105
-
106
- client.jira.add_simple_link(
107
- issue=str(issue_id),
108
- object={
109
- "url": incident.status_page_url,
110
- "title": f"{APP_DISPLAY_NAME} incident #{incident.id}",
111
- },
112
- )
113
-
114
- client.jira.add_simple_link(
115
- issue=str(issue_id),
116
- object={"url": channel.link, "title": f"Slack conversation #{channel.name}"},
117
- )
118
- issue["business_impact"] = issue.get("business_impact", "")
119
- jira_ticket = JiraTicket.objects.create(**issue, incident=incident)
120
-
121
- channel.add_bookmark(
122
- title="Jira ticket",
123
- link=jira_ticket.url,
124
- emoji=":jira_new:",
125
- )
126
-
127
- channel.send_message_and_save(SlackMessageIncidentDeclaredAnnouncement(incident))
128
-
129
- return jira_ticket
@@ -1,372 +0,0 @@
1
- """Tests for P1-P3 (critical) incident Jira ticket creation with all custom fields.
2
-
3
- This test suite verifies that P1-P3 incidents properly pass all custom fields
4
- to Jira when creating tickets via the incident_channel_done signal, including:
5
- - environments (customfield_11049)
6
- - platform (customfield_10201)
7
- - business_impact (customfield_10936)
8
- - customer/seller specific fields from jira_extra_fields
9
- """
10
- from __future__ import annotations
11
-
12
- from unittest.mock import MagicMock, PropertyMock, patch
13
-
14
- import pytest
15
-
16
- from firefighter.incidents.forms.unified_incident import UnifiedIncidentForm
17
- from firefighter.incidents.models.impact import ImpactLevel, ImpactType, LevelChoices
18
- from firefighter.incidents.signals import create_incident_conversation
19
- from firefighter.jira_app.client import client
20
- from firefighter.jira_app.models import JiraUser
21
- from firefighter.raid.signals.incident_created import create_ticket
22
-
23
-
24
- @pytest.mark.django_db
25
- class TestP1P2P3JiraTicketFields:
26
- """Test P1-P3 incident Jira ticket creation includes all custom fields."""
27
-
28
- def test_p1_with_customer_impact_creates_jira_with_all_fields(
29
- self,
30
- priority_factory,
31
- environment_factory,
32
- incident_category_factory,
33
- user_factory,
34
- ):
35
- """P1 customer impact should create Jira with zendesk, environments, platform, business_impact."""
36
- # Setup P1 priority
37
- p1_priority = priority_factory(value=1, name="P1", default=True)
38
- env_prd = environment_factory(value="PRD", default=True)
39
- env_stg = environment_factory(value="STG", default=False)
40
- category = incident_category_factory()
41
- user = user_factory()
42
-
43
- # Get customer impact
44
- customers_impact_type = ImpactType.objects.get(value="customers_impact")
45
- customer_impact = ImpactLevel.objects.get(
46
- impact_type=customers_impact_type,
47
- value=LevelChoices.HIGHEST.value
48
- )
49
-
50
- # Form data
51
- form_data = {
52
- "title": "P1 Critical customer issue",
53
- "description": "Test P1 critical incident",
54
- "incident_category": category.id,
55
- "environment": [env_prd.id, env_stg.id], # Multiple environments!
56
- "platform": ["platform-FR", "platform-DE"], # Multiple platforms!
57
- "priority": p1_priority.id,
58
- "zendesk_ticket_id": "ZD-CRITICAL-123",
59
- }
60
-
61
- impacts_data = {
62
- "customers_impact": customer_impact, # Pass object for get_business_impact()
63
- }
64
-
65
- form = UnifiedIncidentForm(form_data)
66
- assert form.is_valid(), f"Form should be valid. Errors: {form.errors}"
67
-
68
- # Setup mock channel
69
- mock_channel = MagicMock()
70
- mock_channel.channel_id = "C123456"
71
-
72
- # Mock the signal send to intercept kwargs and directly call create_ticket handler
73
- def mock_signal_send(sender, incident, **kwargs):
74
- # Skip calling the real signal handler (which would call Slack API)
75
- # Instead, directly call the JIRA ticket creation handler
76
- create_ticket(
77
- sender="test",
78
- incident=incident,
79
- channel=mock_channel,
80
- jira_extra_fields=kwargs.get("jira_extra_fields", {}),
81
- impacts_data=kwargs.get("impacts_data", {}),
82
- )
83
-
84
- # Mock Jira client property at the CLASS level to prevent real connection
85
- mock_jira_client = MagicMock()
86
-
87
- with (
88
- patch.object(create_incident_conversation, "send", side_effect=mock_signal_send),
89
- patch.object(type(client), "jira", new_callable=PropertyMock, return_value=mock_jira_client),
90
- patch("firefighter.raid.signals.incident_created.client.create_issue") as mock_jira_create,
91
- patch("firefighter.raid.signals.incident_created.client.get_jira_user_from_jira_id") as mock_get_default_jira_user,
92
- patch("firefighter.raid.signals.incident_created.get_jira_user_from_user") as mock_get_jira_user,
93
- patch("firefighter.raid.forms.get_business_impact") as mock_get_business_impact,
94
- patch("firefighter.incidents.forms.unified_incident.SelectImpactForm.save"),
95
- patch("firefighter.raid.signals.incident_created.JiraTicket.objects.create"),
96
- ):
97
- # Mock Jira ticket creation (format from _jira_object)
98
- mock_jira_create.return_value = {
99
- "id": 99999,
100
- "key": "P1-TEST-123",
101
- "project_key": "P1",
102
- "assignee_id": None,
103
- "reporter_id": "test_account",
104
- "description": "Test",
105
- "summary": "Test",
106
- "issue_type": "Incident",
107
- "business_impact": "",
108
- }
109
- mock_jira_user = JiraUser(id="test_account")
110
- mock_get_jira_user.return_value = mock_jira_user
111
- mock_default_jira_user = JiraUser(id="default_account")
112
- mock_get_default_jira_user.return_value = mock_default_jira_user
113
- mock_get_business_impact.return_value = "High"
114
-
115
- # Trigger the P1-P3 workflow
116
- form.trigger_incident_workflow(
117
- creator=user,
118
- impacts_data=impacts_data,
119
- response_type="critical",
120
- )
121
-
122
- # Verify Jira create_issue was called
123
- assert mock_jira_create.called, "Jira create_issue should have been called"
124
-
125
- # Get the call arguments
126
- call_kwargs = mock_jira_create.call_args.kwargs
127
-
128
- # ✅ CRITICAL ASSERTIONS - These will FAIL initially
129
- assert "environments" in call_kwargs, "environments should be passed to Jira for P1-P3"
130
- # P1-P3 use first environment only (from non-deterministic QuerySet order)
131
- assert len(call_kwargs["environments"]) == 1, "Should pass exactly one environment"
132
- assert call_kwargs["environments"][0] in {"PRD", "STG"}, "Should pass one of the form environments"
133
-
134
- assert "platform" in call_kwargs, "platform should be passed to Jira for P1-P3"
135
- assert call_kwargs["platform"] == "platform-FR", "Should pass first platform value"
136
-
137
- assert "business_impact" in call_kwargs, "business_impact should be passed to Jira for P1-P3"
138
- assert call_kwargs["business_impact"] is not None, "Business impact should be computed from customer impact"
139
-
140
- # Verify zendesk is passed via jira_extra_fields
141
- assert "zendesk_ticket_id" in call_kwargs
142
- assert call_kwargs["zendesk_ticket_id"] == "ZD-CRITICAL-123"
143
-
144
- def test_p2_with_seller_impact_creates_jira_with_all_fields(
145
- self,
146
- priority_factory,
147
- environment_factory,
148
- incident_category_factory,
149
- user_factory,
150
- ):
151
- """P2 seller impact should create Jira with seller fields + environments."""
152
- p2_priority = priority_factory(value=2, name="P2", default=False)
153
- env_prd = environment_factory(value="PRD", default=True)
154
- category = incident_category_factory()
155
- user = user_factory()
156
-
157
- # Get seller impact
158
- sellers_impact_type = ImpactType.objects.get(value="sellers_impact")
159
- seller_impact = ImpactLevel.objects.get(
160
- impact_type=sellers_impact_type,
161
- value=LevelChoices.HIGH.value
162
- )
163
-
164
- form_data = {
165
- "title": "P2 Critical seller issue",
166
- "description": "Test P2 seller incident",
167
- "incident_category": category.id,
168
- "environment": [env_prd.id],
169
- "platform": ["platform-FR"],
170
- "priority": p2_priority.id,
171
- "seller_contract_id": "SC-CRITICAL-999",
172
- "zoho_desk_ticket_id": "ZOHO-CRITICAL-888",
173
- "is_key_account": True,
174
- "is_seller_in_golden_list": True,
175
- }
176
-
177
- impacts_data = {
178
- "sellers_impact": seller_impact, # Pass object for get_business_impact()
179
- }
180
-
181
- form = UnifiedIncidentForm(form_data)
182
- assert form.is_valid(), f"Form should be valid. Errors: {form.errors}"
183
-
184
- # Setup mock channel
185
- mock_channel = MagicMock()
186
- mock_channel.channel_id = "C789012"
187
-
188
- # Mock the signal send to intercept kwargs and directly call create_ticket handler
189
- def mock_signal_send(sender, incident, **kwargs):
190
- # Skip calling the real signal handler (which would call Slack API)
191
- # Instead, directly call the JIRA ticket creation handler
192
- create_ticket(
193
- sender="test",
194
- incident=incident,
195
- channel=mock_channel,
196
- jira_extra_fields=kwargs.get("jira_extra_fields", {}),
197
- impacts_data=kwargs.get("impacts_data", {}),
198
- )
199
-
200
- # Mock Jira client property at the CLASS level to prevent real connection
201
- mock_jira_client = MagicMock()
202
-
203
- with (
204
- patch.object(create_incident_conversation, "send", side_effect=mock_signal_send),
205
- patch.object(type(client), "jira", new_callable=PropertyMock, return_value=mock_jira_client),
206
- patch("firefighter.raid.signals.incident_created.client.create_issue") as mock_jira_create,
207
- patch("firefighter.raid.signals.incident_created.client.get_jira_user_from_jira_id") as mock_get_default_jira_user,
208
- patch("firefighter.raid.signals.incident_created.get_jira_user_from_user") as mock_get_jira_user,
209
- patch("firefighter.raid.forms.get_business_impact") as mock_get_business_impact,
210
- patch("firefighter.incidents.forms.unified_incident.SelectImpactForm.save"),
211
- patch("firefighter.raid.signals.incident_created.JiraTicket.objects.create"),
212
- ):
213
- mock_jira_create.return_value = {
214
- "id": 88888,
215
- "key": "P2-SELLER-456",
216
- "project_key": "P2",
217
- "assignee_id": None,
218
- "reporter_id": "test_account",
219
- "description": "Test",
220
- "summary": "Test",
221
- "issue_type": "Incident",
222
- "business_impact": "",
223
- }
224
- mock_jira_user = JiraUser(id="test_account")
225
- mock_get_jira_user.return_value = mock_jira_user
226
- mock_default_jira_user = JiraUser(id="default_account")
227
- mock_get_default_jira_user.return_value = mock_default_jira_user
228
- mock_get_business_impact.return_value = "High"
229
-
230
- form.trigger_incident_workflow(
231
- creator=user,
232
- impacts_data=impacts_data,
233
- response_type="critical",
234
- )
235
-
236
- call_kwargs = mock_jira_create.call_args.kwargs
237
-
238
- # ✅ CRITICAL: environments must be passed with exact value
239
- assert "environments" in call_kwargs, "environments should be passed to Jira for P2"
240
- assert call_kwargs["environments"] == ["PRD"], "Should pass environment value"
241
-
242
- # ✅ Verify platform and business_impact with exact values
243
- assert "platform" in call_kwargs, "platform should be passed to Jira for P2"
244
- assert call_kwargs["platform"] == "platform-FR", "Should pass platform value"
245
- assert "business_impact" in call_kwargs, "business_impact should be passed to Jira for P2"
246
- assert call_kwargs["business_impact"] is not None, "Business impact should be computed"
247
-
248
- # ✅ Verify seller-specific fields
249
- assert "seller_contract_id" in call_kwargs
250
- assert call_kwargs["seller_contract_id"] == "SC-CRITICAL-999"
251
- assert "zoho_desk_ticket_id" in call_kwargs
252
- assert call_kwargs["zoho_desk_ticket_id"] == "ZOHO-CRITICAL-888"
253
- assert "is_key_account" in call_kwargs
254
- assert call_kwargs["is_key_account"] is True
255
- assert "is_seller_in_golden_list" in call_kwargs
256
- assert call_kwargs["is_seller_in_golden_list"] is True
257
-
258
- def test_p3_with_both_impacts_creates_jira_with_all_fields(
259
- self,
260
- priority_factory,
261
- environment_factory,
262
- incident_category_factory,
263
- user_factory,
264
- ):
265
- """P3 with both customer and seller impact should pass all fields to Jira."""
266
- p3_priority = priority_factory(value=3, name="P3", default=False)
267
- env_prd = environment_factory(value="PRD", default=True)
268
- env_stg = environment_factory(value="STG", default=False)
269
- category = incident_category_factory()
270
- user = user_factory()
271
-
272
- # Get both impacts
273
- customers_impact_type = ImpactType.objects.get(value="customers_impact")
274
- customer_impact = ImpactLevel.objects.get(
275
- impact_type=customers_impact_type,
276
- value=LevelChoices.MEDIUM.value
277
- )
278
- sellers_impact_type = ImpactType.objects.get(value="sellers_impact")
279
- seller_impact = ImpactLevel.objects.get(
280
- impact_type=sellers_impact_type,
281
- value=LevelChoices.LOW.value
282
- )
283
-
284
- form_data = {
285
- "title": "P3 Combined customer and seller",
286
- "description": "Test P3 with multiple impacts",
287
- "incident_category": category.id,
288
- "environment": [env_prd.id, env_stg.id],
289
- "platform": ["platform-All"],
290
- "priority": p3_priority.id,
291
- "zendesk_ticket_id": "ZD-P3-111",
292
- "seller_contract_id": "SC-P3-222",
293
- "zoho_desk_ticket_id": "ZOHO-P3-333",
294
- "is_key_account": False,
295
- "is_seller_in_golden_list": False,
296
- }
297
-
298
- impacts_data = {
299
- "customers_impact": customer_impact, # Pass object for get_business_impact()
300
- "sellers_impact": seller_impact, # Pass object for get_business_impact()
301
- }
302
-
303
- form = UnifiedIncidentForm(form_data)
304
- assert form.is_valid(), f"Form should be valid. Errors: {form.errors}"
305
-
306
- # Setup mock channel
307
- mock_channel = MagicMock()
308
- mock_channel.channel_id = "C111222"
309
-
310
- # Mock the signal send to intercept kwargs and directly call create_ticket handler
311
- def mock_signal_send(sender, incident, **kwargs):
312
- # Skip calling the real signal handler (which would call Slack API)
313
- # Instead, directly call the JIRA ticket creation handler
314
- create_ticket(
315
- sender="test",
316
- incident=incident,
317
- channel=mock_channel,
318
- jira_extra_fields=kwargs.get("jira_extra_fields", {}),
319
- impacts_data=kwargs.get("impacts_data", {}),
320
- )
321
-
322
- # Mock Jira client property at the CLASS level to prevent real connection
323
- mock_jira_client = MagicMock()
324
-
325
- with (
326
- patch.object(create_incident_conversation, "send", side_effect=mock_signal_send),
327
- patch.object(type(client), "jira", new_callable=PropertyMock, return_value=mock_jira_client),
328
- patch("firefighter.raid.signals.incident_created.client.create_issue") as mock_jira_create,
329
- patch("firefighter.raid.signals.incident_created.client.get_jira_user_from_jira_id") as mock_get_default_jira_user,
330
- patch("firefighter.raid.signals.incident_created.get_jira_user_from_user") as mock_get_jira_user,
331
- patch("firefighter.raid.forms.get_business_impact") as mock_get_business_impact,
332
- patch("firefighter.incidents.forms.unified_incident.SelectImpactForm.save"),
333
- patch("firefighter.raid.signals.incident_created.JiraTicket.objects.create"),
334
- ):
335
- mock_jira_create.return_value = {
336
- "id": 77777,
337
- "key": "P3-COMBO-789",
338
- "project_key": "P3",
339
- "assignee_id": None,
340
- "reporter_id": "test_account",
341
- "description": "Test",
342
- "summary": "Test",
343
- "issue_type": "Incident",
344
- "business_impact": "",
345
- }
346
- mock_jira_user = JiraUser(id="test_account")
347
- mock_get_jira_user.return_value = mock_jira_user
348
- mock_default_jira_user = JiraUser(id="default_account")
349
- mock_get_default_jira_user.return_value = mock_default_jira_user
350
- mock_get_business_impact.return_value = "High"
351
-
352
- form.trigger_incident_workflow(
353
- creator=user,
354
- impacts_data=impacts_data,
355
- response_type="critical",
356
- )
357
-
358
- call_kwargs = mock_jira_create.call_args.kwargs
359
-
360
- # ✅ All fields should be present with exact values
361
- assert "environments" in call_kwargs, "environments should be passed to Jira for P3"
362
- # P1-P3 use first environment only (from non-deterministic QuerySet order)
363
- assert len(call_kwargs["environments"]) == 1, "Should pass exactly one environment"
364
- assert call_kwargs["environments"][0] in {"PRD", "STG"}, "Should pass one of the form environments"
365
- assert "platform" in call_kwargs, "platform should be passed to Jira for P3"
366
- assert call_kwargs["platform"] == "platform-All", "Should pass first platform value"
367
- assert "business_impact" in call_kwargs, "business_impact should be passed to Jira for P3"
368
- assert call_kwargs["business_impact"] is not None, "Business impact should be computed"
369
- assert "zendesk_ticket_id" in call_kwargs
370
- assert call_kwargs["zendesk_ticket_id"] == "ZD-P3-111"
371
- assert "seller_contract_id" in call_kwargs
372
- assert call_kwargs["seller_contract_id"] == "SC-P3-222"