Skip to content

Commit 86c8590

Browse files
jsjoeiojawnsycode-asher
authored
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 <[email protected]> * Update src/node/app.ts Co-authored-by: Asher <[email protected]> Co-authored-by: Jonathan Yu <[email protected]> Co-authored-by: Asher <[email protected]>
1 parent 91cabbc commit 86c8590

File tree

3 files changed

+88
-51
lines changed

3 files changed

+88
-51
lines changed

src/node/app.ts

+17-22
Original file line numberDiff line numberDiff line change
@@ -22,40 +22,35 @@ export interface App extends Disposable {
2222
server: http.Server
2323
}
2424

25-
const listen = (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
26-
return new Promise<void>(async (resolve, reject) => {
25+
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
26+
if (socket) {
27+
try {
28+
await fs.unlink(socket)
29+
} catch (error: any) {
30+
handleArgsSocketCatchError(error)
31+
}
32+
}
33+
await new Promise<void>(async (resolve, reject) => {
2734
server.on("error", reject)
28-
2935
const onListen = () => {
3036
// Promise resolved earlier so this is an unrelated error.
3137
server.off("error", reject)
3238
server.on("error", (err) => util.logError(logger, "http server error", err))
33-
34-
if (socket && mode) {
35-
fs.chmod(socket, mode)
36-
.then(resolve)
37-
.catch((err) => {
38-
util.logError(logger, "socket chmod", err)
39-
reject(err)
40-
})
41-
} else {
42-
resolve()
43-
}
39+
resolve()
4440
}
45-
4641
if (socket) {
47-
try {
48-
await fs.unlink(socket)
49-
} catch (error: any) {
50-
handleArgsSocketCatchError(error)
51-
}
52-
5342
server.listen(socket, onListen)
5443
} else {
5544
// [] is the correct format when using :: but Node errors with them.
5645
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
5746
}
5847
})
48+
49+
// NOTE@jsjoeio: we need to chmod after the server is finished
50+
// listening. Otherwise, the socket may not have been created yet.
51+
if (socket && mode) {
52+
await fs.chmod(socket, mode)
53+
}
5954
}
6055

6156
/**
@@ -138,6 +133,6 @@ export const handleServerError = (resolved: boolean, err: Error, reject: (err: E
138133
*/
139134
export const handleArgsSocketCatchError = (error: any) => {
140135
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
141-
logger.error(error.message ? error.message : error)
136+
throw Error(error.message ? error.message : error)
142137
}
143138
}

test/unit/node/app.test.ts

+56-23
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { promises } from "fs"
33
import * as http from "http"
44
import * as https from "https"
55
import * as path from "path"
6-
import { createApp, ensureAddress, handleArgsSocketCatchError, handleServerError } from "../../../src/node/app"
6+
import { createApp, ensureAddress, handleArgsSocketCatchError, handleServerError, listen } from "../../../src/node/app"
77
import { OptionalString, setDefaults } from "../../../src/node/cli"
88
import { generateCertificate } from "../../../src/node/util"
99
import { clean, mockLogger, getAvailablePort, tmpdir } from "../../utils/helpers"
@@ -201,31 +201,33 @@ describe("handleArgsSocketCatchError", () => {
201201
})
202202

203203
it("should log an error if its not an NodeJS.ErrnoException", () => {
204-
const error = new Error()
204+
const message = "other message"
205+
const error = new Error(message)
205206

206-
handleArgsSocketCatchError(error)
207-
208-
expect(logger.error).toHaveBeenCalledTimes(1)
209-
expect(logger.error).toHaveBeenCalledWith(error)
207+
expect(() => {
208+
handleArgsSocketCatchError(error)
209+
}).toThrowError(error)
210210
})
211211

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

216-
handleArgsSocketCatchError(error)
217-
218-
expect(logger.error).toHaveBeenCalledTimes(1)
219-
expect(logger.error).toHaveBeenCalledWith(errorMessage)
216+
expect(() => {
217+
handleArgsSocketCatchError(error)
218+
}).toThrowError(error)
220219
})
221220

222-
it("should not log an error if its a iNodeJS.ErrnoException", () => {
223-
const error: NodeJS.ErrnoException = new Error()
224-
error.code = "ENOENT"
221+
it("should not log an error if its a NodeJS.ErrnoException", () => {
222+
const code = "ENOENT"
223+
const error: NodeJS.ErrnoException = new Error(code)
224+
error.code = code
225225

226226
handleArgsSocketCatchError(error)
227227

228-
expect(logger.error).toHaveBeenCalledTimes(0)
228+
expect(() => {
229+
handleArgsSocketCatchError(error)
230+
}).not.toThrowError()
229231
})
230232

231233
it("should log an error if the code is not ENOENT (and the error has a message)", () => {
@@ -234,19 +236,50 @@ describe("handleArgsSocketCatchError", () => {
234236
error.code = "EACCESS"
235237
error.message = errorMessage
236238

237-
handleArgsSocketCatchError(error)
238-
239-
expect(logger.error).toHaveBeenCalledTimes(1)
240-
expect(logger.error).toHaveBeenCalledWith(errorMessage)
239+
expect(() => {
240+
handleArgsSocketCatchError(error)
241+
}).toThrowError(error)
241242
})
242243

243244
it("should log an error if the code is not ENOENT", () => {
244-
const error: NodeJS.ErrnoException = new Error()
245-
error.code = "EACCESS"
245+
const code = "EACCESS"
246+
const error: NodeJS.ErrnoException = new Error(code)
247+
error.code = code
246248

247-
handleArgsSocketCatchError(error)
249+
expect(() => {
250+
handleArgsSocketCatchError(error)
251+
}).toThrowError(error)
252+
})
253+
})
248254

249-
expect(logger.error).toHaveBeenCalledTimes(1)
250-
expect(logger.error).toHaveBeenCalledWith(error)
255+
describe("listen", () => {
256+
let tmpDirPath: string
257+
let mockServer: http.Server
258+
259+
const testName = "listen"
260+
261+
beforeEach(async () => {
262+
await clean(testName)
263+
mockLogger()
264+
tmpDirPath = await tmpdir(testName)
265+
mockServer = http.createServer()
266+
})
267+
268+
afterEach(() => {
269+
mockServer.close()
270+
jest.clearAllMocks()
271+
})
272+
273+
it("should throw an error if a directory is passed in instead of a file", async () => {
274+
const errorMessage = "EISDIR: illegal operation on a directory, unlink"
275+
const port = await getAvailablePort()
276+
const mockArgs = { port, host: "0.0.0.0", socket: tmpDirPath }
277+
278+
try {
279+
await listen(mockServer, mockArgs)
280+
} catch (error) {
281+
expect(error).toBeInstanceOf(Error)
282+
expect((error as any).message).toMatch(errorMessage)
283+
}
251284
})
252285
})

test/unit/node/constants.test.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { logger } from "@coder/logger"
22
import { mockLogger } from "../../utils/helpers"
33
import * as semver from "semver"
4+
import path from "path"
45

56
describe("constants", () => {
67
let constants: typeof import("../../../src/node/constants")
@@ -20,14 +21,18 @@ describe("constants", () => {
2021
}
2122

2223
beforeAll(() => {
24+
jest.clearAllMocks()
2325
mockLogger()
24-
jest.mock("../../../package.json", () => mockPackageJson, { virtual: true })
25-
jest.mock("../../../vendor/modules/code-oss-dev/package.json", () => mockCodePackageJson, { virtual: true })
26+
jest.mock(path.resolve(__dirname, "../../../package.json"), () => mockPackageJson, { virtual: true })
27+
jest.mock(
28+
path.resolve(__dirname, "../../../vendor/modules/code-oss-dev/package.json"),
29+
() => mockCodePackageJson,
30+
{ virtual: true },
31+
)
2632
constants = require("../../../src/node/constants")
2733
})
2834

2935
afterAll(() => {
30-
jest.clearAllMocks()
3136
jest.resetModules()
3237
})
3338

@@ -106,13 +111,17 @@ describe("constants", () => {
106111
}
107112

108113
beforeAll(() => {
109-
jest.mock("../../../package.json", () => mockPackageJson, { virtual: true })
110-
jest.mock("../../../vendor/modules/code-oss-dev/package.json", () => mockCodePackageJson, { virtual: true })
114+
jest.clearAllMocks()
115+
jest.mock(path.resolve(__dirname, "../../../package.json"), () => mockPackageJson, { virtual: true })
116+
jest.mock(
117+
path.resolve(__dirname, "../../../vendor/modules/code-oss-dev/package.json"),
118+
() => mockCodePackageJson,
119+
{ virtual: true },
120+
)
111121
constants = require("../../../src/node/constants")
112122
})
113123

114124
afterAll(() => {
115-
jest.clearAllMocks()
116125
jest.resetModules()
117126
})
118127

0 commit comments

Comments
 (0)