test_group_details.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. from unittest import mock
  2. from rest_framework.exceptions import ErrorDetail
  3. from sentry.models import (
  4. GROUP_OWNER_TYPE,
  5. Environment,
  6. GroupInboxReason,
  7. GroupOwner,
  8. GroupOwnerType,
  9. Release,
  10. )
  11. from sentry.models.groupinbox import add_group_to_inbox, remove_group_from_inbox
  12. from sentry.testutils import APITestCase, SnubaTestCase
  13. from sentry.testutils.helpers.datetime import before_now, iso_format
  14. from sentry.testutils.silo import region_silo_test
  15. @region_silo_test(stable=True)
  16. class GroupDetailsTest(APITestCase, SnubaTestCase):
  17. def test_multiple_environments(self):
  18. group = self.create_group()
  19. self.login_as(user=self.user)
  20. environment = Environment.get_or_create(group.project, "production")
  21. environment2 = Environment.get_or_create(group.project, "staging")
  22. url = f"/api/0/issues/{group.id}/"
  23. from sentry.api.endpoints.group_details import tsdb
  24. with mock.patch(
  25. "sentry.api.endpoints.group_details.tsdb.get_range", side_effect=tsdb.get_range
  26. ) as get_range:
  27. response = self.client.get(
  28. f"{url}?environment=production&environment=staging", format="json"
  29. )
  30. assert response.status_code == 200
  31. assert get_range.call_count == 2
  32. for args, kwargs in get_range.call_args_list:
  33. assert kwargs["environment_ids"] == [environment.id, environment2.id]
  34. response = self.client.get(f"{url}?environment=invalid", format="json")
  35. assert response.status_code == 404
  36. def test_with_first_last_release(self):
  37. self.login_as(user=self.user)
  38. first_release = {
  39. "firstEvent": before_now(minutes=3),
  40. "lastEvent": before_now(minutes=2, seconds=30),
  41. }
  42. last_release = {
  43. "firstEvent": before_now(minutes=1, seconds=30),
  44. "lastEvent": before_now(minutes=1),
  45. }
  46. for timestamp in first_release.values():
  47. self.store_event(
  48. data={"release": "1.0", "timestamp": iso_format(timestamp)},
  49. project_id=self.project.id,
  50. )
  51. self.store_event(
  52. data={"release": "1.1", "timestamp": iso_format(before_now(minutes=2))},
  53. project_id=self.project.id,
  54. )
  55. event = None
  56. for timestamp in last_release.values():
  57. event = self.store_event(
  58. data={"release": "1.0a", "timestamp": iso_format(timestamp)},
  59. project_id=self.project.id,
  60. )
  61. group = event.group
  62. url = f"/api/0/issues/{group.id}/"
  63. response = self.client.get(url, format="json")
  64. assert response.status_code == 200, response.content
  65. assert response.data["id"] == str(group.id)
  66. release = response.data["firstRelease"]
  67. assert release["version"] == "1.0"
  68. for event, timestamp in first_release.items():
  69. assert release[event].ctime() == timestamp.ctime()
  70. release = response.data["lastRelease"]
  71. assert release["version"] == "1.0a"
  72. for event, timestamp in last_release.items():
  73. assert release[event].ctime() == timestamp.ctime()
  74. def test_first_last_only_one_tagstore(self):
  75. self.login_as(user=self.user)
  76. event = self.store_event(
  77. data={"release": "1.0", "timestamp": iso_format(before_now(days=3))},
  78. project_id=self.project.id,
  79. )
  80. self.store_event(
  81. data={"release": "1.1", "timestamp": iso_format(before_now(minutes=3))},
  82. project_id=self.project.id,
  83. )
  84. group = event.group
  85. url = f"/api/0/issues/{group.id}/"
  86. with mock.patch(
  87. "sentry.api.endpoints.group_details.tagstore.get_release_tags"
  88. ) as get_release_tags:
  89. response = self.client.get(url, format="json")
  90. assert response.status_code == 200
  91. assert get_release_tags.call_count == 1
  92. def test_first_release_only(self):
  93. self.login_as(user=self.user)
  94. first_event = before_now(days=3)
  95. self.store_event(
  96. data={"release": "1.0", "timestamp": iso_format(first_event)},
  97. project_id=self.project.id,
  98. )
  99. event = self.store_event(
  100. data={"release": "1.1", "timestamp": iso_format(before_now(days=1))},
  101. project_id=self.project.id,
  102. )
  103. # Forcibly remove one of the releases
  104. Release.objects.get(version="1.1").delete()
  105. group = event.group
  106. url = f"/api/0/issues/{group.id}/"
  107. response = self.client.get(url, format="json")
  108. assert response.status_code == 200, response.content
  109. assert response.data["firstRelease"]["version"] == "1.0"
  110. # only one event
  111. assert (
  112. response.data["firstRelease"]["firstEvent"]
  113. == response.data["firstRelease"]["lastEvent"]
  114. )
  115. assert response.data["firstRelease"]["firstEvent"].ctime() == first_event.ctime()
  116. assert response.data["lastRelease"] is None
  117. def test_group_expand_inbox(self):
  118. self.login_as(user=self.user)
  119. event = self.store_event(
  120. data={"timestamp": iso_format(before_now(minutes=3))},
  121. project_id=self.project.id,
  122. )
  123. group = event.group
  124. add_group_to_inbox(group, GroupInboxReason.NEW)
  125. url = f"/api/0/issues/{group.id}/?expand=inbox"
  126. response = self.client.get(url, format="json")
  127. assert response.status_code == 200, response.content
  128. assert response.data["inbox"] is not None
  129. assert response.data["inbox"]["reason"] == GroupInboxReason.NEW.value
  130. assert response.data["inbox"]["reason_details"] is None
  131. remove_group_from_inbox(event.group)
  132. response = self.client.get(url, format="json")
  133. assert response.status_code == 200, response.content
  134. assert response.data["inbox"] is None
  135. def test_group_expand_owners(self):
  136. self.login_as(user=self.user)
  137. event = self.store_event(
  138. data={"timestamp": iso_format(before_now(seconds=500)), "fingerprint": ["group-1"]},
  139. project_id=self.project.id,
  140. )
  141. group = event.group
  142. url = f"/api/0/issues/{group.id}/?expand=owners"
  143. self.login_as(user=self.user)
  144. # Test with no owner
  145. response = self.client.get(url, format="json")
  146. assert response.status_code == 200
  147. assert response.data["owners"] is None
  148. # Test with owners
  149. GroupOwner.objects.create(
  150. group=event.group,
  151. project=event.project,
  152. organization=event.project.organization,
  153. type=GroupOwnerType.SUSPECT_COMMIT.value,
  154. user_id=self.user.id,
  155. )
  156. response = self.client.get(url, format="json")
  157. assert response.status_code == 200, response.content
  158. assert response.data["owners"] is not None
  159. assert len(response.data["owners"]) == 1
  160. assert response.data["owners"][0]["owner"] == f"user:{self.user.id}"
  161. assert response.data["owners"][0]["type"] == GROUP_OWNER_TYPE[GroupOwnerType.SUSPECT_COMMIT]
  162. def test_assigned_to_unknown(self):
  163. self.login_as(user=self.user)
  164. event = self.store_event(
  165. data={"timestamp": iso_format(before_now(minutes=3))},
  166. project_id=self.project.id,
  167. )
  168. group = event.group
  169. url = f"/api/0/issues/{group.id}/"
  170. response = self.client.put(
  171. url, {"assignedTo": "admin@localhost", "status": "unresolved"}, format="json"
  172. )
  173. assert response.status_code == 200
  174. response = self.client.put(
  175. url, {"assignedTo": "user@doesnotexist.com", "status": "unresolved"}, format="json"
  176. )
  177. assert response.status_code == 400
  178. assert response.data == {
  179. "assignedTo": [
  180. ErrorDetail(
  181. string="Could not parse actor. Format should be `type:id` where type is `team` or `user`.",
  182. code="invalid",
  183. )
  184. ]
  185. }
  186. def test_collapse_stats_does_not_work(self):
  187. """
  188. 'collapse' param should hide the stats data and not return anything in the response, but the impl
  189. doesn't seem to respect this param.
  190. include this test here in-case the endpoint behavior changes in the future.
  191. """
  192. self.login_as(user=self.user)
  193. event = self.store_event(
  194. data={"timestamp": iso_format(before_now(minutes=3))},
  195. project_id=self.project.id,
  196. )
  197. group = event.group
  198. url = f"/api/0/issues/{group.id}/"
  199. response = self.client.get(url, {"collapse": ["stats"]}, format="json")
  200. assert response.status_code == 200
  201. assert int(response.data["id"]) == event.group.id
  202. assert response.data["stats"] # key shouldn't be present
  203. assert response.data["count"] is not None # key shouldn't be present
  204. assert response.data["userCount"] is not None # key shouldn't be present
  205. assert response.data["firstSeen"] is not None # key shouldn't be present
  206. assert response.data["lastSeen"] is not None # key shouldn't be present
  207. def test_issue_type_category(self):
  208. """Test that the issue's type and category is returned in the results"""
  209. self.login_as(user=self.user)
  210. event = self.store_event(
  211. data={"timestamp": iso_format(before_now(minutes=3))},
  212. project_id=self.project.id,
  213. )
  214. url = f"/api/0/issues/{event.group.id}/"
  215. response = self.client.get(url, format="json")
  216. assert response.status_code == 200
  217. assert int(response.data["id"]) == event.group.id
  218. assert response.data["issueType"] == "error"
  219. assert response.data["issueCategory"] == "error"