firefighter-incident 0.0.14__py3-none-any.whl → 0.0.16__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/api/serializers.py +9 -0
- firefighter/confluence/signals/incident_updated.py +2 -2
- firefighter/incidents/enums.py +22 -2
- firefighter/incidents/forms/closure_reason.py +45 -0
- firefighter/incidents/forms/unified_incident.py +406 -0
- firefighter/incidents/forms/update_status.py +87 -1
- 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/incident.py +32 -5
- firefighter/incidents/static/css/main.min.css +1 -1
- firefighter/incidents/templates/layouts/partials/status_pill.html +1 -1
- firefighter/incidents/views/reports.py +3 -3
- firefighter/raid/apps.py +9 -26
- firefighter/raid/client.py +2 -2
- firefighter/raid/forms.py +75 -238
- firefighter/raid/signals/incident_created.py +38 -13
- firefighter/raid/signals/incident_updated.py +3 -2
- firefighter/slack/messages/slack_messages.py +19 -4
- firefighter/slack/rules.py +1 -1
- firefighter/slack/signals/create_incident_conversation.py +6 -0
- firefighter/slack/signals/incident_updated.py +7 -1
- firefighter/slack/views/modals/__init__.py +4 -0
- firefighter/slack/views/modals/base_modal/form_utils.py +63 -0
- firefighter/slack/views/modals/close.py +15 -2
- firefighter/slack/views/modals/closure_reason.py +193 -0
- firefighter/slack/views/modals/open.py +60 -13
- firefighter/slack/views/modals/opening/details/unified.py +203 -0
- firefighter/slack/views/modals/opening/select_impact.py +1 -1
- 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 +28 -2
- firefighter/slack/views/modals/utils.py +51 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/RECORD +62 -38
- 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_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_models/test_incident_model.py +68 -0
- 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_raid_forms.py +10 -253
- firefighter_tests/test_raid/test_raid_signals.py +187 -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 +65 -3
- 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 +327 -3
- 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_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Pytest fixtures for RAID tests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import factory
|
|
6
|
+
import pytest
|
|
7
|
+
from factory.django import DjangoModelFactory
|
|
8
|
+
|
|
9
|
+
from firefighter.incidents.factories import (
|
|
10
|
+
IncidentCategoryFactory,
|
|
11
|
+
IncidentFactory,
|
|
12
|
+
UserFactory,
|
|
13
|
+
)
|
|
14
|
+
from firefighter.incidents.models import Environment, Priority
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JiraUserFactoryClass(DjangoModelFactory):
|
|
18
|
+
"""Factory for JiraUser model."""
|
|
19
|
+
|
|
20
|
+
class Meta:
|
|
21
|
+
model = "jira_app.JiraUser"
|
|
22
|
+
django_get_or_create = ("id",)
|
|
23
|
+
|
|
24
|
+
id = factory.Sequence(lambda n: f"jira-user-{n}")
|
|
25
|
+
user = factory.SubFactory(UserFactory)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class JiraTicketFactory(DjangoModelFactory):
|
|
29
|
+
"""Factory for JiraTicket model."""
|
|
30
|
+
|
|
31
|
+
class Meta:
|
|
32
|
+
model = "raid.JiraTicket"
|
|
33
|
+
|
|
34
|
+
id = factory.Sequence(lambda n: 10000 + n)
|
|
35
|
+
key = factory.Sequence(lambda n: f"INC-{n}")
|
|
36
|
+
summary = "Test Jira Ticket"
|
|
37
|
+
description = "Test description for Jira ticket"
|
|
38
|
+
reporter = factory.SubFactory(JiraUserFactoryClass)
|
|
39
|
+
incident = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def user_factory():
|
|
44
|
+
"""Pytest fixture for UserFactory."""
|
|
45
|
+
return UserFactory
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def incident_factory():
|
|
50
|
+
"""Pytest fixture for IncidentFactory."""
|
|
51
|
+
return IncidentFactory
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.fixture
|
|
55
|
+
def jira_user_factory():
|
|
56
|
+
"""Pytest fixture for JiraUserFactory."""
|
|
57
|
+
return JiraUserFactoryClass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def jira_ticket_factory():
|
|
62
|
+
"""Pytest fixture for JiraTicketFactory."""
|
|
63
|
+
return JiraTicketFactory
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.fixture
|
|
67
|
+
def priority_factory(db):
|
|
68
|
+
"""Factory to create Priority instances."""
|
|
69
|
+
|
|
70
|
+
def _create(**kwargs):
|
|
71
|
+
value = kwargs.get("value", 1)
|
|
72
|
+
name = kwargs.get("name", f"P{value}")
|
|
73
|
+
set_as_default = kwargs.get("default", False)
|
|
74
|
+
|
|
75
|
+
# If default=True, clear any other defaults first
|
|
76
|
+
if set_as_default:
|
|
77
|
+
Priority.objects.filter(default=True).update(default=False)
|
|
78
|
+
|
|
79
|
+
defaults = {
|
|
80
|
+
"emoji": "🔴",
|
|
81
|
+
"order": value,
|
|
82
|
+
"default": set_as_default,
|
|
83
|
+
"enabled_create": True,
|
|
84
|
+
"enabled_update": True,
|
|
85
|
+
"needs_postmortem": value <= 2, # P1-P2 need postmortem
|
|
86
|
+
}
|
|
87
|
+
# Remove name and value from kwargs if present
|
|
88
|
+
kwargs_copy = kwargs.copy()
|
|
89
|
+
kwargs_copy.pop("name", None)
|
|
90
|
+
kwargs_copy.pop("value", None)
|
|
91
|
+
defaults.update(kwargs_copy)
|
|
92
|
+
|
|
93
|
+
priority, created = Priority.objects.get_or_create(
|
|
94
|
+
name=name,
|
|
95
|
+
value=value,
|
|
96
|
+
defaults=defaults,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# If already exists and we want it as default, just set that
|
|
100
|
+
if not created and set_as_default:
|
|
101
|
+
priority.default = True
|
|
102
|
+
priority.save(update_fields=["default"])
|
|
103
|
+
|
|
104
|
+
return priority
|
|
105
|
+
|
|
106
|
+
return _create
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.fixture
|
|
110
|
+
def environment_factory(db):
|
|
111
|
+
"""Factory to create Environment instances."""
|
|
112
|
+
|
|
113
|
+
def _create(**kwargs):
|
|
114
|
+
value = kwargs.get("value", "TST")
|
|
115
|
+
set_as_default = kwargs.get("default", False)
|
|
116
|
+
|
|
117
|
+
# If default=True, clear any other defaults first
|
|
118
|
+
if set_as_default:
|
|
119
|
+
Environment.objects.filter(default=True).update(default=False)
|
|
120
|
+
|
|
121
|
+
defaults = {
|
|
122
|
+
"description": f"Environment {value}",
|
|
123
|
+
"order": 1,
|
|
124
|
+
"default": set_as_default,
|
|
125
|
+
}
|
|
126
|
+
# Remove value and default from kwargs if present
|
|
127
|
+
kwargs_copy = kwargs.copy()
|
|
128
|
+
kwargs_copy.pop("value", None)
|
|
129
|
+
kwargs_copy.pop("default", None)
|
|
130
|
+
defaults.update(kwargs_copy)
|
|
131
|
+
|
|
132
|
+
environment, created = Environment.objects.get_or_create(
|
|
133
|
+
value=value,
|
|
134
|
+
defaults=defaults,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# If already exists and we want it as default, just set that
|
|
138
|
+
if not created and set_as_default:
|
|
139
|
+
environment.default = True
|
|
140
|
+
environment.save(update_fields=["default"])
|
|
141
|
+
|
|
142
|
+
return environment
|
|
143
|
+
|
|
144
|
+
return _create
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@pytest.fixture
|
|
148
|
+
def incident_category_factory(db):
|
|
149
|
+
"""Factory to create IncidentCategory instances."""
|
|
150
|
+
|
|
151
|
+
def _create(**kwargs):
|
|
152
|
+
return IncidentCategoryFactory(**kwargs)
|
|
153
|
+
|
|
154
|
+
return _create
|
|
@@ -0,0 +1,372 @@
|
|
|
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"
|