firefighter-incident 0.0.13__py3-none-any.whl → 0.0.15__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 (136) hide show
  1. firefighter/_version.py +16 -3
  2. firefighter/api/serializers.py +17 -8
  3. firefighter/api/urls.py +8 -1
  4. firefighter/api/views/_base.py +1 -1
  5. firefighter/api/views/components.py +5 -5
  6. firefighter/api/views/incidents.py +9 -9
  7. firefighter/confluence/signals/incident_updated.py +2 -2
  8. firefighter/firefighter/settings/components/raid.py +3 -0
  9. firefighter/incidents/admin.py +24 -24
  10. firefighter/incidents/enums.py +22 -2
  11. firefighter/incidents/factories.py +14 -5
  12. firefighter/incidents/forms/close_incident.py +4 -4
  13. firefighter/incidents/forms/closure_reason.py +45 -0
  14. firefighter/incidents/forms/create_incident.py +4 -4
  15. firefighter/incidents/forms/unified_incident.py +406 -0
  16. firefighter/incidents/forms/update_status.py +91 -5
  17. firefighter/incidents/menus.py +2 -2
  18. firefighter/incidents/migrations/0005_enable_from_p1_to_p5_priority.py +7 -5
  19. firefighter/incidents/migrations/0009_update_sla.py +7 -5
  20. firefighter/incidents/migrations/0020_create_incident_category_model.py +64 -0
  21. firefighter/incidents/migrations/0021_copy_component_data_to_incident_category.py +57 -0
  22. firefighter/incidents/migrations/0022_add_incident_category_fields.py +34 -0
  23. firefighter/incidents/migrations/0023_populate_incident_category_references.py +57 -0
  24. firefighter/incidents/migrations/0024_remove_component_fields_and_model.py +26 -0
  25. firefighter/incidents/migrations/0025_make_incident_category_required.py +24 -0
  26. firefighter/incidents/migrations/0026_alter_incidentcategory_options_and_more.py +39 -0
  27. firefighter/incidents/migrations/0027_add_closure_fields.py +40 -0
  28. firefighter/incidents/migrations/0028_add_closure_reason_constraint.py +33 -0
  29. firefighter/incidents/migrations/0029_add_custom_fields_to_incident.py +22 -0
  30. firefighter/incidents/models/__init__.py +1 -1
  31. firefighter/incidents/models/group.py +1 -1
  32. firefighter/incidents/models/incident.py +47 -20
  33. firefighter/incidents/models/{component.py → incident_category.py} +30 -29
  34. firefighter/incidents/models/incident_update.py +3 -3
  35. firefighter/incidents/static/css/main.min.css +1 -1
  36. firefighter/incidents/tables.py +9 -9
  37. firefighter/incidents/templates/layouts/partials/incident_card.html +1 -1
  38. firefighter/incidents/templates/layouts/partials/incident_timeline.html +2 -2
  39. firefighter/incidents/templates/layouts/partials/status_pill.html +1 -1
  40. firefighter/incidents/templates/pages/{component_detail.html → incident_category_detail.html} +13 -13
  41. firefighter/incidents/templates/pages/{component_list.html → incident_category_list.html} +2 -2
  42. firefighter/incidents/templates/pages/incident_detail.html +3 -3
  43. firefighter/incidents/urls.py +6 -6
  44. firefighter/incidents/views/components/details.py +9 -9
  45. firefighter/incidents/views/components/list.py +9 -9
  46. firefighter/incidents/views/reports.py +5 -5
  47. firefighter/incidents/views/users/details.py +2 -2
  48. firefighter/incidents/views/views.py +7 -7
  49. firefighter/jira_app/client.py +1 -1
  50. firefighter/logging/custom_json_formatter.py +2 -1
  51. firefighter/pagerduty/tasks/trigger_oncall.py +1 -1
  52. firefighter/raid/admin.py +0 -11
  53. firefighter/raid/apps.py +9 -26
  54. firefighter/raid/client.py +5 -5
  55. firefighter/raid/forms.py +84 -213
  56. firefighter/raid/migrations/0003_delete_raidarea.py +16 -0
  57. firefighter/raid/models.py +2 -21
  58. firefighter/raid/serializers.py +5 -4
  59. firefighter/raid/service.py +29 -27
  60. firefighter/raid/signals/incident_created.py +42 -15
  61. firefighter/raid/signals/incident_updated.py +3 -2
  62. firefighter/raid/utils.py +1 -1
  63. firefighter/raid/views/__init__.py +1 -1
  64. firefighter/slack/admin.py +8 -8
  65. firefighter/slack/management/commands/switch_test_users.py +272 -0
  66. firefighter/slack/messages/slack_messages.py +24 -9
  67. firefighter/slack/migrations/0005_add_incident_categories_fields.py +33 -0
  68. firefighter/slack/migrations/0006_copy_components_to_incident_categories.py +57 -0
  69. firefighter/slack/migrations/0007_remove_components_fields.py +22 -0
  70. firefighter/slack/migrations/0008_alter_conversation_incident_categories_and_more.py +33 -0
  71. firefighter/slack/models/conversation.py +3 -3
  72. firefighter/slack/models/incident_channel.py +1 -1
  73. firefighter/slack/models/user.py +1 -1
  74. firefighter/slack/models/user_group.py +3 -3
  75. firefighter/slack/rules.py +2 -2
  76. firefighter/slack/signals/create_incident_conversation.py +6 -0
  77. firefighter/slack/signals/get_users.py +2 -2
  78. firefighter/slack/signals/incident_updated.py +8 -2
  79. firefighter/slack/utils.py +2 -2
  80. firefighter/slack/views/events/home.py +2 -2
  81. firefighter/slack/views/modals/__init__.py +4 -0
  82. firefighter/slack/views/modals/base_modal/form_utils.py +78 -0
  83. firefighter/slack/views/modals/close.py +18 -5
  84. firefighter/slack/views/modals/closure_reason.py +193 -0
  85. firefighter/slack/views/modals/open.py +83 -12
  86. firefighter/slack/views/modals/opening/check_current_incidents.py +2 -2
  87. firefighter/slack/views/modals/opening/details/unified.py +203 -0
  88. firefighter/slack/views/modals/opening/select_impact.py +5 -2
  89. firefighter/slack/views/modals/opening/set_details.py +3 -2
  90. firefighter/slack/views/modals/postmortem.py +10 -2
  91. firefighter/slack/views/modals/update_status.py +32 -6
  92. firefighter/slack/views/modals/utils.py +51 -0
  93. firefighter_fixtures/incidents/{components.json → incident_categories.json} +52 -52
  94. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/METADATA +2 -2
  95. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/RECORD +133 -88
  96. firefighter_tests/conftest.py +4 -5
  97. firefighter_tests/test_api/test_api_landbot.py +1 -1
  98. firefighter_tests/test_firefighter/test_sso.py +146 -0
  99. firefighter_tests/test_incidents/test_enums.py +100 -0
  100. firefighter_tests/test_incidents/test_forms/conftest.py +179 -0
  101. firefighter_tests/test_incidents/test_forms/test_closure_reason.py +91 -0
  102. firefighter_tests/test_incidents/test_forms/test_form_utils.py +15 -15
  103. firefighter_tests/test_incidents/test_forms/test_unified_incident_form.py +570 -0
  104. firefighter_tests/test_incidents/test_forms/test_unified_incident_form_integration.py +581 -0
  105. firefighter_tests/test_incidents/test_forms/test_unified_incident_form_p4_p5.py +410 -0
  106. firefighter_tests/test_incidents/test_forms/test_update_status_workflow.py +343 -0
  107. firefighter_tests/test_incidents/test_forms/test_workflow_transitions.py +167 -0
  108. firefighter_tests/test_incidents/test_incident_urls.py +3 -3
  109. firefighter_tests/test_incidents/test_models/test_incident_category.py +165 -0
  110. firefighter_tests/test_incidents/test_models/test_incident_model.py +70 -2
  111. firefighter_tests/test_raid/conftest.py +154 -0
  112. firefighter_tests/test_raid/test_p1_p3_jira_fields.py +372 -0
  113. firefighter_tests/test_raid/test_priority_mapping.py +267 -0
  114. firefighter_tests/test_raid/test_raid_client.py +580 -0
  115. firefighter_tests/test_raid/test_raid_forms.py +552 -0
  116. firefighter_tests/test_raid/test_raid_models.py +185 -0
  117. firefighter_tests/test_raid/test_raid_serializers.py +507 -0
  118. firefighter_tests/test_raid/test_raid_service.py +442 -0
  119. firefighter_tests/test_raid/test_raid_signals.py +187 -0
  120. firefighter_tests/test_raid/test_raid_views.py +196 -0
  121. firefighter_tests/test_slack/messages/__init__.py +0 -0
  122. firefighter_tests/test_slack/messages/test_slack_messages.py +367 -0
  123. firefighter_tests/test_slack/views/modals/conftest.py +140 -0
  124. firefighter_tests/test_slack/views/modals/test_close.py +71 -9
  125. firefighter_tests/test_slack/views/modals/test_closure_reason_modal.py +138 -0
  126. firefighter_tests/test_slack/views/modals/test_form_utils_multiple_choice.py +249 -0
  127. firefighter_tests/test_slack/views/modals/test_open.py +146 -2
  128. firefighter_tests/test_slack/views/modals/test_opening_unified.py +421 -0
  129. firefighter_tests/test_slack/views/modals/test_update_status.py +331 -7
  130. firefighter_tests/test_slack/views/modals/test_utils.py +135 -0
  131. firefighter/raid/views/open_normal.py +0 -139
  132. firefighter/slack/views/modals/opening/details/critical.py +0 -88
  133. firefighter_fixtures/raid/area.json +0 -1
  134. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/WHEEL +0 -0
  135. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/entry_points.txt +0 -0
  136. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,410 @@
1
+ """Tests for P4/P5 (normal) incident creation with Jira ticket custom fields.
2
+
3
+ This test suite verifies that P4/P5 incidents (response_type="normal") properly
4
+ pass all custom fields to Jira when creating tickets, including:
5
+ - environments (customfield_11049)
6
+ - platform (customfield_10201)
7
+ - business_impact (customfield_10936)
8
+ - customer-specific fields (zendesk_ticket_id)
9
+ - seller-specific fields (seller_contract_id, zoho_desk_ticket_id, etc.)
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from unittest.mock import patch
14
+
15
+ import pytest
16
+
17
+ from firefighter.incidents.forms.unified_incident import UnifiedIncidentForm
18
+ from firefighter.incidents.models.impact import ImpactLevel, ImpactType, LevelChoices
19
+ from firefighter.jira_app.models import JiraUser
20
+ from firefighter.raid.models import FeatureTeam
21
+
22
+
23
+ @pytest.fixture
24
+ def feature_team_payment(db):
25
+ """Create a FeatureTeam for Payment."""
26
+ return FeatureTeam.objects.create(
27
+ id=10915,
28
+ name="Payment",
29
+ jira_project_key="PAY",
30
+ )
31
+
32
+
33
+ @pytest.fixture
34
+ def feature_team_seller(db):
35
+ """Create a FeatureTeam for Seller Services."""
36
+ return FeatureTeam.objects.create(
37
+ id=11007,
38
+ name="Seller Services",
39
+ jira_project_key="SELLER",
40
+ )
41
+
42
+
43
+ @pytest.fixture
44
+ def feature_team_platform(db):
45
+ """Create a FeatureTeam for Platform."""
46
+ return FeatureTeam.objects.create(
47
+ id=11034,
48
+ name="Platform",
49
+ jira_project_key="PLAT",
50
+ )
51
+
52
+
53
+ @pytest.mark.django_db
54
+ class TestP4P5CustomerImpactJiraFields:
55
+ """Test P4/P5 customer impact incidents create Jira tickets with all custom fields."""
56
+
57
+ def test_p4_customer_impact_creates_jira_with_all_fields(
58
+ self,
59
+ priority_factory,
60
+ environment_factory,
61
+ incident_category_factory,
62
+ user_factory,
63
+ feature_team_payment,
64
+ ):
65
+ """P4 customer impact should create Jira ticket with zendesk, environments, platform, business_impact."""
66
+ # Setup P4 priority
67
+ p4_priority = priority_factory(value=4, name="P4", default=False)
68
+ env_prd = environment_factory(value="PRD", default=True)
69
+ env_stg = environment_factory(value="STG", default=False)
70
+ category = incident_category_factory()
71
+ user = user_factory()
72
+
73
+ # Get customer impact
74
+ customers_impact_type = ImpactType.objects.get(value="customers_impact")
75
+ customer_impact = ImpactLevel.objects.get(
76
+ impact_type=customers_impact_type,
77
+ value=LevelChoices.LOW.value # P4 = low impact
78
+ )
79
+
80
+ # Form data with customer-specific fields
81
+ form_data = {
82
+ "title": "P4 Customer issue with Zendesk",
83
+ "description": "Test P4 customer incident",
84
+ "incident_category": category.id,
85
+ "environment": [env_prd.id, env_stg.id], # Multiple environments
86
+ "platform": ["platform-FR", "platform-DE"], # Multiple platforms
87
+ "priority": p4_priority.id,
88
+ "zendesk_ticket_id": "ZD-12345",
89
+ "suggested_team_routing": feature_team_payment.id, # Required for P4/P5
90
+ }
91
+
92
+ impacts_data = {
93
+ "customers_impact": customer_impact,
94
+ }
95
+
96
+ form = UnifiedIncidentForm(form_data)
97
+ assert form.is_valid(), f"Form should be valid. Errors: {form.errors}"
98
+
99
+ # Mock the Jira client to capture what's passed
100
+ with (
101
+ patch("firefighter.raid.service.jira_client.create_issue") as mock_create_issue,
102
+ patch("firefighter.raid.service.get_jira_user_from_user") as mock_get_jira_user,
103
+ patch("firefighter.raid.forms.SelectImpactForm"),
104
+ patch("firefighter.raid.forms.set_jira_ticket_watchers_raid"),
105
+ patch("firefighter.raid.forms.alert_slack_new_jira_ticket"),
106
+ patch("firefighter.raid.forms.JiraTicket.objects.create"),
107
+ ):
108
+ # Mock return value (format from _jira_object, compatible with JiraTicket.objects.create)
109
+ mock_create_issue.return_value = {
110
+ "id": 12345,
111
+ "key": "TEST-123",
112
+ "project_key": "TEST",
113
+ "assignee_id": None,
114
+ "reporter_id": "test_account",
115
+ "description": "Test description",
116
+ "summary": "Test summary",
117
+ "issue_type": "Incident",
118
+ "business_impact": "",
119
+ }
120
+ mock_jira_user = JiraUser(id="test_account")
121
+ mock_get_jira_user.return_value = mock_jira_user
122
+
123
+ # Trigger workflow
124
+ form.trigger_incident_workflow(
125
+ creator=user,
126
+ impacts_data=impacts_data,
127
+ response_type="normal",
128
+ )
129
+
130
+ # Verify create_issue was called
131
+ assert mock_create_issue.called, "Jira create_issue should have been called"
132
+
133
+ # Get the call arguments
134
+ call_kwargs = mock_create_issue.call_args.kwargs
135
+
136
+ # ✅ CRITICAL ASSERTIONS - These should FAIL initially
137
+ assert "environments" in call_kwargs, "environments should be passed to Jira"
138
+ assert set(call_kwargs["environments"]) == {"PRD", "STG"}, "Should pass environment values"
139
+
140
+ # ✅ Verify platform is passed
141
+ assert "platform" in call_kwargs, "platform should be passed to Jira"
142
+ assert call_kwargs["platform"] == "platform-FR", "Should pass first platform"
143
+
144
+ # ✅ Verify business_impact is passed
145
+ assert "business_impact" in call_kwargs, "business_impact should be passed to Jira"
146
+ # Business impact should be computed from customer impact level
147
+ assert call_kwargs["business_impact"] is not None
148
+
149
+ # ✅ Verify zendesk_ticket_id is passed
150
+ assert "zendesk_ticket_id" in call_kwargs, "zendesk_ticket_id should be passed"
151
+ assert call_kwargs["zendesk_ticket_id"] == "ZD-12345"
152
+
153
+ def test_p5_customer_impact_creates_jira_with_all_fields(
154
+ self,
155
+ priority_factory,
156
+ environment_factory,
157
+ incident_category_factory,
158
+ user_factory,
159
+ feature_team_payment,
160
+ ):
161
+ """P5 customer impact should create Jira ticket with all custom fields."""
162
+ # Setup P5 priority
163
+ p5_priority = priority_factory(value=5, name="P5", default=False)
164
+ env_prd = environment_factory(value="PRD", default=True)
165
+ category = incident_category_factory()
166
+ user = user_factory()
167
+
168
+ # Get customer impact (lowest for P5)
169
+ customers_impact_type = ImpactType.objects.get(value="customers_impact")
170
+ customer_impact = ImpactLevel.objects.get(
171
+ impact_type=customers_impact_type,
172
+ value=LevelChoices.LOWEST.value
173
+ )
174
+
175
+ form_data = {
176
+ "title": "P5 Customer cosmetic issue",
177
+ "description": "Test P5 customer incident",
178
+ "incident_category": category.id,
179
+ "environment": [env_prd.id],
180
+ "platform": ["platform-All"],
181
+ "priority": p5_priority.id,
182
+ "zendesk_ticket_id": "ZD-99999",
183
+ "suggested_team_routing": feature_team_payment.id,
184
+ }
185
+
186
+ impacts_data = {
187
+ "customers_impact": customer_impact,
188
+ }
189
+
190
+ form = UnifiedIncidentForm(form_data)
191
+ assert form.is_valid(), f"Form should be valid. Errors: {form.errors}"
192
+
193
+ with (
194
+ patch("firefighter.raid.service.jira_client.create_issue") as mock_create_issue,
195
+ patch("firefighter.raid.service.get_jira_user_from_user") as mock_get_jira_user,
196
+ patch("firefighter.raid.forms.SelectImpactForm"),
197
+ patch("firefighter.raid.forms.set_jira_ticket_watchers_raid"),
198
+ patch("firefighter.raid.forms.alert_slack_new_jira_ticket"),
199
+ patch("firefighter.raid.forms.JiraTicket.objects.create"),
200
+ ):
201
+ mock_create_issue.return_value = {
202
+ "id": 67890,
203
+ "key": "TEST-456",
204
+ "project_key": "TEST",
205
+ "assignee_id": None,
206
+ "reporter_id": "test_account",
207
+ "description": "Test",
208
+ "summary": "Test",
209
+ "issue_type": "Incident",
210
+ "business_impact": "",
211
+ }
212
+ mock_jira_user = JiraUser(id="test_account")
213
+ mock_get_jira_user.return_value = mock_jira_user
214
+
215
+ form.trigger_incident_workflow(
216
+ creator=user,
217
+ impacts_data=impacts_data,
218
+ response_type="normal",
219
+ )
220
+
221
+ call_kwargs = mock_create_issue.call_args.kwargs
222
+
223
+ # Critical assertions
224
+ assert "environments" in call_kwargs
225
+ assert call_kwargs["environments"] == ["PRD"]
226
+ assert "platform" in call_kwargs
227
+ assert call_kwargs["platform"] == "platform-All"
228
+ assert "business_impact" in call_kwargs
229
+ assert "zendesk_ticket_id" in call_kwargs
230
+ assert call_kwargs["zendesk_ticket_id"] == "ZD-99999"
231
+
232
+
233
+ @pytest.mark.django_db
234
+ class TestP4P5SellerImpactJiraFields:
235
+ """Test P4/P5 seller impact incidents create Jira tickets with all seller custom fields."""
236
+
237
+ def test_p4_seller_impact_creates_jira_with_all_fields(
238
+ self,
239
+ priority_factory,
240
+ environment_factory,
241
+ incident_category_factory,
242
+ user_factory,
243
+ feature_team_seller,
244
+ ):
245
+ """P4 seller impact should create Jira with seller fields + environments."""
246
+ p4_priority = priority_factory(value=4, name="P4", default=False)
247
+ env_prd = environment_factory(value="PRD", default=True)
248
+ env_int = environment_factory(value="INT", default=False)
249
+ category = incident_category_factory()
250
+ user = user_factory()
251
+
252
+ # Get seller impact
253
+ sellers_impact_type = ImpactType.objects.get(value="sellers_impact")
254
+ seller_impact = ImpactLevel.objects.get(
255
+ impact_type=sellers_impact_type,
256
+ value=LevelChoices.LOW.value
257
+ )
258
+
259
+ form_data = {
260
+ "title": "P4 Seller contract issue",
261
+ "description": "Test P4 seller incident",
262
+ "incident_category": category.id,
263
+ "environment": [env_prd.id, env_int.id],
264
+ "platform": ["platform-FR"],
265
+ "priority": p4_priority.id,
266
+ "suggested_team_routing": feature_team_seller.id,
267
+ "seller_contract_id": "SC-789",
268
+ "zoho_desk_ticket_id": "ZOHO-456",
269
+ "is_key_account": True,
270
+ "is_seller_in_golden_list": False,
271
+ }
272
+
273
+ impacts_data = {
274
+ "sellers_impact": seller_impact,
275
+ }
276
+
277
+ form = UnifiedIncidentForm(form_data)
278
+ assert form.is_valid(), f"Form should be valid. Errors: {form.errors}"
279
+
280
+ with (
281
+ patch("firefighter.raid.service.jira_client.create_issue") as mock_create_issue,
282
+ patch("firefighter.raid.service.get_jira_user_from_user") as mock_get_jira_user,
283
+ patch("firefighter.raid.forms.SelectImpactForm"),
284
+ patch("firefighter.raid.forms.set_jira_ticket_watchers_raid"),
285
+ patch("firefighter.raid.forms.alert_slack_new_jira_ticket"),
286
+ patch("firefighter.raid.forms.JiraTicket.objects.create"),
287
+ ):
288
+ mock_create_issue.return_value = {
289
+ "id": 11111,
290
+ "key": "SELLER-123",
291
+ "project_key": "SELLER",
292
+ "assignee_id": None,
293
+ "reporter_id": "test_account",
294
+ "description": "Test",
295
+ "summary": "Test",
296
+ "issue_type": "Incident",
297
+ "business_impact": "",
298
+ }
299
+ mock_jira_user = JiraUser(id="test_account")
300
+ mock_get_jira_user.return_value = mock_jira_user
301
+
302
+ form.trigger_incident_workflow(
303
+ creator=user,
304
+ impacts_data=impacts_data,
305
+ response_type="normal",
306
+ )
307
+
308
+ call_kwargs = mock_create_issue.call_args.kwargs
309
+
310
+ # ✅ CRITICAL ASSERTIONS for environments
311
+ assert "environments" in call_kwargs, "environments should be passed"
312
+ assert set(call_kwargs["environments"]) == {"PRD", "INT"}, "Should pass all environment values"
313
+
314
+ # ✅ Verify seller-specific fields
315
+ assert "seller_contract_id" in call_kwargs
316
+ assert call_kwargs["seller_contract_id"] == "SC-789"
317
+ assert "zoho_desk_ticket_id" in call_kwargs
318
+ assert call_kwargs["zoho_desk_ticket_id"] == "ZOHO-456"
319
+ assert "is_key_account" in call_kwargs
320
+ assert call_kwargs["is_key_account"] is True
321
+ assert "is_seller_in_golden_list" in call_kwargs
322
+ assert call_kwargs["is_seller_in_golden_list"] is False
323
+
324
+ # ✅ Verify platform and business_impact
325
+ assert "platform" in call_kwargs
326
+ assert call_kwargs["platform"] == "platform-FR"
327
+ assert "business_impact" in call_kwargs
328
+
329
+
330
+ @pytest.mark.django_db
331
+ class TestP4P5InternalImpactJiraFields:
332
+ """Test P4/P5 internal (employee) impact incidents create Jira with all fields."""
333
+
334
+ def test_p4_employee_impact_creates_jira_with_environments(
335
+ self,
336
+ priority_factory,
337
+ environment_factory,
338
+ incident_category_factory,
339
+ user_factory,
340
+ feature_team_platform,
341
+ ):
342
+ """P4 employee impact should create Jira with environments."""
343
+ p4_priority = priority_factory(value=4, name="P4", default=False)
344
+ env_stg = environment_factory(value="STG", default=False)
345
+ env_int = environment_factory(value="INT", default=False)
346
+ category = incident_category_factory()
347
+ user = user_factory()
348
+
349
+ # Get employee impact
350
+ employees_impact_type = ImpactType.objects.get(value="employees_impact")
351
+ employee_impact = ImpactLevel.objects.get(
352
+ impact_type=employees_impact_type,
353
+ value=LevelChoices.LOW.value
354
+ )
355
+
356
+ form_data = {
357
+ "title": "P4 Internal tool degraded",
358
+ "description": "Test P4 internal incident",
359
+ "incident_category": category.id,
360
+ "environment": [env_stg.id, env_int.id],
361
+ "platform": ["platform-Internal"],
362
+ "priority": p4_priority.id,
363
+ "suggested_team_routing": feature_team_platform.id,
364
+ }
365
+
366
+ impacts_data = {
367
+ "employees_impact": employee_impact,
368
+ }
369
+
370
+ form = UnifiedIncidentForm(form_data)
371
+ assert form.is_valid(), f"Form should be valid. Errors: {form.errors}"
372
+
373
+ with (
374
+ patch("firefighter.raid.service.jira_client.create_issue") as mock_create_issue,
375
+ patch("firefighter.raid.service.get_jira_user_from_user") as mock_get_jira_user,
376
+ patch("firefighter.raid.forms.SelectImpactForm"),
377
+ patch("firefighter.raid.forms.set_jira_ticket_watchers_raid"),
378
+ patch("firefighter.raid.forms.alert_slack_new_jira_ticket"),
379
+ patch("firefighter.raid.forms.JiraTicket.objects.create"),
380
+ ):
381
+ mock_create_issue.return_value = {
382
+ "id": 22222,
383
+ "key": "INTERNAL-456",
384
+ "project_key": "INTERNAL",
385
+ "assignee_id": None,
386
+ "reporter_id": "test_account",
387
+ "description": "Test",
388
+ "summary": "Test",
389
+ "issue_type": "Incident",
390
+ "business_impact": "",
391
+ }
392
+ mock_jira_user = JiraUser(id="test_account")
393
+ mock_get_jira_user.return_value = mock_jira_user
394
+
395
+ form.trigger_incident_workflow(
396
+ creator=user,
397
+ impacts_data=impacts_data,
398
+ response_type="normal",
399
+ )
400
+
401
+ call_kwargs = mock_create_issue.call_args.kwargs
402
+
403
+ # ✅ CRITICAL: environments must be passed for internal incidents too
404
+ assert "environments" in call_kwargs
405
+ assert set(call_kwargs["environments"]) == {"STG", "INT"}, "Should pass all environment values"
406
+
407
+ # ✅ Verify platform and business_impact
408
+ assert "platform" in call_kwargs
409
+ assert call_kwargs["platform"] == "platform-Internal"
410
+ assert "business_impact" in call_kwargs