fix: wrap socket in proxy before passing to vscode (#4840)

* chore: add ipc hook to e2e script

* refactor: allow codeServerArgs in e2e tests

* feat: add --cert e2e extension test

* fix: wrap websocket in proxy

* fixup: remvoe ignoreHTTPSErrors

* fixup: make codeServerArgs readonly

* fixup! add back ignoreHTTPSErrors
This commit is contained in:
Joe Previte 2022-02-15 14:51:42 -07:00 committed by GitHub
parent b26cce589f
commit e3e9f052c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 34 additions and 13 deletions

View File

@ -17,7 +17,7 @@
"release:github-draft": "./ci/build/release-github-draft.sh", "release:github-draft": "./ci/build/release-github-draft.sh",
"release:github-assets": "./ci/build/release-github-assets.sh", "release:github-assets": "./ci/build/release-github-assets.sh",
"release:prep": "./ci/build/release-prep.sh", "release:prep": "./ci/build/release-prep.sh",
"test:e2e": "./ci/dev/test-e2e.sh", "test:e2e": "VSCODE_IPC_HOOK_CLI= ./ci/dev/test-e2e.sh",
"test:standalone-release": "./ci/build/test-standalone-release.sh", "test:standalone-release": "./ci/build/test-standalone-release.sh",
"test:unit": "./ci/dev/test-unit.sh --forceExit --detectOpenHandles", "test:unit": "./ci/dev/test-unit.sh --forceExit --detectOpenHandles",
"test:scripts": "./ci/dev/test-scripts.sh", "test:scripts": "./ci/dev/test-scripts.sh",

View File

@ -5,6 +5,7 @@ import { logError } from "../../common/util"
import { toVsCodeArgs } from "../cli" import { toVsCodeArgs } from "../cli"
import { isDevMode } from "../constants" import { isDevMode } from "../constants"
import { authenticated, ensureAuthenticated, redirect, self } from "../http" import { authenticated, ensureAuthenticated, redirect, self } from "../http"
import { SocketProxyProvider } from "../socket"
import { loadAMDModule } from "../util" import { loadAMDModule } from "../util"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
import { errorHandler } from "./errors" import { errorHandler } from "./errors"
@ -13,6 +14,7 @@ export class CodeServerRouteWrapper {
/** Assigned in `ensureCodeServerLoaded` */ /** Assigned in `ensureCodeServerLoaded` */
private _codeServerMain!: CodeServerLib.IServerAPI private _codeServerMain!: CodeServerLib.IServerAPI
private _wsRouterWrapper = WsRouter() private _wsRouterWrapper = WsRouter()
private _socketProxyProvider = new SocketProxyProvider()
public router = express.Router() public router = express.Router()
public get wsRouter() { public get wsRouter() {
@ -77,9 +79,10 @@ export class CodeServerRouteWrapper {
} }
private $proxyWebsocket = async (req: WebsocketRequest) => { private $proxyWebsocket = async (req: WebsocketRequest) => {
this._codeServerMain.handleUpgrade(req, req.socket) const wrappedSocket = await this._socketProxyProvider.createProxy(req.ws)
this._codeServerMain.handleUpgrade(req, wrappedSocket)
req.socket.resume() req.ws.resume()
} }
//#endregion //#endregion
@ -130,5 +133,6 @@ export class CodeServerRouteWrapper {
dispose() { dispose() {
this._codeServerMain?.dispose() this._codeServerMain?.dispose()
this._socketProxyProvider.stop()
} }
} }

View File

@ -9,10 +9,15 @@ import { CodeServer, CodeServerPage } from "./models/CodeServer"
* *
* If `includeCredentials` is `true` page requests will be authenticated. * If `includeCredentials` is `true` page requests will be authenticated.
*/ */
export const describe = (name: string, includeCredentials: boolean, fn: (codeServer: CodeServer) => void) => { export const describe = (
name: string,
includeCredentials: boolean,
codeServerArgs: string[],
fn: (codeServer: CodeServer) => void,
) => {
test.describe(name, () => { test.describe(name, () => {
// This will spawn on demand so nothing is necessary on before. // This will spawn on demand so nothing is necessary on before.
const codeServer = new CodeServer(name) const codeServer = new CodeServer(name, codeServerArgs)
// Kill code-server after the suite has ended. This may happen even without // Kill code-server after the suite has ended. This may happen even without
// doing it explicitly but it seems prudent to be sure. // doing it explicitly but it seems prudent to be sure.
@ -36,6 +41,9 @@ export const describe = (name: string, includeCredentials: boolean, fn: (codeSer
authenticated: includeCredentials, authenticated: includeCredentials,
// This provides a cookie that authenticates with code-server. // This provides a cookie that authenticates with code-server.
storageState: includeCredentials ? storageState : {}, storageState: includeCredentials ? storageState : {},
// NOTE@jsjoeio some tests use --cert which uses a self-signed certificate
// without this option, those tests will fail.
ignoreHTTPSErrors: true,
}) })
fn(codeServer) fn(codeServer)

View File

@ -1,6 +1,6 @@
import { describe, test, expect } from "./baseFixture" import { describe, test, expect } from "./baseFixture"
describe("CodeServer", true, () => { describe("CodeServer", true, [], () => {
test("should navigate to home page", async ({ codeServerPage }) => { test("should navigate to home page", async ({ codeServerPage }) => {
// We navigate codeServer before each test // We navigate codeServer before each test
// and we start the test with a storage state // and we start the test with a storage state

View File

@ -1,6 +1,6 @@
import { describe, test } from "./baseFixture" import { describe, test } from "./baseFixture"
describe("Extensions", true, () => { function runTestExtensionTests() {
// This will only work if the test extension is loaded into code-server. // This will only work if the test extension is loaded into code-server.
test("should have access to VSCODE_PROXY_URI", async ({ codeServerPage }) => { test("should have access to VSCODE_PROXY_URI", async ({ codeServerPage }) => {
const address = await codeServerPage.address() const address = await codeServerPage.address()
@ -9,4 +9,12 @@ describe("Extensions", true, () => {
await codeServerPage.page.waitForSelector(`text=${address}/proxy/{{port}}`) await codeServerPage.page.waitForSelector(`text=${address}/proxy/{{port}}`)
}) })
}
describe("Extensions", true, [], () => {
runTestExtensionTests()
})
describe("Extensions with --cert", true, ["--cert"], () => {
runTestExtensionTests()
}) })

View File

@ -2,7 +2,7 @@ import { describe, test, expect } from "./baseFixture"
// This test is to make sure the globalSetup works as expected // This test is to make sure the globalSetup works as expected
// meaning globalSetup ran and stored the storageState // meaning globalSetup ran and stored the storageState
describe("globalSetup", true, () => { describe("globalSetup", true, [], () => {
test("should keep us logged in using the storageState", async ({ codeServerPage }) => { test("should keep us logged in using the storageState", async ({ codeServerPage }) => {
// Make sure the editor actually loaded // Make sure the editor actually loaded
expect(await codeServerPage.isEditorVisible()).toBe(true) expect(await codeServerPage.isEditorVisible()).toBe(true)

View File

@ -1,7 +1,7 @@
import { PASSWORD } from "../utils/constants" import { PASSWORD } from "../utils/constants"
import { describe, test, expect } from "./baseFixture" import { describe, test, expect } from "./baseFixture"
describe("login", false, () => { describe("login", false, [], () => {
test("should see the login page", async ({ codeServerPage }) => { test("should see the login page", async ({ codeServerPage }) => {
// It should send us to the login page // It should send us to the login page
expect(await codeServerPage.page.title()).toBe("code-server login") expect(await codeServerPage.page.title()).toBe("code-server login")

View File

@ -1,7 +1,7 @@
// NOTE@jsjoeio commenting out until we can figure out what's wrong // NOTE@jsjoeio commenting out until we can figure out what's wrong
// import { describe, test, expect } from "./baseFixture" // import { describe, test, expect } from "./baseFixture"
// describe("logout", true, () => { // describe("logout", true, [], () => {
// test("should be able logout", async ({ codeServerPage }) => { // test("should be able logout", async ({ codeServerPage }) => {
// // Recommended by Playwright for async navigation // // Recommended by Playwright for async navigation
// // https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151 // // https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151

View File

@ -31,7 +31,7 @@ export class CodeServer {
public readonly logger: Logger public readonly logger: Logger
private closed = false private closed = false
constructor(name: string) { constructor(name: string, private readonly codeServerArgs: string[]) {
this.logger = logger.named(name) this.logger = logger.named(name)
} }
@ -78,6 +78,7 @@ export class CodeServer {
"node", "node",
[ [
process.env.CODE_SERVER_TEST_ENTRY || ".", process.env.CODE_SERVER_TEST_ENTRY || ".",
...this.codeServerArgs,
// Using port zero will spawn on a random port. // Using port zero will spawn on a random port.
"--bind-addr", "--bind-addr",
"127.0.0.1:0", "127.0.0.1:0",

View File

@ -1,6 +1,6 @@
import { describe, test, expect } from "./baseFixture" import { describe, test, expect } from "./baseFixture"
describe("Open Help > About", true, () => { describe("Open Help > About", true, [], () => {
test("should see code-server version in about dialog", async ({ codeServerPage }) => { test("should see code-server version in about dialog", async ({ codeServerPage }) => {
// Open using the menu. // Open using the menu.
await codeServerPage.navigateMenus(["Help", "About"]) await codeServerPage.navigateMenus(["Help", "About"])

View File

@ -4,7 +4,7 @@ import util from "util"
import { clean, tmpdir } from "../utils/helpers" import { clean, tmpdir } from "../utils/helpers"
import { describe, expect, test } from "./baseFixture" import { describe, expect, test } from "./baseFixture"
describe("Integrated Terminal", true, () => { describe("Integrated Terminal", true, [], () => {
const testName = "integrated-terminal" const testName = "integrated-terminal"
test.beforeAll(async () => { test.beforeAll(async () => {
await clean(testName) await clean(testName)