1import * as Cloudflare from "@/Cloudflare";
2import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as mnm from "@distilled.cloud/cloudflare/magic-network-monitoring";
6import { describe, expect } from "alchemy-test";
7import * as Data from "effect/Data";
8import * as Effect from "effect/Effect";
9import { MinimumLogLevel } from "effect/References";
10import * as Schedule from "effect/Schedule";
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 own
21// out-of-band setup calls.
22const forbiddenRetry = {
23 while: (e: { _tag: string }) => e._tag === "Forbidden",
24 schedule: Schedule.exponential("500 millis"),
25 times: 8,
26} as const;
27
28class MnmRuleNotListed extends Data.TaggedError("MnmRuleNotListed")<{}> {}
29
30// Canonical `list()` test (account-scoped collection): deploy a config +
31// rule, then resolve the provider via the typed `Provider.findProvider`
32// helper and assert the deployed rule appears in the exhaustively-
33// paginated `list()` result.
34describe.sequential(
35 "MagicNetworkMonitoring.Rule",
36 {
37 tags: [
38 "provider:cloudflare",
39 "provider:cloudflare:magicnetworkmonitoring",
40 "live",
41 ],
42 },
43 () => {
44 test.provider(
45 "list enumerates the deployed rule",
46 (stack) =>
47 Effect.gen(function* () {
48 const { accountId } = yield* yield* CloudflareEnvironment;
49
50 yield* stack.destroy();
51 // The config is an account singleton with no ownership markers — a
52 // leftover from an interrupted run would surface as `Unowned` and
53 // block this stack's create, so normalize to "absent" first.
54 yield* mnm.deleteConfig({ accountId }).pipe(
55 Effect.catchTag("MnmConfigNotFound", () => Effect.void),
56 Effect.retry(forbiddenRetry),
57 );
58
59 const { rule } = yield* stack.deploy(
60 Effect.gen(function* () {
61 const config = yield* Cloudflare.MagicNetworkMonitoring.Config(
62 "Config",
63 {
64 name: "alchemy-mnm-list-test",
65 defaultSampling: 1,
66 },
67 );
68 // Rules cannot exist without the account config — sequence the
69 // rule after the config via its accountId output.
70 const rule = yield* Cloudflare.MagicNetworkMonitoring.Rule(
71 "Rule",
72 {
73 accountId: config.accountId,
74 type: "threshold",
75 prefixes: ["10.0.0.0/24"],
76 bandwidthThreshold: 1_000_000,
77 duration: "1m",
78 },
79 );
80 return { config, rule };
81 }),
82 );
83
84 const provider = yield* Provider.findProvider(
85 Cloudflare.MagicNetworkMonitoring.Rule,
86 );
87 const all = yield* provider.list().pipe(
88 Effect.flatMap((all) =>
89 all.some((r) => r.ruleId === rule.ruleId)
90 ? Effect.succeed(all)
91 : Effect.fail(new MnmRuleNotListed()),
92 ),
93 Effect.retry({
94 while: (e) => e._tag === "MnmRuleNotListed",
95 schedule: Schedule.max([
96 Schedule.exponential("500 millis"),
97 Schedule.recurs(10),
98 ]),
99 }),
100 );
101
102 // The exhaustively-paginated result contains the deployed rule, and
103 // each element carries the full `read` Attributes shape.
104 const found = all.find((r) => r.ruleId === rule.ruleId);
105 expect(found).toBeDefined();
106 expect(found?.accountId).toEqual(accountId);
107 expect(found?.name).toEqual(rule.name);
108 expect(found?.type).toEqual("threshold");
109 expect(found?.prefixes).toEqual(["10.0.0.0/24"]);
110
111 yield* stack.destroy();
112 }).pipe(logLevel),
113 { exclusive: true },
114 );
115 },
116);