1import * as Cloudflare from "@/Cloudflare";
2import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as zeroTrust from "@distilled.cloud/cloudflare/zero-trust";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10
11const { test } = Test.make({ providers: Cloudflare.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18// The scoped API token the test harness mints propagates eventually-
19// consistently across Cloudflare's edge — ride out 403 blips
20// (`Forbidden`, declared in the distilled error union) on the test's
21// own out-of-band verification calls.
22const getNetwork = (accountId: string, networkId: string) =>
23 zeroTrust.getDeviceNetwork({ accountId, networkId }).pipe(
24 Effect.retry({
25 while: (e) => e._tag === "Forbidden",
26 schedule: Schedule.exponential("500 millis"),
27 times: 8,
28 }),
29 );
30
31const expectGone = (accountId: string, networkId: string) =>
32 getNetwork(accountId, networkId).pipe(
33 Effect.flatMap((n) =>
34 // The managed-networks GET returns a nullable result rather than a
35 // 404 once the network is deleted — treat both shapes as gone.
36 n.networkId == null
37 ? Effect.void
38 : Effect.fail({ _tag: "NetworkNotDeleted" } as const),
39 ),
40 Effect.catchTag("DeviceNetworkNotFound", () => Effect.void),
41 Effect.retry({
42 while: (e) => e._tag === "NetworkNotDeleted",
43 schedule: Schedule.max([
44 Schedule.exponential("500 millis"),
45 Schedule.recurs(10),
46 ]),
47 }),
48 );
49
50const SHA_A =
51 "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c" as const;
52const SHA_B =
53 "7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730" as const;
54
55test.provider(
56 "create, update in place, and delete a managed network",
57 (stack) =>
58 Effect.gen(function* () {
59 const { accountId } = yield* yield* CloudflareEnvironment;
60
61 yield* stack.destroy();
62
63 const network = yield* stack.deploy(
64 Cloudflare.Devices.DeviceManagedNetwork("Office", {
65 name: "alchemy-test-managed-network",
66 config: { tlsSockaddr: "192.0.2.1:443", sha256: SHA_A },
67 }),
68 );
69
70 expect(network.networkId).toBeTruthy();
71 expect(network.accountId).toEqual(accountId);
72 expect(network.name).toEqual("alchemy-test-managed-network");
73 expect(network.type).toEqual("tls");
74 expect(network.config.tlsSockaddr).toEqual("192.0.2.1:443");
75 expect(network.config.sha256).toEqual(SHA_A);
76
77 // Out-of-band verify against the live API.
78 const live = yield* getNetwork(accountId, network.networkId);
79 expect(live.name).toEqual("alchemy-test-managed-network");
80 expect(live.config?.tlsSockaddr).toEqual("192.0.2.1:443");
81
82 // Update config in place — same networkId.
83 const updated = yield* stack.deploy(
84 Cloudflare.Devices.DeviceManagedNetwork("Office", {
85 name: "alchemy-test-managed-network",
86 config: { tlsSockaddr: "192.0.2.2:443", sha256: SHA_B },
87 }),
88 );
89 expect(updated.networkId).toEqual(network.networkId);
90 expect(updated.config.tlsSockaddr).toEqual("192.0.2.2:443");
91 expect(updated.config.sha256).toEqual(SHA_B);
92
93 const live2 = yield* getNetwork(accountId, network.networkId);
94 expect(live2.config?.tlsSockaddr).toEqual("192.0.2.2:443");
95
96 // Redeploying identical props is a no-op (same network).
97 const noop = yield* stack.deploy(
98 Cloudflare.Devices.DeviceManagedNetwork("Office", {
99 name: "alchemy-test-managed-network",
100 config: { tlsSockaddr: "192.0.2.2:443", sha256: SHA_B },
101 }),
102 );
103 expect(noop.networkId).toEqual(network.networkId);
104
105 yield* stack.destroy();
106 yield* expectGone(accountId, network.networkId);
107 }).pipe(logLevel),
108 { tags: ["provider:cloudflare", "provider:cloudflare:devices", "live"] },
109);
110
111test.provider(
112 "list enumerates the deployed managed network",
113 (stack) =>
114 Effect.gen(function* () {
115 yield* stack.destroy();
116
117 const deployed = yield* stack.deploy(
118 Cloudflare.Devices.DeviceManagedNetwork("ListResource", {
119 name: "alchemy-test-managed-network-list",
120 config: { tlsSockaddr: "192.0.2.3:443", sha256: SHA_A },
121 }),
122 );
123
124 const provider = yield* Provider.findProvider(
125 Cloudflare.Devices.DeviceManagedNetwork,
126 );
127 const all = yield* provider.list();
128
129 expect(all.some((n) => n.networkId === deployed.networkId)).toBe(true);
130
131 yield* stack.destroy();
132 yield* expectGone(deployed.accountId, deployed.networkId);
133 }).pipe(logLevel),
134 { tags: ["provider:cloudflare", "provider:cloudflare:devices", "live"] },
135);