| 1 | import { Action } from "@/Action"; |
| 2 | import * as Cloudflare from "@/Cloudflare"; |
| 3 | import * as Test from "@/Test/Alchemy"; |
| 4 | import { expect } from "alchemy-test"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import { MinimumLogLevel } from "effect/References"; |
| 7 | |
| 8 | const { test } = Test.make({ providers: Cloudflare.providers() }); |
| 9 | |
| 10 | const 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. |
| 20 | test.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 | ); |