feat(testing): add test for app > listen (#4971)
* feat(testing): add test for app > listen * Update test/unit/node/app.test.ts * refactor: modernize listen fn in app * wip * fix: update error message * fixup: remove console.log * fixup: use clearAllMocks once in beforeAll * fix: move chmod after socket listen * fixup: formatting * Update src/node/app.ts Co-authored-by: Jonathan Yu <jonathan@coder.com> * Update src/node/app.ts Co-authored-by: Asher <ash@coder.com> Co-authored-by: Jonathan Yu <jonathan@coder.com> Co-authored-by: Asher <ash@coder.com>
This commit is contained in:
parent
91cabbc246
commit
86c8590bd5
@ -22,40 +22,35 @@ export interface App extends Disposable {
|
|||||||
server: http.Server
|
server: http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
const listen = (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
|
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
|
||||||
return new Promise<void>(async (resolve, reject) => {
|
if (socket) {
|
||||||
|
try {
|
||||||
|
await fs.unlink(socket)
|
||||||
|
} catch (error: any) {
|
||||||
|
handleArgsSocketCatchError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await new Promise<void>(async (resolve, reject) => {
|
||||||
server.on("error", reject)
|
server.on("error", reject)
|
||||||
|
|
||||||
const onListen = () => {
|
const onListen = () => {
|
||||||
// Promise resolved earlier so this is an unrelated error.
|
// Promise resolved earlier so this is an unrelated error.
|
||||||
server.off("error", reject)
|
server.off("error", reject)
|
||||||
server.on("error", (err) => util.logError(logger, "http server error", err))
|
server.on("error", (err) => util.logError(logger, "http server error", err))
|
||||||
|
resolve()
|
||||||
if (socket && mode) {
|
|
||||||
fs.chmod(socket, mode)
|
|
||||||
.then(resolve)
|
|
||||||
.catch((err) => {
|
|
||||||
util.logError(logger, "socket chmod", err)
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket) {
|
if (socket) {
|
||||||
try {
|
|
||||||
await fs.unlink(socket)
|
|
||||||
} catch (error: any) {
|
|
||||||
handleArgsSocketCatchError(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
server.listen(socket, onListen)
|
server.listen(socket, onListen)
|
||||||
} else {
|
} else {
|
||||||
// [] is the correct format when using :: but Node errors with them.
|
// [] is the correct format when using :: but Node errors with them.
|
||||||
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
|
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// NOTE@jsjoeio: we need to chmod after the server is finished
|
||||||
|
// listening. Otherwise, the socket may not have been created yet.
|
||||||
|
if (socket && mode) {
|
||||||
|
await fs.chmod(socket, mode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -138,6 +133,6 @@ export const handleServerError = (resolved: boolean, err: Error, reject: (err: E
|
|||||||
*/
|
*/
|
||||||
export const handleArgsSocketCatchError = (error: any) => {
|
export const handleArgsSocketCatchError = (error: any) => {
|
||||||
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
|
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
|
||||||
logger.error(error.message ? error.message : error)
|
throw Error(error.message ? error.message : error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { promises } from "fs"
|
|||||||
import * as http from "http"
|
import * as http from "http"
|
||||||
import * as https from "https"
|
import * as https from "https"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { createApp, ensureAddress, handleArgsSocketCatchError, handleServerError } from "../../../src/node/app"
|
import { createApp, ensureAddress, handleArgsSocketCatchError, handleServerError, listen } from "../../../src/node/app"
|
||||||
import { OptionalString, setDefaults } from "../../../src/node/cli"
|
import { OptionalString, setDefaults } from "../../../src/node/cli"
|
||||||
import { generateCertificate } from "../../../src/node/util"
|
import { generateCertificate } from "../../../src/node/util"
|
||||||
import { clean, mockLogger, getAvailablePort, tmpdir } from "../../utils/helpers"
|
import { clean, mockLogger, getAvailablePort, tmpdir } from "../../utils/helpers"
|
||||||
@ -201,31 +201,33 @@ describe("handleArgsSocketCatchError", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should log an error if its not an NodeJS.ErrnoException", () => {
|
it("should log an error if its not an NodeJS.ErrnoException", () => {
|
||||||
const error = new Error()
|
const message = "other message"
|
||||||
|
const error = new Error(message)
|
||||||
|
|
||||||
handleArgsSocketCatchError(error)
|
expect(() => {
|
||||||
|
handleArgsSocketCatchError(error)
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
}).toThrowError(error)
|
||||||
expect(logger.error).toHaveBeenCalledWith(error)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should log an error if its not an NodeJS.ErrnoException (and the error has a message)", () => {
|
it("should log an error if its not an NodeJS.ErrnoException (and the error has a message)", () => {
|
||||||
const errorMessage = "handleArgsSocketCatchError Error"
|
const errorMessage = "handleArgsSocketCatchError Error"
|
||||||
const error = new Error(errorMessage)
|
const error = new Error(errorMessage)
|
||||||
|
|
||||||
handleArgsSocketCatchError(error)
|
expect(() => {
|
||||||
|
handleArgsSocketCatchError(error)
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
}).toThrowError(error)
|
||||||
expect(logger.error).toHaveBeenCalledWith(errorMessage)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not log an error if its a iNodeJS.ErrnoException", () => {
|
it("should not log an error if its a NodeJS.ErrnoException", () => {
|
||||||
const error: NodeJS.ErrnoException = new Error()
|
const code = "ENOENT"
|
||||||
error.code = "ENOENT"
|
const error: NodeJS.ErrnoException = new Error(code)
|
||||||
|
error.code = code
|
||||||
|
|
||||||
handleArgsSocketCatchError(error)
|
handleArgsSocketCatchError(error)
|
||||||
|
|
||||||
expect(logger.error).toHaveBeenCalledTimes(0)
|
expect(() => {
|
||||||
|
handleArgsSocketCatchError(error)
|
||||||
|
}).not.toThrowError()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should log an error if the code is not ENOENT (and the error has a message)", () => {
|
it("should log an error if the code is not ENOENT (and the error has a message)", () => {
|
||||||
@ -234,19 +236,50 @@ describe("handleArgsSocketCatchError", () => {
|
|||||||
error.code = "EACCESS"
|
error.code = "EACCESS"
|
||||||
error.message = errorMessage
|
error.message = errorMessage
|
||||||
|
|
||||||
handleArgsSocketCatchError(error)
|
expect(() => {
|
||||||
|
handleArgsSocketCatchError(error)
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
}).toThrowError(error)
|
||||||
expect(logger.error).toHaveBeenCalledWith(errorMessage)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should log an error if the code is not ENOENT", () => {
|
it("should log an error if the code is not ENOENT", () => {
|
||||||
const error: NodeJS.ErrnoException = new Error()
|
const code = "EACCESS"
|
||||||
error.code = "EACCESS"
|
const error: NodeJS.ErrnoException = new Error(code)
|
||||||
|
error.code = code
|
||||||
|
|
||||||
handleArgsSocketCatchError(error)
|
expect(() => {
|
||||||
|
handleArgsSocketCatchError(error)
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
}).toThrowError(error)
|
||||||
expect(logger.error).toHaveBeenCalledWith(error)
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("listen", () => {
|
||||||
|
let tmpDirPath: string
|
||||||
|
let mockServer: http.Server
|
||||||
|
|
||||||
|
const testName = "listen"
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await clean(testName)
|
||||||
|
mockLogger()
|
||||||
|
tmpDirPath = await tmpdir(testName)
|
||||||
|
mockServer = http.createServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockServer.close()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error if a directory is passed in instead of a file", async () => {
|
||||||
|
const errorMessage = "EISDIR: illegal operation on a directory, unlink"
|
||||||
|
const port = await getAvailablePort()
|
||||||
|
const mockArgs = { port, host: "0.0.0.0", socket: tmpDirPath }
|
||||||
|
|
||||||
|
try {
|
||||||
|
await listen(mockServer, mockArgs)
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error)
|
||||||
|
expect((error as any).message).toMatch(errorMessage)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { logger } from "@coder/logger"
|
import { logger } from "@coder/logger"
|
||||||
import { mockLogger } from "../../utils/helpers"
|
import { mockLogger } from "../../utils/helpers"
|
||||||
import * as semver from "semver"
|
import * as semver from "semver"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
describe("constants", () => {
|
describe("constants", () => {
|
||||||
let constants: typeof import("../../../src/node/constants")
|
let constants: typeof import("../../../src/node/constants")
|
||||||
@ -20,14 +21,18 @@ describe("constants", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
mockLogger()
|
mockLogger()
|
||||||
jest.mock("../../../package.json", () => mockPackageJson, { virtual: true })
|
jest.mock(path.resolve(__dirname, "../../../package.json"), () => mockPackageJson, { virtual: true })
|
||||||
jest.mock("../../../vendor/modules/code-oss-dev/package.json", () => mockCodePackageJson, { virtual: true })
|
jest.mock(
|
||||||
|
path.resolve(__dirname, "../../../vendor/modules/code-oss-dev/package.json"),
|
||||||
|
() => mockCodePackageJson,
|
||||||
|
{ virtual: true },
|
||||||
|
)
|
||||||
constants = require("../../../src/node/constants")
|
constants = require("../../../src/node/constants")
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.clearAllMocks()
|
|
||||||
jest.resetModules()
|
jest.resetModules()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -106,13 +111,17 @@ describe("constants", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
jest.mock("../../../package.json", () => mockPackageJson, { virtual: true })
|
jest.clearAllMocks()
|
||||||
jest.mock("../../../vendor/modules/code-oss-dev/package.json", () => mockCodePackageJson, { virtual: true })
|
jest.mock(path.resolve(__dirname, "../../../package.json"), () => mockPackageJson, { virtual: true })
|
||||||
|
jest.mock(
|
||||||
|
path.resolve(__dirname, "../../../vendor/modules/code-oss-dev/package.json"),
|
||||||
|
() => mockCodePackageJson,
|
||||||
|
{ virtual: true },
|
||||||
|
)
|
||||||
constants = require("../../../src/node/constants")
|
constants = require("../../../src/node/constants")
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.clearAllMocks()
|
|
||||||
jest.resetModules()
|
jest.resetModules()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user