diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75f5960..99d99cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,12 +18,11 @@ jobs: registry-url: https://registry.npmjs.org/ - name: Install the dependencies - run: npm i - - # - name: Clone the toolkit - # run: | - # git clone https://github.com/microsoftgraph/microsoft-graph-toolkit - # npm run snippets + run: npm install + + - name: Build and run automation tests + run: xvfb-run -a npm run test - - name: Publish + - name: Publish to VSCode Marketplace + if: ${{ github.ref == 'refs/heads/main' }} run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index a62435a..6d15de1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ ] }, { - "name": "Extension Tests", + "name": "Test Extension", "type": "extensionHost", "request": "launch", "args": [ diff --git a/package-lock.json b/package-lock.json index 8b95060..7a7e5e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-rest-control", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-rest-control", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "dependencies": { "tcp-port-used": "^1.0.2" diff --git a/src/demo.py b/sampleWorkspace/demo.py similarity index 100% rename from src/demo.py rename to sampleWorkspace/demo.py diff --git a/src/samples.http b/sampleWorkspace/samples.http similarity index 100% rename from src/samples.http rename to sampleWorkspace/samples.http diff --git a/src/extension.ts b/src/extension.ts index 7f70310..a89e977 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,8 @@ import * as vscode from "vscode"; import * as tcpPorts from "tcp-port-used"; import { Logger } from "./services/logger"; -import http, { IncomingMessage, ServerResponse } from "http"; +import { IncomingMessage, ServerResponse } from "http"; +import * as http from "http"; import { AddressInfo } from "net"; import { ControlRequest } from "./models/controlRequest"; import { processRemoteControlRequest } from "./services/requestProcessor"; @@ -9,7 +10,7 @@ import { processRemoteControlRequest } from "./services/requestProcessor"; let server: http.Server; let statusbar: vscode.StatusBarItem; -const EXTENSION_ID: string = "dpar39.vscode-rest-control"; +export const EXTENSION_ID: string = "dpar39.vscode-rest-control"; const SETTINGS_NAME: string = "restRemoteControl"; const startHttpServer = async ( @@ -47,6 +48,7 @@ const startHttpServer = async ( const reqData = JSON.parse(body); processRemoteControlRequest(reqData as ControlRequest) .then((data) => { + res.setHeader("Content-Type", "application/json"); res.write(JSON.stringify(data || {})); res.end(); }) @@ -72,7 +74,6 @@ const startHttpServer = async ( context.environmentVariableCollection.replace("REMOTE_CONTROL_PORT", `${verifiedPort}`); statusbar.text = `$(plug) RC Port: ${verifiedPort}`; statusbar.tooltip = listeningMessage; - }); }; @@ -92,7 +93,7 @@ function setupRestControl(context: vscode.ExtensionContext) { Logger.info("VSCode REST Control is now active!"); } else { statusbar.tooltip = `REST remote control has been disabled via setting "${SETTINGS_NAME}.enabled": false`; - statusbar.text = '$(debug-disconnect) RC Disabled'; + statusbar.text = "$(debug-disconnect) RC Disabled"; server?.close(); Logger.info("VSCode REST Control has been disabled via settings!"); } diff --git a/src/services/requestProcessor.ts b/src/services/requestProcessor.ts index 17296d2..9eab976 100644 --- a/src/services/requestProcessor.ts +++ b/src/services/requestProcessor.ts @@ -7,13 +7,13 @@ import { ControlRequest } from "../models/controlRequest"; function createObject(arg: any): any { if (typeof arg === "object" && arg.hasOwnProperty("__type__")) { const type = arg.__type__; - if (type == "Uri") { + if (type === "Uri") { return vscode.Uri.parse(arg.args[0]); - } else if (type == "Position") { + } else if (type === "Position") { return new vscode.Position(arg.args[0], arg.args[1]); - } else if (type == "Range") { + } else if (type === "Range") { return new vscode.Range(arg.args[0], arg.args[1], arg.args[2], arg.args[3]); - } else if (type == "Location") { + } else if (type === "Location") { return new vscode.Location(createObject(arg.args[0]), createObject(arg.args[1])); } } @@ -50,7 +50,7 @@ export async function processRemoteControlRequest(requestObject: ControlRequest) throw new Error("No active terminal available"); } - if (command == "custom.startDebugSession") { + if (command === "custom.startDebugSession") { const folder = args[0]; const debugConfig = args[1]; const success = await vscode.debug.startDebugging(folder, debugConfig); @@ -85,18 +85,18 @@ export async function processRemoteControlRequest(requestObject: ControlRequest) return await quickPick(args[0]); } - if (command == "custom.goToFileLineCharacter") { + if (command === "custom.goToFileLineCharacter") { const filePath = args[0]; let uri = null; if (!path.isAbsolute(filePath)) { let candidates = await vscode.workspace.findFiles(args[0]); - if (candidates.length == 1) { + if (candidates.length === 1) { uri = candidates[0]; } } else { uri = vscode.Uri.file(filePath); } - if (uri == null) { + if (uri === null) { throw new Error(`Unable to locate file: ${filePath}`); } const position = new vscode.Position(args[1] || 0, args[2] || 0); diff --git a/src/test/runTest.ts b/src/test/runTest.ts new file mode 100644 index 0000000..11793bf --- /dev/null +++ b/src/test/runTest.ts @@ -0,0 +1,23 @@ +import * as path from "path"; + +import { runTests } from "@vscode/test-electron"; + +async function main() { + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); + + // The path to the extension test script + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, "./suite/index"); + + // Download VS Code, unzip it and run the integration test + await runTests({ extensionDevelopmentPath, extensionTestsPath}); + } catch (err) { + console.error("Failed to run tests"); + process.exit(1); + } +} + +main(); diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts new file mode 100644 index 0000000..6bccaf9 --- /dev/null +++ b/src/test/suite/extension.test.ts @@ -0,0 +1,27 @@ +import * as assert from "assert"; +import * as path from "path"; +import { EXTENSION_ID } from "../../extension"; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +import * as vscode from "vscode"; +import { makeRequest } from "./sendPostRequest"; +// import * as myExtension from '../extension'; + +suite("Extension Test Suite", () => { + suiteSetup(async () => { + const sampleWorkspace = path.resolve(__dirname, "../../../sampleWorkspace"); + let uri = vscode.Uri.file(sampleWorkspace); + await vscode.commands.executeCommand("vscode.openFolder", uri); + }); + + suiteTeardown(() => {}); + + test("check can list extensions", async () => { + const extensionIds: string[] = (await makeRequest( + "custom.listInstalledExtensions", + [] + )) as string[]; + assert(extensionIds.includes(EXTENSION_ID)); + }); +}); diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts new file mode 100644 index 0000000..3e791cf --- /dev/null +++ b/src/test/suite/index.ts @@ -0,0 +1,37 @@ +import * as path from "path"; +import * as Mocha from "mocha"; +import * as glob from "glob"; + +export function run(): Promise { + // Create the mocha test + const mocha = new Mocha({ + ui: "tdd", + }); + + const testsRoot = path.resolve(__dirname, ".."); + + return new Promise((c, e) => { + glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run((failures) => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + console.error(err); + e(err); + } + }); + }); +} diff --git a/src/test/suite/sendPostRequest.ts b/src/test/suite/sendPostRequest.ts new file mode 100644 index 0000000..9485c55 --- /dev/null +++ b/src/test/suite/sendPostRequest.ts @@ -0,0 +1,26 @@ +import * as http from "http"; + +export async function makeRequest(command: string, args: any[], port: number = 37100) { + return new Promise((resolve, reject) => { + const req = http.request( + { + method: "POST", + hostname: "localhost", + port: port, + path: "/", + }, + (res) => { + const chunks: any[] = []; + res.on("data", (data) => chunks.push(data)); + res.on("end", () => { + const resBody = Buffer.concat(chunks); + resolve(JSON.parse(resBody.toString())); + }); + } + ); + req.on("error", reject); + const body = JSON.stringify({ command: command, args: args }); + req.write(body); + req.end(); + }); +} diff --git a/tsconfig.json b/tsconfig.json index ac061d4..59e3ea4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,17 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "out", - "lib": [ - "es6" - ], - "sourceMap": true, - "rootDir": "src", - "strict": true, - "esModuleInterop": true - }, - "exclude": [ - "node_modules", - ".vscode-test" - ] -} + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": [ + "es2020" + ], + "outDir": "out", + "sourceMap": true, + "rootDir": "src", + "strict": true, + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file