test_organization_group_index.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. from __future__ import absolute_import
  2. import json
  3. import six
  4. from datetime import timedelta
  5. from django.core.urlresolvers import reverse
  6. from django.utils import timezone
  7. from exam import fixture
  8. from sentry.models import ApiToken, Event, EventMapping, GroupStatus, Release
  9. from sentry.testutils import APITestCase, SnubaTestCase
  10. from sentry.testutils.helpers import parse_link_header
  11. from six.moves.urllib.parse import quote
  12. class GroupListTest(APITestCase, SnubaTestCase):
  13. def setUp(self):
  14. super(GroupListTest, self).setUp()
  15. self.min_ago = timezone.now() - timedelta(minutes=1)
  16. def _parse_links(self, header):
  17. # links come in {url: {...attrs}}, but we need {rel: {...attrs}}
  18. links = {}
  19. for url, attrs in six.iteritems(parse_link_header(header)):
  20. links[attrs['rel']] = attrs
  21. attrs['href'] = url
  22. return links
  23. @fixture
  24. def path(self):
  25. return reverse(
  26. 'sentry-api-0-organization-group-index',
  27. args=[self.project.organization.slug]
  28. )
  29. def test_sort_by_date_with_tag(self):
  30. # XXX(dcramer): this tests a case where an ambiguous column name existed
  31. now = timezone.now()
  32. group1 = self.create_group(
  33. checksum='a' * 32,
  34. last_seen=now - timedelta(seconds=1),
  35. )
  36. self.login_as(user=self.user)
  37. response = self.client.get(
  38. u'{}?sort_by=date&query=is:unresolved'.format(self.path),
  39. format='json',
  40. )
  41. assert response.status_code == 200
  42. assert len(response.data) == 1
  43. assert response.data[0]['id'] == six.text_type(group1.id)
  44. def test_invalid_query(self):
  45. now = timezone.now()
  46. self.create_group(
  47. checksum='a' * 32,
  48. last_seen=now - timedelta(seconds=1),
  49. )
  50. self.login_as(user=self.user)
  51. response = self.client.get(
  52. u'{}?sort_by=date&query=timesSeen:>1k'.format(self.path),
  53. format='json',
  54. )
  55. assert response.status_code == 400
  56. assert 'could not' in response.data['detail']
  57. def test_simple_pagination(self):
  58. now = timezone.now().replace(microsecond=0)
  59. group1 = self.create_group(
  60. project=self.project,
  61. last_seen=now - timedelta(seconds=1),
  62. )
  63. self.create_event(
  64. group=group1,
  65. datetime=now - timedelta(seconds=1),
  66. )
  67. group2 = self.create_group(
  68. project=self.project,
  69. last_seen=now,
  70. )
  71. self.create_event(
  72. stacktrace=[['foo.py']],
  73. group=group2,
  74. datetime=now,
  75. )
  76. self.login_as(user=self.user)
  77. response = self.client.get(
  78. u'{}?sort_by=date&limit=1'.format(self.path),
  79. format='json',
  80. )
  81. assert response.status_code == 200
  82. assert len(response.data) == 1
  83. assert response.data[0]['id'] == six.text_type(group2.id)
  84. links = self._parse_links(response['Link'])
  85. assert links['previous']['results'] == 'false'
  86. assert links['next']['results'] == 'true'
  87. response = self.client.get(links['next']['href'], format='json')
  88. assert response.status_code == 200
  89. assert len(response.data) == 1
  90. assert response.data[0]['id'] == six.text_type(group1.id)
  91. links = self._parse_links(response['Link'])
  92. assert links['previous']['results'] == 'true'
  93. assert links['next']['results'] == 'false'
  94. def test_stats_period(self):
  95. # TODO(dcramer): this test really only checks if validation happens
  96. # on statsPeriod
  97. now = timezone.now()
  98. self.create_group(
  99. checksum='a' * 32,
  100. last_seen=now - timedelta(seconds=1),
  101. )
  102. self.create_group(
  103. checksum='b' * 32,
  104. last_seen=now,
  105. )
  106. self.login_as(user=self.user)
  107. response = self.client.get(u'{}?statsPeriod=24h'.format(self.path), format='json')
  108. assert response.status_code == 200
  109. response = self.client.get(u'{}?statsPeriod=14d'.format(self.path), format='json')
  110. assert response.status_code == 200
  111. response = self.client.get(u'{}?statsPeriod='.format(self.path), format='json')
  112. assert response.status_code == 200
  113. response = self.client.get(u'{}?statsPeriod=48h'.format(self.path), format='json')
  114. assert response.status_code == 400
  115. def test_environment(self):
  116. self.create_environment(name='production', organization=self.project.organization)
  117. self.create_event(tags={'environment': 'production'})
  118. self.login_as(user=self.user)
  119. response = self.client.get(self.path + '?environment=production', format='json')
  120. assert response.status_code == 200
  121. response = self.client.get(self.path + '?environment=garbage', format='json')
  122. assert response.status_code == 404
  123. def test_auto_resolved(self):
  124. project = self.project
  125. project.update_option('sentry:resolve_age', 1)
  126. now = timezone.now()
  127. self.create_group(
  128. checksum='a' * 32,
  129. last_seen=now - timedelta(days=1),
  130. )
  131. group2 = self.create_group(
  132. checksum='b' * 32,
  133. last_seen=now,
  134. )
  135. self.login_as(user=self.user)
  136. response = self.client.get(self.path, format='json')
  137. assert response.status_code == 200
  138. assert len(response.data) == 1
  139. assert response.data[0]['id'] == six.text_type(group2.id)
  140. def test_lookup_by_event_id(self):
  141. project = self.project
  142. project.update_option('sentry:resolve_age', 1)
  143. group = self.create_group(checksum='a' * 32)
  144. self.create_group(checksum='b' * 32)
  145. event_id = 'c' * 32
  146. Event.objects.create(project_id=self.project.id, event_id=event_id)
  147. EventMapping.objects.create(
  148. event_id=event_id,
  149. project=group.project,
  150. group=group,
  151. )
  152. self.login_as(user=self.user)
  153. response = self.client.get(u'{}?query={}'.format(self.path, 'c' * 32), format='json')
  154. assert response.status_code == 200
  155. assert len(response.data) == 1
  156. assert response.data[0]['id'] == six.text_type(group.id)
  157. def test_lookup_by_event_id_with_whitespace(self):
  158. project = self.project
  159. project.update_option('sentry:resolve_age', 1)
  160. group = self.create_group(checksum='a' * 32)
  161. self.create_group(checksum='b' * 32)
  162. EventMapping.objects.create(
  163. event_id='c' * 32,
  164. project=group.project,
  165. group=group,
  166. )
  167. self.login_as(user=self.user)
  168. response = self.client.get(
  169. u'{}?query=%20%20{}%20%20'.format(self.path, 'c' * 32), format='json'
  170. )
  171. assert response.status_code == 200
  172. assert len(response.data) == 1
  173. assert response.data[0]['id'] == six.text_type(group.id)
  174. def test_lookup_by_unknown_event_id(self):
  175. project = self.project
  176. project.update_option('sentry:resolve_age', 1)
  177. self.create_group(checksum='a' * 32)
  178. self.create_group(checksum='b' * 32)
  179. self.login_as(user=self.user)
  180. response = self.client.get(u'{}?query={}'.format(self.path, 'c' * 32), format='json')
  181. assert response.status_code == 200
  182. assert len(response.data) == 0
  183. def test_lookup_by_short_id(self):
  184. group = self.group
  185. short_id = group.qualified_short_id
  186. self.login_as(user=self.user)
  187. response = self.client.get(
  188. u'{}?query={}&shortIdLookup=1'.format(
  189. self.path, short_id), format='json')
  190. assert response.status_code == 200
  191. assert len(response.data) == 1
  192. def test_lookup_by_short_id_no_perms(self):
  193. organization = self.create_organization()
  194. project = self.create_project(organization=organization)
  195. project2 = self.create_project(organization=organization)
  196. team = self.create_team(organization=organization)
  197. project2.add_team(team)
  198. group = self.create_group(project=project)
  199. user = self.create_user()
  200. self.create_member(organization=organization, user=user, teams=[team])
  201. short_id = group.qualified_short_id
  202. self.login_as(user=user)
  203. path = reverse(
  204. 'sentry-api-0-organization-group-index',
  205. args=[organization.slug]
  206. )
  207. response = self.client.get(
  208. u'{}?query={}&shortIdLookup=1'.format(
  209. path, short_id), format='json')
  210. assert response.status_code == 200
  211. assert len(response.data) == 0
  212. def test_lookup_by_first_release(self):
  213. self.login_as(self.user)
  214. project = self.project
  215. project2 = self.create_project(name='baz', organization=project.organization)
  216. release = Release.objects.create(organization=project.organization, version='12345')
  217. release.add_project(project)
  218. release.add_project(project2)
  219. group = self.create_group(checksum='a' * 32, project=project, first_release=release)
  220. group2 = self.create_group(checksum='b' * 32, project=project2, first_release=release)
  221. url = '%s?query=%s' % (self.path, quote('first-release:"%s"' % release.version))
  222. response = self.client.get(url, format='json')
  223. issues = json.loads(response.content)
  224. assert response.status_code == 200
  225. assert len(issues) == 2
  226. assert int(issues[0]['id']) == group2.id
  227. assert int(issues[1]['id']) == group.id
  228. def test_lookup_by_release(self):
  229. self.login_as(self.user)
  230. project = self.project
  231. release = Release.objects.create(organization=project.organization, version='12345')
  232. release.add_project(project)
  233. self.create_event(
  234. group_id=self.group.id,
  235. datetime=self.min_ago,
  236. tags={'sentry:release': release.version},
  237. )
  238. url = '%s?query=%s' % (self.path, quote('release:"%s"' % release.version))
  239. response = self.client.get(url, format='json')
  240. issues = json.loads(response.content)
  241. assert response.status_code == 200
  242. assert len(issues) == 1
  243. assert int(issues[0]['id']) == self.group.id
  244. def test_pending_delete_pending_merge_excluded(self):
  245. self.create_group(
  246. checksum='a' * 32,
  247. status=GroupStatus.PENDING_DELETION,
  248. )
  249. group = self.create_group(
  250. checksum='b' * 32,
  251. )
  252. self.create_group(
  253. checksum='c' * 32,
  254. status=GroupStatus.DELETION_IN_PROGRESS,
  255. )
  256. self.create_group(
  257. checksum='d' * 32,
  258. status=GroupStatus.PENDING_MERGE,
  259. )
  260. self.login_as(user=self.user)
  261. response = self.client.get(self.path, format='json')
  262. assert len(response.data) == 1
  263. assert response.data[0]['id'] == six.text_type(group.id)
  264. def test_filters_based_on_retention(self):
  265. self.login_as(user=self.user)
  266. self.create_group(last_seen=timezone.now() - timedelta(days=2))
  267. with self.options({'system.event-retention-days': 1}):
  268. response = self.client.get(self.path)
  269. assert response.status_code == 200, response.content
  270. assert len(response.data) == 0
  271. def test_token_auth(self):
  272. token = ApiToken.objects.create(user=self.user, scope_list=['org:read'])
  273. response = self.client.get(
  274. self.path,
  275. format='json',
  276. HTTP_AUTHORIZATION='Bearer %s' %
  277. token.token)
  278. assert response.status_code == 200, response.content