test_project_event_details.py 16 KB

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