vsts.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. from __future__ import annotations
  2. from typing import Any
  3. from urllib.parse import parse_qs, urlencode, urlparse
  4. import pytest
  5. import responses
  6. from sentry.integrations.vsts import VstsIntegrationProvider
  7. from sentry.silo.base import SiloMode
  8. from sentry.testutils.cases import IntegrationTestCase
  9. from sentry.testutils.silo import assume_test_silo_mode
  10. class VstsIntegrationTestCase(IntegrationTestCase):
  11. provider = VstsIntegrationProvider()
  12. @pytest.fixture(autouse=True)
  13. def setup_data(self):
  14. self.access_token = "9d646e20-7a62-4bcc-abc0-cb2d4d075e36"
  15. self.refresh_token = "32004633-a3c0-4616-9aa0-a40632adac77"
  16. self.vsts_account_id = "c8a585ae-b61f-4ba6-833c-9e8d5d1674d8"
  17. self.vsts_account_name = "MyVSTSAccount"
  18. self.vsts_account_uri = "https://MyVSTSAccount.vssps.visualstudio.com:443/"
  19. self.vsts_base_url = "https://MyVSTSAccount.visualstudio.com/"
  20. self.vsts_user_id = "d6245f20-2af8-44f4-9451-8107cb2767db"
  21. self.vsts_user_name = "Foo Bar"
  22. self.vsts_user_email = "foobar@example.com"
  23. self.repo_id = "47166099-3e16-4868-9137-22ac6b05b06e"
  24. self.repo_name = "cool-service"
  25. self.project_a = {"id": "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", "name": "ProjectA"}
  26. self.project_b = {"id": "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", "name": "ProjectB"}
  27. with responses.mock:
  28. self._stub_vsts()
  29. yield
  30. def _stub_vsts(self):
  31. responses.reset()
  32. responses.add(
  33. responses.POST,
  34. "https://app.vssps.visualstudio.com/oauth2/token",
  35. json={
  36. "access_token": self.access_token,
  37. "token_type": "grant",
  38. "expires_in": 300, # seconds (5 min)
  39. "refresh_token": self.refresh_token,
  40. },
  41. )
  42. responses.add(
  43. responses.GET,
  44. "https://app.vssps.visualstudio.com/_apis/accounts?memberId=%s&api-version=4.1"
  45. % self.vsts_user_id,
  46. json={
  47. "count": 1,
  48. "value": [
  49. {
  50. "accountId": self.vsts_account_id,
  51. "accountUri": self.vsts_account_uri,
  52. "accountName": self.vsts_account_name,
  53. "properties": {},
  54. }
  55. ],
  56. },
  57. )
  58. responses.add(
  59. responses.GET,
  60. "https://app.vssps.visualstudio.com/_apis/resourceareas/79134C72-4A58-4B42-976C-04E7115F32BF?hostId=%s&api-preview=5.0-preview.1"
  61. % self.vsts_account_id,
  62. json={"locationUrl": self.vsts_base_url},
  63. )
  64. responses.add(
  65. responses.GET,
  66. "https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=1.0",
  67. json={
  68. "id": self.vsts_user_id,
  69. "displayName": self.vsts_user_name,
  70. "emailAddress": self.vsts_user_email,
  71. },
  72. )
  73. responses.add(
  74. responses.GET,
  75. "https://app.vssps.visualstudio.com/_apis/connectionData/",
  76. json={"authenticatedUser": {"subjectDescriptor": self.vsts_account_id}},
  77. )
  78. responses.add(
  79. responses.GET,
  80. f"https://{self.vsts_account_name.lower()}.visualstudio.com/_apis/projects",
  81. json={"value": [self.project_a, self.project_b], "count": 2},
  82. )
  83. responses.add(
  84. responses.POST,
  85. f"https://{self.vsts_account_name.lower()}.visualstudio.com/_apis/hooks/subscriptions",
  86. json=CREATE_SUBSCRIPTION,
  87. )
  88. responses.add(
  89. responses.GET,
  90. f"https://{self.vsts_account_name.lower()}.visualstudio.com/_apis/git/repositories",
  91. json={
  92. "value": [
  93. {
  94. "id": self.repo_id,
  95. "name": self.repo_name,
  96. "project": {"name": self.project_a["name"]},
  97. }
  98. ]
  99. },
  100. )
  101. responses.add(
  102. responses.GET,
  103. f"https://{self.vsts_account_name.lower()}.visualstudio.com/ProjectA/_apis/git/repositories/ProjectA",
  104. json={
  105. "repository": {
  106. "id": self.repo_id,
  107. "name": self.repo_name,
  108. "project": {"name": self.project_a["name"]},
  109. }
  110. },
  111. )
  112. for project in [self.project_a, self.project_b]:
  113. responses.add(
  114. responses.GET,
  115. "https://{}.visualstudio.com/{}/_apis/wit/workitemtypes/{}/states".format(
  116. self.vsts_account_name.lower(), project["id"], "Bug"
  117. ),
  118. json={
  119. "count": 6,
  120. "value": [
  121. {"name": "resolve_status"},
  122. {"name": "resolve_when"},
  123. {"name": "regression_status"},
  124. {"name": "sync_comments"},
  125. {"name": "sync_forward_assignment"},
  126. {"name": "sync_reverse_assignment"},
  127. ],
  128. },
  129. )
  130. responses.add(
  131. responses.GET,
  132. "https://{}.visualstudio.com/{}/_apis/wit/workitemtypes/{}/states".format(
  133. self.vsts_account_name.lower(), project["id"], "Issue"
  134. ),
  135. json={
  136. "count": 0,
  137. "value": [],
  138. },
  139. )
  140. responses.add(
  141. responses.GET,
  142. "https://{}.visualstudio.com/{}/_apis/wit/workitemtypes/{}/states".format(
  143. self.vsts_account_name.lower(), project["id"], "Task"
  144. ),
  145. json={
  146. "count": 0,
  147. "value": [],
  148. },
  149. )
  150. def make_init_request(self, path=None, body=None):
  151. return self.client.get(path or self.init_path, body or {})
  152. def make_oauth_redirect_request(self, state):
  153. return self.client.get(
  154. "{}?{}".format(self.setup_path, urlencode({"code": "oauth-code", "state": state}))
  155. )
  156. def assert_vsts_oauth_redirect(self, redirect):
  157. assert redirect.scheme == "https"
  158. assert redirect.netloc == "app.vssps.visualstudio.com"
  159. assert redirect.path == "/oauth2/authorize"
  160. def assert_account_selection(self, response, account_id=None):
  161. account_id = account_id or self.vsts_account_id
  162. assert response.status_code == 200
  163. assert f'<option value="{account_id}"'.encode() in response.content
  164. @assume_test_silo_mode(SiloMode.CONTROL)
  165. def assert_installation(self):
  166. # Initial request to the installation URL for VSTS
  167. resp = self.make_init_request()
  168. redirect = urlparse(resp["Location"])
  169. assert resp.status_code == 302
  170. self.assert_vsts_oauth_redirect(redirect)
  171. query = parse_qs(redirect.query)
  172. # OAuth redirect back to Sentry (identity_pipeline_view)
  173. resp = self.make_oauth_redirect_request(query["state"][0])
  174. self.assert_account_selection(resp)
  175. # User choosing which VSTS Account to use (AccountConfigView)
  176. # Final step.
  177. resp = self.client.post(
  178. self.setup_path, {"account": self.vsts_account_id, "provider": "vsts"}
  179. )
  180. return resp
  181. COMPARE_COMMITS_EXAMPLE = b"""
  182. {
  183. "count": 1,
  184. "value": [
  185. {
  186. "commitId": "6c36052c58bde5e57040ebe6bdb9f6a52c906fff",
  187. "author": {
  188. "name": "max bittker",
  189. "email": "max@sentry.io",
  190. "date": "2018-04-24T00:03:18Z"
  191. },
  192. "committer": {
  193. "name": "max bittker",
  194. "email": "max@sentry.io",
  195. "date": "2018-04-24T00:03:18Z"
  196. },
  197. "comment": "Updated README.md",
  198. "commentTruncated": true,
  199. "changeCounts": {"Add": 0, "Edit": 1, "Delete": 0},
  200. "url":
  201. "https://mbittker.visualstudio.com/_apis/git/repositories/b1e25999-c080-4ea1-8c61-597c4ec41f06/commits/6c36052c58bde5e57040ebe6bdb9f6a52c906fff",
  202. "remoteUrl":
  203. "https://mbittker.visualstudio.com/_git/MyFirstProject/commit/6c36052c58bde5e57040ebe6bdb9f6a52c906fff"
  204. }
  205. ]
  206. }
  207. """
  208. COMMIT_DETAILS_EXAMPLE = r"""
  209. {
  210. "_links": {
  211. "changes": {
  212. "href": "https://mbittker.visualstudio.com/_apis/git/repositories/666ffcce-8ffa-46ec-bccf-b93b55bb2320/commits/6c36052c58bde5e57040ebe6bdb9f6a52c906fff/changes"
  213. },
  214. "repository": {
  215. "href": "https://mbittker.visualstudio.com/_apis/git/repositories/666ffcce-8ffa-46ec-bccf-b93b55bb2320"
  216. },
  217. "self": {
  218. "href": "https://mbittker.visualstudio.com/_apis/git/repositories/666ffcce-8ffa-46ec-bccf-b93b55bb2320/commits/6c36052c58bde5e57040ebe6bdb9f6a52c906fff"
  219. },
  220. "web": {
  221. "href": "https://mbittker.visualstudio.com/_git/MyFirstProject/commit/6c36052c58bde5e57040ebe6bdb9f6a52c906fff"
  222. }
  223. },
  224. "author": {
  225. "date": "2018-11-23T15:59:19Z",
  226. "email": "max@sentry.io",
  227. "imageUrl": "https://www.gravatar.com/avatar/1cee8d752bcad4c172d60e56bb398c11?r=g&d=mm",
  228. "name": "max bitker"
  229. },
  230. "comment": "Updated README.md\n\nSecond line\n\nFixes SENTRY-1",
  231. "commitId": "6c36052c58bde5e57040ebe6bdb9f6a52c906fff",
  232. "committer": {
  233. "date": "2018-11-23T15:59:19Z",
  234. "email": "max@sentry.io",
  235. "imageUrl": "https://www.gravatar.com/avatar/1cee8d752bcad4c172d60e56bb398c11?r=g&d=mm",
  236. "name": "max bittker"
  237. },
  238. "parents": [
  239. "641e82ce0ed14f3cf3670b0bf5f669d7fbd40a68"
  240. ],
  241. "push": {
  242. "date": "2018-11-23T16:01:10.7246278Z",
  243. "pushId": 2,
  244. "pushedBy": {
  245. "_links": {
  246. "avatar": {
  247. "href": "https://mbittker.visualstudio.com/_apis/GraphProfile/MemberAvatars/msa.NjI0ZGRhOWMtODgyZC03ZmRhLTk3OWItZTdhMjI5MWMzMzBk"
  248. }
  249. },
  250. "descriptor": "msa.NjI0ZGRhOWMtODgyZC03ZmRhLTk3OWItZTdhMjI5MWMzMzBk",
  251. "displayName": "Mark Story",
  252. "id": "624dda9c-882d-6fda-979b-e7a2291c330d",
  253. "imageUrl": "https://mbittker.visualstudio.com/_api/_common/identityImage?id=624dda9c-882d-6fda-979b-e7a2291c330d",
  254. "uniqueName": "mark@mark-story.com",
  255. "url": "https://mbittker.visualstudio.com/Aa365971d-9897-47eb-becf-c5142d33db08/_apis/Identities/624dda9c-882d-6fda-979b-e7a2291c330d"
  256. }
  257. },
  258. "remoteUrl": "https://mbittker.visualstudio.com/MyFirstProject/_git/box-of-things/commit/6c36052c58bde5e57040ebe6bdb9f6a52c906fff",
  259. "treeId": "026257a5e53eb923497c0217ef76e567f3a60088",
  260. "url": "https://mbittker.visualstudio.com/_apis/git/repositories/666ffcce-8ffa-46ec-bccf-b93b55bb2320/commits/6c36052c58bde5e57040ebe6bdb9f6a52c906fff"
  261. }
  262. """
  263. FILE_CHANGES_EXAMPLE = b"""
  264. {
  265. "changeCounts": {"Edit": 1},
  266. "changes": [
  267. {
  268. "item": {
  269. "objectId": "b48e843656a0a12926a0bcedefe8ef3710fe2867",
  270. "originalObjectId": "270b590a4edf3f19aa7acc7b57379729e34fc681",
  271. "gitObjectType": "blob",
  272. "commitId": "6c36052c58bde5e57040ebe6bdb9f6a52c906fff",
  273. "path": "/README.md",
  274. "url":
  275. "https://mbittker.visualstudio.com/DefaultCollection/_apis/git/repositories/b1e25999-c080-4ea1-8c61-597c4ec41f06/items/README.md?versionType=Commit&version=6c36052c58bde5e57040ebe6bdb9f6a52c906fff"
  276. },
  277. "changeType": "edit"
  278. }
  279. ]
  280. }
  281. """
  282. WORK_ITEM_RESPONSE = """{
  283. "id": 309,
  284. "rev": 1,
  285. "fields": {
  286. "System.AreaPath": "Fabrikam-Fiber-Git",
  287. "System.TeamProject": "Fabrikam-Fiber-Git",
  288. "System.IterationPath": "Fabrikam-Fiber-Git",
  289. "System.WorkItemType": "Product Backlog Item",
  290. "System.State": "New",
  291. "System.Reason": "New backlog item",
  292. "System.CreatedDate": "2015-01-07T18:13:01.807Z",
  293. "System.CreatedBy": "Jamal Hartnett <fabrikamfiber4@hotmail.com>",
  294. "System.ChangedDate": "2015-01-07T18:13:01.807Z",
  295. "System.ChangedBy": "Jamal Hartnett <fabrikamfiber4@hotmail.com>",
  296. "System.Title": "Hello",
  297. "Microsoft.VSTS.Scheduling.Effort": 8,
  298. "WEF_6CB513B6E70E43499D9FC94E5BBFB784_Kanban.Column": "New",
  299. "System.Description": "Fix this."
  300. },
  301. "_links": {
  302. "self": {
  303. "href": "https://fabrikam-fiber-inc.visualstudio.com/DefaultCollection/_apis/wit/workItems/309"
  304. },
  305. "workItemUpdates": {
  306. "href": "https://fabrikam-fiber-inc.visualstudio.com/DefaultCollection/_apis/wit/workItems/309/updates"
  307. },
  308. "workItemRevisions": {
  309. "href": "https://fabrikam-fiber-inc.visualstudio.com/DefaultCollection/_apis/wit/workItems/309/revisions"
  310. },
  311. "workItemHistory": {
  312. "href": "https://fabrikam-fiber-inc.visualstudio.com/DefaultCollection/_apis/wit/workItems/309/history"
  313. },
  314. "html": {
  315. "href": "https://fabrikam-fiber-inc.visualstudio.com/web/wi.aspx?pcguid=d81542e4-cdfa-4333-b082-1ae2d6c3ad16&id=309"
  316. },
  317. "workItemType": {
  318. "href": "https://fabrikam-fiber-inc.visualstudio.com/DefaultCollection/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c/_apis/wit/workItemTypes/Product%20Backlog%20Item"
  319. },
  320. "fields": {
  321. "href": "https://fabrikam-fiber-inc.visualstudio.com/DefaultCollection/_apis/wit/fields"
  322. }
  323. },
  324. "url": "https://fabrikam-fiber-inc.visualstudio.com/DefaultCollection/_apis/wit/workItems/309"
  325. }"""
  326. GET_USERS_RESPONSE = b"""{
  327. "count": 4,
  328. "value": [
  329. {
  330. "subjectKind": "user",
  331. "cuid": "ec09a4d8-d914-4f28-9e39-23d52b683f90",
  332. "domain": "Build",
  333. "principalName": "51ac8d19-6694-459f-a65e-bec30e9e2e33",
  334. "mailAddress": "",
  335. "origin": "vsts",
  336. "originId": "ec09a4d8-d914-4f28-9e39-23d52b683f90",
  337. "displayName": "Project Collection Build Service (Ftottentest2)",
  338. "_links": {
  339. "self": {
  340. "href": "https://fabrikam.vssps.visualstudio.com/_apis/graph/users/TWljcm9zb2Z0LlRlYW1Gb3VuZGF0aW9uLlNlcnZpY2VJZGVudGl0eTtmMzViOTAxNS1jZGU4LTQ4MzQtYTFkNS0wOWU4ZjM1OWNiODU6QnVpbGQ6NTFhYzhkMTktNjY5NC00NTlmLWE2NWUtYmVjMzBlOWUyZTMz"
  341. },
  342. "memberships": {
  343. "href": "https://fabrikam.vssps.visualstudio.com/_apis/graph/memberships/TWljcm9zb2Z0LlRlYW1Gb3VuZGF0aW9uLlNlcnZpY2VJZGVudGl0eTtmMzViOTAxNS1jZGU4LTQ4MzQtYTFkNS0wOWU4ZjM1OWNiODU6QnVpbGQ6NTFhYzhkMTktNjY5NC00NTlmLWE2NWUtYmVjMzBlOWUyZTMz"
  344. }
  345. },
  346. "url": "https://fabrikam.vssps.visualstudio.com/_apis/graph/users/TWljcm9zb2Z0LlRlYW1Gb3VuZGF0aW9uLlNlcnZpY2VJZGVudGl0eTtmMzViOTAxNS1jZGU4LTQ4MzQtYTFkNS0wOWU4ZjM1OWNiODU6QnVpbGQ6NTFhYzhkMTktNjY5NC00NTlmLWE2NWUtYmVjMzBlOWUyZTMz",
  347. "descriptor": "TWljcm9zb2Z0LlRlYW1Gb3VuZGF0aW9uLlNlcnZpY2VJZGVudGl0eTtmMzViOTAxNS1jZGU4LTQ4MzQtYTFkNS0wOWU4ZjM1OWNiODU6QnVpbGQ6NTFhYzhkMTktNjY5NC00NTlmLWE2NWUtYmVjMzBlOWUyZTMz"
  348. },
  349. {
  350. "subjectKind": "user",
  351. "metaType": "member",
  352. "cuid": "00ca946b-2fe9-4f2a-ae2f-40d5c48001bc",
  353. "domain": "LOCAL AUTHORITY",
  354. "principalName": "TeamFoundationService (TEAM FOUNDATION)",
  355. "mailAddress": "",
  356. "origin": "vsts",
  357. "originId": "00ca946b-2fe9-4f2a-ae2f-40d5c48001bc",
  358. "displayName": "TeamFoundationService (TEAM FOUNDATION)",
  359. "_links": {
  360. "self": {
  361. "href": "https://fabrikam.vssps.visualstudio.com/_apis/graph/users/TWljcm9zb2Z0LklkZW50aXR5TW9kZWwuQ2xhaW1zLkNsYWltc0lkZW50aXR5Ozc3ODlmMDlkLWUwNTMtNGYyZS1iZGVlLTBjOGY4NDc2YTRiYw"
  362. },
  363. "memberships": {
  364. "href": "https://fabrikam.vssps.visualstudio.com/_apis/graph/memberships/TWljcm9zb2Z0LklkZW50aXR5TW9kZWwuQ2xhaW1zLkNsYWltc0lkZW50aXR5Ozc3ODlmMDlkLWUwNTMtNGYyZS1iZGVlLTBjOGY4NDc2YTRiYw"
  365. }
  366. },
  367. "url": "https://fabrikam.vssps.visualstudio.com/_apis/graph/users/TWljcm9zb2Z0LklkZW50aXR5TW9kZWwuQ2xhaW1zLkNsYWltc0lkZW50aXR5Ozc3ODlmMDlkLWUwNTMtNGYyZS1iZGVlLTBjOGY4NDc2YTRiYw",
  368. "descriptor": "TWljcm9zb2Z0LklkZW50aXR5TW9kZWwuQ2xhaW1zLkNsYWltc0lkZW50aXR5Ozc3ODlmMDlkLWUwNTMtNGYyZS1iZGVlLTBjOGY4NDc2YTRiYw"
  369. },
  370. {
  371. "subjectKind": "user",
  372. "metaType": "member",
  373. "cuid": "ddd94918-1fc8-459b-994a-cca86c4fbe95",
  374. "domain": "TEAM FOUNDATION",
  375. "principalName": "Anonymous",
  376. "mailAddress": "",
  377. "origin": "vsts",
  378. "originId": "ddd94918-1fc8-459b-994a-cca86c4fbe95",
  379. "displayName": "Anonymous",
  380. "_links": {
  381. "self": {
  382. "href": "https://fabrikam.vssps.visualstudio.com/_apis/graph/users/TWljcm9zb2Z0LlRlYW1Gb3VuZGF0aW9uLlVuYXV0aGVudGljYXRlZElkZW50aXR5O1MtMS0wLTA"
  383. },
  384. "memberships": {
  385. "href": "https://fabrikam.vssps.visualstudio.com/_apis/graph/memberships/TWljcm9zb2Z0LlRlYW1Gb3VuZGF0aW9uLlVuYXV0aGVudGljYXRlZElkZW50aXR5O1MtMS0wLTA"
  386. }
  387. },
  388. "url": "https://fabrikam.vssps.visualstudio.com/_apis/graph/users/TWljcm9zb2Z0LlRlYW1Gb3VuZGF0aW9uLlVuYXV0aGVudGljYXRlZElkZW50aXR5O1MtMS0wLTA",
  389. "descriptor": "TWljcm9zb2Z0LlRlYW1Gb3VuZGF0aW9uLlVuYXV0aGVudGljYXRlZElkZW50aXR5O1MtMS0wLTA"
  390. },
  391. {
  392. "subjectKind": "user",
  393. "metaType": "member",
  394. "cuid": "65903f92-53dc-61b3-bb0e-e69cfa1cb719",
  395. "domain": "45aa3d2d-7442-473d-b4d3-3c670da9dd96",
  396. "principalName": "ftotten@vscsi.us",
  397. "mailAddress": "ftotten@vscsi.us",
  398. "origin": "aad",
  399. "originId": "4be8f294-000d-4431-8506-57420b88e204",
  400. "displayName": "Francis Totten",
  401. "_links": {
  402. "self": {
  403. "href": "https://fabrikam.vssps.visualstudio.com/_apis/graph/users/TWljcm9zb2Z0LklkZW50aXR5TW9kZWwuQ2xhaW1zLkNsYWltc0lkZW50aXR5OzQ1YWEzZDJkLTc0NDItNDczZC1iNGQzLTNjNjcwZGE5ZGQ5NlxmdG90dGVuQHZzY3NpLnVz"
  404. },
  405. "memberships": {
  406. "href": "https://fabrikam.vssps.visualstudio.com/_apis/graph/memberships/TWljcm9zb2Z0LklkZW50aXR5TW9kZWwuQ2xhaW1zLkNsYWltc0lkZW50aXR5OzQ1YWEzZDJkLTc0NDItNDczZC1iNGQzLTNjNjcwZGE5ZGQ5NlxmdG90dGVuQHZzY3NpLnVz"
  407. }
  408. },
  409. "url": "https://fabrikam.vssps.visualstudio.com/_apis/graph/users/TWljcm9zb2Z0LklkZW50aXR5TW9kZWwuQ2xhaW1zLkNsYWltc0lkZW50aXR5OzQ1YWEzZDJkLTc0NDItNDczZC1iNGQzLTNjNjcwZGE5ZGQ5NlxmdG90dGVuQHZzY3NpLnVz",
  410. "descriptor": "TWljcm9zb2Z0LklkZW50aXR5TW9kZWwuQ2xhaW1zLkNsYWltc0lkZW50aXR5OzQ1YWEzZDJkLTc0NDItNDczZC1iNGQzLTNjNjcwZGE5ZGQ5NlxmdG90dGVuQHZzY3NpLnVz"
  411. }
  412. ]
  413. }
  414. """
  415. CREATE_SUBSCRIPTION = {
  416. "id": "fd672255-8b6b-4769-9260-beea83d752ce",
  417. "url": "https://fabrikam.visualstudio.com/_apis/hooks/subscriptions/fd672255-8b6b-4769-9260-beea83d752ce",
  418. "publisherId": "tfs",
  419. "eventType": "workitem.update",
  420. "resourceVersion": "1.0-preview.1",
  421. "eventDescription": "WorkItem Updated",
  422. "consumerId": "webHooks",
  423. "consumerActionId": "httpRequest",
  424. "actionDescription": "To host myservice",
  425. "createdBy": {"id": "00ca946b-2fe9-4f2a-ae2f-40d5c48001bc"},
  426. "createdDate": "2014-10-27T15:37:24.873Z",
  427. "modifiedBy": {"id": "00ca946b-2fe9-4f2a-ae2f-40d5c48001bc"},
  428. "modifiedDate": "2014-10-27T15:37:26.23Z",
  429. "publisherInputs": {
  430. "buildStatus": "Failed",
  431. "definitionName": "MyWebSite CI",
  432. "hostId": "d81542e4-cdfa-4333-b082-1ae2d6c3ad16",
  433. "projectId": "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c",
  434. "tfsSubscriptionId": "3e8b33e7-426d-4c92-9bf9-58e163dd7dd5",
  435. },
  436. "consumerInputs": {"url": "https://myservice/newreceiver"},
  437. }
  438. WORK_ITEM_UPDATED: dict[str, Any] = {
  439. "resourceContainers": {
  440. "project": {
  441. "id": "c0bf429a-c03c-4a99-9336-d45be74db5a6",
  442. "baseUrl": "https://laurynsentry.visualstudio.com/",
  443. },
  444. "account": {
  445. "id": "90e9a854-eb98-4c56-ae1a-035a0f331dd6",
  446. "baseUrl": "https://laurynsentry.visualstudio.com/",
  447. },
  448. "collection": {
  449. "id": "80ded3e8-3cd3-43b1-9f96-52032624aa3a",
  450. "baseUrl": "https://laurynsentry.visualstudio.com/",
  451. },
  452. },
  453. "resource": {
  454. "revisedBy": {
  455. "displayName": "lauryn",
  456. "name": "lauryn <lauryn@sentry.io>",
  457. "url": "https://app.vssps.visualstudio.com/A90e9a854-eb98-4c56-ae1a-035a0f331dd6/_apis/Identities/21354f98-ab06-67d9-b974-5a54d992082e",
  458. "imageUrl": "https://laurynsentry.visualstudio.com/_api/_common/identityImage?id=21354f98-ab06-67d9-b974-5a54d992082e",
  459. "descriptor": "msa.MjEzNTRmOTgtYWIwNi03N2Q5LWI5NzQtNWE1NGQ5OTIwODJl",
  460. "_links": {
  461. "avatar": {
  462. "href": "https://laurynsentry.visualstudio.com/_apis/GraphProfile/MemberAvatars/msa.MjEzNTRmOTgtYWIwNi03N2Q5LWI5NzQtNWE1NGQ5OTIwODJl"
  463. }
  464. },
  465. "uniqueName": "lauryn@sentry.io",
  466. "id": "21354f98-ab06-67d9-b974-5a54d992082e",
  467. },
  468. "revisedDate": "9999-01-01T00:00:00Z",
  469. "url": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/31/updates/2",
  470. "fields": {
  471. "System.AuthorizedDate": {
  472. "newValue": "2018-07-05T20:52:14.777Z",
  473. "oldValue": "2018-07-05T20:51:58.927Z",
  474. },
  475. "System.AssignedTo": {
  476. "newValue": "lauryn <lauryn@sentry.io>",
  477. "oldValue": "lauryn2 <lauryn2@sentry.io>",
  478. },
  479. "System.Watermark": {"newValue": 78, "oldValue": 77},
  480. "System.Rev": {"newValue": 2, "oldValue": 1},
  481. "System.RevisedDate": {
  482. "newValue": "9999-01-01T00:00:00Z",
  483. "oldValue": "2018-07-05T20:52:14.777Z",
  484. },
  485. "System.ChangedDate": {
  486. "newValue": "2018-07-05T20:52:14.777Z",
  487. "oldValue": "2018-07-05T20:51:58.927Z",
  488. },
  489. },
  490. "workItemId": 31,
  491. "rev": 2,
  492. "_links": {
  493. "self": {
  494. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/31/updates/2"
  495. },
  496. "workItemUpdates": {
  497. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/31/updates"
  498. },
  499. "html": {
  500. "href": "https://laurynsentry.visualstudio.com/web/wi.aspx?pcguid=80ded3e8-3cd3-43b1-9f96-52032624aa3a&id=31"
  501. },
  502. "parent": {
  503. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/31"
  504. },
  505. },
  506. "id": 2,
  507. "revision": {
  508. "url": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/31/revisions/2",
  509. "fields": {
  510. "System.AreaPath": "MyFirstProject",
  511. "System.WorkItemType": "Bug",
  512. "System.Reason": "New",
  513. "System.Title": "NameError: global name 'BitbucketRepositoryProvider' is not defined",
  514. "Microsoft.VSTS.Common.Priority": 2,
  515. "System.CreatedBy": "lauryn <lauryn@sentry.io>",
  516. "System.AssignedTo": "lauryn <lauryn@sentry.io>",
  517. "System.CreatedDate": "2018-07-05T20:51:58.927Z",
  518. "System.TeamProject": "MyFirstProject",
  519. "Microsoft.VSTS.Common.Severity": "3 - Medium",
  520. "Microsoft.VSTS.Common.ValueArea": "Business",
  521. "System.State": "New",
  522. "System.Description": "<p><a href=\"https://lauryn.ngrok.io/sentry/internal/issues/55/\">https://lauryn.ngrok.io/sentry/internal/issues/55/</a></p>\n<pre><code>NameError: global name 'BitbucketRepositoryProvider' is not defined\n(1 additional frame(s) were not displayed)\n...\n File &quot;sentry/runner/__init__.py&quot;, line 125, in configure\n configure(ctx, py, yaml, skip_service_validation)\n File &quot;sentry/runner/settings.py&quot;, line 152, in configure\n skip_service_validation=skip_service_validation\n File &quot;sentry/runner/initializer.py&quot;, line 315, in initialize_app\n register_plugins(settings)\n File &quot;sentry/runner/initializer.py&quot;, line 60, in register_plugins\n integration.setup()\n File &quot;sentry/integrations/bitbucket/integration.py&quot;, line 78, in setup\n BitbucketRepositoryProvider,\n\nNameError: global name 'BitbucketRepositoryProvider' is not defined\n</code></pre>\n",
  523. "System.ChangedBy": "lauryn <lauryn@sentry.io>",
  524. "System.ChangedDate": "2018-07-05T20:52:14.777Z",
  525. "Microsoft.VSTS.Common.StateChangeDate": "2018-07-05T20:51:58.927Z",
  526. "System.IterationPath": "MyFirstProject",
  527. },
  528. "rev": 2,
  529. "id": 31,
  530. "_links": {
  531. "self": {
  532. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/31/revisions/2"
  533. },
  534. "workItemRevisions": {
  535. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/31/revisions"
  536. },
  537. "parent": {
  538. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/31"
  539. },
  540. },
  541. },
  542. },
  543. "eventType": "workitem.updated",
  544. "detailedMessage": None,
  545. "createdDate": "2018-07-05T20:52:16.3051288Z",
  546. "id": "18f51331-2640-4bce-9ebd-c59c855956a2",
  547. "resourceVersion": "1.0",
  548. "notificationId": 1,
  549. "subscriptionId": "7bf628eb-b3a7-4fb2-ab4d-8b60f2e8cb9b",
  550. "publisherId": "tfs",
  551. "message": None,
  552. }
  553. WORK_ITEM_UNASSIGNED: dict[str, Any] = {
  554. "resourceContainers": {
  555. "project": {
  556. "id": "c0bf429a-c03c-4a99-9336-d45be74db5a6",
  557. "baseUrl": "https://laurynsentry.visualstudio.com/",
  558. },
  559. "account": {
  560. "id": "90e9a854-eb98-4c56-ae1a-035a0f331dd6",
  561. "baseUrl": "https://laurynsentry.visualstudio.com/",
  562. },
  563. "collection": {
  564. "id": "80ded3e8-3cd3-43b1-9f96-52032624aa3a",
  565. "baseUrl": "https://laurynsentry.visualstudio.com/",
  566. },
  567. },
  568. "resource": {
  569. "revisedBy": {
  570. "displayName": "lauryn",
  571. "name": "lauryn <lauryn@sentry.io>",
  572. "url": "https://app.vssps.visualstudio.com/A90e9a854-eb98-4c56-ae1a-035a0f331dd6/_apis/Identities/21354f98-ab06-67d9-b974-5a54d992082e",
  573. "imageUrl": "https://laurynsentry.visualstudio.com/_api/_common/identityImage?id=21354f98-ab06-67d9-b974-5a54d992082e",
  574. "descriptor": "msa.MjEzNTRmOTgtYWIwNi03N2Q5LWI5NzQtNWE1NGQ5OTIwODJl",
  575. "_links": {
  576. "avatar": {
  577. "href": "https://laurynsentry.visualstudio.com/_apis/GraphProfile/MemberAvatars/msa.MjEzNTRmOTgtYWIwNi03N2Q5LWI5NzQtNWE1NGQ5OTIwODJl"
  578. }
  579. },
  580. "uniqueName": "lauryn@sentry.io",
  581. "id": "21354f98-ab06-67d9-b974-5a54d992082e",
  582. },
  583. "revisedDate": "9999-01-01T00:00:00 Z",
  584. "url": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/updates/3",
  585. "fields": {
  586. "System.AuthorizedDate": {
  587. "newValue": "2018-07-05T23:23:09.493 Z",
  588. "oldValue": "2018-07-05T23:21:38.243 Z",
  589. },
  590. "System.AssignedTo": {"oldValue": "lauryn <lauryn@sentry.io>"},
  591. "System.Watermark": {"newValue": 83, "oldValue": 82},
  592. "System.Rev": {"newValue": 3, "oldValue": 2},
  593. "System.RevisedDate": {
  594. "newValue": "9999-01-01T00:00:00 Z",
  595. "oldValue": "2018-07-05T23:23:09.493 Z",
  596. },
  597. "System.ChangedDate": {
  598. "newValue": "2018-07-05T23:23:09.493 Z",
  599. "oldValue": "2018-07-05T23:21:38.243 Z",
  600. },
  601. },
  602. "workItemId": 33,
  603. "rev": 3,
  604. "_links": {
  605. "self": {
  606. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/updates/3"
  607. },
  608. "workItemUpdates": {
  609. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/updates"
  610. },
  611. "html": {
  612. "href": "https://laurynsentry.visualstudio.com/web/wi.aspx?pcguid=80ded3e8-3cd3-43b1-9f96-52032624aa3a&id=33"
  613. },
  614. "parent": {
  615. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33"
  616. },
  617. },
  618. "id": 3,
  619. "revision": {
  620. "url": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/revisions/3",
  621. "fields": {
  622. "System.AreaPath": "MyFirstProject",
  623. "System.WorkItemType": "Bug",
  624. "System.Reason": "New",
  625. "System.Title": "NotImplementedError:Visual Studio Team Services requires an organization_id",
  626. "Microsoft.VSTS.Common.Priority": 2,
  627. "System.CreatedBy": "lauryn <lauryn@sentry.io>",
  628. "Microsoft.VSTS.Common.StateChangeDate": "2018-07-05T23:21:25.847 Z",
  629. "System.CreatedDate": "2018-07-05T23:21:25.847 Z",
  630. "System.TeamProject": "MyFirstProject",
  631. "Microsoft.VSTS.Common.ValueArea": "Business",
  632. "System.State": "New",
  633. "System.Description": '<p><a href="https: //lauryn.ngrok.io/sentry/internal/issues/196/">https: //lauryn.ngrok.io/sentry/internal/issues/196/</a></p>\n<pre><code>NotImplementedError:Visual Studio Team Services requires an organization_id\n(57 additional frame(s) were not displayed)\n...\n File &quot;sentry/tasks/base.py&quot;',
  634. "System.ChangedBy": "lauryn <lauryn@sentry.io>",
  635. "System.ChangedDate": "2018-07-05T23:23:09.493 Z",
  636. "Microsoft.VSTS.Common.Severity": "3 - Medium",
  637. "System.IterationPath": "MyFirstProject",
  638. },
  639. "rev": 3,
  640. "id": 33,
  641. "_links": {
  642. "self": {
  643. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/revisions/3"
  644. },
  645. "workItemRevisions": {
  646. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/revisions"
  647. },
  648. "parent": {
  649. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33"
  650. },
  651. },
  652. },
  653. },
  654. "eventType": "workitem.updated",
  655. "detailedMessage": None,
  656. "createdDate": "2018-07-05T23:23:11.1935112 Z",
  657. "id": "cc349c85-6595-4939-9b69-f89480be6a26",
  658. "resourceVersion": "1.0",
  659. "notificationId": 2,
  660. "subscriptionId": "7405a600-6a25-48e6-81b6-1dde044783ad",
  661. "publisherId": "tfs",
  662. "message": None,
  663. }
  664. WORK_ITEM_UPDATED_STATUS: dict[str, Any] = {
  665. "resourceContainers": {
  666. "project": {
  667. "id": "c0bf429a-c03c-4a99-9336-d45be74db5a6",
  668. "baseUrl": "https://laurynsentry.visualstudio.com/",
  669. },
  670. "account": {
  671. "id": "90e9a854-eb98-4c56-ae1a-035a0f331dd6",
  672. "baseUrl": "https://laurynsentry.visualstudio.com/",
  673. },
  674. "collection": {
  675. "id": "80ded3e8-3cd3-43b1-9f96-52032624aa3a",
  676. "baseUrl": "https://laurynsentry.visualstudio.com/",
  677. },
  678. },
  679. "resource": {
  680. "revisedBy": {
  681. "displayName": "lauryn",
  682. "name": "lauryn <lauryn@sentry.io>",
  683. "url": "https://app.vssps.visualstudio.com/A90e9a854-eb98-4c56-ae1a-035a0f331dd6/_apis/Identities/21354f98-ab06-67d9-b974-5a54d992082e",
  684. "imageUrl": "https://laurynsentry.visualstudio.com/_api/_common/identityImage?id=21354f98-ab06-67d9-b974-5a54d992082e",
  685. "descriptor": "msa.MjEzNTRmOTgtYWIwNi03N2Q5LWI5NzQtNWE1NGQ5OTIwODJl",
  686. "_links": {
  687. "avatar": {
  688. "href": "https://laurynsentry.visualstudio.com/_apis/GraphProfile/MemberAvatars/msa.MjEzNTRmOTgtYWIwNi03N2Q5LWI5NzQtNWE1NGQ5OTIwODJl"
  689. }
  690. },
  691. "uniqueName": "lauryn@sentry.io",
  692. "id": "21354f98-ab06-67d9-b974-5a54d992082e",
  693. },
  694. "revisedDate": "9999-01-01T00:00:00 Z",
  695. "url": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/updates/3",
  696. "fields": {
  697. "System.AuthorizedDate": {
  698. "newValue": "2018-07-05T23:23:09.493 Z",
  699. "oldValue": "2018-07-05T23:21:38.243 Z",
  700. },
  701. "System.State": {"oldValue": "New", "newValue": "Resolved"},
  702. "System.Watermark": {"newValue": 83, "oldValue": 82},
  703. "System.Rev": {"newValue": 3, "oldValue": 2},
  704. "System.RevisedDate": {
  705. "newValue": "9999-01-01T00:00:00 Z",
  706. "oldValue": "2018-07-05T23:23:09.493 Z",
  707. },
  708. "System.ChangedDate": {
  709. "newValue": "2018-07-05T23:23:09.493 Z",
  710. "oldValue": "2018-07-05T23:21:38.243 Z",
  711. },
  712. },
  713. "workItemId": 33,
  714. "rev": 3,
  715. "_links": {
  716. "self": {
  717. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/updates/3"
  718. },
  719. "workItemUpdates": {
  720. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/updates"
  721. },
  722. "html": {
  723. "href": "https://laurynsentry.visualstudio.com/web/wi.aspx?pcguid=80ded3e8-3cd3-43b1-9f96-52032624aa3a&id=33"
  724. },
  725. "parent": {
  726. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33"
  727. },
  728. },
  729. "id": 3,
  730. "revision": {
  731. "url": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/revisions/3",
  732. "fields": {
  733. "System.AreaPath": "MyFirstProject",
  734. "System.WorkItemType": "Bug",
  735. "System.Reason": "New",
  736. "System.Title": "NotImplementedError:Visual Studio Team Services requires an organization_id",
  737. "Microsoft.VSTS.Common.Priority": 2,
  738. "System.CreatedBy": "lauryn <lauryn@sentry.io>",
  739. "Microsoft.VSTS.Common.StateChangeDate": "2018-07-05T23:21:25.847 Z",
  740. "System.CreatedDate": "2018-07-05T23:21:25.847 Z",
  741. "System.TeamProject": "MyFirstProject",
  742. "Microsoft.VSTS.Common.ValueArea": "Business",
  743. "System.State": "New",
  744. "System.Description": '<p><a href="https: //lauryn.ngrok.io/sentry/internal/issues/196/">https: //lauryn.ngrok.io/sentry/internal/issues/196/</a></p>\n<pre><code>NotImplementedError:Visual Studio Team Services requires an organization_id\n(57 additional frame(s) were not displayed)\n...\n File &quot;sentry/tasks/base.py&quot;',
  745. "System.ChangedBy": "lauryn <lauryn@sentry.io>",
  746. "System.ChangedDate": "2018-07-05T23:23:09.493 Z",
  747. "Microsoft.VSTS.Common.Severity": "3 - Medium",
  748. "System.IterationPath": "MyFirstProject",
  749. },
  750. "rev": 3,
  751. "id": 33,
  752. "_links": {
  753. "self": {
  754. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/revisions/3"
  755. },
  756. "workItemRevisions": {
  757. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33/revisions"
  758. },
  759. "parent": {
  760. "href": "https://laurynsentry.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workItems/33"
  761. },
  762. },
  763. },
  764. },
  765. "eventType": "workitem.updated",
  766. "detailedMessage": None,
  767. "createdDate": "2018-07-05T23:23:11.1935112 Z",
  768. "id": "cc349c85-6595-4939-9b69-f89480be6a26",
  769. "resourceVersion": "1.0",
  770. "notificationId": 2,
  771. "subscriptionId": "7405a600-6a25-48e6-81b6-1dde044783ad",
  772. "publisherId": "tfs",
  773. "message": None,
  774. }
  775. WORK_ITEM_STATES = {
  776. "count": 5,
  777. "value": [
  778. {"name": "New", "color": "b2b2b2", "category": "Proposed"},
  779. {"name": "Active", "color": "007acc", "category": "InProgress"},
  780. {"name": "CustomState", "color": "5688E0", "category": "InProgress"},
  781. {"name": "Resolved", "color": "ff9d00", "category": "Resolved"},
  782. {"name": "Closed", "color": "339933", "category": "Completed"},
  783. ],
  784. }
  785. GET_PROJECTS_RESPONSE = """{
  786. "count": 1,
  787. "value": [{
  788. "id": "ac7c05bb-7f8e-4880-85a6-e08f37fd4a10",
  789. "name": "Fabrikam-Fiber-Git",
  790. "url": "https://jess-dev.visualstudio.com/_apis/projects/ac7c05bb-7f8e-4880-85a6-e08f37fd4a10",
  791. "state": "wellFormed",
  792. "revision": 16,
  793. "visibility": "private"
  794. }]
  795. }"""