| 1 | import { EnvironmentId, type ExecutionEnvironmentDescriptor } from "@t3tools/contracts"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; |
| 4 | |
| 5 | import { |
| 6 | getPrimaryKnownEnvironment, |
| 7 | isDesktopEnvironmentBootstrapIncompleteError, |
| 8 | isPrimaryEnvironmentProtocolUnsupportedError, |
| 9 | isPrimaryEnvironmentUrlInvalidError, |
| 10 | readPrimaryEnvironmentTarget, |
| 11 | resolvePrimaryEnvironmentHttpUrl, |
| 12 | resolveInitialPrimaryEnvironmentDescriptor, |
| 13 | resetPrimaryEnvironmentDescriptorForTests, |
| 14 | writePrimaryEnvironmentDescriptor, |
| 15 | } from "."; |
| 16 | import { installEnvironmentHttpTest } from "../../../test/environmentHttpTest"; |
| 17 | |
| 18 | const BASE_ENVIRONMENT = { |
| 19 | environmentId: EnvironmentId.make("environment-local"), |
| 20 | label: "Local environment", |
| 21 | platform: { |
| 22 | os: "darwin", |
| 23 | arch: "arm64", |
| 24 | }, |
| 25 | serverVersion: "0.0.0-test", |
| 26 | capabilities: { |
| 27 | repositoryIdentity: true, |
| 28 | }, |
| 29 | } satisfies ExecutionEnvironmentDescriptor; |
| 30 | |
| 31 | let disposeHttpTest: (() => Promise<void>) | undefined; |
| 32 | |
| 33 | async function installDescriptorApi() { |
| 34 | const testApi = await installEnvironmentHttpTest({ |
| 35 | descriptor: () => Effect.succeed(BASE_ENVIRONMENT), |
| 36 | }); |
| 37 | disposeHttpTest = testApi.dispose; |
| 38 | return testApi; |
| 39 | } |
| 40 | |
| 41 | function installTestBrowser(url: string) { |
| 42 | vi.stubGlobal("window", { |
| 43 | location: new URL(url), |
| 44 | history: { |
| 45 | replaceState: vi.fn(), |
| 46 | }, |
| 47 | }); |
| 48 | } |
| 49 | |
| 50 | function captureThrown(run: () => unknown): unknown { |
| 51 | try { |
| 52 | run(); |
| 53 | } catch (error) { |
| 54 | return error; |
| 55 | } |
| 56 | throw new Error("Expected the operation to throw."); |
| 57 | } |
| 58 | |
| 59 | describe("environmentBootstrap", () => { |
| 60 | beforeEach(() => { |
| 61 | vi.restoreAllMocks(); |
| 62 | vi.useRealTimers(); |
| 63 | installTestBrowser("http://localhost/"); |
| 64 | }); |
| 65 | |
| 66 | afterEach(async () => { |
| 67 | await disposeHttpTest?.(); |
| 68 | disposeHttpTest = undefined; |
| 69 | resetPrimaryEnvironmentDescriptorForTests(); |
| 70 | vi.unstubAllEnvs(); |
| 71 | vi.useRealTimers(); |
| 72 | vi.restoreAllMocks(); |
| 73 | vi.unstubAllGlobals(); |
| 74 | }); |
| 75 | |
| 76 | it("attaches the bootstrapped environment descriptor to the primary environment", () => { |
| 77 | vi.stubGlobal("window", { |
| 78 | location: { |
| 79 | origin: "http://localhost:3773", |
| 80 | }, |
| 81 | desktopBridge: undefined, |
| 82 | }); |
| 83 | writePrimaryEnvironmentDescriptor({ |
| 84 | environmentId: EnvironmentId.make("environment-local"), |
| 85 | label: "Bootstrapped environment", |
| 86 | platform: { |
| 87 | os: "darwin", |
| 88 | arch: "arm64", |
| 89 | }, |
| 90 | serverVersion: "0.0.0-test", |
| 91 | capabilities: { |
| 92 | repositoryIdentity: true, |
| 93 | }, |
| 94 | }); |
| 95 | |
| 96 | expect(getPrimaryKnownEnvironment()).toEqual({ |
| 97 | id: "environment-local", |
| 98 | label: "Bootstrapped environment", |
| 99 | source: "window-origin", |
| 100 | environmentId: "environment-local", |
| 101 | target: { |
| 102 | httpBaseUrl: "http://localhost:3773/", |
| 103 | wsBaseUrl: "ws://localhost:3773/", |
| 104 | }, |
| 105 | }); |
| 106 | }); |
| 107 | |
| 108 | it("reuses an in-flight descriptor bootstrap request", async () => { |
| 109 | const testApi = await installDescriptorApi(); |
| 110 | |
| 111 | await Promise.all([ |
| 112 | resolveInitialPrimaryEnvironmentDescriptor(), |
| 113 | resolveInitialPrimaryEnvironmentDescriptor(), |
| 114 | ]); |
| 115 | |
| 116 | expect(testApi.calls.descriptor).toBe(1); |
| 117 | }); |
| 118 | |
| 119 | it("uses https descriptor urls when the primary environment uses wss", async () => { |
| 120 | vi.stubEnv("VITE_HTTP_URL", "https://remote.example.com"); |
| 121 | vi.stubEnv("VITE_WS_URL", "wss://remote.example.com"); |
| 122 | await installDescriptorApi(); |
| 123 | |
| 124 | await expect(resolveInitialPrimaryEnvironmentDescriptor()).resolves.toEqual(BASE_ENVIRONMENT); |
| 125 | expect(resolvePrimaryEnvironmentHttpUrl("/.well-known/t3/environment")).toBe( |
| 126 | "https://remote.example.com/.well-known/t3/environment", |
| 127 | ); |
| 128 | }); |
| 129 | |
| 130 | it("keeps configured environment paths in primary descriptor and auth URLs", async () => { |
| 131 | vi.stubEnv("VITE_HTTP_URL", "https://hub.example/prefix/environments/one/?old=1#fragment"); |
| 132 | await installDescriptorApi(); |
| 133 | await expect(resolveInitialPrimaryEnvironmentDescriptor()).resolves.toEqual(BASE_ENVIRONMENT); |
| 134 | expect(resolvePrimaryEnvironmentHttpUrl("/.well-known/t3/environment")).toBe( |
| 135 | "https://hub.example/prefix/environments/one/.well-known/t3/environment", |
| 136 | ); |
| 137 | expect(readPrimaryEnvironmentTarget()?.target.wsBaseUrl).toBe( |
| 138 | "wss://hub.example/prefix/environments/one/", |
| 139 | ); |
| 140 | expect(resolvePrimaryEnvironmentHttpUrl("/api/auth/session", { token: "ticket" })).toBe( |
| 141 | "https://hub.example/prefix/environments/one/api/auth/session?token=ticket", |
| 142 | ); |
| 143 | }); |
| 144 | |
| 145 | it("derives the websocket url when only VITE_HTTP_URL is configured", async () => { |
| 146 | vi.stubEnv("VITE_HTTP_URL", "https://remote.example.com"); |
| 147 | await installDescriptorApi(); |
| 148 | |
| 149 | await expect(resolveInitialPrimaryEnvironmentDescriptor()).resolves.toEqual(BASE_ENVIRONMENT); |
| 150 | expect(resolvePrimaryEnvironmentHttpUrl("/.well-known/t3/environment")).toBe( |
| 151 | "https://remote.example.com/.well-known/t3/environment", |
| 152 | ); |
| 153 | expect(getPrimaryKnownEnvironment()?.target).toEqual({ |
| 154 | httpBaseUrl: "https://remote.example.com/", |
| 155 | wsBaseUrl: "wss://remote.example.com/", |
| 156 | }); |
| 157 | }); |
| 158 | |
| 159 | it("derives the http url when only VITE_WS_URL is configured", async () => { |
| 160 | vi.stubEnv("VITE_WS_URL", "wss://remote.example.com"); |
| 161 | await installDescriptorApi(); |
| 162 | |
| 163 | await expect(resolveInitialPrimaryEnvironmentDescriptor()).resolves.toEqual(BASE_ENVIRONMENT); |
| 164 | expect(resolvePrimaryEnvironmentHttpUrl("/.well-known/t3/environment")).toBe( |
| 165 | "https://remote.example.com/.well-known/t3/environment", |
| 166 | ); |
| 167 | expect(getPrimaryKnownEnvironment()?.target).toEqual({ |
| 168 | httpBaseUrl: "https://remote.example.com/", |
| 169 | wsBaseUrl: "wss://remote.example.com/", |
| 170 | }); |
| 171 | }); |
| 172 | |
| 173 | it.each(["wss://hub.example/prefix/ws", "wss://hub.example/prefix/"])( |
| 174 | "derives HTTP base paths from a configured WebSocket address: %s", |
| 175 | (wsBaseUrl) => { |
| 176 | vi.stubEnv("VITE_WS_URL", wsBaseUrl); |
| 177 | expect(resolvePrimaryEnvironmentHttpUrl("/api/auth/session")).toBe( |
| 178 | "https://hub.example/prefix/api/auth/session", |
| 179 | ); |
| 180 | }, |
| 181 | ); |
| 182 | |
| 183 | it("keeps an uppercase wss scheme secure when deriving the http url", () => { |
| 184 | vi.stubEnv("VITE_WS_URL", "WSS://remote.example.com"); |
| 185 | |
| 186 | expect(readPrimaryEnvironmentTarget()?.target).toEqual({ |
| 187 | httpBaseUrl: "https://remote.example.com/", |
| 188 | wsBaseUrl: "wss://remote.example.com/", |
| 189 | }); |
| 190 | }); |
| 191 | |
| 192 | it("keeps an uppercase https scheme secure when deriving the websocket url", () => { |
| 193 | vi.stubEnv("VITE_HTTP_URL", "HTTPS://remote.example.com"); |
| 194 | |
| 195 | expect(readPrimaryEnvironmentTarget()?.target).toEqual({ |
| 196 | httpBaseUrl: "https://remote.example.com/", |
| 197 | wsBaseUrl: "wss://remote.example.com/", |
| 198 | }); |
| 199 | }); |
| 200 | |
| 201 | it("uses the current origin as the descriptor base for local dev environments", async () => { |
| 202 | installTestBrowser("http://localhost:5735/"); |
| 203 | await installDescriptorApi(); |
| 204 | |
| 205 | await expect(resolveInitialPrimaryEnvironmentDescriptor()).resolves.toEqual(BASE_ENVIRONMENT); |
| 206 | expect(resolvePrimaryEnvironmentHttpUrl("/.well-known/t3/environment")).toBe( |
| 207 | "http://localhost:5735/.well-known/t3/environment", |
| 208 | ); |
| 209 | }); |
| 210 | |
| 211 | it("uses the vite proxy for desktop-managed loopback descriptor requests during local dev", async () => { |
| 212 | vi.stubEnv("VITE_DEV_SERVER_URL", "http://127.0.0.1:5733"); |
| 213 | vi.stubGlobal("window", { |
| 214 | location: new URL("http://127.0.0.1:5733/"), |
| 215 | history: { |
| 216 | replaceState: vi.fn(), |
| 217 | }, |
| 218 | desktopBridge: { |
| 219 | getLocalEnvironmentBootstraps: () => [ |
| 220 | { |
| 221 | id: "primary", |
| 222 | label: "Windows", |
| 223 | httpBaseUrl: "http://127.0.0.1:3773", |
| 224 | wsBaseUrl: "ws://127.0.0.1:3773", |
| 225 | bootstrapToken: "desktop-bootstrap-token", |
| 226 | }, |
| 227 | ], |
| 228 | }, |
| 229 | }); |
| 230 | await installDescriptorApi(); |
| 231 | |
| 232 | await expect(resolveInitialPrimaryEnvironmentDescriptor()).resolves.toEqual(BASE_ENVIRONMENT); |
| 233 | expect(resolvePrimaryEnvironmentHttpUrl("/.well-known/t3/environment")).toBe( |
| 234 | "http://127.0.0.1:5733/.well-known/t3/environment", |
| 235 | ); |
| 236 | }); |
| 237 | |
| 238 | it("retains the URL parser cause without exposing the configured URL in its message", () => { |
| 239 | vi.stubEnv("VITE_HTTP_URL", "http://["); |
| 240 | |
| 241 | const error = captureThrown(readPrimaryEnvironmentTarget); |
| 242 | |
| 243 | expect(isPrimaryEnvironmentUrlInvalidError(error)).toBe(true); |
| 244 | if (!isPrimaryEnvironmentUrlInvalidError(error)) { |
| 245 | throw new Error("Expected a structured primary environment URL error."); |
| 246 | } |
| 247 | expect(error).toMatchObject({ |
| 248 | source: "configured", |
| 249 | urlKind: "http-base-url", |
| 250 | message: "Could not parse http-base-url for the configured primary environment target.", |
| 251 | }); |
| 252 | expect(error.cause).toBeInstanceOf(TypeError); |
| 253 | expect(error.message).not.toContain("http://["); |
| 254 | }); |
| 255 | |
| 256 | it("describes which desktop bootstrap endpoint is missing", () => { |
| 257 | vi.stubGlobal("window", { |
| 258 | location: new URL("http://127.0.0.1:5733/"), |
| 259 | history: { replaceState: vi.fn() }, |
| 260 | desktopBridge: { |
| 261 | getLocalEnvironmentBootstraps: () => [ |
| 262 | { |
| 263 | id: "primary", |
| 264 | label: "Local environment", |
| 265 | httpBaseUrl: "http://127.0.0.1:3773", |
| 266 | bootstrapToken: "desktop-bootstrap-token", |
| 267 | }, |
| 268 | ], |
| 269 | }, |
| 270 | }); |
| 271 | |
| 272 | const error = captureThrown(readPrimaryEnvironmentTarget); |
| 273 | |
| 274 | expect(isDesktopEnvironmentBootstrapIncompleteError(error)).toBe(true); |
| 275 | if (!isDesktopEnvironmentBootstrapIncompleteError(error)) { |
| 276 | throw new Error("Expected a structured desktop bootstrap error."); |
| 277 | } |
| 278 | expect(error).toMatchObject({ |
| 279 | hasHttpBaseUrl: true, |
| 280 | hasWsBaseUrl: false, |
| 281 | message: "Desktop bootstrap is missing wsBaseUrl for the local environment.", |
| 282 | }); |
| 283 | }); |
| 284 | |
| 285 | it("has no primary target when the desktop local environment is disabled", () => { |
| 286 | vi.stubGlobal("window", { |
| 287 | location: new URL("t3code://app/"), |
| 288 | desktopBridge: { |
| 289 | getLocalEnvironmentEnabled: () => false, |
| 290 | getLocalEnvironmentBootstraps: () => [], |
| 291 | }, |
| 292 | }); |
| 293 | |
| 294 | expect(readPrimaryEnvironmentTarget()).toBeNull(); |
| 295 | expect(getPrimaryKnownEnvironment()).toBeNull(); |
| 296 | expect(() => resolvePrimaryEnvironmentHttpUrl("/api/auth/session")).toThrow( |
| 297 | "The local environment is disabled.", |
| 298 | ); |
| 299 | }); |
| 300 | |
| 301 | it("preserves an unsupported window-origin protocol", () => { |
| 302 | vi.stubGlobal("window", { |
| 303 | location: { origin: "file:///tmp/t3code/" }, |
| 304 | history: { replaceState: vi.fn() }, |
| 305 | }); |
| 306 | |
| 307 | const error = captureThrown(readPrimaryEnvironmentTarget); |
| 308 | |
| 309 | expect(isPrimaryEnvironmentProtocolUnsupportedError(error)).toBe(true); |
| 310 | if (!isPrimaryEnvironmentProtocolUnsupportedError(error)) { |
| 311 | throw new Error("Expected a structured primary environment protocol error."); |
| 312 | } |
| 313 | expect(error).toMatchObject({ |
| 314 | source: "window-origin", |
| 315 | protocol: "file:", |
| 316 | message: "The window-origin primary environment target uses unsupported protocol file:.", |
| 317 | }); |
| 318 | }); |
| 319 | }); |