1import { Action } from "@/Action";
2import * as Cloudflare from "@/Cloudflare";
3import * as Test from "@/Test/Alchemy";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7
8const { test } = Test.make({ providers: Cloudflare.providers() });
9
10const logLevel = Effect.provideService(
11 MinimumLogLevel,
12 process.env.DEBUG ? "Debug" : "Info",
13);
14
15// Binding a Cloudflare Tunnel inside an Action via `ReadWriteTunnelLocal` — the
16// local (current-credentials) implementation of the `ReadWriteTunnel` binding.
17// Exercises the write side (`putConfiguration`) and the read side
18// (`getConfiguration`/`get`/`getToken`) against a real tunnel, plus the deferred
19// `yield* tunnel.tunnelId` accessor resolved at apply time.
20test.provider(
21 "ReadWriteTunnelLocal: configure and read a tunnel from an Action",
22 (stack) =>
23 Effect.gen(function* () {
24 yield* stack.destroy();
25
26 const out = yield* stack.deploy(
27 Effect.gen(function* () {
28 const tunnel = yield* Cloudflare.Tunnel.Tunnel("LocalBindingTunnel");
29
30 const Manage = Action(
31 "Manage",
32 Effect.gen(function* () {
33 const tunnels = yield* Cloudflare.Tunnel.ReadWriteTunnel();
34 // Accessor — resolved at apply time against the tracker.
35 const tunnelId = yield* tunnel.tunnelId;
36
37 return Effect.fn(function* () {
38 const id = yield* tunnelId;
39
40 // Write: push ingress configuration.
41 yield* tunnels.putConfiguration(id, {
42 ingress: [
43 {
44 hostname: "local-binding.alchemy-test-2.us",
45 service: "http://localhost:3000",
46 },
47 { service: "http_status:404" },
48 ],
49 });
50
51 // Read back the configuration we just wrote.
52 const cfg = yield* tunnels.getConfiguration(id);
53 // Read the tunnel + its connector token.
54 const t = yield* tunnels.get(id);
55 const token = yield* tunnels.getToken(id);
56
57 return {
58 tunnelId: id,
59 name: t.id === id ? t.name : undefined,
60 ingress: (cfg.config?.ingress ?? []).map((rule) => ({
61 hostname: rule.hostname ?? undefined,
62 service: rule.service,
63 })),
64 hasToken: typeof token === "string" && token.length > 0,
65 };
66 });
67 }).pipe(Effect.provide(Cloudflare.Tunnel.ReadWriteTunnelLocal)),
68 );
69
70 return yield* Manage({});
71 }),
72 );
73
74 expect(out.tunnelId).toBeTruthy();
75 expect(out.hasToken).toBe(true);
76 expect(out.ingress).toEqual([
77 {
78 hostname: "local-binding.alchemy-test-2.us",
79 service: "http://localhost:3000",
80 },
81 { hostname: undefined, service: "http_status:404" },
82 ]);
83
84 yield* stack.destroy();
85 }).pipe(logLevel),
86 {
87 tags: ["provider:cloudflare", "provider:cloudflare:tunnel", "live"],
88 timeout: 120_000,
89 },
90);