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";
9
10const { test } = Test.make({
11 providers: Cloudflare.providers(),
12 state: Cloudflare.state(),
13});
14
15const logLevel = Effect.provideService(
16 MinimumLogLevel,
17 process.env.DEBUG ? "Debug" : "Info",
18);
19
20test.provider(
21 "create, update rules, and delete group",
22 (stack) =>
23 Effect.gen(function* () {
24 const { accountId } = yield* yield* CloudflareEnvironment;
25
26 yield* stack.destroy();
27
28 const group = yield* stack.deploy(
29 Effect.gen(function* () {
30 return yield* Cloudflare.Access.Group("BasicGroup", {
31 include: [{ emailDomain: { domain: "example.com" } }],
32 });
33 }),
34 );
35
36 expect(group.groupId).toBeDefined();
37 expect(group.accountId).toEqual(accountId);
38 expect(group.name).toBeDefined();
39
40 const actual = yield* zeroTrust.getAccessGroupForAccount({
41 accountId,
42 groupId: group.groupId,
43 });
44 expect(actual.id).toEqual(group.groupId);
45 expect(actual.name).toEqual(group.name);
46 expect(actual.include?.length).toEqual(1);
47
48 // Update — add an include arm plus exclude/require rule groups. The
49 // group must converge in place (same id).
50 const updated = yield* stack.deploy(
51 Effect.gen(function* () {
52 return yield* Cloudflare.Access.Group("BasicGroup", {
53 include: [
54 { emailDomain: { domain: "example.com" } },
55 { geo: { countryCode: "US" } },
56 ],
57 exclude: [{ email: { email: "intern@example.com" } }],
58 require: [{ emailDomain: { domain: "example.com" } }],
59 });
60 }),
61 );
62 expect(updated.groupId).toEqual(group.groupId);
63
64 const afterUpdate = yield* zeroTrust.getAccessGroupForAccount({
65 accountId,
66 groupId: group.groupId,
67 });
68 expect(afterUpdate.include?.length).toEqual(2);
69 expect(afterUpdate.exclude?.length).toEqual(1);
70 expect(afterUpdate.require?.length).toEqual(1);
71
72 yield* stack.destroy();
73
74 const afterDestroy = yield* zeroTrust
75 .getAccessGroupForAccount({ accountId, groupId: group.groupId })
76 .pipe(
77 Effect.catchTag("AccessGroupNotFound", () =>
78 Effect.succeed(undefined),
79 ),
80 );
81 expect(afterDestroy?.id ?? undefined).toBeUndefined();
82 }).pipe(logLevel),
83 { tags: ["provider:cloudflare", "provider:cloudflare:access", "live"] },
84);
85
86test.provider(
87 "rename updates the group in place",
88 (stack) =>
89 Effect.gen(function* () {
90 const { accountId } = yield* yield* CloudflareEnvironment;
91
92 yield* stack.destroy();
93
94 const group = yield* stack.deploy(
95 Effect.gen(function* () {
96 return yield* Cloudflare.Access.Group("RenameGroup", {
97 name: "alchemy-test-access-group-rename-a",
98 include: [{ everyone: {} }],
99 });
100 }),
101 );
102 expect(group.name).toEqual("alchemy-test-access-group-rename-a");
103
104 const renamed = yield* stack.deploy(
105 Effect.gen(function* () {
106 return yield* Cloudflare.Access.Group("RenameGroup", {
107 name: "alchemy-test-access-group-rename-b",
108 include: [{ everyone: {} }],
109 });
110 }),
111 );
112 expect(renamed.groupId).toEqual(group.groupId);
113 expect(renamed.name).toEqual("alchemy-test-access-group-rename-b");
114
115 const actual = yield* zeroTrust.getAccessGroupForAccount({
116 accountId,
117 groupId: group.groupId,
118 });
119 expect(actual.name).toEqual("alchemy-test-access-group-rename-b");
120
121 yield* stack.destroy();
122 }).pipe(logLevel),
123 { tags: ["provider:cloudflare", "provider:cloudflare:access", "live"] },
124);
125
126test.provider(
127 "list enumerates the deployed access group",
128 (stack) =>
129 Effect.gen(function* () {
130 yield* stack.destroy();
131
132 const group = yield* stack.deploy(
133 Effect.gen(function* () {
134 return yield* Cloudflare.Access.Group("ListGroup", {
135 include: [{ emailDomain: { domain: "example.com" } }],
136 });
137 }),
138 );
139
140 const provider = yield* Provider.findProvider(Cloudflare.Access.Group);
141 const all = yield* provider.list();
142
143 const found = all.find((g) => g.groupId === group.groupId);
144 expect(found).toBeDefined();
145 expect(found?.accountId).toEqual(group.accountId);
146 expect(found?.name).toEqual(group.name);
147
148 yield* stack.destroy();
149 }).pipe(logLevel),
150 { tags: ["provider:cloudflare", "provider:cloudflare:access", "live"] },
151);
152
153test.provider(
154 "group can be referenced from an access policy",
155 (stack) =>
156 Effect.gen(function* () {
157 const { accountId } = yield* yield* CloudflareEnvironment;
158
159 yield* stack.destroy();
160
161 const { group, policy } = yield* stack.deploy(
162 Effect.gen(function* () {
163 const group = yield* Cloudflare.Access.Group("PolicyGroup", {
164 include: [{ emailDomain: { domain: "example.com" } }],
165 });
166 const policy = yield* Cloudflare.Access.Policy("GroupPolicy", {
167 decision: "allow",
168 include: [{ group: { id: group.groupId } }],
169 });
170 return { group, policy };
171 }),
172 );
173
174 const actual = yield* zeroTrust.getAccessPolicy({
175 accountId,
176 policyId: policy.policyId,
177 });
178 const includes = (actual.include ?? []) as Array<{
179 group?: { id: string };
180 }>;
181 expect(includes[0]?.group?.id).toEqual(group.groupId);
182
183 yield* stack.destroy();
184 }).pipe(logLevel),
185 { tags: ["provider:cloudflare", "provider:cloudflare:access", "live"] },
186);