|
1 | 1 | from datetime import datetime, timedelta |
2 | 2 | from unittest.mock import MagicMock, patch |
3 | 3 |
|
| 4 | +import pytest |
4 | 5 | from django.test import TestCase |
5 | 6 |
|
6 | 7 | from sentry.seer.autofix.constants import AutofixStatus, SeerAutomationSource |
7 | 8 | from sentry.seer.autofix.utils import AutofixState |
8 | | -from sentry.seer.models import SummarizeIssueResponse, SummarizeIssueScores |
9 | | -from sentry.tasks.autofix import check_autofix_status, generate_issue_summary_only |
| 9 | +from sentry.seer.models import SeerApiError, SummarizeIssueResponse, SummarizeIssueScores |
| 10 | +from sentry.tasks.autofix import ( |
| 11 | + check_autofix_status, |
| 12 | + configure_seer_for_existing_org, |
| 13 | + generate_issue_summary_only, |
| 14 | +) |
10 | 15 | from sentry.testutils.cases import TestCase as SentryTestCase |
11 | 16 |
|
12 | 17 |
|
@@ -127,3 +132,104 @@ def test_generates_fixability_score( |
127 | 132 |
|
128 | 133 | group.refresh_from_db() |
129 | 134 | assert group.seer_fixability_score == 0.75 |
| 135 | + |
| 136 | + |
| 137 | +class TestConfigureSeerForExistingOrg(SentryTestCase): |
| 138 | + @patch("sentry.tasks.autofix.bulk_set_project_preferences") |
| 139 | + @patch("sentry.tasks.autofix.bulk_get_project_preferences") |
| 140 | + def test_configures_org_and_project_settings( |
| 141 | + self, mock_bulk_get: MagicMock, mock_bulk_set: MagicMock |
| 142 | + ) -> None: |
| 143 | + """Test that org and project settings are configured correctly.""" |
| 144 | + project1 = self.create_project(organization=self.organization) |
| 145 | + project2 = self.create_project(organization=self.organization) |
| 146 | + # Set to non-off value so we can verify it gets changed to medium |
| 147 | + project1.update_option("sentry:autofix_automation_tuning", "low") |
| 148 | + project2.update_option("sentry:autofix_automation_tuning", "high") |
| 149 | + |
| 150 | + mock_bulk_get.return_value = {} |
| 151 | + |
| 152 | + configure_seer_for_existing_org(organization_id=self.organization.id) |
| 153 | + |
| 154 | + # Check org-level option |
| 155 | + assert self.organization.get_option("sentry:enable_seer_coding") is True |
| 156 | + |
| 157 | + # Check project-level options |
| 158 | + assert project1.get_option("sentry:seer_scanner_automation") is True |
| 159 | + assert project1.get_option("sentry:autofix_automation_tuning") == "medium" |
| 160 | + assert project2.get_option("sentry:seer_scanner_automation") is True |
| 161 | + assert project2.get_option("sentry:autofix_automation_tuning") == "medium" |
| 162 | + |
| 163 | + mock_bulk_get.assert_called_once() |
| 164 | + mock_bulk_set.assert_called_once() |
| 165 | + |
| 166 | + @patch("sentry.tasks.autofix.bulk_set_project_preferences") |
| 167 | + @patch("sentry.tasks.autofix.bulk_get_project_preferences") |
| 168 | + def test_overrides_autofix_off_to_medium( |
| 169 | + self, mock_bulk_get: MagicMock, mock_bulk_set: MagicMock |
| 170 | + ) -> None: |
| 171 | + """Test that projects with autofix set to off are migrated to medium.""" |
| 172 | + project = self.create_project(organization=self.organization) |
| 173 | + project.update_option("sentry:autofix_automation_tuning", "off") |
| 174 | + |
| 175 | + mock_bulk_get.return_value = {} |
| 176 | + |
| 177 | + configure_seer_for_existing_org(organization_id=self.organization.id) |
| 178 | + |
| 179 | + # autofix_automation_tuning should be migrated to medium for new pricing |
| 180 | + assert project.get_option("sentry:autofix_automation_tuning") == "medium" |
| 181 | + # Scanner should be enabled |
| 182 | + assert project.get_option("sentry:seer_scanner_automation") is True |
| 183 | + |
| 184 | + @patch("sentry.tasks.autofix.bulk_set_project_preferences") |
| 185 | + @patch("sentry.tasks.autofix.bulk_get_project_preferences") |
| 186 | + def test_skips_projects_with_existing_stopping_point( |
| 187 | + self, mock_bulk_get: MagicMock, mock_bulk_set: MagicMock |
| 188 | + ) -> None: |
| 189 | + """Test that projects with open_pr or code_changes stopping point are skipped.""" |
| 190 | + project1 = self.create_project(organization=self.organization) |
| 191 | + project2 = self.create_project(organization=self.organization) |
| 192 | + |
| 193 | + mock_bulk_get.return_value = { |
| 194 | + str(project1.id): {"automated_run_stopping_point": "open_pr"}, |
| 195 | + str(project2.id): {"automated_run_stopping_point": "code_changes"}, |
| 196 | + } |
| 197 | + |
| 198 | + configure_seer_for_existing_org(organization_id=self.organization.id) |
| 199 | + |
| 200 | + # bulk_set should not be called since both projects are skipped |
| 201 | + mock_bulk_set.assert_not_called() |
| 202 | + |
| 203 | + @patch("sentry.tasks.autofix.bulk_get_project_preferences") |
| 204 | + def test_raises_on_bulk_get_api_failure(self, mock_bulk_get: MagicMock) -> None: |
| 205 | + """Test that task raises on bulk GET API failure to trigger retry.""" |
| 206 | + project1 = self.create_project(organization=self.organization) |
| 207 | + project2 = self.create_project(organization=self.organization) |
| 208 | + |
| 209 | + mock_bulk_get.side_effect = SeerApiError("API error", 500) |
| 210 | + |
| 211 | + with pytest.raises(SeerApiError): |
| 212 | + configure_seer_for_existing_org(organization_id=self.organization.id) |
| 213 | + |
| 214 | + # Sentry DB options should still be set before the API call |
| 215 | + assert project1.get_option("sentry:seer_scanner_automation") is True |
| 216 | + assert project2.get_option("sentry:seer_scanner_automation") is True |
| 217 | + |
| 218 | + @patch("sentry.tasks.autofix.bulk_set_project_preferences") |
| 219 | + @patch("sentry.tasks.autofix.bulk_get_project_preferences") |
| 220 | + def test_raises_on_bulk_set_api_failure( |
| 221 | + self, mock_bulk_get: MagicMock, mock_bulk_set: MagicMock |
| 222 | + ) -> None: |
| 223 | + """Test that task raises on bulk SET API failure to trigger retry.""" |
| 224 | + project1 = self.create_project(organization=self.organization) |
| 225 | + project2 = self.create_project(organization=self.organization) |
| 226 | + |
| 227 | + mock_bulk_get.return_value = {} |
| 228 | + mock_bulk_set.side_effect = SeerApiError("API error", 500) |
| 229 | + |
| 230 | + with pytest.raises(SeerApiError): |
| 231 | + configure_seer_for_existing_org(organization_id=self.organization.id) |
| 232 | + |
| 233 | + # Sentry DB options should still be set before the API call |
| 234 | + assert project1.get_option("sentry:seer_scanner_automation") is True |
| 235 | + assert project2.get_option("sentry:seer_scanner_automation") is True |
0 commit comments