Update unit tests

This commit is contained in:
Andrew Baldwin 2025-10-25 21:18:16 +02:00
parent ea538ed79e
commit cecb83018f
2 changed files with 44 additions and 33 deletions

View file

@ -1,21 +1,10 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import { readFile, writeFile, stat, utimes } from "fs/promises" import { readFile, writeFile, stat, utimes } from "fs/promises"
import { Heart, heartbeatTimer } from "../../../src/node/heart" import { Heart } from "../../../src/node/heart"
import { wrapper } from "../../../src/node/wrapper"
import { clean, mockLogger, tmpdir } from "../../utils/helpers" import { clean, mockLogger, tmpdir } from "../../utils/helpers"
const mockIsActive = (resolveTo: boolean) => jest.fn().mockResolvedValue(resolveTo) const mockIsActive = (resolveTo: boolean) => jest.fn().mockResolvedValue(resolveTo)
jest.mock("../../../src/node/wrapper", () => {
const original = jest.requireActual("../../../src/node/wrapper")
return {
...original,
wrapper: {
exit: jest.fn(),
},
}
})
describe("Heart", () => { describe("Heart", () => {
const testName = "heartTests" const testName = "heartTests"
let testDir = "" let testDir = ""
@ -27,7 +16,7 @@ describe("Heart", () => {
testDir = await tmpdir(testName) testDir = await tmpdir(testName)
}) })
beforeEach(() => { beforeEach(() => {
heart = new Heart(`${testDir}/shutdown.txt`, undefined, mockIsActive(true)) heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive(true))
}) })
afterAll(() => { afterAll(() => {
jest.restoreAllMocks() jest.restoreAllMocks()
@ -53,7 +42,7 @@ describe("Heart", () => {
expect(fileContents).toBe(text) expect(fileContents).toBe(text)
heart = new Heart(pathToFile, undefined, mockIsActive(true)) heart = new Heart(pathToFile, mockIsActive(true))
await heart.beat() await heart.beat()
// Check that the heart wrote to the heartbeatFilePath and overwrote our text // Check that the heart wrote to the heartbeatFilePath and overwrote our text
const fileContentsAfterBeat = await readFile(pathToFile, { encoding: "utf8" }) const fileContentsAfterBeat = await readFile(pathToFile, { encoding: "utf8" })
@ -63,7 +52,7 @@ describe("Heart", () => {
expect(fileStatusAfterEdit.mtimeMs).toBeGreaterThan(0) expect(fileStatusAfterEdit.mtimeMs).toBeGreaterThan(0)
}) })
it("should log a warning when given an invalid file path", async () => { it("should log a warning when given an invalid file path", async () => {
heart = new Heart(`fakeDir/fake.txt`, undefined, mockIsActive(false)) heart = new Heart(`fakeDir/fake.txt`, mockIsActive(false))
await heart.beat() await heart.beat()
expect(logger.warn).toHaveBeenCalled() expect(logger.warn).toHaveBeenCalled()
}) })
@ -82,7 +71,7 @@ describe("Heart", () => {
it("should beat twice without warnings", async () => { it("should beat twice without warnings", async () => {
// Use fake timers so we can speed up setTimeout // Use fake timers so we can speed up setTimeout
jest.useFakeTimers() jest.useFakeTimers()
heart = new Heart(`${testDir}/hello.txt`, undefined, mockIsActive(true)) heart = new Heart(`${testDir}/hello.txt`, mockIsActive(true))
await heart.beat() await heart.beat()
// we need to speed up clocks, timeouts // we need to speed up clocks, timeouts
// call heartbeat again (and it won't be alive I think) // call heartbeat again (and it won't be alive I think)
@ -93,37 +82,47 @@ describe("Heart", () => {
}) })
describe("heartbeatTimer", () => { describe("heartbeatTimer", () => {
beforeAll(() => { const testName = "heartbeatTimer"
let testDir = ""
beforeAll(async () => {
await clean(testName)
testDir = await tmpdir(testName)
mockLogger() mockLogger()
}) })
afterAll(() => { afterAll(() => {
jest.restoreAllMocks() jest.restoreAllMocks()
}) })
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => { afterEach(() => {
jest.resetAllMocks() jest.resetAllMocks()
jest.clearAllTimers()
jest.useRealTimers()
}) })
it("should call beat when isActive resolves to true", async () => { it("should call isActive when timeout expires", async () => {
const isActive = true const isActive = true
const mockIsActive = jest.fn().mockResolvedValue(isActive) const mockIsActive = jest.fn().mockResolvedValue(isActive)
const mockBeatFn = jest.fn() const heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive)
await heartbeatTimer(mockIsActive, mockBeatFn) await heart.beat()
jest.advanceTimersByTime(60 * 1000)
expect(mockIsActive).toHaveBeenCalled() expect(mockIsActive).toHaveBeenCalled()
expect(mockBeatFn).toHaveBeenCalled()
}) })
it("should log a warning when isActive rejects", async () => { it("should log a warning when isActive rejects", async () => {
const errorMsg = "oh no" const errorMsg = "oh no"
const error = new Error(errorMsg) const error = new Error(errorMsg)
const mockIsActive = jest.fn().mockRejectedValue(error) const mockIsActive = jest.fn().mockRejectedValue(error)
const mockBeatFn = jest.fn() const heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive)
await heartbeatTimer(mockIsActive, mockBeatFn) await heart.beat()
jest.advanceTimersByTime(60 * 1000)
expect(mockIsActive).toHaveBeenCalled() expect(mockIsActive).toHaveBeenCalled()
expect(mockBeatFn).not.toHaveBeenCalled()
expect(logger.warn).toHaveBeenCalledWith(errorMsg) expect(logger.warn).toHaveBeenCalledWith(errorMsg)
}) })
}) })
describe("idleTimeout", () => { describe("stateChange", () => {
const testName = "idleHeartTests" const testName = "stateChange"
let testDir = "" let testDir = ""
let heart: Heart let heart: Heart
beforeAll(async () => { beforeAll(async () => {
@ -140,12 +139,23 @@ describe("idleTimeout", () => {
heart.dispose() heart.dispose()
} }
}) })
it("should call beat when isActive resolves to true", async () => { it("should change to alive after a beat", async () => {
jest.useFakeTimers() heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive(true))
heart = new Heart(`${testDir}/shutdown.txt`, 60, mockIsActive(true)) const mockOnChange = jest.fn()
heart.onChange(mockOnChange)
await heart.beat()
jest.advanceTimersByTime(60 * 1000) expect(mockOnChange.mock.calls[0][0]).toBe("alive")
expect(wrapper.exit).toHaveBeenCalled() })
it.only("should change to idle when not active", async () => {
jest.useFakeTimers()
heart = new Heart(`${testDir}/shutdown.txt`, () => new Promise((resolve) => resolve(false)))
const mockOnChange = jest.fn()
heart.onChange(mockOnChange)
await heart.beat()
await jest.advanceTimersByTime(60 * 1000)
expect(mockOnChange.mock.calls[1][0]).toBe("idle")
jest.clearAllTimers() jest.clearAllTimers()
jest.useRealTimers() jest.useRealTimers()
}) })

View file

@ -16,6 +16,7 @@ jest.mock("@coder/logger", () => ({
debug: jest.fn(), debug: jest.fn(),
warn: jest.fn(), warn: jest.fn(),
error: jest.fn(), error: jest.fn(),
named: jest.fn(),
level: 0, level: 0,
}, },
field: jest.fn(), field: jest.fn(),
@ -94,7 +95,7 @@ describe("main", () => {
// Mock routes module // Mock routes module
jest.doMock("../../../src/node/routes", () => ({ jest.doMock("../../../src/node/routes", () => ({
register: jest.fn().mockResolvedValue(jest.fn()), register: jest.fn().mockResolvedValue({ disposeRoutes: jest.fn() }),
})) }))
// Mock loadCustomStrings to succeed // Mock loadCustomStrings to succeed
@ -131,7 +132,7 @@ describe("main", () => {
// Mock routes module // Mock routes module
jest.doMock("../../../src/node/routes", () => ({ jest.doMock("../../../src/node/routes", () => ({
register: jest.fn().mockResolvedValue(jest.fn()), register: jest.fn().mockResolvedValue({ disposeRoutes: jest.fn() }),
})) }))
// Import runCodeServer after mocking // Import runCodeServer after mocking