Skip to content
Source
Sign inSign up

linkRequest.test.ts

487 lines17.3 KB

1import { EnvironmentId, type ServerConfig, ThreadId, TurnItemId } from "@t3tools/contracts";
2import type { RelayEnvironmentStatusResponse } from "@t3tools/contracts/relay";
3import * as Option from "effect/Option";
4import { describe, expect, it } from "vite-plus/test";
5​
6import { BearerConnectionProfile } from "./connection/catalog.ts";
7import { BearerConnectionTarget, RelayConnectionTarget } from "./connection/model.ts";
8import type { EnvironmentPresentation } from "./connection/presentation.ts";
9import type { RelayDiscoveredEnvironment } from "./relay/discovery.ts";
10import {
11 ANOTHER_ADDRESS,
12 linkableMachines,
13 linkedEnvironmentNames,
14 linkRequestAnswerInput,
15 linkRequestDisplay,
16 linkRequestFailureMessage,
17 linkRequestInitialMachine,
18 linkRequestTarget,
19 linkRequestTargetName,
20 rankMachinesByHint,
21} from "./linkRequest.ts";
22​
23const item = {
24 id: TurnItemId.make("turn-item:link-request:1"),
25 threadId: ThreadId.make("thread-1"),
26};
27const asked = { url: "https://box.example.ts.net", label: "Box" };
28const accessLabel = (access: string) => `<${access}>`;
29​
30describe("linkRequestDisplay", () => {
31 it("names the environment linked and the access the user chose", () => {
32 expect(
33 linkRequestDisplay(
34 { ...asked, linkStatus: "linked", linkedLabel: "Box", linkedAccess: "auto" },
35 "local",
36 accessLabel,
37 ),
38 ).toEqual({ kind: "answered", outcome: "linked", label: "Linked Box · <auto>" });
39 });
40​
41 it("shows why a link failed, and never offers the actions outside the asking thread", () => {
42 expect(
43 linkRequestDisplay(
44 { ...asked, linkStatus: "failed", failure: "Pairing code expired." },
45 "local",
46 accessLabel,
47 ),
48 ).toMatchObject({ outcome: "failed", label: "Could not link: Pairing code expired." });
49 expect(
50 linkRequestDisplay({ ...asked, linkStatus: "pending" }, "inherited", accessLabel),
51 ).toMatchObject({ kind: "pending-elsewhere" });
52 });
53});
54​
55describe("linkRequestAnswerInput", () => {
56 const vps = {
57 environmentId: EnvironmentId.make("environment-vps"),
58 label: "VPS",
59 urls: ["https://vps.ts.net", "http://10.0.0.2:3773"],
60 };
61​
62 it("answers with the machine picked and every address known for it", () => {
63 expect(
64 linkRequestAnswerInput(item, {
65 type: "link",
66 target: vps,
67 access: "auto",
68 pairingCode: " abc ",
69 }),
70 ).toEqual({
71 threadId: item.threadId,
72 turnItemId: item.id,
73 answer: { type: "link", ...vps, access: "auto", pairingCode: "abc" },
74 });
75 expect(
76 linkRequestAnswerInput(item, {
77 type: "link",
78 target: { url: " https://box.ts.net " },
79 access: "auto",
80 pairingCode: "abc",
81 }),
82 ).toMatchObject({ answer: { urls: ["https://box.ts.net"] } });
83 });
84​
85 it("refuses a blank code, a blank address, or a machine with no address", () => {
86 const blank = (target: Parameters<typeof linkRequestAnswerInput>[1] & { type: "link" }) =>
87 linkRequestAnswerInput(item, target);
88 expect(blank({ type: "link", target: vps, access: "auto", pairingCode: " " })).toBeNull();
89 expect(
90 blank({ type: "link", target: { url: " " }, access: "auto", pairingCode: "abc" }),
91 ).toBeNull();
92 expect(
93 blank({ type: "link", target: { ...vps, urls: [] }, access: "auto", pairingCode: "abc" }),
94 ).toBeNull();
95 });
96});
97​
98const presentation = (
99 id: string,
100 label: string,
101 options: {
102 readonly saved?: ReadonlyArray<string>;
103 readonly reported?: ReadonlyArray<string>;
104 readonly connected?: boolean;
105 } = {},
106): EnvironmentPresentation & { readonly environmentId: EnvironmentId } => {
107 const environmentId = EnvironmentId.make(id);
108 const [first, ...rest] = (options.saved ?? []).map((httpBaseUrl, index) => {
109 const connectionId = `${id}-${index}`;
110 return {
111 target: new BearerConnectionTarget({ environmentId, label, connectionId }),
112 profile: Option.some(
113 new BearerConnectionProfile({
114 connectionId,
115 environmentId,
116 label,
117 httpBaseUrl,
118 wsBaseUrl: httpBaseUrl,
119 }),
120 ),
121 };
122 });
123 return {
124 environmentId,
125 entry: {
126 ...(first ?? {
127 target: new RelayConnectionTarget({ environmentId, label }),
128 profile: Option.none(),
129 }),
130 ...(rest.length === 0 ? {} : { alternateRoutes: rest }),
131 enabled: true,
132 },
133 connection: {
134 phase: options.connected === false ? "offline" : "connected",
135 error: null,
136 traceId: null,
137 },
138 serverConfig:
139 options.reported === undefined
140 ? null
141 : ({
142 environment: { platform: { machine: "laptop" } },
143 directEndpoints: options.reported.map((httpBaseUrl) => ({
144 kind: "tailnet",
145 httpBaseUrl,
146 })),
147 } as unknown as ServerConfig),
148 };
149};
150​
151const discovered = (
152 id: string,
153 label: string,
154 machine?: "mac-mini",
155): RelayDiscoveredEnvironment => ({
156 environment: {
157 environmentId: EnvironmentId.make(id),
158 label,
159 endpoint: {
160 httpBaseUrl: `https://${id}.t3.example`,
161 wsBaseUrl: `wss://${id}.t3.example`,
162 providerKind: "cloudflare_tunnel",
163 },
164 linkedAt: "2026-10-01T00:00:00.000Z",
165 },
166 availability: "online",
167 status:
168 machine === undefined
169 ? Option.none()
170 : Option.some({
171 environmentId: EnvironmentId.make(id),
172 endpoint: {
173 httpBaseUrl: `https://${id}.t3.example`,
174 wsBaseUrl: `wss://${id}.t3.example`,
175 providerKind: "cloudflare_tunnel",
176 },
177 status: "online",
178 checkedAt: "2026-10-01T00:00:00.000Z",
179 descriptor: {
180 environmentId: EnvironmentId.make(id),
181 label,
182 platform: { os: "darwin", arch: "arm64", machine },
183 serverVersion: "1.0.0",
184 capabilities: {},
185 },
186 } as unknown as RelayEnvironmentStatusResponse),
187 error: Option.none(),
188});
189​
190describe("linkableMachines", () => {
191 it("lists the other machines with reported addresses before saved ones, once each", () => {
192 const here = presentation("environment-here", "Here", { saved: ["http://localhost:3773"] });
193 // A loopback address would name the linking server itself, so it is no address of the VPS.
194 const vps = presentation("environment-vps", "VPS", {
195 saved: ["https://vps.example.com", "https://vps.ts.net", "http://127.0.0.1:3773"],
196 reported: ["https://vps.ts.net", "http://localhost:3773"],
197 });
198 const mini = presentation("environment-mini", "Mini", { connected: false });
199 expect(
200 linkableMachines({
201 environments: [here, vps, mini],
202 threadEnvironmentId: here.environmentId,
203 links: [{ environmentId: mini.environmentId, status: "unreachable" }],
204 connect: new Map(),
205 }),
206 ).toEqual([
207 {
208 environmentId: vps.environmentId,
209 label: "VPS",
210 machine: "laptop",
211 connected: true,
212 connectOnly: false,
213 linked: false,
214 urls: ["https://vps.ts.net", "https://vps.example.com"],
215 },
216 {
217 environmentId: mini.environmentId,
218 label: "Mini",
219 machine: "server",
220 connected: false,
221 connectOnly: false,
222 linked: true,
223 urls: [],
224 },
225 ]);
226 });
227​
228 it("reaches a machine saved only over T3 Connect at its Connect address", () => {
229 const here = presentation("environment-here", "Here", { saved: ["http://localhost:3773"] });
230 const box = presentation("environment-box", "Box");
231 const [listed] = linkableMachines({
232 environments: [here, box],
233 threadEnvironmentId: here.environmentId,
234 links: [],
235 connect: new Map([[box.environmentId, discovered(box.environmentId, "Box")]]),
236 });
237 expect(listed?.urls).toEqual(["https://environment-box.t3.example"]);
238 // Until discovery has found it, there is no address to offer.
239 expect(
240 linkableMachines({
241 environments: [here, box],
242 threadEnvironmentId: here.environmentId,
243 links: [],
244 connect: new Map(),
245 })[0]?.urls,
246 ).toEqual([]);
247 });
248​
249 it("also offers the Connect address of a machine saved by direct address, last", () => {
250 const here = presentation("environment-here", "Here", { saved: ["http://localhost:3773"] });
251 const vps = presentation("environment-vps", "VPS", {
252 saved: ["http://192.168.1.20:3773"],
253 reported: ["https://vps.ts.net"],
254 });
255 const [listed] = linkableMachines({
256 environments: [here, vps],
257 threadEnvironmentId: here.environmentId,
258 links: [],
259 connect: new Map([[vps.environmentId, discovered(vps.environmentId, "VPS")]]),
260 });
261 expect(listed?.urls).toEqual([
262 "https://vps.ts.net",
263 "http://192.168.1.20:3773",
264 "https://environment-vps.t3.example",
265 ]);
266 });
267​
268 it("no longer counts a machine as linked once its link expired", () => {
269 const here = presentation("environment-here", "Here", { saved: ["http://localhost:3773"] });
270 const vps = presentation("environment-vps", "VPS", { saved: ["https://vps.ts.net"] });
271 const mini = presentation("environment-mini", "Mini", { saved: ["http://mini.local:3773"] });
272 const listed = linkableMachines({
273 environments: [here, vps, mini],
274 threadEnvironmentId: here.environmentId,
275 links: [
276 { environmentId: vps.environmentId, status: "expired" },
277 { environmentId: mini.environmentId, status: "unreachable" },
278 { environmentId: EnvironmentId.make("environment-old"), status: "expired" },
279 ],
280 connect: new Map([["environment-old", discovered("environment-old", "Old box")]]),
281 });
282 expect(listed.map((machine) => [machine.label, machine.linked])).toEqual([
283 ["VPS", false],
284 ["Mini", true],
285 ["Old box", false],
286 ]);
287 });
288​
289 it("offers machines only on T3 Connect after the saved ones, at their Connect address", () => {
290 const here = presentation("environment-here", "Here", { saved: ["http://localhost:3773"] });
291 const vps = presentation("environment-vps", "VPS", { saved: ["https://vps.ts.net"] });
292 const listed = linkableMachines({
293 environments: [here, vps],
294 threadEnvironmentId: here.environmentId,
295 links: [{ environmentId: EnvironmentId.make("environment-old"), status: "reachable" }],
296 connect: new Map([
297 // This environment and saved machines are listed once, as saved.
298 [here.environmentId, discovered(here.environmentId, "Here")],
299 [vps.environmentId, discovered(vps.environmentId, "VPS")],
300 ["environment-mini", discovered("environment-mini", "Mac mini", "mac-mini")],
301 ["environment-old", discovered("environment-old", "Old box")],
302 ]),
303 });
304 expect(listed.map((machine) => [machine.label, machine.connectOnly])).toEqual([
305 ["VPS", false],
306 ["Mac mini", true],
307 ["Old box", true],
308 ]);
309 expect(listed[1]).toEqual({
310 environmentId: EnvironmentId.make("environment-mini"),
311 label: "Mac mini",
312 machine: "mac-mini",
313 connected: false,
314 connectOnly: true,
315 linked: false,
316 urls: ["https://environment-mini.t3.example"],
317 });
318 expect(listed[2]).toMatchObject({ machine: "server", linked: true });
319​
320 // A Connect record that publishes only a loopback address gives the machine none.
321 const local = discovered("environment-local", "Local");
322 const published = {
323 ...local,
324 environment: {
325 ...local.environment,
326 endpoint: { ...local.environment.endpoint, httpBaseUrl: "http://127.0.0.1:3773/" },
327 },
328 };
329 expect(
330 linkableMachines({
331 environments: [],
332 threadEnvironmentId: EnvironmentId.make("environment-here"),
333 links: [],
334 connect: new Map([["environment-local", published]]),
335 })[0]?.urls,
336 ).toEqual([]);
337 });
338});
339​
340describe("linkedEnvironmentNames", () => {
341 it("names the unexpired links as this client does, else as the link does", () => {
342 const vps = EnvironmentId.make("environment-vps");
343 const mini = EnvironmentId.make("environment-mini");
344 const unsaved = EnvironmentId.make("environment-unsaved");
345 expect(
346 linkedEnvironmentNames({
347 links: [
348 { environmentId: vps, label: "vps-01", status: "unreachable" },
349 { environmentId: mini, label: "mini", status: "expired" },
350 { environmentId: unsaved, label: "Office box", status: "reachable" },
351 ],
352 environments: [
353 { environmentId: vps, label: "Hetzner VPS" },
354 { environmentId: mini, label: "Mac mini" },
355 ],
356 }),
357 ).toEqual(
358 new Map([
359 [vps, "Hetzner VPS"],
360 [unsaved, "Office box"],
361 ]),
362 );
363 });
364});
365​
366describe("rankMachinesByHint", () => {
367 const machines = [
368 { label: "Old vps box" },
369 { label: "VPS-2" },
370 { label: "Mac mini" },
371 { label: "vps" },
372 ];
373​
374 it("puts an exact name first, then a prefix, then a substring, keeping order within each", () => {
375 expect(rankMachinesByHint(machines, " VPS ").map((machine) => machine.label)).toEqual([
376 "vps",
377 "VPS-2",
378 "Old vps box",
379 "Mac mini",
380 ]);
381 expect(rankMachinesByHint(machines, undefined)).toBe(machines);
382 });
383​
384 it("preselects the card's machine, or the hint's match, and nothing for a hint that matches none", () => {
385 const listed = [
386 { environmentId: EnvironmentId.make("environment-mini"), label: "Mac mini" },
387 { environmentId: EnvironmentId.make("environment-vps"), label: "Hetzner VPS" },
388 ].map(
389 (machine) =>
390 ({
391 ...machine,
392 machine: "server",
393 connected: true,
394 connectOnly: false,
395 linked: false,
396 urls: [],
397 }) as const,
398 );
399 expect(linkRequestInitialMachine({ hint: "vps" }, listed)).toBe("environment-vps");
400 // Agents pass the user's words, which name the machine among others.
401 expect(linkRequestInitialMachine({ hint: "my mac mini" }, listed)).toBe("environment-mini");
402 expect(linkRequestInitialMachine({ hint: "my mac" }, listed)).toBe("environment-mini");
403 expect(linkRequestInitialMachine({ hint: "the hetzner vps box" }, listed)).toBe(
404 "environment-vps",
405 );
406 expect(linkRequestInitialMachine({ hint: "desk" }, listed)).toBeNull();
407 expect(linkRequestInitialMachine({ hint: "minimal" }, listed)).toBeNull();
408 expect(
409 linkRequestInitialMachine(
410 { environmentId: EnvironmentId.make("environment-x"), hint: "vps" },
411 listed,
412 ),
413 ).toBe("environment-x");
414 });
415});
416​
417describe("linkRequestTarget", () => {
418 const box = {
419 environmentId: EnvironmentId.make("environment-box"),
420 label: "Box",
421 machine: "server",
422 connected: true,
423 connectOnly: false,
424 linked: false,
425 urls: ["http://10.0.0.2:3773", "https://box.t3connect.example/"],
426 } as const;
427​
428 it("takes a machine from an address the agent gave only where this app already reaches it", () => {
429 // Anything can answer at an address as the box, so an id beside one is not trusted.
430 const unfamiliar = {
431 url: "https://other.example",
432 environmentId: box.environmentId,
433 label: "Box",
434 };
435 expect(linkRequestTarget(unfamiliar, [box], null)).toEqual({ kind: "address" });
436 expect(linkRequestTarget(unfamiliar, [box], box.environmentId)).toEqual({ kind: "address" });
437 expect(linkRequestTargetName(unfamiliar)).toBe("https://other.example");
438 // A username in the address must not pass for the host the code goes to.
439 expect(linkRequestTargetName({ url: "https://box.t3connect.example@other.example/x" })).toBe(
440 "https://other.example",
441 );
442 // A known address in another spelling is the box, linked at its own addresses only.
443 expect(
444 linkRequestTarget({ url: "https://BOX.t3connect.example:443/mcp" }, [box], null),
445 ).toEqual({
446 kind: "machine",
447 environmentId: box.environmentId,
448 label: "Box",
449 urls: box.urls,
450 linked: false,
451 });
452 expect(linkRequestTarget({ url: "http://box.t3connect.example" }, [box], null)).toEqual({
453 kind: "address",
454 });
455 expect(linkRequestTarget({ url: "not a url" }, [box], null)).toEqual({ kind: "address" });
456 expect(linkRequestTarget({ url: "blob:https://box.t3connect.example/1" }, [box], null)).toEqual(
457 { kind: "address" },
458 );
459 // A private address two machines share names neither.
460 const mini = { ...box, environmentId: EnvironmentId.make("environment-mini") };
461 expect(linkRequestTarget({ url: "http://10.0.0.2:3773" }, [box, mini], null)).toEqual({
462 kind: "address",
463 });
464 });
465​
466 it("links the machine the agent named or the user picked, at the addresses this app has", () => {
467 expect(linkRequestTarget({ environmentId: box.environmentId }, [box], null)).toMatchObject({
468 kind: "machine",
469 label: "Box",
470 urls: box.urls,
471 });
472 expect(linkRequestTarget({}, [box], box.environmentId)).toMatchObject({ kind: "machine" });
473 expect(linkRequestTarget({}, [box], ANOTHER_ADDRESS)).toEqual({ kind: "address" });
474 expect(linkRequestTarget({}, [box], null)).toEqual({ kind: "none" });
475 });
476});
477​
478describe("linkRequestFailureMessage", () => {
479 it("passes through only known user-facing errors", () => {
480 expect(
481 linkRequestFailureMessage({ _tag: "PeerLinkRequestError", message: "Already answered." }),
482 ).toBe("Already answered.");
483 expect(linkRequestFailureMessage({ _tag: "SchemaError", message: "code=abc" })).toBe(
484 "Could not answer the request. Try again.",
485 );
486 });
487});