test_organization_event_details.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. from datetime import timedelta
  2. import pytest
  3. from django.urls import NoReverseMatch, reverse
  4. from sentry.issues.occurrence_consumer import process_event_and_issue_occurrence
  5. from sentry.models.group import Group
  6. from sentry.testutils.cases import APITestCase, SnubaTestCase
  7. from sentry.testutils.helpers.datetime import before_now, iso_format
  8. from sentry.testutils.silo import region_silo_test
  9. from sentry.utils.samples import load_data
  10. from tests.sentry.issues.test_utils import OccurrenceTestMixin
  11. def format_project_event(project_slug, event_id):
  12. return f"{project_slug}:{event_id}"
  13. @region_silo_test
  14. class OrganizationEventDetailsEndpointTest(APITestCase, SnubaTestCase, OccurrenceTestMixin):
  15. def setUp(self):
  16. super().setUp()
  17. min_ago = iso_format(before_now(minutes=1))
  18. two_min_ago = iso_format(before_now(minutes=2))
  19. three_min_ago = iso_format(before_now(minutes=3))
  20. self.login_as(user=self.user)
  21. self.project = self.create_project()
  22. self.project_2 = self.create_project()
  23. self.store_event(
  24. data={
  25. "event_id": "a" * 32,
  26. "message": "oh no",
  27. "timestamp": three_min_ago,
  28. "fingerprint": ["group-1"],
  29. },
  30. project_id=self.project.id,
  31. )
  32. self.store_event(
  33. data={
  34. "event_id": "b" * 32,
  35. "message": "very bad",
  36. "timestamp": two_min_ago,
  37. "fingerprint": ["group-1"],
  38. },
  39. project_id=self.project.id,
  40. )
  41. self.store_event(
  42. data={
  43. "event_id": "c" * 32,
  44. "message": "very bad",
  45. "timestamp": min_ago,
  46. "fingerprint": ["group-2"],
  47. },
  48. project_id=self.project.id,
  49. )
  50. self.groups = list(Group.objects.all().order_by("id"))
  51. def test_performance_flag(self):
  52. url = reverse(
  53. "sentry-api-0-organization-event-details",
  54. kwargs={
  55. "organization_slug": self.project.organization.slug,
  56. "project_slug": self.project.slug,
  57. "event_id": "a" * 32,
  58. },
  59. )
  60. with self.feature(
  61. {"organizations:discover-basic": False, "organizations:performance-view": True}
  62. ):
  63. response = self.client.get(url, format="json")
  64. assert response.status_code == 200, response.content
  65. assert response.data["id"] == "a" * 32
  66. assert response.data["projectSlug"] == self.project.slug
  67. def test_simple(self):
  68. url = reverse(
  69. "sentry-api-0-organization-event-details",
  70. kwargs={
  71. "organization_slug": self.project.organization.slug,
  72. "project_slug": self.project.slug,
  73. "event_id": "a" * 32,
  74. },
  75. )
  76. with self.feature("organizations:discover-basic"):
  77. response = self.client.get(url, format="json")
  78. assert response.status_code == 200, response.content
  79. assert response.data["id"] == "a" * 32
  80. assert response.data["projectSlug"] == self.project.slug
  81. def test_simple_transaction(self):
  82. min_ago = iso_format(before_now(minutes=1))
  83. event = self.store_event(
  84. data={
  85. "event_id": "d" * 32,
  86. "type": "transaction",
  87. "transaction": "api.issue.delete",
  88. "spans": [],
  89. "contexts": {"trace": {"op": "foobar", "trace_id": "a" * 32, "span_id": "a" * 16}},
  90. "start_timestamp": iso_format(before_now(minutes=1, seconds=5)),
  91. "timestamp": min_ago,
  92. },
  93. project_id=self.project.id,
  94. )
  95. url = reverse(
  96. "sentry-api-0-organization-event-details",
  97. kwargs={
  98. "organization_slug": self.project.organization.slug,
  99. "project_slug": self.project.slug,
  100. "event_id": event.event_id,
  101. },
  102. )
  103. with self.feature("organizations:discover-basic"):
  104. response = self.client.get(url, format="json")
  105. assert response.status_code == 200
  106. assert response.data["id"] == "d" * 32
  107. assert response.data["type"] == "transaction"
  108. def test_no_access_missing_feature(self):
  109. with self.feature({"organizations:discover-basic": False}):
  110. url = reverse(
  111. "sentry-api-0-organization-event-details",
  112. kwargs={
  113. "organization_slug": self.project.organization.slug,
  114. "project_slug": self.project.slug,
  115. "event_id": "a" * 32,
  116. },
  117. )
  118. response = self.client.get(url, format="json")
  119. assert response.status_code == 404, response.content
  120. def test_access_non_member_project(self):
  121. # Add a new user to a project and then access events on project they are not part of.
  122. member_user = self.create_user()
  123. team = self.create_team(members=[member_user])
  124. self.create_project(organization=self.organization, teams=[team])
  125. # Enable open membership
  126. self.organization.flags.allow_joinleave = True
  127. self.organization.save()
  128. self.login_as(member_user)
  129. url = reverse(
  130. "sentry-api-0-organization-event-details",
  131. kwargs={
  132. "organization_slug": self.organization.slug,
  133. "project_slug": self.project.slug,
  134. "event_id": "a" * 32,
  135. },
  136. )
  137. with self.feature("organizations:discover-basic"):
  138. response = self.client.get(url, format="json")
  139. assert response.status_code == 200, response.content
  140. # When open membership is off, access should be denied to non owner users
  141. self.organization.flags.allow_joinleave = False
  142. self.organization.save()
  143. with self.feature("organizations:discover-basic"):
  144. response = self.client.get(url, format="json")
  145. assert response.status_code == 404, response.content
  146. def test_no_event(self):
  147. url = reverse(
  148. "sentry-api-0-organization-event-details",
  149. kwargs={
  150. "organization_slug": self.project.organization.slug,
  151. "project_slug": self.project.slug,
  152. "event_id": "d" * 32,
  153. },
  154. )
  155. with self.feature("organizations:discover-basic"):
  156. response = self.client.get(url, format="json")
  157. assert response.status_code == 404, response.content
  158. def test_invalid_event_id(self):
  159. with pytest.raises(NoReverseMatch):
  160. reverse(
  161. "sentry-api-0-organization-event-details",
  162. kwargs={
  163. "organization_slug": self.project.organization.slug,
  164. "project_slug": self.project.slug,
  165. "event_id": "not-an-event",
  166. },
  167. )
  168. def test_long_trace_description(self):
  169. data = load_data("transaction")
  170. data["event_id"] = "d" * 32
  171. data["timestamp"] = iso_format(before_now(minutes=1))
  172. data["start_timestamp"] = iso_format(before_now(minutes=1) - timedelta(seconds=5))
  173. data["contexts"]["trace"]["description"] = "b" * 512
  174. self.store_event(data=data, project_id=self.project.id)
  175. url = reverse(
  176. "sentry-api-0-organization-event-details",
  177. kwargs={
  178. "organization_slug": self.project.organization.slug,
  179. "project_slug": self.project.slug,
  180. "event_id": "d" * 32,
  181. },
  182. )
  183. with self.feature("organizations:discover-basic"):
  184. response = self.client.get(url, format="json")
  185. assert response.status_code == 200, response.content
  186. trace = response.data["contexts"]["trace"]
  187. original_trace = data["contexts"]["trace"]
  188. assert trace["trace_id"] == original_trace["trace_id"]
  189. assert trace["span_id"] == original_trace["span_id"]
  190. assert trace["parent_span_id"] == original_trace["parent_span_id"]
  191. assert trace["description"][:-3] in original_trace["description"]
  192. def test_blank_fields(self):
  193. url = reverse(
  194. "sentry-api-0-organization-event-details",
  195. kwargs={
  196. "organization_slug": self.project.organization.slug,
  197. "project_slug": self.project.slug,
  198. "event_id": "a" * 32,
  199. },
  200. )
  201. with self.feature("organizations:discover-basic"):
  202. response = self.client.get(
  203. url,
  204. data={"field": ["", " "], "statsPeriod": "24h"},
  205. format="json",
  206. )
  207. assert response.status_code == 200, response.content
  208. assert response.data["id"] == "a" * 32
  209. assert response.data["projectSlug"] == self.project.slug
  210. def test_out_of_retention(self):
  211. self.store_event(
  212. data={
  213. "event_id": "d" * 32,
  214. "message": "oh no",
  215. "timestamp": iso_format(before_now(days=2)),
  216. "fingerprint": ["group-1"],
  217. },
  218. project_id=self.project.id,
  219. )
  220. url = reverse(
  221. "sentry-api-0-organization-event-details",
  222. kwargs={
  223. "organization_slug": self.project.organization.slug,
  224. "project_slug": self.project.slug,
  225. "event_id": "d" * 32,
  226. },
  227. )
  228. with self.options({"system.event-retention-days": 1}):
  229. response = self.client.get(
  230. url,
  231. format="json",
  232. )
  233. assert response.status_code == 404, response.content
  234. def test_generic_event(self):
  235. occurrence_data = self.build_occurrence_data(project_id=self.project.id)
  236. occurrence, group_info = process_event_and_issue_occurrence(
  237. occurrence_data,
  238. event_data={
  239. "event_id": occurrence_data["event_id"],
  240. "project_id": occurrence_data["project_id"],
  241. "level": "info",
  242. },
  243. )
  244. url = reverse(
  245. "sentry-api-0-organization-event-details",
  246. kwargs={
  247. "organization_slug": self.project.organization.slug,
  248. "project_slug": self.project.slug,
  249. "event_id": occurrence_data["event_id"],
  250. },
  251. )
  252. with self.feature("organizations:discover-basic"):
  253. response = self.client.get(url, format="json")
  254. assert response.status_code == 200, response.content
  255. assert response.data["id"] == occurrence_data["event_id"]
  256. assert response.data["projectSlug"] == self.project.slug
  257. assert response.data["occurrence"] is not None
  258. assert response.data["occurrence"]["id"] == occurrence.id