1import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
2import * as Cloudflare from "@/Cloudflare/index.ts";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as alerting from "@distilled.cloud/cloudflare/alerting";
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
18const EMAIL = "test@alchemy.run";
19
20test.provider(
21 "create, update, delete notification policy",
22 (stack) =>
23 Effect.gen(function* () {
24 const { accountId } = yield* yield* CloudflareEnvironment;
25
26 yield* stack.destroy();
27
28 const policy = yield* stack.deploy(
29 Cloudflare.Alerting.NotificationPolicy("SslPolicy", {
30 alertType: "universal_ssl_event_type",
31 mechanisms: { email: [{ id: EMAIL }] },
32 }),
33 );
34
35 expect(policy.policyId).toBeDefined();
36 expect(policy.accountId).toEqual(accountId);
37 expect(policy.alertType).toEqual("universal_ssl_event_type");
38 expect(policy.enabled).toBe(true);
39
40 // Verify out-of-band via the API.
41 const actual = yield* alerting.getPolicy({
42 accountId,
43 policyId: policy.policyId,
44 });
45 expect(actual.name).toEqual(policy.name);
46 expect(actual.alertType).toEqual("universal_ssl_event_type");
47 expect(actual.mechanisms?.email?.[0]?.id).toEqual(EMAIL);
48
49 // Update mutable props in place — same id.
50 const updated = yield* stack.deploy(
51 Cloudflare.Alerting.NotificationPolicy("SslPolicy", {
52 alertType: "universal_ssl_event_type",
53 enabled: false,
54 description: "paused during migration",
55 mechanisms: { email: [{ id: EMAIL }] },
56 }),
57 );
58 expect(updated.policyId).toEqual(policy.policyId);
59 expect(updated.enabled).toBe(false);
60
61 const afterUpdate = yield* alerting.getPolicy({
62 accountId,
63 policyId: policy.policyId,
64 });
65 expect(afterUpdate.enabled).toBe(false);
66 expect(afterUpdate.description).toEqual("paused during migration");
67
68 yield* stack.destroy();
69
70 yield* waitForPolicyDeleted(accountId, policy.policyId);
71 }).pipe(logLevel),
72 { tags: ["provider:cloudflare", "provider:cloudflare:alerting", "live"] },
73);
74
75test.provider(
76 "replaces policy when alertType changes",
77 (stack) =>
78 Effect.gen(function* () {
79 const { accountId } = yield* yield* CloudflareEnvironment;
80
81 yield* stack.destroy();
82
83 const policy = yield* stack.deploy(
84 Cloudflare.Alerting.NotificationPolicy("ReplacePolicy", {
85 alertType: "universal_ssl_event_type",
86 mechanisms: { email: [{ id: EMAIL }] },
87 }),
88 );
89 expect(policy.alertType).toEqual("universal_ssl_event_type");
90
91 const replaced = yield* stack.deploy(
92 Cloudflare.Alerting.NotificationPolicy("ReplacePolicy", {
93 alertType: "incident_alert",
94 mechanisms: { email: [{ id: EMAIL }] },
95 }),
96 );
97 expect(replaced.alertType).toEqual("incident_alert");
98 expect(replaced.policyId).not.toEqual(policy.policyId);
99
100 // The replaced (old) policy must be gone.
101 yield* waitForPolicyDeleted(accountId, policy.policyId);
102
103 const actual = yield* alerting.getPolicy({
104 accountId,
105 policyId: replaced.policyId,
106 });
107 expect(actual.alertType).toEqual("incident_alert");
108
109 yield* stack.destroy();
110
111 yield* waitForPolicyDeleted(accountId, replaced.policyId);
112 }).pipe(logLevel),
113 { tags: ["provider:cloudflare", "provider:cloudflare:alerting", "live"] },
114);
115
116// Canonical `list()` test (account-scoped collection): deploy a real policy,
117// then assert it appears in the exhaustively-paginated account-wide result.
118test.provider(
119 "list enumerates the deployed notification policy",
120 (stack) =>
121 Effect.gen(function* () {
122 yield* stack.destroy();
123
124 const deployed = yield* stack.deploy(
125 Cloudflare.Alerting.NotificationPolicy("ListPolicy", {
126 alertType: "universal_ssl_event_type",
127 mechanisms: { email: [{ id: EMAIL }] },
128 }),
129 );
130
131 const provider = yield* Provider.findProvider(
132 Cloudflare.Alerting.NotificationPolicy,
133 );
134 const all = yield* provider.list();
135
136 expect(all.some((p) => p.policyId === deployed.policyId)).toBe(true);
137
138 yield* stack.destroy();
139 }).pipe(logLevel),
140 { tags: ["provider:cloudflare", "provider:cloudflare:alerting", "live"] },
141);
142
143const waitForPolicyDeleted = (accountId: string, policyId: string) =>
144 alerting.getPolicy({ accountId, policyId }).pipe(
145 Effect.flatMap(() => Effect.fail({ _tag: "PolicyNotDeleted" } as const)),
146 Effect.catchTag("PolicyNotFound", () => Effect.void),
147 Effect.retry({
148 while: (e) => e._tag === "PolicyNotDeleted",
149 schedule: Schedule.max([
150 Schedule.exponential("500 millis"),
151 Schedule.recurs(10),
152 ]),
153 }),
154 );