Skip to content

Commit a2ff6e9

Browse files
authored
fix(autofix): Validate Seer-supported SCM providers for Seer project preferences (#112518)
Validate that only Seer-supported SCM providers (GitHub, GitHub Enterprise) can be added to Seer project preferences. - RepositorySerializer.validate_provider — rejects unsupported providers at the API layer (covers ProjectSeerPreferencesEndpoint.post and OrganizationAutofixAutomationSettingsEndpoint.post) - get_autofix_repos_from_project_code_mappings — filters out unsupported providers from code-mapping-derived repos (covers _resolve_project_preference and configure_seer_for_existing_org) This covers more of Seer's deletion task `cleanup_stale_seer_preferences_task`, as part of the migration of Seer settings from Seer to Sentry ([spec](https://www.notion.so/sentry/Tech-Spec-Migrate-Seer-Settings-to-Sentry-Database-3208b10e4b5d80f58ea0d7b77a301e2a)).
1 parent 45198ea commit a2ff6e9

File tree

5 files changed

+86
-2
lines changed

5 files changed

+86
-2
lines changed

src/sentry/seer/autofix/utils.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from sentry.models.repository import Repository
3131
from sentry.net.http import connection_from_url
3232
from sentry.seer.autofix.constants import AutofixAutomationTuningSettings, AutofixStatus
33+
from sentry.seer.constants import SEER_SUPPORTED_SCM_PROVIDERS
3334
from sentry.seer.models import (
3435
BranchOverride,
3536
SeerApiError,
@@ -777,8 +778,13 @@ def get_autofix_repos_from_project_code_mappings(
777778
repo: Repository = code_mapping.repository
778779
repo_name_sections = repo.name.split("/")
779780

780-
# We expect a repository name to be in the format of "owner/name" for now.
781-
if len(repo_name_sections) > 1 and repo.provider:
781+
if (
782+
# We expect a repository name to be in the format of "owner/name" for now.
783+
len(repo_name_sections) > 1
784+
# Filter out code mappings with unsupported providers.
785+
and repo.provider
786+
and repo.provider in SEER_SUPPORTED_SCM_PROVIDERS
787+
):
782788
repo_dict = {
783789
"repository_id": repo.id,
784790
"organization_id": repo.organization_id,

src/sentry/seer/endpoints/organization_autofix_automation_settings.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
default_seer_project_preference,
3232
resolve_repository_ids,
3333
)
34+
from sentry.seer.constants import SEER_SUPPORTED_SCM_PROVIDERS
3435
from sentry.seer.models import SeerProjectPreference, SeerRepoDefinition
3536
from sentry.seer.utils import filter_repo_by_provider
3637

@@ -67,6 +68,14 @@ class RepositorySerializer(CamelSnakeSerializer):
6768
base_commit_sha = serializers.CharField(required=False, allow_null=True)
6869
provider_raw = serializers.CharField(required=False, allow_null=True)
6970

71+
def validate_provider(self, value):
72+
if value not in SEER_SUPPORTED_SCM_PROVIDERS:
73+
supported = ", ".join(sorted(SEER_SUPPORTED_SCM_PROVIDERS))
74+
raise serializers.ValidationError(
75+
f'"{value}" is not a supported Seer provider. Supported providers: {supported}'
76+
)
77+
return value
78+
7079
def validate_branch_overrides(self, value):
7180
if not value:
7281
return value

tests/sentry/autofix/test_utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,30 @@ def test_get_repos_from_project_code_mappings_with_data(self) -> None:
4949
]
5050
assert repos == expected_repos
5151

52+
def test_filters_out_unsupported_providers(self) -> None:
53+
project = self.create_project()
54+
github_repo = self.create_repo(
55+
name="getsentry/sentry",
56+
provider="integrations:github",
57+
external_id="123",
58+
integration_id=234,
59+
)
60+
self.create_code_mapping(project=project, repo=github_repo)
61+
62+
gitlab_repo = self.create_repo(
63+
name="getsentry/sentry-gitlab",
64+
provider="integrations:gitlab",
65+
external_id="456",
66+
integration_id=345,
67+
)
68+
self.create_code_mapping(
69+
project=project, repo=gitlab_repo, stack_root="gitlab/", source_root="src/gitlab/"
70+
)
71+
72+
repos = get_autofix_repos_from_project_code_mappings(project)
73+
assert len(repos) == 1
74+
assert repos[0]["provider"] == "integrations:github"
75+
5276

5377
class TestGetAutofixStateFromPrId(TestCase):
5478
@patch("sentry.seer.autofix.utils.make_signed_seer_api_request")

tests/sentry/seer/endpoints/test_organization_autofix_automation_settings.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,3 +1052,28 @@ def test_post_append_resolves_repo_id_for_existing_repos(
10521052
assert len(seer_repos) == 2
10531053
assert seer_repos[0].repository_id == existing_repo.id
10541054
assert seer_repos[1].repository_id == new_repo.id
1055+
1056+
@patch(
1057+
"sentry.seer.endpoints.organization_autofix_automation_settings.bulk_set_project_preferences"
1058+
)
1059+
def test_post_rejects_unsupported_repo_provider(self, mock_bulk_set_preferences):
1060+
project = self.create_project(organization=self.organization)
1061+
1062+
repo_data = {
1063+
"provider": "gitlab",
1064+
"owner": "test-org",
1065+
"name": "test-repo",
1066+
"externalId": "12345",
1067+
}
1068+
1069+
response = self.client.post(
1070+
self.url,
1071+
{
1072+
"projectIds": [project.id],
1073+
"projectRepoMappings": {
1074+
str(project.id): [repo_data],
1075+
},
1076+
},
1077+
)
1078+
assert response.status_code == 400
1079+
mock_bulk_set_preferences.assert_not_called()

tests/sentry/seer/endpoints/test_project_seer_preferences.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,26 @@ def test_post_rejects_mismatched_repo_name_or_owner(self, mock_request: MagicMoc
684684
assert response.data["detail"] == "Invalid repository"
685685
mock_request.assert_not_called()
686686

687+
@patch("sentry.seer.endpoints.project_seer_preferences.make_set_project_preference_request")
688+
def test_post_rejects_unsupported_repo_provider(self, mock_request: MagicMock) -> None:
689+
request_data = {
690+
"repositories": [
691+
{
692+
"organization_id": self.org.id,
693+
"integration_id": "111",
694+
"provider": "gitlab",
695+
"owner": "getsentry",
696+
"name": "sentry",
697+
"external_id": "123456",
698+
}
699+
],
700+
}
701+
702+
response = self.client.post(self.url, data=request_data)
703+
704+
assert response.status_code == 400
705+
mock_request.assert_not_called()
706+
687707
@patch("sentry.seer.endpoints.project_seer_preferences.make_set_project_preference_request")
688708
def test_post_creates_seer_project_repository(self, mock_request: MagicMock) -> None:
689709
"""Test that POST writes to SeerProjectRepository when feature flag is enabled."""

0 commit comments

Comments
 (0)