diff --git a/test/unit/node/heart.test.ts b/test/unit/node/heart.test.ts index f6e0858a8..0af7cb9fb 100644 --- a/test/unit/node/heart.test.ts +++ b/test/unit/node/heart.test.ts @@ -1,21 +1,10 @@ import { logger } from "@coder/logger" import { readFile, writeFile, stat, utimes } from "fs/promises" -import { Heart, heartbeatTimer } from "../../../src/node/heart" -import { wrapper } from "../../../src/node/wrapper" +import { Heart } from "../../../src/node/heart" import { clean, mockLogger, tmpdir } from "../../utils/helpers" 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", () => { const testName = "heartTests" let testDir = "" @@ -27,7 +16,7 @@ describe("Heart", () => { testDir = await tmpdir(testName) }) beforeEach(() => { - heart = new Heart(`${testDir}/shutdown.txt`, undefined, mockIsActive(true)) + heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive(true)) }) afterAll(() => { jest.restoreAllMocks() @@ -53,7 +42,7 @@ describe("Heart", () => { expect(fileContents).toBe(text) - heart = new Heart(pathToFile, undefined, mockIsActive(true)) + heart = new Heart(pathToFile, mockIsActive(true)) await heart.beat() // Check that the heart wrote to the heartbeatFilePath and overwrote our text const fileContentsAfterBeat = await readFile(pathToFile, { encoding: "utf8" }) @@ -63,7 +52,7 @@ describe("Heart", () => { expect(fileStatusAfterEdit.mtimeMs).toBeGreaterThan(0) }) 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() expect(logger.warn).toHaveBeenCalled() }) @@ -82,7 +71,7 @@ describe("Heart", () => { it("should beat twice without warnings", async () => { // Use fake timers so we can speed up setTimeout jest.useFakeTimers() - heart = new Heart(`${testDir}/hello.txt`, undefined, mockIsActive(true)) + heart = new Heart(`${testDir}/hello.txt`, mockIsActive(true)) await heart.beat() // we need to speed up clocks, timeouts // call heartbeat again (and it won't be alive I think) @@ -93,37 +82,47 @@ describe("Heart", () => { }) describe("heartbeatTimer", () => { - beforeAll(() => { + const testName = "heartbeatTimer" + let testDir = "" + beforeAll(async () => { + await clean(testName) + testDir = await tmpdir(testName) mockLogger() }) afterAll(() => { jest.restoreAllMocks() }) + beforeEach(() => { + jest.useFakeTimers() + }) afterEach(() => { 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 mockIsActive = jest.fn().mockResolvedValue(isActive) - const mockBeatFn = jest.fn() - await heartbeatTimer(mockIsActive, mockBeatFn) + const heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive) + await heart.beat() + jest.advanceTimersByTime(60 * 1000) expect(mockIsActive).toHaveBeenCalled() - expect(mockBeatFn).toHaveBeenCalled() }) it("should log a warning when isActive rejects", async () => { const errorMsg = "oh no" const error = new Error(errorMsg) const mockIsActive = jest.fn().mockRejectedValue(error) - const mockBeatFn = jest.fn() - await heartbeatTimer(mockIsActive, mockBeatFn) + const heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive) + await heart.beat() + jest.advanceTimersByTime(60 * 1000) + expect(mockIsActive).toHaveBeenCalled() - expect(mockBeatFn).not.toHaveBeenCalled() expect(logger.warn).toHaveBeenCalledWith(errorMsg) }) }) -describe("idleTimeout", () => { - const testName = "idleHeartTests" +describe("stateChange", () => { + const testName = "stateChange" let testDir = "" let heart: Heart beforeAll(async () => { @@ -140,12 +139,23 @@ describe("idleTimeout", () => { heart.dispose() } }) - it("should call beat when isActive resolves to true", async () => { - jest.useFakeTimers() - heart = new Heart(`${testDir}/shutdown.txt`, 60, mockIsActive(true)) + it("should change to alive after a beat", async () => { + heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive(true)) + const mockOnChange = jest.fn() + heart.onChange(mockOnChange) + await heart.beat() - jest.advanceTimersByTime(60 * 1000) - expect(wrapper.exit).toHaveBeenCalled() + expect(mockOnChange.mock.calls[0][0]).toBe("alive") + }) + 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.useRealTimers() }) diff --git a/test/unit/node/main.test.ts b/test/unit/node/main.test.ts index 09ee6b512..39ba2ca8b 100644 --- a/test/unit/node/main.test.ts +++ b/test/unit/node/main.test.ts @@ -16,6 +16,7 @@ jest.mock("@coder/logger", () => ({ debug: jest.fn(), warn: jest.fn(), error: jest.fn(), + named: jest.fn(), level: 0, }, field: jest.fn(), @@ -94,7 +95,7 @@ describe("main", () => { // Mock routes module jest.doMock("../../../src/node/routes", () => ({ - register: jest.fn().mockResolvedValue(jest.fn()), + register: jest.fn().mockResolvedValue({ disposeRoutes: jest.fn() }), })) // Mock loadCustomStrings to succeed @@ -131,7 +132,7 @@ describe("main", () => { // Mock routes module jest.doMock("../../../src/node/routes", () => ({ - register: jest.fn().mockResolvedValue(jest.fn()), + register: jest.fn().mockResolvedValue({ disposeRoutes: jest.fn() }), })) // Import runCodeServer after mocking