|
1 | 1 | import { logger } from "@coder/logger" |
2 | 2 | import { readFile, writeFile, stat, utimes } from "fs/promises" |
3 | | -import { Heart, heartbeatTimer } from "../../../src/node/heart" |
| 3 | +import { Heart } from "../../../src/node/heart" |
4 | 4 | import { clean, mockLogger, tmpdir } from "../../utils/helpers" |
5 | 5 |
|
6 | 6 | const mockIsActive = (resolveTo: boolean) => jest.fn().mockResolvedValue(resolveTo) |
@@ -82,31 +82,81 @@ describe("Heart", () => { |
82 | 82 | }) |
83 | 83 |
|
84 | 84 | describe("heartbeatTimer", () => { |
85 | | - beforeAll(() => { |
| 85 | + const testName = "heartbeatTimer" |
| 86 | + let testDir = "" |
| 87 | + beforeAll(async () => { |
| 88 | + await clean(testName) |
| 89 | + testDir = await tmpdir(testName) |
86 | 90 | mockLogger() |
87 | 91 | }) |
88 | 92 | afterAll(() => { |
89 | 93 | jest.restoreAllMocks() |
90 | 94 | }) |
| 95 | + beforeEach(() => { |
| 96 | + jest.useFakeTimers() |
| 97 | + }) |
91 | 98 | afterEach(() => { |
92 | 99 | jest.resetAllMocks() |
| 100 | + jest.clearAllTimers() |
| 101 | + jest.useRealTimers() |
93 | 102 | }) |
94 | | - it("should call beat when isActive resolves to true", async () => { |
| 103 | + it("should call isActive when timeout expires", async () => { |
95 | 104 | const isActive = true |
96 | 105 | const mockIsActive = jest.fn().mockResolvedValue(isActive) |
97 | | - const mockBeatFn = jest.fn() |
98 | | - await heartbeatTimer(mockIsActive, mockBeatFn) |
| 106 | + const heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive) |
| 107 | + await heart.beat() |
| 108 | + jest.advanceTimersByTime(60 * 1000) |
99 | 109 | expect(mockIsActive).toHaveBeenCalled() |
100 | | - expect(mockBeatFn).toHaveBeenCalled() |
101 | 110 | }) |
102 | 111 | it("should log a warning when isActive rejects", async () => { |
103 | 112 | const errorMsg = "oh no" |
104 | 113 | const error = new Error(errorMsg) |
105 | 114 | const mockIsActive = jest.fn().mockRejectedValue(error) |
106 | | - const mockBeatFn = jest.fn() |
107 | | - await heartbeatTimer(mockIsActive, mockBeatFn) |
| 115 | + const heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive) |
| 116 | + await heart.beat() |
| 117 | + jest.advanceTimersByTime(60 * 1000) |
| 118 | + |
108 | 119 | expect(mockIsActive).toHaveBeenCalled() |
109 | | - expect(mockBeatFn).not.toHaveBeenCalled() |
110 | 120 | expect(logger.warn).toHaveBeenCalledWith(errorMsg) |
111 | 121 | }) |
112 | 122 | }) |
| 123 | + |
| 124 | +describe("stateChange", () => { |
| 125 | + const testName = "stateChange" |
| 126 | + let testDir = "" |
| 127 | + let heart: Heart |
| 128 | + beforeAll(async () => { |
| 129 | + await clean(testName) |
| 130 | + testDir = await tmpdir(testName) |
| 131 | + mockLogger() |
| 132 | + }) |
| 133 | + afterAll(() => { |
| 134 | + jest.restoreAllMocks() |
| 135 | + }) |
| 136 | + afterEach(() => { |
| 137 | + jest.resetAllMocks() |
| 138 | + if (heart) { |
| 139 | + heart.dispose() |
| 140 | + } |
| 141 | + }) |
| 142 | + it("should change to alive after a beat", async () => { |
| 143 | + heart = new Heart(`${testDir}/shutdown.txt`, mockIsActive(true)) |
| 144 | + const mockOnChange = jest.fn() |
| 145 | + heart.onChange(mockOnChange) |
| 146 | + await heart.beat() |
| 147 | + |
| 148 | + expect(mockOnChange.mock.calls[0][0]).toBe("alive") |
| 149 | + }) |
| 150 | + it.only("should change to expired when not active", async () => { |
| 151 | + jest.useFakeTimers() |
| 152 | + heart = new Heart(`${testDir}/shutdown.txt`, () => new Promise((resolve) => resolve(false))) |
| 153 | + const mockOnChange = jest.fn() |
| 154 | + heart.onChange(mockOnChange) |
| 155 | + await heart.beat() |
| 156 | + |
| 157 | + await jest.advanceTimersByTime(60 * 1000) |
| 158 | + expect(mockOnChange.mock.calls[1][0]).toBe("expired") |
| 159 | + jest.clearAllTimers() |
| 160 | + jest.useRealTimers() |
| 161 | + }) |
| 162 | +}) |
0 commit comments