test_plugin.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. from datetime import datetime, timedelta
  2. from functools import cached_property
  3. from urllib.parse import parse_qsl, urlparse
  4. import pytest
  5. import responses
  6. from django.test import RequestFactory
  7. from freezegun import freeze_time
  8. from sentry.integrations.request_buffer import IntegrationRequestBuffer
  9. from sentry.testutils.cases import PluginTestCase
  10. from sentry.testutils.helpers import with_feature
  11. from sentry.utils import json
  12. from sentry_plugins.trello.plugin import TrelloPlugin
  13. class TrelloPluginTestBase(PluginTestCase):
  14. @cached_property
  15. def plugin(self):
  16. return TrelloPlugin()
  17. @cached_property
  18. def request(self):
  19. return RequestFactory()
  20. class TrelloPluginTest(TrelloPluginTestBase):
  21. def test_conf_key(self):
  22. assert self.plugin.conf_key == "trello"
  23. def test_entry_point(self):
  24. self.assertPluginInstalled("trello", self.plugin)
  25. def test_get_issue_label(self):
  26. group = self.create_group(message="Hello world", culprit="foo.bar")
  27. # test new and old format
  28. assert self.plugin.get_issue_label(group, "rPPDb") == "Trello-rPPDb"
  29. assert (
  30. self.plugin.get_issue_label(group, "5dafd/https://trello.com/c/rPPDb/75-title")
  31. == "Trello-5dafd"
  32. )
  33. def test_get_issue_url(self):
  34. group = self.create_group(message="Hello world", culprit="foo.bar")
  35. # test new and old format
  36. assert self.plugin.get_issue_url(group, "rPPDb") == "https://trello.com/c/rPPDb"
  37. assert self.plugin.get_issue_url(group, {"id": "rPPDb"}) == "https://trello.com/c/rPPDb"
  38. assert (
  39. self.plugin.get_issue_url(group, "5dafd/https://trello.com/c/rPPDb/75-title")
  40. == "https://trello.com/c/rPPDb/75-title"
  41. )
  42. def test_is_configured(self):
  43. assert self.plugin.is_configured(None, self.project) is False
  44. self.plugin.set_option("token", "7c8951d1", self.project)
  45. assert self.plugin.is_configured(None, self.project) is False
  46. self.plugin.set_option("key", "39g", self.project)
  47. assert self.plugin.is_configured(None, self.project) is True
  48. class TrelloPluginApiTests(TrelloPluginTestBase):
  49. def setUp(self):
  50. self.group = self.create_group(message="Hello world", culprit="foo.bar")
  51. self.plugin.set_option("token", "7c8951d1", self.project)
  52. self.plugin.set_option("key", "39g", self.project)
  53. self.plugin.set_option("organization", "f187", self.project)
  54. self.login_as(self.user)
  55. def test_get_config_no_org(self):
  56. self.plugin.unset_option("organization", self.project)
  57. out = self.plugin.get_config(self.project)
  58. assert out == [
  59. {
  60. "default": "39g",
  61. "required": True,
  62. "type": "text",
  63. "name": "key",
  64. "label": "Trello API Key",
  65. },
  66. {
  67. "name": "token",
  68. "default": None,
  69. "required": False,
  70. "label": "Trello API Token",
  71. "prefix": "7c895",
  72. "type": "secret",
  73. "has_saved_value": True,
  74. },
  75. ]
  76. @responses.activate
  77. def test_get_config_include_additional(self):
  78. self.plugin.unset_option("organization", self.project)
  79. responses.add(
  80. responses.GET,
  81. "https://api.trello.com/1/members/me/organizations",
  82. json=[{"name": "team 1", "id": "2d8e"}, {"name": "team 2", "id": "d0cc"}],
  83. )
  84. out = self.plugin.get_config(self.project, add_additial_fields=True)
  85. assert out == [
  86. {
  87. "default": "39g",
  88. "required": True,
  89. "type": "text",
  90. "name": "key",
  91. "label": "Trello API Key",
  92. },
  93. {
  94. "name": "token",
  95. "default": None,
  96. "required": False,
  97. "label": "Trello API Token",
  98. "prefix": "7c895",
  99. "type": "secret",
  100. "has_saved_value": True,
  101. },
  102. {
  103. "name": "organization",
  104. "default": None,
  105. "required": False,
  106. "choices": [("2d8e", "team 1"), ("d0cc", "team 2")],
  107. "label": "Trello Organization",
  108. "type": "select",
  109. },
  110. ]
  111. @responses.activate
  112. def test_create_issue(self):
  113. responses.add(responses.POST, "https://api.trello.com/1/cards", json={"shortLink": "rds43"})
  114. form_data = {
  115. "title": "Hello",
  116. "description": "Fix this.",
  117. "board": "ads23f",
  118. "list": "23tds",
  119. }
  120. request = self.make_request(user=self.user, method="POST")
  121. assert self.plugin.create_issue(request, self.group, form_data) == "rds43"
  122. responses_request = responses.calls[0].request
  123. assert responses_request.url == "https://api.trello.com/1/cards?token=7c8951d1&key=39g"
  124. payload = json.loads(responses_request.body)
  125. assert payload == {"name": "Hello", "desc": "Fix this.", "idList": "23tds"}
  126. @responses.activate
  127. def test_link_issue(self):
  128. responses.add(
  129. responses.GET,
  130. "https://api.trello.com/1/cards/SstgnBIQ",
  131. json={"idShort": 2, "name": "MyTitle", "shortLink": "SstgnBIQ"},
  132. )
  133. responses.add(
  134. responses.POST, "https://api.trello.com/1/cards/SstgnBIQ/actions/comments", json={}
  135. )
  136. form_data = {"comment": "please fix this", "issue_id": "SstgnBIQ"}
  137. request = self.make_request(user=self.user, method="POST")
  138. assert self.plugin.link_issue(request, self.group, form_data) == {
  139. "title": "MyTitle",
  140. "id": "SstgnBIQ",
  141. }
  142. responses_request = responses.calls[0].request
  143. assert (
  144. responses_request.url
  145. == "https://api.trello.com/1/cards/SstgnBIQ?fields=name%2CshortLink%2CidShort&token=7c8951d1&key=39g"
  146. )
  147. responses_request = responses.calls[1].request
  148. assert (
  149. responses_request.url
  150. == "https://api.trello.com/1/cards/SstgnBIQ/actions/comments?text=please+fix+this&token=7c8951d1&key=39g"
  151. )
  152. @responses.activate
  153. def test_view_options(self):
  154. responses.add(
  155. responses.GET,
  156. "https://api.trello.com/1/boards/f34/lists",
  157. json=[{"id": "8f3", "name": "list 1"}, {"id": "j8f", "name": "list 2"}],
  158. )
  159. request = self.make_request(
  160. user=self.user, method="GET", GET={"option_field": "list", "board": "f34"}
  161. )
  162. response = self.plugin.view_options(request, self.group)
  163. assert response.data == {"list": [("8f3", "list 1"), ("j8f", "list 2")]}
  164. responses_request = responses.calls[0].request
  165. assert (
  166. responses_request.url
  167. == "https://api.trello.com/1/boards/f34/lists?token=7c8951d1&key=39g"
  168. )
  169. @responses.activate
  170. def test_view_autocomplete(self):
  171. responses.add(
  172. responses.GET,
  173. "https://api.trello.com/1/search",
  174. json={
  175. "cards": [
  176. {"id": "4fsdafad", "name": "KeyError", "idShort": 1, "shortLink": "0lr"},
  177. {"id": "f4usdfa", "name": "Key Missing", "idShort": 3, "shortLink": "9lf"},
  178. ]
  179. },
  180. )
  181. request = self.make_request(
  182. user=self.user,
  183. method="GET",
  184. GET={"autocomplete_field": "issue_id", "autocomplete_query": "Key"},
  185. )
  186. response = self.plugin.view_autocomplete(request, self.group)
  187. assert response.data == {
  188. "issue_id": [
  189. {"id": "0lr", "text": "(#1) KeyError"},
  190. {"id": "9lf", "text": "(#3) Key Missing"},
  191. ]
  192. }
  193. responses_request = responses.calls[0].request
  194. url = urlparse(responses_request.url)
  195. query = dict(parse_qsl(url.query))
  196. assert url.path == "/1/search"
  197. assert query == {
  198. "cards_limit": "100",
  199. "partial": "true",
  200. "modelTypes": "cards",
  201. "token": "7c8951d1",
  202. "card_fields": "name,shortLink,idShort",
  203. "key": "39g",
  204. "query": "Key",
  205. "idOrganizations": "f187",
  206. }
  207. @responses.activate
  208. def test_view_autocomplete_no_org(self):
  209. self.plugin.unset_option("organization", self.project)
  210. responses.add(
  211. responses.GET,
  212. "https://api.trello.com/1/search",
  213. json={
  214. "cards": [
  215. {"id": "4fsdafad", "name": "KeyError", "idShort": 1, "shortLink": "0lr"},
  216. {"id": "f4usdfa", "name": "Key Missing", "idShort": 3, "shortLink": "9lf"},
  217. ]
  218. },
  219. )
  220. request = self.make_request(
  221. user=self.user,
  222. method="GET",
  223. GET={"autocomplete_field": "issue_id", "autocomplete_query": "Key"},
  224. )
  225. response = self.plugin.view_autocomplete(request, self.group)
  226. assert response.data == {
  227. "issue_id": [
  228. {"id": "0lr", "text": "(#1) KeyError"},
  229. {"id": "9lf", "text": "(#3) Key Missing"},
  230. ]
  231. }
  232. responses_request = responses.calls[0].request
  233. url = urlparse(responses_request.url)
  234. query = dict(parse_qsl(url.query))
  235. assert url.path == "/1/search"
  236. assert query == {
  237. "cards_limit": "100",
  238. "partial": "true",
  239. "modelTypes": "cards",
  240. "token": "7c8951d1",
  241. "card_fields": "name,shortLink,idShort",
  242. "key": "39g",
  243. "query": "Key",
  244. }
  245. @responses.activate
  246. def test_error_recorded(self):
  247. self.plugin.unset_option("organization", self.project)
  248. responses.add(responses.GET, "https://api.trello.com/1/search", status=401)
  249. request = self.make_request(
  250. user=self.user,
  251. method="GET",
  252. GET={"autocomplete_field": "issue_id", "autocomplete_query": "Key"},
  253. )
  254. with pytest.raises(Exception):
  255. self.plugin.view_autocomplete(request, self.group)
  256. buffer = IntegrationRequestBuffer(self.plugin.get_client(self.project)._get_redis_key())
  257. assert int(buffer._get_all_from_buffer()[0]["error_count"]) == 1
  258. @responses.activate
  259. @with_feature("organizations:plugin-disable-on-broken")
  260. @freeze_time("2022-01-01 03:30:00")
  261. def test_plugin_disabled(self):
  262. self.plugin.unset_option("organization", self.project)
  263. responses.add(responses.GET, "https://api.trello.com/1/search", status=401)
  264. request = self.make_request(
  265. user=self.user,
  266. method="GET",
  267. GET={"autocomplete_field": "issue_id", "autocomplete_query": "Key"},
  268. )
  269. buffer = IntegrationRequestBuffer(self.plugin.get_client(self.project)._get_redis_key())
  270. now = datetime.now() - timedelta(hours=1)
  271. for i in reversed(range(10)):
  272. with freeze_time(now - timedelta(days=i)):
  273. buffer.record_error()
  274. assert buffer.is_integration_broken() is True
  275. with pytest.raises(Exception):
  276. self.plugin.view_autocomplete(request, self.group)
  277. assert self.plugin.is_enabled(self.project) is False