diff --git a/src/sentry/api/endpoints/organization_artifactbundle_assemble.py b/src/sentry/api/endpoints/organization_artifactbundle_assemble.py index 33dabcee3ec8ac..669b22f7828d74 100644 --- a/src/sentry/api/endpoints/organization_artifactbundle_assemble.py +++ b/src/sentry/api/endpoints/organization_artifactbundle_assemble.py @@ -1,5 +1,6 @@ import jsonschema import orjson +from django.db.models import Q from rest_framework.request import Request from rest_framework.response import Response @@ -56,16 +57,26 @@ def post(self, request: Request, organization) -> Response: except Exception: return Response({"error": "Invalid json body"}, status=400) - projects = set(data.get("projects", [])) - if len(projects) == 0: + input_projects = data.get("projects", []) + if len(input_projects) == 0: return Response({"error": "You need to specify at least one project"}, status=400) - project_ids = list( - Project.objects.filter( - organization=organization, status=ObjectStatus.ACTIVE, slug__in=projects - ).values_list("id", flat=True) - ) - if len(project_ids) != len(projects): + input_project_slug = set() + input_project_id = set() + for project in input_projects: + # IDs are always numeric, slugs cannot be numeric + if str(project).isdecimal(): + input_project_id.add(project) + else: + input_project_slug.add(project) + + project_ids = Project.objects.filter( + (Q(id__in=input_project_id) | Q(slug__in=input_project_slug)), + organization=organization, + status=ObjectStatus.ACTIVE, + ).values_list("id", flat=True) + + if len(project_ids) != len(input_projects): return Response({"error": "One or more projects are invalid"}, status=400) if not self.has_release_permission(request, organization, project_ids=set(project_ids)): @@ -131,6 +142,6 @@ def post(self, request: Request, organization) -> Response: ) if is_org_auth_token_auth(request.auth): - update_org_auth_token_last_used(request.auth, project_ids) + update_org_auth_token_last_used(request.auth, list(project_ids)) return Response({"state": ChunkFileState.CREATED, "missingChunks": []}, status=200) diff --git a/tests/sentry/api/endpoints/test_organization_artifactbundle_assemble.py b/tests/sentry/api/endpoints/test_organization_artifactbundle_assemble.py index ba0761e18e7cde..4fe8b0285820c0 100644 --- a/tests/sentry/api/endpoints/test_organization_artifactbundle_assemble.py +++ b/tests/sentry/api/endpoints/test_organization_artifactbundle_assemble.py @@ -141,6 +141,86 @@ def test_assemble_with_invalid_projects(self): assert response.status_code == 400, response.content assert response.data["error"] == "One or more projects are invalid" + def test_assemble_with_valid_project_slugs(self): + # Test with all valid project slugs + valid_project = self.create_project() + another_valid_project = self.create_project() + + bundle_file = self.create_artifact_bundle_zip( + org=self.organization.slug, release=self.release.version + ) + total_checksum = sha1(bundle_file).hexdigest() + + blob = FileBlob.from_file(ContentFile(bundle_file)) + FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob) + + response = self.client.post( + self.url, + data={ + "checksum": total_checksum, + "chunks": [blob.checksum], + "projects": [valid_project.slug, another_valid_project.slug], + }, + HTTP_AUTHORIZATION=f"Bearer {self.token.token}", + ) + + self.assertEqual(response.status_code, 200) + + def test_assemble_with_valid_project_ids(self): + # Test with all valid project IDs + valid_project = self.create_project() + another_valid_project = self.create_project() + + bundle_file = self.create_artifact_bundle_zip( + org=self.organization.slug, release=self.release.version + ) + total_checksum = sha1(bundle_file).hexdigest() + + blob = FileBlob.from_file(ContentFile(bundle_file)) + FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob) + + response = self.client.post( + self.url, + data={ + "checksum": total_checksum, + "chunks": [blob.checksum], + "projects": [str(valid_project.id), str(another_valid_project.id)], + }, + HTTP_AUTHORIZATION=f"Bearer {self.token.token}", + ) + + self.assertEqual(response.status_code, 200) + + def test_assemble_with_mix_of_slugs_and_ids(self): + # Test with a mix of valid project slugs and IDs + valid_project = self.create_project() + another_valid_project = self.create_project() + third_valid_project = self.create_project() + + bundle_file = self.create_artifact_bundle_zip( + org=self.organization.slug, release=self.release.version + ) + total_checksum = sha1(bundle_file).hexdigest() + + blob = FileBlob.from_file(ContentFile(bundle_file)) + FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob) + + response = self.client.post( + self.url, + data={ + "checksum": total_checksum, + "chunks": [blob.checksum], + "projects": [ + valid_project.slug, + str(another_valid_project.id), + str(third_valid_project.id), + ], + }, + HTTP_AUTHORIZATION=f"Bearer {self.token.token}", + ) + + self.assertEqual(response.status_code, 200) + @patch("sentry.tasks.assemble.assemble_artifacts") def test_assemble_without_version_and_dist(self, mock_assemble_artifacts): bundle_file = self.create_artifact_bundle_zip(