test_project_event_details.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. from django.urls import reverse
  2. from sentry.issues.grouptype import PerformanceRenderBlockingAssetSpanGroupType
  3. from sentry.issues.occurrence_consumer import process_event_and_issue_occurrence
  4. from sentry.testutils import APITestCase, SnubaTestCase
  5. from sentry.testutils.helpers.datetime import before_now, iso_format
  6. from sentry.testutils.silo import region_silo_test
  7. from tests.sentry.issues.test_utils import OccurrenceTestMixin
  8. @region_silo_test
  9. class ProjectEventDetailsTest(APITestCase, SnubaTestCase):
  10. def setUp(self):
  11. super().setUp()
  12. self.login_as(user=self.user)
  13. self.setup_data()
  14. def setup_data(self):
  15. one_min_ago = iso_format(before_now(minutes=1))
  16. two_min_ago = iso_format(before_now(minutes=2))
  17. three_min_ago = iso_format(before_now(minutes=3))
  18. four_min_ago = iso_format(before_now(minutes=4))
  19. self.prev_event = self.store_event(
  20. data={"event_id": "a" * 32, "timestamp": four_min_ago, "fingerprint": ["group-1"]},
  21. project_id=self.project.id,
  22. )
  23. self.cur_event = self.store_event(
  24. data={"event_id": "b" * 32, "timestamp": three_min_ago, "fingerprint": ["group-1"]},
  25. project_id=self.project.id,
  26. )
  27. self.next_event = self.store_event(
  28. data={
  29. "event_id": "c" * 32,
  30. "timestamp": two_min_ago,
  31. "fingerprint": ["group-1"],
  32. "environment": "production",
  33. "tags": {"environment": "production"},
  34. },
  35. project_id=self.project.id,
  36. )
  37. self.cur_group = self.next_event.group
  38. # Event in different group
  39. self.store_event(
  40. data={
  41. "event_id": "d" * 32,
  42. "timestamp": one_min_ago,
  43. "fingerprint": ["group-2"],
  44. "environment": "production",
  45. "tags": {"environment": "production"},
  46. },
  47. project_id=self.project.id,
  48. )
  49. def test_simple(self):
  50. url = reverse(
  51. "sentry-api-0-project-event-details",
  52. kwargs={
  53. "event_id": self.cur_event.event_id,
  54. "project_slug": self.project.slug,
  55. "organization_slug": self.project.organization.slug,
  56. },
  57. )
  58. response = self.client.get(url, format="json")
  59. assert response.status_code == 200, response.content
  60. assert response.data["id"] == str(self.cur_event.event_id)
  61. assert response.data["nextEventID"] == str(self.next_event.event_id)
  62. assert response.data["previousEventID"] == str(self.prev_event.event_id)
  63. assert response.data["groupID"] == str(self.cur_group.id)
  64. def test_snuba_no_prev(self):
  65. url = reverse(
  66. "sentry-api-0-project-event-details",
  67. kwargs={
  68. "event_id": self.prev_event.event_id,
  69. "project_slug": self.project.slug,
  70. "organization_slug": self.project.organization.slug,
  71. },
  72. )
  73. response = self.client.get(url, format="json")
  74. assert response.status_code == 200, response.content
  75. assert response.data["id"] == str(self.prev_event.event_id)
  76. assert response.data["previousEventID"] is None
  77. assert response.data["nextEventID"] == self.cur_event.event_id
  78. assert response.data["groupID"] == str(self.cur_group.id)
  79. def test_snuba_with_environment(self):
  80. url = reverse(
  81. "sentry-api-0-project-event-details",
  82. kwargs={
  83. "event_id": self.cur_event.event_id,
  84. "project_slug": self.project.slug,
  85. "organization_slug": self.project.organization.slug,
  86. },
  87. )
  88. response = self.client.get(
  89. url, format="json", data={"environment": ["production", "staging"]}
  90. )
  91. assert response.status_code == 200, response.content
  92. assert response.data["id"] == str(self.cur_event.event_id)
  93. assert response.data["previousEventID"] is None
  94. assert response.data["nextEventID"] == self.next_event.event_id
  95. assert response.data["groupID"] == str(self.cur_group.id)
  96. def test_ignores_different_group(self):
  97. url = reverse(
  98. "sentry-api-0-project-event-details",
  99. kwargs={
  100. "event_id": self.next_event.event_id,
  101. "project_slug": self.project.slug,
  102. "organization_slug": self.project.organization.slug,
  103. },
  104. )
  105. response = self.client.get(url, format="json")
  106. assert response.status_code == 200, response.content
  107. assert response.data["id"] == str(self.next_event.event_id)
  108. assert response.data["nextEventID"] is None
  109. @region_silo_test
  110. class ProjectEventDetailsGenericTest(OccurrenceTestMixin, ProjectEventDetailsTest):
  111. def setup_data(self):
  112. one_min_ago = iso_format(before_now(minutes=1))
  113. two_min_ago = iso_format(before_now(minutes=2))
  114. three_min_ago = iso_format(before_now(minutes=3))
  115. four_min_ago = iso_format(before_now(minutes=4))
  116. prev_event_id = "a" * 32
  117. self.prev_event, prev_group_info = process_event_and_issue_occurrence(
  118. self.build_occurrence_data(
  119. event_id=prev_event_id, project_id=self.project.id, fingerprint=["group-1"]
  120. ),
  121. {
  122. "event_id": prev_event_id,
  123. "project_id": self.project.id,
  124. "timestamp": four_min_ago,
  125. "message_timestamp": four_min_ago,
  126. },
  127. )
  128. cur_event_id = "b" * 32
  129. self.cur_event, cur_group_info = process_event_and_issue_occurrence(
  130. self.build_occurrence_data(
  131. event_id=cur_event_id, project_id=self.project.id, fingerprint=["group-1"]
  132. ),
  133. {
  134. "event_id": cur_event_id,
  135. "project_id": self.project.id,
  136. "timestamp": three_min_ago,
  137. "message_timestamp": three_min_ago,
  138. },
  139. )
  140. self.cur_group = cur_group_info.group
  141. next_event_id = "c" * 32
  142. self.next_event, next_group_info = process_event_and_issue_occurrence(
  143. self.build_occurrence_data(
  144. event_id=next_event_id, project_id=self.project.id, fingerprint=["group-1"]
  145. ),
  146. {
  147. "event_id": next_event_id,
  148. "project_id": self.project.id,
  149. "timestamp": two_min_ago,
  150. "message_timestamp": two_min_ago,
  151. "tags": {"environment": "production"},
  152. },
  153. )
  154. unrelated_event_id = "d" * 32
  155. process_event_and_issue_occurrence(
  156. self.build_occurrence_data(
  157. event_id=unrelated_event_id, project_id=self.project.id, fingerprint=["group-2"]
  158. ),
  159. {
  160. "event_id": unrelated_event_id,
  161. "project_id": self.project.id,
  162. "timestamp": one_min_ago,
  163. "message_timestamp": one_min_ago,
  164. "tags": {"environment": "production"},
  165. },
  166. )[0]
  167. def test_generic_event_with_occurrence(self):
  168. url = reverse(
  169. "sentry-api-0-project-event-details",
  170. kwargs={
  171. "event_id": self.cur_event.event_id,
  172. "project_slug": self.project.slug,
  173. "organization_slug": self.project.organization.slug,
  174. },
  175. )
  176. response = self.client.get(url, format="json", data={"group_id": self.cur_group.id})
  177. assert response.status_code == 200, response.content
  178. assert response.data["id"] == self.cur_event.event_id
  179. assert response.data["occurrence"] is not None
  180. assert response.data["occurrence"]["id"] == self.cur_event.id
  181. @region_silo_test
  182. class ProjectEventDetailsTransactionTest(APITestCase, SnubaTestCase):
  183. def setUp(self):
  184. super().setUp()
  185. self.login_as(user=self.user)
  186. project = self.create_project()
  187. one_min_ago = iso_format(before_now(minutes=1))
  188. two_min_ago = iso_format(before_now(minutes=2))
  189. three_min_ago = iso_format(before_now(minutes=3))
  190. four_min_ago = iso_format(before_now(minutes=4))
  191. transaction_event_data = {
  192. "level": "info",
  193. "message": "ayoo",
  194. "type": "transaction",
  195. "culprit": "app/components/events/eventEntries in map",
  196. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  197. }
  198. self.prev_transaction_event = self.store_event(
  199. data={
  200. **transaction_event_data,
  201. "event_id": "a" * 32,
  202. "timestamp": four_min_ago,
  203. "start_timestamp": four_min_ago,
  204. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group1"],
  205. },
  206. project_id=project.id,
  207. )
  208. self.cur_transaction_event = self.store_event(
  209. data={
  210. **transaction_event_data,
  211. "event_id": "b" * 32,
  212. "timestamp": three_min_ago,
  213. "start_timestamp": three_min_ago,
  214. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group1"],
  215. },
  216. project_id=project.id,
  217. )
  218. self.next_transaction_event = self.store_event(
  219. data={
  220. **transaction_event_data,
  221. "event_id": "c" * 32,
  222. "timestamp": two_min_ago,
  223. "start_timestamp": two_min_ago,
  224. "environment": "production",
  225. "tags": {"environment": "production"},
  226. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group1"],
  227. },
  228. project_id=project.id,
  229. )
  230. self.group = self.prev_transaction_event.groups[0]
  231. # Event in different group
  232. self.store_event(
  233. data={
  234. **transaction_event_data,
  235. "event_id": "d" * 32,
  236. "timestamp": one_min_ago,
  237. "start_timestamp": one_min_ago,
  238. "environment": "production",
  239. "tags": {"environment": "production"},
  240. },
  241. project_id=project.id,
  242. )
  243. def test_transaction_event(self):
  244. """Test that you can look up a transaction event w/ a prev and next event"""
  245. url = reverse(
  246. "sentry-api-0-project-event-details",
  247. kwargs={
  248. "event_id": self.cur_transaction_event.event_id,
  249. "project_slug": self.cur_transaction_event.project.slug,
  250. "organization_slug": self.cur_transaction_event.project.organization.slug,
  251. },
  252. )
  253. response = self.client.get(url, format="json", data={"group_id": self.group.id})
  254. assert response.status_code == 200, response.content
  255. assert response.data["id"] == str(self.cur_transaction_event.event_id)
  256. assert response.data["nextEventID"] == str(self.next_transaction_event.event_id)
  257. assert response.data["previousEventID"] == str(self.prev_transaction_event.event_id)
  258. assert response.data["groupID"] == str(self.cur_transaction_event.groups[0].id)
  259. def test_no_previous_event(self):
  260. """Test the case in which there is no previous event"""
  261. url = reverse(
  262. "sentry-api-0-project-event-details",
  263. kwargs={
  264. "event_id": self.prev_transaction_event.event_id,
  265. "project_slug": self.prev_transaction_event.project.slug,
  266. "organization_slug": self.prev_transaction_event.project.organization.slug,
  267. },
  268. )
  269. response = self.client.get(url, format="json", data={"group_id": self.group.id})
  270. assert response.status_code == 200, response.content
  271. assert response.data["id"] == str(self.prev_transaction_event.event_id)
  272. assert response.data["previousEventID"] is None
  273. assert response.data["nextEventID"] == self.cur_transaction_event.event_id
  274. assert response.data["groupID"] == str(self.prev_transaction_event.groups[0].id)
  275. def test_ignores_different_group(self):
  276. """Test that a different group's events aren't attributed to the one that was passed"""
  277. url = reverse(
  278. "sentry-api-0-project-event-details",
  279. kwargs={
  280. "event_id": self.next_transaction_event.event_id,
  281. "project_slug": self.next_transaction_event.project.slug,
  282. "organization_slug": self.next_transaction_event.project.organization.slug,
  283. },
  284. )
  285. response = self.client.get(url, format="json", data={"group_id": self.group.id})
  286. assert response.status_code == 200, response.content
  287. assert response.data["id"] == str(self.next_transaction_event.event_id)
  288. assert response.data["nextEventID"] is None
  289. def test_no_group_id(self):
  290. """Test the case where a group_id was not passed"""
  291. url = reverse(
  292. "sentry-api-0-project-event-details",
  293. kwargs={
  294. "event_id": self.cur_transaction_event.event_id,
  295. "project_slug": self.cur_transaction_event.project.slug,
  296. "organization_slug": self.cur_transaction_event.project.organization.slug,
  297. },
  298. )
  299. response = self.client.get(url, format="json")
  300. assert response.status_code == 200, response.content
  301. assert response.data["id"] == str(self.cur_transaction_event.event_id)
  302. assert response.data["previousEventID"] is None
  303. assert response.data["nextEventID"] is None
  304. assert response.data["groupID"] is None
  305. @region_silo_test
  306. class ProjectEventJsonEndpointTest(APITestCase, SnubaTestCase):
  307. def setUp(self):
  308. super().setUp()
  309. self.login_as(user=self.user)
  310. self.event_id = "c" * 32
  311. self.fingerprint = ["group_2"]
  312. self.min_ago = iso_format(before_now(minutes=1))
  313. self.event = self.store_event(
  314. data={
  315. "event_id": self.event_id,
  316. "timestamp": self.min_ago,
  317. "fingerprint": self.fingerprint,
  318. "user": {"email": self.user.email},
  319. },
  320. project_id=self.project.id,
  321. )
  322. self.url = reverse(
  323. "sentry-api-0-event-json",
  324. kwargs={
  325. "organization_slug": self.organization.slug,
  326. "project_slug": self.project.slug,
  327. "event_id": self.event_id,
  328. },
  329. )
  330. def assert_event(self, data):
  331. assert data["event_id"] == self.event_id
  332. assert data["user"]["email"] == self.user.email
  333. assert data["datetime"][:19] == self.min_ago
  334. assert data["fingerprint"] == self.fingerprint
  335. def test_simple(self):
  336. response = self.client.get(self.url, format="json")
  337. assert response.status_code == 200, response.content
  338. self.assert_event(response.data)
  339. def test_event_does_not_exist(self):
  340. self.url = reverse(
  341. "sentry-api-0-event-json",
  342. kwargs={
  343. "organization_slug": self.organization.slug,
  344. "project_slug": self.project.slug,
  345. "event_id": "no" * 16,
  346. },
  347. )
  348. response = self.client.get(self.url, format="json")
  349. assert response.status_code == 404, response.content
  350. assert response.data == {"detail": "Event not found"}
  351. def test_user_unauthorized(self):
  352. user = self.create_user()
  353. self.login_as(user)
  354. response = self.client.get(self.url, format="json")
  355. assert response.status_code == 403, response.content
  356. assert response.data == {"detail": "You do not have permission to perform this action."}
  357. def test_project_not_associated_with_event(self):
  358. project2 = self.create_project(organization=self.organization)
  359. url = reverse(
  360. "sentry-api-0-event-json",
  361. kwargs={
  362. "organization_slug": self.organization.slug,
  363. "project_slug": project2.slug,
  364. "event_id": self.event_id,
  365. },
  366. )
  367. response = self.client.get(url, format="json")
  368. assert response.status_code == 404, response.content
  369. assert response.data == {"detail": "Event not found"}