1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 RemoteBackendUrlInvalidError,
5 RemoteBackendUrlMissingError,
6 RemotePairingTokenMissingError,
7 RemotePairingUrlInvalidError,
8 resolveRemotePairingTarget,
9} from "./remote.ts";
10
11describe("remote", () => {
12 it("derives backend urls and token from a pairing url", () => {
13 expect(
14 resolveRemotePairingTarget({
15 pairingUrl: "https://remote.example.com/pair#token=pairing-token",
16 }),
17 ).toEqual({
18 credential: "pairing-token",
19 httpBaseUrl: "https://remote.example.com/",
20 wsBaseUrl: "wss://remote.example.com/",
21 });
22 });
23
24 it.each([
25 { pairingUrl: "https://hub.example/prefix/environments/one/pair#token=pairing-token" },
26 { pairingUrl: "https://hub.example/prefix/environments/one/#token=pairing-token" },
27 {
28 pairingUrl:
29 "https://app.example/pair?host=https%3A%2F%2Fhub.example%2Fprefix%2Fenvironments%2Fone%2F#token=pairing-token",
30 },
31 {
32 host: "wss://hub.example/prefix/environments/one///?old=1#fragment",
33 pairingCode: "pairing-token",
34 },
35 ])("preserves a proxied environment base through pairing: %o", (input) => {
36 expect(resolveRemotePairingTarget(input)).toEqual({
37 credential: "pairing-token",
38 httpBaseUrl: "https://hub.example/prefix/environments/one/",
39 wsBaseUrl: "wss://hub.example/prefix/environments/one/",
40 });
41 });
42
43 it("accepts pairing urls that still use a query token", () => {
44 expect(
45 resolveRemotePairingTarget({
46 pairingUrl: "https://remote.example.com/pair?token=pairing-token",
47 }),
48 ).toEqual({
49 credential: "pairing-token",
50 httpBaseUrl: "https://remote.example.com/",
51 wsBaseUrl: "wss://remote.example.com/",
52 });
53 });
54
55 it("derives backend urls from hosted app pairing links", () => {
56 expect(
57 resolveRemotePairingTarget({
58 pairingUrl:
59 "https://app.t3.codes/pair?host=https%3A%2F%2Fdesktop.tailnet.ts.net%3A44342%2F#token=pairing-token",
60 }),
61 ).toEqual({
62 credential: "pairing-token",
63 httpBaseUrl: "https://desktop.tailnet.ts.net:44342/",
64 wsBaseUrl: "wss://desktop.tailnet.ts.net:44342/",
65 });
66 });
67
68 it("derives backend urls from a host and pairing code", () => {
69 expect(
70 resolveRemotePairingTarget({
71 host: "https://remote.example.com",
72 pairingCode: "pairing-token",
73 }),
74 ).toEqual({
75 credential: "pairing-token",
76 httpBaseUrl: "https://remote.example.com/",
77 wsBaseUrl: "wss://remote.example.com/",
78 });
79 });
80
81 it("treats a protocol-relative host as https", () => {
82 expect(
83 resolveRemotePairingTarget({
84 host: "//remote.example.com",
85 pairingCode: "pairing-token",
86 }),
87 ).toEqual({
88 credential: "pairing-token",
89 httpBaseUrl: "https://remote.example.com/",
90 wsBaseUrl: "wss://remote.example.com/",
91 });
92 });
93
94 it("preserves the port when normalizing a protocol-relative host", () => {
95 expect(
96 resolveRemotePairingTarget({
97 host: "//remote.example.com:3000",
98 pairingCode: "pairing-token",
99 }),
100 ).toEqual({
101 credential: "pairing-token",
102 httpBaseUrl: "https://remote.example.com:3000/",
103 wsBaseUrl: "wss://remote.example.com:3000/",
104 });
105 });
106
107 it("normalizes a protocol-relative host from a hosted pairing link", () => {
108 expect(
109 resolveRemotePairingTarget({
110 pairingUrl: "https://app.t3.codes/pair?host=%2F%2Fremote.example.com#token=pairing-token",
111 }),
112 ).toEqual({
113 credential: "pairing-token",
114 httpBaseUrl: "https://remote.example.com/",
115 wsBaseUrl: "wss://remote.example.com/",
116 });
117 });
118
119 it("collapses extra leading slashes instead of producing an empty host", () => {
120 expect(
121 resolveRemotePairingTarget({
122 host: "///example.com",
123 pairingCode: "pairing-token",
124 }),
125 ).toEqual({
126 credential: "pairing-token",
127 httpBaseUrl: "https://example.com/",
128 wsBaseUrl: "wss://example.com/",
129 });
130 });
131
132 it("does not double-prepend https when the host already carries a scheme", () => {
133 expect(
134 resolveRemotePairingTarget({
135 host: "//https://example.com",
136 pairingCode: "pairing-token",
137 }),
138 ).toEqual({
139 credential: "pairing-token",
140 httpBaseUrl: "https://example.com/",
141 wsBaseUrl: "wss://example.com/",
142 });
143 });
144
145 it("preserves host ports when normalizing a bare host input", () => {
146 expect(
147 resolveRemotePairingTarget({
148 host: "myserver.com:3000",
149 pairingCode: "pairing-token",
150 }),
151 ).toEqual({
152 credential: "pairing-token",
153 httpBaseUrl: "https://myserver.com:3000/",
154 wsBaseUrl: "wss://myserver.com:3000/",
155 });
156 });
157
158 it("rejects unsupported direct pairing URL protocols", () => {
159 let pairingUrlError: unknown;
160 try {
161 resolveRemotePairingTarget({
162 pairingUrl: "ftp://remote.example.com/pair#token=pairing-token",
163 });
164 } catch (cause) {
165 pairingUrlError = cause;
166 }
167
168 expect(pairingUrlError).toBeInstanceOf(RemotePairingUrlInvalidError);
169 expect(pairingUrlError).toMatchObject({ protocol: "ftp:" });
170 expect((pairingUrlError as RemotePairingUrlInvalidError).cause).toBeUndefined();
171 });
172
173 it("rejects unsupported hosted pairing backend protocols", () => {
174 let hostError: unknown;
175 try {
176 resolveRemotePairingTarget({
177 pairingUrl:
178 "https://app.t3.codes/pair?host=ftp%3A%2F%2Fremote.example.com#token=pairing-token",
179 });
180 } catch (cause) {
181 hostError = cause;
182 }
183
184 expect(hostError).toBeInstanceOf(RemoteBackendUrlInvalidError);
185 expect(hostError).toMatchObject({ source: "hosted-pairing-host", protocol: "ftp:" });
186 expect((hostError as RemoteBackendUrlInvalidError).cause).toBeUndefined();
187 });
188
189 it("rejects unsupported direct host protocols", () => {
190 let hostError: unknown;
191 try {
192 resolveRemotePairingTarget({
193 host: "ftp://remote.example.com",
194 pairingCode: "pairing-token",
195 });
196 } catch (cause) {
197 hostError = cause;
198 }
199
200 expect(hostError).toBeInstanceOf(RemoteBackendUrlInvalidError);
201 expect(hostError).toMatchObject({ source: "direct-host", protocol: "ftp:" });
202 expect((hostError as RemoteBackendUrlInvalidError).cause).toBeUndefined();
203 });
204
205 it("uses distinct structural errors for missing pairing inputs", () => {
206 expect(() => resolveRemotePairingTarget({})).toThrowError(RemoteBackendUrlMissingError);
207 expect(() =>
208 resolveRemotePairingTarget({ pairingUrl: "https://remote.example.com/pair" }),
209 ).toThrowError(RemotePairingTokenMissingError);
210 expect(() =>
211 resolveRemotePairingTarget({
212 host: "https://user:secret@remote.example.com/path?token=sensitive#fragment",
213 }),
214 ).toThrowError(
215 expect.objectContaining({
216 _tag: "RemotePairingCodeMissingError",
217 host: "remote.example.com",
218 }),
219 );
220 });
221
222 it("preserves URL parsing causes with their input source", () => {
223 let pairingUrlError: unknown;
224 try {
225 resolveRemotePairingTarget({ pairingUrl: "not a url" });
226 } catch (cause) {
227 pairingUrlError = cause;
228 }
229 expect(pairingUrlError).toBeInstanceOf(RemotePairingUrlInvalidError);
230 expect((pairingUrlError as RemotePairingUrlInvalidError).cause).toBeInstanceOf(TypeError);
231
232 let hostError: unknown;
233 try {
234 resolveRemotePairingTarget({ host: "https://[invalid", pairingCode: "pairing-token" });
235 } catch (cause) {
236 hostError = cause;
237 }
238 expect(hostError).toBeInstanceOf(RemoteBackendUrlInvalidError);
239 expect(hostError).toMatchObject({ source: "direct-host" });
240 expect((hostError as RemoteBackendUrlInvalidError).cause).toBeInstanceOf(TypeError);
241 });
242});