Skip to content

Commit d46d0e1

Browse files
committed
fix merge conflicts
1 parent 6fd4aff commit d46d0e1

File tree

1 file changed

+117
-87
lines changed

1 file changed

+117
-87
lines changed

tests/sentry/seer/code_review/test_webhooks.py

Lines changed: 117 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -33,69 +33,77 @@
3333
process_github_webhook_event,
3434
)
3535
from sentry.testutils.cases import TestCase
36-
from sentry.testutils.helpers.features import Feature
36+
from sentry.testutils.helpers.features import with_feature
3737
from sentry.testutils.helpers.github import GitHubWebhookTestCase
3838

3939
CODE_REVIEW_FEATURES = {"organizations:gen-ai-features", "organizations:code-review-beta"}
4040

4141
DEFAULT_PR_AUTHOR_ID = "12345678"
4242

4343

44-
@patch("sentry.seer.code_review.billing.quotas.backend.check_seer_quota", return_value=True)
4544
class GitHubWebhookHelper(GitHubWebhookTestCase):
4645
"""Base class for GitHub webhook integration tests."""
4746

4847
github_integration: Integration | None = None
4948

49+
@pytest.fixture(autouse=True)
50+
def mock_billing_quota(self) -> Generator[None]:
51+
"""Mock billing quota check to return True for all tests."""
52+
with patch(
53+
"sentry.seer.code_review.billing.quotas.backend.check_seer_quota", return_value=True
54+
):
55+
yield
56+
57+
def _enable_code_review(self) -> None:
58+
"""Enable all required options for code review to work."""
59+
self.organization.update_option("sentry:enable_pr_review_test_generation", True)
60+
61+
# Setup billing data
62+
self.github_integration = self.create_github_integration()
63+
OrganizationContributors.objects.get_or_create(
64+
organization_id=self.organization.id,
65+
integration_id=self.github_integration.id,
66+
external_identifier=DEFAULT_PR_AUTHOR_ID,
67+
)
68+
5069
@contextmanager
5170
def code_review_setup(
5271
self, features: Collection[str] | Mapping[str, Any] = CODE_REVIEW_FEATURES
5372
) -> Generator[None]:
5473
"""Helper to set up code review test context."""
5574
self.organization.update_option("sentry:enable_pr_review_test_generation", True)
75+
76+
# Setup billing data
77+
self.github_integration = self.create_github_integration()
78+
OrganizationContributors.objects.get_or_create(
79+
organization_id=self.organization.id,
80+
integration_id=self.github_integration.id,
81+
external_identifier=DEFAULT_PR_AUTHOR_ID,
82+
)
83+
5684
with (
5785
self.feature(features),
5886
self.options({"github.webhook.issue-comment": False}),
5987
):
6088
yield
6189

6290
def _send_webhook_event(
63-
self,
64-
github_event: GithubWebhookType,
65-
event_data: bytes | str,
66-
enable_code_review: bool = False,
67-
features: set[str] | None = None,
91+
self, github_event: GithubWebhookType, event_data: bytes | str
6892
) -> HttpResponseBase:
69-
if enable_code_review:
70-
self.organization.update_option("sentry:enable_pr_review_test_generation", True)
71-
self.github_integration = self.create_github_integration()
72-
OrganizationContributors.objects.get_or_create(
73-
organization_id=self.organization.id,
74-
integration_id=self.github_integration.id,
75-
external_identifier=DEFAULT_PR_AUTHOR_ID,
76-
)
77-
93+
"""Helper to send a GitHub webhook event."""
7894
self.event_dict = (
7995
orjson.loads(event_data) if isinstance(event_data, (bytes, str)) else event_data
8096
)
8197
repo_id = int(self.event_dict["repository"]["id"])
8298

8399
integration = self.github_integration or self.create_github_integration()
84-
85100
self.create_repo(
86101
project=self.project,
87102
provider="integrations:github",
88103
external_id=repo_id,
89104
integration_id=integration.id,
90105
)
91-
92-
if enable_code_review:
93-
features_to_enable = features if features is not None else CODE_REVIEW_FEATURES
94-
with Feature(features_to_enable):
95-
response = self.send_github_webhook_event(github_event, event_data)
96-
else:
97-
response = self.send_github_webhook_event(github_event, event_data)
98-
106+
response = self.send_github_webhook_event(github_event, event_data)
99107
assert response.status_code == 204
100108
return response
101109

@@ -104,12 +112,13 @@ class CheckRunEventWebhookTest(GitHubWebhookHelper):
104112
"""Integration tests for GitHub check_run webhook events."""
105113

106114
@patch("sentry.seer.code_review.webhooks.task.process_github_webhook_event")
115+
@with_feature(CODE_REVIEW_FEATURES)
107116
def test_base_case(self, mock_task: MagicMock) -> None:
108117
"""Test that rerequested action enqueues task with correct parameters."""
118+
self._enable_code_review()
109119
self._send_webhook_event(
110120
GithubWebhookType.CHECK_RUN,
111121
CHECK_RUN_REREQUESTED_ACTION_EVENT_EXAMPLE,
112-
enable_code_review=True,
113122
)
114123

115124
mock_task.delay.assert_called_once()
@@ -126,42 +135,44 @@ def test_base_case(self, mock_task: MagicMock) -> None:
126135
assert isinstance(call_kwargs["enqueued_at_str"], str)
127136

128137
@patch("sentry.seer.code_review.webhooks.task.process_github_webhook_event")
129-
def test_check_run_skips_when_code_review_option_disabled(self, mock_task: MagicMock) -> None:
130-
"""Test that the handler skips when preflight requirements are not met."""
138+
@with_feature(CODE_REVIEW_FEATURES)
139+
def test_check_run_skips_when_ai_features_disabled(self, mock_task: MagicMock) -> None:
140+
"""Test that the handler returns early when AI features are not enabled (even though the option is enabled)."""
131141
self._send_webhook_event(
132142
GithubWebhookType.CHECK_RUN,
133143
CHECK_RUN_REREQUESTED_ACTION_EVENT_EXAMPLE,
134-
enable_code_review=False,
135144
)
136145
mock_task.delay.assert_not_called()
137146

138147
@patch("sentry.seer.code_review.webhooks.task.process_github_webhook_event")
148+
@with_feature(CODE_REVIEW_FEATURES)
139149
def test_check_run_fails_when_action_missing(self, mock_task: MagicMock) -> None:
140150
"""Test that missing action field is handled gracefully without KeyError."""
151+
self._enable_code_review()
141152
event_without_action = orjson.loads(CHECK_RUN_REREQUESTED_ACTION_EVENT_EXAMPLE)
142153
del event_without_action["action"]
143154

144155
with patch("sentry.seer.code_review.webhooks.check_run.logger") as mock_logger:
145156
self._send_webhook_event(
146157
GithubWebhookType.CHECK_RUN,
147158
orjson.dumps(event_without_action),
148-
enable_code_review=True,
149159
)
150160
mock_task.delay.assert_not_called()
151161
mock_logger.error.assert_called_once()
152162
assert "github.webhook.check_run.missing-action" in str(mock_logger.error.call_args)
153163

154164
@patch("sentry.seer.code_review.webhooks.task.process_github_webhook_event")
165+
@with_feature(CODE_REVIEW_FEATURES)
155166
def test_check_run_fails_when_external_id_missing(self, mock_task: MagicMock) -> None:
156167
"""Test that missing external_id is handled gracefully."""
168+
self._enable_code_review()
157169
event_without_external_id = orjson.loads(CHECK_RUN_REREQUESTED_ACTION_EVENT_EXAMPLE)
158170
del event_without_external_id["check_run"]["external_id"]
159171

160172
with patch("sentry.seer.code_review.webhooks.check_run.logger") as mock_logger:
161173
self._send_webhook_event(
162174
GithubWebhookType.CHECK_RUN,
163175
orjson.dumps(event_without_external_id),
164-
enable_code_review=True,
165176
)
166177
mock_task.delay.assert_not_called()
167178
mock_logger.exception.assert_called_once()
@@ -170,16 +181,17 @@ def test_check_run_fails_when_external_id_missing(self, mock_task: MagicMock) ->
170181
)
171182

172183
@patch("sentry.seer.code_review.webhooks.task.process_github_webhook_event")
184+
@with_feature(CODE_REVIEW_FEATURES)
173185
def test_check_run_fails_when_external_id_not_numeric(self, mock_task: MagicMock) -> None:
174186
"""Test that non-numeric external_id is handled gracefully."""
187+
self._enable_code_review()
175188
event_with_invalid_external_id = orjson.loads(CHECK_RUN_REREQUESTED_ACTION_EVENT_EXAMPLE)
176189
event_with_invalid_external_id["check_run"]["external_id"] = "not-a-number"
177190

178191
with patch("sentry.seer.code_review.webhooks.check_run.logger") as mock_logger:
179192
self._send_webhook_event(
180193
GithubWebhookType.CHECK_RUN,
181194
orjson.dumps(event_with_invalid_external_id),
182-
enable_code_review=True,
183195
)
184196
mock_task.delay.assert_not_called()
185197
mock_logger.exception.assert_called_once()
@@ -188,12 +200,13 @@ def test_check_run_fails_when_external_id_not_numeric(self, mock_task: MagicMock
188200
)
189201

190202
@patch("sentry.seer.code_review.webhooks.task.process_github_webhook_event")
203+
@with_feature(CODE_REVIEW_FEATURES)
191204
def test_check_run_enqueues_task_for_processing(self, mock_task: MagicMock) -> None:
192205
"""Test that webhook successfully enqueues task for async processing."""
206+
self._enable_code_review()
193207
self._send_webhook_event(
194208
GithubWebhookType.CHECK_RUN,
195209
CHECK_RUN_REREQUESTED_ACTION_EVENT_EXAMPLE,
196-
enable_code_review=True,
197210
)
198211

199212
mock_task.delay.assert_called_once()
@@ -212,17 +225,30 @@ def test_check_run_without_integration_returns_204(self) -> None:
212225
)
213226
assert response.status_code == 204
214227

228+
@patch("sentry.seer.code_review.webhooks.task.process_github_webhook_event")
229+
@with_feature({"organizations:gen-ai-features"})
230+
def test_check_run_runs_when_code_review_beta_flag_disabled_but_pr_review_test_generation_enabled(
231+
self, mock_task: MagicMock
232+
) -> None:
233+
"""Test that task is enqueued when code-review-beta flag is off but pr_review_test_generation is enabled."""
234+
self._enable_code_review()
235+
self._send_webhook_event(
236+
GithubWebhookType.CHECK_RUN,
237+
CHECK_RUN_REREQUESTED_ACTION_EVENT_EXAMPLE,
238+
)
239+
mock_task.delay.assert_called_once()
240+
215241
@patch("sentry.seer.code_review.utils.make_seer_request")
242+
@with_feature(CODE_REVIEW_FEATURES)
216243
def test_check_run_skips_when_hide_ai_features_enabled(
217244
self, mock_make_seer_request: MagicMock
218245
) -> None:
219246
"""Test that task is not enqueued when hide_ai_features option is True."""
220-
# Enable hide_ai_features before sending - preflight will fail legal AI consent check
247+
self._enable_code_review()
221248
self.organization.update_option("sentry:hide_ai_features", True)
222249
self._send_webhook_event(
223250
GithubWebhookType.CHECK_RUN,
224251
CHECK_RUN_REREQUESTED_ACTION_EVENT_EXAMPLE,
225-
enable_code_review=True,
226252
)
227253
mock_make_seer_request.assert_not_called()
228254

@@ -786,7 +812,7 @@ def _build_issue_comment_event(
786812
"number": 42,
787813
"pull_request": {"url": "https://api.github.com/repos/owner/repo/pulls/42"},
788814
"user": {
789-
"id": 12345678,
815+
"id": int(DEFAULT_PR_AUTHOR_ID),
790816
"login": "pr-author",
791817
},
792818
},
@@ -802,21 +828,6 @@ def _build_issue_comment_event(
802828
}
803829
return orjson.dumps(event)
804830

805-
@patch("sentry.seer.code_review.webhooks.task.schedule_task")
806-
def test_skips_when_code_review_not_enabled(self, mock_schedule: MagicMock) -> None:
807-
"""Test that issue_comment skips when preflight requirements are not met."""
808-
event = self._build_issue_comment_event(f"Please {SENTRY_REVIEW_COMMAND} this PR")
809-
self._send_issue_comment_event(event, enable_code_review=False)
810-
mock_schedule.assert_not_called()
811-
812-
@patch("sentry.seer.code_review.webhooks.task.schedule_task")
813-
def test_skips_when_no_review_command(self, mock_schedule: MagicMock) -> None:
814-
"""Test that issue_comment skips when the comment doesn't contain the review command."""
815-
event = self._build_issue_comment_event("This is a regular comment without the command")
816-
self._send_issue_comment_event(event, enable_code_review=True)
817-
mock_schedule.assert_not_called()
818-
819-
@patch("sentry.seer.code_review.webhooks.task.schedule_task")
820831
def test_skips_when_code_review_features_are_missing(self) -> None:
821832
"""Test that processing is skipped when code review features are missing."""
822833
with self.code_review_setup(features={}): # Missing on purpose
@@ -828,60 +839,79 @@ def test_skips_when_code_review_features_are_missing(self) -> None:
828839

829840
self.mock_seer.assert_not_called()
830841

842+
def test_skips_when_no_review_command(self) -> None:
843+
"""Test that processing is skipped when comment doesn't contain review command."""
844+
with self.code_review_setup():
845+
event = self._build_issue_comment_event("This is a regular comment without the command")
846+
847+
with self.tasks():
848+
response = self._send_issue_comment_event(event)
849+
assert response.status_code == 204
850+
851+
self.mock_seer.assert_not_called()
852+
831853
def test_runs_when_code_review_beta_flag_disabled_but_pr_review_test_generation_enabled(
832854
self,
833855
) -> None:
834-
"""Test that code review works via legacy option even without the beta feature flag."""
835-
# Only enable gen-ai-features flag, not code-review-beta
836-
with self.options({"github.webhook.issue-comment": False}):
837-
event = self._build_issue_comment_event(f"Please {SENTRY_REVIEW_COMMAND} this PR")
838-
self._send_issue_comment_event(
839-
event,
840-
enable_code_review=True,
841-
features={"organizations:gen-ai-features"},
842-
)
843-
844-
@patch("sentry.seer.code_review.webhooks.task.make_seer_request")
845-
@patch("sentry.integrations.github.client.GitHubApiClient.create_comment_reaction")
846-
def test_adds_reaction_and_forwards_when_valid(
847-
self, mock_create_reaction: MagicMock, mock_seer: MagicMock
848-
) -> None:
849-
with self.options({"github.webhook.issue-comment": False}):
856+
"""Test that processing runs with gen-ai-features flag alone when org option is enabled."""
857+
with self.code_review_setup(features={"organizations:gen-ai-features"}):
850858
event = self._build_issue_comment_event(f"Please {SENTRY_REVIEW_COMMAND} this PR")
851859

852860
with self.tasks():
853-
self._send_issue_comment_event(event, enable_code_review=True)
854-
"""Test that processing runs with gen-ai-features flag alone when org option is enabled."""
855-
with self.options(
856-
{"organizations:code-review-beta": False, "github.webhook.issue-comment": False}
857-
):
858-
self.organization.update_option("sentry:enable_pr_review_test_generation", True)
861+
response = self._send_issue_comment_event(event)
862+
assert response.status_code == 204
863+
864+
self.mock_seer.assert_called_once()
865+
866+
def test_adds_reaction_and_forwards_when_valid(self) -> None:
867+
"""Test successful PR review command processing with reaction and Seer request."""
868+
with self.code_review_setup():
859869
event = self._build_issue_comment_event(f"Please {SENTRY_REVIEW_COMMAND} this PR")
860870

861871
with self.tasks():
862872
response = self._send_issue_comment_event(event)
863873
assert response.status_code == 204
864874

865-
self.mock_seer.assert_called_once()
875+
self.mock_reaction.assert_called_once_with(
876+
"owner/repo", "123456789", GitHubReaction.EYES
877+
)
878+
self.mock_seer.assert_called_once()
879+
880+
call_args = self.mock_seer.call_args
881+
assert call_args[1]["path"] == "/v1/automation/overwatch-request"
882+
payload = call_args[1]["payload"]
883+
assert payload["request_type"] == "pr-review"
884+
assert payload["data"]["repo"]["base_commit_sha"] == "abc123"
866885

867886
@patch("sentry.seer.code_review.webhooks.issue_comment._add_eyes_reaction_to_comment")
868-
@patch("sentry.seer.code_review.webhooks.task.schedule_task")
869-
def test_skips_reaction_when_no_comment_id(
870-
self, mock_schedule: MagicMock, mock_reaction: MagicMock
871-
) -> None:
872-
with self.options({"github.webhook.issue-comment": False}):
887+
def test_skips_reaction_when_no_comment_id(self, mock_reaction: MagicMock) -> None:
888+
"""Test that reaction is skipped when comment has no ID, but processing continues."""
889+
with self.code_review_setup():
873890
event = self._build_issue_comment_event(SENTRY_REVIEW_COMMAND, comment_id=None)
874-
self._send_issue_comment_event(event, enable_code_review=True)
891+
892+
with self.tasks():
893+
response = self._send_issue_comment_event(event)
894+
assert response.status_code == 204
895+
896+
mock_reaction.assert_not_called()
897+
self.mock_seer.assert_called_once()
875898

876899
@patch("sentry.seer.code_review.webhooks.issue_comment._add_eyes_reaction_to_comment")
877-
@patch("sentry.seer.code_review.webhooks.task.schedule_task")
878-
def test_skips_processing_when_option_is_true(
879-
self, mock_schedule: MagicMock, mock_reaction: MagicMock
880-
) -> None:
881-
"""Test that when github.webhook.issue-comment option is True (default), no processing occurs."""
882-
with self.options({"github.webhook.issue-comment": True}):
900+
def test_skips_processing_when_option_is_true(self, mock_reaction: MagicMock) -> None:
901+
"""Test that when github.webhook.issue-comment option is True (kill switch), no processing occurs."""
902+
self._enable_code_review()
903+
with (
904+
self.feature(CODE_REVIEW_FEATURES),
905+
self.options({"github.webhook.issue-comment": True}),
906+
):
883907
event = self._build_issue_comment_event(f"Please {SENTRY_REVIEW_COMMAND} this PR")
884-
self._send_issue_comment_event(event, enable_code_review=True)
908+
909+
with self.tasks():
910+
response = self._send_issue_comment_event(event)
911+
assert response.status_code == 204
912+
913+
mock_reaction.assert_not_called()
914+
self.mock_seer.assert_not_called()
885915

886916
def test_validates_seer_request_contains_trigger_metadata(self) -> None:
887917
"""Test that Seer request includes trigger metadata from the comment."""

0 commit comments

Comments
 (0)