From f2aa9af80068cb41ff4e4377e21af18aca21bbd4 Mon Sep 17 00:00:00 2001
From: Mohamed AZIKIOU <mazikiou@takima.fr>
Date: Fri, 10 Jun 2022 09:58:55 +0000
Subject: [PATCH] feat-added_keycloack_auth_tests

---
 .../deadlock-extension/jest.config.ts         |   1 +
 .../deadlock-extension/package-lock.json      | 920 +++++++++++++++++-
 .../deadlock-extension/package.json           |   1 +
 .../src/core/api.service.ts                   |   3 +-
 .../deadlock-extension/src/core/controller.ts |  28 +-
 ...keycloakOAuth2DeviceFlowConnection.test.ts |  76 +-
 .../keycloakOAuth2DeviceFlowConnection.ts     |  57 +-
 .../src/core/sshKeyManager.ts                 |  20 +-
 .../deadlock-extension/src/extension.ts       |  12 +-
 .../src/test/service/mocks/handlers.ts        | 113 +++
 .../src/test/service/mocks/server.ts          |   4 +
 .../deadlock-extension/src/test/setupTests.ts |   5 +
 .../src/theia/deadlockPanel.ts                |  25 +-
 .../src/theia/userConfigTheia.ts              |   4 +-
 .../src/view/quickSetupView.ts                |  10 +-
 .../deadlock-extension/src/view/view.ts       |  41 +-
 .../deadlock-extension/tsconfig.json          |   2 +-
 17 files changed, 1181 insertions(+), 141 deletions(-)
 create mode 100644 deadlock-plugins/deadlock-extension/src/test/service/mocks/handlers.ts
 create mode 100644 deadlock-plugins/deadlock-extension/src/test/service/mocks/server.ts
 create mode 100644 deadlock-plugins/deadlock-extension/src/test/setupTests.ts

diff --git a/deadlock-plugins/deadlock-extension/jest.config.ts b/deadlock-plugins/deadlock-extension/jest.config.ts
index fd75b552..66ff808b 100644
--- a/deadlock-plugins/deadlock-extension/jest.config.ts
+++ b/deadlock-plugins/deadlock-extension/jest.config.ts
@@ -142,6 +142,7 @@ export default {
 
   // A list of paths to modules that run some code to configure or set up the testing framework before each test
   // setupFilesAfterEnv: [],
+  setupFilesAfterEnv: ['<rootDir>/src/test/setupTests.ts'],
 
   // The number of seconds after which a test is considered as slow and reported as such in the results.
   // slowTestThreshold: 5,
diff --git a/deadlock-plugins/deadlock-extension/package-lock.json b/deadlock-plugins/deadlock-extension/package-lock.json
index ba6333d8..8fb8b4ec 100644
--- a/deadlock-plugins/deadlock-extension/package-lock.json
+++ b/deadlock-plugins/deadlock-extension/package-lock.json
@@ -39,6 +39,7 @@
         "eslint": "^7.32.0",
         "glob": "^7.2.0",
         "jest": "^28.1.0",
+        "msw": "^0.42.1",
         "prettier": "2.6.2",
         "terser-webpack-plugin": "^5.2.5",
         "ts-jest": "^28.0.4",
@@ -1277,6 +1278,42 @@
         "exenv-es6": "^1.1.1"
       }
     },
+    "node_modules/@mswjs/cookies": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.1.tgz",
+      "integrity": "sha512-0tDfcPw5/s7QsNQqS3knAvAD5w5PF1nNPagRhKO/yECY+sMbJxoC2sLWnH7Lzmh52mTSVLKDhd1r92Q3kfljnQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/set-cookie-parser": "^2.4.0",
+        "set-cookie-parser": "^2.4.6"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@mswjs/interceptors": {
+      "version": "0.16.4",
+      "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.16.4.tgz",
+      "integrity": "sha512-McPKUFlZNS/wo+OAor15k0fv2skK+EdWl9CEcdxAqsN4vKajlxCxDU4B5W/pn1y0TJPSAOmxR6LYFe/8esePrg==",
+      "dev": true,
+      "dependencies": {
+        "@open-draft/until": "^1.0.3",
+        "@xmldom/xmldom": "^0.7.5",
+        "debug": "^4.3.3",
+        "headers-polyfill": "^3.0.4",
+        "outvariant": "^1.2.1",
+        "strict-event-emitter": "^0.2.4"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@open-draft/until": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz",
+      "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==",
+      "dev": true
+    },
     "node_modules/@sinclair/typebox": {
       "version": "0.23.5",
       "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz",
@@ -1419,6 +1456,12 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/cookie": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+      "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
+      "dev": true
+    },
     "node_modules/@types/crypto-js": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
@@ -1542,6 +1585,12 @@
         "pretty-format": "^27.0.0"
       }
     },
+    "node_modules/@types/js-levenshtein": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz",
+      "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==",
+      "dev": true
+    },
     "node_modules/@types/json-schema": {
       "version": "7.0.9",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
@@ -1625,6 +1674,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/set-cookie-parser": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz",
+      "integrity": "sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/sockjs": {
       "version": "0.3.33",
       "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz",
@@ -2069,6 +2127,15 @@
         }
       }
     },
+    "node_modules/@xmldom/xmldom": {
+      "version": "0.7.5",
+      "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz",
+      "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==",
+      "dev": true,
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -2934,6 +3001,12 @@
         "node": ">=10"
       }
     },
+    "node_modules/chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
     "node_modules/cheerio": {
       "version": "1.0.0-rc.10",
       "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz",
@@ -3031,6 +3104,39 @@
       "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
       "dev": true
     },
+    "node_modules/cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "dependencies": {
+        "restore-cursor": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cli-spinners": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz",
+      "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/cli-width": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
+      "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -3042,6 +3148,15 @@
         "wrap-ansi": "^7.0.0"
       }
     },
+    "node_modules/clone": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/clone-deep": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -3383,6 +3498,15 @@
         "node": ">= 10"
       }
     },
+    "node_modules/defaults": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+      "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==",
+      "dev": true,
+      "dependencies": {
+        "clone": "^1.0.2"
+      }
+    },
     "node_modules/define-lazy-prop": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -4174,6 +4298,32 @@
       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
       "dev": true
     },
+    "node_modules/external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "dependencies": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/external-editor/node_modules/tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "dependencies": {
+        "os-tmpdir": "~1.0.2"
+      },
+      "engines": {
+        "node": ">=0.6.0"
+      }
+    },
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4228,6 +4378,30 @@
         "pend": "~1.2.0"
       }
     },
+    "node_modules/figures": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+      "dev": true,
+      "dependencies": {
+        "escape-string-regexp": "^1.0.5"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/figures/node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
     "node_modules/file-entry-cache": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -4620,6 +4794,15 @@
       "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
       "dev": true
     },
+    "node_modules/graphql": {
+      "version": "16.5.0",
+      "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.5.0.tgz",
+      "integrity": "sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
+      }
+    },
     "node_modules/handle-thing": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
@@ -4686,6 +4869,12 @@
       "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
       "dev": true
     },
+    "node_modules/headers-polyfill": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.0.7.tgz",
+      "integrity": "sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w==",
+      "dev": true
+    },
     "node_modules/hosted-git-info": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz",
@@ -4947,6 +5136,32 @@
       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
       "dev": true
     },
+    "node_modules/inquirer": {
+      "version": "8.2.4",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz",
+      "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==",
+      "dev": true,
+      "dependencies": {
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^4.1.1",
+        "cli-cursor": "^3.1.0",
+        "cli-width": "^3.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.21",
+        "mute-stream": "0.0.8",
+        "ora": "^5.4.1",
+        "run-async": "^2.4.0",
+        "rxjs": "^7.5.5",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0",
+        "through": "^2.3.6",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/interpret": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
@@ -5039,6 +5254,21 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-interactive": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+      "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-node-process": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.0.1.tgz",
+      "integrity": "sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==",
+      "dev": true
+    },
     "node_modules/is-number": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -5084,6 +5314,18 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-unicode-supported": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+      "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-wsl": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -6252,6 +6494,15 @@
         "url": "https://github.com/chalk/supports-color?sponsor=1"
       }
     },
+    "node_modules/js-levenshtein": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+      "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6429,6 +6680,22 @@
       "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
       "dev": true
     },
+    "node_modules/log-symbols": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+      "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+      "dev": true,
+      "dependencies": {
+        "chalk": "^4.1.0",
+        "is-unicode-supported": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/loose-envify": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -6694,6 +6961,150 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
     },
+    "node_modules/msw": {
+      "version": "0.42.1",
+      "resolved": "https://registry.npmjs.org/msw/-/msw-0.42.1.tgz",
+      "integrity": "sha512-LZZuz7VddL45gCBgfBWHyXj6a4W7OTJY0mZPoipJ3P/xwbuJwrtwB3IJrWlqBM8aink/eTKlRxwzmtIAwCj5yQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "dependencies": {
+        "@mswjs/cookies": "^0.2.0",
+        "@mswjs/interceptors": "^0.16.3",
+        "@open-draft/until": "^1.0.3",
+        "@types/cookie": "^0.4.1",
+        "@types/js-levenshtein": "^1.1.1",
+        "chalk": "4.1.1",
+        "chokidar": "^3.4.2",
+        "cookie": "^0.4.2",
+        "graphql": "^16.3.0",
+        "headers-polyfill": "^3.0.4",
+        "inquirer": "^8.2.0",
+        "is-node-process": "^1.0.1",
+        "js-levenshtein": "^1.1.6",
+        "node-fetch": "^2.6.7",
+        "outvariant": "^1.3.0",
+        "path-to-regexp": "^6.2.0",
+        "statuses": "^2.0.0",
+        "strict-event-emitter": "^0.2.0",
+        "type-fest": "^1.2.2",
+        "yargs": "^17.3.1"
+      },
+      "bin": {
+        "msw": "cli/index.js"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mswjs"
+      },
+      "peerDependencies": {
+        "typescript": ">= 4.2.x <= 4.7.x"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/msw/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/msw/node_modules/chalk": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
+      "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/msw/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/msw/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/msw/node_modules/cookie": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+      "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/msw/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/msw/node_modules/path-to-regexp": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
+      "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
+      "dev": true
+    },
+    "node_modules/msw/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/msw/node_modules/type-fest": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+      "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/multicast-dns": {
       "version": "7.2.5",
       "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz",
@@ -6973,9 +7384,47 @@
         "word-wrap": "^1.2.3"
       },
       "engines": {
-        "node": ">= 0.8.0"
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/ora": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
+      "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+      "dev": true,
+      "dependencies": {
+        "bl": "^4.1.0",
+        "chalk": "^4.1.0",
+        "cli-cursor": "^3.1.0",
+        "cli-spinners": "^2.5.0",
+        "is-interactive": "^1.0.0",
+        "is-unicode-supported": "^0.1.0",
+        "log-symbols": "^4.1.0",
+        "strip-ansi": "^6.0.0",
+        "wcwidth": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
       }
     },
+    "node_modules/outvariant": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.3.0.tgz",
+      "integrity": "sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ==",
+      "dev": true
+    },
     "node_modules/p-limit": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -7588,6 +8037,19 @@
         "node": ">=10"
       }
     },
+    "node_modules/restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "dependencies": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/retry": {
       "version": "0.13.1",
       "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
@@ -7612,6 +8074,30 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/run-async": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+      "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/rxjs": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
+      "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/rxjs/node_modules/tslib": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+      "dev": true
+    },
     "node_modules/safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -7848,6 +8334,12 @@
       "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
       "dev": true
     },
+    "node_modules/set-cookie-parser": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.0.tgz",
+      "integrity": "sha512-cHMAtSXilfyBePduZEBVPTCftTQWz6ehWJD5YNUg4mqvRosrrjKbo4WS8JkB0/RxonMoohHm7cOGH60mDkRQ9w==",
+      "dev": true
+    },
     "node_modules/setimmediate": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -8133,6 +8625,15 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/strict-event-emitter": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz",
+      "integrity": "sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ==",
+      "dev": true,
+      "dependencies": {
+        "events": "^3.3.0"
+      }
+    },
     "node_modules/string_decoder": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -8467,6 +8968,12 @@
       "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==",
       "dev": true
     },
+    "node_modules/through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
     "node_modules/thunky": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -8973,6 +9480,15 @@
         "minimalistic-assert": "^1.0.0"
       }
     },
+    "node_modules/wcwidth": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+      "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+      "dev": true,
+      "dependencies": {
+        "defaults": "^1.0.3"
+      }
+    },
     "node_modules/webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -10508,6 +11024,36 @@
         "exenv-es6": "^1.1.1"
       }
     },
+    "@mswjs/cookies": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.1.tgz",
+      "integrity": "sha512-0tDfcPw5/s7QsNQqS3knAvAD5w5PF1nNPagRhKO/yECY+sMbJxoC2sLWnH7Lzmh52mTSVLKDhd1r92Q3kfljnQ==",
+      "dev": true,
+      "requires": {
+        "@types/set-cookie-parser": "^2.4.0",
+        "set-cookie-parser": "^2.4.6"
+      }
+    },
+    "@mswjs/interceptors": {
+      "version": "0.16.4",
+      "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.16.4.tgz",
+      "integrity": "sha512-McPKUFlZNS/wo+OAor15k0fv2skK+EdWl9CEcdxAqsN4vKajlxCxDU4B5W/pn1y0TJPSAOmxR6LYFe/8esePrg==",
+      "dev": true,
+      "requires": {
+        "@open-draft/until": "^1.0.3",
+        "@xmldom/xmldom": "^0.7.5",
+        "debug": "^4.3.3",
+        "headers-polyfill": "^3.0.4",
+        "outvariant": "^1.2.1",
+        "strict-event-emitter": "^0.2.4"
+      }
+    },
+    "@open-draft/until": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz",
+      "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==",
+      "dev": true
+    },
     "@sinclair/typebox": {
       "version": "0.23.5",
       "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz",
@@ -10562,12 +11108,6 @@
       "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
       "dev": true
     },
-    "@types/async": {
-      "version": "3.2.13",
-      "resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.13.tgz",
-      "integrity": "sha512-7Q3awrhnvm89OzfsmqeqRQh8mh+8Pxfgq1UvSAn2nWQ5y/F3+NrbIF0RbkWq8+5dY99ozgap2b3DNBNwjLVOxw==",
-      "dev": true
-    },
     "@types/babel__core": {
       "version": "7.1.19",
       "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz",
@@ -10647,6 +11187,12 @@
         "@types/node": "*"
       }
     },
+    "@types/cookie": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+      "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
+      "dev": true
+    },
     "@types/crypto-js": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
@@ -10770,6 +11316,12 @@
         "pretty-format": "^27.0.0"
       }
     },
+    "@types/js-levenshtein": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz",
+      "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==",
+      "dev": true
+    },
     "@types/json-schema": {
       "version": "7.0.9",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
@@ -10853,6 +11405,15 @@
         "@types/node": "*"
       }
     },
+    "@types/set-cookie-parser": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz",
+      "integrity": "sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/sockjs": {
       "version": "0.3.33",
       "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz",
@@ -11202,6 +11763,12 @@
       "dev": true,
       "requires": {}
     },
+    "@xmldom/xmldom": {
+      "version": "0.7.5",
+      "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz",
+      "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==",
+      "dev": true
+    },
     "@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -11851,6 +12418,12 @@
       "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
       "dev": true
     },
+    "chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
     "cheerio": {
       "version": "1.0.0-rc.10",
       "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz",
@@ -11927,6 +12500,27 @@
       "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
       "dev": true
     },
+    "cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^3.1.0"
+      }
+    },
+    "cli-spinners": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz",
+      "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==",
+      "dev": true
+    },
+    "cli-width": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
+      "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+      "dev": true
+    },
     "cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -11938,6 +12532,12 @@
         "wrap-ansi": "^7.0.0"
       }
     },
+    "clone": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+      "dev": true
+    },
     "clone-deep": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -12213,6 +12813,15 @@
         "execa": "^5.0.0"
       }
     },
+    "defaults": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+      "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==",
+      "dev": true,
+      "requires": {
+        "clone": "^1.0.2"
+      }
+    },
     "define-lazy-prop": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -12818,6 +13427,28 @@
         }
       }
     },
+    "external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "requires": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      },
+      "dependencies": {
+        "tmp": {
+          "version": "0.0.33",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+          "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+          "dev": true,
+          "requires": {
+            "os-tmpdir": "~1.0.2"
+          }
+        }
+      }
+    },
     "fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -12869,6 +13500,23 @@
         "pend": "~1.2.0"
       }
     },
+    "figures": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "^1.0.5"
+      },
+      "dependencies": {
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+          "dev": true
+        }
+      }
+    },
     "file-entry-cache": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -13171,6 +13819,12 @@
       "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
       "dev": true
     },
+    "graphql": {
+      "version": "16.5.0",
+      "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.5.0.tgz",
+      "integrity": "sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==",
+      "dev": true
+    },
     "handle-thing": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
@@ -13217,6 +13871,12 @@
       "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
       "dev": true
     },
+    "headers-polyfill": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.0.7.tgz",
+      "integrity": "sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w==",
+      "dev": true
+    },
     "hosted-git-info": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz",
@@ -13407,6 +14067,29 @@
       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
       "dev": true
     },
+    "inquirer": {
+      "version": "8.2.4",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz",
+      "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^4.1.1",
+        "cli-cursor": "^3.1.0",
+        "cli-width": "^3.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.21",
+        "mute-stream": "0.0.8",
+        "ora": "^5.4.1",
+        "run-async": "^2.4.0",
+        "rxjs": "^7.5.5",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0",
+        "through": "^2.3.6",
+        "wrap-ansi": "^7.0.0"
+      }
+    },
     "interpret": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
@@ -13475,6 +14158,18 @@
         "is-extglob": "^2.1.1"
       }
     },
+    "is-interactive": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+      "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+      "dev": true
+    },
+    "is-node-process": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.0.1.tgz",
+      "integrity": "sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==",
+      "dev": true
+    },
     "is-number": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -13502,6 +14197,12 @@
       "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
       "dev": true
     },
+    "is-unicode-supported": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+      "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+      "dev": true
+    },
     "is-wsl": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -14395,6 +15096,12 @@
         }
       }
     },
+    "js-levenshtein": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+      "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+      "dev": true
+    },
     "js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -14538,6 +15245,16 @@
       "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
       "dev": true
     },
+    "log-symbols": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+      "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+      "dev": true,
+      "requires": {
+        "chalk": "^4.1.0",
+        "is-unicode-supported": "^0.1.0"
+      }
+    },
     "loose-envify": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -14741,6 +15458,103 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
     },
+    "msw": {
+      "version": "0.42.1",
+      "resolved": "https://registry.npmjs.org/msw/-/msw-0.42.1.tgz",
+      "integrity": "sha512-LZZuz7VddL45gCBgfBWHyXj6a4W7OTJY0mZPoipJ3P/xwbuJwrtwB3IJrWlqBM8aink/eTKlRxwzmtIAwCj5yQ==",
+      "dev": true,
+      "requires": {
+        "@mswjs/cookies": "^0.2.0",
+        "@mswjs/interceptors": "^0.16.3",
+        "@open-draft/until": "^1.0.3",
+        "@types/cookie": "^0.4.1",
+        "@types/js-levenshtein": "^1.1.1",
+        "chalk": "4.1.1",
+        "chokidar": "^3.4.2",
+        "cookie": "^0.4.2",
+        "graphql": "^16.3.0",
+        "headers-polyfill": "^3.0.4",
+        "inquirer": "^8.2.0",
+        "is-node-process": "^1.0.1",
+        "js-levenshtein": "^1.1.6",
+        "node-fetch": "^2.6.7",
+        "outvariant": "^1.3.0",
+        "path-to-regexp": "^6.2.0",
+        "statuses": "^2.0.0",
+        "strict-event-emitter": "^0.2.0",
+        "type-fest": "^1.2.2",
+        "yargs": "^17.3.1"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
+          "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        },
+        "cookie": {
+          "version": "0.4.2",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+          "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "path-to-regexp": {
+          "version": "6.2.1",
+          "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
+          "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        },
+        "type-fest": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+          "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+          "dev": true
+        }
+      }
+    },
     "multicast-dns": {
       "version": "7.2.5",
       "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz",
@@ -14956,6 +15770,35 @@
         "word-wrap": "^1.2.3"
       }
     },
+    "ora": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
+      "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+      "dev": true,
+      "requires": {
+        "bl": "^4.1.0",
+        "chalk": "^4.1.0",
+        "cli-cursor": "^3.1.0",
+        "cli-spinners": "^2.5.0",
+        "is-interactive": "^1.0.0",
+        "is-unicode-supported": "^0.1.0",
+        "log-symbols": "^4.1.0",
+        "strip-ansi": "^6.0.0",
+        "wcwidth": "^1.0.1"
+      }
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+      "dev": true
+    },
+    "outvariant": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.3.0.tgz",
+      "integrity": "sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ==",
+      "dev": true
+    },
     "p-limit": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -15423,6 +16266,16 @@
       "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==",
       "dev": true
     },
+    "restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "requires": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
     "retry": {
       "version": "0.13.1",
       "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
@@ -15438,6 +16291,29 @@
         "glob": "^7.1.3"
       }
     },
+    "run-async": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+      "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+      "dev": true
+    },
+    "rxjs": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
+      "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
+      "dev": true,
+      "requires": {
+        "tslib": "^2.1.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+          "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+          "dev": true
+        }
+      }
+    },
     "safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -15632,6 +16508,12 @@
       "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
       "dev": true
     },
+    "set-cookie-parser": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.0.tgz",
+      "integrity": "sha512-cHMAtSXilfyBePduZEBVPTCftTQWz6ehWJD5YNUg4mqvRosrrjKbo4WS8JkB0/RxonMoohHm7cOGH60mDkRQ9w==",
+      "dev": true
+    },
     "setimmediate": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -15857,6 +16739,15 @@
       "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
       "dev": true
     },
+    "strict-event-emitter": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz",
+      "integrity": "sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ==",
+      "dev": true,
+      "requires": {
+        "events": "^3.3.0"
+      }
+    },
     "string_decoder": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -16105,6 +16996,12 @@
       "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==",
       "dev": true
     },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
     "thunky": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -16475,6 +17372,15 @@
         "minimalistic-assert": "^1.0.0"
       }
     },
+    "wcwidth": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+      "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+      "dev": true,
+      "requires": {
+        "defaults": "^1.0.3"
+      }
+    },
     "webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
diff --git a/deadlock-plugins/deadlock-extension/package.json b/deadlock-plugins/deadlock-extension/package.json
index 9c16b2ec..71d9b3fc 100644
--- a/deadlock-plugins/deadlock-extension/package.json
+++ b/deadlock-plugins/deadlock-extension/package.json
@@ -130,6 +130,7 @@
     "eslint": "^7.32.0",
     "glob": "^7.2.0",
     "jest": "^28.1.0",
+    "msw": "^0.42.1",
     "prettier": "2.6.2",
     "terser-webpack-plugin": "^5.2.5",
     "ts-jest": "^28.0.4",
diff --git a/deadlock-plugins/deadlock-extension/src/core/api.service.ts b/deadlock-plugins/deadlock-extension/src/core/api.service.ts
index 5ed61fc5..4bbdee73 100644
--- a/deadlock-plugins/deadlock-extension/src/core/api.service.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/api.service.ts
@@ -3,6 +3,7 @@ import { API_QUERY_REFERER, API_URL } from '../config';
 import { GiteaPublicProperties } from '../model/giteaPublicProperties.model';
 import { SshKeyPair } from '../model/sshKeyPair.model';
 import { User } from '../model/user.model';
+import { extensionError as error } from '../recorder/utils/log';
 import Controller from './controller';
 import ExtensionStore from './extensionStore';
 import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection';
@@ -97,7 +98,7 @@ export default class ApiService {
           return await this.onInvalidRefreshToken(originalConfig);
         }
       } else {
-        console.error(`[DEADLOCK-EXTENSION] - Error not handled: ${_error}`);
+        error(`Error not handled: ${_error}`);
         throw _error;
       }
     }
diff --git a/deadlock-plugins/deadlock-extension/src/core/controller.ts b/deadlock-plugins/deadlock-extension/src/core/controller.ts
index 2531da63..14597460 100644
--- a/deadlock-plugins/deadlock-extension/src/core/controller.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/controller.ts
@@ -1,4 +1,3 @@
-import * as vscode from 'vscode';
 import { KEYCLOAK_DEVICE_AUTH_URL, KEYCLOAK_TOKEN_CREATE_URL, KEYCLOAK_USER_INFO_URL } from '../config';
 import BriefingView from '../view/briefingView';
 import QuickSetupView from '../view/quickSetupView';
@@ -6,12 +5,13 @@ import { chooseMissionWorkdirCommand, CommandHandler, openUrlInBrowserCommand }
 import ExtensionStore from './extensionStore';
 import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection';
 import ApiService from './api.service';
-import { createSshKeyFiles, isSshKeyPairExist } from './sshKeyManager';
 import { GiteaPublicProperties } from '../model/giteaPublicProperties.model';
 import { User } from '../model/user.model';
 import { Mission } from './mission/mission';
 import { MissionDevContainer } from './mission/missionDevContainer';
 import { extensionLog as log } from '../recorder/utils/log';
+import { commands, ExtensionContext, Uri, window } from 'vscode';
+import { createSshKeyFiles, isSshKeyPairExist } from './sshKeyManager';
 
 export default class Controller {
   public connection: KeycloakOAuth2DeviceFlowConnection;
@@ -21,7 +21,7 @@ export default class Controller {
   private extensionStore: ExtensionStore;
   private apiService: ApiService;
 
-  constructor(private context: vscode.ExtensionContext) {
+  constructor(private context: ExtensionContext) {
     this.extensionStore = ExtensionStore.getInstance(context);
     this.briefingView = new BriefingView();
     this.quickSetupView = new QuickSetupView(context.extensionUri);
@@ -39,8 +39,8 @@ export default class Controller {
 
   private async init() {
     const that = this;
-    vscode.window.registerUriHandler({
-      handleUri(uri: vscode.Uri) {
+    window.registerUriHandler({
+      handleUri(uri: Uri) {
         const queryParams = new URLSearchParams(uri.query);
         const action = queryParams.get('action');
         // TODO: Should we follow eslint no-case-declarations ?
@@ -51,7 +51,7 @@ export default class Controller {
         switch (action) {
           case 'open-mission':
             if (!missionId || !missionVersion) {
-              vscode.window.showErrorMessage('Identifiant ou version de la mission incorrect');
+              window.showErrorMessage('Identifiant ou version de la mission incorrect');
             } else {
               that.launchMission(missionId, missionVersion);
             }
@@ -59,7 +59,7 @@ export default class Controller {
             break;
 
           default:
-            vscode.window.showInformationMessage('Aucune action trouvée!');
+            window.showInformationMessage('Aucune action trouvée!');
         }
       },
     });
@@ -70,8 +70,8 @@ export default class Controller {
   async chooseMissionWorkdir() {
     const actualMissionWorkDir = this.extensionStore.getMissionWorkdir();
 
-    const folderUri = await vscode.window.showOpenDialog({
-      defaultUri: actualMissionWorkDir ? vscode.Uri.file(actualMissionWorkDir) : undefined,
+    const folderUri = await window.showOpenDialog({
+      defaultUri: actualMissionWorkDir ? Uri.file(actualMissionWorkDir) : undefined,
       canSelectFolders: true,
       canSelectFiles: false,
       title: 'Choisis le dossier qui contiendra tes missions',
@@ -108,21 +108,21 @@ export default class Controller {
   }
 
   public static openBrowserWithUrl(url: string) {
-    vscode.commands.executeCommand(openUrlInBrowserCommand.cmd, vscode.Uri.parse(url));
+    commands.executeCommand(openUrlInBrowserCommand.cmd, Uri.parse(url));
   }
 
   public async launchMission(missionId: string, missionVersion: string) {
-    vscode.window.showInformationMessage(`vous lancez la mission ${missionId}`);
+    window.showInformationMessage(`vous lancez la mission ${missionId}`);
     const hadMissionWorkdir = this.extensionStore.getMissionWorkdir() !== undefined;
     if (!hadMissionWorkdir) {
-      await vscode.commands.executeCommand(chooseMissionWorkdirCommand.cmd);
+      await commands.executeCommand(chooseMissionWorkdirCommand.cmd);
     }
 
     const hadBeenConnected = (await this.extensionStore.getAccessToken()) !== undefined;
 
     if (!hadBeenConnected) {
       await this.authenticate();
-      vscode.window.showInformationMessage('Connexion validée');
+      window.showInformationMessage('Connexion validée');
     }
 
     const mission = new Mission(missionId, missionVersion);
@@ -133,7 +133,7 @@ export default class Controller {
 
     const missionDevcontainer = new MissionDevContainer(missionsWorkdir, user, mission, giteaPublicProperties);
 
-    vscode.window.showInformationMessage(
+    window.showInformationMessage(
       'opening inside folder ' + this.extensionStore.getMissionWorkdir()! + '/' + missionId,
     );
 
diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts
index 98f98ec2..3d97d9aa 100644
--- a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts
@@ -1,50 +1,60 @@
+import { Response } from 'node-fetch';
+import { KEYCLOAK_DEVICE_AUTH_URL, KEYCLOAK_TOKEN_CREATE_URL, KEYCLOAK_USER_INFO_URL } from '../config';
 import KeycloakOAuth2DeviceFlowConnection from '../core/keycloakOAuth2DeviceFlowConnection';
+import { setConnected, setPending, setRefused, test_token } from '../test/service/mocks/handlers';
 
 describe('KeycloakOAuth2DeviceFlowConnection', () => {
   let connection: KeycloakOAuth2DeviceFlowConnection;
-  let tokens: {
-    accessToken: string;
-    refreshToken: string;
-  };
-  let refreshedTokens: {
-    accessToken: string;
-    refreshToken: string;
-  };
 
-  const openLinkPlaceholder = (message: string) => console.log(message);
+  const log = (message: string) => console.log(`[DEADLOCK TEST] [KEYCLOAK AUTH]: ## ${message} ##`);
 
-  beforeEach(async () => {
-    connection = new KeycloakOAuth2DeviceFlowConnection(
-      'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/auth/device',
-      'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token',
-      'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo',
-    );
+  it('should be pending', async () => {
+    setPending();
+    let response: Response | undefined = undefined;
+    await connection.getToken({ openLink: log }, true).catch(async (e) => {
+      response = e;
+      expect(response?.status).toStrictEqual(400);
 
-    tokens = await connection.getToken({ openLink: openLinkPlaceholder });
-
-    refreshedTokens = await connection.getToken({
-      refreshToken: tokens.refreshToken,
-      openLink: openLinkPlaceholder,
+      expect(await response?.json()).toStrictEqual({
+        error: 'authorization_pending',
+        error_description: 'The authorization request is still pending',
+      });
     });
+    expect(response).toBeInstanceOf(Response);
   });
 
-  it('"12345" is not a valid token', async () => {
-    const isValid = await connection.tokenIsValid('12345');
-    expect(isValid).toBe(false);
-  });
+  it('should succeed', async () => {
+    setConnected();
+    let response: Response | undefined = undefined;
+    await connection.getToken({ openLink: log }, true).catch(async (e) => {
+      response = e;
+      expect(response?.status).toStrictEqual(200);
 
-  it('"" is not a valid token', async () => {
-    const isValid = await connection.tokenIsValid('');
-    expect(isValid).toBe(false);
+      expect(await response?.json()).toStrictEqual(test_token);
+    });
+    expect(response).toBeInstanceOf(Response);
   });
 
-  it('"accessToken" is a valid token', async () => {
-    const isValid = await connection.tokenIsValid(tokens.accessToken);
-    expect(isValid).toBe(true);
+  it('should failed with device not valid', async () => {
+    setRefused();
+    let response: Response | undefined = undefined;
+    await connection.getToken({ openLink: log }, true).catch(async (e) => {
+      response = e;
+      expect(response?.status).toStrictEqual(400);
+
+      expect(await response?.json()).toStrictEqual({
+        error: 'invalid_grant',
+        error_description: 'Device code not valid',
+      });
+    });
+    expect(response).toBeInstanceOf(Response);
   });
 
-  it('"refreshedAccessToken" is a valid token', async () => {
-    const isValid = await connection.tokenIsValid(refreshedTokens.accessToken);
-    expect(isValid).toBe(true);
+  beforeEach(async () => {
+    connection = new KeycloakOAuth2DeviceFlowConnection(
+      KEYCLOAK_DEVICE_AUTH_URL,
+      KEYCLOAK_TOKEN_CREATE_URL,
+      KEYCLOAK_USER_INFO_URL,
+    );
   });
 });
diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts
index 88d6d20f..a28b7197 100644
--- a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts
@@ -66,20 +66,23 @@ export default class KeycloakOAuth2DeviceFlowConnection {
     }
   }
 
-  public async getToken(args: { refreshToken?: string; openLink?: (link: string) => void }) {
+  public async getToken(args: { refreshToken?: string; openLink?: (link: string) => void }, enableTest = false) {
     const { refreshToken, openLink } = args;
     if (refreshToken !== undefined) {
-      await this.createUserAuthentication({
-        url: this.tokenUrl,
-        body: (() => {
-          const params = new URLSearchParams();
-          params.append('response_type', 'token');
-          params.append('client_id', 'deadlock-desktop');
-          params.append('grant_type', 'refresh_token');
-          params.append('refresh_token', refreshToken);
-          return params.toString();
-        })(),
-      });
+      await this.createUserAuthentication(
+        {
+          url: this.tokenUrl,
+          body: (() => {
+            const params = new URLSearchParams();
+            params.append('response_type', 'token');
+            params.append('client_id', 'deadlock-desktop');
+            params.append('grant_type', 'refresh_token');
+            params.append('refresh_token', refreshToken);
+            return params.toString();
+          })(),
+        },
+        enableTest,
+      );
       return Promise.resolve({ accessToken: this.accessToken, refreshToken: this.refreshToken });
     }
     if (!this.deviceIsRegistered()) {
@@ -88,17 +91,20 @@ export default class KeycloakOAuth2DeviceFlowConnection {
     try {
       if (!openLink) throw new Error('You neeed to provide a way to open a link for oauth device flow protocol');
       openLink(this.deviceAuthorizationRequestResponse.verification_uri_complete!);
-      await this.createUserAuthentication({
-        url: this.tokenUrl,
-        body: (() => {
-          const params = new URLSearchParams();
-          params.append('response_type', 'token');
-          params.append('device_code', this.deviceAuthorizationRequestResponse.device_code ?? '');
-          params.append('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
-          params.append('client_id', 'deadlock-desktop');
-          return params.toString();
-        })(),
-      });
+      await this.createUserAuthentication(
+        {
+          url: this.tokenUrl,
+          body: (() => {
+            const params = new URLSearchParams();
+            params.append('response_type', 'token');
+            params.append('device_code', this.deviceAuthorizationRequestResponse.device_code ?? '');
+            params.append('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
+            params.append('client_id', 'deadlock-desktop');
+            return params.toString();
+          })(),
+        },
+        enableTest,
+      );
       return Promise.resolve({ accessToken: this.accessToken, refreshToken: this.refreshToken });
     } catch (error) {
       return Promise.reject(error);
@@ -141,11 +147,13 @@ export default class KeycloakOAuth2DeviceFlowConnection {
    * @param args API URL endpoint to ask for a new token & request form parameters
    * @throw Error containing Keycloak API `error_code`
    */
-  private async createUserAuthentication(args: { url: string; body: string }) {
+  private async createUserAuthentication(args: { url: string; body: string }, enableTest = false) {
     const { url, body } = args;
     let userAuthenticationRequestResponseCode = HttpStatusCode.I_AM_A_TEAPOT;
     while (userAuthenticationRequestResponseCode !== HttpStatusCode.OK) {
       log(`[POST] ${url} \n ${body}`);
+      console.log(body);
+
       const userAuthenticationRequestResponse: Response = await fetch(url, {
         method: 'POST',
         headers: {
@@ -156,6 +164,7 @@ export default class KeycloakOAuth2DeviceFlowConnection {
       });
       userAuthenticationRequestResponseCode = userAuthenticationRequestResponse.status;
       log(` Status ${userAuthenticationRequestResponseCode}`);
+      if (enableTest) throw userAuthenticationRequestResponse;
       switch (userAuthenticationRequestResponseCode) {
         case HttpStatusCode.BAD_REQUEST: {
           await this.onUserAuthenticationBadRequest(userAuthenticationRequestResponse);
diff --git a/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts b/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts
index 65340b05..c3fdbb76 100644
--- a/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts
@@ -1,32 +1,30 @@
 import { existsSync, promises } from 'fs';
 import { userSshKeyFolderPath } from './config';
 
-export default class SshKeyManager {
-  public isSshKeyPairExist(): boolean {
-    return this.isPrivateKeyExist() && this.isPublicKeyExist();
+  export function isSshKeyPairExist(): boolean {
+    return isPrivateKeyExist() && isPublicKeyExist();
   }
 
-  private isPublicKeyExist(): boolean {
+  function isPublicKeyExist(): boolean {
     return existsSync(`${userSshKeyFolderPath}/id_rsa.pub`);
   }
 
-  private isPrivateKeyExist(): boolean {
+  function isPrivateKeyExist(): boolean {
     return existsSync(`${userSshKeyFolderPath}/id_rsa`);
   }
 
-  public async createSshKeyFiles(publicKey: string, privateKey: string) {
-    await this.createSshKeyFolderIfNotExist(userSshKeyFolderPath);
+  export async function createSshKeyFiles(publicKey: string, privateKey: string) {
+    await createSshKeyFolderIfNotExist(userSshKeyFolderPath);
     await promises.writeFile(`${userSshKeyFolderPath}/id_rsa.pub`, publicKey);
 
     await promises.writeFile(`${userSshKeyFolderPath}/id_rsa`, privateKey, { mode: 0o600 });
   }
-  private async createSshKeyFolderIfNotExist(sshKeyFolderPath) {
-    if (!this.isSshKeyFolderExist(sshKeyFolderPath)) {
+  async function createSshKeyFolderIfNotExist(sshKeyFolderPath) {
+    if (!isSshKeyFolderExist(sshKeyFolderPath)) {
       await promises.mkdir(sshKeyFolderPath, { recursive: true });
     }
   }
 
-  private isSshKeyFolderExist(sshKeyFolderPath: string) {
+  export function isSshKeyFolderExist(sshKeyFolderPath: string) {
     return existsSync(sshKeyFolderPath);
   }
-}
diff --git a/deadlock-plugins/deadlock-extension/src/extension.ts b/deadlock-plugins/deadlock-extension/src/extension.ts
index 4a921731..15a2cf75 100644
--- a/deadlock-plugins/deadlock-extension/src/extension.ts
+++ b/deadlock-plugins/deadlock-extension/src/extension.ts
@@ -1,4 +1,4 @@
-import * as vscode from 'vscode';
+import { window, ExtensionContext, workspace } from 'vscode';
 import Controller from './core/controller';
 import { extensionError as error } from './recorder/utils/log';
 import { DepNodeProvider } from './theia/deadlockPanel';
@@ -6,15 +6,15 @@ import UserConfigTheia from './theia/userConfigTheia';
 
 export const userConfig = new UserConfigTheia();
 
-export async function activate(context: vscode.ExtensionContext) {
-  vscode.window.showInformationMessage('Bienvenue sur Deadlock!');
+export async function activate(context: ExtensionContext) {
+  window.showInformationMessage('Bienvenue sur Deadlock!');
 
   new Controller(context);
 
-  const workspaceFolders = vscode.workspace.workspaceFolders?.toString() ?? '';
-  if (!workspaceFolders) vscode.window.showInformationMessage('Pas de répertoires ouverts');
+  const workspaceFolders = workspace.workspaceFolders?.toString() ?? '';
+  if (!workspaceFolders) window.showInformationMessage('Pas de répertoires ouverts');
   const deadlockPanelProvider = new DepNodeProvider(workspaceFolders);
-  vscode.window.registerTreeDataProvider('deadlockPanel', deadlockPanelProvider);
+  window.registerTreeDataProvider('deadlockPanel', deadlockPanelProvider);
 
   try {
     await userConfig.init();
diff --git a/deadlock-plugins/deadlock-extension/src/test/service/mocks/handlers.ts b/deadlock-plugins/deadlock-extension/src/test/service/mocks/handlers.ts
new file mode 100644
index 00000000..906b8a0d
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/test/service/mocks/handlers.ts
@@ -0,0 +1,113 @@
+import { rest } from 'msw';
+
+export const test_token = {
+  access_token:
+    'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4MjdZYW5fWllISElSclJiUDlnenFFSHhMbm5RMUNUaUNqenZTY19hUFpRIn0.eyJleHAiOjE2NTQ3OTg0MzQsImlhdCI6MTY1NDc2MjkwNCwiYXV0aF90aW1lIjoxNjU0NzYyNDM0LCJqdGkiOiJiNmM3MDJhOS0wYzgzLTQwYTUtODRlNS05ZGY4YmIxMzBhNjEiLCJpc3MiOiJodHRwczovL2F1dGguZGV2LmRlYWRsb2NrLmlvL2F1dGgvcmVhbG1zL0RlYWRsb2NrIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImEzOTRiM2IxLTQxZmEtNGNiZS1iZGVkLTExNTcyNDBmOTI0ZSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImRlYWRsb2NrLWRlc2t0b3AiLCJzZXNzaW9uX3N0YXRlIjoiYTkwMWZlOGUtZTY2Yy00NTQxLWI1MDEtM2YzM2EwNDhlYjg1IiwiYWNyIjoiMCIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWRlYWRsb2NrIiwicHJvZmVzc29yIiwiY2FuZGlkYXRlIiwibWFuYWdlciIsIm9mZmxpbmVfYWNjZXNzIiwiYWRtaW4iLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImE5MDFmZThlLWU2NmMtNDU0MS1iNTAxLTNmMzNhMDQ4ZWI4NSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ik1vaGFtZWQgQVpJS0lPVSIsInByZWZlcnJlZF91c2VybmFtZSI6Im1hemlraW91QHRha2ltYS5mciIsImdpdmVuX25hbWUiOiJNb2hhbWVkIiwiZmFtaWx5X25hbWUiOiJBWklLSU9VIiwiZW1haWwiOiJtYXppa2lvdUB0YWtpbWEuZnIifQ.UZDq12D6HVkseSotAC_kCEBpx7jqyndHO7-WEd3MslJKz_O1F8phrZO_zvEhYaZ_DO2yWQJFPeHYyMcNMFXsM1COfvpT-KZV3a4LXQNstDP7Eyqw-VzGpOUIaY4NZDI-2HgkclB3o5lnwdmzn9TBTE0LGZ2Pkz4j0_giC7R161RP2w4vqhL-VwC19OBlwqvqiIT-Rhj7IeQabj1MCJyQR-ocWwh8ensSdd1_sptz4J7YKxqzpw2eFVEWIaWcxKu7mSD5uU7teNRTlRoDOuJKXBTqzsjYV4A3CB0_m6cRkRj7zgxPfxJtdL0jFAKvZEykS6DINVvZSz-QS_3ROdO1uA',
+  expires_in: 35530,
+  refresh_expires_in: 1800,
+  refresh_token:
+    'eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjNzc3MGE3Yy01NTg0LTQxOWUtYWU3OS05ZjgxNGE2NWEwMWIifQ.eyJleHAiOjE2NTQ3NjQ3MDQsImlhdCI6MTY1NDc2MjkwNCwianRpIjoiNWUwZjk2NWYtZGFlNy00ZGZmLWJmMjctMWFiY2IyN2YzNGRhIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmRldi5kZWFkbG9jay5pby9hdXRoL3JlYWxtcy9EZWFkbG9jayIsImF1ZCI6Imh0dHBzOi8vYXV0aC5kZXYuZGVhZGxvY2suaW8vYXV0aC9yZWFsbXMvRGVhZGxvY2siLCJzdWIiOiJhMzk0YjNiMS00MWZhLTRjYmUtYmRlZC0xMTU3MjQwZjkyNGUiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiZGVhZGxvY2stZGVza3RvcCIsInNlc3Npb25fc3RhdGUiOiJhOTAxZmU4ZS1lNjZjLTQ1NDEtYjUwMS0zZjMzYTA0OGViODUiLCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJzaWQiOiJhOTAxZmU4ZS1lNjZjLTQ1NDEtYjUwMS0zZjMzYTA0OGViODUifQ.01pGEulqMn3VIvOFAAj2AJn7uJGhf586cWRz0dzHb9w',
+  token_type: 'Bearer',
+  'not-before-policy': 0,
+  session_state: 'a901fe8e-e66c-4541-b501-3f33a048eb85',
+  scope: 'profile email',
+};
+
+const device_codes = {
+  pending_device_code: 'ePutZ5EuR2zdrUwsBvp6b7hHb_qXK_F82N-bQPo4Q28',
+  connected_device_code: 'ePutZ5EuR2zdrUwsBvp6b7hHb_qXK_F82N-bQPo4Q29',
+  refused_device_code: 'ePutZ5EuR2zdrUwsBvp6b7hHb_qXK_F82N-bQPo4Q30',
+};
+
+enum State {
+  Pending,
+  Connected,
+  Refused,
+}
+
+let state = State.Pending;
+
+function getDeviceCode() {
+  switch (state) {
+    case State.Pending:
+      return device_codes.pending_device_code;
+    case State.Connected:
+      return device_codes.connected_device_code;
+    case State.Refused:
+      return device_codes.refused_device_code;
+  }
+}
+
+export function setPending() {
+  state = State.Pending;
+}
+
+export function setConnected() {
+  state = State.Connected;
+}
+
+export function setRefused() {
+  state = State.Refused;
+}
+
+function mockCreateDeviceAuthorization() {
+  return rest.post(
+    'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/auth/device',
+    (req, res, ctx) => {
+      if (req.body === 'client_id=deadlock-desktop')
+        return res(
+          ctx.status(200),
+          ctx.json({
+            device_code: getDeviceCode(),
+            user_code: 'DHTJ-BJBF',
+            verification_uri: 'https://auth.dev.deadlock.io/auth/realms/Deadlock/device',
+            verification_uri_complete: 'https://auth.dev.deadlock.io/auth/realms/Deadlock/device?user_code=DHTJ-BJBF',
+            expires_in: 2592000,
+            interval: 1,
+          }),
+        );
+    },
+  );
+}
+
+function createUrlSearchParams(device_code: string) {
+  const params = new URLSearchParams();
+  params.append('response_type', 'token');
+  params.append('device_code', device_code);
+  params.append('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
+  params.append('client_id', 'deadlock-desktop');
+  return params;
+}
+
+function mockCreateUserAuthenticationPending() {
+  const pending = createUrlSearchParams(device_codes.pending_device_code).toString();
+  const connected = createUrlSearchParams(device_codes.connected_device_code).toString();
+  const refused = createUrlSearchParams(device_codes.refused_device_code).toString();
+  return rest.post(
+    'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token',
+    (req, res, ctx) => {
+      switch (req.body) {
+        case pending:
+          return res(
+            ctx.status(400),
+            ctx.json({
+              error: 'authorization_pending',
+              error_description: 'The authorization request is still pending',
+            }),
+          );
+        case connected:
+          return res(ctx.status(200), ctx.json(test_token));
+        case refused:
+          return res(
+            ctx.status(400),
+            ctx.json({
+              error: 'invalid_grant',
+              error_description: 'Device code not valid',
+            }),
+          );
+      }
+    },
+  );
+}
+
+export const handlers = [mockCreateDeviceAuthorization(), mockCreateUserAuthenticationPending()];
diff --git a/deadlock-plugins/deadlock-extension/src/test/service/mocks/server.ts b/deadlock-plugins/deadlock-extension/src/test/service/mocks/server.ts
new file mode 100644
index 00000000..e52fee0a
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/test/service/mocks/server.ts
@@ -0,0 +1,4 @@
+import { setupServer } from 'msw/node';
+import { handlers } from './handlers';
+
+export const server = setupServer(...handlers);
diff --git a/deadlock-plugins/deadlock-extension/src/test/setupTests.ts b/deadlock-plugins/deadlock-extension/src/test/setupTests.ts
new file mode 100644
index 00000000..b4874d6d
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/test/setupTests.ts
@@ -0,0 +1,5 @@
+import { server } from './service/mocks/server';
+
+beforeAll(() => server.listen());
+afterEach(() => server.resetHandlers());
+afterAll(() => server.close());
diff --git a/deadlock-plugins/deadlock-extension/src/theia/deadlockPanel.ts b/deadlock-plugins/deadlock-extension/src/theia/deadlockPanel.ts
index b687e753..3c81acd8 100644
--- a/deadlock-plugins/deadlock-extension/src/theia/deadlockPanel.ts
+++ b/deadlock-plugins/deadlock-extension/src/theia/deadlockPanel.ts
@@ -1,16 +1,13 @@
-import * as path from 'path';
-import * as vscode from 'vscode';
-import { TreeItemCollapsibleState } from 'vscode';
+import { join } from 'path';
+import { Command, Event, EventEmitter, TreeDataProvider, TreeItem, TreeItemCollapsibleState } from 'vscode';
 import { openBriefingCommand } from './command';
 
 const documentationLabel = 'Documentation';
 
-export class DepNodeProvider implements vscode.TreeDataProvider<Action> {
-  private _onDidChangeTreeData: vscode.EventEmitter<Action | undefined | void> = new vscode.EventEmitter<
-    Action | undefined | void
-  >();
+export class DepNodeProvider implements TreeDataProvider<Action> {
+  private _onDidChangeTreeData: EventEmitter<Action | undefined | void> = new EventEmitter<Action | undefined | void>();
 
-  readonly onDidChangeTreeData: vscode.Event<Action | undefined | void> = this._onDidChangeTreeData.event;
+  readonly onDidChangeTreeData: Event<Action | undefined | void> = this._onDidChangeTreeData.event;
 
   constructor(private workspaceRoot: string) {}
 
@@ -22,7 +19,7 @@ export class DepNodeProvider implements vscode.TreeDataProvider<Action> {
     return [new Action(documentationLabel, TreeItemCollapsibleState.Collapsed, 'folder')];
   }
 
-  getTreeItem(element: Action): vscode.TreeItem {
+  getTreeItem(element: Action): TreeItem {
     return element;
   }
 
@@ -39,12 +36,12 @@ export class DepNodeProvider implements vscode.TreeDataProvider<Action> {
   }
 }
 
-export class Action extends vscode.TreeItem {
+export class Action extends TreeItem {
   constructor(
     public readonly label: string,
-    public readonly collapsibleState: vscode.TreeItemCollapsibleState,
+    public readonly collapsibleState: TreeItemCollapsibleState,
     public readonly iconName: string,
-    public readonly command?: vscode.Command,
+    public readonly command?: Command,
   ) {
     super(label, collapsibleState);
     this.tooltip = label;
@@ -52,8 +49,8 @@ export class Action extends vscode.TreeItem {
   }
 
   iconPath = {
-    light: path.join(__filename, '..', '..', 'resources', 'light', `${this.iconName}.svg`),
-    dark: path.join(__filename, '..', '..', 'resources', 'dark', `${this.iconName}.svg`),
+    light: join(__filename, '..', '..', 'resources', 'light', `${this.iconName}.svg`),
+    dark: join(__filename, '..', '..', 'resources', 'dark', `${this.iconName}.svg`),
   };
 
   contextValue = 'dependency';
diff --git a/deadlock-plugins/deadlock-extension/src/theia/userConfigTheia.ts b/deadlock-plugins/deadlock-extension/src/theia/userConfigTheia.ts
index 387a8089..7bdeb915 100644
--- a/deadlock-plugins/deadlock-extension/src/theia/userConfigTheia.ts
+++ b/deadlock-plugins/deadlock-extension/src/theia/userConfigTheia.ts
@@ -1,11 +1,11 @@
 import UserConfig from '../core/userConfig';
-import * as vscode from 'vscode';
 
 import { USER_CHALLENGE_PATH } from '../core/config';
+import { Uri, workspace } from 'vscode';
 
 export default class UserConfigTheia extends UserConfig {
   async loadText(): Promise<string> {
-    const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.parse(USER_CHALLENGE_PATH));
+    const textDocument = await workspace.openTextDocument(Uri.parse(USER_CHALLENGE_PATH));
     return Promise.resolve(textDocument.getText());
   }
 }
diff --git a/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts b/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts
index f0e004b5..a87f244b 100644
--- a/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts
+++ b/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts
@@ -1,4 +1,4 @@
-import * as vscode from 'vscode';
+import { commands, Uri } from 'vscode';
 import { authenticateCommand, chooseMissionWorkdirCommand } from '../core/commandHandler';
 import ExtensionStore from '../core/extensionStore';
 import { extensionLog as log } from '../recorder/utils/log';
@@ -8,11 +8,11 @@ import { WebviewBase } from './webviewBase';
 export const quickSetupId = 'quickSetup';
 
 export default class QuickSetupView extends WebviewBase {
-  private extensionUri: vscode.Uri;
+  private extensionUri: Uri;
   private extensionStore: ExtensionStore;
   private _isAlreadyConnected: boolean;
 
-  constructor(extensionUri: vscode.Uri) {
+  constructor(extensionUri: Uri) {
     super(quickSetupId, 'QuickSetup', openQuickSetupCommand);
     this.extensionUri = extensionUri;
     this.extensionStore = ExtensionStore.getInstance();
@@ -121,10 +121,10 @@ export default class QuickSetupView extends WebviewBase {
   onMessageReceive(message: any): void {
     switch (message.command) {
       case 'launchChooseMissionWorkdirAction':
-        vscode.commands.executeCommand(chooseMissionWorkdirCommand.cmd).then(() => this.reload());
+        commands.executeCommand(chooseMissionWorkdirCommand.cmd).then(() => this.reload());
         return;
       case 'openAuthenticationPageAction':
-        vscode.commands.executeCommand(authenticateCommand.cmd);
+        commands.executeCommand(authenticateCommand.cmd);
         return;
     }
   }
diff --git a/deadlock-plugins/deadlock-extension/src/view/view.ts b/deadlock-plugins/deadlock-extension/src/view/view.ts
index f4851a03..53c17be4 100644
--- a/deadlock-plugins/deadlock-extension/src/view/view.ts
+++ b/deadlock-plugins/deadlock-extension/src/view/view.ts
@@ -1,4 +1,4 @@
-import * as vscode from 'vscode';
+import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from 'vscode';
 
 /**
  * @deprecated use #webviewBase instead of
@@ -6,13 +6,13 @@ import * as vscode from 'vscode';
 export default abstract class View {
   private static views: Map<string, View> = new Map();
 
-  public currentWebviewPanel: vscode.WebviewPanel | undefined;
-  private _disposables: vscode.Disposable[] = [];
+  public currentWebviewPanel: WebviewPanel | undefined;
+  private _disposables: Disposable[] = [];
   private isRegisteredOnWebviewPanelSerializer: boolean;
 
   constructor(
     id: string,
-    private readonly context: vscode.ExtensionContext,
+    private readonly context: ExtensionContext,
     public readonly panelName: string,
     public readonly title: string,
   ) {
@@ -59,7 +59,7 @@ export default abstract class View {
       (message) => {
         switch (message.command) {
           case 'alert':
-            vscode.window.showErrorMessage(message.text);
+            window.showErrorMessage(message.text);
             return;
         }
       },
@@ -73,7 +73,7 @@ export default abstract class View {
   }
 
   public createOrShow() {
-    const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
+    const column = window.activeTextEditor ? window.activeTextEditor.viewColumn : undefined;
 
     // If we already have a panel, show it.
     if (this.currentWebviewPanel) {
@@ -82,21 +82,16 @@ export default abstract class View {
     }
 
     // Otherwise, create a new panel.
-    this.currentWebviewPanel = vscode.window.createWebviewPanel(
-      this.panelName,
-      this.title,
-      column || vscode.ViewColumn.One,
-      {
-        // Enable javascript in the webview
-        enableScripts: true,
-
-        // And restrict the webview to only loading content from our extension's `media` directory.
-        // for now we don't need to include the media folder, because React bundle is not used
-        // localResourceRoots: [
-        // vscode.Uri.file(path.join(this.extensionPath, 'media')),
-        // ],
-      },
-    );
+    this.currentWebviewPanel = window.createWebviewPanel(this.panelName, this.title, column || ViewColumn.One, {
+      // Enable javascript in the webview
+      enableScripts: true,
+
+      // And restrict the webview to only loading content from our extension's `media` directory.
+      // for now we don't need to include the media folder, because React bundle is not used
+      // localResourceRoots: [
+      // Uri.file(path.join(this.extensionPath, 'media')),
+      // ],
+    });
     // update first time to force render of webviewPanel
     this.update();
     this.init(this.currentWebviewPanel);
@@ -136,10 +131,10 @@ export default abstract class View {
   private _getHtmlForWebview() {
     // /!\ EXPERIMENTAL /!\
     // Local path to main script run in the webview
-    // const scriptPathOnDisk = vscode.Uri.file(
+    // const scriptPathOnDisk = Uri.file(
     // path.join(this.extensionPath, 'media', 'static/bundle.js')
     // );
-    // const cssPathOnDisk = vscode.Uri.file(
+    // const cssPathOnDisk = Uri.file(
     // path.join(this.extensionPath, 'media', 'static/bundle.css')
     // );
 
diff --git a/deadlock-plugins/deadlock-extension/tsconfig.json b/deadlock-plugins/deadlock-extension/tsconfig.json
index 966fc640..c55dd073 100644
--- a/deadlock-plugins/deadlock-extension/tsconfig.json
+++ b/deadlock-plugins/deadlock-extension/tsconfig.json
@@ -2,7 +2,7 @@
   "compilerOptions": {
     "module": "commonjs",
     "target": "es2019",
-    "lib": ["ES2019"],
+    "lib": ["ES2019", "dom"],
     "sourceMap": true,
     "outDir": "dist",
     "strict": true,
-- 
GitLab