test_plugin.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import responses
  2. from django.contrib.auth.models import AnonymousUser
  3. from django.test import RequestFactory
  4. from django.urls import reverse
  5. from exam import fixture
  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. @fixture
  186. def plugin(self):
  187. return JiraPlugin()
  188. @fixture
  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 (
  269. self.plugin._get_formatted_user(
  270. {
  271. "name": "robot",
  272. "avatarUrls": {
  273. "16x16": "https://avatar-cdn.atlassian.com/someid",
  274. "24x24": "https://avatar-cdn.atlassian.com/someotherid",
  275. },
  276. "self": "https://something.atlassian.net/rest/api/2/user?username=someaddon",
  277. }
  278. )
  279. == {"id": "robot", "text": "robot (robot)"}
  280. )