Skip to content
Source
Sign inSign up

linkEnvironment.test.ts

287 lines10.3 KB

1import {
2 type DesktopBridge,
3 EnvironmentId,
4 type RelayClientInstallProgressEvent,
5 WS_METHODS,
6} from "@t3tools/contracts";
7import { RelayWebClientId } from "@t3tools/contracts/relay";
8import { describe, expect, it } from "@effect/vitest";
9import * as Effect from "effect/Effect";
10import * as Layer from "effect/Layer";
11import * as Option from "effect/Option";
12import * as Stream from "effect/Stream";
13import * as SubscriptionRef from "effect/SubscriptionRef";
14import { HttpClient } from "effect/http";
15import { afterEach, beforeEach, vi } from "vite-plus/test";
16import {
17 AVAILABLE_CONNECTION_STATE,
18 type PreparedConnection,
19 BearerConnectionTarget,
20 PrimaryConnectionTarget,
21 EnvironmentSupervisor,
22} from "@t3tools/client-runtime/connection";
23import { type RpcSession } from "@t3tools/client-runtime/rpc";
24import { EnvironmentRegistry } from "@t3tools/client-runtime/connection";
25import { ManagedRelay } from "@t3tools/client-runtime/relay";
26import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc";
27import { __resetDesktopPrimaryAuthForTests } from "../environments/primary/desktopAuth";
28​
29import { readCloudLinkState, unlinkEnvironmentFromCloud } from "./linkEnvironment";
30​
31const TARGET = {
32 environmentId: "environment-1",
33 label: "Desktop",
34 httpBaseUrl: "http://127.0.0.1:3000",
35 wsBaseUrl: "ws://127.0.0.1:3000",
36};
37​
38const SAVED_SERVICE_TARGET = {
39 environmentId: "saved-service",
40 label: "Service",
41 httpBaseUrl: "http://127.0.0.1:3773",
42 wsBaseUrl: "ws://127.0.0.1:3773",
43};
44​
45const relayClientInstallDialog = vi.hoisted(() => ({
46 requestConfirmation: vi.fn(),
47 reportProgress: vi.fn(),
48 finish: vi.fn(),
49}));
50​
51vi.mock("./relayClientInstallDialog", () => ({
52 requestRelayClientInstallConfirmation: relayClientInstallDialog.requestConfirmation,
53 reportRelayClientInstallProgress: relayClientInstallDialog.reportProgress,
54 finishRelayClientInstall: relayClientInstallDialog.finish,
55}));
56​
57const createProof = vi.fn(() => Effect.succeed("dpop-proof"));
58const layerDpopSigner = Layer.succeed(
59 ManagedRelay.ManagedRelayDpopSigner,
60 ManagedRelay.ManagedRelayDpopSigner.of({
61 thumbprint: Effect.succeed("thumbprint"),
62 createProof,
63 }),
64);
65​
66function layerRelay() {
67 const layerHttp = layerRemoteHttpClient(globalThis.fetch);
68 return Layer.mergeAll(
69 layerHttp,
70 ManagedRelay.layer({
71 relayUrl: "https://relay.example.test",
72 clientId: RelayWebClientId,
73 }).pipe(Layer.provideMerge(layerDpopSigner), Layer.provide(layerHttp)),
74 );
76​
77function layerRegistry(options?: {
78 readonly status?: { readonly status: "available"; readonly version: string };
79 readonly installEvents?: ReadonlyArray<RelayClientInstallProgressEvent>;
80}) {
81 return Layer.effect(
82 EnvironmentRegistry.EnvironmentRegistry,
83 Effect.gen(function* () {
84 const client = {
85 [WS_METHODS.cloudGetRelayClientStatus]: () =>
86 Effect.succeed(options?.status ?? { status: "available", version: "2026.6.0" }),
87 [WS_METHODS.cloudInstallRelayClient]: () =>
88 Stream.fromIterable(options?.installEvents ?? []),
89 } as unknown as RpcSession["client"];
90 const session: RpcSession = {
91 client,
92 initialConfig: Effect.never,
93 subscribeServerConfig: (input = {}) => client.subscribeServerConfig(input),
94 ready: Effect.void,
95 probe: Effect.void,
96 inboundFrames: Effect.succeed(0),
97 closed: Effect.never,
98 };
99 const makeSupervisor = Effect.fn(function* (prepared: Option.Option<PreparedConnection>) {
100 return EnvironmentSupervisor.EnvironmentSupervisor.of({
101 target: new PrimaryConnectionTarget({
102 environmentId: EnvironmentId.make(TARGET.environmentId),
103 label: TARGET.label,
104 httpBaseUrl: TARGET.httpBaseUrl,
105 wsBaseUrl: TARGET.wsBaseUrl,
106 }),
107 state: yield* SubscriptionRef.make(AVAILABLE_CONNECTION_STATE),
108 session: yield* SubscriptionRef.make(Option.some(session)),
109 prepared: yield* SubscriptionRef.make(prepared),
110 connect: Effect.void,
111 disconnect: Effect.void,
112 retryNow: Effect.void,
113 } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]);
114 });
115 const supervisors = new Map([
116 [
117 TARGET.environmentId,
118 yield* makeSupervisor(
119 Option.some({
120 environmentId: EnvironmentId.make(TARGET.environmentId),
121 label: TARGET.label,
122 httpBaseUrl: TARGET.httpBaseUrl,
123 socketUrl: `${TARGET.wsBaseUrl}/ws`,
124 httpAuthorization: { _tag: "Bearer", token: "desktop-bearer-token" },
125 target: new PrimaryConnectionTarget({
126 environmentId: EnvironmentId.make(TARGET.environmentId),
127 label: TARGET.label,
128 httpBaseUrl: TARGET.httpBaseUrl,
129 wsBaseUrl: TARGET.wsBaseUrl,
130 }),
131 }),
132 ),
133 ],
134 [
135 SAVED_SERVICE_TARGET.environmentId,
136 yield* makeSupervisor(
137 Option.some({
138 environmentId: EnvironmentId.make(SAVED_SERVICE_TARGET.environmentId),
139 label: SAVED_SERVICE_TARGET.label,
140 httpBaseUrl: SAVED_SERVICE_TARGET.httpBaseUrl,
141 socketUrl: `${SAVED_SERVICE_TARGET.wsBaseUrl}/ws`,
142 httpAuthorization: { _tag: "Bearer", token: "saved-service-token" },
143 target: new BearerConnectionTarget({
144 environmentId: EnvironmentId.make(SAVED_SERVICE_TARGET.environmentId),
145 label: SAVED_SERVICE_TARGET.label,
146 connectionId: "saved-service-connection",
147 }),
148 }),
149 ),
150 ],
151 ]);
152 const supervisorFor = (environmentId: EnvironmentId) => supervisors.get(environmentId)!;
153 const registry = {
154 run: <A, E, R>(environmentId: EnvironmentId, effect: Effect.Effect<A, E, R>) =>
155 Effect.provideService(
156 effect,
157 EnvironmentSupervisor.EnvironmentSupervisor,
158 supervisorFor(environmentId),
159 ),
160 runStream: <A, E, R>(environmentId: EnvironmentId, stream: Stream.Stream<A, E, R>) =>
161 Stream.provideService(
162 stream,
163 EnvironmentSupervisor.EnvironmentSupervisor,
164 supervisorFor(environmentId),
165 ),
166 } as unknown as EnvironmentRegistry.EnvironmentRegistry["Service"];
167 return EnvironmentRegistry.EnvironmentRegistry.of(registry);
168 }),
169 );
171​
172function layerServices(options?: Parameters<typeof layerRegistry>[0]) {
173 return Layer.mergeAll(layerRelay(), layerRegistry(options));
175​
176function withServices<A, E>(
177 effect: Effect.Effect<
178 A,
179 E,
180 | HttpClient.HttpClient
181 | ManagedRelay.ManagedRelayClient
182 | EnvironmentRegistry.EnvironmentRegistry
183 >,
184 options?: Parameters<typeof layerRegistry>[0],
185) {
186 return effect.pipe(Effect.provide(layerServices(options)));
188​
189beforeEach(() => {
190 vi.clearAllMocks();
191 vi.stubEnv("VITE_T3CODE_RELAY_URL", "https://relay.example.test");
192 relayClientInstallDialog.requestConfirmation.mockResolvedValue(true);
193});
194​
195afterEach(() => {
196 __resetDesktopPrimaryAuthForTests();
197 vi.unstubAllGlobals();
198 vi.unstubAllEnvs();
199 vi.restoreAllMocks();
200});
201​
202describe("web cloud link environment client", () => {
203 it.effect("reads link state from the chosen environment with its own credentials", () =>
204 Effect.gen(function* () {
205 const fetchMock = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) =>
206 Response.json({
207 linked: true,
208 cloudUserId: "user-1",
209 relayUrl: "https://relay.example.test",
210 relayIssuer: "https://relay.example.test",
211 managedTunnelActive: true,
212 publishAgentActivity: false,
213 }),
214 );
215 vi.stubGlobal("fetch", fetchMock);
216 vi.stubGlobal("window", {
217 location: { origin: "t3code://app" },
218 desktopBridge: {
219 getLocalEnvironmentBearerToken: vi.fn().mockResolvedValue("desktop-bearer-token"),
220 } as unknown as DesktopBridge,
221 });
222​
223 for (const [target, token] of [
224 [TARGET, "desktop-bearer-token"],
225 [SAVED_SERVICE_TARGET, "saved-service-token"],
226 ] as const) {
227 fetchMock.mockClear();
228 yield* withServices(
229 readCloudLinkState({
230 target: { environmentId: EnvironmentId.make(target.environmentId) },
231 }),
232 );
233​
234 const [input, init] = fetchMock.mock.calls[0]!;
235 const request = new Request(input, init);
236 expect(new URL(request.url).origin).toBe(target.httpBaseUrl);
237 expect(request.credentials).not.toBe("include");
238 expect(request.headers.get("authorization")).toBe(`Bearer ${token}`);
239 }
240 }),
241 );
242​
243 it.effect("reports why the relay kept the environment after a local unlink", () =>
244 Effect.gen(function* () {
245 const fetchMock = vi.fn((input: RequestInfo | URL) =>
246 Promise.resolve(
247 String(input).startsWith("https://relay.example.test/")
248 ? Response.json(
249 {
250 _tag: "RelayInternalError",
251 code: "internal_error",
252 reason: "upstream_unavailable",
253 traceId: "trace-unlink",
254 },
255 { status: 500 },
256 )
257 : Response.json({ ok: true, endpointRuntimeStatus: null }),
258 ),
259 );
260 vi.stubGlobal("fetch", fetchMock);
261 vi.stubGlobal("window", {
262 location: { origin: "t3code://app" },
263 desktopBridge: {
264 getLocalEnvironmentBearerToken: vi.fn().mockResolvedValue("desktop-bearer-token"),
265 } as unknown as DesktopBridge,
266 });
267​
268 const error = yield* withServices(
269 unlinkEnvironmentFromCloud({
270 target: { environmentId: EnvironmentId.make(TARGET.environmentId) },
271 clerkToken: "clerk-token",
272 }),
273 ).pipe(Effect.flip);
274​
275 expect(fetchMock.mock.calls.map(([input]) => String(input))).toEqual([
276 "http://127.0.0.1:3000/api/connect/unlink",
277 "https://relay.example.test/v1/client/environment-links/environment-1",
278 ]);
279 expect(error).toMatchObject({
280 _tag: "CloudEnvironmentLinkError",
281 message:
282 "Disconnected locally, but T3 Connect did not confirm it removed this environment: Relay encountered an internal error (upstream_unavailable).",
283 traceId: "trace-unlink",
284 });
285 }),
286 );
287});