|
1 | | -import { beforeEach, describe, it, vi } from "vitest"; |
| 1 | +import { afterEach, beforeEach, describe, it, vi } from "vitest"; |
2 | 2 | import { RemoteRuntimeController } from "../../../api/startDevWorker/RemoteRuntimeController"; |
3 | | -import { unwrapHook } from "../../../api/startDevWorker/utils"; |
4 | 3 | // Import the mocked functions so we can set their behavior |
5 | 4 | import { |
6 | 5 | createPreviewSession, |
@@ -38,10 +37,6 @@ vi.mock("../../../user/access", () => ({ |
38 | 37 | domainUsesAccess: vi.fn(), |
39 | 38 | })); |
40 | 39 |
|
41 | | -vi.mock("../../../api/startDevWorker/utils", () => ({ |
42 | | - unwrapHook: vi.fn(), |
43 | | -})); |
44 | | - |
45 | 40 | function makeConfig( |
46 | 41 | overrides: Partial<StartDevWorkerOptions> = {} |
47 | 42 | ): StartDevWorkerOptions { |
@@ -103,12 +98,6 @@ describe("RemoteRuntimeController", () => { |
103 | 98 | } |
104 | 99 |
|
105 | 100 | beforeEach(() => { |
106 | | - // Setup mock implementations |
107 | | - vi.mocked(unwrapHook).mockResolvedValue({ |
108 | | - accountId: "test-account-id", |
109 | | - apiToken: { apiToken: "test-token" }, |
110 | | - }); |
111 | | - |
112 | 101 | vi.mocked(getWorkerAccountAndContext).mockResolvedValue({ |
113 | 102 | workerAccount: { |
114 | 103 | accountId: "test-account-id", |
@@ -159,12 +148,106 @@ describe("RemoteRuntimeController", () => { |
159 | 148 | vi.mocked(createWorkerPreview).mockResolvedValue({ |
160 | 149 | value: "test-preview-token", |
161 | 150 | host: "test.workers.dev", |
162 | | - tailUrl: "wss://test.workers.dev/tail", |
| 151 | + // No tailUrl — avoids real WebSocket connections in unit tests |
163 | 152 | }); |
164 | 153 |
|
165 | 154 | vi.mocked(getAccessHeaders).mockResolvedValue({}); |
166 | 155 | }); |
167 | 156 |
|
| 157 | + describe("proactive token refresh", () => { |
| 158 | + afterEach(() => vi.useRealTimers()); |
| 159 | + |
| 160 | + it("should proactively refresh the token before expiry", async ({ |
| 161 | + expect, |
| 162 | + }) => { |
| 163 | + vi.useFakeTimers(); |
| 164 | + |
| 165 | + const { controller, bus } = setup(); |
| 166 | + const config = makeConfig(); |
| 167 | + const bundle = makeBundle(); |
| 168 | + |
| 169 | + controller.onBundleStart({ type: "bundleStart", config }); |
| 170 | + controller.onBundleComplete({ type: "bundleComplete", config, bundle }); |
| 171 | + await bus.waitFor("reloadComplete"); |
| 172 | + |
| 173 | + vi.mocked(createWorkerPreview).mockClear(); |
| 174 | + vi.mocked(createRemoteWorkerInit).mockClear(); |
| 175 | + vi.mocked(createWorkerPreview).mockResolvedValue({ |
| 176 | + value: "proactively-refreshed-token", |
| 177 | + host: "test.workers.dev", |
| 178 | + }); |
| 179 | + |
| 180 | + // Register the waiter before advancing so it's in place when the |
| 181 | + // event fires. Use a timeout larger than the advance window so the |
| 182 | + // waiter's own faked setTimeout doesn't race the refresh timer. |
| 183 | + const reloadPromise = bus.waitFor( |
| 184 | + "reloadComplete", |
| 185 | + undefined, |
| 186 | + 60 * 60 * 1000 |
| 187 | + ); |
| 188 | + await vi.advanceTimersByTimeAsync(50 * 60 * 1000 + 1); |
| 189 | + const reloadEvent = await reloadPromise; |
| 190 | + |
| 191 | + expect(createWorkerPreview).toHaveBeenCalledTimes(1); |
| 192 | + expect(reloadEvent).toMatchObject({ |
| 193 | + type: "reloadComplete", |
| 194 | + proxyData: { |
| 195 | + headers: { |
| 196 | + "cf-workers-preview-token": "proactively-refreshed-token", |
| 197 | + }, |
| 198 | + }, |
| 199 | + }); |
| 200 | + }); |
| 201 | + |
| 202 | + it("should cancel the proactive refresh timer on bundle start", async ({ |
| 203 | + expect, |
| 204 | + }) => { |
| 205 | + vi.useFakeTimers(); |
| 206 | + |
| 207 | + const { controller, bus } = setup(); |
| 208 | + const config = makeConfig(); |
| 209 | + const bundle = makeBundle(); |
| 210 | + |
| 211 | + controller.onBundleStart({ type: "bundleStart", config }); |
| 212 | + controller.onBundleComplete({ type: "bundleComplete", config, bundle }); |
| 213 | + await bus.waitFor("reloadComplete"); |
| 214 | + |
| 215 | + vi.mocked(createWorkerPreview).mockClear(); |
| 216 | + |
| 217 | + // A new bundleStart cancels the old timer before it fires |
| 218 | + controller.onBundleStart({ type: "bundleStart", config }); |
| 219 | + controller.onBundleComplete({ type: "bundleComplete", config, bundle }); |
| 220 | + await bus.waitFor("reloadComplete"); |
| 221 | + |
| 222 | + vi.mocked(createWorkerPreview).mockClear(); |
| 223 | + |
| 224 | + // Advance to just before T2 would fire — no proactive refresh should occur |
| 225 | + await vi.advanceTimersByTimeAsync(50 * 60 * 1000 - 1); |
| 226 | + expect(createWorkerPreview).not.toHaveBeenCalled(); |
| 227 | + }); |
| 228 | + |
| 229 | + it("should cancel the proactive refresh timer on teardown", async ({ |
| 230 | + expect, |
| 231 | + }) => { |
| 232 | + vi.useFakeTimers(); |
| 233 | + |
| 234 | + const { controller, bus } = setup(); |
| 235 | + const config = makeConfig(); |
| 236 | + const bundle = makeBundle(); |
| 237 | + |
| 238 | + controller.onBundleStart({ type: "bundleStart", config }); |
| 239 | + controller.onBundleComplete({ type: "bundleComplete", config, bundle }); |
| 240 | + await bus.waitFor("reloadComplete"); |
| 241 | + |
| 242 | + vi.mocked(createWorkerPreview).mockClear(); |
| 243 | + await controller.teardown(); |
| 244 | + |
| 245 | + // Advance past where the timer would have fired |
| 246 | + await vi.advanceTimersByTimeAsync(50 * 60 * 1000 + 1); |
| 247 | + expect(createWorkerPreview).not.toHaveBeenCalled(); |
| 248 | + }); |
| 249 | + }); |
| 250 | + |
168 | 251 | describe("preview token refresh", () => { |
169 | 252 | it("should handle missing state gracefully", async ({ expect }) => { |
170 | 253 | const { controller } = setup(); |
|
0 commit comments