vsts.py 38 KB

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