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.
- firefighter/_version.py +16 -3
- firefighter/api/serializers.py +17 -8
- firefighter/api/urls.py +8 -1
- firefighter/api/views/_base.py +1 -1
- firefighter/api/views/components.py +5 -5
- firefighter/api/views/incidents.py +9 -9
- firefighter/confluence/signals/incident_updated.py +2 -2
- firefighter/firefighter/settings/components/raid.py +3 -0
- firefighter/incidents/admin.py +24 -24
- firefighter/incidents/enums.py +22 -2
- firefighter/incidents/factories.py +14 -5
- firefighter/incidents/forms/close_incident.py +4 -4
- firefighter/incidents/forms/closure_reason.py +45 -0
- firefighter/incidents/forms/create_incident.py +4 -4
- firefighter/incidents/forms/unified_incident.py +406 -0
- firefighter/incidents/forms/update_status.py +91 -5
- firefighter/incidents/menus.py +2 -2
- firefighter/incidents/migrations/0005_enable_from_p1_to_p5_priority.py +7 -5
- firefighter/incidents/migrations/0009_update_sla.py +7 -5
- firefighter/incidents/migrations/0020_create_incident_category_model.py +64 -0
- firefighter/incidents/migrations/0021_copy_component_data_to_incident_category.py +57 -0
- firefighter/incidents/migrations/0022_add_incident_category_fields.py +34 -0
- firefighter/incidents/migrations/0023_populate_incident_category_references.py +57 -0
- firefighter/incidents/migrations/0024_remove_component_fields_and_model.py +26 -0
- firefighter/incidents/migrations/0025_make_incident_category_required.py +24 -0
- firefighter/incidents/migrations/0026_alter_incidentcategory_options_and_more.py +39 -0
- firefighter/incidents/migrations/0027_add_closure_fields.py +40 -0
- firefighter/incidents/migrations/0028_add_closure_reason_constraint.py +33 -0
- firefighter/incidents/migrations/0029_add_custom_fields_to_incident.py +22 -0
- firefighter/incidents/models/__init__.py +1 -1
- firefighter/incidents/models/group.py +1 -1
- firefighter/incidents/models/incident.py +47 -20
- firefighter/incidents/models/{component.py → incident_category.py} +30 -29
- firefighter/incidents/models/incident_update.py +3 -3
- firefighter/incidents/static/css/main.min.css +1 -1
- firefighter/incidents/tables.py +9 -9
- firefighter/incidents/templates/layouts/partials/incident_card.html +1 -1
- firefighter/incidents/templates/layouts/partials/incident_timeline.html +2 -2
- firefighter/incidents/templates/layouts/partials/status_pill.html +1 -1
- firefighter/incidents/templates/pages/{component_detail.html → incident_category_detail.html} +13 -13
- firefighter/incidents/templates/pages/{component_list.html → incident_category_list.html} +2 -2
- firefighter/incidents/templates/pages/incident_detail.html +3 -3
- firefighter/incidents/urls.py +6 -6
- firefighter/incidents/views/components/details.py +9 -9
- firefighter/incidents/views/components/list.py +9 -9
- firefighter/incidents/views/reports.py +5 -5
- firefighter/incidents/views/users/details.py +2 -2
- firefighter/incidents/views/views.py +7 -7
- firefighter/jira_app/client.py +1 -1
- firefighter/logging/custom_json_formatter.py +2 -1
- firefighter/pagerduty/tasks/trigger_oncall.py +1 -1
- firefighter/raid/admin.py +0 -11
- firefighter/raid/apps.py +9 -26
- firefighter/raid/client.py +5 -5
- firefighter/raid/forms.py +84 -213
- firefighter/raid/migrations/0003_delete_raidarea.py +16 -0
- firefighter/raid/models.py +2 -21
- firefighter/raid/serializers.py +5 -4
- firefighter/raid/service.py +29 -27
- firefighter/raid/signals/incident_created.py +42 -15
- firefighter/raid/signals/incident_updated.py +3 -2
- firefighter/raid/utils.py +1 -1
- firefighter/raid/views/__init__.py +1 -1
- firefighter/slack/admin.py +8 -8
- firefighter/slack/management/commands/switch_test_users.py +272 -0
- firefighter/slack/messages/slack_messages.py +24 -9
- firefighter/slack/migrations/0005_add_incident_categories_fields.py +33 -0
- firefighter/slack/migrations/0006_copy_components_to_incident_categories.py +57 -0
- firefighter/slack/migrations/0007_remove_components_fields.py +22 -0
- firefighter/slack/migrations/0008_alter_conversation_incident_categories_and_more.py +33 -0
- firefighter/slack/models/conversation.py +3 -3
- firefighter/slack/models/incident_channel.py +1 -1
- firefighter/slack/models/user.py +1 -1
- firefighter/slack/models/user_group.py +3 -3
- firefighter/slack/rules.py +2 -2
- firefighter/slack/signals/create_incident_conversation.py +6 -0
- firefighter/slack/signals/get_users.py +2 -2
- firefighter/slack/signals/incident_updated.py +8 -2
- firefighter/slack/utils.py +2 -2
- firefighter/slack/views/events/home.py +2 -2
- firefighter/slack/views/modals/__init__.py +4 -0
- firefighter/slack/views/modals/base_modal/form_utils.py +78 -0
- firefighter/slack/views/modals/close.py +18 -5
- firefighter/slack/views/modals/closure_reason.py +193 -0
- firefighter/slack/views/modals/open.py +83 -12
- firefighter/slack/views/modals/opening/check_current_incidents.py +2 -2
- firefighter/slack/views/modals/opening/details/unified.py +203 -0
- firefighter/slack/views/modals/opening/select_impact.py +5 -2
- firefighter/slack/views/modals/opening/set_details.py +3 -2
- firefighter/slack/views/modals/postmortem.py +10 -2
- firefighter/slack/views/modals/update_status.py +32 -6
- firefighter/slack/views/modals/utils.py +51 -0
- firefighter_fixtures/incidents/{components.json → incident_categories.json} +52 -52
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/METADATA +2 -2
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/RECORD +133 -88
- firefighter_tests/conftest.py +4 -5
- firefighter_tests/test_api/test_api_landbot.py +1 -1
- firefighter_tests/test_firefighter/test_sso.py +146 -0
- firefighter_tests/test_incidents/test_enums.py +100 -0
- firefighter_tests/test_incidents/test_forms/conftest.py +179 -0
- firefighter_tests/test_incidents/test_forms/test_closure_reason.py +91 -0
- firefighter_tests/test_incidents/test_forms/test_form_utils.py +15 -15
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form.py +570 -0
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form_integration.py +581 -0
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form_p4_p5.py +410 -0
- firefighter_tests/test_incidents/test_forms/test_update_status_workflow.py +343 -0
- firefighter_tests/test_incidents/test_forms/test_workflow_transitions.py +167 -0
- firefighter_tests/test_incidents/test_incident_urls.py +3 -3
- firefighter_tests/test_incidents/test_models/test_incident_category.py +165 -0
- firefighter_tests/test_incidents/test_models/test_incident_model.py +70 -2
- firefighter_tests/test_raid/conftest.py +154 -0
- firefighter_tests/test_raid/test_p1_p3_jira_fields.py +372 -0
- firefighter_tests/test_raid/test_priority_mapping.py +267 -0
- firefighter_tests/test_raid/test_raid_client.py +580 -0
- firefighter_tests/test_raid/test_raid_forms.py +552 -0
- firefighter_tests/test_raid/test_raid_models.py +185 -0
- firefighter_tests/test_raid/test_raid_serializers.py +507 -0
- firefighter_tests/test_raid/test_raid_service.py +442 -0
- firefighter_tests/test_raid/test_raid_signals.py +187 -0
- firefighter_tests/test_raid/test_raid_views.py +196 -0
- firefighter_tests/test_slack/messages/__init__.py +0 -0
- firefighter_tests/test_slack/messages/test_slack_messages.py +367 -0
- firefighter_tests/test_slack/views/modals/conftest.py +140 -0
- firefighter_tests/test_slack/views/modals/test_close.py +71 -9
- firefighter_tests/test_slack/views/modals/test_closure_reason_modal.py +138 -0
- firefighter_tests/test_slack/views/modals/test_form_utils_multiple_choice.py +249 -0
- firefighter_tests/test_slack/views/modals/test_open.py +146 -2
- firefighter_tests/test_slack/views/modals/test_opening_unified.py +421 -0
- firefighter_tests/test_slack/views/modals/test_update_status.py +331 -7
- firefighter_tests/test_slack/views/modals/test_utils.py +135 -0
- firefighter/raid/views/open_normal.py +0 -139
- firefighter/slack/views/modals/opening/details/critical.py +0 -88
- firefighter_fixtures/raid/area.json +0 -1
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,8 +6,10 @@ from unittest.mock import MagicMock, Mock, patch
|
|
|
6
6
|
import pytest
|
|
7
7
|
from slack_sdk.models.blocks.blocks import (
|
|
8
8
|
ContextBlock,
|
|
9
|
+
SectionBlock,
|
|
9
10
|
)
|
|
10
11
|
|
|
12
|
+
from firefighter.incidents.factories import IncidentCategoryFactory
|
|
11
13
|
from firefighter.incidents.forms.create_incident import CreateIncidentFormBase
|
|
12
14
|
from firefighter.incidents.models.user import User
|
|
13
15
|
from firefighter.slack.views.modals.open import OpeningData, OpenModal
|
|
@@ -64,6 +66,7 @@ def test_validate_details_form_valid() -> None:
|
|
|
64
66
|
details_form_modal_class = MagicMock(spec=SetIncidentDetails)
|
|
65
67
|
details_form_class = MagicMock(spec=CreateIncidentFormBase)
|
|
66
68
|
details_form_data = {"key": "value"}
|
|
69
|
+
open_incident_context = build_opening_data()
|
|
67
70
|
|
|
68
71
|
details_form_modal_class.form_class = details_form_class
|
|
69
72
|
|
|
@@ -72,7 +75,7 @@ def test_validate_details_form_valid() -> None:
|
|
|
72
75
|
details_form_class.return_value = details_form_instance
|
|
73
76
|
|
|
74
77
|
is_valid, returned_form_class, returned_form = OpenModal._validate_details_form(
|
|
75
|
-
details_form_modal_class, details_form_data
|
|
78
|
+
details_form_modal_class, details_form_data, open_incident_context
|
|
76
79
|
)
|
|
77
80
|
|
|
78
81
|
assert is_valid is True
|
|
@@ -84,6 +87,7 @@ def test_validate_details_form_invalid() -> None:
|
|
|
84
87
|
details_form_modal_class = MagicMock(spec=SetIncidentDetails)
|
|
85
88
|
details_form_class = MagicMock(spec=CreateIncidentFormBase)
|
|
86
89
|
details_form_data = {"key": "value"}
|
|
90
|
+
open_incident_context = build_opening_data()
|
|
87
91
|
|
|
88
92
|
details_form_modal_class.form_class = details_form_class
|
|
89
93
|
|
|
@@ -92,7 +96,7 @@ def test_validate_details_form_invalid() -> None:
|
|
|
92
96
|
details_form_class.return_value = details_form_instance
|
|
93
97
|
|
|
94
98
|
is_valid, returned_form_class, returned_form = OpenModal._validate_details_form(
|
|
95
|
-
details_form_modal_class, details_form_data
|
|
99
|
+
details_form_modal_class, details_form_data, open_incident_context
|
|
96
100
|
)
|
|
97
101
|
|
|
98
102
|
assert is_valid is False
|
|
@@ -139,3 +143,143 @@ def test_build_modal_fn_empty(user: User) -> None:
|
|
|
139
143
|
|
|
140
144
|
# View should not have a submit button
|
|
141
145
|
assert view.submit is None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@pytest.mark.django_db
|
|
149
|
+
def test_get_done_review_blocks_with_custom_fields(
|
|
150
|
+
user: User, priority_factory, environment_factory
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Test that get_done_review_blocks doesn't crash when form has custom fields."""
|
|
153
|
+
# Use factories to create DB objects
|
|
154
|
+
priority = priority_factory(value=1, default=True)
|
|
155
|
+
environment = environment_factory(value="PRD", default=True)
|
|
156
|
+
category = IncidentCategoryFactory()
|
|
157
|
+
|
|
158
|
+
# Create a mock form with cleaned_data containing custom fields
|
|
159
|
+
mock_form = MagicMock(spec=CreateIncidentFormBase)
|
|
160
|
+
mock_form.cleaned_data = {
|
|
161
|
+
"title": "Test incident with custom fields",
|
|
162
|
+
"description": "Testing custom fields handling",
|
|
163
|
+
"priority": priority,
|
|
164
|
+
"incident_category": category,
|
|
165
|
+
"environment": [environment],
|
|
166
|
+
"platform": ["platform-FR"],
|
|
167
|
+
# Custom fields that should be removed before creating Incident
|
|
168
|
+
"zendesk_ticket_id": "ZD-12345",
|
|
169
|
+
"seller_contract_id": "SELLER-789",
|
|
170
|
+
"zoho_desk_ticket_id": "ZOHO-456",
|
|
171
|
+
"is_key_account": True,
|
|
172
|
+
"is_seller_in_golden_list": False,
|
|
173
|
+
"suggested_team_routing": None,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
open_incident_context = build_opening_data(response_type="critical")
|
|
177
|
+
|
|
178
|
+
# Should not raise TypeError about unexpected keyword arguments
|
|
179
|
+
blocks = OpenModal.get_done_review_blocks(
|
|
180
|
+
open_incident_context,
|
|
181
|
+
user,
|
|
182
|
+
details_form_done=True,
|
|
183
|
+
details_form_class=type(mock_form),
|
|
184
|
+
details_form=mock_form,
|
|
185
|
+
can_submit=True,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Should return blocks (at least the divider and tada block)
|
|
189
|
+
assert len(blocks) >= 2
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@pytest.mark.django_db
|
|
193
|
+
def test_get_done_review_blocks_critical_includes_slack_and_jira_messages(
|
|
194
|
+
user: User, priority_factory, environment_factory
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Test that critical incidents show both Slack channel and Jira ticket messages."""
|
|
197
|
+
# Use factories to create DB objects
|
|
198
|
+
priority = priority_factory(value=1, default=True)
|
|
199
|
+
environment = environment_factory(value="PRD", default=True)
|
|
200
|
+
category = IncidentCategoryFactory()
|
|
201
|
+
|
|
202
|
+
# Create a mock form with cleaned_data
|
|
203
|
+
mock_form = MagicMock(spec=CreateIncidentFormBase)
|
|
204
|
+
mock_form.cleaned_data = {
|
|
205
|
+
"title": "Test critical incident",
|
|
206
|
+
"description": "Testing Slack and Jira messages",
|
|
207
|
+
"priority": priority,
|
|
208
|
+
"incident_category": category,
|
|
209
|
+
"environment": [environment],
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
open_incident_context = build_opening_data(response_type="critical")
|
|
213
|
+
|
|
214
|
+
blocks = OpenModal.get_done_review_blocks(
|
|
215
|
+
open_incident_context,
|
|
216
|
+
user,
|
|
217
|
+
details_form_done=True,
|
|
218
|
+
details_form_class=type(mock_form),
|
|
219
|
+
details_form=mock_form,
|
|
220
|
+
can_submit=True,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Find the SectionBlock containing the message
|
|
224
|
+
section_blocks = [block for block in blocks if isinstance(block, SectionBlock)]
|
|
225
|
+
assert len(section_blocks) >= 1
|
|
226
|
+
|
|
227
|
+
# Get the last SectionBlock which should contain the Slack + Jira message
|
|
228
|
+
message_block = section_blocks[-1].text
|
|
229
|
+
# Extract text string - it could be a string or a MarkdownTextObject
|
|
230
|
+
assert message_block is not None, "SectionBlock text should not be None"
|
|
231
|
+
message_text = message_block.text if hasattr(message_block, "text") else str(message_block)
|
|
232
|
+
|
|
233
|
+
# Verify both Slack and Jira messages are present
|
|
234
|
+
assert ":slack:" in message_text, "Slack channel message should be present for critical incidents"
|
|
235
|
+
assert "A dedicated Slack channel will be created" in message_text
|
|
236
|
+
assert ":jira_new:" in message_text
|
|
237
|
+
assert "An associated Jira ticket will also be created" in message_text
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@pytest.mark.django_db
|
|
241
|
+
def test_get_done_review_blocks_normal_includes_only_jira_message(
|
|
242
|
+
user: User, priority_factory, environment_factory
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Test that normal incidents show only Jira ticket message (no Slack channel)."""
|
|
245
|
+
# Use factories to create DB objects
|
|
246
|
+
priority = priority_factory(value=4, default=False)
|
|
247
|
+
environment = environment_factory(value="PRD", default=True)
|
|
248
|
+
category = IncidentCategoryFactory()
|
|
249
|
+
|
|
250
|
+
# Create a mock form with cleaned_data
|
|
251
|
+
mock_form = MagicMock(spec=CreateIncidentFormBase)
|
|
252
|
+
mock_form.cleaned_data = {
|
|
253
|
+
"title": "Test normal incident",
|
|
254
|
+
"description": "Testing Jira-only message",
|
|
255
|
+
"priority": priority,
|
|
256
|
+
"incident_category": category,
|
|
257
|
+
"environment": [environment],
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
open_incident_context = build_opening_data(response_type="normal")
|
|
261
|
+
|
|
262
|
+
blocks = OpenModal.get_done_review_blocks(
|
|
263
|
+
open_incident_context,
|
|
264
|
+
user,
|
|
265
|
+
details_form_done=True,
|
|
266
|
+
details_form_class=type(mock_form),
|
|
267
|
+
details_form=mock_form,
|
|
268
|
+
can_submit=True,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Find the SectionBlock containing the message
|
|
272
|
+
section_blocks = [block for block in blocks if isinstance(block, SectionBlock)]
|
|
273
|
+
assert len(section_blocks) >= 1
|
|
274
|
+
|
|
275
|
+
# Get the last SectionBlock which should contain only the Jira message
|
|
276
|
+
message_block = section_blocks[-1].text
|
|
277
|
+
# Extract text string - it could be a string or a MarkdownTextObject
|
|
278
|
+
assert message_block is not None, "SectionBlock text should not be None"
|
|
279
|
+
message_text = message_block.text if hasattr(message_block, "text") else str(message_block)
|
|
280
|
+
|
|
281
|
+
# Verify only Jira message is present (no Slack channel mention)
|
|
282
|
+
assert ":slack:" not in message_text, "Slack channel message should NOT be present for normal incidents"
|
|
283
|
+
assert "Slack channel" not in message_text
|
|
284
|
+
assert ":jira_new:" in message_text
|
|
285
|
+
assert "A Jira ticket will be created" in message_text
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"""Tests for unified incident opening modal."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from firefighter.incidents.models.impact import LevelChoices
|
|
9
|
+
from firefighter.slack.views.modals.base_modal.form_utils import SlackForm
|
|
10
|
+
from firefighter.slack.views.modals.opening.details.unified import (
|
|
11
|
+
OpeningUnifiedModal,
|
|
12
|
+
UnifiedIncidentFormSlack,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.django_db
|
|
17
|
+
class TestUnifiedIncidentFormSlack:
|
|
18
|
+
"""Test Slack-specific version of unified form."""
|
|
19
|
+
|
|
20
|
+
def test_form_initializes_with_impacts_and_response_type(
|
|
21
|
+
self, priority_factory, impact_level_factory
|
|
22
|
+
):
|
|
23
|
+
"""Form should properly receive impacts_data and response_type."""
|
|
24
|
+
priority_factory(value=1, default=True)
|
|
25
|
+
customer_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Customers")
|
|
26
|
+
|
|
27
|
+
impacts_data = {"customers_impact": customer_impact}
|
|
28
|
+
|
|
29
|
+
form = UnifiedIncidentFormSlack(
|
|
30
|
+
impacts_data=impacts_data,
|
|
31
|
+
response_type="critical",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Should configure field visibility
|
|
35
|
+
assert "zendesk_ticket_id" in form.fields
|
|
36
|
+
assert "suggested_team_routing" not in form.fields
|
|
37
|
+
|
|
38
|
+
def test_slack_blocks_generation_with_multiple_choice_fields(
|
|
39
|
+
self, priority_factory, environment_factory
|
|
40
|
+
):
|
|
41
|
+
"""Slack blocks should generate correctly with multiple choice fields."""
|
|
42
|
+
priority_factory(value=1, default=True)
|
|
43
|
+
environment_factory(value="PRD", default=True)
|
|
44
|
+
environment_factory(value="STG", default=False)
|
|
45
|
+
|
|
46
|
+
# Use SlackForm wrapper to generate blocks
|
|
47
|
+
slack_form = SlackForm(UnifiedIncidentFormSlack)(
|
|
48
|
+
impacts_data={},
|
|
49
|
+
response_type="critical",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Should generate blocks without errors
|
|
53
|
+
blocks = slack_form.slack_blocks()
|
|
54
|
+
assert len(blocks) > 0
|
|
55
|
+
|
|
56
|
+
# Find environment block (multi-select)
|
|
57
|
+
env_blocks = [b for b in blocks if hasattr(b, "block_id") and b.block_id == "environment"]
|
|
58
|
+
assert len(env_blocks) == 1
|
|
59
|
+
|
|
60
|
+
env_block = env_blocks[0]
|
|
61
|
+
assert env_block.element.type == "multi_static_select"
|
|
62
|
+
|
|
63
|
+
def test_priority_field_is_hidden(self, priority_factory):
|
|
64
|
+
"""Priority field should not appear in Slack blocks."""
|
|
65
|
+
priority_factory(value=1, default=True)
|
|
66
|
+
|
|
67
|
+
# Use SlackForm wrapper to generate blocks
|
|
68
|
+
slack_form = SlackForm(UnifiedIncidentFormSlack)(
|
|
69
|
+
impacts_data={},
|
|
70
|
+
response_type="critical",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
blocks = slack_form.slack_blocks()
|
|
74
|
+
|
|
75
|
+
# Priority field should not generate a block (it's hidden)
|
|
76
|
+
priority_blocks = [
|
|
77
|
+
b for b in blocks if hasattr(b, "block_id") and b.block_id == "priority"
|
|
78
|
+
]
|
|
79
|
+
assert len(priority_blocks) == 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.django_db
|
|
83
|
+
class TestOpeningUnifiedModal:
|
|
84
|
+
"""Test the unified opening modal."""
|
|
85
|
+
|
|
86
|
+
def test_build_modal_fn_passes_context_to_form(
|
|
87
|
+
self, priority_factory, impact_level_factory
|
|
88
|
+
):
|
|
89
|
+
"""build_modal_fn should pass impacts and response_type to form."""
|
|
90
|
+
priority_factory(value=1, default=True)
|
|
91
|
+
customer_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Customers")
|
|
92
|
+
|
|
93
|
+
modal = OpeningUnifiedModal()
|
|
94
|
+
|
|
95
|
+
open_incident_context = {
|
|
96
|
+
"response_type": "critical",
|
|
97
|
+
"impact_form_data": {"customers_impact": customer_impact},
|
|
98
|
+
"details_form_data": {},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
102
|
+
|
|
103
|
+
# Should build view without errors
|
|
104
|
+
assert view is not None
|
|
105
|
+
assert view.type == "modal"
|
|
106
|
+
assert len(view.blocks) > 0
|
|
107
|
+
|
|
108
|
+
# Check private_metadata contains our context
|
|
109
|
+
metadata = json.loads(view.private_metadata)
|
|
110
|
+
assert metadata["response_type"] == "critical"
|
|
111
|
+
assert "impact_form_data" in metadata
|
|
112
|
+
|
|
113
|
+
def test_build_modal_fn_critical_incident_hides_feature_team(
|
|
114
|
+
self, priority_factory
|
|
115
|
+
):
|
|
116
|
+
"""Critical incident should not show feature team field."""
|
|
117
|
+
priority_factory(value=1, default=True)
|
|
118
|
+
|
|
119
|
+
modal = OpeningUnifiedModal()
|
|
120
|
+
|
|
121
|
+
open_incident_context = {
|
|
122
|
+
"response_type": "critical",
|
|
123
|
+
"impact_form_data": {},
|
|
124
|
+
"details_form_data": {},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
128
|
+
|
|
129
|
+
# Check that feature team field is not in blocks
|
|
130
|
+
feature_team_blocks = [
|
|
131
|
+
b
|
|
132
|
+
for b in view.blocks
|
|
133
|
+
if hasattr(b, "block_id") and b.block_id == "suggested_team_routing"
|
|
134
|
+
]
|
|
135
|
+
assert len(feature_team_blocks) == 0
|
|
136
|
+
|
|
137
|
+
def test_build_modal_fn_normal_incident_shows_feature_team(
|
|
138
|
+
self, priority_factory
|
|
139
|
+
):
|
|
140
|
+
"""Normal incident (P4-P5) should show feature team field."""
|
|
141
|
+
priority_factory(value=4, default=True)
|
|
142
|
+
|
|
143
|
+
modal = OpeningUnifiedModal()
|
|
144
|
+
|
|
145
|
+
open_incident_context = {
|
|
146
|
+
"response_type": "normal",
|
|
147
|
+
"impact_form_data": {},
|
|
148
|
+
"details_form_data": {},
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
152
|
+
|
|
153
|
+
# Check that feature team field IS in blocks
|
|
154
|
+
feature_team_blocks = [
|
|
155
|
+
b
|
|
156
|
+
for b in view.blocks
|
|
157
|
+
if hasattr(b, "block_id") and b.block_id == "suggested_team_routing"
|
|
158
|
+
]
|
|
159
|
+
assert len(feature_team_blocks) == 1
|
|
160
|
+
|
|
161
|
+
def test_build_modal_fn_customer_impact_shows_zendesk(
|
|
162
|
+
self, priority_factory, impact_level_factory
|
|
163
|
+
):
|
|
164
|
+
"""Customer impact should show Zendesk field."""
|
|
165
|
+
priority_factory(value=1, default=True)
|
|
166
|
+
customer_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Customers")
|
|
167
|
+
|
|
168
|
+
modal = OpeningUnifiedModal()
|
|
169
|
+
|
|
170
|
+
open_incident_context = {
|
|
171
|
+
"response_type": "critical",
|
|
172
|
+
"impact_form_data": {"customers_impact": customer_impact},
|
|
173
|
+
"details_form_data": {},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
177
|
+
|
|
178
|
+
# Check that zendesk field IS in blocks
|
|
179
|
+
zendesk_blocks = [
|
|
180
|
+
b
|
|
181
|
+
for b in view.blocks
|
|
182
|
+
if hasattr(b, "block_id") and b.block_id == "zendesk_ticket_id"
|
|
183
|
+
]
|
|
184
|
+
assert len(zendesk_blocks) == 1
|
|
185
|
+
|
|
186
|
+
def test_build_modal_fn_seller_impact_shows_seller_fields(
|
|
187
|
+
self, priority_factory, impact_level_factory
|
|
188
|
+
):
|
|
189
|
+
"""Seller impact should show seller-specific fields."""
|
|
190
|
+
priority_factory(value=1, default=True)
|
|
191
|
+
seller_impact = impact_level_factory(value=LevelChoices.LOW, impact__name="Sellers")
|
|
192
|
+
|
|
193
|
+
modal = OpeningUnifiedModal()
|
|
194
|
+
|
|
195
|
+
open_incident_context = {
|
|
196
|
+
"response_type": "critical",
|
|
197
|
+
"impact_form_data": {"sellers_impact": seller_impact},
|
|
198
|
+
"details_form_data": {},
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
202
|
+
|
|
203
|
+
# Check seller fields are in blocks
|
|
204
|
+
seller_contract_blocks = [
|
|
205
|
+
b
|
|
206
|
+
for b in view.blocks
|
|
207
|
+
if hasattr(b, "block_id") and b.block_id == "seller_contract_id"
|
|
208
|
+
]
|
|
209
|
+
assert len(seller_contract_blocks) == 1
|
|
210
|
+
|
|
211
|
+
zoho_blocks = [
|
|
212
|
+
b
|
|
213
|
+
for b in view.blocks
|
|
214
|
+
if hasattr(b, "block_id") and b.block_id == "zoho_desk_ticket_id"
|
|
215
|
+
]
|
|
216
|
+
assert len(zoho_blocks) == 1
|
|
217
|
+
|
|
218
|
+
def test_build_modal_fn_business_impact_only_hides_customer_seller_fields(
|
|
219
|
+
self, priority_factory, impact_level_factory
|
|
220
|
+
):
|
|
221
|
+
"""Business impact only should not show customer/seller fields."""
|
|
222
|
+
priority_factory(value=1, default=True)
|
|
223
|
+
business_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Business")
|
|
224
|
+
|
|
225
|
+
modal = OpeningUnifiedModal()
|
|
226
|
+
|
|
227
|
+
open_incident_context = {
|
|
228
|
+
"response_type": "critical",
|
|
229
|
+
"impact_form_data": {"business_impact": business_impact},
|
|
230
|
+
"details_form_data": {},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
234
|
+
|
|
235
|
+
# Should NOT show customer or seller fields
|
|
236
|
+
zendesk_blocks = [
|
|
237
|
+
b
|
|
238
|
+
for b in view.blocks
|
|
239
|
+
if hasattr(b, "block_id") and b.block_id == "zendesk_ticket_id"
|
|
240
|
+
]
|
|
241
|
+
assert len(zendesk_blocks) == 0
|
|
242
|
+
|
|
243
|
+
seller_blocks = [
|
|
244
|
+
b
|
|
245
|
+
for b in view.blocks
|
|
246
|
+
if hasattr(b, "block_id") and b.block_id == "seller_contract_id"
|
|
247
|
+
]
|
|
248
|
+
assert len(seller_blocks) == 0
|
|
249
|
+
|
|
250
|
+
def test_build_modal_fn_employee_impact_only_hides_customer_seller_fields(
|
|
251
|
+
self, priority_factory, impact_level_factory
|
|
252
|
+
):
|
|
253
|
+
"""Employee impact only should not show customer/seller fields."""
|
|
254
|
+
priority_factory(value=1, default=True)
|
|
255
|
+
employee_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Employees")
|
|
256
|
+
|
|
257
|
+
modal = OpeningUnifiedModal()
|
|
258
|
+
|
|
259
|
+
open_incident_context = {
|
|
260
|
+
"response_type": "critical",
|
|
261
|
+
"impact_form_data": {"employees_impact": employee_impact},
|
|
262
|
+
"details_form_data": {},
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
266
|
+
|
|
267
|
+
# Should NOT show customer or seller fields
|
|
268
|
+
zendesk_blocks = [
|
|
269
|
+
b
|
|
270
|
+
for b in view.blocks
|
|
271
|
+
if hasattr(b, "block_id") and b.block_id == "zendesk_ticket_id"
|
|
272
|
+
]
|
|
273
|
+
assert len(zendesk_blocks) == 0
|
|
274
|
+
|
|
275
|
+
seller_blocks = [
|
|
276
|
+
b
|
|
277
|
+
for b in view.blocks
|
|
278
|
+
if hasattr(b, "block_id") and b.block_id == "seller_contract_id"
|
|
279
|
+
]
|
|
280
|
+
assert len(seller_blocks) == 0
|
|
281
|
+
|
|
282
|
+
def test_build_modal_fn_all_impacts_shows_all_fields(
|
|
283
|
+
self, priority_factory, impact_level_factory
|
|
284
|
+
):
|
|
285
|
+
"""All impact types should show all specific fields."""
|
|
286
|
+
priority_factory(value=1, default=True)
|
|
287
|
+
customer_impact = impact_level_factory(value=LevelChoices.LOW, impact__name="Customers")
|
|
288
|
+
seller_impact = impact_level_factory(value=LevelChoices.LOW, impact__name="Sellers")
|
|
289
|
+
employee_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Employees")
|
|
290
|
+
business_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Business")
|
|
291
|
+
|
|
292
|
+
modal = OpeningUnifiedModal()
|
|
293
|
+
|
|
294
|
+
open_incident_context = {
|
|
295
|
+
"response_type": "critical",
|
|
296
|
+
"impact_form_data": {
|
|
297
|
+
"customers_impact": customer_impact,
|
|
298
|
+
"sellers_impact": seller_impact,
|
|
299
|
+
"employees_impact": employee_impact,
|
|
300
|
+
"business_impact": business_impact,
|
|
301
|
+
},
|
|
302
|
+
"details_form_data": {},
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
306
|
+
|
|
307
|
+
# Should show both customer and seller fields
|
|
308
|
+
zendesk_blocks = [
|
|
309
|
+
b
|
|
310
|
+
for b in view.blocks
|
|
311
|
+
if hasattr(b, "block_id") and b.block_id == "zendesk_ticket_id"
|
|
312
|
+
]
|
|
313
|
+
assert len(zendesk_blocks) == 1
|
|
314
|
+
|
|
315
|
+
seller_blocks = [
|
|
316
|
+
b
|
|
317
|
+
for b in view.blocks
|
|
318
|
+
if hasattr(b, "block_id") and b.block_id == "seller_contract_id"
|
|
319
|
+
]
|
|
320
|
+
assert len(seller_blocks) == 1
|
|
321
|
+
|
|
322
|
+
def test_build_modal_fn_none_impacts_hides_all_fields(
|
|
323
|
+
self, priority_factory, impact_level_factory
|
|
324
|
+
):
|
|
325
|
+
"""All impacts at NONE level should not show any specific fields."""
|
|
326
|
+
priority_factory(value=1, default=True)
|
|
327
|
+
customer_impact = impact_level_factory(value=LevelChoices.NONE, impact__name="Customers")
|
|
328
|
+
seller_impact = impact_level_factory(value=LevelChoices.NONE, impact__name="Sellers")
|
|
329
|
+
|
|
330
|
+
modal = OpeningUnifiedModal()
|
|
331
|
+
|
|
332
|
+
open_incident_context = {
|
|
333
|
+
"response_type": "critical",
|
|
334
|
+
"impact_form_data": {
|
|
335
|
+
"customers_impact": customer_impact,
|
|
336
|
+
"sellers_impact": seller_impact,
|
|
337
|
+
},
|
|
338
|
+
"details_form_data": {},
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
342
|
+
|
|
343
|
+
# Should NOT show any specific fields
|
|
344
|
+
zendesk_blocks = [
|
|
345
|
+
b
|
|
346
|
+
for b in view.blocks
|
|
347
|
+
if hasattr(b, "block_id") and b.block_id == "zendesk_ticket_id"
|
|
348
|
+
]
|
|
349
|
+
assert len(zendesk_blocks) == 0
|
|
350
|
+
|
|
351
|
+
seller_blocks = [
|
|
352
|
+
b
|
|
353
|
+
for b in view.blocks
|
|
354
|
+
if hasattr(b, "block_id") and b.block_id == "seller_contract_id"
|
|
355
|
+
]
|
|
356
|
+
assert len(seller_blocks) == 0
|
|
357
|
+
|
|
358
|
+
def test_build_modal_fn_normal_with_customer_shows_team_and_zendesk(
|
|
359
|
+
self, priority_factory, impact_level_factory
|
|
360
|
+
):
|
|
361
|
+
"""P4-P5 with customer impact should show both feature team and Zendesk."""
|
|
362
|
+
priority_factory(value=4, default=True)
|
|
363
|
+
customer_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Customers")
|
|
364
|
+
|
|
365
|
+
modal = OpeningUnifiedModal()
|
|
366
|
+
|
|
367
|
+
open_incident_context = {
|
|
368
|
+
"response_type": "normal",
|
|
369
|
+
"impact_form_data": {"customers_impact": customer_impact},
|
|
370
|
+
"details_form_data": {},
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
view = modal.build_modal_fn(open_incident_context=open_incident_context)
|
|
374
|
+
|
|
375
|
+
# Should show both fields
|
|
376
|
+
team_blocks = [
|
|
377
|
+
b
|
|
378
|
+
for b in view.blocks
|
|
379
|
+
if hasattr(b, "block_id") and b.block_id == "suggested_team_routing"
|
|
380
|
+
]
|
|
381
|
+
assert len(team_blocks) == 1
|
|
382
|
+
|
|
383
|
+
zendesk_blocks = [
|
|
384
|
+
b
|
|
385
|
+
for b in view.blocks
|
|
386
|
+
if hasattr(b, "block_id") and b.block_id == "zendesk_ticket_id"
|
|
387
|
+
]
|
|
388
|
+
assert len(zendesk_blocks) == 1
|
|
389
|
+
|
|
390
|
+
def test_handle_modal_fn_preserves_custom_fields_in_validation(
|
|
391
|
+
self, priority_factory, impact_level_factory
|
|
392
|
+
):
|
|
393
|
+
"""Form validation during submission should preserve custom fields based on impact context."""
|
|
394
|
+
# Setup
|
|
395
|
+
priority = priority_factory(value=1, default=True)
|
|
396
|
+
customer_impact = impact_level_factory(value=LevelChoices.HIGH, impact__name="Customers")
|
|
397
|
+
|
|
398
|
+
modal = OpeningUnifiedModal()
|
|
399
|
+
|
|
400
|
+
# Simulate private_metadata as it would be in the modal submission
|
|
401
|
+
private_metadata = {
|
|
402
|
+
"response_type": "critical",
|
|
403
|
+
"impact_form_data": {"customers_impact": str(customer_impact.id)},
|
|
404
|
+
"details_form_data": {"priority": str(priority.id)},
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
# This is what handle_modal_fn now does (with our fix)
|
|
408
|
+
# It passes open_incident_context to the form initialization
|
|
409
|
+
slack_form = modal.get_form_class()(
|
|
410
|
+
data={}, # Empty data for this test - we just want to verify field visibility
|
|
411
|
+
open_incident_context=private_metadata,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
form = slack_form.form
|
|
415
|
+
|
|
416
|
+
# Verify that zendesk_ticket_id field exists in the form
|
|
417
|
+
# This proves the context was passed correctly and field visibility was configured
|
|
418
|
+
assert "zendesk_ticket_id" in form.fields, "zendesk_ticket_id should be present in form fields when customer impact is selected"
|
|
419
|
+
|
|
420
|
+
# Verify that suggested_team_routing is NOT in the form (critical incident)
|
|
421
|
+
assert "suggested_team_routing" not in form.fields, "suggested_team_routing should not be present for critical incidents"
|