Skip to content

Commit 3efa332

Browse files
test(server): interrupted Serve setup must still disable the applied mapping
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent bc8cb10 commit 3efa332

1 file changed

Lines changed: 34 additions & 6 deletions

File tree

‎apps/server/src/tailscaleServe.test.ts‎

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { assert, describe, it } from "@effect/vitest";
22
import * as HostProcess from "@t3tools/shared/HostProcess";
33
import * as Deferred from "effect/Deferred";
4+
import * as Duration from "effect/Duration";
45
import * as Effect from "effect/Effect";
56
import * as Exit from "effect/Exit";
7+
import * as Fiber from "effect/Fiber";
68
import * as Layer from "effect/Layer";
79
import * as Scope from "effect/Scope";
810
import * as Sink from "effect/Sink";
@@ -18,8 +20,13 @@ const encoder = new TextEncoder();
1820

1921
// Stands in for the tailscale CLI: `serve --bg` is refused until the daemon
2022
// accepts it (operator permission granted, or Tailscale.app finished starting).
21-
// With `hangs`, the attempt after the refusals never answers.
22-
const makeFakeTailscale = (refusedServeAttempts: number, hangs = false) => {
23+
// With `hangs`, the attempt after the refusals never answers. With `ackDelay`,
24+
// the daemon applies the mapping at once but the CLI exits only after the delay.
25+
const makeFakeTailscale = (
26+
refusedServeAttempts: number,
27+
hangs = false,
28+
ackDelay?: Duration.Input,
29+
) => {
2330
const state = { serveAttempts: 0, configured: false, disables: 0 };
2431
const handle = (args: ReadonlyArray<string>) => {
2532
if (args[0] === "serve" && args[1] === "--bg") {
@@ -31,7 +38,7 @@ const makeFakeTailscale = (refusedServeAttempts: number, hangs = false) => {
3138
return undefined;
3239
}
3340
state.configured = true;
34-
return { code: 0 };
41+
return { code: 0, ackDelay };
3542
}
3643
if (args[0] === "serve" && args.at(-1) === "off") {
3744
state.disables += 1;
@@ -49,7 +56,9 @@ const makeFakeTailscale = (refusedServeAttempts: number, hangs = false) => {
4956
exitCode:
5057
result === undefined
5158
? Effect.never
52-
: Effect.succeed(ChildProcessSpawner.ExitCode(result.code)),
59+
: Effect.succeed(ChildProcessSpawner.ExitCode(result.code)).pipe(
60+
Effect.delay("ackDelay" in result && result.ackDelay ? result.ackDelay : 0),
61+
),
5362
isRunning: Effect.succeed(false),
5463
kill: () => Effect.void,
5564
unref: Effect.succeed(Effect.void),
@@ -65,8 +74,12 @@ const makeFakeTailscale = (refusedServeAttempts: number, hangs = false) => {
6574
return { state, spawner };
6675
};
6776

68-
const startTailscaleServe = Effect.fn(function* (refusedServeAttempts: number, hangs = false) {
69-
const fake = makeFakeTailscale(refusedServeAttempts, hangs);
77+
const startTailscaleServe = Effect.fn(function* (
78+
refusedServeAttempts: number,
79+
hangs = false,
80+
ackDelay?: Duration.Input,
81+
) {
82+
const fake = makeFakeTailscale(refusedServeAttempts, hangs, ackDelay);
7083
const parked = yield* Deferred.make<void>();
7184
const scope = yield* Scope.make();
7285
yield* Layer.buildWithScope(
@@ -136,6 +149,21 @@ describe("TailscaleServe", () => {
136149
}),
137150
);
138151

152+
it.effect("disables Serve on shutdown while the CLI has yet to acknowledge it", () =>
153+
Effect.gen(function* () {
154+
const { state, scope } = yield* startTailscaleServe(0, false, "2 seconds");
155+
yield* Effect.yieldNow;
156+
assert.equal(state.serveAttempts, 1);
157+
assert.isTrue(state.configured, "the daemon never received the mapping");
158+
159+
const shutdown = yield* Scope.close(scope, Exit.void).pipe(Effect.forkChild);
160+
yield* TestClock.adjust("2 seconds");
161+
yield* Fiber.join(shutdown);
162+
163+
assert.equal(state.disables, 1, "a mapping the daemon applied outlived shutdown");
164+
}),
165+
);
166+
139167
it.effect("stops retrying on shutdown and leaves Serve alone if no attempt succeeded", () =>
140168
Effect.gen(function* () {
141169
const { state, scope } = yield* startTailscaleServe(Number.POSITIVE_INFINITY);

0 commit comments

Comments
 (0)