1import { assert, describe, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import { HttpClient } from "effect/http";
5import { ChildProcessSpawner } from "effect/process";
6
7import {
8 parseTailscaleMagicDnsName,
9 resolveTailscaleAdvertisedEndpoints,
10} from "./tailscaleEndpointProvider.ts";
11
12const layerUnusedTailscaleExternalServices = Layer.mergeAll(
13 Layer.succeed(
14 HttpClient.HttpClient,
15 HttpClient.make(() => Effect.die("unexpected Tailscale HTTPS probe")),
16 ),
17 Layer.succeed(
18 ChildProcessSpawner.ChildProcessSpawner,
19 ChildProcessSpawner.make(() => Effect.die("unexpected tailscale status process")),
20 ),
21);
22
23describe("tailscale endpoint provider", () => {
24 it.effect("parses MagicDNS names from tailscale status", () =>
25 Effect.gen(function* () {
26 const dnsName = yield* parseTailscaleMagicDnsName(
27 `{"Self":{"DNSName":"desktop.tail.ts.net."}}`,
28 );
29 assert.equal(dnsName, "desktop.tail.ts.net");
30 assert.equal(yield* parseTailscaleMagicDnsName("{}"), null);
31 const malformed = yield* Effect.result(parseTailscaleMagicDnsName("not-json"));
32 assert.isTrue(malformed._tag === "Failure");
33 }),
34 );
35
36 it.effect("resolves Tailscale endpoints as add-on advertised endpoints", () =>
37 Effect.gen(function* () {
38 const endpoints = yield* resolveTailscaleAdvertisedEndpoints({
39 port: 3773,
40 networkInterfaces: {
41 tailscale0: [
42 {
43 address: "100.100.100.100",
44 family: "IPv4",
45 internal: false,
46 netmask: "255.192.0.0",
47 cidr: "100.100.100.100/10",
48 mac: "00:00:00:00:00:00",
49 },
50 ],
51 },
52 statusJson: `{"Self":{"DNSName":"desktop.tail.ts.net."}}`,
53 });
54 assert.deepEqual(endpoints, [
55 {
56 id: "tailscale-ip:http://100.100.100.100:3773",
57 label: "Tailscale IP",
58 provider: {
59 id: "tailscale",
60 label: "Tailscale",
61 kind: "private-network",
62 isAddon: true,
63 },
64 httpBaseUrl: "http://100.100.100.100:3773/",
65 wsBaseUrl: "ws://100.100.100.100:3773/",
66 reachability: "private-network",
67 compatibility: {
68 hostedHttpsApp: "mixed-content-blocked",
69 desktopApp: "compatible",
70 },
71 source: "desktop-addon",
72 status: "available",
73 description: "Reachable from devices on the same Tailnet.",
74 },
75 {
76 id: "tailscale-magicdns:https://desktop.tail.ts.net/",
77 label: "Tailscale HTTPS",
78 provider: {
79 id: "tailscale",
80 label: "Tailscale",
81 kind: "private-network",
82 isAddon: true,
83 },
84 httpBaseUrl: "https://desktop.tail.ts.net/",
85 wsBaseUrl: "wss://desktop.tail.ts.net/",
86 reachability: "private-network",
87 compatibility: {
88 hostedHttpsApp: "requires-configuration",
89 desktopApp: "compatible",
90 },
91 source: "desktop-addon",
92 status: "unavailable",
93 description: "MagicDNS hostname. Configure Tailscale Serve for HTTPS access.",
94 },
95 ]);
96 }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)),
97 );
98
99 it.effect("uses an injected magic DNS name reader instead of spawning tailscale", () =>
100 Effect.gen(function* () {
101 let readerCalls = 0;
102 const endpoints = yield* resolveTailscaleAdvertisedEndpoints({
103 port: 3773,
104 networkInterfaces: {},
105 readMagicDnsName: Effect.sync(() => {
106 readerCalls += 1;
107 return "desktop.tail.ts.net";
108 }),
109 });
110 assert.equal(readerCalls, 1);
111 assert.deepEqual(
112 endpoints.map((endpoint) => endpoint.httpBaseUrl),
113 ["https://desktop.tail.ts.net/"],
114 );
115 }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)),
116 );
117
118 it.effect(
119 "marks the Tailscale HTTPS endpoint available after Serve is enabled and reachable",
120 () =>
121 Effect.gen(function* () {
122 const endpoints = yield* resolveTailscaleAdvertisedEndpoints({
123 port: 3773,
124 networkInterfaces: {},
125 statusJson: `{"Self":{"DNSName":"desktop.tail.ts.net."}}`,
126 serveEnabled: true,
127 probe: () => Effect.succeed(true),
128 });
129 assert.deepEqual(endpoints, [
130 {
131 id: "tailscale-magicdns:https://desktop.tail.ts.net/",
132 label: "Tailscale HTTPS",
133 provider: {
134 id: "tailscale",
135 label: "Tailscale",
136 kind: "private-network",
137 isAddon: true,
138 },
139 httpBaseUrl: "https://desktop.tail.ts.net/",
140 wsBaseUrl: "wss://desktop.tail.ts.net/",
141 reachability: "private-network",
142 compatibility: {
143 hostedHttpsApp: "compatible",
144 desktopApp: "compatible",
145 },
146 source: "desktop-addon",
147 status: "available",
148 description: "HTTPS endpoint served by Tailscale Serve.",
149 },
150 ]);
151 }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)),
152 );
153});