1import {
2 RelayEnvironmentEndpointUnavailableError,
3 type RelayProtectedError,
4} from "@t3tools/contracts/relay";
5import { describe, expect, it } from "@effect/vitest";
6
7import { presentConnectionDiagnosis } from "./diagnosis.ts";
8import { mapManagedRelayError, mapRemoteEnvironmentError } from "./errors.ts";
9import {
10 AVAILABLE_CONNECTION_STATE,
11 ConnectionBlockedError,
12 ConnectionTransientError,
13 type ConnectionAttemptError,
14 type SupervisorConnectionState,
15} from "./model.ts";
16import { ManagedRelayRequestFailedError } from "../relay/managedRelay.ts";
17import { RemoteEnvironmentAuthFetchError } from "../rpc/http.ts";
18
19const backoff = (lastFailure: ConnectionAttemptError): SupervisorConnectionState => ({
20 ...AVAILABLE_CONNECTION_STATE,
21 desired: true,
22 network: "online",
23 phase: "backoff",
24 attempt: 3,
25 lastFailure,
26 retryAt: Date.UTC(2026, 9, 19, 12, 0, 30),
27});
28
29const fromRelay = (relayError: RelayProtectedError) =>
30 mapManagedRelayError(
31 new ManagedRelayRequestFailedError({
32 action: "connect relay environment",
33 cause: relayError,
34 relayError,
35 }),
36 );
37
38const unavailable = (endpointFailure: "tunnel_down" | "origin_unreachable") =>
39 fromRelay(
40 new RelayEnvironmentEndpointUnavailableError({
41 code: "environment_endpoint_unavailable",
42 reason: "endpoint_request_failed",
43 endpointFailure,
44 endpointStatus: endpointFailure === "tunnel_down" ? 530 : 502,
45 traceId: "trace-1",
46 }),
47 );
48
49const diagnose = (state: SupervisorConnectionState, via: "relay" | "direct" = "relay") =>
50 presentConnectionDiagnosis({ hostLabel: "Mac mini", state, via });
51
52const results = (state: SupervisorConnectionState, via?: "relay" | "direct") =>
53 diagnose(state, via)?.hops.map((hop) => `${hop.hop}:${hop.result}`);
54
55describe("presentConnectionDiagnosis", () => {
56 it("tells a working tunnel apart from T3 Code not answering", () => {
57 expect(diagnose(backoff(unavailable("origin_unreachable")))?.headline).toBe(
58 "Tunnel works, but T3 Code on Mac mini did not answer",
59 );
60 expect(results(backoff(unavailable("origin_unreachable")), "direct")).toEqual([
61 "device:ok",
62 "relay:ok",
63 "tunnel:ok",
64 "server:failed",
65 ]);
66 });
67
68 it("blames this device when it cannot reach a tunnel the relay already reached", () => {
69 const failure = mapRemoteEnvironmentError(
70 new RemoteEnvironmentAuthFetchError({ message: "Failed to fetch", cause: new TypeError() }),
71 "relay",
72 );
73 // A learned LAN route makes the environment's usual route direct; the failure still names the relay.
74 const diagnosis = diagnose(backoff(failure), "direct");
75 expect(diagnosis?.headline).toBe("This device can't reach Mac mini's tunnel");
76 expect(diagnosis?.hops).toHaveLength(4);
77 expect(diagnosis?.steps.join(" ")).not.toContain("On Mac mini");
78 });
79
80 it("stops at T3 Connect when this device cannot reach the relay", () => {
81 const failure = mapManagedRelayError(
82 new ManagedRelayRequestFailedError({
83 action: "connect relay environment",
84 transportFailed: true,
85 cause: new TypeError("Failed to fetch"),
86 }),
87 );
88 expect(diagnose(backoff(failure))?.headline).toBe("This device can't reach T3 Connect");
89 expect(results(backoff(failure))).toEqual([
90 "device:ok",
91 "relay:failed",
92 "tunnel:unchecked",
93 "server:unchecked",
94 ]);
95 });
96
97 it("does not blame this device's network when T3 Connect answered with an error", () => {
98 const failure = mapManagedRelayError(
99 new ManagedRelayRequestFailedError({
100 action: "connect relay environment",
101 cause: new Error("Bad Gateway"),
102 status: 502,
103 }),
104 );
105 const diagnosis = diagnose(backoff(failure));
106 expect(diagnosis?.headline).toBe("T3 Connect returned an error");
107 expect(diagnosis?.steps.join(" ")).not.toContain("this device");
108 });
109
110 it("reports this device as offline before any other hop", () => {
111 const state = { ...backoff(unavailable("tunnel_down")), network: "offline" as const };
112 expect(diagnose(state)?.headline).toBe("This device is offline");
113 expect(results(state)?.[0]).toBe("device:failed");
114 });
115
116 it("uses the relay health check before any connect attempt fails", () => {
117 const diagnosis = presentConnectionDiagnosis({
118 hostLabel: "Mac mini",
119 state: AVAILABLE_CONNECTION_STATE,
120 via: "relay",
121 relayStatus: { status: "offline", endpointFailure: "tunnel_down", endpointStatus: 530 },
122 });
123 expect(diagnosis?.headline).toBe("Mac mini's tunnel is offline");
124 const origin = presentConnectionDiagnosis({
125 hostLabel: "Mac mini",
126 state: AVAILABLE_CONNECTION_STATE,
127 via: "direct",
128 relayStatus: { status: "offline", endpointFailure: "origin_unreachable" },
129 });
130 expect(origin?.hops.map((hop) => hop.hop)).toEqual(["device", "relay", "tunnel", "server"]);
131 });
132
133 it("blames this device, not the host, while its own T3 Connect sign-in loads", () => {
134 const failure = new ConnectionTransientError({
135 reason: "session-pending",
136 detail: "Waiting for the T3 Connect sign-in to load.",
137 });
138 const diagnosis = diagnose(backoff(failure));
139 expect(diagnosis?.headline).not.toContain("did not answer");
140 expect(diagnosis?.hops.find((hop) => hop.result === "failed")?.hop).toBe("device");
141 expect(diagnosis?.steps.join(" ")).not.toContain("Mac mini");
142 expect(diagnosis?.moreInfoUrl).toContain("sign-in-account-or-link-problems");
143 // A host with a LAN route as well uses the direct route, still authorized through T3 Connect.
144 expect(diagnose(backoff(failure), "direct")?.moreInfoUrl).toContain(
145 "sign-in-account-or-link-problems",
146 );
147 });
148
149 it("does not report hops as working when the failure names none", () => {
150 const failure = new ConnectionBlockedError({
151 reason: "authentication",
152 detail: "Your cloud sign-in changed. Sign in again to authorize the environment.",
153 });
154 expect(results(backoff(failure))).not.toContainEqual(expect.stringMatching(/:ok$/));
155 });
156
157 it("keeps the relay health status out of a failure that has its own cause", () => {
158 const failure = mapRemoteEnvironmentError(
159 new RemoteEnvironmentAuthFetchError({ message: "Failed to fetch", cause: new TypeError() }),
160 );
161 const diagnosis = presentConnectionDiagnosis({
162 hostLabel: "Mac mini",
163 state: backoff(failure),
164 via: "direct",
165 relayStatus: { status: "offline", endpointFailure: "tunnel_down", endpointStatus: 530 },
166 });
167 expect(diagnosis?.facts.map(([label]) => label)).not.toContain("HTTP status");
168 });
169});