test_discover_key_transactions.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. from django.core.urlresolvers import reverse
  2. from sentry.discover.models import KeyTransaction, MAX_KEY_TRANSACTIONS
  3. from sentry.utils.samples import load_data
  4. from sentry.testutils import APITestCase, SnubaTestCase
  5. from sentry.testutils.helpers.datetime import iso_format, before_now
  6. class KeyTransactionTest(APITestCase, SnubaTestCase):
  7. def setUp(self):
  8. super().setUp()
  9. self.login_as(user=self.user, superuser=False)
  10. self.org = self.create_organization(owner=self.user, name="foo")
  11. self.project = self.create_project(name="bar", organization=self.org)
  12. def test_save_key_transaction_as_member(self):
  13. user = self.create_user()
  14. self.create_member(user=user, organization=self.org, role="member")
  15. self.login_as(user=user, superuser=False)
  16. data = load_data("transaction")
  17. with self.feature("organizations:performance-view"):
  18. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  19. response = self.client.post(
  20. url + f"?project={self.project.id}", {"transaction": data["transaction"]}
  21. )
  22. assert response.status_code == 201
  23. key_transactions = KeyTransaction.objects.filter(owner=user)
  24. assert len(key_transactions) == 1
  25. def test_save_key_transaction(self):
  26. data = load_data("transaction")
  27. with self.feature("organizations:performance-view"):
  28. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  29. response = self.client.post(
  30. url + f"?project={self.project.id}", {"transaction": data["transaction"]}
  31. )
  32. assert response.status_code == 201
  33. key_transactions = KeyTransaction.objects.filter(owner=self.user)
  34. assert len(key_transactions) == 1
  35. key_transaction = key_transactions.first()
  36. assert key_transaction.transaction == data["transaction"]
  37. assert key_transaction.organization == self.org
  38. def test_multiple_user_save(self):
  39. data = load_data("transaction")
  40. with self.feature("organizations:performance-view"):
  41. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  42. response = self.client.post(
  43. url + f"?project={self.project.id}", {"transaction": data["transaction"]}
  44. )
  45. user = self.create_user()
  46. self.create_member(user=user, organization=self.org, role="member")
  47. self.login_as(user=user, superuser=False)
  48. with self.feature("organizations:performance-view"):
  49. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  50. response = self.client.post(
  51. url + f"?project={self.project.id}", {"transaction": data["transaction"]}
  52. )
  53. assert response.status_code == 201
  54. key_transactions = KeyTransaction.objects.filter(transaction=data["transaction"])
  55. assert len(key_transactions) == 2
  56. def test_duplicate_key_transaction(self):
  57. data = load_data("transaction")
  58. with self.feature("organizations:performance-view"):
  59. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  60. response = self.client.post(
  61. url + f"?project={self.project.id}", {"transaction": data["transaction"]}
  62. )
  63. assert response.status_code == 201
  64. response = self.client.post(
  65. url + f"?project={self.project.id}", {"transaction": data["transaction"]}
  66. )
  67. assert response.status_code == 204
  68. key_transactions = KeyTransaction.objects.filter(owner=self.user)
  69. assert len(key_transactions) == 1
  70. key_transaction = key_transactions.first()
  71. assert key_transaction.transaction == data["transaction"]
  72. assert key_transaction.organization == self.org
  73. def test_save_with_wrong_project(self):
  74. other_user = self.create_user()
  75. other_org = self.create_organization(owner=other_user)
  76. other_project = self.create_project(organization=other_org)
  77. data = load_data("transaction")
  78. with self.feature("organizations:performance-view"):
  79. url = reverse("sentry-api-0-organization-key-transactions", args=[other_org.slug])
  80. response = self.client.post(
  81. url + f"?project={other_project.id}", {"transaction": data["transaction"]}
  82. )
  83. assert response.status_code == 403
  84. def test_save_with_multiple_projects(self):
  85. other_project = self.create_project(organization=self.org)
  86. data = load_data("transaction")
  87. with self.feature("organizations:performance-view"):
  88. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  89. response = self.client.post(
  90. url + f"?project={other_project.id}&project={self.project.id}",
  91. {"transaction": data["transaction"]},
  92. )
  93. assert response.status_code == 400
  94. assert response.data == {"detail": "Only 1 project per Key Transaction"}
  95. def test_create_with_overly_long_transaction(self):
  96. with self.feature("organizations:performance-view"):
  97. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  98. response = self.client.post(
  99. url + f"?project={self.project.id}", {"transaction": "a" * 500}
  100. )
  101. assert response.status_code == 400
  102. assert response.data == {
  103. "transaction": ["Ensure this field has no more than 200 characters."]
  104. }
  105. def test_max_key_transaction(self):
  106. data = load_data("transaction")
  107. other_project = self.create_project(organization=self.org)
  108. for i in range(MAX_KEY_TRANSACTIONS):
  109. if i % 2 == 0:
  110. project = self.project
  111. else:
  112. project = other_project
  113. KeyTransaction.objects.create(
  114. owner=self.user,
  115. organization=self.org,
  116. transaction=data["transaction"] + str(i),
  117. project=project,
  118. )
  119. with self.feature("organizations:performance-view"):
  120. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  121. response = self.client.post(
  122. url + f"?project={self.project.id}", {"transaction": data["transaction"]}
  123. )
  124. assert response.status_code == 400
  125. assert response.data == {
  126. "non_field_errors": [f"At most {MAX_KEY_TRANSACTIONS} Key Transactions can be added"]
  127. }
  128. def test_is_key_transaction(self):
  129. event_data = load_data("transaction")
  130. start_timestamp = iso_format(before_now(minutes=1))
  131. end_timestamp = iso_format(before_now(minutes=1))
  132. event_data.update({"start_timestamp": start_timestamp, "timestamp": end_timestamp})
  133. KeyTransaction.objects.create(
  134. owner=self.user,
  135. organization=self.org,
  136. transaction=event_data["transaction"],
  137. project=self.project,
  138. )
  139. with self.feature("organizations:performance-view"):
  140. url = reverse("sentry-api-0-organization-is-key-transactions", args=[self.org.slug])
  141. response = self.client.get(
  142. url, {"project": [self.project.id], "transaction": event_data["transaction"]}
  143. )
  144. assert response.status_code == 200
  145. assert response.data["isKey"]
  146. def test_is_not_key_transaction(self):
  147. event_data = load_data("transaction")
  148. start_timestamp = iso_format(before_now(minutes=1))
  149. end_timestamp = iso_format(before_now(minutes=1))
  150. event_data.update({"start_timestamp": start_timestamp, "timestamp": end_timestamp})
  151. with self.feature("organizations:performance-view"):
  152. url = reverse("sentry-api-0-organization-is-key-transactions", args=[self.org.slug])
  153. response = self.client.get(
  154. url, {"project": [self.project.id], "transaction": event_data["transaction"]}
  155. )
  156. assert response.status_code == 200
  157. assert not response.data["isKey"]
  158. def test_delete_transaction(self):
  159. event_data = load_data("transaction")
  160. KeyTransaction.objects.create(
  161. owner=self.user,
  162. organization=self.org,
  163. transaction=event_data["transaction"],
  164. project=self.project,
  165. )
  166. with self.feature("organizations:performance-view"):
  167. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  168. response = self.client.delete(
  169. url + f"?project={self.project.id}",
  170. {"transaction": event_data["transaction"]},
  171. )
  172. assert response.status_code == 204
  173. assert (
  174. KeyTransaction.objects.filter(
  175. owner=self.user,
  176. organization=self.org,
  177. transaction=event_data["transaction"],
  178. project=self.project,
  179. ).count()
  180. == 0
  181. )
  182. def test_delete_transaction_with_another_user(self):
  183. event_data = load_data("transaction")
  184. KeyTransaction.objects.create(
  185. owner=self.user,
  186. organization=self.org,
  187. transaction=event_data["transaction"],
  188. project=self.project,
  189. )
  190. user = self.create_user()
  191. self.create_member(user=user, organization=self.org, role="member")
  192. self.login_as(user=user, superuser=False)
  193. KeyTransaction.objects.create(
  194. owner=user,
  195. organization=self.org,
  196. transaction=event_data["transaction"],
  197. project=self.project,
  198. )
  199. with self.feature("organizations:performance-view"):
  200. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  201. response = self.client.delete(
  202. url + f"?project={self.project.id}",
  203. {"transaction": event_data["transaction"]},
  204. )
  205. assert response.status_code == 204
  206. # Original user still has a key transaction
  207. assert (
  208. KeyTransaction.objects.filter(
  209. owner=self.user,
  210. organization=self.org,
  211. transaction=event_data["transaction"],
  212. project=self.project,
  213. ).count()
  214. == 1
  215. )
  216. # Deleting user has deleted the key transaction
  217. assert (
  218. KeyTransaction.objects.filter(
  219. owner=user,
  220. organization=self.org,
  221. transaction=event_data["transaction"],
  222. project=self.project,
  223. ).count()
  224. == 0
  225. )
  226. def test_delete_key_transaction_as_member(self):
  227. user = self.create_user()
  228. self.create_member(user=user, organization=self.org, role="member")
  229. self.login_as(user=user, superuser=False)
  230. event_data = load_data("transaction")
  231. KeyTransaction.objects.create(
  232. owner=user,
  233. organization=self.org,
  234. transaction=event_data["transaction"],
  235. project=self.project,
  236. )
  237. with self.feature("organizations:performance-view"):
  238. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  239. response = self.client.delete(
  240. url + f"?project={self.project.id}",
  241. {"transaction": event_data["transaction"]},
  242. )
  243. assert response.status_code == 204
  244. key_transactions = KeyTransaction.objects.filter(owner=user)
  245. assert len(key_transactions) == 0
  246. def test_delete_nonexistent_transaction(self):
  247. event_data = load_data("transaction")
  248. with self.feature("organizations:performance-view"):
  249. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  250. response = self.client.delete(
  251. url + f"?project={self.project.id}",
  252. {"transaction": event_data["transaction"]},
  253. )
  254. assert response.status_code == 204
  255. def test_delete_with_multiple_projects(self):
  256. other_user = self.create_user()
  257. other_org = self.create_organization(owner=other_user)
  258. other_project = self.create_project(organization=other_org)
  259. data = load_data("transaction")
  260. with self.feature("organizations:performance-view"):
  261. url = reverse("sentry-api-0-organization-key-transactions", args=[other_org.slug])
  262. response = self.client.delete(
  263. url + f"?project={other_project.id}&project={self.project.id}",
  264. {"transaction": data["transaction"]},
  265. )
  266. assert response.status_code == 403
  267. def test_create_after_deleting_tenth_transaction(self):
  268. data = load_data("transaction")
  269. for i in range(MAX_KEY_TRANSACTIONS):
  270. KeyTransaction.objects.create(
  271. owner=self.user,
  272. organization=self.org,
  273. transaction=data["transaction"] + str(i),
  274. project=self.project,
  275. )
  276. with self.feature("organizations:performance-view"):
  277. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  278. response = self.client.delete(
  279. url + f"?project={self.project.id}",
  280. {"transaction": data["transaction"] + "0"},
  281. )
  282. assert response.status_code == 204
  283. response = self.client.post(
  284. url + f"?project={self.project.id}", {"transaction": data["transaction"]}
  285. )
  286. assert response.status_code == 201
  287. def test_delete_with_wrong_project(self):
  288. data = load_data("transaction")
  289. other_user = self.create_user()
  290. other_org = self.create_organization(owner=other_user)
  291. other_project = self.create_project(organization=other_org)
  292. KeyTransaction.objects.create(
  293. owner=other_user,
  294. organization=other_org,
  295. transaction=data["transaction"],
  296. project=other_project,
  297. )
  298. with self.feature("organizations:performance-view"):
  299. url = reverse("sentry-api-0-organization-key-transactions", args=[other_org.slug])
  300. response = self.client.delete(
  301. url + f"?project={other_project.id}", {"transaction": data["transaction"]}
  302. )
  303. assert response.status_code == 403
  304. def test_key_transactions_without_feature(self):
  305. url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  306. functions = [self.client.post, self.client.delete]
  307. for function in functions:
  308. response = function(url)
  309. assert response.status_code == 404
  310. url = reverse("sentry-api-0-organization-is-key-transactions", args=[self.org.slug])
  311. response = self.client.get(url)
  312. assert response.status_code == 404