diff --git a/webhook_handlers/tests/test_github.py b/webhook_handlers/tests/test_github.py index 9e11cc7e5b..b7c7413217 100644 --- a/webhook_handlers/tests/test_github.py +++ b/webhook_handlers/tests/test_github.py @@ -47,6 +47,7 @@ def __getitem__(self, key): WEBHOOK_SECRET = b"testixik8qdauiab1yiffydimvi72ekq" DEFAULT_APP_ID = 1234 +AI_FEATURES_GH_APP_ID = 9999 class GithubWebhookHandlerTests(APITestCase): @@ -58,16 +59,23 @@ def inject_mocker(request, mocker): def mock_webhook_secret(self, mocker): mock_config_helper(mocker, configs={"github.webhook_secret": WEBHOOK_SECRET}) + @pytest.fixture(autouse=True) + def mock_ai_features_app_id(self, mocker): + mock_config_helper( + mocker, configs={"github.ai_features_app_id": AI_FEATURES_GH_APP_ID} + ) + @pytest.fixture(autouse=True) def mock_default_app_id(self, mocker): mock_config_helper(mocker, configs={"github.integration.id": DEFAULT_APP_ID}) - def _post_event_data(self, event, data={}): + def _post_event_data(self, event, data={}, app_id=DEFAULT_APP_ID): return self.client.post( reverse("github-webhook"), **{ GitHubHTTPHeaders.EVENT: event, GitHubHTTPHeaders.DELIVERY_TOKEN: uuid.UUID(int=5), + GitHubHTTPHeaders.HOOK_INSTALLATION_TARGET_ID: app_id, GitHubHTTPHeaders.SIGNATURE_256: "sha256=" + hmac.new( WEBHOOK_SECRET, @@ -1420,3 +1428,98 @@ def test_repo_creation_doesnt_crash_for_forked_repo(self): ) assert owner.repository_set.filter(name="testrepo").exists() + + def test_check_codecov_ai_auto_enabled_reviews_enabled(self): + # Create an organization with AI PR review enabled + org_with_ai_enabled = OwnerFactory( + service=Service.GITHUB.value, yaml={"ai_pr_review": {"auto_review": True}} + ) + + response = self._post_event_data( + event=GitHubWebhookEvents.PULL_REQUEST, + data={ + "action": "pull_request", + "repository": { + "id": 506003, + "name": "testrepo", + "private": False, + "default_branch": "main", + "owner": {"id": org_with_ai_enabled.service_id}, + "fork": True, + "parent": { + "name": "mainrepo", + "language": "python", + "id": 7940284, + "private": False, + "default_branch": "main", + "owner": {"id": 8495712939, "login": "alogin"}, + }, + }, + }, + app_id=AI_FEATURES_GH_APP_ID, + ) + assert response.data == {"auto_review_enabled": True} + + def test_check_codecov_ai_auto_enabled_reviews_disabled(self): + # Test with AI PR review disabled + org_with_ai_disabled = OwnerFactory( + service=Service.GITHUB.value, yaml={"ai_pr_review": {"auto_review": False}} + ) + + response = self._post_event_data( + event=GitHubWebhookEvents.PULL_REQUEST, + data={ + "action": "pull_request", + "repository": { + "id": 506004, + "name": "testrepo2", + "private": False, + "default_branch": "main", + "owner": {"id": org_with_ai_disabled.service_id}, + }, + }, + app_id=AI_FEATURES_GH_APP_ID, + ) + assert response.data == {"auto_review_enabled": False} + + def test_check_codecov_ai_auto_enabled_reviews_no_config(self): + # Test with no yaml config + org_with_no_config = OwnerFactory(service=Service.GITHUB.value, yaml={}) + + response = self._post_event_data( + event=GitHubWebhookEvents.PULL_REQUEST, + data={ + "action": "pull_request", + "repository": { + "id": 506005, + "name": "testrepo3", + "private": False, + "default_branch": "main", + "owner": {"id": org_with_no_config.service_id}, + }, + }, + app_id=AI_FEATURES_GH_APP_ID, + ) + assert response.data == {"auto_review_enabled": False} + + def test_check_codecov_ai_auto_enabled_reviews_partial_config(self): + # Test with partial yaml config + org_with_partial_config = OwnerFactory( + service=Service.GITHUB.value, yaml={"ai_pr_review": {}} + ) + + response = self._post_event_data( + event=GitHubWebhookEvents.PULL_REQUEST, + data={ + "action": "pull_request", + "repository": { + "id": 506006, + "name": "testrepo4", + "private": False, + "default_branch": "main", + "owner": {"id": org_with_partial_config.service_id}, + }, + }, + app_id=AI_FEATURES_GH_APP_ID, + ) + assert response.data == {"auto_review_enabled": False} diff --git a/webhook_handlers/views/github.py b/webhook_handlers/views/github.py index 6e9a4e1a89..1d278b1fdf 100644 --- a/webhook_handlers/views/github.py +++ b/webhook_handlers/views/github.py @@ -38,6 +38,7 @@ # This should probably go somewhere where it can be easily shared regexp_ci_skip = re.compile(r"\[(ci|skip| |-){3,}\]").search +AI_FEATURES_GH_APP_ID = get_config("github", "ai_features_app_id") class GithubWebhookHandler(APIView): @@ -365,7 +366,10 @@ def status(self, request, *args, **kwargs): return Response() def pull_request(self, request, *args, **kwargs): - if request.headers.get(GitHubHTTPHeaders.HOOK_INSTALLATION_TARGET_ID): + if ( + request.META.get(GitHubHTTPHeaders.HOOK_INSTALLATION_TARGET_ID, "") + == AI_FEATURES_GH_APP_ID + ): return self.check_codecov_ai_auto_enabled_reviews(request) repo = self._get_repo(request) @@ -404,8 +408,9 @@ def pull_request(self, request, *args, **kwargs): def check_codecov_ai_auto_enabled_reviews(self, request): org = Owner.objects.get( service=self.service_name, - service_id=request.data["organization"]["id"], + service_id=request.data["repository"]["owner"]["id"], ) + auto_review_enabled = org.yaml.get("ai_pr_review", {}).get("auto_review", False) return Response( data={ @@ -768,7 +773,6 @@ def post(self, request, *args, **kwargs): delivery=self.request.META.get(GitHubHTTPHeaders.DELIVERY_TOKEN), ), ) - self.validate_signature(request) if handler := getattr(self, self.event, None):