From f0917c1d73a057d6fcf844e570ebf4aa69f6a6b6 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Fri, 3 Jan 2025 16:21:46 -0500 Subject: [PATCH] ref: remove monitor checkin attachments backend --- pyproject.toml | 1 - src/sentry/api/urls.py | 27 +-- src/sentry/api/utils.py | 27 +-- .../base_monitor_checkin_attachment.py | 45 ----- .../monitor_ingest_checkin_attachment.py | 186 ------------------ ...organization_monitor_checkin_attachment.py | 29 --- .../project_monitor_checkin_attachment.py | 28 --- src/sentry/monitors/serializers.py | 2 - tests/sentry/api/test_path_params.py | 4 - .../test_monitor_ingest_checkin_attachment.py | 171 ---------------- ...organization_monitor_checkin_attachment.py | 8 - ...test_project_monitor_checkin_attachment.py | 11 -- 12 files changed, 2 insertions(+), 537 deletions(-) delete mode 100644 src/sentry/monitors/endpoints/base_monitor_checkin_attachment.py delete mode 100644 src/sentry/monitors/endpoints/monitor_ingest_checkin_attachment.py delete mode 100644 src/sentry/monitors/endpoints/organization_monitor_checkin_attachment.py delete mode 100644 src/sentry/monitors/endpoints/project_monitor_checkin_attachment.py delete mode 100644 tests/sentry/monitors/endpoints/test_monitor_ingest_checkin_attachment.py delete mode 100644 tests/sentry/monitors/endpoints/test_organization_monitor_checkin_attachment.py delete mode 100644 tests/sentry/monitors/endpoints/test_project_monitor_checkin_attachment.py diff --git a/pyproject.toml b/pyproject.toml index 0a21b614af9296..e3cc0f8ad98035 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -248,7 +248,6 @@ module = [ "sentry.middleware.superuser", "sentry.monitors.consumers.monitor_consumer", "sentry.monitors.endpoints.base", - "sentry.monitors.endpoints.monitor_ingest_checkin_attachment", "sentry.monitors.endpoints.organization_monitor_index", "sentry.net.http", "sentry.net.socket", diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index ab1953a40c79e0..3708c9ce38c911 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -64,7 +64,6 @@ from sentry.api.endpoints.source_map_debug_blue_thunder_edition import ( SourceMapDebugBlueThunderEditionEndpoint, ) -from sentry.api.utils import method_dispatch from sentry.data_export.endpoints.data_export import DataExportEndpoint from sentry.data_export.endpoints.data_export_details import DataExportDetailsEndpoint from sentry.data_secrecy.api.waive_data_secrecy import WaiveDataSecrecyEndpoint @@ -198,12 +197,6 @@ SourceMapDebugEndpoint, TeamGroupsOldEndpoint, ) -from sentry.monitors.endpoints.monitor_ingest_checkin_attachment import ( - MonitorIngestCheckinAttachmentEndpoint, -) -from sentry.monitors.endpoints.organization_monitor_checkin_attachment import ( - OrganizationMonitorCheckInAttachmentEndpoint, -) from sentry.monitors.endpoints.organization_monitor_checkin_index import ( OrganizationMonitorCheckInIndexEndpoint, ) @@ -224,9 +217,6 @@ OrganizationMonitorScheduleSampleDataEndpoint, ) from sentry.monitors.endpoints.organization_monitor_stats import OrganizationMonitorStatsEndpoint -from sentry.monitors.endpoints.project_monitor_checkin_attachment import ( - ProjectMonitorCheckInAttachmentEndpoint, -) from sentry.monitors.endpoints.project_monitor_checkin_index import ( ProjectMonitorCheckInIndexEndpoint, ) @@ -1124,7 +1114,7 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]: ), ] -ORGANIZATION_URLS = [ +ORGANIZATION_URLS: list[URLPattern | URLResolver] = [ re_path( r"^$", OrganizationIndexEndpoint.as_view(), @@ -1716,16 +1706,6 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]: OrganizationMonitorCheckInIndexEndpoint.as_view(), name="sentry-api-0-organization-monitor-check-in-index", ), - re_path( - r"^(?P[^\/]+)/monitors/(?P[^\/]+)/checkins/(?P[^\/]+)/attachment/$", - method_dispatch( - GET=OrganizationMonitorCheckInAttachmentEndpoint.as_view(), - OPTIONS=OrganizationMonitorCheckInAttachmentEndpoint.as_view(), - POST=MonitorIngestCheckinAttachmentEndpoint.as_view(), # Legacy ingest endpoint - csrf_exempt=True, - ), - name="sentry-api-0-organization-monitor-check-in-attachment", - ), re_path( r"^(?P[^\/]+)/group-search-views/$", OrganizationGroupSearchViewsEndpoint.as_view(), @@ -2741,11 +2721,6 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]: ProjectStatisticalDetectors.as_view(), name="sentry-api-0-project-statistical-detector", ), - re_path( - r"^(?P[^\/]+)/(?P[^\/]+)/monitors/(?P[^\/]+)/checkins/(?P[^\/]+)/attachment/$", - ProjectMonitorCheckInAttachmentEndpoint.as_view(), - name="sentry-api-0-project-monitor-check-in-attachment", - ), re_path( r"^(?P[^\/]+)/(?P[^\/]+)/monitors/(?P[^\/]+)/$", ProjectMonitorDetailsEndpoint.as_view(), diff --git a/src/sentry/api/utils.py b/src/sentry/api/utils.py index ebfbf0dfac2cd4..809a4161a6647b 100644 --- a/src/sentry/api/utils.py +++ b/src/sentry/api/utils.py @@ -11,9 +11,8 @@ import sentry_sdk from django.conf import settings -from django.http import HttpRequest, HttpResponseNotAllowed +from django.http import HttpRequest from django.utils import timezone -from django.views.decorators.csrf import csrf_exempt from rest_framework.exceptions import APIException, ParseError from rest_framework.request import Request from sentry_sdk import Scope @@ -309,30 +308,6 @@ def generate_region_url(region_name: str | None = None) -> str: return region_url_template.replace("{region}", region_name) -def method_dispatch(**dispatch_mapping): - """ - Dispatches an incoming request to a different handler based on the HTTP method - - >>> re_path('^foo$', method_dispatch(POST = post_handler, GET = get_handler))) - """ - - def invalid_method(request, *args, **kwargs): - return HttpResponseNotAllowed(dispatch_mapping.keys()) - - def dispatcher(request, *args, **kwargs): - handler = dispatch_mapping.get(request.method, invalid_method) - return handler(request, *args, **kwargs) - - # This allows us to surface the mapping when iterating through the URL patterns - # Check `test_id_or_slug_path_params.py` for usage - dispatcher.dispatch_mapping = dispatch_mapping # type: ignore[attr-defined] - - if dispatch_mapping.get("csrf_exempt"): - return csrf_exempt(dispatcher) - - return dispatcher - - def print_and_capture_handler_exception( exception: Exception, handler_context: Mapping[str, Any] | None = None, diff --git a/src/sentry/monitors/endpoints/base_monitor_checkin_attachment.py b/src/sentry/monitors/endpoints/base_monitor_checkin_attachment.py deleted file mode 100644 index 5019b15018d19c..00000000000000 --- a/src/sentry/monitors/endpoints/base_monitor_checkin_attachment.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import annotations - -from django.http.response import FileResponse -from rest_framework.request import Request -from rest_framework.response import Response - -from sentry.api.base import BaseEndpointMixin -from sentry.api.endpoints.event_attachment_details import EventAttachmentDetailsPermission -from sentry.models.files.file import File - -from .base import ProjectMonitorPermission - - -class MonitorCheckInAttachmentPermission(EventAttachmentDetailsPermission): - scope_map = ProjectMonitorPermission.scope_map - - def has_object_permission(self, request: Request, view, project): - result = super().has_object_permission(request, view, project) - - # Allow attachment uploads via DSN - if request.method == "POST": - return True - - return result - - -class BaseMonitorCheckInAttachmentEndpoint(BaseEndpointMixin): - def download(self, file_id): - file = File.objects.get(id=file_id) - fp = file.getfile() - response = FileResponse( - fp, - content_type=file.headers.get("Content-Type", "application/octet-stream"), - ) - response["Content-Length"] = file.size - response["Content-Disposition"] = f"attachment; filename={file.name}" - return response - - def get_monitor_checkin_attachment( - self, request: Request, project, monitor, checkin - ) -> Response: - if checkin.attachment_id: - return self.download(checkin.attachment_id) - else: - return Response({"detail": "Check-in has no attachment"}, status=404) diff --git a/src/sentry/monitors/endpoints/monitor_ingest_checkin_attachment.py b/src/sentry/monitors/endpoints/monitor_ingest_checkin_attachment.py deleted file mode 100644 index 1159158ae3e2c8..00000000000000 --- a/src/sentry/monitors/endpoints/monitor_ingest_checkin_attachment.py +++ /dev/null @@ -1,186 +0,0 @@ -from __future__ import annotations - -from uuid import UUID - -from django.core.files.uploadedfile import UploadedFile -from rest_framework.request import Request -from rest_framework.response import Response -from sentry_sdk import Scope - -from sentry.api.api_owners import ApiOwner -from sentry.api.api_publish_status import ApiPublishStatus -from sentry.api.authentication import ( - ApiKeyAuthentication, - DSNAuthentication, - OrgAuthTokenAuthentication, - UserAuthTokenAuthentication, -) -from sentry.api.base import Endpoint, region_silo_endpoint -from sentry.api.exceptions import ParameterValidationError, ResourceDoesNotExist -from sentry.api.serializers import serialize -from sentry.constants import ObjectStatus -from sentry.models.files.file import File -from sentry.models.organization import Organization -from sentry.models.project import Project -from sentry.models.projectkey import ProjectKey -from sentry.monitors.models import Monitor, MonitorCheckIn -from sentry.utils.sdk import bind_organization_context - -from .base import ProjectMonitorPermission, get_monitor_by_org_id_or_slug, try_checkin_lookup - -MAX_ATTACHMENT_SIZE = 1024 * 100 # 100kb - - -@region_silo_endpoint -class MonitorIngestCheckinAttachmentEndpoint(Endpoint): - publish_status = { - "GET": ApiPublishStatus.PRIVATE, - } - owner = ApiOwner.CRONS - - authentication_classes = ( - DSNAuthentication, - UserAuthTokenAuthentication, - OrgAuthTokenAuthentication, - ApiKeyAuthentication, - ) - permission_classes = (ProjectMonitorPermission,) - - """ - Loosens the base endpoint such that a monitor with the provided monitor_id_or_slug - does not need to exist. This is used for initial checkin creation with - monitor upsert. - - [!!]: This will ONLY work when using DSN auth. - """ - - def convert_args( - self, - request: Request, - monitor_id_or_slug: int | str, - checkin_id: str, - organization_id_or_slug: int | str | None = None, - *args, - **kwargs, - ): - monitor = None - - using_dsn_auth = isinstance(request.auth, ProjectKey) - if checkin_id != "latest": - # We require a checkin for this endpoint. If one doesn't exist then error. If the - # checkin_id is `latest` we'll need to resolve the monitor before we can get it. - try: - UUID(checkin_id) - except ValueError: - raise ParameterValidationError("Invalid check-in UUID") - - try: - checkin = MonitorCheckIn.objects.select_related("monitor").get(guid=checkin_id) - monitor = checkin.monitor - project = Project.objects.select_related("organization").get(id=monitor.project_id) - except (MonitorCheckIn.DoesNotExist, Project.DoesNotExist): - raise ResourceDoesNotExist - else: - - # When using DSN auth we're able to infer the organization slug (organization_id_or_slug is slug in this case) - if not organization_id_or_slug and using_dsn_auth: - organization_id_or_slug = request.auth.project.organization.slug - - # The only monitor endpoints that do not have the org id or slug in their - # parameters are the GUID-style checkin endpoints - if organization_id_or_slug: - try: - # Try lookup by id or slug first. This requires organization context. - if str(organization_id_or_slug).isdecimal(): - organization = Organization.objects.get_from_cache( - id=organization_id_or_slug - ) - else: - organization = Organization.objects.get_from_cache( - slug=organization_id_or_slug - ) - - monitor = get_monitor_by_org_id_or_slug(organization, monitor_id_or_slug) - except (Organization.DoesNotExist, Monitor.DoesNotExist): - pass - - # Try lookup by GUID - if not monitor: - # Validate GUIDs - try: - UUID(monitor_id_or_slug) - # When looking up by guid we don't include the org conditional - # (since GUID lookup allows orgless routes), we will validate - # permissions later in this function - try: - monitor = Monitor.objects.get(guid=monitor_id_or_slug) - except Monitor.DoesNotExist: - monitor = None - except ValueError: - # This error is a bit confusing, because this may also mean - # that we've failed to look up their monitor by slug. - raise ParameterValidationError("Invalid monitor UUID") - - if not monitor: - raise ResourceDoesNotExist - - project = Project.objects.get_from_cache(id=monitor.project_id) - checkin = try_checkin_lookup(monitor, checkin_id) - - if project.status != ObjectStatus.ACTIVE: - raise ResourceDoesNotExist - - # Validate that the authenticated project matches the monitor. This is - # used for DSN style authentication - if using_dsn_auth and project.id != request.auth.project_id: - raise ResourceDoesNotExist - - # When looking up via GUID we do not check the organization slug, - # validate that the slug matches the org of the monitors project - - # We only raise if the organization_id_or_slug was set and it doesn't match. - if ( - organization_id_or_slug - and project.organization.slug != organization_id_or_slug - and project.organization.id != organization_id_or_slug - ): - raise ResourceDoesNotExist - - # Check project permission. Required for Token style authentication - self.check_object_permissions(request, project) - - Scope.get_isolation_scope().set_tag("project", project.id) - - bind_organization_context(project.organization) - - request._request.organization = project.organization - kwargs["checkin"] = checkin - - return args, kwargs - - def post(self, request: Request, checkin) -> Response: - """ - Uploads a check-in attachment file. - - Unlike other API requests, files must be uploaded using the traditional multipart/form-data content type. - """ - if "file" not in request.data: - return Response({"detail": "Missing uploaded file"}, status=400) - - if checkin.attachment_id: - return Response({"detail": "Check-in already has an attachment"}, status=400) - - fileobj = request.data["file"] - if not isinstance(fileobj, UploadedFile): - return Response({"detail": "Please upload a valid file object"}, status=400) - - if fileobj.size > MAX_ATTACHMENT_SIZE: - return Response({"detail": "Please keep uploads below 100kb"}, status=400) - - headers = {"Content-Type": fileobj.content_type} - - file = File.objects.create(name=fileobj.name, type="checkin.attachment", headers=headers) - file.putfile(fileobj) - - checkin.update(attachment_id=file.id) - return self.respond(serialize(checkin, request.user)) diff --git a/src/sentry/monitors/endpoints/organization_monitor_checkin_attachment.py b/src/sentry/monitors/endpoints/organization_monitor_checkin_attachment.py deleted file mode 100644 index a11cbc329ed91e..00000000000000 --- a/src/sentry/monitors/endpoints/organization_monitor_checkin_attachment.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -from rest_framework.request import Request -from rest_framework.response import Response - -from sentry.api.api_owners import ApiOwner -from sentry.api.api_publish_status import ApiPublishStatus -from sentry.api.base import region_silo_endpoint - -from .base import MonitorEndpoint -from .base_monitor_checkin_attachment import ( - BaseMonitorCheckInAttachmentEndpoint, - MonitorCheckInAttachmentPermission, -) - - -@region_silo_endpoint -class OrganizationMonitorCheckInAttachmentEndpoint( - MonitorEndpoint, BaseMonitorCheckInAttachmentEndpoint -): - publish_status = { - "GET": ApiPublishStatus.PRIVATE, - } - owner = ApiOwner.CRONS - - permission_classes = (MonitorCheckInAttachmentPermission,) - - def get(self, request: Request, organization, project, monitor, checkin) -> Response: - return self.get_monitor_checkin_attachment(request, project, monitor, checkin) diff --git a/src/sentry/monitors/endpoints/project_monitor_checkin_attachment.py b/src/sentry/monitors/endpoints/project_monitor_checkin_attachment.py deleted file mode 100644 index 3d2eca27b0d9b3..00000000000000 --- a/src/sentry/monitors/endpoints/project_monitor_checkin_attachment.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import annotations - -from rest_framework.request import Request -from rest_framework.response import Response - -from sentry.api.api_owners import ApiOwner -from sentry.api.api_publish_status import ApiPublishStatus -from sentry.api.base import region_silo_endpoint - -from .base import ProjectMonitorCheckinEndpoint -from .base_monitor_checkin_attachment import ( - BaseMonitorCheckInAttachmentEndpoint, - MonitorCheckInAttachmentPermission, -) - - -@region_silo_endpoint -class ProjectMonitorCheckInAttachmentEndpoint( - ProjectMonitorCheckinEndpoint, BaseMonitorCheckInAttachmentEndpoint -): - publish_status = { - "GET": ApiPublishStatus.PRIVATE, - } - owner = ApiOwner.CRONS - permission_classes = (MonitorCheckInAttachmentPermission,) - - def get(self, request: Request, project, monitor, checkin) -> Response: - return self.get_monitor_checkin_attachment(request, project, monitor, checkin) diff --git a/src/sentry/monitors/serializers.py b/src/sentry/monitors/serializers.py index 78d04987dbf199..1317ccd3ccf574 100644 --- a/src/sentry/monitors/serializers.py +++ b/src/sentry/monitors/serializers.py @@ -277,7 +277,6 @@ class MonitorCheckInSerializerResponse(MonitorCheckInSerializerResponseOptional) status: str duration: int dateCreated: datetime - attachmentId: str expectedTime: datetime monitorConfig: Any @@ -339,7 +338,6 @@ def serialize(self, obj, attrs, user, **kwargs) -> MonitorCheckInSerializerRespo "status": obj.get_status_display(), "duration": obj.duration, "dateCreated": obj.date_added, - "attachmentId": obj.attachment_id, "expectedTime": obj.expected_time, "monitorConfig": obj.monitor_config or {}, } diff --git a/tests/sentry/api/test_path_params.py b/tests/sentry/api/test_path_params.py index f68f9340c9eb1e..0251598b3a174c 100644 --- a/tests/sentry/api/test_path_params.py +++ b/tests/sentry/api/test_path_params.py @@ -24,10 +24,6 @@ def extract_all_url_patterns( callback = pattern.callback if hasattr(callback, "view_class"): yield (url_pattern, callback.view_class) - elif hasattr(callback, "dispatch_mapping"): - for view_func in callback.dispatch_mapping.keys(): - if callable(view_func): - yield (url_pattern, view_func.cls) @no_silo_test diff --git a/tests/sentry/monitors/endpoints/test_monitor_ingest_checkin_attachment.py b/tests/sentry/monitors/endpoints/test_monitor_ingest_checkin_attachment.py deleted file mode 100644 index 319cc3f1a15502..00000000000000 --- a/tests/sentry/monitors/endpoints/test_monitor_ingest_checkin_attachment.py +++ /dev/null @@ -1,171 +0,0 @@ -from unittest import mock - -from django.core.files.uploadedfile import SimpleUploadedFile -from django.urls import reverse - -from sentry.models.files.file import File -from sentry.monitors.models import CheckInStatus, MonitorCheckIn -from sentry.testutils.cases import MonitorIngestTestCase - - -class MonitorIngestCheckinAttachmentEndpointTest(MonitorIngestTestCase): - endpoint = "sentry-api-0-organization-monitor-check-in-attachment" - - def get_path(self, monitor, checkin): - return reverse(self.endpoint, args=[self.organization.slug, monitor.slug, checkin.guid]) - - def test_upload(self): - monitor = self._create_monitor() - monitor_environment = self._create_monitor_environment(monitor) - - checkin = MonitorCheckIn.objects.create( - monitor=monitor, - monitor_environment=monitor_environment, - project_id=self.project.id, - date_added=monitor.date_added, - status=CheckInStatus.IN_PROGRESS, - ) - - path = self.get_path(monitor, checkin) - resp = self.client.post( - path, - { - "file": SimpleUploadedFile( - "log.txt", b"test log data", content_type="application/text" - ), - }, - format="multipart", - **self.token_auth_headers, - ) - - assert resp.status_code == 200, resp.content - - checkin = MonitorCheckIn.objects.get(id=checkin.id) - - assert checkin.status == CheckInStatus.IN_PROGRESS - file = File.objects.get(id=checkin.attachment_id) - assert file.name == "log.txt" - assert file.getfile().read() == b"test log data" - - def test_upload_no_file(self): - monitor = self._create_monitor() - monitor_environment = self._create_monitor_environment(monitor) - - checkin = MonitorCheckIn.objects.create( - monitor=monitor, - monitor_environment=monitor_environment, - project_id=self.project.id, - date_added=monitor.date_added, - status=CheckInStatus.IN_PROGRESS, - ) - - path = self.get_path(monitor, checkin) - resp = self.client.post( - path, - {}, - format="multipart", - **self.token_auth_headers, - ) - - assert resp.status_code == 400 - assert resp.data["detail"] == "Missing uploaded file" - - @mock.patch( - "sentry.monitors.endpoints.monitor_ingest_checkin_attachment.MAX_ATTACHMENT_SIZE", 1 - ) - def test_upload_file_too_big(self): - monitor = self._create_monitor() - monitor_environment = self._create_monitor_environment(monitor) - - checkin = MonitorCheckIn.objects.create( - monitor=monitor, - monitor_environment=monitor_environment, - project_id=self.project.id, - date_added=monitor.date_added, - status=CheckInStatus.IN_PROGRESS, - ) - - path = self.get_path(monitor, checkin) - resp = self.client.post( - path, - { - "file": SimpleUploadedFile( - "log.txt", b"test log data", content_type="application/text" - ), - }, - format="multipart", - **self.token_auth_headers, - ) - - assert resp.status_code == 400 - assert resp.data["detail"] == "Please keep uploads below 100kb" - - def test_duplicate_upload(self): - monitor = self._create_monitor() - monitor_environment = self._create_monitor_environment(monitor) - - checkin = MonitorCheckIn.objects.create( - monitor=monitor, - monitor_environment=monitor_environment, - project_id=self.project.id, - date_added=monitor.date_added, - status=CheckInStatus.IN_PROGRESS, - ) - - path = self.get_path(monitor, checkin) - resp = self.client.post( - path, - { - "file": SimpleUploadedFile( - "log.txt", b"test log data", content_type="application/text" - ), - }, - format="multipart", - **self.token_auth_headers, - ) - - assert resp.status_code == 200, resp.content - - checkin = MonitorCheckIn.objects.get(id=checkin.id) - - assert checkin.status == CheckInStatus.IN_PROGRESS - file = File.objects.get(id=checkin.attachment_id) - assert file.name == "log.txt" - assert file.getfile().read() == b"test log data" - - resp = self.client.post( - path, - { - "file": SimpleUploadedFile( - "log.txt", b"test log data", content_type="application/text" - ), - }, - format="multipart", - **self.token_auth_headers, - ) - - assert resp.status_code == 400 - assert resp.data["detail"] == "Check-in already has an attachment" - - def test_invalid_file_upload(self): - monitor = self._create_monitor() - monitor_environment = self._create_monitor_environment(monitor) - - checkin = MonitorCheckIn.objects.create( - monitor=monitor, - monitor_environment=monitor_environment, - project_id=self.project.id, - date_added=monitor.date_added, - status=CheckInStatus.IN_PROGRESS, - ) - - path = self.get_path(monitor, checkin) - resp = self.client.post( - path, - {"file": "invalid_file"}, - format="multipart", - **self.token_auth_headers, - ) - - assert resp.status_code == 400 - assert resp.data["detail"] == "Please upload a valid file object" diff --git a/tests/sentry/monitors/endpoints/test_organization_monitor_checkin_attachment.py b/tests/sentry/monitors/endpoints/test_organization_monitor_checkin_attachment.py deleted file mode 100644 index 7fb90e25a6db50..00000000000000 --- a/tests/sentry/monitors/endpoints/test_organization_monitor_checkin_attachment.py +++ /dev/null @@ -1,8 +0,0 @@ -from tests.sentry.monitors.endpoints.test_base_monitor_checkin_attachment import ( - BaseMonitorCheckInAttachmentEndpointTest, -) - - -class OrganizationMonitorCheckInAttachmentEndpointTest(BaseMonitorCheckInAttachmentEndpointTest): - endpoint = "sentry-api-0-organization-monitor-check-in-attachment" - __test__ = True diff --git a/tests/sentry/monitors/endpoints/test_project_monitor_checkin_attachment.py b/tests/sentry/monitors/endpoints/test_project_monitor_checkin_attachment.py deleted file mode 100644 index 439de3633a79a2..00000000000000 --- a/tests/sentry/monitors/endpoints/test_project_monitor_checkin_attachment.py +++ /dev/null @@ -1,11 +0,0 @@ -from tests.sentry.monitors.endpoints.test_base import BaseProjectMonitorTest -from tests.sentry.monitors.endpoints.test_base_monitor_checkin_attachment import ( - BaseMonitorCheckInAttachmentEndpointTest, -) - - -class OrganizationMonitorCheckInAttachmentEndpointTest( - BaseMonitorCheckInAttachmentEndpointTest, BaseProjectMonitorTest -): - endpoint = "sentry-api-0-project-monitor-check-in-attachment" - __test__ = True