test_plugin.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. from functools import cached_property
  2. import responses
  3. from django.contrib.auth.models import AnonymousUser
  4. from django.test import RequestFactory
  5. from django.urls import reverse
  6. from sentry.testutils import TestCase
  7. from sentry.utils import json
  8. from sentry_plugins.jira.plugin import JiraPlugin
  9. create_meta_response = {
  10. "expand": "projects",
  11. "projects": [
  12. {
  13. "expand": "issuetypes",
  14. "self": "https://getsentry.atlassian.net/rest/api/2/project/10000",
  15. "id": "10000",
  16. "key": "SEN",
  17. "name": "Sentry",
  18. "avatarUrls": {
  19. "48x48": "https://getsentry.atlassian.net/secure/projectavatar?avatarId=10324",
  20. "24x24": "https://getsentry.atlassian.net/secure/projectavatar?size=small&avatarId=10324",
  21. "16x16": "https://getsentry.atlassian.net/secure/projectavatar?size=xsmall&avatarId=10324",
  22. "32x32": "https://getsentry.atlassian.net/secure/projectavatar?size=medium&avatarId=10324",
  23. },
  24. "issuetypes": [
  25. {
  26. "self": "https://getsentry.atlassian.net/rest/api/2/issuetype/10002",
  27. "id": "10002",
  28. "description": "A task that needs to be done.",
  29. "iconUrl": "https://getsentry.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10318&avatarType=issuetype",
  30. "name": "Task",
  31. "subtask": False,
  32. "expand": "fields",
  33. "fields": {
  34. "summary": {
  35. "required": True,
  36. "schema": {"type": "string", "system": "summary"},
  37. "name": "Summary",
  38. "hasDefaultValue": False,
  39. "operations": ["set"],
  40. },
  41. "issuetype": {
  42. "required": True,
  43. "schema": {"type": "issuetype", "system": "issuetype"},
  44. "name": "Issue Type",
  45. "hasDefaultValue": False,
  46. "operations": [],
  47. "allowedValues": [
  48. {
  49. "self": "https://getsentry.atlassian.net/rest/api/2/issuetype/10002",
  50. "id": "10002",
  51. "description": "A task that needs to be done.",
  52. "iconUrl": "https://getsentry.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10318&avatarType=issuetype",
  53. "name": "Task",
  54. "subtask": False,
  55. "avatarId": 10318,
  56. }
  57. ],
  58. },
  59. "components": {
  60. "required": False,
  61. "schema": {
  62. "type": "array",
  63. "items": "component",
  64. "system": "components",
  65. },
  66. "name": "Component/s",
  67. "hasDefaultValue": False,
  68. "operations": ["add", "set", "remove"],
  69. "allowedValues": [],
  70. },
  71. "description": {
  72. "required": False,
  73. "schema": {"type": "string", "system": "description"},
  74. "name": "Description",
  75. "hasDefaultValue": False,
  76. "operations": ["set"],
  77. },
  78. "project": {
  79. "required": True,
  80. "schema": {"type": "project", "system": "project"},
  81. "name": "Project",
  82. "hasDefaultValue": False,
  83. "operations": ["set"],
  84. "allowedValues": [
  85. {
  86. "self": "https://getsentry.atlassian.net/rest/api/2/project/10000",
  87. "id": "10000",
  88. "key": "SEN",
  89. "name": "Sentry",
  90. "avatarUrls": {
  91. "48x48": "https://getsentry.atlassian.net/secure/projectavatar?avatarId=10324",
  92. "24x24": "https://getsentry.atlassian.net/secure/projectavatar?size=small&avatarId=10324",
  93. "16x16": "https://getsentry.atlassian.net/secure/projectavatar?size=xsmall&avatarId=10324",
  94. "32x32": "https://getsentry.atlassian.net/secure/projectavatar?size=medium&avatarId=10324",
  95. },
  96. }
  97. ],
  98. },
  99. "reporter": {
  100. "required": True,
  101. "schema": {"type": "user", "system": "reporter"},
  102. "name": "Reporter",
  103. "autoCompleteUrl": "https://getsentry.atlassian.net/rest/api/latest/user/search?username=",
  104. "hasDefaultValue": False,
  105. "operations": ["set"],
  106. },
  107. "fixVersions": {
  108. "required": False,
  109. "schema": {
  110. "type": "array",
  111. "items": "version",
  112. "system": "fixVersions",
  113. },
  114. "name": "Fix Version/s",
  115. "hasDefaultValue": False,
  116. "operations": ["set", "add", "remove"],
  117. "allowedValues": [],
  118. },
  119. "priority": {
  120. "required": False,
  121. "schema": {"type": "priority", "system": "priority"},
  122. "name": "Priority",
  123. "hasDefaultValue": True,
  124. "operations": ["set"],
  125. "allowedValues": [
  126. {
  127. "self": "https://getsentry.atlassian.net/rest/api/2/priority/1",
  128. "iconUrl": "https://getsentry.atlassian.net/images/icons/priorities/highest.svg",
  129. "name": "Highest",
  130. "id": "1",
  131. }
  132. ],
  133. },
  134. "customfield_10003": {
  135. "required": False,
  136. "schema": {
  137. "type": "array",
  138. "items": "string",
  139. "custom": "com.pyxis.greenhopper.jira:gh-sprint",
  140. "customId": 10003,
  141. },
  142. "name": "Sprint",
  143. "hasDefaultValue": False,
  144. "operations": ["set"],
  145. },
  146. "labels": {
  147. "required": False,
  148. "schema": {"type": "array", "items": "string", "system": "labels"},
  149. "name": "Labels",
  150. "autoCompleteUrl": "https://getsentry.atlassian.net/rest/api/1.0/labels/suggest?query=",
  151. "hasDefaultValue": False,
  152. "operations": ["add", "set", "remove"],
  153. },
  154. "attachment": {
  155. "required": False,
  156. "schema": {
  157. "type": "array",
  158. "items": "attachment",
  159. "system": "attachment",
  160. },
  161. "name": "Attachment",
  162. "hasDefaultValue": False,
  163. "operations": [],
  164. },
  165. "assignee": {
  166. "required": False,
  167. "schema": {"type": "user", "system": "assignee"},
  168. "name": "Assignee",
  169. "autoCompleteUrl": "https://getsentry.atlassian.net/rest/api/latest/user/assignable/search?issueKey=null&username=",
  170. "hasDefaultValue": False,
  171. "operations": ["set"],
  172. },
  173. },
  174. }
  175. ],
  176. }
  177. ],
  178. }
  179. issue_response = {
  180. "key": "SEN-19",
  181. "id": "10708",
  182. "fields": {"summary": "TypeError: 'set' object has no attribute '__getitem__'"},
  183. }
  184. class JiraPluginTest(TestCase):
  185. @cached_property
  186. def plugin(self):
  187. return JiraPlugin()
  188. @cached_property
  189. def request(self):
  190. return RequestFactory()
  191. def test_conf_key(self):
  192. assert self.plugin.conf_key == "jira"
  193. def test_get_issue_label(self):
  194. group = self.create_group(message="Hello world", culprit="foo.bar")
  195. assert self.plugin.get_issue_label(group, "SEN-1") == "SEN-1"
  196. def test_get_issue_url(self):
  197. self.plugin.set_option("instance_url", "https://getsentry.atlassian.net", self.project)
  198. group = self.create_group(message="Hello world", culprit="foo.bar")
  199. assert (
  200. self.plugin.get_issue_url(group, "SEN-1")
  201. == "https://getsentry.atlassian.net/browse/SEN-1"
  202. )
  203. def test_is_configured(self):
  204. assert self.plugin.is_configured(None, self.project) is False
  205. self.plugin.set_option("default_project", "SEN", self.project)
  206. assert self.plugin.is_configured(None, self.project) is True
  207. @responses.activate
  208. def test_create_issue(self):
  209. responses.add(
  210. responses.GET,
  211. "https://getsentry.atlassian.net/rest/api/2/issue/createmeta",
  212. json=create_meta_response,
  213. )
  214. responses.add(
  215. responses.POST,
  216. "https://getsentry.atlassian.net/rest/api/2/issue",
  217. json={"key": "SEN-1"},
  218. )
  219. self.plugin.set_option("instance_url", "https://getsentry.atlassian.net", self.project)
  220. group = self.create_group(message="Hello world", culprit="foo.bar")
  221. request = self.request.get("/")
  222. request.user = AnonymousUser()
  223. form_data = {
  224. "title": "Hello",
  225. "description": "Fix this.",
  226. "issuetype": "bug",
  227. "project": "SEN",
  228. }
  229. assert self.plugin.create_issue(request, group, form_data) == "SEN-1"
  230. @responses.activate
  231. def test_link_issue(self):
  232. responses.add(
  233. responses.GET,
  234. "https://getsentry.atlassian.net/rest/api/2/issue/SEN-19",
  235. json=issue_response,
  236. )
  237. self.plugin.set_option("instance_url", "https://getsentry.atlassian.net", self.project)
  238. group = self.create_group(message="Hello world", culprit="foo.bar")
  239. request = self.request.get("/")
  240. request.user = AnonymousUser()
  241. form_data = {"issue_id": "SEN-19"}
  242. assert (
  243. self.plugin.link_issue(request, group, form_data)["title"]
  244. == issue_response["fields"]["summary"]
  245. )
  246. def test_no_secrets(self):
  247. self.user = self.create_user("foo@example.com")
  248. self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
  249. self.team = self.create_team(organization=self.org, name="Mariachi Band")
  250. self.project = self.create_project(organization=self.org, teams=[self.team], name="Bengal")
  251. self.login_as(self.user)
  252. self.plugin.set_option("password", "abcdef", self.project)
  253. url = reverse(
  254. "sentry-api-0-project-plugin-details", args=[self.org.slug, self.project.slug, "jira"]
  255. )
  256. res = self.client.get(url)
  257. config = json.loads(res.content)["config"]
  258. password_config = [item for item in config if item["name"] == "password"][0]
  259. assert password_config.get("type") == "secret"
  260. assert password_config.get("value") is None
  261. assert password_config.get("hasSavedValue") is True
  262. assert password_config.get("prefix") == ""
  263. def test_get_formatted_user(self):
  264. assert self.plugin._get_formatted_user(
  265. {"displayName": "Foo Bar", "emailAddress": "foo@sentry.io", "name": "foobar"}
  266. ) == {"text": "Foo Bar - foo@sentry.io (foobar)", "id": "foobar"}
  267. # test weird addon users that don't have email addresses
  268. assert self.plugin._get_formatted_user(
  269. {
  270. "name": "robot",
  271. "avatarUrls": {
  272. "16x16": "https://avatar-cdn.atlassian.com/someid",
  273. "24x24": "https://avatar-cdn.atlassian.com/someotherid",
  274. },
  275. "self": "https://something.atlassian.net/rest/api/2/user?username=someaddon",
  276. }
  277. ) == {"id": "robot", "text": "robot (robot)"}