|
12 | 12 | from sentry.issues.ingest import save_issue_occurrence |
13 | 13 | from sentry.locks import locks |
14 | 14 | from sentry.seer.autofix.constants import SeerAutomationSource |
15 | | -from sentry.seer.autofix.issue_summary import _call_seer, _get_event, get_issue_summary |
| 15 | +from sentry.seer.autofix.issue_summary import ( |
| 16 | + _call_seer, |
| 17 | + _get_event, |
| 18 | + _get_stopping_point_from_fixability, |
| 19 | + _run_automation, |
| 20 | + get_issue_summary, |
| 21 | +) |
| 22 | +from sentry.seer.autofix.utils import AutofixStoppingPoint |
16 | 23 | from sentry.seer.models import SummarizeIssueResponse, SummarizeIssueScores |
17 | 24 | from sentry.testutils.cases import APITestCase, SnubaTestCase |
18 | 25 | from sentry.testutils.helpers.datetime import before_now |
@@ -682,3 +689,103 @@ def test_get_issue_summary_handles_trace_tree_errors( |
682 | 689 |
|
683 | 690 | assert status_code == 200 |
684 | 691 | mock_call_seer.assert_called_once_with(self.group, serialized_event, None) |
| 692 | + |
| 693 | + |
| 694 | +class TestGetStoppingPointFromFixability: |
| 695 | + @pytest.mark.parametrize( |
| 696 | + "score,expected", |
| 697 | + [ |
| 698 | + (0.0, None), |
| 699 | + (0.39, None), |
| 700 | + (0.40, AutofixStoppingPoint.SOLUTION), |
| 701 | + (0.65, AutofixStoppingPoint.SOLUTION), |
| 702 | + (0.66, AutofixStoppingPoint.CODE_CHANGES), |
| 703 | + (1.0, AutofixStoppingPoint.CODE_CHANGES), |
| 704 | + ], |
| 705 | + ) |
| 706 | + def test_stopping_point_mapping(self, score, expected): |
| 707 | + assert _get_stopping_point_from_fixability(score) == expected |
| 708 | + |
| 709 | + |
| 710 | +@with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) |
| 711 | +class TestRunAutomationStoppingPoint(APITestCase, SnubaTestCase): |
| 712 | + def setUp(self) -> None: |
| 713 | + super().setUp() |
| 714 | + self.group = self.create_group() |
| 715 | + event_data = load_data("python") |
| 716 | + self.event = self.store_event(data=event_data, project_id=self.project.id) |
| 717 | + |
| 718 | + @patch("sentry.seer.autofix.issue_summary._trigger_autofix_task.delay") |
| 719 | + @patch( |
| 720 | + "sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited", |
| 721 | + return_value=False, |
| 722 | + ) |
| 723 | + @patch("sentry.seer.autofix.issue_summary.get_autofix_state", return_value=None) |
| 724 | + @patch("sentry.quotas.backend.has_available_reserved_budget", return_value=True) |
| 725 | + @patch("sentry.seer.autofix.issue_summary._generate_fixability_score") |
| 726 | + def test_high_fixability_code_changes( |
| 727 | + self, mock_gen, mock_budget, mock_state, mock_rate, mock_trigger |
| 728 | + ): |
| 729 | + self.project.update_option("sentry:autofix_automation_tuning", "always") |
| 730 | + mock_gen.return_value = SummarizeIssueResponse( |
| 731 | + group_id=str(self.group.id), |
| 732 | + headline="h", |
| 733 | + whats_wrong="w", |
| 734 | + trace="t", |
| 735 | + possible_cause="c", |
| 736 | + scores=SummarizeIssueScores(fixability_score=0.80), |
| 737 | + ) |
| 738 | + _run_automation(self.group, self.user, self.event, SeerAutomationSource.ALERT) |
| 739 | + mock_trigger.assert_called_once() |
| 740 | + assert mock_trigger.call_args[1]["stopping_point"] == AutofixStoppingPoint.CODE_CHANGES |
| 741 | + |
| 742 | + @patch("sentry.seer.autofix.issue_summary._trigger_autofix_task.delay") |
| 743 | + @patch( |
| 744 | + "sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited", |
| 745 | + return_value=False, |
| 746 | + ) |
| 747 | + @patch("sentry.seer.autofix.issue_summary.get_autofix_state", return_value=None) |
| 748 | + @patch("sentry.quotas.backend.has_available_reserved_budget", return_value=True) |
| 749 | + @patch("sentry.seer.autofix.issue_summary._generate_fixability_score") |
| 750 | + def test_medium_fixability_solution( |
| 751 | + self, mock_gen, mock_budget, mock_state, mock_rate, mock_trigger |
| 752 | + ): |
| 753 | + self.project.update_option("sentry:autofix_automation_tuning", "always") |
| 754 | + mock_gen.return_value = SummarizeIssueResponse( |
| 755 | + group_id=str(self.group.id), |
| 756 | + headline="h", |
| 757 | + whats_wrong="w", |
| 758 | + trace="t", |
| 759 | + possible_cause="c", |
| 760 | + scores=SummarizeIssueScores(fixability_score=0.50), |
| 761 | + ) |
| 762 | + _run_automation(self.group, self.user, self.event, SeerAutomationSource.ALERT) |
| 763 | + mock_trigger.assert_called_once() |
| 764 | + assert mock_trigger.call_args[1]["stopping_point"] == AutofixStoppingPoint.SOLUTION |
| 765 | + |
| 766 | + @patch("sentry.seer.autofix.issue_summary._trigger_autofix_task.delay") |
| 767 | + @patch( |
| 768 | + "sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited", |
| 769 | + return_value=False, |
| 770 | + ) |
| 771 | + @patch("sentry.seer.autofix.issue_summary.get_autofix_state", return_value=None) |
| 772 | + @patch("sentry.quotas.backend.has_available_reserved_budget", return_value=True) |
| 773 | + @patch("sentry.seer.autofix.issue_summary._generate_fixability_score") |
| 774 | + def test_without_feature_flag(self, mock_gen, mock_budget, mock_state, mock_rate, mock_trigger): |
| 775 | + self.project.update_option("sentry:autofix_automation_tuning", "always") |
| 776 | + mock_gen.return_value = SummarizeIssueResponse( |
| 777 | + group_id=str(self.group.id), |
| 778 | + headline="h", |
| 779 | + whats_wrong="w", |
| 780 | + trace="t", |
| 781 | + possible_cause="c", |
| 782 | + scores=SummarizeIssueScores(fixability_score=0.80), |
| 783 | + ) |
| 784 | + |
| 785 | + with self.feature( |
| 786 | + {"organizations:gen-ai-features": True, "projects:triage-signals-v0": False} |
| 787 | + ): |
| 788 | + _run_automation(self.group, self.user, self.event, SeerAutomationSource.ALERT) |
| 789 | + |
| 790 | + mock_trigger.assert_called_once() |
| 791 | + assert mock_trigger.call_args[1]["stopping_point"] is None |
0 commit comments