|
15 | 15 | filter_null_from_string, |
16 | 16 | get_stacktrace_string, |
17 | 17 | get_token_count, |
| 18 | + set_default_project_auto_open_prs, |
18 | 19 | stacktrace_exceeds_limits, |
19 | 20 | ) |
20 | 21 | from sentry.services.eventstore.models import Event |
@@ -1220,3 +1221,140 @@ def test_generates_stacktrace_string_from_variants(self) -> None: |
1220 | 1221 | assert token_count > 0 |
1221 | 1222 | # Verify we get the expected token count for this specific stacktrace |
1222 | 1223 | assert token_count == 33 |
| 1224 | + |
| 1225 | + |
| 1226 | +class TestSetDefaultProjectAutoOpenPrs(TestCase): |
| 1227 | + """Tests for set_default_project_auto_open_prs which wires org-level Seer |
| 1228 | + defaults (stopping point, coding agent, auto_open_prs) into project-level |
| 1229 | + preferences at project creation time. |
| 1230 | + """ |
| 1231 | + |
| 1232 | + def setUp(self): |
| 1233 | + super().setUp() |
| 1234 | + self.project = self.create_project(organization=self.organization) |
| 1235 | + |
| 1236 | + @patch("sentry.seer.similarity.utils.write_preference_to_sentry_db") |
| 1237 | + @patch("sentry.seer.similarity.utils.set_project_seer_preference") |
| 1238 | + @patch("sentry.seer.similarity.utils.is_seer_seat_based_tier_enabled", return_value=False) |
| 1239 | + def test_skips_when_tier_not_enabled( |
| 1240 | + self, mock_tier: MagicMock, mock_set_pref: MagicMock, mock_dual_write: MagicMock |
| 1241 | + ): |
| 1242 | + set_default_project_auto_open_prs(self.organization, self.project) |
| 1243 | + mock_set_pref.assert_not_called() |
| 1244 | + mock_dual_write.assert_not_called() |
| 1245 | + |
| 1246 | + @patch("sentry.seer.similarity.utils.write_preference_to_sentry_db") |
| 1247 | + @patch("sentry.seer.similarity.utils.set_project_seer_preference") |
| 1248 | + @patch("sentry.seer.similarity.utils.is_seer_seat_based_tier_enabled", return_value=True) |
| 1249 | + def test_seer_agent_default_stopping_point( |
| 1250 | + self, mock_tier: MagicMock, mock_set_pref: MagicMock, mock_dual_write: MagicMock |
| 1251 | + ): |
| 1252 | + """Seer agent, no auto_open_prs, default stopping point (code_changes).""" |
| 1253 | + set_default_project_auto_open_prs(self.organization, self.project) |
| 1254 | + |
| 1255 | + pref = mock_set_pref.call_args[0][0] |
| 1256 | + assert pref.automated_run_stopping_point == "code_changes" |
| 1257 | + assert pref.automation_handoff is None |
| 1258 | + |
| 1259 | + @patch("sentry.seer.similarity.utils.write_preference_to_sentry_db") |
| 1260 | + @patch("sentry.seer.similarity.utils.set_project_seer_preference") |
| 1261 | + @patch("sentry.seer.similarity.utils.is_seer_seat_based_tier_enabled", return_value=True) |
| 1262 | + def test_seer_agent_custom_stopping_point( |
| 1263 | + self, mock_tier: MagicMock, mock_set_pref: MagicMock, mock_dual_write: MagicMock |
| 1264 | + ): |
| 1265 | + """Seer agent, no auto_open_prs, custom stopping point (root_cause).""" |
| 1266 | + self.organization.update_option("sentry:default_stopping_point", "root_cause") |
| 1267 | + |
| 1268 | + set_default_project_auto_open_prs(self.organization, self.project) |
| 1269 | + |
| 1270 | + pref = mock_set_pref.call_args[0][0] |
| 1271 | + assert pref.automated_run_stopping_point == "root_cause" |
| 1272 | + assert pref.automation_handoff is None |
| 1273 | + |
| 1274 | + @patch("sentry.seer.similarity.utils.write_preference_to_sentry_db") |
| 1275 | + @patch("sentry.seer.similarity.utils.set_project_seer_preference") |
| 1276 | + @patch("sentry.seer.similarity.utils.is_seer_seat_based_tier_enabled", return_value=True) |
| 1277 | + def test_seer_agent_with_auto_open_prs( |
| 1278 | + self, mock_tier: MagicMock, mock_set_pref: MagicMock, mock_dual_write: MagicMock |
| 1279 | + ): |
| 1280 | + """auto_open_prs=True forces stopping point to open_pr regardless of default.""" |
| 1281 | + self.organization.update_option("sentry:auto_open_prs", True) |
| 1282 | + self.organization.update_option("sentry:default_stopping_point", "root_cause") |
| 1283 | + |
| 1284 | + set_default_project_auto_open_prs(self.organization, self.project) |
| 1285 | + |
| 1286 | + pref = mock_set_pref.call_args[0][0] |
| 1287 | + assert pref.automated_run_stopping_point == "open_pr" |
| 1288 | + assert pref.automation_handoff is None |
| 1289 | + |
| 1290 | + @patch("sentry.seer.similarity.utils.write_preference_to_sentry_db") |
| 1291 | + @patch("sentry.seer.similarity.utils.set_project_seer_preference") |
| 1292 | + @patch("sentry.seer.similarity.utils.is_seer_seat_based_tier_enabled", return_value=True) |
| 1293 | + def test_external_agent_no_auto_open_prs( |
| 1294 | + self, mock_tier: MagicMock, mock_set_pref: MagicMock, mock_dual_write: MagicMock |
| 1295 | + ): |
| 1296 | + agents = [ |
| 1297 | + ("cursor_background_agent", 1234), |
| 1298 | + ("claude_code_agent", 5678), |
| 1299 | + ] |
| 1300 | + for agent, integration_id in agents: |
| 1301 | + with self.subTest(agent=agent): |
| 1302 | + mock_set_pref.reset_mock() |
| 1303 | + self.organization.update_option("sentry:seer_default_coding_agent", agent) |
| 1304 | + self.organization.update_option( |
| 1305 | + "sentry:seer_default_coding_agent_integration_id", integration_id |
| 1306 | + ) |
| 1307 | + |
| 1308 | + set_default_project_auto_open_prs(self.organization, self.project) |
| 1309 | + |
| 1310 | + pref = mock_set_pref.call_args[0][0] |
| 1311 | + assert pref.automated_run_stopping_point == "code_changes" |
| 1312 | + assert pref.automation_handoff is not None |
| 1313 | + assert pref.automation_handoff.handoff_point == "root_cause" |
| 1314 | + assert pref.automation_handoff.target == agent |
| 1315 | + assert pref.automation_handoff.integration_id == integration_id |
| 1316 | + assert pref.automation_handoff.auto_create_pr is False |
| 1317 | + |
| 1318 | + @patch("sentry.seer.similarity.utils.write_preference_to_sentry_db") |
| 1319 | + @patch("sentry.seer.similarity.utils.set_project_seer_preference") |
| 1320 | + @patch("sentry.seer.similarity.utils.is_seer_seat_based_tier_enabled", return_value=True) |
| 1321 | + def test_external_agent_with_auto_open_prs( |
| 1322 | + self, mock_tier: MagicMock, mock_set_pref: MagicMock, mock_dual_write: MagicMock |
| 1323 | + ): |
| 1324 | + """auto_open_prs=True forces open_pr and sets auto_create_pr on handoff.""" |
| 1325 | + self.organization.update_option("sentry:auto_open_prs", True) |
| 1326 | + agents = [ |
| 1327 | + ("cursor_background_agent", 1234), |
| 1328 | + ("claude_code_agent", 5678), |
| 1329 | + ] |
| 1330 | + for agent, integration_id in agents: |
| 1331 | + with self.subTest(agent=agent): |
| 1332 | + mock_set_pref.reset_mock() |
| 1333 | + self.organization.update_option("sentry:seer_default_coding_agent", agent) |
| 1334 | + self.organization.update_option( |
| 1335 | + "sentry:seer_default_coding_agent_integration_id", integration_id |
| 1336 | + ) |
| 1337 | + |
| 1338 | + set_default_project_auto_open_prs(self.organization, self.project) |
| 1339 | + |
| 1340 | + pref = mock_set_pref.call_args[0][0] |
| 1341 | + assert pref.automated_run_stopping_point == "open_pr" |
| 1342 | + assert pref.automation_handoff is not None |
| 1343 | + assert pref.automation_handoff.target == agent |
| 1344 | + assert pref.automation_handoff.integration_id == integration_id |
| 1345 | + assert pref.automation_handoff.auto_create_pr is True |
| 1346 | + |
| 1347 | + @patch("sentry.seer.similarity.utils.write_preference_to_sentry_db") |
| 1348 | + @patch("sentry.seer.similarity.utils.set_project_seer_preference") |
| 1349 | + @patch("sentry.seer.similarity.utils.is_seer_seat_based_tier_enabled", return_value=True) |
| 1350 | + def test_external_agent_without_integration_id_skips_handoff( |
| 1351 | + self, mock_tier: MagicMock, mock_set_pref: MagicMock, mock_dual_write: MagicMock |
| 1352 | + ): |
| 1353 | + self.organization.update_option( |
| 1354 | + "sentry:seer_default_coding_agent", "cursor_background_agent" |
| 1355 | + ) |
| 1356 | + |
| 1357 | + set_default_project_auto_open_prs(self.organization, self.project) |
| 1358 | + |
| 1359 | + pref = mock_set_pref.call_args[0][0] |
| 1360 | + assert pref.automation_handoff is None |
0 commit comments