Skip to content
Source
Sign inSign up

previewTunnelSweep.test.ts

101 lines3.6 KB

1import { describe, expect, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Exit from "effect/Exit";
4import * as Redacted from "effect/Redacted";
5import * as HttpClient from "effect/http/HttpClient";
6import * as HttpClientResponse from "effect/http/HttpClientResponse";
7​
8import { sweepPreviewStage } from "./previewTunnelSweep.ts";
9​
10const KEY = "0123456789abcdef";
11const TUNNEL_NAME = `t3coderelay-managedendpoint-pr-42-${KEY}`;
12​
13// Answers the Cloudflare endpoints the sweep calls and records each request.
14const fakeCloudflare = (options: {
15 readonly tunnels: ReadonlyArray<{ readonly id: string; readonly name: string }>;
16 readonly zoneExists: boolean;
17 readonly deleteStatus?: number;
18}) => {
19 const seen: Array<string> = [];
20 const client = HttpClient.make((request) => {
21 const url = new URL(request.url);
22 seen.push(`${request.method} ${url.pathname}`);
23 const reply = (status: number, body: unknown) =>
24 Effect.succeed(
25 HttpClientResponse.fromWeb(
26 request,
27 new Response(JSON.stringify(body), {
28 status,
29 headers: { "content-type": "application/json" },
30 }),
31 ),
32 );
33 if (request.method === "DELETE") {
34 return reply(options.deleteStatus ?? 200, { success: true });
35 }
36 if (url.pathname.endsWith("/cfd_tunnel")) return reply(200, { result: options.tunnels });
37 if (url.pathname.endsWith("/zones")) {
38 return reply(200, { result: options.zoneExists ? [{ id: "zone-1" }] : [] });
39 }
40 if (url.pathname.endsWith("/dns_records")) return reply(200, { result: [] });
41 return reply(404, {});
42 });
43 return { client, seen };
44};
45​
46const sweep = (cloudflare: ReturnType<typeof fakeCloudflare>) =>
47 sweepPreviewStage({
48 stage: "pr-42",
49 tunnelZoneName: "tunnels.example.com",
50 accountId: "account-1",
51 token: Redacted.make("token"),
52 }).pipe(Effect.provideService(HttpClient.HttpClient, cloudflare.client));
53​
54describe("sweepPreviewStage", () => {
55 it.effect("does not need the zone once no tunnels remain", () =>
56 Effect.gen(function* () {
57 const cloudflare = fakeCloudflare({ tunnels: [], zoneExists: false });
58 yield* sweep(cloudflare);
59 expect(cloudflare.seen.some((request) => request.includes("/zones"))).toBe(false);
60 }),
61 );
62​
63 it.effect("removes tunnels even when their zone is already gone", () =>
64 Effect.gen(function* () {
65 const cloudflare = fakeCloudflare({
66 tunnels: [{ id: "tunnel-1", name: TUNNEL_NAME }],
67 zoneExists: false,
68 });
69 yield* sweep(cloudflare);
70 expect(cloudflare.seen).toContain(
71 "DELETE /client/v4/accounts/account-1/cfd_tunnel/tunnel-1/connections",
72 );
73 expect(cloudflare.seen).toContain("DELETE /client/v4/accounts/account-1/cfd_tunnel/tunnel-1");
74 expect(cloudflare.seen.some((request) => request.includes("/dns_records"))).toBe(false);
75 }),
76 );
77​
78 it.effect("treats an already-deleted tunnel as swept", () =>
79 Effect.gen(function* () {
80 const cloudflare = fakeCloudflare({
81 tunnels: [{ id: "tunnel-1", name: TUNNEL_NAME }],
82 zoneExists: false,
83 deleteStatus: 404,
84 });
85 const exit = yield* Effect.exit(sweep(cloudflare));
86 expect(Exit.isSuccess(exit)).toBe(true);
87 }),
88 );
89​
90 it.effect("fails when Cloudflare rate-limits a delete so the next run retries it", () =>
91 Effect.gen(function* () {
92 const cloudflare = fakeCloudflare({
93 tunnels: [{ id: "tunnel-1", name: TUNNEL_NAME }],
94 zoneExists: false,
95 deleteStatus: 429,
96 });
97 const exit = yield* Effect.exit(sweep(cloudflare));
98 expect(Exit.isFailure(exit)).toBe(true);
99 }),
100 );
101});