1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as acm from "@distilled.cloud/gcp/accesscontextmanager_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import { logLevel, projectContext, runLifecycle } from "./common.ts";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const waitUntilGone = (name: string) =>
12 acm.getAccessPoliciesAccessLevels({ name }).pipe(
13 Effect.as("found" as const),
14 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
15 Effect.repeat({
16 schedule: Schedule.spaced("2 seconds"),
17 until: (status) => status === "gone",
18 times: 10,
19 }),
20 );
21
22test.provider(
23 "getAccessPoliciesAccessLevels on an inaccessible level fails with Forbidden",
24 (stack) =>
25 Effect.gen(function* () {
26 yield* stack.destroy();
27
28 const error = yield* Effect.flip(
29 acm.getAccessPoliciesAccessLevels({
30 name: "accessPolicies/123456789012/accessLevels/alchemy_missing_level",
31 }),
32 );
33 expect(error._tag).toEqual("Forbidden");
34
35 yield* stack.destroy();
36 }).pipe(logLevel),
37 {
38 tags: ["provider:gcp", "provider:gcp:accesscontextmanager", "live"],
39 timeout: 90_000,
40 },
41);
42
43test.provider.skipIf(!runLifecycle)(
44 "create, update, and delete an access level",
45 (stack) =>
46 Effect.gen(function* () {
47 yield* stack.destroy();
48
49 const ctx = yield* projectContext();
50 const scopes =
51 ctx.projectNumber.length > 0
52 ? [`projects/${ctx.projectNumber}`]
53 : undefined;
54
55 const created = yield* stack.deploy(
56 Effect.gen(function* () {
57 const policy = yield* GCP.AccessContextManager.AccessPolicy(
58 "LevelPolicy",
59 {
60 title: "access level policy",
61 scopes,
62 },
63 );
64 const level =
65 yield* GCP.AccessContextManager.AccessPoliciesAccessLevel(
66 "CorpUsers",
67 {
68 policy: policy.name,
69 title: "corp users",
70 description: "us region",
71 basic: { conditions: [{ regions: ["US"] }] },
72 },
73 );
74 return { policy, level };
75 }),
76 );
77
78 expect(created.level.name).toContain("/accessLevels/");
79 expect(created.level.policy).toEqual(created.policy.name);
80 expect(created.level.title).toEqual("corp users");
81 expect(created.level.description).toEqual("us region");
82 expect(created.level.basic?.conditions?.[0]?.regions).toContain("US");
83
84 const fetched = yield* acm.getAccessPoliciesAccessLevels({
85 name: created.level.name,
86 });
87 expect(fetched.name).toEqual(created.level.name);
88 expect(fetched.description).toContain("alchemy-id=");
89 expect(fetched.description).toContain("us region");
90
91 const updated = yield* stack.deploy(
92 Effect.gen(function* () {
93 const policy = yield* GCP.AccessContextManager.AccessPolicy(
94 "LevelPolicy",
95 {
96 title: "access level policy",
97 scopes,
98 },
99 );
100 const level =
101 yield* GCP.AccessContextManager.AccessPoliciesAccessLevel(
102 "CorpUsers",
103 {
104 policy: policy.name,
105 accessLevelId: created.level.accessLevelId,
106 title: "corp users prod",
107 description: "ca region",
108 basic: { conditions: [{ regions: ["CA"] }] },
109 },
110 );
111 return { policy, level };
112 }),
113 );
114
115 expect(updated.level.name).toEqual(created.level.name);
116 expect(updated.level.title).toEqual("corp users prod");
117 expect(updated.level.description).toEqual("ca region");
118 expect(updated.level.basic?.conditions?.[0]?.regions).toContain("CA");
119
120 const fetchedUpdate = yield* acm.getAccessPoliciesAccessLevels({
121 name: updated.level.name,
122 });
123 expect(fetchedUpdate.description).toContain("ca region");
124
125 yield* stack.destroy();
126
127 const gone = yield* waitUntilGone(created.level.name);
128 expect(gone).toEqual("gone");
129 }).pipe(logLevel),
130 {
131 tags: ["provider:gcp", "provider:gcp:accesscontextmanager", "live"],
132 timeout: 120_000,
133 },
134);