Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/sentry/seer/code_review/webhooks/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,30 @@ def schedule_task(
)
return

# Validate payload before scheduling to catch schema mismatches early
try:
request_type = transformed_event.get("request_type")
if request_type == "pr-closed":
SeerCodeReviewTaskRequestForPrClosed.parse_obj(transformed_event)
else:
SeerCodeReviewTaskRequestForPrReview.parse_obj(transformed_event)
except Exception as e:
logger.warning(
"%s.validation_failed_before_scheduling",
PREFIX,
extra={
"error": str(e),
"github_event": github_event.value,
"github_event_action": github_event_action,
"organization_id": organization.id,
"repo_id": repo.id,
},
)
record_webhook_filtered(
github_event, github_event_action, WebhookFilteredReason.INVALID_PAYLOAD
)
return

# Convert enum to string for Celery serialization
process_github_webhook_event.delay(
github_event=github_event.value,
Expand Down
58 changes: 58 additions & 0 deletions tests/sentry/seer/code_review/webhooks/test_pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,61 @@ def test_pull_request_closed_draft_still_sends_to_seer(self) -> None:
call_kwargs = self.mock_seer.call_args[1]
payload = call_kwargs["payload"]
assert payload["request_type"] == SeerCodeReviewRequestType.PR_CLOSED.value

def test_validation_happens_before_task_scheduling_pr_closed(self) -> None:
"""Test that invalid pr-closed payloads are caught before scheduling the Celery task."""
with (
self.code_review_setup(),
self.tasks(),
patch(
"sentry.seer.code_review.webhooks.task.transform_webhook_to_codegen_request"
) as mock_transform,
):
# Return an invalid payload missing required fields for pr-closed
mock_transform.return_value = {
"request_type": "pr-closed",
"data": {
# Missing required fields like repo, pr_id, etc.
"invalid": "payload"
},
}

event = orjson.loads(PULL_REQUEST_OPENED_EVENT_EXAMPLE)
event["action"] = "closed"

self._send_webhook_event(
GithubWebhookType.PULL_REQUEST,
orjson.dumps(event),
)

# Task should NOT be scheduled due to validation failure
self.mock_seer.assert_not_called()

def test_validation_happens_before_task_scheduling_pr_review(self) -> None:
"""Test that invalid pr-review payloads are caught before scheduling the Celery task."""
with (
self.code_review_setup(),
self.tasks(),
patch(
"sentry.seer.code_review.webhooks.task.transform_webhook_to_codegen_request"
) as mock_transform,
):
# Return an invalid payload missing required fields for pr-review
mock_transform.return_value = {
"request_type": "pr-review",
"data": {
# Missing required fields
"invalid": "payload"
},
}

event = orjson.loads(PULL_REQUEST_OPENED_EVENT_EXAMPLE)
event["action"] = "opened"

self._send_webhook_event(
GithubWebhookType.PULL_REQUEST,
orjson.dumps(event),
)

# Task should NOT be scheduled due to validation failure
self.mock_seer.assert_not_called()
Loading