Skip to content

feat(testing): add test for app > listen #4971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

39 changes: 17 additions & 22 deletions src/node/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,33 @@ export interface App extends Disposable {
server: http.Server
}

const listen = (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
return new Promise<void>(async (resolve, reject) => {
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
if (socket) {
try {
await fs.unlink(socket)
} catch (error: any) {
handleArgsSocketCatchError(error)
}
}
await new Promise<void>(async (resolve, reject) => {
server.on("error", reject)

const onListen = () => {
// Promise resolved earlier so this is an unrelated error.
server.off("error", reject)
server.on("error", (err) => util.logError(logger, "http server error", err))

if (socket && mode) {
fs.chmod(socket, mode)
.then(resolve)
.catch((err) => {
util.logError(logger, "socket chmod", err)
reject(err)
})
} else {
resolve()
}
resolve()
}

if (socket) {
try {
await fs.unlink(socket)
} catch (error: any) {
handleArgsSocketCatchError(error)
}

server.listen(socket, onListen)
} else {
// [] is the correct format when using :: but Node errors with them.
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
}
if (socket && mode) {
await fs.chmod(socket, mode).catch((err) => {
util.logError(logger, "socket chmod", err)
})
}
})
}

Expand Down Expand Up @@ -137,7 +131,8 @@ export const handleServerError = (resolved: boolean, err: Error, reject: (err: E
* test this logic more easily.
*/
export const handleArgsSocketCatchError = (error: any) => {
console.log("hello")
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
logger.error(error.message ? error.message : error)
throw Error(error.message ? error.message : error)
}
}
79 changes: 56 additions & 23 deletions test/unit/node/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { promises } from "fs"
import * as http from "http"
import * as https from "https"
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 { generateCertificate } from "../../../src/node/util"
import { clean, mockLogger, getAvailablePort, tmpdir } from "../../utils/helpers"
Expand Down Expand Up @@ -201,31 +201,33 @@ describe("handleArgsSocketCatchError", () => {
})

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(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(error)
expect(() => {
handleArgsSocketCatchError(error)
}).toThrowError(error)
})

it("should log an error if its not an NodeJS.ErrnoException (and the error has a message)", () => {
const errorMessage = "handleArgsSocketCatchError Error"
const error = new Error(errorMessage)

handleArgsSocketCatchError(error)

expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(errorMessage)
expect(() => {
handleArgsSocketCatchError(error)
}).toThrowError(error)
})

it("should not log an error if its a iNodeJS.ErrnoException", () => {
const error: NodeJS.ErrnoException = new Error()
error.code = "ENOENT"
it("should not log an error if its a NodeJS.ErrnoException", () => {
const code = "ENOENT"
const error: NodeJS.ErrnoException = new Error(code)
error.code = code

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)", () => {
Expand All @@ -234,19 +236,50 @@ describe("handleArgsSocketCatchError", () => {
error.code = "EACCESS"
error.message = errorMessage

handleArgsSocketCatchError(error)

expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(errorMessage)
expect(() => {
handleArgsSocketCatchError(error)
}).toThrowError(error)
})

it("should log an error if the code is not ENOENT", () => {
const error: NodeJS.ErrnoException = new Error()
error.code = "EACCESS"
const code = "EACCESS"
const error: NodeJS.ErrnoException = new Error(code)
error.code = code

handleArgsSocketCatchError(error)
expect(() => {
handleArgsSocketCatchError(error)
}).toThrowError(error)
})
})

expect(logger.error).toHaveBeenCalledTimes(1)
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)
}
})
})
19 changes: 15 additions & 4 deletions test/unit/node/constants.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { logger } from "@coder/logger"
import { mockLogger } from "../../utils/helpers"
import * as semver from "semver"
import path from "path"

describe("constants", () => {
let constants: typeof import("../../../src/node/constants")
Expand All @@ -20,9 +21,14 @@ describe("constants", () => {
}

beforeAll(() => {
jest.clearAllMocks()
mockLogger()
jest.mock("../../../package.json", () => mockPackageJson, { virtual: true })
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")
})

Expand Down Expand Up @@ -106,8 +112,13 @@ describe("constants", () => {
}

beforeAll(() => {
jest.mock("../../../package.json", () => mockPackageJson, { virtual: true })
jest.mock("../../../vendor/modules/code-oss-dev/package.json", () => mockCodePackageJson, { virtual: true })
jest.clearAllMocks()
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")
})

Expand Down