test_plugin.py 13 KB

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