Skip to content
Source
Sign inSign up

connect.test.ts

192 lines6.9 KB

1// @effect-diagnostics nodeBuiltinImport:off - CLI integration exercises the Node HTTP boundary.
2import * as NodeHttp from "node:http";
3​
4import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
5import * as NodeServices from "@effect/platform-node/NodeServices";
6import * as NetService from "@t3tools/shared/Net";
7import * as RelayClient from "@t3tools/shared/relayClient";
8import { assert, it } from "@effect/vitest";
9import * as ConfigProvider from "effect/ConfigProvider";
10import * as Console from "effect/Console";
11import * as Effect from "effect/Effect";
12import * as FileSystem from "effect/FileSystem";
13import * as Layer from "effect/Layer";
14import * as Option from "effect/Option";
15import * as TestConsole from "effect/testing/TestConsole";
16import { Command } from "effect/cli";
17​
18import { makeCli } from "../binCli.ts";
19import { CLOUD_CLI_DESIRED_LINK_SECRET } from "../cloud/CliState.ts";
20import {
21 makePersistedServerRuntimeState,
22 persistServerRuntimeState,
23} from "../serverRuntimeState.ts";
24import { acquireRelayClientForLink } from "./connect.ts";
25​
26const managedExecutable = {
27 status: "available",
28 executablePath: "/tmp/cloudflared",
29 source: "managed",
30 version: RelayClient.CLOUDFLARED_VERSION,
31} as const;
32​
33it.effect("does not install the relay client when the user declines the managed download", () =>
34 Effect.gen(function* () {
35 let installCalls = 0;
36 const result = yield* acquireRelayClientForLink(
37 {
38 resolve: Effect.succeed({
39 status: "missing",
40 version: RelayClient.CLOUDFLARED_VERSION,
41 }),
42 install: Effect.sync(() => {
43 installCalls += 1;
44 return managedExecutable;
45 }),
46 installWithProgress: () =>
47 Effect.sync(() => {
48 installCalls += 1;
49 return managedExecutable;
50 }),
51 pruneManagedVersions: Effect.void,
52 },
53 () => Effect.succeed(false),
54 () => Effect.void,
55 );
56​
57 assert.isTrue(Option.isNone(result));
58 assert.equal(installCalls, 0);
59 }),
60);
61​
62it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer, NetService.layer))(
63 "t3 connect unlink and status",
64 (it) => {
65 const runConnect = (
66 recordedPid: number,
67 options?: { readonly portFreed?: boolean; readonly command?: "unlink" | "status" },
68 ) =>
69 Effect.gen(function* () {
70 const requests: Array<string> = [];
71 const warnings: Array<string> = [];
72 const server = yield* Effect.acquireRelease(
73 Effect.callback<NodeHttp.Server>((resume) => {
74 const listening = NodeHttp.createServer((request, response) => {
75 requests.push(`${request.method} ${request.url}`);
76 response.writeHead(200, { "content-type": "application/json" });
77 response.end(JSON.stringify({ ok: true, endpointRuntimeStatus: null }));
78 });
79 listening.listen(0, "127.0.0.1", () => resume(Effect.succeed(listening)));
80 }),
81 (listening) => Effect.sync(() => listening.close()),
82 );
83 const address = server.address();
84 if (address === null || typeof address === "string") {
85 return yield* Effect.die(new Error("Expected a TCP address"));
86 }
87 if (options?.portFreed) {
88 yield* Effect.callback<void>((resume) => {
89 server.close(() => resume(Effect.void));
90 });
91 }
92​
93 const fs = yield* FileSystem.FileSystem;
94 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-connect-unlink-test-" });
95 const state = yield* makePersistedServerRuntimeState({
96 config: { host: "127.0.0.1", devUrl: undefined },
97 port: address.port,
98 });
99 yield* persistServerRuntimeState({
100 path: `${baseDir}/userdata/server-runtime.json`,
101 state: { ...state, pid: recordedPid },
102 });
103 if (options?.command === "status") {
104 // Status asks the server for reachability only for a managed link.
105 yield* fs.makeDirectory(`${baseDir}/userdata/secrets`, { recursive: true });
106 yield* fs.writeFileString(
107 `${baseDir}/userdata/secrets/${CLOUD_CLI_DESIRED_LINK_SECRET}.bin`,
108 "managed",
109 );
110 }
111​
112 yield* Command.runWith(makeCli({ cloudEnabled: true }), { version: "0.0.0" })([
113 "connect",
114 options?.command ?? "unlink",
115 "--base-dir",
116 baseDir,
117 ]).pipe(
118 // TestConsole has no reader for Console.warn.
119 Effect.updateService(Console.Console, (console) => ({
120 ...console,
121 warn: (...args: ReadonlyArray<unknown>) => {
122 warnings.push(args.join(" "));
123 },
124 })),
125 );
126​
127 return { requests, warnings, printed: (yield* TestConsole.logLines).join("\n") };
128 }).pipe(
129 Effect.scoped,
130 Effect.provide(
131 Layer.mergeAll(
132 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
133 Layer.fresh(TestConsole.layer),
134 ),
135 ),
136 );
137​
138 // Above Linux's PID ceiling, as in pair.test.ts: no process can hold it.
139 const deadPid = 4_194_305;
140​
141 it.effect("does not tell the user to restart a server that is gone", () =>
142 Effect.gen(function* () {
143 const { printed, warnings } = yield* runConnect(deadPid, { portFreed: true });
144​
145 assert.include(printed, "T3 Connect is disabled locally.");
146 assert.notInclude(warnings.join("\n"), "Restart that server");
147 }),
148 );
149​
150 it.effect("warns that a server that is gone may have left its connector running", () =>
151 Effect.gen(function* () {
152 const { warnings } = yield* runConnect(deadPid, { portFreed: true });
153​
154 assert.include(warnings.join("\n"), "its cloudflared process may still be running");
155 }),
156 );
157​
158 it.effect("does not send a token to whatever took over a dead server's port", () =>
159 Effect.gen(function* () {
160 const { requests } = yield* runConnect(deadPid);
161​
162 assert.deepEqual(requests, []);
163 }),
164 );
165​
166 it.effect("status does not send a token to whatever took over a dead server's port", () =>
167 Effect.gen(function* () {
168 const { requests } = yield* runConnect(deadPid, { command: "status" });
169​
170 assert.deepEqual(requests, []);
171 }),
172 );
173​
174 it.effect("status asks a running server to check reachability", () =>
175 Effect.gen(function* () {
176 const { requests } = yield* runConnect(process.pid, { command: "status" });
177​
178 assert.deepEqual(requests, ["POST /api/connect/reachability"]);
179 }),
180 );
181​
182 it.effect("asks a running server to stop its tunnel", () =>
183 Effect.gen(function* () {
184 const { requests, printed, warnings } = yield* runConnect(process.pid);
185​
186 assert.deepEqual(requests, ["POST /api/connect/unlink"]);
187 assert.include(printed, "T3 Connect is disabled locally.");
188 assert.notInclude(warnings.join("\n"), "cloudflared process");
189 }),
190 );
191 },
192);