Skip to content

Commit c9c5c54

Browse files
authored
feat: add tests for update.ts (#4835)
* feat: add isAddressInfo helper function * feat(update): add test for rejection UpdateProvider * feat: add more tests for UpdateProvider * fixup! move isAddressInfo, add .address check * fixup! remove extra writeHead * fixup! use -1 in redirect logic * fixup! remove unnecessary String call * fixup! use /latest for redirect * fixup! use match group for regex * fixup!: replace match/split logic
1 parent 102478b commit c9c5c54

File tree

2 files changed

+119
-4
lines changed

2 files changed

+119
-4
lines changed

test/unit/node/update.test.ts

+105-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as http from "http"
2+
import { logger } from "@coder/logger"
3+
import { AddressInfo } from "net"
24
import * as path from "path"
35
import { SettingsProvider, UpdateSettings } from "../../../src/node/settings"
46
import { LatestResponse, UpdateProvider } from "../../../src/node/update"
5-
import { clean, mockLogger, tmpdir } from "../../utils/helpers"
7+
import { clean, isAddressInfo, mockLogger, tmpdir } from "../../utils/helpers"
68

79
describe("update", () => {
810
let version = "1.0.0"
@@ -23,6 +25,46 @@ describe("update", () => {
2325
return response.end(JSON.stringify(latest))
2426
}
2527

28+
if (request.url === "/reject-status-code") {
29+
response.writeHead(500)
30+
return response.end("rejected status code test")
31+
}
32+
33+
if (request.url === "/no-location-header") {
34+
response.writeHead(301, "testing", {
35+
location: "",
36+
})
37+
return response.end("rejected status code test")
38+
}
39+
40+
if (request.url === "/with-location-header") {
41+
response.writeHead(301, "testing", {
42+
location: "/latest",
43+
})
44+
45+
return response.end()
46+
}
47+
48+
// Checks if url matches /redirect/${number}
49+
// with optional trailing slash
50+
const match = request.url.match(/\/redirect\/([0-9]+)\/?$/)
51+
if (match) {
52+
if (request.url === "/redirect/0") {
53+
response.writeHead(200)
54+
return response.end("done")
55+
}
56+
57+
// Subtract 1 from the current redirect number
58+
// i.e. /redirect/10 -> /redirect/9 -> /redirect/8
59+
const currentRedirectNumber = parseInt(match[1])
60+
const newRedirectNumber = currentRedirectNumber - 1
61+
62+
response.writeHead(302, "testing", {
63+
location: `/redirect/${String(newRedirectNumber)}`,
64+
})
65+
return response.end("")
66+
}
67+
2668
// Anything else is a 404.
2769
response.writeHead(404)
2870
response.end("not found")
@@ -37,6 +79,7 @@ describe("update", () => {
3779
}
3880

3981
let _provider: UpdateProvider | undefined
82+
let _address: string | AddressInfo | null
4083
const provider = (): UpdateProvider => {
4184
if (!_provider) {
4285
throw new Error("Update provider has not been created")
@@ -62,19 +105,20 @@ describe("update", () => {
62105
})
63106
})
64107

65-
const address = server.address()
66-
if (!address || typeof address === "string" || !address.port) {
108+
_address = server.address()
109+
if (!isAddressInfo(_address)) {
67110
throw new Error("unexpected address")
68111
}
69112

70-
_provider = new UpdateProvider(`http://${address.address}:${address.port}/latest`, _settings)
113+
_provider = new UpdateProvider(`http://${_address?.address}:${_address?.port}/latest`, _settings)
71114
})
72115

73116
afterAll(() => {
74117
server.close()
75118
})
76119

77120
beforeEach(() => {
121+
jest.clearAllMocks()
78122
spy = []
79123
})
80124

@@ -170,4 +214,61 @@ describe("update", () => {
170214
expect(update.checked < Date.now() && update.checked >= now).toEqual(true)
171215
expect(update.version).toStrictEqual("unknown")
172216
})
217+
218+
it("should reject if response has status code 500", async () => {
219+
if (isAddressInfo(_address)) {
220+
const mockURL = `http://${_address.address}:${_address.port}/reject-status-code`
221+
let provider = new UpdateProvider(mockURL, settings())
222+
let update = await provider.getUpdate(true)
223+
224+
expect(update.version).toBe("unknown")
225+
expect(logger.error).toHaveBeenCalled()
226+
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
227+
identifier: "error",
228+
value: `${mockURL}: 500`,
229+
})
230+
}
231+
})
232+
233+
it("should reject if no location header provided", async () => {
234+
if (isAddressInfo(_address)) {
235+
const mockURL = `http://${_address.address}:${_address.port}/no-location-header`
236+
let provider = new UpdateProvider(mockURL, settings())
237+
let update = await provider.getUpdate(true)
238+
239+
expect(update.version).toBe("unknown")
240+
expect(logger.error).toHaveBeenCalled()
241+
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
242+
identifier: "error",
243+
value: `received redirect with no location header`,
244+
})
245+
}
246+
})
247+
248+
it("should resolve the request with response.headers.location", async () => {
249+
version = "4.1.1"
250+
if (isAddressInfo(_address)) {
251+
const mockURL = `http://${_address.address}:${_address.port}/with-location-header`
252+
let provider = new UpdateProvider(mockURL, settings())
253+
let update = await provider.getUpdate(true)
254+
255+
expect(logger.error).not.toHaveBeenCalled()
256+
expect(update.version).toBe("4.1.1")
257+
}
258+
})
259+
260+
it("should reject if more than 10 redirects", async () => {
261+
if (isAddressInfo(_address)) {
262+
const mockURL = `http://${_address.address}:${_address.port}/redirect/11`
263+
let provider = new UpdateProvider(mockURL, settings())
264+
let update = await provider.getUpdate(true)
265+
266+
expect(update.version).toBe("unknown")
267+
expect(logger.error).toHaveBeenCalled()
268+
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
269+
identifier: "error",
270+
value: `reached max redirects`,
271+
})
272+
}
273+
})
173274
})

test/utils/helpers.ts

+14
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,17 @@ export function idleTimer(message: string, reject: (error: Error) => void, delay
105105
},
106106
}
107107
}
108+
109+
/**
110+
* A helper function which returns a boolean indicating whether
111+
* the given address is AddressInfo and has .address
112+
* and a .port property.
113+
*/
114+
export function isAddressInfo(address: unknown): address is net.AddressInfo {
115+
return (
116+
address !== null &&
117+
typeof address !== "string" &&
118+
(address as net.AddressInfo).port !== undefined &&
119+
(address as net.AddressInfo).address !== undefined
120+
)
121+
}

0 commit comments

Comments
 (0)